9PK !>+]chrome/res/phonenumberutils/PhoneNumber.jsmPK ! ensureCssLoaded(window); FormAutofillStatus.init(); ChromeUtils.registerWindowActor("FormAutofill", { parent: { moduleURI: "resource://formautofill/FormAutofillParent.jsm", }, child: { moduleURI: "resource://formautofill/FormAutofillChild.jsm", events: { focusin: {}, DOMFormBeforeSubmit: {}, }, }, allFrames: true, }); } onShutdown(isAppShutdown) { if (isAppShutdown) { return; } resProto.setSubstitution(RESOURCE_HOST, null); this.chromeHandle.destruct(); this.chromeHandle = null; if (this.autofillManifest) { Components.manager.removeBootstrappedManifestLocation( this.autofillManifest ); } ChromeUtils.unregisterWindowActor("FormAutofill"); AutoCompleteParent.removePopupStateListener(ensureCssLoaded); FormAutofillParent.removeMessageObserver(this); for (let win of Services.wm.getEnumerator("navigator:browser")) { let cachedStyleSheets = CACHED_STYLESHEETS.get(win); if (!cachedStyleSheets) { continue; } while (cachedStyleSheets.length !== 0) { cachedStyleSheets.pop().remove(); } } } }; PK !} validFields * A list containing non-metadata field names. * @param {Array.} validComputedFields * A list containing computed field names. * @param {number} schemaVersion * The schema version for the new record. */ constructor( store, collectionName, validFields, validComputedFields, schemaVersion ) { FormAutofill.defineLazyLogGetter(this, "AutofillRecords:" + collectionName); this.VALID_FIELDS = validFields; this.VALID_COMPUTED_FIELDS = validComputedFields; this._store = store; this._collectionName = collectionName; this._schemaVersion = schemaVersion; this._initializePromise = Promise.all( this._data.map(async (record, index) => this._migrateRecord(record, index) ) ).then(hasChangesArr => { let dataHasChanges = hasChangesArr.includes(true); if (dataHasChanges) { this._store.saveSoon(); } }); } /** * Gets the schema version number. * * @returns {number} * The current schema version number. */ get version() { return this._schemaVersion; } /** * Gets the data of this collection. * * @returns {array} * The data object. */ get _data() { return this._store.data[this._collectionName]; } // Ensures that we don't try to apply synced records with newer schema // versions. This is a temporary measure to ensure we don't accidentally // bump the schema version without a syncing strategy in place (bug 1377204). _ensureMatchingVersion(record) { if (record.version != this.version) { throw new Error( `Got unknown record version ${record.version}; want ${this.version}` ); } } /** * Initialize the records in the collection, resolves when the migration completes. * @returns {Promise} */ initialize() { return this._initializePromise; } /** * Adds a new record. * * @param {Object} record * The new record for saving. * @param {boolean} [options.sourceSync = false] * Did sync generate this addition? * @returns {Promise} * The GUID of the newly added item.. */ async add(record, { sourceSync = false } = {}) { this.log.debug("add:", record); let recordToSave = this._clone(record); if (sourceSync) { // Remove tombstones for incoming items that were changed on another // device. Local deletions always lose to avoid data loss. let index = this._findIndexByGUID(recordToSave.guid, { includeDeleted: true, }); if (index > -1) { let existing = this._data[index]; if (existing.deleted) { this._data.splice(index, 1); } else { throw new Error(`Record ${recordToSave.guid} already exists`); } } } else if (!recordToSave.deleted) { this._normalizeRecord(recordToSave); // _normalizeRecord shouldn't do any validation (throw) because in the // `update` case it is called with partial records whereas // `_validateFields` is called with a complete one. this._validateFields(recordToSave); recordToSave.guid = this._generateGUID(); recordToSave.version = this.version; // Metadata let now = Date.now(); recordToSave.timeCreated = now; recordToSave.timeLastModified = now; recordToSave.timeLastUsed = 0; recordToSave.timesUsed = 0; } return this._saveRecord(recordToSave, { sourceSync }); } async _saveRecord(record, { sourceSync = false } = {}) { if (!record.guid) { throw new Error("Record missing GUID"); } let recordToSave; if (record.deleted) { if (this._findByGUID(record.guid, { includeDeleted: true })) { throw new Error("a record with this GUID already exists"); } recordToSave = { guid: record.guid, timeLastModified: record.timeLastModified || Date.now(), deleted: true, }; } else { this._ensureMatchingVersion(record); recordToSave = record; await this.computeFields(recordToSave); } if (sourceSync) { let sync = this._getSyncMetaData(recordToSave, true); sync.changeCounter = 0; } this._data.push(recordToSave); this.updateUseCountTelemetry(); this._store.saveSoon(); Services.obs.notifyObservers( { wrappedJSObject: { sourceSync, guid: record.guid, collectionName: this._collectionName, }, }, "formautofill-storage-changed", "add" ); return recordToSave.guid; } _generateGUID() { let guid; while (!guid || this._findByGUID(guid)) { guid = gUUIDGenerator .generateUUID() .toString() .replace(/[{}-]/g, "") .substring(0, 12); } return guid; } /** * Update the specified record. * * @param {string} guid * Indicates which record to update. * @param {Object} record * The new record used to overwrite the old one. * @param {Promise} [preserveOldProperties = false] * Preserve old record's properties if they don't exist in new record. */ async update(guid, record, preserveOldProperties = false) { this.log.debug("update:", guid, record); let recordFoundIndex = this._findIndexByGUID(guid); if (recordFoundIndex == -1) { throw new Error("No matching record."); } // Clone the record before modifying it to avoid exposing incomplete changes. let recordFound = this._clone(this._data[recordFoundIndex]); await this._stripComputedFields(recordFound); let recordToUpdate = this._clone(record); this._normalizeRecord(recordToUpdate, true); let hasValidField = false; for (let field of this.VALID_FIELDS) { let oldValue = recordFound[field]; let newValue = recordToUpdate[field]; // Resume the old field value in the perserve case if (preserveOldProperties && newValue === undefined) { newValue = oldValue; } if (newValue === undefined || newValue === "") { delete recordFound[field]; } else { hasValidField = true; recordFound[field] = newValue; } this._maybeStoreLastSyncedField(recordFound, field, oldValue); } if (!hasValidField) { throw new Error("Record contains no valid field."); } // _normalizeRecord above is called with the `record` argument provided to // `update` which may not contain all resulting fields when // `preserveOldProperties` is used. This means we need to validate for // missing fields after we compose the record (`recordFound`) with the stored // record like we do in the loop above. this._validateFields(recordFound); recordFound.timeLastModified = Date.now(); let syncMetadata = this._getSyncMetaData(recordFound); if (syncMetadata) { syncMetadata.changeCounter += 1; } await this.computeFields(recordFound); this._data[recordFoundIndex] = recordFound; this._store.saveSoon(); Services.obs.notifyObservers( { wrappedJSObject: { guid, collectionName: this._collectionName, }, }, "formautofill-storage-changed", "update" ); } /** * Notifies the storage of the use of the specified record, so we can update * the metadata accordingly. This does not bump the Sync change counter, since * we don't sync `timesUsed` or `timeLastUsed`. * * @param {string} guid * Indicates which record to be notified. */ notifyUsed(guid) { this.log.debug("notifyUsed:", guid); let recordFound = this._findByGUID(guid); if (!recordFound) { throw new Error("No matching record."); } recordFound.timesUsed++; recordFound.timeLastUsed = Date.now(); this.updateUseCountTelemetry(); this._store.saveSoon(); Services.obs.notifyObservers( { wrappedJSObject: { guid, collectionName: this._collectionName, }, }, "formautofill-storage-changed", "notifyUsed" ); } updateUseCountTelemetry() {} /** * Removes the specified record. No error occurs if the record isn't found. * * @param {string} guid * Indicates which record to remove. * @param {boolean} [options.sourceSync = false] * Did Sync generate this removal? */ remove(guid, { sourceSync = false } = {}) { this.log.debug("remove:", guid); if (sourceSync) { this._removeSyncedRecord(guid); } else { let index = this._findIndexByGUID(guid, { includeDeleted: false }); if (index == -1) { this.log.warn("attempting to remove non-existing entry", guid); return; } let existing = this._data[index]; if (existing.deleted) { return; // already a tombstone - don't touch it. } let existingSync = this._getSyncMetaData(existing); if (existingSync) { // existing sync metadata means it has been synced. This means we must // leave a tombstone behind. this._data[index] = { guid, timeLastModified: Date.now(), deleted: true, _sync: existingSync, }; existingSync.changeCounter++; } else { // If there's no sync meta-data, this record has never been synced, so // we can delete it. this._data.splice(index, 1); } } this.updateUseCountTelemetry(); this._store.saveSoon(); Services.obs.notifyObservers( { wrappedJSObject: { sourceSync, guid, collectionName: this._collectionName, }, }, "formautofill-storage-changed", "remove" ); } /** * Returns the record with the specified GUID. * * @param {string} guid * Indicates which record to retrieve. * @param {boolean} [options.rawData = false] * Returns a raw record without modifications and the computed fields * (this includes private fields) * @returns {Promise} * A clone of the record. */ async get(guid, { rawData = false } = {}) { this.log.debug("get:", guid, rawData); let recordFound = this._findByGUID(guid); if (!recordFound) { return null; } // The record is cloned to avoid accidental modifications from outside. let clonedRecord = this._cloneAndCleanUp(recordFound); if (rawData) { await this._stripComputedFields(clonedRecord); } else { this._recordReadProcessor(clonedRecord); } return clonedRecord; } /** * Returns all records. * * @param {boolean} [options.rawData = false] * Returns raw records without modifications and the computed fields. * @param {boolean} [options.includeDeleted = false] * Also return any tombstone records. * @returns {Promise>} * An array containing clones of all records. */ async getAll({ rawData = false, includeDeleted = false } = {}) { this.log.debug("getAll", rawData, includeDeleted); let records = this._data.filter(r => !r.deleted || includeDeleted); // Records are cloned to avoid accidental modifications from outside. let clonedRecords = records.map(r => this._cloneAndCleanUp(r)); await Promise.all( clonedRecords.map(async record => { if (rawData) { await this._stripComputedFields(record); } else { this._recordReadProcessor(record); } }) ); return clonedRecords; } /** * Return all saved field names in the collection. This method * has to be sync because its caller _updateSavedFieldNames() needs * to dispatch content message synchronously. * * @returns {Set} Set containing saved field names. */ getSavedFieldNames() { this.log.debug("getSavedFieldNames"); let records = this._data.filter(r => !r.deleted); records .map(record => this._cloneAndCleanUp(record)) .forEach(record => this._recordReadProcessor(record)); let fieldNames = new Set(); for (let record of records) { for (let fieldName of Object.keys(record)) { if (INTERNAL_FIELDS.includes(fieldName) || !record[fieldName]) { continue; } fieldNames.add(fieldName); } } return fieldNames; } /** * Functions intended to be used in the support of Sync. */ /** * Stores a hash of the last synced value for a field in a locally updated * record. We use this value to rebuild the shared parent, or base, when * reconciling incoming records that may have changed on another device. * * Storing the hash of the values that we last wrote to the Sync server lets * us determine if a remote change conflicts with a local change. If the * hashes for the base, current local value, and remote value all differ, we * have a conflict. * * These fields are not themselves synced, and will be removed locally as * soon as we have successfully written the record to the Sync server - so * it is expected they will not remain for long, as changes which cause a * last synced field to be written will itself cause a sync. * * We also skip this for updates made by Sync, for internal fields, for * records that haven't been uploaded yet, and for fields which have already * been changed since the last sync. * * @param {Object} record * The updated local record. * @param {string} field * The field name. * @param {string} lastSyncedValue * The last synced field value. */ _maybeStoreLastSyncedField(record, field, lastSyncedValue) { let sync = this._getSyncMetaData(record); if (!sync) { // The record hasn't been uploaded yet, so we can't end up with merge // conflicts. return; } let alreadyChanged = field in sync.lastSyncedFields; if (alreadyChanged) { // This field was already changed multiple times since the last sync. return; } let newValue = record[field]; if (lastSyncedValue != newValue) { sync.lastSyncedFields[field] = sha512(lastSyncedValue); } } /** * Attempts a three-way merge between a changed local record, an incoming * remote record, and the shared parent that we synthesize from the last * synced fields - see _maybeStoreLastSyncedField. * * @param {Object} strippedLocalRecord * The changed local record, currently in storage. Computed fields * are stripped. * @param {Object} remoteRecord * The remote record. * @returns {Object|null} * The merged record, or `null` if there are conflicts and the * records can't be merged. */ _mergeSyncedRecords(strippedLocalRecord, remoteRecord) { let sync = this._getSyncMetaData(strippedLocalRecord, true); // Copy all internal fields from the remote record. We'll update their // values in `_replaceRecordAt`. let mergedRecord = {}; for (let field of INTERNAL_FIELDS) { if (remoteRecord[field] != null) { mergedRecord[field] = remoteRecord[field]; } } for (let field of this.VALID_FIELDS) { let isLocalSame = false; let isRemoteSame = false; if (field in sync.lastSyncedFields) { // If the field has changed since the last sync, compare hashes to // determine if the local and remote values are different. Hashing is // expensive, but we don't expect this to happen frequently. let lastSyncedValue = sync.lastSyncedFields[field]; isLocalSame = lastSyncedValue == sha512(strippedLocalRecord[field]); isRemoteSame = lastSyncedValue == sha512(remoteRecord[field]); } else { // Otherwise, if the field hasn't changed since the last sync, we know // it's the same locally. isLocalSame = true; isRemoteSame = strippedLocalRecord[field] == remoteRecord[field]; } let value; if (isLocalSame && isRemoteSame) { // Local and remote are the same; doesn't matter which one we pick. value = strippedLocalRecord[field]; } else if (isLocalSame && !isRemoteSame) { value = remoteRecord[field]; } else if (!isLocalSame && isRemoteSame) { // We don't need to bump the change counter when taking the local // change, because the counter must already be > 0 if we're attempting // a three-way merge. value = strippedLocalRecord[field]; } else if (strippedLocalRecord[field] == remoteRecord[field]) { // Shared parent doesn't match either local or remote, but the values // are identical, so there's no conflict. value = strippedLocalRecord[field]; } else { // Both local and remote changed to different values. We'll need to fork // the local record to resolve the conflict. return null; } if (value != null) { mergedRecord[field] = value; } } return mergedRecord; } /** * Replaces a local record with a remote or merged record, copying internal * fields and Sync metadata. * * @param {number} index * @param {Object} remoteRecord * @param {Promise} [options.keepSyncMetadata = false] * Should we copy Sync metadata? This is true if `remoteRecord` is a * merged record with local changes that we need to upload. Passing * `keepSyncMetadata` retains the record's change counter and * last synced fields, so that we don't clobber the local change if * the sync is interrupted after the record is merged, but before * it's uploaded. */ async _replaceRecordAt( index, remoteRecord, { keepSyncMetadata = false } = {} ) { let localRecord = this._data[index]; let newRecord = this._clone(remoteRecord); await this._stripComputedFields(newRecord); this._data[index] = newRecord; if (keepSyncMetadata) { // It's safe to move the Sync metadata from the old record to the new // record, since we always clone records when we return them, and we // never hand out references to the metadata object via public methods. newRecord._sync = localRecord._sync; } else { // As a side effect, `_getSyncMetaData` marks the record as syncing if the // existing `localRecord` is a dupe of `remoteRecord`, and we're replacing // local with remote. let sync = this._getSyncMetaData(newRecord, true); sync.changeCounter = 0; } if ( !newRecord.timeCreated || localRecord.timeCreated < newRecord.timeCreated ) { newRecord.timeCreated = localRecord.timeCreated; } if ( !newRecord.timeLastModified || localRecord.timeLastModified > newRecord.timeLastModified ) { newRecord.timeLastModified = localRecord.timeLastModified; } // Copy local-only fields from the existing local record. for (let field of ["timeLastUsed", "timesUsed"]) { if (localRecord[field] != null) { newRecord[field] = localRecord[field]; } } await this.computeFields(newRecord); } /** * Clones a local record, giving the clone a new GUID and Sync metadata. The * original record remains unchanged in storage. * * @param {Object} strippedLocalRecord * The local record. Computed fields are stripped. * @returns {string} * A clone of the local record with a new GUID. */ async _forkLocalRecord(strippedLocalRecord) { let forkedLocalRecord = this._cloneAndCleanUp(strippedLocalRecord); forkedLocalRecord.guid = this._generateGUID(); // Give the record fresh Sync metadata and bump its change counter as a // side effect. This also excludes the forked record from de-duping on the // next sync, if the current sync is interrupted before the record can be // uploaded. this._getSyncMetaData(forkedLocalRecord, true); await this.computeFields(forkedLocalRecord); this._data.push(forkedLocalRecord); return forkedLocalRecord; } /** * Reconciles an incoming remote record into the matching local record. This * method is only used by Sync; other callers should use `merge`. * * @param {Object} remoteRecord * The incoming record. `remoteRecord` must not be a tombstone, and * must have a matching local record with the same GUID. Use * `add` to insert remote records that don't exist locally, and * `remove` to apply remote tombstones. * @returns {Promise} * A `{forkedGUID}` tuple. `forkedGUID` is `null` if the merge * succeeded without conflicts, or a new GUID referencing the * existing locally modified record if the conflicts could not be * resolved. */ async reconcile(remoteRecord) { this._ensureMatchingVersion(remoteRecord); if (remoteRecord.deleted) { throw new Error(`Can't reconcile tombstone ${remoteRecord.guid}`); } let localIndex = this._findIndexByGUID(remoteRecord.guid); if (localIndex < 0) { throw new Error(`Record ${remoteRecord.guid} not found`); } let localRecord = this._data[localIndex]; let sync = this._getSyncMetaData(localRecord, true); let forkedGUID = null; if (sync.changeCounter === 0) { // Local not modified. Replace local with remote. await this._replaceRecordAt(localIndex, remoteRecord, { keepSyncMetadata: false, }); } else { let strippedLocalRecord = this._clone(localRecord); await this._stripComputedFields(strippedLocalRecord); let mergedRecord = this._mergeSyncedRecords( strippedLocalRecord, remoteRecord ); if (mergedRecord) { // Local and remote modified, but we were able to merge. Replace the // local record with the merged record. await this._replaceRecordAt(localIndex, mergedRecord, { keepSyncMetadata: true, }); } else { // Merge conflict. Fork the local record, then replace the original // with the merged record. let forkedLocalRecord = await this._forkLocalRecord( strippedLocalRecord ); forkedGUID = forkedLocalRecord.guid; await this._replaceRecordAt(localIndex, remoteRecord, { keepSyncMetadata: false, }); } } this._store.saveSoon(); Services.obs.notifyObservers( { wrappedJSObject: { sourceSync: true, guid: remoteRecord.guid, forkedGUID, collectionName: this._collectionName, }, }, "formautofill-storage-changed", "reconcile" ); return { forkedGUID }; } _removeSyncedRecord(guid) { let index = this._findIndexByGUID(guid, { includeDeleted: true }); if (index == -1) { // Removing a record we don't know about. It may have been synced and // removed by another device before we saw it. Store the tombstone in // case the server is later wiped and we need to reupload everything. let tombstone = { guid, timeLastModified: Date.now(), deleted: true, }; let sync = this._getSyncMetaData(tombstone, true); sync.changeCounter = 0; this._data.push(tombstone); return; } let existing = this._data[index]; let sync = this._getSyncMetaData(existing, true); if (sync.changeCounter > 0) { // Deleting a record with unsynced local changes. To avoid potential // data loss, we ignore the deletion in favor of the changed record. this.log.info( "Ignoring deletion for record with local changes", existing ); return; } if (existing.deleted) { this.log.info("Ignoring deletion for tombstone", existing); return; } // Removing a record that's not changed locally, and that's not already // deleted. Replace the record with a synced tombstone. this._data[index] = { guid, timeLastModified: Date.now(), deleted: true, _sync: sync, }; } /** * Provide an object that describes the changes to sync. * * This is called at the start of the sync process to determine what needs * to be updated on the server. As the server is updated, sync will update * entries in the returned object, and when sync is complete it will pass * the object to pushSyncChanges, which will apply the changes to the store. * * @returns {object} * An object describing the changes to sync. */ pullSyncChanges() { let changes = {}; let profiles = this._data; for (let profile of profiles) { let sync = this._getSyncMetaData(profile, true); if (sync.changeCounter < 1) { if (sync.changeCounter != 0) { this.log.error("negative change counter", profile); } continue; } changes[profile.guid] = { profile, counter: sync.changeCounter, modified: profile.timeLastModified, synced: false, }; } this._store.saveSoon(); return changes; } /** * Apply the metadata changes made by Sync. * * This is called with metadata about what was synced - see pullSyncChanges. * * @param {object} changes * The possibly modified object obtained via pullSyncChanges. */ pushSyncChanges(changes) { for (let [guid, { counter, synced }] of Object.entries(changes)) { if (!synced) { continue; } let recordFound = this._findByGUID(guid, { includeDeleted: true }); if (!recordFound) { this.log.warn("No profile found to persist changes for guid " + guid); continue; } let sync = this._getSyncMetaData(recordFound, true); sync.changeCounter = Math.max(0, sync.changeCounter - counter); if (sync.changeCounter === 0) { // Clear the shared parent fields once we've uploaded all pending // changes, since the server now matches what we have locally. sync.lastSyncedFields = {}; } } this._store.saveSoon(); } /** * Reset all sync metadata for all items. * * This is called when Sync is disconnected from this device. All sync * metadata for all items is removed. */ resetSync() { for (let record of this._data) { delete record._sync; } // XXX - we should probably also delete all tombstones? this.log.info("All sync metadata was reset"); } /** * Changes the GUID of an item. This should be called only by Sync. There * must be an existing record with oldID and it must never have been synced * or an error will be thrown. There must be no existing record with newID. * * No tombstone will be created for the old GUID - we check it hasn't * been synced, so no tombstone is necessary. * * @param {string} oldID * GUID of the existing item to change the GUID of. * @param {string} newID * The new GUID for the item. */ changeGUID(oldID, newID) { this.log.debug("changeGUID: ", oldID, newID); if (oldID == newID) { throw new Error("changeGUID: old and new IDs are the same"); } if (this._findIndexByGUID(newID) >= 0) { throw new Error("changeGUID: record with destination id exists already"); } let index = this._findIndexByGUID(oldID); let profile = this._data[index]; if (!profile) { throw new Error("changeGUID: no source record"); } if (this._getSyncMetaData(profile)) { throw new Error("changeGUID: existing record has already been synced"); } profile.guid = newID; this._store.saveSoon(); } // Used to get, and optionally create, sync metadata. Brand new records will // *not* have sync meta-data - it will be created when they are first // synced. _getSyncMetaData(record, forceCreate = false) { if (!record._sync && forceCreate) { // create default metadata and indicate we need to save. record._sync = { changeCounter: 1, lastSyncedFields: {}, }; this._store.saveSoon(); } return record._sync; } /** * Finds a local record with matching common fields and a different GUID. * Sync uses this method to find and update unsynced local records with * fields that match incoming remote records. This avoids creating * duplicate profiles with the same information. * * @param {Object} remoteRecord * The remote record. * @returns {Promise} * The GUID of the matching local record, or `null` if no records * match. */ async findDuplicateGUID(remoteRecord) { if (!remoteRecord.guid) { throw new Error("Record missing GUID"); } this._ensureMatchingVersion(remoteRecord); if (remoteRecord.deleted) { // Tombstones don't carry enough info to de-dupe, and we should have // handled them separately when applying the record. throw new Error("Tombstones can't have duplicates"); } let localRecords = this._data; for (let localRecord of localRecords) { if (localRecord.deleted) { continue; } if (localRecord.guid == remoteRecord.guid) { throw new Error(`Record ${remoteRecord.guid} already exists`); } if (this._getSyncMetaData(localRecord)) { // This local record has already been uploaded, so it can't be a dupe of // another incoming item. continue; } // Ignore computed fields when matching records as they aren't synced at all. let strippedLocalRecord = this._clone(localRecord); await this._stripComputedFields(strippedLocalRecord); let keys = new Set(Object.keys(remoteRecord)); for (let key of Object.keys(strippedLocalRecord)) { keys.add(key); } // Ignore internal fields when matching records. Internal fields are synced, // but almost certainly have different values than the local record, and // we'll update them in `reconcile`. for (let field of INTERNAL_FIELDS) { keys.delete(field); } if (!keys.size) { // This shouldn't ever happen; a valid record will always have fields // that aren't computed or internal. Sync can't do anything about that, // so we ignore the dubious local record instead of throwing. continue; } let same = true; for (let key of keys) { // For now, we ensure that both (or neither) records have the field // with matching values. This doesn't account for the version yet // (bug 1377204). same = key in strippedLocalRecord == key in remoteRecord && strippedLocalRecord[key] == remoteRecord[key]; if (!same) { break; } } if (same) { return strippedLocalRecord.guid; } } return null; } /** * Internal helper functions. */ _clone(record) { return Object.assign({}, record); } _cloneAndCleanUp(record) { let result = {}; for (let key in record) { // Do not expose hidden fields and fields with empty value (mainly used // as placeholders of the computed fields). if (!key.startsWith("_") && record[key] !== "") { result[key] = record[key]; } } return result; } _findByGUID(guid, { includeDeleted = false } = {}) { let found = this._findIndexByGUID(guid, { includeDeleted }); return found < 0 ? undefined : this._data[found]; } _findIndexByGUID(guid, { includeDeleted = false } = {}) { return this._data.findIndex(record => { return record.guid == guid && (!record.deleted || includeDeleted); }); } async _migrateRecord(record, index) { let hasChanges = false; if (record.deleted) { return hasChanges; } if (!record.version || isNaN(record.version) || record.version < 1) { this.log.warn("Invalid record version:", record.version); // Force to run the migration. record.version = 0; } if (record.version < this.version) { hasChanges = true; record = await this._computeMigratedRecord(record); if (record.deleted) { // record is deleted by _computeMigratedRecord(), // go ahead and put it in the store. this._data[index] = record; return hasChanges; } // Compute the computed fields before putting it to store. await this.computeFields(record); this._data[index] = record; return hasChanges; } hasChanges |= await this.computeFields(record); return hasChanges; } _normalizeRecord(record, preserveEmptyFields = false) { 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: ${typeof record[key]}` ); } if (!preserveEmptyFields && record[key] === "") { delete record[key]; } } if (!Object.keys(record).length) { throw new Error("Record contains no valid field."); } } /** * Merge the record if storage has multiple mergeable records. * @param {Object} targetRecord * The record for merge. * @param {boolean} [strict = false] * In strict merge mode, we'll treat the subset record with empty field * as unable to be merged, but mergeable if in non-strict mode. * @returns {Array.} * Return an array of the merged GUID string. */ async mergeToStorage(targetRecord, strict = false) { let mergedGUIDs = []; for (let record of this._data) { if ( !record.deleted && (await this.mergeIfPossible(record.guid, targetRecord, strict)) ) { mergedGUIDs.push(record.guid); } } this.log.debug( "Existing records matching and merging count is", mergedGUIDs.length ); return mergedGUIDs; } /** * Unconditionally remove all data and tombstones for this collection. */ removeAll({ sourceSync = false } = {}) { this._store.data[this._collectionName] = []; this._store.saveSoon(); Services.obs.notifyObservers( { wrappedJSObject: { sourceSync, collectionName: this._collectionName, }, }, "formautofill-storage-changed", "removeAll" ); } /** * Strip the computed fields based on the record version. * @param {Object} record The record to migrate * @returns {Object} Migrated record. * Record is always cloned, with version updated, * with computed fields stripped. * Could be a tombstone record, if the record * should be discorded. */ async _computeMigratedRecord(record) { if (!record.deleted) { record = this._clone(record); await this._stripComputedFields(record); record.version = this.version; } return record; } async _stripComputedFields(record) { this.VALID_COMPUTED_FIELDS.forEach(field => delete record[field]); } // An interface to be inherited. _recordReadProcessor(record) {} // An interface to be inherited. async computeFields(record) {} /** * An interface to be inherited to mutate the argument to normalize it. * * @param {object} partialRecord containing the record passed by the consumer of * storage and in the case of `update` with * `preserveOldProperties` will only include the * properties that the user is changing so the * lack of a field doesn't mean that the record * won't have that field. */ _normalizeFields(partialRecord) {} /** * An interface to be inherited to validate that the complete record is * consistent and isn't missing required fields. Overrides should throw for * invalid records. * * @param {object} record containing the complete record that would be stored * if this doesn't throw due to an error. * @throws */ _validateFields(record) {} // An interface to be inherited. async mergeIfPossible(guid, record, strict) {} } class Addresses extends AutofillRecords { constructor(store) { super( store, "addresses", VALID_ADDRESS_FIELDS, VALID_ADDRESS_COMPUTED_FIELDS, ADDRESS_SCHEMA_VERSION ); } _recordReadProcessor(address) { if (address.country && !FormAutofill.countries.has(address.country)) { delete address.country; delete address["country-name"]; } } async computeFields(address) { // 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. let hasNewComputedFields = false; if (address.deleted) { return hasNewComputedFields; } // Compute name if (!("name" in address)) { let name = FormAutofillNameUtils.joinNameParts({ given: address["given-name"], middle: address["additional-name"], family: address["family-name"], }); address.name = name; hasNewComputedFields = true; } // Compute address lines if (!("address-line1" in address)) { let streetAddress = []; if (address["street-address"]) { streetAddress = address["street-address"] .split("\n") .map(s => s.trim()); } for (let i = 0; i < 3; i++) { address["address-line" + (i + 1)] = streetAddress[i] || ""; } if (streetAddress.length > 3) { address["address-line3"] = FormAutofillUtils.toOneLineAddress( streetAddress.splice(2) ); } hasNewComputedFields = true; } // Compute country name if (!("country-name" in address)) { if (address.country) { try { address[ "country-name" ] = Services.intl.getRegionDisplayNames(undefined, [address.country]); } catch (e) { address["country-name"] = ""; } } else { address["country-name"] = ""; } hasNewComputedFields = true; } // Compute tel if (!("tel-national" in address)) { if (address.tel) { let tel = PhoneNumber.Parse( address.tel, address.country || FormAutofill.DEFAULT_REGION ); if (tel) { if (tel.countryCode) { address["tel-country-code"] = tel.countryCode; } if (tel.nationalNumber) { address["tel-national"] = tel.nationalNumber; } // PhoneNumberUtils doesn't support parsing the components of a telephone // number so we hard coded the parser for US numbers only. We will need // to figure out how to parse numbers from other regions when we support // new countries in the future. if (tel.nationalNumber && tel.countryCode == "+1") { let telComponents = tel.nationalNumber.match( /(\d{3})((\d{3})(\d{4}))$/ ); if (telComponents) { address["tel-area-code"] = telComponents[1]; address["tel-local"] = telComponents[2]; address["tel-local-prefix"] = telComponents[3]; address["tel-local-suffix"] = telComponents[4]; } } } else { // Treat "tel" as "tel-national" directly if it can't be parsed. address["tel-national"] = address.tel; } } TEL_COMPONENTS.forEach(c => { address[c] = address[c] || ""; }); } return hasNewComputedFields; } _normalizeFields(address) { this._normalizeName(address); this._normalizeAddress(address); this._normalizeCountry(address); this._normalizeTel(address); } _normalizeName(address) { if (address.name) { let nameParts = FormAutofillNameUtils.splitName(address.name); if (!address["given-name"] && nameParts.given) { address["given-name"] = nameParts.given; } if (!address["additional-name"] && nameParts.middle) { address["additional-name"] = nameParts.middle; } if (!address["family-name"] && nameParts.family) { address["family-name"] = nameParts.family; } } delete address.name; } _normalizeAddress(address) { if (STREET_ADDRESS_COMPONENTS.some(c => !!address[c])) { // Treat "street-address" as "address-line1" if it contains only one line // and "address-line1" is omitted. if ( !address["address-line1"] && address["street-address"] && !address["street-address"].includes("\n") ) { address["address-line1"] = address["street-address"]; delete address["street-address"]; } // Concatenate "address-line*" if "street-address" is omitted. if (!address["street-address"]) { address["street-address"] = STREET_ADDRESS_COMPONENTS.map( c => address[c] ) .join("\n") .replace(/\n+$/, ""); } } STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]); } _normalizeCountry(address) { let country; if (address.country) { country = address.country.toUpperCase(); } else if (address["country-name"]) { country = FormAutofillUtils.identifyCountryCode(address["country-name"]); } // Only values included in the region list will be saved. let hasLocalizedName = false; try { if (country) { let localizedName = Services.intl.getRegionDisplayNames(undefined, [ country, ]); hasLocalizedName = localizedName != country; } } catch (e) {} if (country && hasLocalizedName) { address.country = country; } else { delete address.country; } delete address["country-name"]; } _normalizeTel(address) { if (address.tel || TEL_COMPONENTS.some(c => !!address[c])) { FormAutofillUtils.compressTel(address); let possibleRegion = address.country || FormAutofill.DEFAULT_REGION; let tel = PhoneNumber.Parse(address.tel, possibleRegion); if (tel && tel.internationalNumber) { // Force to save numbers in E.164 format if parse success. address.tel = tel.internationalNumber; } } TEL_COMPONENTS.forEach(c => delete address[c]); } /** * Merge new address into the specified address if mergeable. * * @param {string} guid * Indicates which address to merge. * @param {Object} address * The new address used to merge into the old one. * @param {boolean} strict * In strict merge mode, we'll treat the subset record with empty field * as unable to be merged, but mergeable if in non-strict mode. * @returns {Promise} * Return true if address is merged into target with specific guid or false if not. */ async mergeIfPossible(guid, address, strict) { this.log.debug("mergeIfPossible:", guid, address); let addressFound = this._findByGUID(guid); if (!addressFound) { throw new Error("No matching address."); } let addressToMerge = this._clone(address); this._normalizeRecord(addressToMerge, strict); let hasMatchingField = false; let country = addressFound.country || addressToMerge.country || FormAutofill.DEFAULT_REGION; let collators = FormAutofillUtils.getSearchCollators(country); for (let field of this.VALID_FIELDS) { let existingField = addressFound[field]; let incomingField = addressToMerge[field]; if (incomingField !== undefined && existingField !== undefined) { if (incomingField != existingField) { // Treat "street-address" as mergeable if their single-line versions // match each other. if ( field == "street-address" && FormAutofillUtils.compareStreetAddress( existingField, incomingField, collators ) ) { // Keep the street-address in storage if its amount of lines is greater than // or equal to the incoming one. if ( existingField.split("\n").length >= incomingField.split("\n").length ) { // Replace the incoming field with the one in storage so it will // be further merged back to storage. addressToMerge[field] = existingField; } } else if ( field != "street-address" && FormAutofillUtils.strCompare( existingField, incomingField, collators ) ) { addressToMerge[field] = existingField; } else { this.log.debug("Conflicts: field", field, "has different value."); return false; } } hasMatchingField = true; } } // We merge the address only when at least one field has the same value. if (!hasMatchingField) { this.log.debug("Unable to merge because no field has the same value"); return false; } // Early return if the data is the same or subset. let noNeedToUpdate = this.VALID_FIELDS.every(field => { // When addressFound doesn't contain a field, it's unnecessary to update // if the same field in addressToMerge is omitted or an empty string. if (addressFound[field] === undefined) { return !addressToMerge[field]; } // When addressFound contains a field, it's unnecessary to update if // the same field in addressToMerge is omitted or a duplicate. return ( addressToMerge[field] === undefined || addressFound[field] === addressToMerge[field] ); }); if (noNeedToUpdate) { return true; } await this.update(guid, addressToMerge, true); return true; } } class CreditCards extends AutofillRecords { constructor(store) { super( store, "creditCards", VALID_CREDIT_CARD_FIELDS, VALID_CREDIT_CARD_COMPUTED_FIELDS, CREDIT_CARD_SCHEMA_VERSION ); Services.obs.addObserver(this, "formautofill-storage-changed"); } observe(subject, topic, data) { switch (topic) { case "formautofill-storage-changed": let count = this._data.filter(entry => !entry.deleted).length; Services.telemetry.scalarSet( "formautofill.creditCards.autofill_profiles_count", count ); break; } } async 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. let hasNewComputedFields = false; if (creditCard.deleted) { return hasNewComputedFields; } if ("cc-number" in creditCard && !("cc-type" in creditCard)) { let type = CreditCard.getType(creditCard["cc-number"]); if (type) { creditCard["cc-type"] = type; } } // Compute split names if (!("cc-given-name" in creditCard)) { let nameParts = FormAutofillNameUtils.splitName(creditCard["cc-name"]); creditCard["cc-given-name"] = nameParts.given; creditCard["cc-additional-name"] = nameParts.middle; creditCard["cc-family-name"] = nameParts.family; hasNewComputedFields = true; } // Compute credit card expiration date if (!("cc-exp" in creditCard)) { if (creditCard["cc-exp-month"] && creditCard["cc-exp-year"]) { creditCard["cc-exp"] = String(creditCard["cc-exp-year"]) + "-" + String(creditCard["cc-exp-month"]).padStart(2, "0"); } else { creditCard["cc-exp"] = ""; } hasNewComputedFields = true; } // Encrypt credit card number if (!("cc-number-encrypted" in creditCard)) { if ("cc-number" in creditCard) { let ccNumber = creditCard["cc-number"]; if (CreditCard.isValidNumber(ccNumber)) { creditCard["cc-number"] = CreditCard.getLongMaskedNumber(ccNumber); } else { // Credit card numbers can be entered on versions of Firefox that don't validate // the number and then synced to this version of Firefox. Therefore, mask the // full number if the number is invalid on this version. creditCard["cc-number"] = "*".repeat(ccNumber.length); } creditCard["cc-number-encrypted"] = await OSKeyStore.encrypt(ccNumber); } else { creditCard["cc-number-encrypted"] = ""; } } return hasNewComputedFields; } async _computeMigratedRecord(creditCard) { if (creditCard["cc-number-encrypted"]) { switch (creditCard.version) { case 1: case 2: { // We cannot decrypt the data, so silently remove the record for // the user. if (creditCard.deleted) { break; } this.log.warn( "Removing version", creditCard.version, "credit card record to migrate to new encryption:", creditCard.guid ); // Replace the record with a tombstone record here, // regardless of existence of sync metadata. let existingSync = this._getSyncMetaData(creditCard); creditCard = { guid: creditCard.guid, timeLastModified: Date.now(), deleted: true, }; if (existingSync) { creditCard._sync = existingSync; existingSync.changeCounter++; } break; } default: throw new Error( "Unknown credit card version to migrate: " + creditCard.version ); } } return super._computeMigratedRecord(creditCard); } async _stripComputedFields(creditCard) { if (creditCard["cc-number-encrypted"]) { try { creditCard["cc-number"] = await OSKeyStore.decrypt( creditCard["cc-number-encrypted"] ); } catch (ex) { if (ex.result == Cr.NS_ERROR_ABORT) { throw ex; } // Quietly recover from encryption error, // so existing credit card entry with undecryptable number // can be updated. } } await super._stripComputedFields(creditCard); } _normalizeFields(creditCard) { this._normalizeCCName(creditCard); this._normalizeCCNumber(creditCard); this._normalizeCCExpirationDate(creditCard); } _normalizeCCName(creditCard) { if ( creditCard["cc-given-name"] || creditCard["cc-additional-name"] || creditCard["cc-family-name"] ) { if (!creditCard["cc-name"]) { creditCard["cc-name"] = FormAutofillNameUtils.joinNameParts({ given: creditCard["cc-given-name"], middle: creditCard["cc-additional-name"], family: creditCard["cc-family-name"], }); } } delete creditCard["cc-given-name"]; delete creditCard["cc-additional-name"]; delete creditCard["cc-family-name"]; } _normalizeCCNumber(creditCard) { if (!("cc-number" in creditCard)) { return; } if (!CreditCard.isValidNumber(creditCard["cc-number"])) { delete creditCard["cc-number"]; return; } let card = new CreditCard({ number: creditCard["cc-number"] }); creditCard["cc-number"] = card.number; } _normalizeCCExpirationDate(creditCard) { let normalizedExpiration = CreditCard.normalizeExpiration({ expirationMonth: creditCard["cc-exp-month"], expirationYear: creditCard["cc-exp-year"], expirationString: creditCard["cc-exp"], }); if (normalizedExpiration.month) { creditCard["cc-exp-month"] = normalizedExpiration.month; } else { delete creditCard["cc-exp-month"]; } if (normalizedExpiration.year) { creditCard["cc-exp-year"] = normalizedExpiration.year; } else { delete creditCard["cc-exp-year"]; } delete creditCard["cc-exp"]; } _validateFields(creditCard) { if (!creditCard["cc-number"]) { throw new Error("Missing/invalid cc-number"); } } _ensureMatchingVersion(record) { if (!record.version || isNaN(record.version) || record.version < 1) { throw new Error( `Got invalid record version ${record.version}; want ${this.version}` ); } if (record.version < this.version) { switch (record.version) { case 1: case 2: // The difference between version 1 and 2 is only about the encryption // method used for the cc-number-encrypted field. // The difference between version 2 and 3 is the name of the OS // key encryption record. // As long as the record is already decrypted, it is safe to bump the // version directly. if (!record["cc-number-encrypted"]) { record.version = this.version; } else { throw new Error( "Could not migrate record version:", record.version, "->", this.version ); } break; default: throw new Error( "Unknown credit card version to match: " + record.version ); } } return super._ensureMatchingVersion(record); } /** * 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 {Promise} * Return the first guid if storage has the same credit card and null otherwise. */ async getDuplicateGuid(targetCreditCard) { let clonedTargetCreditCard = this._clone(targetCreditCard); this._normalizeRecord(clonedTargetCreditCard); if (!clonedTargetCreditCard["cc-number"]) { return null; } for (let creditCard of this._data) { if (creditCard.deleted) { continue; } let decrypted = await OSKeyStore.decrypt( creditCard["cc-number-encrypted"], false ); if (decrypted == clonedTargetCreditCard["cc-number"]) { return creditCard.guid; } } return null; } /** * Merge new credit card into the specified record if cc-number is identical. * (Note that credit card records always do non-strict merge.) * * @param {string} guid * Indicates which credit card to merge. * @param {Object} creditCard * The new credit card used to merge into the old one. * @returns {boolean} * Return true if credit card is merged into target with specific guid or false if not. */ async mergeIfPossible(guid, creditCard) { this.log.debug("mergeIfPossible:", guid, creditCard); // Credit card number is required since it also must match. if (!creditCard["cc-number"]) { return false; } // Query raw data for comparing the decrypted credit card number let creditCardFound = await this.get(guid, { rawData: true }); if (!creditCardFound) { throw new Error("No matching credit card."); } let creditCardToMerge = this._clone(creditCard); this._normalizeRecord(creditCardToMerge); for (let field of this.VALID_FIELDS) { let existingField = creditCardFound[field]; // Make sure credit card field is existed and have value if ( field == "cc-number" && (!existingField || !creditCardToMerge[field]) ) { return false; } if (!creditCardToMerge[field] && typeof existingField != "undefined") { creditCardToMerge[field] = existingField; } let incomingField = creditCardToMerge[field]; if (incomingField && existingField) { if (incomingField != existingField) { this.log.debug("Conflicts: field", field, "has different value."); return false; } } } // Early return if the data is the same. let exactlyMatch = this.VALID_FIELDS.every( field => creditCardFound[field] === creditCardToMerge[field] ); if (exactlyMatch) { return true; } await this.update(guid, creditCardToMerge, true); return true; } updateUseCountTelemetry() { let histogram = Services.telemetry.getHistogramById("CREDITCARD_NUM_USES"); histogram.clear(); let records = this._data.filter(r => !r.deleted); for (let record of records) { histogram.add(record.timesUsed); } } } function FormAutofillStorage(path) { this._path = path; this._initializePromise = null; this.INTERNAL_FIELDS = INTERNAL_FIELDS; } FormAutofillStorage.prototype = { get version() { return STORAGE_SCHEMA_VERSION; }, get addresses() { if (!this._addresses) { this._store.ensureDataReady(); this._addresses = new Addresses(this._store); } return this._addresses; }, get creditCards() { if (!this._creditCards) { this._store.ensureDataReady(); this._creditCards = new CreditCards(this._store); } return this._creditCards; }, /** * Loads the profile data from file to memory. * * @returns {Promise} * @resolves When the operation finished successfully. * @rejects JavaScript exception. */ initialize() { if (!this._initializePromise) { this._store = new JSONFile({ path: this._path, dataPostProcessor: this._dataPostProcessor.bind(this), }); this._initializePromise = this._store.load().then(() => { let initializeAutofillRecords = [this.addresses.initialize()]; if (FormAutofill.isAutofillCreditCardsAvailable) { initializeAutofillRecords.push(this.creditCards.initialize()); } else { // Make creditCards records unavailable to other modules // because we never initialize it. Object.defineProperty(this, "creditCards", { get() { throw new Error( "CreditCards is not initialized. " + "Please restart if you flip the pref manually." ); }, }); } return Promise.all(initializeAutofillRecords); }); } return this._initializePromise; }, _dataPostProcessor(data) { data.version = this.version; if (!data.addresses) { data.addresses = []; } if (!data.creditCards) { data.creditCards = []; } return data; }, // For test only. _saveImmediately() { return this._store._save(); }, _finalize() { return this._store.finalize(); }, }; // The singleton exposed by this module. this.formAutofillStorage = new FormAutofillStorage( OS.Path.join(OS.Constants.Path.profileDir, PROFILE_JSON_FILE_NAME) ); PK ! { // By listening to but ignoring this event, any updates will // be delayed until the next browser restart. // Note that if we ever wanted to change this, we should make // sure we manually invalidate the startup cache using the // startupcache-invalidate notification. }); PK !<BNN#chrome/content/autofillEditForms.js/* 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/. */ /* exported EditAddress, EditCreditCard */ /* eslint-disable mozilla/balanced-listeners */ // Not relevant since the document gets unloaded. "use strict"; class EditAutofillForm { constructor(elements) { this._elements = elements; } /** * Fill the form with a record object. * @param {object} [record = {}] */ loadRecord(record = {}) { for (let field of this._elements.form.elements) { let value = record[field.id]; value = typeof value == "undefined" ? "" : value; if (record.guid) { field.value = value; } else if (field.localName == "select") { this.setDefaultSelectedOptionByValue(field, value); } else { // Use .defaultValue instead of .value to avoid setting the `dirty` flag // which triggers form validation UI. field.defaultValue = value; } } if (!record.guid) { // Reset the dirty value flag and validity state. this._elements.form.reset(); } else { for (let field of this._elements.form.elements) { this.updatePopulatedState(field); this.updateCustomValidity(field); } } } setDefaultSelectedOptionByValue(select, value) { for (let option of select.options) { option.defaultSelected = option.value == value; } } /** * Get a record from the form suitable for a save/update in storage. * @returns {object} */ buildFormObject() { let initialObject = {}; if (this.hasMailingAddressFields) { // Start with an empty string for each mailing-address field so that any // fields hidden for the current country are blanked in the return value. initialObject = { "street-address": "", "address-level3": "", "address-level2": "", "address-level1": "", "postal-code": "", }; } return Array.from(this._elements.form.elements).reduce((obj, input) => { if (!input.disabled) { obj[input.id] = input.value; } return obj; }, initialObject); } /** * Handle events * * @param {DOMEvent} event */ handleEvent(event) { switch (event.type) { case "change": { this.handleChange(event); break; } case "input": { this.handleInput(event); break; } } } /** * Handle change events * * @param {DOMEvent} event */ handleChange(event) { this.updatePopulatedState(event.target); } /** * Handle input events * * @param {DOMEvent} event */ handleInput(event) {} /** * Attach event listener */ attachEventListeners() { this._elements.form.addEventListener("input", this); } /** * Set the field-populated attribute if the field has a value. * * @param {DOMElement} field The field that will be checked for a value. */ updatePopulatedState(field) { let span = field.parentNode.querySelector(".label-text"); if (!span) { return; } span.toggleAttribute("field-populated", !!field.value.trim()); } /** * Run custom validity routines specific to the field and type of form. * * @param {DOMElement} field The field that will be validated. */ updateCustomValidity(field) {} } class EditAddress extends EditAutofillForm { /** * @param {HTMLElement[]} elements * @param {object} record * @param {object} config * @param {string[]} config.DEFAULT_REGION * @param {function} config.getFormFormat Function to return form layout info for a given country. * @param {function} config.findAddressSelectOption Finds the matching select option for a given select element, address, and fieldName. * @param {string[]} config.countries * @param {boolean} [config.noValidate=undefined] Whether to validate the form */ constructor(elements, record, config) { super(elements); Object.assign(this, config); let { form } = this._elements; Object.assign(this._elements, { addressLevel3Label: form.querySelector( "#address-level3-container > .label-text" ), addressLevel2Label: form.querySelector( "#address-level2-container > .label-text" ), addressLevel1Label: form.querySelector( "#address-level1-container > .label-text" ), postalCodeLabel: form.querySelector( "#postal-code-container > .label-text" ), country: form.querySelector("#country"), }); this.populateCountries(); // Need to populate the countries before trying to set the initial country. // Also need to use this._record so it has the default country selected. this.loadRecord(record); this.attachEventListeners(); form.noValidate = !!config.noValidate; } loadRecord(record) { this._record = record; if (!record) { record = { country: this.DEFAULT_REGION, }; } let { addressLevel1Options } = this.getFormFormat(record.country); this.populateAddressLevel1(addressLevel1Options, record.country); super.loadRecord(record); this.loadAddressLevel1(record["address-level1"], record.country); this.formatForm(record.country); } get hasMailingAddressFields() { let { addressFields } = this._elements.form.dataset; return ( !addressFields || addressFields .trim() .split(/\s+/) .includes("mailing-address") ); } /** * `mailing-address` is a special attribute token to indicate mailing fields + country. * * @param {object[]} mailingFieldsOrder - `fieldsOrder` from `getFormFormat` * @param {string} addressFields - white-space-separated string of requested address fields to show * @returns {object[]} in the same structure as `mailingFieldsOrder` but including non-mail fields */ static computeVisibleFields(mailingFieldsOrder, addressFields) { if (addressFields) { let requestedFieldClasses = addressFields.trim().split(/\s+/); let fieldClasses = []; if (requestedFieldClasses.includes("mailing-address")) { fieldClasses = fieldClasses.concat(mailingFieldsOrder); // `country` isn't part of the `mailingFieldsOrder` so add it when filling a mailing-address requestedFieldClasses.splice( requestedFieldClasses.indexOf("mailing-address"), 1, "country" ); } for (let fieldClassName of requestedFieldClasses) { fieldClasses.push({ fieldId: fieldClassName, newLine: fieldClassName == "name", }); } return fieldClasses; } // This is the default which is shown in the management interface and includes all fields. return mailingFieldsOrder.concat([ { fieldId: "country", }, { fieldId: "tel", }, { fieldId: "email", newLine: true, }, ]); } /** * Format the form based on country. The address-level1 and postal-code labels * should be specific to the given country. * @param {string} country */ formatForm(country) { const { addressLevel3Label, addressLevel2Label, addressLevel1Label, addressLevel1Options, postalCodeLabel, fieldsOrder: mailingFieldsOrder, postalCodePattern, countryRequiredFields, } = this.getFormFormat(country); this._elements.addressLevel3Label.dataset.localization = addressLevel3Label; this._elements.addressLevel2Label.dataset.localization = addressLevel2Label; this._elements.addressLevel1Label.dataset.localization = addressLevel1Label; this._elements.postalCodeLabel.dataset.localization = postalCodeLabel; let addressFields = this._elements.form.dataset.addressFields; let extraRequiredFields = this._elements.form.dataset.extraRequiredFields; let fieldClasses = EditAddress.computeVisibleFields( mailingFieldsOrder, addressFields ); let requiredFields = new Set(countryRequiredFields); if (extraRequiredFields) { for (let extraRequiredField of extraRequiredFields.trim().split(/\s+/)) { requiredFields.add(extraRequiredField); } } this.arrangeFields(fieldClasses, requiredFields); this.updatePostalCodeValidation(postalCodePattern); this.populateAddressLevel1(addressLevel1Options, country); } /** * Update address field visibility and order based on libaddressinput data. * * @param {object[]} fieldsOrder array of objects with `fieldId` and optional `newLine` properties * @param {Set} requiredFields Set of `fieldId` strings that mark which fields are required */ arrangeFields(fieldsOrder, requiredFields) { /** * @see FormAutofillStorage.VALID_ADDRESS_FIELDS */ let fields = [ // `name` is a wrapper for the 3 name fields. "name", "organization", "street-address", "address-level3", "address-level2", "address-level1", "postal-code", "country", "tel", "email", ]; let inputs = []; for (let i = 0; i < fieldsOrder.length; i++) { let { fieldId, newLine } = fieldsOrder[i]; let container = this._elements.form.querySelector( `#${fieldId}-container` ); let containerInputs = [ ...container.querySelectorAll("input, textarea, select"), ]; containerInputs.forEach(function(input) { input.disabled = false; // libaddressinput doesn't list 'country' or 'name' as required. // The additional-name field should never get marked as required. input.required = (fieldId == "country" || fieldId == "name" || requiredFields.has(fieldId)) && input.id != "additional-name"; }); inputs.push(...containerInputs); container.style.display = "flex"; container.style.order = i; container.style.pageBreakAfter = newLine ? "always" : "auto"; // Remove the field from the list of fields fields.splice(fields.indexOf(fieldId), 1); } for (let i = 0; i < inputs.length; i++) { // Assign tabIndex starting from 1 inputs[i].tabIndex = i + 1; } // Hide the remaining fields for (let field of fields) { let container = this._elements.form.querySelector(`#${field}-container`); container.style.display = "none"; for (let input of [ ...container.querySelectorAll("input, textarea, select"), ]) { input.disabled = true; } } } updatePostalCodeValidation(postalCodePattern) { let postalCodeInput = this._elements.form.querySelector("#postal-code"); if (postalCodePattern && postalCodeInput.style.display != "none") { postalCodeInput.setAttribute("pattern", postalCodePattern); } else { postalCodeInput.removeAttribute("pattern"); } } /** * Set the address-level1 value on the form field (input or select, whichever is present). * * @param {string} addressLevel1Value Value of the address-level1 from the autofill record * @param {string} country The corresponding country */ loadAddressLevel1(addressLevel1Value, country) { let field = this._elements.form.querySelector("#address-level1"); if (field.localName == "input") { field.value = addressLevel1Value || ""; return; } let matchedSelectOption = this.findAddressSelectOption( field, { country, "address-level1": addressLevel1Value, }, "address-level1" ); if (matchedSelectOption && !matchedSelectOption.selected) { field.value = matchedSelectOption.value; field.dispatchEvent(new Event("input", { bubbles: true })); field.dispatchEvent(new Event("change", { bubbles: true })); } else if (addressLevel1Value) { // If the option wasn't found, insert an option at the beginning of // the select that matches the stored value. field.insertBefore( new Option(addressLevel1Value, addressLevel1Value, true, true), field.firstChild ); } } /** * Replace the text input for address-level1 with a select dropdown if * a fixed set of names exists. Otherwise show a text input. * * @param {Map?} options Map of options with regionCode -> name mappings * @param {string} country The corresponding country */ populateAddressLevel1(options, country) { let field = this._elements.form.querySelector("#address-level1"); if (field.dataset.country == country) { return; } if (!options) { if (field.localName == "input") { return; } let input = document.createElement("input"); input.setAttribute("type", "text"); input.id = "address-level1"; input.required = field.required; input.disabled = field.disabled; input.tabIndex = field.tabIndex; field.replaceWith(input); return; } if (field.localName == "input") { let select = document.createElement("select"); select.id = "address-level1"; select.required = field.required; select.disabled = field.disabled; select.tabIndex = field.tabIndex; field.replaceWith(select); field = select; } field.textContent = ""; field.dataset.country = country; let fragment = document.createDocumentFragment(); fragment.appendChild(new Option(undefined, undefined, true, true)); for (let [regionCode, regionName] of options) { let option = new Option(regionName, regionCode); fragment.appendChild(option); } field.appendChild(fragment); } populateCountries() { let fragment = document.createDocumentFragment(); // Sort countries by their visible names. let countries = [...this.countries.entries()].sort((e1, e2) => e1[1].localeCompare(e2[1]) ); for (let country of countries) { let option = new Option(); option.value = country[0]; option.dataset.localizationRegion = country[0].toLowerCase(); fragment.appendChild(option); } this._elements.country.appendChild(fragment); } handleChange(event) { if (event.target == this._elements.country) { this.formatForm(event.target.value); } super.handleChange(event); } attachEventListeners() { this._elements.form.addEventListener("change", this); super.attachEventListeners(); } } class EditCreditCard extends EditAutofillForm { /** * @param {HTMLElement[]} elements * @param {object} record with a decrypted cc-number * @param {object} addresses in an object with guid keys for the billing address picker. * @param {object} config * @param {function} config.isCCNumber Function to determine if a string is a valid CC number. * @param {function} config.getSupportedNetworks Function to get the list of card networks */ constructor(elements, record, addresses, config) { super(elements); this._addresses = addresses; Object.assign(this, config); Object.assign(this._elements, { ccNumber: this._elements.form.querySelector("#cc-number"), invalidCardNumberStringElement: this._elements.form.querySelector( "#invalidCardNumberString" ), month: this._elements.form.querySelector("#cc-exp-month"), year: this._elements.form.querySelector("#cc-exp-year"), ccType: this._elements.form.querySelector("#cc-type"), billingAddress: this._elements.form.querySelector("#billingAddressGUID"), billingAddressRow: this._elements.form.querySelector( ".billingAddressRow" ), }); this.attachEventListeners(); this.loadRecord(record, addresses); } loadRecord(record, addresses, preserveFieldValues) { // _record must be updated before generateYears and generateBillingAddressOptions are called. this._record = record; this._addresses = addresses; this.generateBillingAddressOptions(preserveFieldValues); if (!preserveFieldValues) { // Re-populating the networks will reset the selected option. this.populateNetworks(); // Re-generating the months will reset the selected option. this.generateMonths(); // Re-generating the years will reset the selected option. this.generateYears(); super.loadRecord(record); } } generateMonths() { const count = 12; // Clear the list this._elements.month.textContent = ""; // Empty month option this._elements.month.appendChild(new Option()); // Populate month list. Format: "month number - month name" let dateFormat = new Intl.DateTimeFormat(navigator.language, { month: "long", }).format; for (let i = 0; i < count; i++) { let monthNumber = (i + 1).toString(); let monthName = dateFormat(new Date(1970, i)); let option = new Option(); option.value = monthNumber; // XXX: Bug 1446164 - Localize this string. option.textContent = `${monthNumber.padStart(2, "0")} - ${monthName}`; this._elements.month.appendChild(option); } } generateYears() { const count = 11; const currentYear = new Date().getFullYear(); const ccExpYear = this._record && this._record["cc-exp-year"]; // Clear the list this._elements.year.textContent = ""; // Provide an empty year option this._elements.year.appendChild(new Option()); if (ccExpYear && ccExpYear < currentYear) { this._elements.year.appendChild(new Option(ccExpYear)); } for (let i = 0; i < count; i++) { let year = currentYear + i; let option = new Option(year); this._elements.year.appendChild(option); } if (ccExpYear && ccExpYear > currentYear + count) { this._elements.year.appendChild(new Option(ccExpYear)); } } populateNetworks() { // Clear the list this._elements.ccType.textContent = ""; let frag = document.createDocumentFragment(); // include an empty first option frag.appendChild(new Option("", "")); let supportedNetworks = this.getSupportedNetworks(); for (let id of supportedNetworks) { let option = new Option(); option.value = id; option.dataset.localization = "cardNetwork." + id; frag.appendChild(option); } this._elements.ccType.appendChild(frag); } generateBillingAddressOptions(preserveFieldValues) { let billingAddressGUID; if (preserveFieldValues && this._elements.billingAddress.value) { billingAddressGUID = this._elements.billingAddress.value; } else if (this._record) { billingAddressGUID = this._record.billingAddressGUID; } this._elements.billingAddress.textContent = ""; this._elements.billingAddress.appendChild(new Option("", "")); let hasAddresses = false; for (let [guid, address] of Object.entries(this._addresses)) { hasAddresses = true; let selected = guid == billingAddressGUID; let option = new Option( this.getAddressLabel(address), guid, selected, selected ); this._elements.billingAddress.appendChild(option); } this._elements.billingAddressRow.hidden = !hasAddresses; } attachEventListeners() { this._elements.form.addEventListener("change", this); super.attachEventListeners(); } handleInput(event) { // Clear the error message if cc-number is valid if ( event.target == this._elements.ccNumber && this.isCCNumber(this._elements.ccNumber.value) ) { this._elements.ccNumber.setCustomValidity(""); } super.handleInput(event); } updateCustomValidity(field) { super.updateCustomValidity(field); // Mark the cc-number field as invalid if the number is empty or invalid. if (field == this._elements.ccNumber && !this.isCCNumber(field.value)) { let invalidCardNumberString = this._elements .invalidCardNumberStringElement.textContent; field.setCustomValidity(invalidCardNumberString || " "); } } } PK ! { const { Services } = ChromeUtils.import( "resource://gre/modules/Services.jsm" ); function sendMessageToBrowser(msgName, data) { let { AutoCompleteParent } = ChromeUtils.import( "resource://gre/actors/AutoCompleteParent.jsm" ); let actor = AutoCompleteParent.getCurrentActor(); if (!actor) { return; } actor.manager.getActor("FormAutofill").sendAsyncMessage(msgName, data); } class MozAutocompleteProfileListitemBase extends MozElements.MozRichlistitem { constructor() { super(); /** * For form autofill, we want to unify the selection no matter by * keyboard navigation or mouseover in order not to confuse user which * profile preview is being shown. This field is set to true to indicate * that selectedIndex of popup should be changed while mouseover item */ this.selectedByMouseOver = true; } get _stringBundle() { if (!this.__stringBundle) { this.__stringBundle = Services.strings.createBundle( "chrome://formautofill/locale/formautofill.properties" ); } return this.__stringBundle; } _cleanup() { this.removeAttribute("formautofillattached"); if (this._itemBox) { this._itemBox.removeAttribute("size"); } } _onOverflow() {} _onUnderflow() {} handleOverUnderflow() {} _adjustAutofillItemLayout() { let outerBoxRect = this.parentNode.getBoundingClientRect(); // Make item fit in popup as XUL box could not constrain // item's width this._itemBox.style.width = outerBoxRect.width + "px"; // Use two-lines layout when width is smaller than 150px or // 185px if an image precedes the label. let oneLineMinRequiredWidth = this.getAttribute("ac-image") ? 185 : 150; if (outerBoxRect.width <= oneLineMinRequiredWidth) { this._itemBox.setAttribute("size", "small"); } else { this._itemBox.removeAttribute("size"); } } } MozElements.MozAutocompleteProfileListitem = class MozAutocompleteProfileListitem extends MozAutocompleteProfileListitemBase { static get markup() { return `
`; } connectedCallback() { if (this.delayConnectedCallback()) { return; } this.textContent = ""; this.appendChild(this.constructor.fragment); this._itemBox = this.querySelector(".autofill-item-box"); this._labelAffix = this.querySelector(".profile-label-affix"); this._label = this.querySelector(".profile-label"); this._comment = this.querySelector(".profile-comment"); this.initializeAttributeInheritance(); this._adjustAcItem(); } static get inheritedAttributes() { return { ".autofill-item-box": "ac-image", }; } set selected(val) { if (val) { this.setAttribute("selected", "true"); } else { this.removeAttribute("selected"); } sendMessageToBrowser("FormAutofill:PreviewProfile"); return val; } get selected() { return this.getAttribute("selected") == "true"; } _adjustAcItem() { this._adjustAutofillItemLayout(); this.setAttribute("formautofillattached", "true"); this._itemBox.style.setProperty( "--primary-icon", `url(${this.getAttribute("ac-image")})` ); let { primaryAffix, primary, secondary, ariaLabel } = JSON.parse( this.getAttribute("ac-value") ); this._labelAffix.textContent = primaryAffix; this._label.textContent = primary; this._comment.textContent = secondary; if (ariaLabel) { this.setAttribute("aria-label", ariaLabel); } } }; customElements.define( "autocomplete-profile-listitem", MozElements.MozAutocompleteProfileListitem, { extends: "richlistitem" } ); class MozAutocompleteProfileListitemFooter extends MozAutocompleteProfileListitemBase { static get markup() { return ` `; } constructor() { super(); this.addEventListener("click", event => { if (event.button != 0) { return; } if (this._warningTextBox.contains(event.originalTarget)) { return; } window.openPreferences("privacy-form-autofill"); }); } connectedCallback() { if (this.delayConnectedCallback()) { return; } this.textContent = ""; this.appendChild(this.constructor.fragment); this._itemBox = this.querySelector(".autofill-footer"); this._optionButton = this.querySelector(".autofill-button"); this._warningTextBox = this.querySelector(".autofill-warning"); /** * A handler for updating warning message once selectedIndex has been changed. * * There're three different states of warning message: * 1. None of addresses were selected: We show all the categories intersection of fields in the * form and fields in the results. * 2. An address was selested: Show the additional categories that will also be filled. * 3. An address was selected, but the focused category is the same as the only one category: Only show * the exact category that we're going to fill in. * * @private * @param {Object} data * Message data * @param {string[]} data.categories * The categories of all the fields contained in the selected address. */ this.updateWarningNote = data => { let categories = data && data.categories ? data.categories : this._allFieldCategories; // If the length of categories is 1, that means all the fillable fields are in the same // category. We will change the way to inform user according to this flag. When the value // is true, we show "Also autofills ...", otherwise, show "Autofills ..." only. let hasExtraCategories = categories.length > 1; // Show the categories in certain order to conform with the spec. let orderedCategoryList = [ { id: "address", l10nId: "category.address" }, { id: "name", l10nId: "category.name" }, { id: "organization", l10nId: "category.organization2" }, { id: "tel", l10nId: "category.tel" }, { id: "email", l10nId: "category.email" }, ]; let showCategories = hasExtraCategories ? orderedCategoryList.filter( category => categories.includes(category.id) && category.id != this._focusedCategory ) : [ orderedCategoryList.find( category => category.id == this._focusedCategory ), ]; let separator = this._stringBundle.GetStringFromName( "fieldNameSeparator" ); let warningTextTmplKey = hasExtraCategories ? "phishingWarningMessage" : "phishingWarningMessage2"; let categoriesText = showCategories .map(category => this._stringBundle.GetStringFromName(category.l10nId) ) .join(separator); this._warningTextBox.textContent = this._stringBundle.formatStringFromName( warningTextTmplKey, [categoriesText] ); this.parentNode.parentNode.adjustHeight(); }; this._adjustAcItem(); } _onCollapse() { if (this.showWarningText) { let { FormAutofillParent } = ChromeUtils.import( "resource://formautofill/FormAutofillParent.jsm" ); FormAutofillParent.removeMessageObserver(this); } this._itemBox.removeAttribute("no-warning"); } _adjustAcItem() { this._adjustAutofillItemLayout(); this.setAttribute("formautofillattached", "true"); let { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm", {} ); let buttonTextBundleKey; if (this._itemBox.getAttribute("size") == "small") { buttonTextBundleKey = AppConstants.platform == "macosx" ? "autocompleteFooterOptionOSXShort2" : "autocompleteFooterOptionShort2"; } else { buttonTextBundleKey = AppConstants.platform == "macosx" ? "autocompleteFooterOptionOSX2" : "autocompleteFooterOption2"; } let buttonText = this._stringBundle.GetStringFromName( buttonTextBundleKey ); this._optionButton.textContent = buttonText; let value = JSON.parse(this.getAttribute("ac-value")); this._allFieldCategories = value.categories; this._focusedCategory = value.focusedCategory; this.showWarningText = this._allFieldCategories && this._focusedCategory; if (this.showWarningText) { let { FormAutofillParent } = ChromeUtils.import( "resource://formautofill/FormAutofillParent.jsm" ); FormAutofillParent.addMessageObserver(this); this.updateWarningNote(); } else { this._itemBox.setAttribute("no-warning", "true"); } } } customElements.define( "autocomplete-profile-listitem-footer", MozAutocompleteProfileListitemFooter, { extends: "richlistitem" } ); class MozAutocompleteCreditcardInsecureField extends MozAutocompleteProfileListitemBase { static get markup() { return `
`; } connectedCallback() { if (this.delayConnectedCallback()) { return; } this.textContent = ""; this.appendChild(this.constructor.fragment); this._itemBox = this.querySelector(".autofill-insecure-item"); this._adjustAcItem(); } set selected(val) { // Make this item unselectable since we see this item as a pure message. return false; } get selected() { return this.getAttribute("selected") == "true"; } _adjustAcItem() { this._adjustAutofillItemLayout(); this.setAttribute("formautofillattached", "true"); let value = this.getAttribute("ac-value"); this._itemBox.textContent = value; } } customElements.define( "autocomplete-creditcard-insecure-field", MozAutocompleteCreditcardInsecureField, { extends: "richlistitem" } ); class MozAutocompleteProfileListitemClearButton extends MozAutocompleteProfileListitemBase { static get markup() { return ` `; } constructor() { super(); this.addEventListener("click", event => { if (event.button != 0) { return; } sendMessageToBrowser("FormAutofill:ClearForm"); }); } connectedCallback() { if (this.delayConnectedCallback()) { return; } this.textContent = ""; this.appendChild(this.constructor.fragment); this._itemBox = this.querySelector(".autofill-item-box"); this._clearBtn = this.querySelector(".autofill-button"); this._adjustAcItem(); } _adjustAcItem() { this._adjustAutofillItemLayout(); this.setAttribute("formautofillattached", "true"); let clearFormBtnLabel = this._stringBundle.GetStringFromName( "clearFormBtnLabel2" ); this._clearBtn.textContent = clearFormBtnLabel; } } customElements.define( "autocomplete-profile-listitem-clear-button", MozAutocompleteProfileListitemClearButton, { extends: "richlistitem" } ); })(); PK !<T5 chrome/content/editAddress.xhtml %globalDTD; ]> <link rel="stylesheet" href="chrome://formautofill/content/skin/editDialog-shared.css"/> <link rel="stylesheet" href="chrome://formautofill/content/skin/editAddress.css"/> <link rel="stylesheet" href="chrome://formautofill/content/skin/editDialog.css"/> <script src="chrome://formautofill/content/l10n.js"></script> <script src="chrome://formautofill/content/editDialog.js"></script> <script src="chrome://formautofill/content/autofillEditForms.js"></script> </head> <body dir="&locale.dir;"> <form id="form" class="editAddressForm" autocomplete="off"> <!-- The <span class="label-text" …/> needs to be after the form field in the same element in order to get proper label styling with :focus and :moz-ui-invalid. --> <div id="name-container" class="container"> <label id="given-name-container"> <input id="given-name" type="text" required="required"/> <span data-localization="givenName" class="label-text"/> </label> <label id="additional-name-container"> <input id="additional-name" type="text"/> <span data-localization="additionalName" class="label-text"/> </label> <label id="family-name-container"> <input id="family-name" type="text" required="required"/> <span data-localization="familyName" class="label-text"/> </label> </div> <label id="organization-container" class="container"> <input id="organization" type="text"/> <span data-localization="organization2" class="label-text"/> </label> <label id="street-address-container" class="container"> <textarea id="street-address" rows="3"/> <span data-localization="streetAddress" class="label-text"/> </label> <label id="address-level3-container" class="container"> <input id="address-level3" type="text"/> <span class="label-text"/> </label> <label id="address-level2-container" class="container"> <input id="address-level2" type="text"/> <span class="label-text"/> </label> <label id="address-level1-container" class="container"> <!-- The address-level1 input will get replaced by a select dropdown by autofillEditForms.js when the selected country has provided specific options. --> <input id="address-level1" type="text"/> <span class="label-text"/> </label> <label id="postal-code-container" class="container"> <input id="postal-code" type="text"/> <span class="label-text"/> </label> <label id="country-container" class="container"> <select id="country" required="required"> <option/> </select> <span data-localization="country" class="label-text"/> </label> <label id="tel-container" class="container"> <input id="tel" type="tel" dir="auto"/> <span data-localization="tel" class="label-text"/> </label> <label id="email-container" class="container"> <input id="email" type="email" required="required"/> <span data-localization="email" class="label-text"/> </label> </form> <div id="controls-container"> <span id="country-warning-message" data-localization="countryWarningMessage2"/> <button id="cancel" data-localization="cancelBtnLabel"/> <button id="save" class="primary" data-localization="saveBtnLabel"/> </div> <script><![CDATA[ "use strict"; /* import-globals-from l10n.js */ let { DEFAULT_REGION, countries, } = FormAutofill; let { getFormFormat, findAddressSelectOption, } = FormAutofillUtils; let args = window.arguments || []; let { record, noValidate, } = args[0] || {}; /* import-globals-from autofillEditForms.js */ var fieldContainer = new EditAddress({ form: document.getElementById("form"), }, record, { DEFAULT_REGION, getFormFormat: getFormFormat.bind(FormAutofillUtils), findAddressSelectOption: findAddressSelectOption.bind(FormAutofillUtils), countries, noValidate, }); /* import-globals-from editDialog.js */ new EditAddressDialog({ title: document.querySelector("title"), fieldContainer, controlsContainer: document.getElementById("controls-container"), cancel: document.getElementById("cancel"), save: document.getElementById("save"), }, record); ]]></script> </body> </html> PK �������!<Z1����#���chrome/content/editCreditCard.xhtml<?xml version="1.0" encoding="UTF-8"?> <!-- 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/. --> <!DOCTYPE html [ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD; ]> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title data-localization="addNewCreditCardTitle"/> <link rel="stylesheet" href="chrome://formautofill/content/skin/editDialog-shared.css"/> <link rel="stylesheet" href="chrome://formautofill/content/skin/editCreditCard.css"/> <link rel="stylesheet" href="chrome://formautofill/content/skin/editDialog.css"/> <script src="chrome://formautofill/content/l10n.js"></script> <script src="chrome://formautofill/content/editDialog.js"></script> <script src="chrome://formautofill/content/autofillEditForms.js"></script> </head> <body dir="&locale.dir;"> <form id="form" class="editCreditCardForm contentPane" autocomplete="off"> <!-- The <span class="label-text" …/> needs to be after the form field in the same element in order to get proper label styling with :focus and :moz-ui-invalid. --> <label id="cc-number-container" class="container" role="none"> <span id="invalidCardNumberString" hidden="hidden" data-localization="invalidCardNumber"></span> <!-- Because there is text both before and after the input, a11y will include the value of the input in the label. Therefore, we override with aria-labelledby. --> <input id="cc-number" type="text" required="required" minlength="14" pattern="[- 0-9]+" aria-labelledby="cc-number-label"/> <span id="cc-number-label" data-localization="cardNumber" class="label-text"/> </label> <label id="cc-exp-month-container" class="container"> <select id="cc-exp-month" required="required"> <option/> </select> <span data-localization="cardExpiresMonth" class="label-text"/> </label> <label id="cc-exp-year-container" class="container"> <select id="cc-exp-year" required="required"> <option/> </select> <span data-localization="cardExpiresYear" class="label-text"/> </label> <label id="cc-name-container" class="container"> <input id="cc-name" type="text" required="required"/> <span data-localization="nameOnCard" class="label-text"/> </label> <label id="cc-type-container" class="container"> <select id="cc-type" required="required"> </select> <span data-localization="cardNetwork" class="label-text"/> </label> <label id="cc-csc-container" class="container" hidden="hidden"> <!-- The CSC container will get filled in by forms that need a CSC (using csc-input.js) --> </label> <div id="billingAddressGUID-container" class="billingAddressRow container rich-picker"> <select id="billingAddressGUID" required="required"> </select> <label for="billingAddressGUID" data-localization="billingAddress" class="label-text"/> </div> </form> <div id="controls-container"> <button id="cancel" data-localization="cancelBtnLabel"/> <button id="save" class="primary" data-localization="saveBtnLabel"/> </div> <script><![CDATA[ "use strict"; /* import-globals-from l10n.js */ (async () => { let { getAddressLabel, isCCNumber, getCreditCardNetworks, } = FormAutofillUtils; let args = window.arguments || []; let { record, } = args[0] || {}; let addresses = {}; for (let address of await formAutofillStorage.addresses.getAll()) { addresses[address.guid] = address; } /* import-globals-from autofillEditForms.js */ let fieldContainer = new EditCreditCard({ form: document.getElementById("form"), }, record, addresses, { getAddressLabel: getAddressLabel.bind(FormAutofillUtils), isCCNumber: isCCNumber.bind(FormAutofillUtils), getSupportedNetworks: getCreditCardNetworks.bind(FormAutofillUtils), }); /* import-globals-from editDialog.js */ new EditCreditCardDialog({ title: document.querySelector("title"), fieldContainer, controlsContainer: document.getElementById("controls-container"), cancel: document.getElementById("cancel"), save: document.getElementById("save"), }, record); })(); ]]></script> </body> </html> PK �������!< 2%�������chrome/content/editDialog.js/* 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/. */ /* exported EditAddressDialog, EditCreditCardDialog */ /* eslint-disable mozilla/balanced-listeners */ // Not relevant since the document gets unloaded. "use strict"; // eslint-disable-next-line no-unused-vars const { FormAutofill } = ChromeUtils.import( "resource://formautofill/FormAutofill.jsm" ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.defineModuleGetter( this, "formAutofillStorage", "resource://formautofill/FormAutofillStorage.jsm" ); class AutofillEditDialog { constructor(subStorageName, elements, record) { this._storageInitPromise = formAutofillStorage.initialize(); this._subStorageName = subStorageName; this._elements = elements; this._record = record; this.localizeDocument(); window.addEventListener("DOMContentLoaded", this, { once: true }); } async init() { this.updateSaveButtonState(); this.attachEventListeners(); // For testing only: signal to tests that the dialog is ready for testing. // This is likely no longer needed since retrieving from storage is fully // handled in manageDialog.js now. window.dispatchEvent(new CustomEvent("FormReady")); } /** * Get storage and ensure it has been initialized. * @returns {object} */ async getStorage() { await this._storageInitPromise; return formAutofillStorage[this._subStorageName]; } /** * Asks FormAutofillParent to save or update an record. * @param {object} record * @param {string} guid [optional] */ async saveRecord(record, guid) { let storage = await this.getStorage(); if (guid) { await storage.update(guid, record); } else { await storage.add(record); } } /** * Handle events * * @param {DOMEvent} event */ handleEvent(event) { switch (event.type) { case "DOMContentLoaded": { this.init(); break; } case "click": { this.handleClick(event); break; } case "input": { this.handleInput(event); break; } case "keypress": { this.handleKeyPress(event); break; } case "contextmenu": { if ( !(event.target instanceof HTMLInputElement) && !(event.target instanceof HTMLTextAreaElement) ) { event.preventDefault(); } break; } } } /** * Handle click events * * @param {DOMEvent} event */ handleClick(event) { if (event.target == this._elements.cancel) { window.close(); } if (event.target == this._elements.save) { this.handleSubmit(); } } /** * Handle input events * * @param {DOMEvent} event */ handleInput(event) { this.updateSaveButtonState(); } /** * Handle key press events * * @param {DOMEvent} event */ handleKeyPress(event) { if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) { window.close(); } } updateSaveButtonState() { // Toggle disabled attribute on the save button based on // whether the form is filled or empty. if (!Object.keys(this._elements.fieldContainer.buildFormObject()).length) { this._elements.save.setAttribute("disabled", true); } else { this._elements.save.removeAttribute("disabled"); } } /** * Attach event listener */ attachEventListeners() { window.addEventListener("keypress", this); window.addEventListener("contextmenu", this); this._elements.controlsContainer.addEventListener("click", this); document.addEventListener("input", this); } // An interface to be inherited. localizeDocument() {} } class EditAddressDialog extends AutofillEditDialog { constructor(elements, record) { super("addresses", elements, record); } localizeDocument() { if (this._record?.guid) { this._elements.title.dataset.localization = "editAddressTitle"; } } async handleSubmit() { await this.saveRecord( this._elements.fieldContainer.buildFormObject(), this._record ? this._record.guid : null ); window.close(); } } class EditCreditCardDialog extends AutofillEditDialog { constructor(elements, record) { elements.fieldContainer._elements.billingAddress.disabled = true; super("creditCards", elements, record); elements.fieldContainer._elements.ccNumber.addEventListener( "blur", this._onCCNumberFieldBlur.bind(this) ); if (record) { Services.telemetry.recordEvent("creditcard", "show_entry", "manage"); } } _onCCNumberFieldBlur() { let elem = this._elements.fieldContainer._elements.ccNumber; this._elements.fieldContainer.updateCustomValidity(elem); } localizeDocument() { if (this._record?.guid) { this._elements.title.dataset.localization = "editCreditCardTitle"; } } async handleSubmit() { let creditCard = this._elements.fieldContainer.buildFormObject(); if (!this._elements.fieldContainer._elements.form.reportValidity()) { return; } try { await this.saveRecord( creditCard, this._record ? this._record.guid : null ); if (this._record?.guid) { Services.telemetry.recordEvent("creditcard", "edit", "manage"); } else { Services.telemetry.recordEvent("creditcard", "add", "manage"); } window.close(); } catch (ex) { Cu.reportError(ex); } } } PK �������!<Fqv�������chrome/content/formautofill.css/* 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/. */ #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-profile"], #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"], #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-insecureWarning"], #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-clear-button"] { display: block; margin: 0; padding: 0; height: auto; min-height: auto; } /* Treat @collpased="true" as display: none similar to how it is for XUL elements. * https://developer.mozilla.org/en-US/docs/Web/CSS/visibility#Values */ #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-profile"][collapsed="true"], #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"][collapsed="true"], #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-insecureWarning"][collapsed="true"], #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-clear-button"][collapsed="true"] { display: none; } #PopupAutoComplete[resultstyles~="autofill-profile"] { min-width: 150px !important; } #PopupAutoComplete[resultstyles~="autofill-insecureWarning"] { min-width: 200px !important; } #PopupAutoComplete > richlistbox > richlistitem[disabled="true"] { opacity: 0.5; } /* Form Autofill Doorhanger */ #autofill-address-notification popupnotificationcontent > .desc-message-box, #autofill-credit-card-notification popupnotificationcontent > .desc-message-box { margin-block-end: 12px; } #autofill-credit-card-notification popupnotificationcontent > .desc-message-box > image { -moz-context-properties: fill; fill: currentColor; margin-inline-start: 6px; width: 16px; height: 16px; list-style-image: url(chrome://formautofill/content/icon-credit-card-generic.svg); } #autofill-address-notification popupnotificationcontent > .desc-message-box > description, #autofill-credit-card-notification popupnotificationcontent > .desc-message-box > description { font-style: italic; } PK �������!<iet����"���chrome/content/formfill-anchor.svg<!-- 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/. --> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity"> <path d="M7.3 6h1.5c.1 0 .2-.1.2-.3V2c0-.5-.4-1-1-1s-1 .4-1 1v3.8c0 .1.1.2.3.2z"/> <path d="M13.5 3H11c-.6 0-1 .4-1 1s.4 1 1 1h2.5c.3 0 .5.2.5.5v7c0 .3-.2.5-.5.5h-11c-.3 0-.5-.3-.5-.5v-7c0-.3.2-.5.5-.5H5c.6 0 1-.4 1-1s-.4-1-1-1H2.5C1.1 3 0 4.1 0 5.5v7C0 13.8 1.1 15 2.5 15h11c1.4 0 2.5-1.1 2.5-2.5v-7C16 4.1 14.9 3 13.5 3z"/> <path d="M3.6 7h2.8c.3 0 .6.2.6.5v2.8c0 .4-.3.7-.6.7H3.6c-.3 0-.6-.3-.6-.6V7.5c0-.3.3-.5.6-.5zM9.5 8h3c.3 0 .5-.3.5-.5s-.2-.5-.5-.5h-3c-.3 0-.5.2-.5.5s.2.5.5.5zM9.5 9c-.3 0-.5.2-.5.5s.2.5.5.5h2c.3 0 .5-.2.5-.5s-.2-.5-.5-.5h-2z"/> </svg> PK �������!<@ (I��(I��"���chrome/content/heuristicsRegexp.js/* 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/. */ /* * Form Autofill field Heuristics RegExp. */ /* exported HeuristicsRegExp */ "use strict"; var HeuristicsRegExp = { RULES: { email: undefined, tel: undefined, organization: undefined, "street-address": undefined, "address-line1": undefined, "address-line2": undefined, "address-line3": undefined, "address-level2": undefined, "address-level1": undefined, "postal-code": undefined, country: undefined, // Note: We place the `cc-name` field for Credit Card first, because // it is more specific than the `name` field below and we want to check // for it before we catch the more generic one. "cc-name": undefined, name: undefined, "given-name": undefined, "additional-name": undefined, "family-name": undefined, "cc-number": undefined, "cc-exp-month": undefined, "cc-exp-year": undefined, "cc-exp": undefined, "cc-type": undefined, }, RULE_SETS: [ //========================================================================= // Firefox-specific rules { "address-line1": "addrline1|address_1", "address-line2": "addrline2|address_2", "address-line3": "addrline3|address_3", "address-level1": "land", // de-DE "additional-name": "apellido.?materno|lastlastname", "cc-number": "(cc|kk)nr", // de-DE "cc-exp-month": "(cc|kk)month", // de-DE "cc-exp-year": "(cc|kk)year", // de-DE "cc-type": "type", }, //========================================================================= // These are the rules used by Bitwarden [0], converted into RegExp form. // [0] https://github.com/bitwarden/browser/blob/c2b8802201fac5e292d55d5caf3f1f78088d823c/src/services/autofill.service.ts#L436 { email: "(^e-?mail$)|(^email-?address$)", tel: "(^phone$)" + "|(^mobile$)" + "|(^mobile-?phone$)" + "|(^tel$)" + "|(^telephone$)" + "|(^phone-?number$)", organization: "(^company$)" + "|(^company-?name$)" + "|(^organization$)" + "|(^organization-?name$)", "street-address": "(^address$)" + "|(^street-?address$)" + "|(^addr$)" + "|(^street$)" + "|(^mailing-?addr(ess)?$)" + // Modified to not grab lines, below "|(^billing-?addr(ess)?$)" + // Modified to not grab lines, below "|(^mail-?addr(ess)?$)" + // Modified to not grab lines, below "|(^bill-?addr(ess)?$)", // Modified to not grab lines, below "address-line1": "(^address-?1$)" + "|(^address-?line-?1$)" + "|(^addr-?1$)" + "|(^street-?1$)", "address-line2": "(^address-?2$)" + "|(^address-?line-?2$)" + "|(^addr-?2$)" + "|(^street-?2$)", "address-line3": "(^address-?3$)" + "|(^address-?line-?3$)" + "|(^addr-?3$)" + "|(^street-?3$)", "address-level2": "(^city$)" + "|(^town$)" + "|(^address-?level-?2$)" + "|(^address-?city$)" + "|(^address-?town$)", "address-level1": "(^state$)" + "|(^province$)" + "|(^provence$)" + "|(^address-?level-?1$)" + "|(^address-?state$)" + "|(^address-?province$)", "postal-code": "(^postal$)" + "|(^zip$)" + "|(^zip2$)" + "|(^zip-?code$)" + "|(^postal-?code$)" + "|(^post-?code$)" + "|(^address-?zip$)" + "|(^address-?postal$)" + "|(^address-?code$)" + "|(^address-?postal-?code$)" + "|(^address-?zip-?code$)", country: "(^country$)" + "|(^country-?code$)" + "|(^country-?name$)" + "|(^address-?country$)" + "|(^address-?country-?name$)" + "|(^address-?country-?code$)", name: "(^name$)|full-?name|your-?name", "given-name": "(^f-?name$)" + "|(^first-?name$)" + "|(^given-?name$)" + "|(^first-?n$)", "additional-name": "(^m-?name$)" + "|(^middle-?name$)" + "|(^additional-?name$)" + "|(^middle-?initial$)" + "|(^middle-?n$)" + "|(^middle-?i$)", "family-name": "(^l-?name$)" + "|(^last-?name$)" + "|(^s-?name$)" + "|(^surname$)" + "|(^family-?name$)" + "|(^family-?n$)" + "|(^last-?n$)", "cc-name": "cc-?name" + "|card-?name" + "|cardholder-?name" + "|cardholder" + // "|(^name$)" + // Removed to avoid overwriting "name", above. "|(^nom$)", "cc-number": "cc-?number" + "|cc-?num" + "|card-?number" + "|card-?num" + "|(^number$)" + "|(^cc$)" + "|cc-?no" + "|card-?no" + "|(^credit-?card$)" + "|numero-?carte" + "|(^carte$)" + "|(^carte-?credit$)" + "|num-?carte" + "|cb-?num", "cc-exp": "(^cc-?exp$)" + "|(^card-?exp$)" + "|(^cc-?expiration$)" + "|(^card-?expiration$)" + "|(^cc-?ex$)" + "|(^card-?ex$)" + "|(^card-?expire$)" + "|(^card-?expiry$)" + "|(^validite$)" + "|(^expiration$)" + "|(^expiry$)" + "|mm-?yy" + "|mm-?yyyy" + "|yy-?mm" + "|yyyy-?mm" + "|expiration-?date" + "|payment-?card-?expiration" + "|(^payment-?cc-?date$)", "cc-exp-month": "(^exp-?month$)" + "|(^cc-?exp-?month$)" + "|(^cc-?month$)" + "|(^card-?month$)" + "|(^cc-?mo$)" + "|(^card-?mo$)" + "|(^exp-?mo$)" + "|(^card-?exp-?mo$)" + "|(^cc-?exp-?mo$)" + "|(^card-?expiration-?month$)" + "|(^expiration-?month$)" + "|(^cc-?mm$)" + "|(^cc-?m$)" + "|(^card-?mm$)" + "|(^card-?m$)" + "|(^card-?exp-?mm$)" + "|(^cc-?exp-?mm$)" + "|(^exp-?mm$)" + "|(^exp-?m$)" + "|(^expire-?month$)" + "|(^expire-?mo$)" + "|(^expiry-?month$)" + "|(^expiry-?mo$)" + "|(^card-?expire-?month$)" + "|(^card-?expire-?mo$)" + "|(^card-?expiry-?month$)" + "|(^card-?expiry-?mo$)" + "|(^mois-?validite$)" + "|(^mois-?expiration$)" + "|(^m-?validite$)" + "|(^m-?expiration$)" + "|(^expiry-?date-?field-?month$)" + "|(^expiration-?date-?month$)" + "|(^expiration-?date-?mm$)" + "|(^exp-?mon$)" + "|(^validity-?mo$)" + "|(^exp-?date-?mo$)" + "|(^cb-?date-?mois$)" + "|(^date-?m$)", "cc-exp-year": "(^exp-?year$)" + "|(^cc-?exp-?year$)" + "|(^cc-?year$)" + "|(^card-?year$)" + "|(^cc-?yr$)" + "|(^card-?yr$)" + "|(^exp-?yr$)" + "|(^card-?exp-?yr$)" + "|(^cc-?exp-?yr$)" + "|(^card-?expiration-?year$)" + "|(^expiration-?year$)" + "|(^cc-?yy$)" + "|(^cc-?y$)" + "|(^card-?yy$)" + "|(^card-?y$)" + "|(^card-?exp-?yy$)" + "|(^cc-?exp-?yy$)" + "|(^exp-?yy$)" + "|(^exp-?y$)" + "|(^cc-?yyyy$)" + "|(^card-?yyyy$)" + "|(^card-?exp-?yyyy$)" + "|(^cc-?exp-?yyyy$)" + "|(^expire-?year$)" + "|(^expire-?yr$)" + "|(^expiry-?year$)" + "|(^expiry-?yr$)" + "|(^card-?expire-?year$)" + "|(^card-?expire-?yr$)" + "|(^card-?expiry-?year$)" + "|(^card-?expiry-?yr$)" + "|(^an-?validite$)" + "|(^an-?expiration$)" + "|(^annee-?validite$)" + "|(^annee-?expiration$)" + "|(^expiry-?date-?field-?year$)" + "|(^expiration-?date-?year$)" + "|(^cb-?date-?ann$)" + "|(^expiration-?date-?yy$)" + "|(^expiration-?date-?yyyy$)" + "|(^validity-?year$)" + "|(^exp-?date-?year$)" + "|(^date-?y$)", "cc-type": "(^cc-?type$)" + "|(^card-?type$)" + "|(^card-?brand$)" + "|(^cc-?brand$)" + "|(^cb-?type$)", }, //========================================================================= // These rules are from Chromium source codes [1]. Most of them // converted to JS format have the same meaning with the original ones except // the first line of "address-level1". // [1] https://source.chromium.org/chromium/chromium/src/+/master:components/autofill/core/common/autofill_regex_constants.cc { // ==== Email ==== email: "e.?mail" + "|courriel" + // fr "|correo.*electr(o|ó)nico" + // es-ES "|メールアドレス" + // ja-JP "|Электронной.?Почты" + // ru "|邮件|邮箱" + // zh-CN "|電郵地址" + // zh-TW "|ഇ-മെയില്‍|ഇലക്ട്രോണിക്.?" + "മെയിൽ" + // ml "|ایمیل|پست.*الکترونیک" + // fa "|ईमेल|इलॅक्ट्रॉनिक.?मेल" + // hi "|(\\b|_)eposta(\\b|_)" + // tr "|(?:이메일|전자.?우편|[Ee]-?mail)(.?주소)?", // ko-KR // ==== Telephone ==== tel: "phone|mobile|contact.?number" + "|telefonnummer" + // de-DE "|telefono|teléfono" + // es "|telfixe" + // fr-FR "|電話" + // ja-JP "|telefone|telemovel" + // pt-BR, pt-PT "|телефон" + // ru "|मोबाइल" + // hi for mobile "|(\\b|_|\\*)telefon(\\b|_|\\*)" + // tr "|电话" + // zh-CN "|മൊബൈല്‍" + // ml for mobile "|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?", // ko-KR // ==== Address Fields ==== organization: "company|business|organization|organisation" + "|(?<!con)firma|firmenname" + // de-DE "|empresa" + // es "|societe|société" + // fr-FR "|ragione.?sociale" + // it-IT "|会社" + // ja-JP "|название.?компании" + // ru "|单位|公司" + // zh-CN "|شرکت" + // fa "|회사|직장", // ko-KR "street-address": "streetaddress|street-address", "address-line1": "^address$|address[_-]?line(one)?|address1|addr1|street" + "|(?:shipping|billing)address$" + "|strasse|straße|hausnummer|housenumber" + // de-DE "|house.?name" + // en-GB "|direccion|dirección" + // es "|adresse" + // fr-FR "|indirizzo" + // it-IT "|^住所$|住所1" + // ja-JP "|morada|((?<!identificação do )endereço)" + // pt-BR, pt-PT "|Адрес" + // ru "|地址" + // zh-CN "|(\\b|_)adres(?! (başlığı(nız)?|tarifi))(\\b|_)" + // tr "|^주소.?$|주소.?1", // ko-KR "address-line2": "address[_-]?line(2|two)|address2|addr2|street|suite|unit(?!e)" + // Firefox adds `(?!e)` to unit to skip `United State` "|adresszusatz|ergänzende.?angaben" + // de-DE "|direccion2|colonia|adicional" + // es "|addresssuppl|complementnom|appartement" + // fr-FR "|indirizzo2" + // it-IT "|住所2" + // ja-JP "|complemento|addrcomplement" + // pt-BR, pt-PT "|Улица" + // ru "|地址2" + // zh-CN "|주소.?2", // ko-KR "address-line3": "address[_-]?line(3|three)|address3|addr3|street|suite|unit(?!e)" + // Firefox adds `(?!e)` to unit to skip `United State` "|adresszusatz|ergänzende.?angaben" + // de-DE "|direccion3|colonia|adicional" + // es "|addresssuppl|complementnom|appartement" + // fr-FR "|indirizzo3" + // it-IT "|住所3" + // ja-JP "|complemento|addrcomplement" + // pt-BR, pt-PT "|Улица" + // ru "|地址3" + // zh-CN "|주소.?3", // ko-KR "address-level2": "city|town" + "|\\bort\\b|stadt" + // de-DE "|suburb" + // en-AU "|ciudad|provincia|localidad|poblacion" + // es "|ville|commune" + // fr-FR "|localita" + // it-IT "|市区町村" + // ja-JP "|cidade" + // pt-BR, pt-PT "|Город" + // ru "|市" + // zh-CN "|分區" + // zh-TW "|شهر" + // fa "|शहर" + // hi for city "|ग्राम|गाँव" + // hi for village "|നഗരം|ഗ്രാമം" + // ml for town|village "|((\\b|_|\\*)([İii̇]l[cç]e(miz|niz)?)(\\b|_|\\*))" + // tr "|^시[^도·・]|시[·・]?군[·・]?구", // ko-KR "address-level1": "(?<!(united|hist|history).?)state|county|region|province" + "|county|principality" + // en-UK "|都道府県" + // ja-JP "|estado|provincia" + // pt-BR, pt-PT "|область" + // ru "|省" + // zh-CN "|地區" + // zh-TW "|സംസ്ഥാനം" + // ml "|استان" + // fa "|राज्य" + // hi "|((\\b|_|\\*)(eyalet|[şs]ehir|[İii̇]l(imiz)?|kent)(\\b|_|\\*))" + // tr "|^시[·・]?도", // ko-KR "postal-code": "zip|postal|post.*code|pcode" + "|pin.?code" + // en-IN "|postleitzahl" + // de-DE "|\\bcp\\b" + // es "|\\bcdp\\b" + // fr-FR "|\\bcap\\b" + // it-IT "|郵便番号" + // ja-JP "|codigo|codpos|\\bcep\\b" + // pt-BR, pt-PT "|Почтовый.?Индекс" + // ru "|पिन.?कोड" + // hi "|പിന്‍കോഡ്" + // ml "|邮政编码|邮编" + // zh-CN "|郵遞區號" + // zh-TW "|(\\b|_)posta kodu(\\b|_)" + // tr "|우편.?번호", // ko-KR country: "country|countries" + "|país|pais" + // es "|(\\b|_)land(\\b|_)(?!.*(mark.*))" + // de-DE landmark is a type in india. "|(?<!(入|出))国" + // ja-JP "|国家" + // zh-CN "|국가|나라" + // ko-KR "|(\\b|_)(ülke|ulce|ulke)(\\b|_)" + // tr "|کشور", // fa // ==== Name Fields ==== "cc-name": "card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card" + "|(?:card|cc).?name|cc.?full.?name" + "|karteninhaber" + // de-DE "|nombre.*tarjeta" + // es "|nom.*carte" + // fr-FR "|nome.*cart" + // it-IT "|名前" + // ja-JP "|Имя.*карты" + // ru "|信用卡开户名|开户名|持卡人姓名" + // zh-CN "|持卡人姓名", // zh-TW name: "^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name" + "|name.*first.*last|firstandlastname" + "|nombre.*y.*apellidos" + // es "|^nom(?!bre)" + // fr-FR "|お名前|氏名" + // ja-JP "|^nome" + // pt-BR, pt-PT "|نام.*نام.*خانوادگی" + // fa "|姓名" + // zh-CN "|(\\b|_|\\*)ad[ı]? soyad[ı]?(\\b|_|\\*)" + // tr "|성명", // ko-KR "given-name": "first.*name|initials|fname|first$|given.*name" + "|vorname" + // de-DE "|nombre" + // es "|forename|prénom|prenom" + // fr-FR "|名" + // ja-JP "|nome" + // pt-BR, pt-PT "|Имя" + // ru "|نام" + // fa "|이름" + // ko-KR "|പേര്" + // ml "|(\\b|_|\\*)(isim|ad|ad(i|ı|iniz|ınız)?)(\\b|_|\\*)" + // tr "|नाम", // hi "additional-name": "middle.*name|mname|middle$|middle.*initial|m\\.i\\.|mi$|\\bmi\\b", "family-name": "last.*name|lname|surname|last$|secondname|family.*name" + "|nachname" + // de-DE "|apellidos?" + // es "|famille|^nom(?!bre)" + // fr-FR "|cognome" + // it-IT "|姓" + // ja-JP "|apelidos|surename|sobrenome" + // pt-BR, pt-PT "|Фамилия" + // ru "|نام.*خانوادگی" + // fa "|उपनाम" + // hi "|മറുപേര്" + // ml "|(\\b|_|\\*)(soyisim|soyad(i|ı|iniz|ınız)?)(\\b|_|\\*)" + // tr "|\\b성(?:[^명]|\\b)", // ko-KR // ==== Credit Card Fields ==== // Note: `cc-name` expression has been moved up, above `name`, in // order to handle specialization through ordering. "cc-number": "(add)?(?:card|cc|acct).?(?:number|#|no|num|field)" + "|(?<!telefon|haus|person|fødsels)nummer" + // de-DE, sv-SE, no "|カード番号" + // ja-JP "|Номер.*карты" + // ru "|信用卡号|信用卡号码" + // zh-CN "|信用卡卡號" + // zh-TW "|카드" + // ko-KR // es/pt/fr "|(numero|número|numéro)(?!.*(document|fono|phone|réservation))", "cc-exp-month": "expir|exp.*mo|exp.*date|ccmonth|cardmonth|addmonth" + "|gueltig|gültig|monat" + // de-DE "|fecha" + // es "|date.*exp" + // fr-FR "|scadenza" + // it-IT "|有効期限" + // ja-JP "|validade" + // pt-BR, pt-PT "|Срок действия карты" + // ru "|月", // zh-CN "cc-exp-year": "exp|^/|(add)?year" + "|ablaufdatum|gueltig|gültig|jahr" + // de-DE "|fecha" + // es "|scadenza" + // it-IT "|有効期限" + // ja-JP "|validade" + // pt-BR, pt-PT "|Срок действия карты" + // ru "|年|有效期", // zh-CN "cc-exp": "expir|exp.*date|^expfield$" + "|gueltig|gültig" + // de-DE "|fecha" + // es "|date.*exp" + // fr-FR "|scadenza" + // it-IT "|有効期限" + // ja-JP "|validade" + // pt-BR, pt-PT "|Срок действия карты", // ru }, ], _getRule(name) { let rules = []; this.RULE_SETS.forEach(set => { if (set[name]) { rules.push(`(${set[name]})`.normalize("NFKC")); } }); const value = new RegExp(rules.join("|"), "iu"); Object.defineProperty(this.RULES, name, { get: undefined }); Object.defineProperty(this.RULES, name, { value }); return value; }, init() { Object.keys(this.RULES).forEach(field => Object.defineProperty(this.RULES, field, { get() { return HeuristicsRegExp._getRule(field); }, }) ); }, }; HeuristicsRegExp.init(); PK �������!<˖����$���chrome/content/icon-address-save.svg<!-- 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/. --> <svg xmlns="http://www.w3.org/2000/svg" fill="context-fill" fill-opacity="context-fill-opacity" viewBox="0 0 32 32"> <path d="M22 13.7H9.4c-.6 0-1.2.5-1.2 1.2 0 .6.5 1.2 1.2 1.2H22c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2zM6.1 26.6V5.5c0-.8.7-1.5 1.5-1.5h16c.9 0 1.5.6 1.5 1.5V16h2V3.8c0-1-.7-1.8-1.8-1.8H5.9c-1 0-1.8.8-1.8 1.8v24.5c0 1 .8 1.7 1.8 1.7h9.3v-2H7.6c-.8 0-1.5-.6-1.5-1.4zm21.1-1.9h-2.5V20c0-.4-.3-.8-.8-.8h-3.1c-.4 0-.8.3-.8.8v4.6h-2.5c-.6 0-.8.4-.3.8l4.3 4.2c.2.2.5.3.8.3s.6-.1.8-.3l4.3-4.2c.6-.4.4-.7-.2-.7zm-11.3-5.6H9.4c-.6 0-1.2.5-1.2 1.2s.5 1.2 1.2 1.2h6.5c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2zM22 7.8H9.4c-.6 0-1.2.5-1.2 1.2s.5 1.2 1.2 1.2H22c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2z"/> </svg> PK �������!<SO[����&���chrome/content/icon-address-update.svg<!-- 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/. --> <svg xmlns="http://www.w3.org/2000/svg" fill="context-fill" fill-opacity="context-fill-opacity" viewBox="0 0 32 32"> <path d="M22 13.7H9.4c-.6 0-1.2.5-1.2 1.2 0 .6.5 1.2 1.2 1.2H22c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2zM6.1 26.6V5.5c0-.8.7-1.5 1.5-1.5h16c.9 0 1.5.6 1.5 1.5V16h2V3.8c0-1-.7-1.8-1.8-1.8H5.9c-1 0-1.8.8-1.8 1.8v24.5c0 1 .8 1.7 1.8 1.7h9.3v-2H7.6c-.8 0-1.5-.6-1.5-1.4zm9.8-7.5H9.4c-.6 0-1.2.5-1.2 1.2s.5 1.2 1.2 1.2h6.5c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2zM22 7.8H9.4c-.6 0-1.2.5-1.2 1.2s.5 1.2 1.2 1.2H22c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2zm-5.7 16l4.4-4.3c.2-.2.5-.3.8-.3s.6.1.8.3l4.4 4.3c.5.5.3.8-.3.8h-2.6v4.7c0 .4-.4.8-.8.8h-3c-.4 0-.8-.4-.8-.8v-4.7h-2.5c-.7 0-.8-.4-.4-.8z"/> </svg> PK �������!<܋����+���chrome/content/icon-credit-card-generic.svg<!-- 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/. --> <svg xmlns="http://www.w3.org/2000/svg" fill="context-fill" viewBox="0 0 16 16"> <path d="M4.5,9.4H3.2c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h1.3c0.3,0,0.5-0.2,0.5-0.5S4.8,9.4,4.5,9.4z"/> <path d="M9.3,9.4H6.2c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h3.2c0.3,0,0.5-0.2,0.5-0.5S9.6,9.4,9.3,9.4z"/> <path d="M14,2H2C0.9,2,0,2.9,0,4v8c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V4C16,2.9,15.1,2,14,2z M14,12H2V7.7h12V12z M14,6H2V4h12V6z"/> </svg> PK �������!<e_����#���chrome/content/icon-credit-card.svg<!-- 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/. --> <svg xmlns="http://www.w3.org/2000/svg" fill="context-fill" fill-opacity="context-fill-opacity" viewBox="0 0 32 32"> <path d="M9 22.2H6.4c-.6 0-1 .4-1 1s.4 1 1 1H9c.6 0 1-.4 1-1s-.4-1-1-1z"/> <path d="M28 7.6v8H4v-4h10v-4H4c-2.2 0-4 1.8-4 4v16c0 2.2 1.8 4 4 4h24c2.2 0 4-1.8 4-4v-16c0-2.2-1.8-4-4-4zm-24 20V19h24v8.6H4z"/> <path d="M19.2 22.2h-6.3c-.6 0-1 .4-1 1s.4 1 1 1h6.3c.6 0 1-.4 1-1s-.5-1-1-1zM16.3 7.9c-.4.4-.4 1 0 1.4l4 4c.4.4 1 .4 1.4 0l4-4c.4-.4.4-1 0-1.4s-1-.4-1.4 0L22 10.2v-9c0-.5-.4-1-1-1-.5 0-1 .4-1 1v9l-2.3-2.3c-.4-.4-1-.4-1.4 0z"/> </svg> PK �������!<Adt[��[�����chrome/content/l10n.js/* 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"; /** * This file will be replaced by Fluent but it's a middle ground so we can share * the edit dialog code with the unprivileged PaymentRequest dialog before the * Fluent conversion */ const { FormAutofillUtils } = ChromeUtils.import( "resource://formautofill/FormAutofillUtils.jsm" ); const CONTENT_WIN = typeof window != "undefined" ? window : this; const L10N_ATTRIBUTES = ["data-localization", "data-localization-region"]; // eslint-disable-next-line mozilla/balanced-listeners CONTENT_WIN.addEventListener("DOMContentLoaded", function onDCL(evt) { let doc = evt.target; FormAutofillUtils.localizeMarkup(doc); let mutationObserver = new doc.ownerGlobal.MutationObserver( function onMutation(mutations) { for (let mutation of mutations) { switch (mutation.type) { case "attributes": { if (!mutation.target.hasAttribute(mutation.attributeName)) { // The attribute was removed in the meantime. continue; } FormAutofillUtils.localizeAttributeForElement( mutation.target, mutation.attributeName ); break; } case "childList": { // We really only care about elements appending inside pages. if (!mutation.addedNodes || !mutation.target.closest(".page")) { break; } FormAutofillUtils.localizeMarkup(mutation.target); break; } } } } ); mutationObserver.observe(doc, { attributes: true, attributeFilter: L10N_ATTRIBUTES, childList: true, subtree: true, }); }); PK �������!<`4A@��@��$���chrome/content/manageAddresses.xhtml<?xml version="1.0" encoding="UTF-8"?> <!-- 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/. --> <!DOCTYPE html [ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD; ]> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title data-localization="manageAddressesTitle"/> <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" /> <link rel="stylesheet" href="chrome://formautofill/content/manageDialog.css" /> <script src="chrome://formautofill/content/manageDialog.js"></script> </head> <body dir="&locale.dir;"> <fieldset> <legend data-localization="addressesListHeader"/> <select id="addresses" size="9" multiple="multiple"/> </fieldset> <div id="controls-container"> <button id="remove" disabled="disabled" data-localization="removeBtnLabel"/> <!-- Wrapper is used to properly compute the search tooltip position --> <div> <button id="add" data-localization="addBtnLabel"/> </div> <button id="edit" disabled="disabled" data-localization="editBtnLabel"/> </div> <script> "use strict"; /* global ManageAddresses */ new ManageAddresses({ records: document.getElementById("addresses"), controlsContainer: document.getElementById("controls-container"), remove: document.getElementById("remove"), add: document.getElementById("add"), edit: document.getElementById("edit"), }); </script> </body> </html> PK �������!<ٖ|Ð����&���chrome/content/manageCreditCards.xhtml<?xml version="1.0" encoding="UTF-8"?> <!-- 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/. --> <!DOCTYPE html [ <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd"> %globalDTD; ]> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title data-localization="manageCreditCardsTitle"/> <link rel="localization" href="toolkit/payments/payments.ftl"/> <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" /> <link rel="stylesheet" href="chrome://formautofill/content/manageDialog.css" /> <script src="chrome://formautofill/content/manageDialog.js"></script> </head> <body dir="&locale.dir;"> <fieldset> <legend data-localization="creditCardsListHeader"/> <select id="credit-cards" size="9" multiple="multiple"/> </fieldset> <div id="controls-container"> <button id="remove" disabled="disabled" data-localization="removeBtnLabel"/> <!-- Wrapper is used to properly compute the search tooltip position --> <div> <button id="add" data-localization="addBtnLabel"/> </div> <button id="edit" disabled="disabled" data-localization="editBtnLabel"/> </div> <script> "use strict"; /* global ManageCreditCards */ new ManageCreditCards({ records: document.getElementById("credit-cards"), controlsContainer: document.getElementById("controls-container"), remove: document.getElementById("remove"), add: document.getElementById("add"), edit: document.getElementById("edit"), }); </script> </body> </html> PK �������!<~On �� �����chrome/content/manageDialog.css/* 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/. */ html { /* Prevent unnecessary horizontal scroll bar from showing */ overflow-x: hidden; } div { display: flex; } button { padding-inline: 10px; } fieldset { margin: 0 4px; padding: 0; border: none; } fieldset > legend { box-sizing: border-box; width: 100%; padding: 0.4em 0.7em; color: #808080; background-color: var(--in-content-box-background-hover); border: 1px solid var(--in-content-box-border-color); border-radius: 2px 2px 0 0; user-select: none; } option:nth-child(even) { background-color: var(--in-content-box-background-odd); } #addresses, #credit-cards { width: 100%; height: 16.6em; margin: 0; padding-inline: 0; border-top: none; border-radius: 0 0 2px 2px; } #addresses > option, #credit-cards > option { display: flex; align-items: center; height: 1.6em; padding-inline-start: 0.6em; } #controls-container { margin-top: 1em; } #remove { margin-inline-end: auto; } #credit-cards > option::before { content: ""; background: url("icon-credit-card-generic.svg") no-repeat; background-size: contain; float: inline-start; width: 16px; height: 16px; padding-inline-end: 10px; -moz-context-properties: fill; fill: currentColor; } /* We use .png / @2x.png images where we don't yet have a vector version of a logo */ #credit-cards.branded > option[cc-type="amex"]::before { background-image: url("third-party/cc-logo-amex.png"); } #credit-cards.branded > option[cc-type="cartebancaire"]::before { background-image: url("third-party/cc-logo-cartebancaire.png"); } #credit-cards.branded > option[cc-type="diners"]::before { background-image: url("third-party/cc-logo-diners.svg"); } #credit-cards.branded > option[cc-type="discover"]::before { background-image: url("third-party/cc-logo-discover.png"); } #credit-cards.branded > option[cc-type="jcb"]::before { background-image: url("third-party/cc-logo-jcb.svg"); } #credit-cards.branded > option[cc-type="mastercard"]::before { background-image: url("third-party/cc-logo-mastercard.svg"); } #credit-cards.branded > option[cc-type="mir"]::before { background-image: url("third-party/cc-logo-mir.svg"); } #credit-cards.branded > option[cc-type="unionpay"]::before { background-image: url("third-party/cc-logo-unionpay.svg"); } #credit-cards.branded > option[cc-type="visa"]::before { background-image: url("third-party/cc-logo-visa.svg"); } @media (min-resolution: 1.1dppx) { #credit-cards.branded > option[cc-type="amex"]::before { background-image: url("third-party/cc-logo-amex@2x.png"); } #credit-cards.branded > option[cc-type="cartebancaire"]::before { background-image: url("third-party/cc-logo-cartebancaire@2x.png"); } #credit-cards.branded > option[cc-type="discover"]::before { background-image: url("third-party/cc-logo-discover@2x.png"); } } PK �������!<({6��6�����chrome/content/manageDialog.js/* 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/. */ /* exported ManageAddresses, ManageCreditCards */ "use strict"; const EDIT_ADDRESS_URL = "chrome://formautofill/content/editAddress.xhtml"; const EDIT_CREDIT_CARD_URL = "chrome://formautofill/content/editCreditCard.xhtml"; const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const { FormAutofill } = ChromeUtils.import( "resource://formautofill/FormAutofill.jsm" ); ChromeUtils.defineModuleGetter( this, "CreditCard", "resource://gre/modules/CreditCard.jsm" ); ChromeUtils.defineModuleGetter( this, "formAutofillStorage", "resource://formautofill/FormAutofillStorage.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofillUtils", "resource://formautofill/FormAutofillUtils.jsm" ); ChromeUtils.defineModuleGetter( this, "OSKeyStore", "resource://gre/modules/OSKeyStore.jsm" ); XPCOMUtils.defineLazyGetter(this, "reauthPasswordPromptMessage", () => { const brandShortName = FormAutofillUtils.brandBundle.GetStringFromName( "brandShortName" ); // The string name for Mac is changed because the value needed updating. const platform = AppConstants.platform.replace("macosx", "macos"); return FormAutofillUtils.stringBundle.formatStringFromName( `editCreditCardPasswordPrompt.${platform}`, [brandShortName] ); }); this.log = null; FormAutofill.defineLazyLogGetter(this, "manageAddresses"); class ManageRecords { constructor(subStorageName, elements) { this._storageInitPromise = formAutofillStorage.initialize(); this._subStorageName = subStorageName; this._elements = elements; this._newRequest = false; this._isLoadingRecords = false; this.prefWin = window.opener; this.localizeDocument(); window.addEventListener("DOMContentLoaded", this, { once: true }); } async init() { await this.loadRecords(); this.attachEventListeners(); // For testing only: Notify when the dialog is ready for interaction window.dispatchEvent(new CustomEvent("FormReady")); } uninit() { log.debug("uninit"); this.detachEventListeners(); this._elements = null; } localizeDocument() { document.documentElement.style.minWidth = FormAutofillUtils.stringBundle.GetStringFromName( "manageDialogsWidth" ); FormAutofillUtils.localizeMarkup(document); } /** * Get the selected options on the addresses element. * * @returns {array<DOMElement>} */ get _selectedOptions() { return Array.from(this._elements.records.selectedOptions); } /** * Get storage and ensure it has been initialized. * @returns {object} */ async getStorage() { await this._storageInitPromise; return formAutofillStorage[this._subStorageName]; } /** * Load records and render them. This function is a wrapper for _loadRecords * to ensure any reentrant will be handled well. */ async loadRecords() { // This function can be early returned when there is any reentrant happends. // "_newRequest" needs to be set to ensure all changes will be applied. if (this._isLoadingRecords) { this._newRequest = true; return; } this._isLoadingRecords = true; await this._loadRecords(); // _loadRecords should be invoked again if there is any multiple entrant // during running _loadRecords(). This step ensures that the latest request // still is applied. while (this._newRequest) { this._newRequest = false; await this._loadRecords(); } this._isLoadingRecords = false; // For testing only: Notify when records are loaded this._elements.records.dispatchEvent(new CustomEvent("RecordsLoaded")); } async _loadRecords() { let storage = await this.getStorage(); let records = await storage.getAll(); // Sort by last used time starting with most recent records.sort((a, b) => { let aLastUsed = a.timeLastUsed || a.timeLastModified; let bLastUsed = b.timeLastUsed || b.timeLastModified; return bLastUsed - aLastUsed; }); await this.renderRecordElements(records); this.updateButtonsStates(this._selectedOptions.length); } /** * Render the records onto the page while maintaining selected options if * they still exist. * * @param {array<object>} records */ async renderRecordElements(records) { let selectedGuids = this._selectedOptions.map(option => option.value); this.clearRecordElements(); for (let record of records) { let { id, args, raw } = this.getLabelInfo(record); let option = new Option( raw ?? "", record.guid, false, selectedGuids.includes(record.guid) ); if (id) { document.l10n.setAttributes(option, id, args); } option.record = record; this._elements.records.appendChild(option); } } /** * Remove all existing record elements. */ clearRecordElements() { let parent = this._elements.records; while (parent.lastChild) { parent.removeChild(parent.lastChild); } } /** * Remove records by selected options. * * @param {array<DOMElement>} options */ async removeRecords(options) { let storage = await this.getStorage(); // Pause listening to storage change event to avoid triggering `loadRecords` // when removing records Services.obs.removeObserver(this, "formautofill-storage-changed"); for (let option of options) { storage.remove(option.value); option.remove(); } this.updateButtonsStates(this._selectedOptions); // Resume listening to storage change event Services.obs.addObserver(this, "formautofill-storage-changed"); // For testing only: notify record(s) has been removed this._elements.records.dispatchEvent(new CustomEvent("RecordsRemoved")); } /** * Enable/disable the Edit and Remove buttons based on number of selected * options. * * @param {number} selectedCount */ updateButtonsStates(selectedCount) { log.debug("updateButtonsStates:", selectedCount); if (selectedCount == 0) { this._elements.edit.setAttribute("disabled", "disabled"); this._elements.remove.setAttribute("disabled", "disabled"); } else if (selectedCount == 1) { this._elements.edit.removeAttribute("disabled"); this._elements.remove.removeAttribute("disabled"); } else if (selectedCount > 1) { this._elements.edit.setAttribute("disabled", "disabled"); this._elements.remove.removeAttribute("disabled"); } } /** * Handle events * * @param {DOMEvent} event */ handleEvent(event) { switch (event.type) { case "DOMContentLoaded": { this.init(); break; } case "click": { this.handleClick(event); break; } case "change": { this.updateButtonsStates(this._selectedOptions.length); break; } case "unload": { this.uninit(); break; } case "keypress": { this.handleKeyPress(event); break; } case "contextmenu": { event.preventDefault(); break; } } } /** * Handle click events * * @param {DOMEvent} event */ handleClick(event) { if (event.target == this._elements.remove) { this.removeRecords(this._selectedOptions); } else if (event.target == this._elements.add) { this.openEditDialog(); } else if ( event.target == this._elements.edit || (event.target.parentNode == this._elements.records && event.detail > 1) ) { this.openEditDialog(this._selectedOptions[0].record); } } /** * Handle key press events * * @param {DOMEvent} event */ handleKeyPress(event) { if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) { window.close(); } if (event.keyCode == KeyEvent.DOM_VK_DELETE) { this.removeRecords(this._selectedOptions); } } observe(subject, topic, data) { switch (topic) { case "formautofill-storage-changed": { this.loadRecords(); } } } /** * Attach event listener */ attachEventListeners() { window.addEventListener("unload", this, { once: true }); window.addEventListener("keypress", this); window.addEventListener("contextmenu", this); this._elements.records.addEventListener("change", this); this._elements.records.addEventListener("click", this); this._elements.controlsContainer.addEventListener("click", this); Services.obs.addObserver(this, "formautofill-storage-changed"); } /** * Remove event listener */ detachEventListeners() { window.removeEventListener("keypress", this); window.removeEventListener("contextmenu", this); this._elements.records.removeEventListener("change", this); this._elements.records.removeEventListener("click", this); this._elements.controlsContainer.removeEventListener("click", this); Services.obs.removeObserver(this, "formautofill-storage-changed"); } } class ManageAddresses extends ManageRecords { constructor(elements) { super("addresses", elements); elements.add.setAttribute( "searchkeywords", FormAutofillUtils.EDIT_ADDRESS_KEYWORDS.map(key => FormAutofillUtils.stringBundle.GetStringFromName(key) ).join("\n") ); } /** * Open the edit address dialog to create/edit an address. * * @param {object} address [optional] */ openEditDialog(address) { this.prefWin.gSubDialog.open(EDIT_ADDRESS_URL, undefined, { record: address, // Don't validate in preferences since it's fine for fields to be missing // for autofill purposes. For PaymentRequest addresses get more validation. noValidate: true, }); } getLabelInfo(address) { return { raw: FormAutofillUtils.getAddressLabel(address) }; } } class ManageCreditCards extends ManageRecords { constructor(elements) { super("creditCards", elements); elements.add.setAttribute( "searchkeywords", FormAutofillUtils.EDIT_CREDITCARD_KEYWORDS.map(key => FormAutofillUtils.stringBundle.GetStringFromName(key) ).join("\n") ); Services.telemetry.recordEvent("creditcard", "show", "manage"); this._isDecrypted = false; } /** * Open the edit address dialog to create/edit a credit card. * * @param {object} creditCard [optional] */ async openEditDialog(creditCard) { // Ask for reauth if user is trying to edit an existing credit card. if ( !creditCard || (await FormAutofillUtils.ensureLoggedIn(reauthPasswordPromptMessage)) .authenticated ) { let decryptedCCNumObj = {}; if (creditCard && creditCard["cc-number-encrypted"]) { try { decryptedCCNumObj["cc-number"] = await OSKeyStore.decrypt( creditCard["cc-number-encrypted"] ); } catch (ex) { if (ex.result == Cr.NS_ERROR_ABORT) { // User shouldn't be ask to reauth here, but it could happen. // Return here and skip opening the dialog. return; } // We've got ourselves a real error. // Recover from encryption error so the user gets a chance to re-enter // unencrypted credit card number. decryptedCCNumObj["cc-number"] = ""; Cu.reportError(ex); } } let decryptedCreditCard = Object.assign( {}, creditCard, decryptedCCNumObj ); this.prefWin.gSubDialog.open( EDIT_CREDIT_CARD_URL, { features: "resizable=no" }, { record: decryptedCreditCard, } ); } } /** * Get credit card display label. It should display masked numbers and the * cardholder's name, separated by a comma. * * @param {object} creditCard * @returns {string} */ getLabelInfo(creditCard) { // The card type is displayed visually using an image. For a11y, we need // to expose it as text. We do this using aria-label. However, // aria-label overrides the text content, so we must include that also. // Since the text content is generated by Fluent, aria-label must be // generated by Fluent also. let type; try { type = FormAutofillUtils.stringBundle.GetStringFromName( `cardNetwork.${creditCard["cc-type"]}` ); } catch (e) { type = ""; // Unknown. } return CreditCard.getLabelInfo({ name: creditCard["cc-name"], number: creditCard["cc-number"], month: creditCard["cc-exp-month"], year: creditCard["cc-exp-year"], type, }); } async renderRecordElements(records) { // Revert back to encrypted form when re-rendering happens this._isDecrypted = false; // Display third-party card icons when possible this._elements.records.classList.toggle( "branded", AppConstants.MOZILLA_OFFICIAL ); await super.renderRecordElements(records); let options = this._elements.records.options; for (let option of options) { let record = option.record; if (record && record["cc-type"]) { option.setAttribute("cc-type", record["cc-type"]); } else { option.removeAttribute("cc-type"); } } } async removeRecords(options) { await super.removeRecords(options); for (let i = 0; i < options.length; i++) { Services.telemetry.recordEvent("creditcard", "delete", "manage"); } } updateButtonsStates(selectedCount) { super.updateButtonsStates(selectedCount); } handleClick(event) { super.handleClick(event); } } PK �������!<Ms����0���chrome/content/skin/autocomplete-item-shared.css/* 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/. */ @namespace url("http://www.w3.org/1999/xhtml"); @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box { background-color: #F2F2F2; } xul|richlistitem[originaltype="autofill-footer"][selected="true"] > .autofill-item-box > .autofill-button, xul|richlistitem[originaltype="autofill-clear-button"][selected="true"] > .autofill-item-box > .autofill-button { background-color: #DCDCDE; } xul|richlistitem[originaltype="autofill-insecureWarning"] { border-bottom: 1px solid var(--panel-separator-color); background-color: var(--arrowpanel-dimmed); } .autofill-item-box { --item-padding-vertical: 7px; --item-padding-horizontal: 10px; --col-spacer: 7px; --item-width: calc(50% - (var(--col-spacer) / 2)); --label-text-color: #262626; --comment-text-color: #646464; --warning-text-color: #646464; --btn-text-color: FieldText; --default-font-size: 12; --label-affix-font-size: 10; --label-font-size: 12; --comment-font-size: 10; --warning-font-size: 10; --btn-font-size: 11; } .autofill-item-box[size="small"] { --item-padding-vertical: 7px; --col-spacer: 0px; --row-spacer: 3px; --item-width: 100%; } .autofill-item-box:not([ac-image=""]) { --item-padding-vertical: 6.5px; --comment-font-size: 11; } .autofill-footer, .autofill-footer[size="small"] { --item-width: 100%; --item-padding-vertical: 0; --item-padding-horizontal: 0; } .autofill-item-box { box-sizing: border-box; margin: 0; border-bottom: 1px solid rgba(38,38,38,.15); padding: var(--item-padding-vertical) 0; padding-inline: var(--item-padding-horizontal); display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; background-color: #FFFFFF; color: var(--label-text-color); } .autofill-item-box:last-child { border-bottom: 0; } .autofill-item-box > .profile-item-col { box-sizing: border-box; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; width: var(--item-width); } .autofill-item-box > .profile-label-col { text-align: start; } .autofill-item-box:not([ac-image=""]) > .profile-label-col::before { margin-inline-end: 5px; float: inline-start; content: ""; width: 16px; height: 16px; background-image: var(--primary-icon); background-size: contain; background-repeat: no-repeat; background-position: center; -moz-context-properties: fill; fill: #4D4D4D; } .autofill-item-box > .profile-label-col > .profile-label { font-size: calc(var(--label-font-size) / var(--default-font-size) * 1em); unicode-bidi: plaintext; } .autofill-item-box > .profile-label-col > .profile-label-affix { font-weight: lighter; font-size: calc(var(--label-affix-font-size) / var(--default-font-size) * 1em); } .autofill-item-box > .profile-comment-col { margin-inline-start: var(--col-spacer); text-align: end; color: var(--comment-text-color); } .autofill-item-box > .profile-comment-col > .profile-comment { font-size: calc(var(--comment-font-size) / var(--default-font-size) * 1em); unicode-bidi: plaintext; } .autofill-item-box[size="small"] { flex-direction: column; } .autofill-item-box[size="small"] > .profile-comment-col { margin-top: var(--row-spacer); text-align: start; } .autofill-footer { padding: 0; flex-direction: column; } .autofill-footer > .autofill-footer-row { display: flex; justify-content: center; align-items: center; width: var(--item-width); } .autofill-footer > .autofill-warning { padding: 2.5px 0; color: var(--warning-text-color); text-align: center; background-color: rgba(248,232,28,.2); border-bottom: 1px solid rgba(38,38,38,.15); font-size: calc(var(--warning-font-size) / var(--default-font-size) * 1em); } .autofill-footer > .autofill-button { box-sizing: border-box; padding: 0 10px; min-height: 40px; background-color: #EDEDED; font-size: calc(var(--btn-font-size) / var(--default-font-size) * 1em); color: var(--btn-text-color); text-align: center; } .autofill-footer[no-warning="true"] > .autofill-warning { display: none; } .autofill-insecure-item { box-sizing: border-box; padding: 4px 0; display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center; color: GrayText; } .autofill-insecure-item::before { display: block; margin-inline: 4px 8px; content: ""; width: 16px; height: 16px; background-image: url(chrome://global/skin/icons/connection-mixed-active-loaded.svg); -moz-context-properties: fill; fill: GrayText; } PK �������!<o'����)���chrome/content/skin/autocomplete-item.css/* 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/. */ @namespace url("http://www.w3.org/1999/xhtml"); @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); .autofill-item-box { --default-font-size: 12; } @media (-moz-windows-default-theme: 0) { xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box { background-color: Highlight; } .autofill-item-box { --label-text-color: FieldText; --comment-text-color: GrayText; } } PK �������!<X��X��#���chrome/content/skin/editAddress.css/* 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/. */ .editAddressForm { display: flex; flex-wrap: wrap; /* Use space-between so --grid-column-row-gap is in between the elements on a row */ justify-content: space-between; } dialog:not([subdialog]) .editAddressForm { margin-inline: calc(var(--grid-column-row-gap) / -2); } .editAddressForm .container { /* !important is needed to override preferences.css's generic label rule. */ margin-top: var(--grid-column-row-gap) !important; margin-inline: calc(var(--grid-column-row-gap) / 2); flex-grow: 1; } #country-container { /* The country dropdown has a different intrinsic (content) width than the other fields which are <input>. */ flex-basis: calc(50% - var(--grid-column-row-gap)); flex-grow: 0; /* Country names can be longer than 50% which ruins the symmetry in the grid. */ max-width: calc(50% - var(--grid-column-row-gap)); } /* Begin name field rules */ #name-container input { /* Override the default @size="20" on <input>, which acts like a min-width, not * allowing the fields to shrink with flexbox as small as they need to to match * the other rows. This is noticeable on narrow viewports e.g. in the * PaymentRequest dialog on Linux due to the larger font-size. */ width: 0; } /* When there is focus within any of the name fields, the border of the inputs * should be the focused color, except for inner ones which get overriden below. */ #name-container:focus-within input { border-color: var(--in-content-border-focus); } /* Invalid name fields should show the error outline instead of the focus border */ #name-container:focus-within input:-moz-ui-invalid { border-color: transparent; } #given-name-container, #additional-name-container, #family-name-container { display: flex; /* The 3 pieces inside the name container don't have the .container class so need to set flex-grow themselves. See `.editAddressForm .container` */ flex-grow: 1; /* Remove the bottom margin from the name containers so that the outer #name-container provides the margin on the outside */ margin-bottom: 0 !important; margin-inline: 0; } /* The name fields are placed adjacent to each other. Remove the border-radius on adjacent fields. */ #given-name:dir(ltr), #family-name:dir(rtl) { border-top-right-radius: 0; border-bottom-right-radius: 0; border-right-width: 0; } #given-name:dir(rtl), #family-name:dir(ltr) { border-top-left-radius: 0; border-bottom-left-radius: 0; border-left-width: 0; } #additional-name { border-radius: 0; /* This provides the inner separators between the fields and should never * change to the focused color. */ border-inline-color: var(--in-content-box-border-color) !important; } /* Since the name fields are adjacent, there isn't room for the -moz-ui-invalid box-shadow so raise invalid name fields and their labels above the siblings so the shadow is shown around all 4 sides. */ #name-container input:-moz-ui-invalid, #name-container input:-moz-ui-invalid ~ .label-text { z-index: 1; } /* End name field rules */ #name-container, #street-address-container { /* Name and street address are always full-width */ flex: 0 1 100%; } #street-address { resize: vertical; } #country-warning-message { box-sizing: border-box; font-size: 1rem; display: flex; align-items: center; text-align: start; opacity: .5; padding-inline-start: 1em; } dialog:not([subdialog]) #country-warning-message { display: none; } PK �������!<q����&���chrome/content/skin/editCreditCard.css/* 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/. */ .editCreditCardForm { display: grid; grid-template-areas: "cc-number cc-exp-month cc-exp-year" "cc-name cc-type cc-csc" "billingAddressGUID billingAddressGUID billingAddressGUID"; grid-template-columns: 4fr 2fr 2fr; grid-row-gap: var(--grid-column-row-gap); grid-column-gap: var(--grid-column-row-gap); } .editCreditCardForm label { /* Remove the margin on these labels since they are styled on top of the input/select element. */ margin-inline-start: 0; margin-inline-end: 0; } .editCreditCardForm .container { display: flex; } #cc-number-container { grid-area: cc-number; } #cc-exp-month-container { grid-area: cc-exp-month; } #cc-exp-year-container { grid-area: cc-exp-year; } #cc-name-container { grid-area: cc-name; } #cc-type-container { grid-area: cc-type; } #cc-csc-container { grid-area: cc-csc; } #billingAddressGUID-container { grid-area: billingAddressGUID; } #billingAddressGUID { grid-area: dropdown; } PK �������!<֢y ��y ��)���chrome/content/skin/editDialog-shared.css/* 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/. */ :root { --in-field-label-size: .8em; --grid-column-row-gap: 8px; /* Use the animation-easing-function that is defined in xul.css. */ --animation-easing-function: cubic-bezier(.07,.95,0,1); } dialog[subdialog] form { /* Add extra space to ensure invalid input box is displayed properly */ padding: 2px; } /* The overly specific input attributes are required to override padding from common.css */ form input[type="email"], form input[type="tel"], form input[type="text"], form textarea, form select { flex-grow: 1; padding-top: calc(var(--in-field-label-size) + .4em); } form input[type="tel"] { text-align: match-parent; } select { margin: 0; padding-bottom: 5px; } form label[class="container"] select { min-width: 0; } form label, form div { /* Positioned so that the .label-text and .error-text children will be positioned relative to this. */ position: relative; display: block; line-height: 1em; } /* Reset margins for inputs and textareas, overriding in-content styles */ #form textarea, #form input { margin: 0; } form :is(label, div) .label-text { position: absolute; opacity: .5; pointer-events: none; inset-inline-start: 10px; top: .2em; transition: top .2s var(--animation-easing-function), font-size .2s var(--animation-easing-function); } form :is(label, div):focus-within .label-text, form :is(label, div) .label-text[field-populated] { top: 0; font-size: var(--in-field-label-size); } form :is(input, select, textarea):focus ~ .label-text { color: var(--in-content-item-selected); opacity: 1; } /* Focused error fields should get a darker text but not the blue one since it * doesn't look good with the red error outline. */ form :is(input, select, textarea):focus:-moz-ui-invalid ~ .label-text { color: var(--in-content-text-color); } form div[required] > label .label-text::after, form :is(label, div)[required] .label-text::after { content: attr(fieldRequiredSymbol); } .persist-checkbox label { display: flex; flex-direction: row; align-items: center; margin-block: var(--grid-column-row-gap); } dialog[subdialog] form { /* Match the margin-inline-start of the #controls-container buttons and provide enough padding at the top of the form so button outlines don't get clipped. */ padding: 4px 4px 0; } #controls-container { display: flex; justify-content: end; margin: 1em 0 0; } #billingAddressGUID-container { display: none; } PK �������!<|sQw��w��"���chrome/content/skin/editDialog.css/* 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/. */ /* The save button should be on the left and cancel on the right for Windows */ #controlsContainer > #save { order: 0; } #controlsContainer > #cancel { order: 1; } PK �������!<(O����+���chrome/content/third-party/cc-logo-amex.pngPNG  ��� IHDR���������;0��IDATxWS4Im۶mlm۶m[cm هDdvvfk˳{ؒ95i2 ZSwb@.giM=Zr g IkB$>ӸvFc+.kuĺ&V!;z$~ ˄sQ #_'P;[!kQh^:69ؕ7{WC\xaP5Q|26 ^eyH-ÒC3@AbF۬,IpUw}D|U[\p)SYV g^56IZC*_bF{? z+>͍;:7_Ёk 85WvB32巬V5=}wd ꢸK&.eʼnzxP 8b{wD?*U\΁ W&Ck6D|PLez [ڱ/<c!9U`b\tw\/& s2@JDR?&l~7'#"fMA|51]TO=hC#q;K--LKu<'ם.sU)l[j .J+#h(�1(TTEXTh?b8hլ^ٻZ׽R{lt(kn65"? 1*f㷗S͔]I;J0eo)0de.?o,w3'&>h$÷cӽe0|KZR :7Vtb TuYI:v'Ik,><ۃꈤ-%k/{$w6Y{W-̞[RO{ڨl#SxV7K XSp6. .maʶ1һQuB[E=YgAf1rf6iNj[%kOx\'k69!fRg|=-_ff,nI!饬$9t<YeD5L]%.;4R{qe>NknBE-ŸmP@8Mר-SE״qZ[ giD"G{gtz,~"z{;3BB3:yub|A`3@2}rx0vp5VmͤtFziAbB^N8[z<e8A8xm_-&]r 93iRIM����IENDB`PK �������!<Ba �� ��.���chrome/content/third-party/cc-logo-amex@2x.pngPNG  ��� IHDR���<���<���:r��IDATxZc+Kͳm۶m3l۶m0m$՞5bZu0]]2 Z>w9SYX-,%^X#Zrɒ[95 X4 8 8 8 ܜBoϩĵK6�']Iu;Mx\KK|I+WV-HbLym`L~jwQ*O+MW|Lt޽Ś9)2随 RTOr"OlY�ѢtlMvJZT?Da:ǢQ''n\]nxġA R=FkW&RAޝ߅11v>w�XsM/a# 7~րCPמ !.+W�`{1v/N6 H { qL Z^4F5! rO-t,#(aL\ZeD. 5Mô*YiI@}&yVp >pWLXbădt"DcaY4y^&�a7EZI3e5އ pŽ&�MD�s=4Iv<+g+f 5d�oqG-rmĎu^`P�Њ9Yن8N9&CjnP/w?G] W O ?KF\r&Gß0Vqj0(^moR}4!`x3 RxH;>Y cuq[L2HN|ؗ}ה0sZO />śӊ JiΓ}AE,1$8uDB@'#n}Xlby9 �@}VAZ0#!` ?rdjUCV2' )1>HDF4220n'x/ *2Hĸx% 6ڊܡg禷S]?6}\q8%oR|o��n6DHiEҁTLvbcU�HG '�4}6qn,9&8O҂J!-_n T=r7]׍F; z:` Nc9x@8F l#Հ ixAq1-=/_㉗߬U6 aODlPi5+ sƠRxbWT: [LRx1jL;dU!)F~| Wᚚ[ZjxHg\✦GCxG<i˭T^( O߄�W< ]I=|RNVz&Wgw !>wķ˻ţ�]*F4'.҂Uc&kh#|VCjcx#ekq@9R0`ʿ_+2�h1_Ejމ/3 &A r* Ppjd[s河Shݢz!"sh,]G=V@|Ga]RO=J[,`L}.|ތJ@3mtcPjL X3 8ya PDg>>u ;Ka%ΘȿWHk!;FF07n-T/ze7s2X /x@+7ѿFw((�@i<1J4 qDBn~GmL݀^6yMqT1D{?WZn8ZBɂq aj<1|~ir]X?,]V fg*8ݴ13-hNz j:adXpP6ʋ3;}22ǥѶC1%ѹ̓oA8/.܂oy7<Oecߙ i/{GՅ5l9c?AU=)-)xEð\/^;W,ykn.-ퟥQݐ:E] ~Nؐ> LW2,2RN%Ck4wI*̿l,ų/}tjpT,,~ nqM厏-h*+0maHujw����IENDB`PK �������!<+82����4���chrome/content/third-party/cc-logo-cartebancaire.pngPNG  ��� IHDR���$������˃+���tRNS������n��IDATxX H=oR:5o_@!u'qC&0Oϻqw~㓤?彏{^'t -A(7(3 ˩iD'v*7mqҔ|B *`m8,Փbı.OLr) [eچFkuFȌv UX,i[ߌf!ڶm۶=vT۶6l?)YQi)wx`8b%H 46!X$f0[J8�"Ib9s-u.Myn` <k Mgndds}Ude0;?}CNL!Y3φᖥ/.եXL-K匝ZB]I-8дCKi}̌h/䁽\icZ۪<vk &HűoF+Zaݼ֛"wSiy^�F@J(- �9)[6! 3ٶ3e=C#tödX\Uس\I¨ ] КT `S`kAxs⌏̕R3wsi}f`bswv#fCd  !hٺV# ܲvDy<-h{F /| h@׀6֦"'+Z6LWmo]^K}UE?") iwtҀ?=$!4W\"(IZ E[~ A*>9%HLZPJ"Y�3Ì3$X?4e3d"?s{mʫ6ݎ5cąEd1zzLe0ŀ&h$ t|ʱ2w٩./t:B !PUvv]nQOHɁ� =#Ḩl"{YcaFճ;'3f7d@ɳs&%$׆ËJ`yB P͹3ZAf?eMȫƲf;')-2DM "V&Ԫ FCJ? ;>49;I6o7U+֛A� jbk?Ǐ%����IENDB`PK �������!<ۺ' ��' ��7���chrome/content/third-party/cc-logo-cartebancaire@2x.pngPNG  ��� IHDR���H���<���J<k���tRNS������n�� IDATxIDAЌے$.}<\u][Ƭ6:8lH)cH\YC lکq@ijuԸ?):vG'駅&#B?nK*J+02 #ox=DM|] <PY;A 9JeQMP|HyYRɺּ~cefA᪹ ӵ8mnw8Ā%HY {FQ(A@}lH ]2{qjY&gϚ?r oUZ5E &MRm'ID:7*6�|߼øa!'sk^"Moe0kI;_3aG<0\ߤ"D`T5F>lAc<js´*&fwkR Qʦ,_>f z oi͞tDш)Ll&Z3bWૐR^-\ ]e|d�4tK0&]٥z`"=b$޳qLϟ-,F�~?vT'd[r+a˯{aN_;?-�%Eὓtsl۶m۶g۶msm޻h zr:U=oPIYNuĊ:ȟBUq)R�c_ſaLgl,fZypͲ 5kkˌ];'==93ݙ*` N!(\,-t�{V* l F?MnjdɆʃVYbU_up}E<?=FNob[/BlSBf:A0eE~o7ɏ;u'�*d o|FD u+ l~mœJ'BsU11õ_*4[.wA(cOl(+sX0cGgR;|0%.5w]樬.$Y aۭ#n}sc-<+^$ ۜɈfhMJz_@JR/k!�9W|wS b�?4.L#kyj-ݪJauWXЛ;oD[7ӬR0 bE$7]iϏVZ|QO9 0b̂*KܒR]:MJXyY|QeB<Px;ק*@DJZcGRT${(@L(EZƐ Aֲ0D|7=prES $l~ŇčAԬ;yh}傞"}<P992]o` � U+ |f'Azϧ3n`'Vw>C@Dm �6~6hƼ&̸xE%ay!a0&?Cۓg tNAZ)9Wd :"p�Ks@bߊ]ؓki[Ք=ǁG (7A X.NyS(6~\1W}8 E wъ Gvݧ�ӊXVP /Ȭܿro}DLˬgjj Dؒߚz7`wo_ָ9#g CQDA(`RkG/nI/9da�83uƙ.Pz=\ﮀrU>` kmߌddh|(vPCM:TՂħN2!ms1�td&O+è񛮲ju%`Ej*RWQ1 >)󹳳f^ϽZc6Z(ʀaHhjռY," Ӡr "b Ud@\ p,lCn1m) :*DԊʉCX%CwfNyvҏ%K{К "Yh-. "@Z9WkZҗvWޟنđ1ma*8@,L֛>_Jķ}caLP"Y3xào BM=a]^zmwyeꇛLF$sߛɄ%&0J|�E�ՠ0nULN3GlR~ޜ1FX�I}$Z}7}?/&ۘ0:/(Z>cY-_6i%sٕ2|XeuL?oZ*ܹ/N}OBń cN%o͜=#tES&{s-Si (6BR蒸P Đ>&thh`SE5K6^ߟߛוJtg=JkXg4d=<oZFP K[hQmd$  3^1@&s`$|�=;/8=9PNzA8a?<Da �us�{>�GL-�06 6@ `~ fR?TMί;+TTBF*8E?L8$_L$E$׿IWM*Ք| "TA(y#UQRy#PrDV*M_Pw9P&<j"`8q21.Ie@`3&`738~QI*w+JT4Eɉ8D? E"?fQ|!RJG42L]gȉ,Au @ED'T_%81TLEяELrYti[(:AQR^OD)RO]VzoL,T ٵ dT3/cZNl @T%$> P:co_ԁB����IENDB`PK �������!<u+����-���chrome/content/third-party/cc-logo-diners.svg<svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path d="M19.863 20.068c4.698.022 8.987-3.839 8.987-8.536 0-5.137-4.289-8.688-8.987-8.686h-4.044c-4.755-.002-8.669 3.55-8.669 8.686 0 4.698 3.914 8.559 8.669 8.536h4.044z" fill="#4186CD"/><path d="M15.76 3.535a7.923 7.923 0 0 0 0 15.844 7.923 7.923 0 0 0 0-15.844zm-4.821 7.75c.004-2.122 1.288-3.931 3.1-4.65v9.3c-1.812-.719-3.096-2.527-3.1-4.65zm6.544 4.65v-9.3c1.811.717 3.097 2.527 3.1 4.65-.003 2.123-1.289 3.931-3.1 4.65z" fill="#FFF"/><g fill="#211E1F"><path d="M.65 22.925c0-.71-.375-.663-.733-.671v-.205c.31.015.63.015.94.015.336 0 .79-.015 1.381-.015 2.065 0 3.19 1.365 3.19 2.763 0 .782-.462 2.748-3.286 2.748-.407 0-.782-.016-1.157-.016-.358 0-.71.008-1.068.016v-.205c.478-.048.71-.064.733-.6v-3.83zm.644 3.636c0 .586.437.654.825.654 1.713 0 2.275-1.24 2.275-2.373 0-1.422-.951-2.449-2.48-2.449-.326 0-.476.022-.62.03v4.138zM5.428 27.364h.152c.225 0 .387 0 .387-.25v-2.041c0-.332-.121-.378-.419-.528v-.12c.378-.107.83-.249.861-.272a.301.301 0 0 1 .145-.038c.04 0 .057.046.057.106v2.893c0 .25.177.25.402.25h.137v.196c-.274 0-.556-.015-.845-.015-.29 0-.58.007-.877.015v-.196zm.689-4.627a.36.36 0 0 1-.345-.35c0-.177.169-.338.345-.338.182 0 .344.148.344.337 0 .19-.155.351-.344.351zM7.993 25.117c0-.278-.084-.353-.438-.496v-.143c.325-.106.634-.204.996-.363.022 0 .045.016.045.076v.49c.43-.309.8-.566 1.307-.566.64 0 .867.468.867 1.055v1.944c0 .25.166.25.377.25h.136v.196c-.265 0-.528-.015-.8-.015s-.544.007-.815.015v-.196h.136c.211 0 .362 0 .362-.25v-1.95c0-.43-.263-.642-.694-.642-.241 0-.626.196-.876.362v2.23c0 .25.166.25.378.25h.136v.196c-.264 0-.529-.015-.8-.015-.272 0-.544.007-.816.015v-.196h.137c.21 0 .362 0 .362-.25v-1.997zM11.943 25.569c-.017.072-.017.192 0 .465.049.762.553 1.388 1.212 1.388.453 0 .809-.24 1.113-.537l.115.113c-.38.489-.849.906-1.525.906-1.31 0-1.575-1.236-1.575-1.75 0-1.573 1.089-2.039 1.665-2.039.668 0 1.386.41 1.394 1.26 0 .05 0 .097-.008.145l-.074.049h-2.317zm1.514-.42c.212 0 .237-.077.237-.147 0-.3-.264-.542-.742-.542-.52 0-.877.264-.98.689h1.485zM14.383 27.364h.191c.198 0 .34 0 .34-.25v-2.117c0-.233-.262-.279-.368-.339v-.113c.516-.234.799-.43.863-.43.042 0 .063.023.063.099v.678h.015c.176-.294.474-.777.905-.777.176 0 .402.128.402.4 0 .203-.133.385-.331.385-.22 0-.22-.182-.468-.182-.12 0-.516.174-.516.626v1.77c0 .25.142.25.34.25h.395v.196c-.389-.008-.684-.015-.99-.015-.289 0-.586.007-.84.015v-.196zM17.282 26.668c.102.53.418.98.996.98.465 0 .64-.29.64-.57 0-.948-1.724-.643-1.724-1.935 0-.45.357-1.028 1.226-1.028.252 0 .592.073.9.234l.056.818h-.182c-.079-.505-.355-.795-.862-.795-.316 0-.616.185-.616.53 0 .94 1.834.65 1.834 1.91 0 .53-.42 1.092-1.36 1.092a2.06 2.06 0 0 1-.964-.272l-.087-.924.143-.04zM26.431 23.625h-.192c-.147-.94-.786-1.318-1.649-1.318-.886 0-2.173.618-2.173 2.548 0 1.626 1.11 2.792 2.296 2.792.763 0 1.395-.547 1.55-1.392l.176.048-.177 1.175c-.323.21-1.194.426-1.703.426-1.802 0-2.942-1.214-2.942-3.024 0-1.649 1.41-2.831 2.92-2.831.623 0 1.224.21 1.817.427l.077 1.15zM26.783 27.36h.153c.226 0 .387 0 .387-.253v-4.268c0-.498-.12-.514-.427-.598v-.123c.322-.099.66-.237.83-.33.087-.045.152-.084.176-.084.05 0 .065.046.065.108v5.295c0 .254.177.254.403.254h.136v.199c-.273 0-.555-.016-.845-.016-.29 0-.58.008-.878.016v-.2zM31.775 27.032c0 .136.084.143.214.143.092 0 .206-.007.305-.007v.159c-.328.03-.955.188-1.1.233l-.038-.023v-.61c-.458.37-.81.633-1.353.633-.412 0-.84-.264-.84-.897v-1.93c0-.196-.03-.384-.457-.422v-.143c.275-.007.885-.053.984-.053.085 0 .085.053.085.219v1.944c0 .226 0 .874.664.874.26 0 .604-.195.924-.458v-2.029c0-.15-.366-.233-.64-.309v-.135c.686-.046 1.115-.106 1.19-.106.062 0 .062.053.062.136v2.78zM33.372 24.72c.323-.27.76-.572 1.206-.572.94 0 1.505.804 1.505 1.671 0 1.042-.776 2.085-1.935 2.085-.599 0-.914-.191-1.125-.278l-.243.182-.169-.087a9.26 9.26 0 0 0 .113-1.416v-3.422c0-.518-.122-.534-.43-.621v-.128c.325-.103.664-.246.834-.342.09-.048.154-.088.18-.088.047 0 .064.048.064.112v2.905zm-.044 2.032c0 .301.291.808.834.808.868 0 1.232-.831 1.232-1.535 0-.854-.664-1.565-1.296-1.565-.3 0-.552.19-.77.372v1.92z"/></g></g></svg>PK �������!<Ȋ+]��]��/���chrome/content/third-party/cc-logo-discover.pngPNG  ��� IHDR���%������#m��$IDATxb`GDD�‘.y{{?�b//y @CD䨶:YH:Gյ+8Y k<{fm۶m<۶m۶mw}'kOݺ'8ݻLP+WdܸqXXl9tR̕+W]v3p@>|χGfp@ہח_ Dׯ_gl߾qX,nܸ)SZ\|{*=zX>SNjF9ڵ+7\r\x\r@ylXx1{ٳdȐ)`ȑ>~j"4Lλfl!olܸQ͚54iժUUV=wM4i0͘L&.]*X B/*S D%Xv-ٳgHڵ<y2͑</|jħo:\ 3o(j֪/Ȝ93_~Emۖ r!͋/P<�%'ׯON(TvSV-t_t`'çaFlU/>֭*UDžSra&MJD9sfL TܹY|9#k֬۷<yzjJefBΕ6:u;g; RLM:; ΃ⶾqMD ɑr1ĈcJ`P0Wz.ӧOrbŊ,YRr+cwط54}Ђ#8rH2(ϐNH>+0oߞG@xm#JT=K cNi^pjSJ{)PF'fIG"?iPF%GlС q ix\űg OBip]߀}UcI &N4^t(cR7PuV1Cr%v4ϫ8ž%<(cR &#%ki“3=:TZUEјƦfN'8:o3[ %%ؽyqJ}e @龇}OuN Pz$+0(��[#0"Q����IENDB`PK �������!<v �� ��2���chrome/content/third-party/cc-logo-discover@2x.pngPNG  ��� IHDR���J���<���,�� nIDATxpd[3cl۶m#cm5d ^YՓ[AϳsWծ\|g}vn+Ydɒ%rN8::7)1x[[[_a֏VS._FRRll<<<JeYd/N"(eYd�%AɠdP2)2_�} ruu]!eˠdP"(V;w۠ݻ|UFFx8q݋wc`Izϟe/шRd"^\�1; uJqMĔ{JrƹݘXZ8źuҞ xQZuXYYJ*T(0h $$$XQF ҥKxwwhUVE푙b9r9KVfMYźyv6x?zQ_Z687{C8;;vڈDY 0�իWqeQ̥Z>uBY麧'|}}ƾ} ƍ YfK ]xxmĈ(**G} OjU|aÆo߾[@0x\猇H@J7R{"(F}v~I@?cs< Zh-[6{lzlo^v$h3fÇӠ*U^xRDFF{=t:cƌAbb"=P,1ɛ6Ϗ7ŊC3Hy ;>& d(S> d P [#2,'HȥB޼y`~-hwAYBj#00|テE,7> 6taǣFs>}:g}·H!8 !z @ dP g'^!K/lsѢE~ ^[z5(_gNwӸP<@}ʕ|oE1C+,<<f.|Y:tիWA1W:W̒ 8;ķt1{P/ @^@$}9NbɊ5`_~%(ʕ+3=0JۃH5;0Gn޼֭/,2l),,(`ӻx+vui<5k,I!˗Hf4@�@(/NP o�@a0'bw!3' gB`d1ydP%Aƛoz  0aZ$ommÆq޿SɵI(Iw9O0&M{'ʘvH挬!h�4@AԳ1<(v[jaľˁ"$7E/{gFTN$b͟?%L,J:ydɺ۫3;wrPbk<$^논0+. * ֺA+%hݺ5~63MbYbk ܊UVk׮hɜc֭.Կܿ?,Q*M<83Kl&i͐?L5]�#�-vtK`TAL&֫v9>kΝ;{nЮYYYR2ONNf9cf]A1$YH{D޽ŶCCCAe=zEsB \7hA/rƍ%o%uimۖ9`x^\HK%˖2zʔ)ԱcubFxuEաC[OxB&M߿IBnwgF0DO), аaCXem۶  G*+˞'EHRX)6�\A*xn߂3eP)Y ZƸj22e&@k4T DXw caP T< Jʆ>r>3>y ȝh2(3,B= 3 _dPdO͂zg�Z ʘA?#/A2(cNCuTA>A w0Å,knNގ.FؠnD}hl@Eeʌ>j4Kpbë"euݠ~ ELL ʔ-n u; -aNrOe2˜q]ߝZ \\@4(Jhy zE%8 ^ c 5+ꙟsʅUEexEY ݶ!*9r|èr@!OLaCwf.:V@h6?7h9<Y{4w ƃP.덄o7ečrD[čt@h:n\)ǻ#j+_C[omm|UZJVJjZ6ͺV凕+[ PV!ȠdP2(e3^u����IENDB`PK �������!<7+~����*���chrome/content/third-party/cc-logo-jcb.svg<svg width="30" height="30" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M3.622.575C1.734.575.009 2.278.009 4.188c0 1.051 0 5.212-.002 9.348.346.217 2.01.752 2.68.799 1.466.103 2.375-.381 2.515-1.603l-.007-4.538h3.243v4.434c-.17 2.54-2.399 3.057-5.79 2.887-.877-.046-2.07-.27-2.64-.42L.004 22.94H5.65c1.54 0 3.544-1.439 3.544-3.627V.575H3.622z" id="a"/><linearGradient x1="-.003%" y1="49.999%" x2="100.002%" y2="49.999%" id="b"><stop stop-color="#313477" offset="0%"/><stop stop-color="#0077BC" offset="100%"/></linearGradient><path d="M0 1.564l.007.001V.007L0 .002v1.562z" id="d"/><linearGradient x1="0%" y1="50.019%" x2="1.21%" y2="50.019%" id="e"><stop stop-color="#313477" offset="0%"/><stop stop-color="#0077BC" offset="100%"/></linearGradient><path d="M3.976.575C2.088.575.363 2.278.363 4.188v4.945c1.132-.885 2.958-1.319 5.281-1.14 1.322.102 2.3.286 2.834.445v1.57c-.588-.294-1.748-.73-2.715-.8-2.191-.158-3.342.868-3.342 2.528 0 1.494.888 2.773 3.331 2.602.806-.056 2.148-.525 2.719-.797l.007 1.523c-.492.155-2.02.488-3.458.5-2.165.017-3.694-.443-4.659-1.189L.36 22.941h5.643c1.54 0 3.546-1.439 3.546-3.627V.575H3.976z" id="g"/><linearGradient x1=".004%" y1="49.999%" x2="99.996%" y2="49.999%" id="h"><stop stop-color="#753136" offset="0%"/><stop stop-color="#ED1746" offset="100%"/></linearGradient><path d="M.123.448L.119 2.424l2.21.007c.43 0 .97-.368.97-1.01a.97.97 0 0 0-.967-.973c-.308.003-.8.001-1.245 0L.375.446C.26.446.17.446.123.448" id="j"/><linearGradient x1="0%" y1="50.008%" x2="99.996%" y2="50.008%" id="k"><stop stop-color="#008049" offset="0%"/><stop stop-color="#62BA44" offset="100%"/></linearGradient><path d="M.115.473l-.008 1.8 2.089.014c.346-.007.834-.325.834-.882 0-.567-.426-.95-.88-.939-.296.008-.702.005-1.078.002L.52.465C.333.465.187.467.115.473" id="m"/><linearGradient x1=".022%" y1="49.994%" x2="100.012%" y2="49.994%" id="n"><stop stop-color="#008049" offset="0%"/><stop stop-color="#62BA44" offset="100%"/></linearGradient><path d="M3.694.575C1.806.575.08 2.278.08 4.188L.08 8.164h5.365c1.067 0 2.324.457 2.324 1.754 0 .696-.37 1.485-1.706 1.74v.03c.78 0 2.132.457 2.132 1.833 0 1.423-1.46 1.817-2.243 1.817l-5.873.006-.001 7.597H5.72c1.54 0 3.544-1.439 3.544-3.627V.575H3.694z" id="p"/><linearGradient x1="-.007%" y1="49.999%" x2="100.004%" y2="49.999%" id="q"><stop stop-color="#008049" offset="0%"/><stop stop-color="#62BA44" offset="100%"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><g transform="translate(0 3.013)"><mask id="c" fill="#fff"><use xlink:href="#a"/></mask><path d="M3.622.575C1.734.575.009 2.278.009 4.188c0 1.051 0 5.212-.002 9.348.346.217 2.01.752 2.68.799 1.466.103 2.375-.381 2.515-1.603l-.007-4.538h3.243v4.434c-.17 2.54-2.399 3.057-5.79 2.887-.877-.046-2.07-.27-2.64-.42L.004 22.94H5.65c1.54 0 3.544-1.439 3.544-3.627V.575H3.622z" fill="url(#b)" mask="url(#c)"/></g><g transform="translate(0 16.543)"><mask id="f" fill="#fff"><use xlink:href="#d"/></mask><path d="M0 1.564l.007.001V.007L0 .002v1.562z" fill="url(#e)" mask="url(#f)"/></g><g transform="translate(10 3.013)"><mask id="i" fill="#fff"><use xlink:href="#g"/></mask><path d="M3.976.575C2.088.575.363 2.278.363 4.188v4.945c1.132-.885 2.958-1.319 5.281-1.14 1.322.102 2.3.286 2.834.445v1.57c-.588-.294-1.748-.73-2.715-.8-2.191-.158-3.342.868-3.342 2.528 0 1.494.888 2.773 3.331 2.602.806-.056 2.148-.525 2.719-.797l.007 1.523c-.492.155-2.02.488-3.458.5-2.165.017-3.694-.443-4.659-1.189L.36 22.941h5.643c1.54 0 3.546-1.439 3.546-3.627V.575H3.976z" fill="url(#h)" mask="url(#i)"/></g><g transform="translate(22.353 14.778)"><mask id="l" fill="#fff"><use xlink:href="#j"/></mask><path d="M.123.448L.119 2.424l2.21.007c.43 0 .97-.368.97-1.01a.97.97 0 0 0-.967-.973c-.308.003-.8.001-1.245 0L.375.446C.26.446.17.446.123.448" fill="url(#k)" mask="url(#l)"/></g><g transform="translate(22.353 11.837)"><mask id="o" fill="#fff"><use xlink:href="#m"/></mask><path d="M.115.473l-.008 1.8 2.089.014c.346-.007.834-.325.834-.882 0-.567-.426-.95-.88-.939-.296.008-.702.005-1.078.002L.52.465C.333.465.187.467.115.473" fill="url(#n)" mask="url(#o)"/></g><g transform="translate(20.588 3.013)"><mask id="r" fill="#fff"><use xlink:href="#p"/></mask><path d="M3.694.575C1.806.575.08 2.278.08 4.188L.08 8.164h5.365c1.067 0 2.324.457 2.324 1.754 0 .696-.37 1.485-1.706 1.74v.03c.78 0 2.132.457 2.132 1.833 0 1.423-1.46 1.817-2.243 1.817l-5.873.006-.001 7.597H5.72c1.54 0 3.544-1.439 3.544-3.627V.575H3.694z" fill="url(#q)" mask="url(#r)"/></g></g></svg>PK �������!<^AEH ��H ��1���chrome/content/third-party/cc-logo-mastercard.svg<svg width="38" height="30" xmlns="http://www.w3.org/2000/svg"><g fill-rule="nonzero" fill="none"><path d="M7.485 29.258v-1.896a1.125 1.125 0 0 0-1.188-1.2 1.17 1.17 0 0 0-1.061.537 1.109 1.109 0 0 0-.999-.537.998.998 0 0 0-.885.448v-.373h-.657v3.021h.664v-1.662a.708.708 0 0 1 .74-.802c.435 0 .656.284.656.796v1.68h.664v-1.674a.71.71 0 0 1 .74-.802c.448 0 .663.284.663.796v1.68l.663-.012zm9.817-3.02h-1.08v-.917h-.664v.916h-.6v.6h.613v1.391c0 .701.271 1.119 1.049 1.119.29 0 .575-.08.821-.234l-.19-.563a1.213 1.213 0 0 1-.58.17c-.317 0-.437-.201-.437-.505v-1.377h1.074l-.006-.6zm5.605-.076a.891.891 0 0 0-.796.442v-.367h-.65v3.021h.656v-1.693c0-.5.215-.778.632-.778.14-.002.28.024.411.076l.202-.632a1.406 1.406 0 0 0-.467-.082l.012.013zm-8.474.316a2.26 2.26 0 0 0-1.232-.316c-.765 0-1.264.366-1.264.966 0 .493.367.797 1.043.891l.316.045c.36.05.53.145.53.316 0 .234-.24.366-.688.366-.361.01-.715-.1-1.005-.316l-.316.512a2.18 2.18 0 0 0 1.308.392c.872 0 1.378-.41 1.378-.986 0-.575-.398-.809-1.056-.904l-.316-.044c-.284-.038-.511-.095-.511-.297 0-.202.214-.354.575-.354.333.004.659.093.947.26l.291-.531zm17.602-.316a.891.891 0 0 0-.796.442v-.367h-.65v3.021h.656v-1.693c0-.5.215-.778.632-.778.14-.002.28.024.411.076l.202-.632a1.406 1.406 0 0 0-.467-.082l.012.013zm-8.467 1.58a1.526 1.526 0 0 0 1.611 1.58 1.58 1.58 0 0 0 1.087-.36l-.316-.532a1.327 1.327 0 0 1-.79.272.97.97 0 0 1 0-1.934c.286.003.563.099.79.272l.316-.53a1.58 1.58 0 0 0-1.087-.361 1.526 1.526 0 0 0-1.611 1.58v.012zm6.155 0v-1.505h-.658v.367a1.147 1.147 0 0 0-.948-.442 1.58 1.58 0 0 0 0 3.16c.37.013.722-.152.948-.443v.366h.658v-1.504zm-2.446 0a.913.913 0 1 1 .916.966.907.907 0 0 1-.916-.967zm-7.93-1.58a1.58 1.58 0 1 0 .044 3.16c.454.023.901-.124 1.254-.411l-.316-.487c-.25.2-.559.311-.878.316a.837.837 0 0 1-.904-.74h2.243v-.252c0-.948-.587-1.58-1.434-1.58l-.01-.006zm0 .587a.749.749 0 0 1 .764.733h-1.58a.777.777 0 0 1 .803-.733h.012zm16.464.999v-2.724h-.632v1.58a1.147 1.147 0 0 0-.948-.442 1.58 1.58 0 0 0 0 3.16c.369.013.722-.152.948-.443v.366h.632v-1.497zm1.096 1.07a.316.316 0 0 1 .218.086.294.294 0 0 1-.098.487.297.297 0 0 1-.12.025.316.316 0 0 1-.284-.183.297.297 0 0 1 .066-.329.316.316 0 0 1 .228-.085h-.01zm0 .535a.224.224 0 0 0 .165-.07.234.234 0 0 0 0-.316.234.234 0 0 0-.165-.07.237.237 0 0 0-.167.07.234.234 0 0 0 0 .316.234.234 0 0 0 .076.05c.032.015.066.021.101.02h-.01zm.02-.376a.126.126 0 0 1 .082.025c.02.016.03.041.028.066a.076.076 0 0 1-.022.057.11.11 0 0 1-.066.029l.091.104h-.072l-.086-.104h-.028v.104h-.06v-.278l.132-.003zm-.07.054v.075h.07a.066.066 0 0 0 .037 0 .032.032 0 0 0 0-.028.032.032 0 0 0 0-.028.066.066 0 0 0-.038 0l-.07-.02zm-3.476-1.283a.913.913 0 1 1 .917.967.907.907 0 0 1-.917-.967zm-22.19 0v-1.51h-.657v.366a1.147 1.147 0 0 0-.948-.442 1.58 1.58 0 1 0 0 3.16c.369.013.722-.152.948-.443v.366h.657v-1.497zm-2.445 0a.913.913 0 1 1 .916.967.907.907 0 0 1-.922-.967h.006z" fill="#231F20"/><path fill="#FF5F00" d="M14.215 3.22h9.953v17.886h-9.953z"/><path d="M14.847 12.165a11.356 11.356 0 0 1 4.345-8.945 11.375 11.375 0 1 0 0 17.886 11.356 11.356 0 0 1-4.345-8.941z" fill="#EB001B"/><path d="M37.596 12.165a11.375 11.375 0 0 1-18.404 8.941 11.375 11.375 0 0 0 0-17.886 11.375 11.375 0 0 1 18.404 8.941v.004zM36.51 19.265v-.412h.148v-.085h-.376v.085h.161v.412h.066zm.73 0v-.497h-.115l-.132.355-.133-.355h-.101v.497h.082v-.373l.123.323h.086l.123-.323v.376l.066-.003z" fill="#F79E1B"/></g></svg>PK �������!<x����*���chrome/content/third-party/cc-logo-mir.svg<svg width="36" height="30" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient x1="100%" y1="312.751%" x2=".612%" y2="312.751%" id="a"><stop stop-color="#1E5CD8" offset="0%"/><stop stop-color="#02AFFF" offset="100%"/></linearGradient></defs><g fill-rule="nonzero" fill="none"><path d="M7.812 11.313l-1.326 4.593h-.227l-1.326-4.594A1.823 1.823 0 0 0 3.18 10H0v10h3.184v-5.91h.227L5.234 20H7.51l1.819-5.91h.226V20h3.185V10H9.56c-.81 0-1.522.535-1.75 1.313zM25.442 20h3.204v-2.957h3.223c1.686 0 3.122-.953 3.677-2.293H25.442V20zm-5.676-8.945l-2.241 4.855h-.227V10h-3.184v10h2.703c.712 0 1.357-.414 1.654-1.055l2.242-4.851h.227V20h3.184V10H21.42c-.712 0-1.358.414-1.655 1.055z" fill="#006848"/><path d="M32.186 0c.92 0 1.752.352 2.382.93a3.49 3.49 0 0 1 1.146 2.59c0 .21-.023.417-.058.62H29.74a4.478 4.478 0 0 1-4.272-3.124c-.007-.02-.011-.043-.02-.067-.015-.054-.027-.113-.042-.168A4.642 4.642 0 0 1 25.293 0h6.893z" fill="url(#a)" transform="translate(0 10)"/></g></svg>PK �������!<z"����/���chrome/content/third-party/cc-logo-unionpay.svg<svg width="36" height="30" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path id="a" d="M0 .04h17.771v22.433H0z"/><path id="c" d="M.134.04h18.093v22.433H.134z"/><path id="e" d="M.202.04h17.77v22.433H.202z"/></defs><g fill="none" fill-rule="evenodd"><g transform="translate(0 3.179)"><mask id="b" fill="#fff"><use xlink:href="#a"/></mask><path d="M7.023.04h8.952C17.225.04 18 1.057 17.71 2.31l-4.168 17.893c-.294 1.25-1.545 2.269-2.795 2.269h-8.95c-1.248 0-2.027-1.02-1.736-2.269l4.17-17.893C4.52 1.058 5.771.04 7.022.04" fill="#E21837" mask="url(#b)"/></g><g transform="translate(8.073 3.179)"><mask id="d" fill="#fff"><use xlink:href="#c"/></mask><path d="M7.157.04h10.294c1.25 0 .686 1.018.392 2.271l-4.167 17.893c-.292 1.25-.201 2.269-1.453 2.269H1.93c-1.252 0-2.026-1.02-1.732-2.269L4.363 2.311C4.66 1.058 5.907.04 7.157.04" fill="#00457C" mask="url(#d)"/></g><g transform="translate(17.89 3.179)"><mask id="f" fill="#fff"><use xlink:href="#e"/></mask><path d="M7.224.04h8.952c1.251 0 2.028 1.018 1.734 2.271l-4.166 17.893c-.295 1.25-1.547 2.269-2.798 2.269H2c-1.252 0-2.028-1.02-1.735-2.269L4.432 2.311C4.723 1.058 5.972.04 7.224.04" fill="#007B84" mask="url(#f)"/></g><path d="M26.582 16.428L25.49 20.04h.295l-.228.746h-.292l-.069.23h-1.038l.07-.23H22.12l.21-.69h.215l1.106-3.667.22-.739h1.06l-.111.373s.282-.203.55-.272c.266-.07 1.801-.096 1.801-.096l-.227.734h-.362zm-1.866 0l-.28.923s.315-.142.484-.189c.174-.046.434-.061.434-.061l.203-.673h-.841zm-.42 1.38l-.29.96s.321-.163.492-.215c.174-.039.438-.072.438-.072l.205-.673h-.845zm-.675 2.24h.844l.242-.81h-.841l-.245.81z" fill="#FEFEFE"/><path d="M27.05 15.694h1.13l.012.42c-.008.072.054.106.186.106h.23l-.21.695h-.612c-.528.038-.73-.19-.715-.445l-.022-.776zM27.2 18.993H26.12l.185-.619h1.232l.175-.566h-1.216l.207-.698h3.384l-.21.698h-1.135l-.178.566h1.139l-.19.619h-1.229l-.219.26h.5l.121.78c.014.078.014.13.04.162.025.028.175.042.262.042h.152l-.231.759h-.385c-.058 0-.147-.005-.27-.01-.114-.01-.195-.077-.273-.116a.367.367 0 0 1-.202-.265l-.12-.778-.56.766c-.177.243-.417.428-.824.428h-.782l.205-.677h.3a.484.484 0 0 0 .218-.063.336.336 0 0 0 .166-.138l.816-1.15zM15.397 17.298h2.855l-.211.68H16.9l-.179.581h1.168l-.213.702h-1.167l-.284.945c-.034.104.278.117.39.117l.584-.08-.235.778H15.65c-.106 0-.185-.015-.299-.04a.312.312 0 0 1-.209-.153c-.048-.077-.122-.14-.071-.305l.378-1.25H14.8l.215-.714h.65l.173-.581h-.648l.207-.68zM17.317 16.074h1.171l-.212.712h-1.6l-.173.15c-.075.072-.1.042-.198.094-.09.045-.28.136-.525.136h-.513l.207-.684h.154c.13 0 .219-.012.264-.04a.617.617 0 0 0 .171-.222l.296-.535h1.163l-.205.389zM18.991 15.694h.997l-.146.502s.316-.252.536-.343c.22-.081.716-.154.716-.154l1.615-.01-.55 1.832a2.139 2.139 0 0 1-.269.608.7.7 0 0 1-.271.251 1.02 1.02 0 0 1-.375.126c-.106.008-.27.01-.496.014h-1.556l-.437 1.447c-.042.144-.061.213-.034.252a.18.18 0 0 0 .148.073l.686-.065-.235.794h-.766c-.245 0-.422-.006-.547-.015-.118-.01-.242 0-.325-.063-.07-.063-.18-.147-.177-.231.007-.078.04-.209.09-.389l1.396-4.63zm2.117 1.848h-1.634l-.1.33h1.414c.167-.02.202.004.216-.004l.104-.326zm-1.545-.297s.32-.292.867-.387c.124-.023.9-.015.9-.015l.119-.392h-1.647l-.24.794z" fill="#FEFEFE"/><path d="M21.899 18.648l-.093.44c-.04.137-.073.24-.177.328-.11.093-.237.19-.536.19l-.554.023-.005.497c-.005.14.032.126.054.149.026.025.049.035.073.045l.175-.01.529-.03-.22.726h-.606c-.425 0-.74-.01-.842-.091-.103-.065-.116-.146-.115-.286l.04-1.938h.968l-.014.397h.233c.08 0 .134-.008.167-.03a.175.175 0 0 0 .065-.1l.097-.31h.76zM8.082 8.932c-.033.158-.655 3.024-.656 3.026-.134.58-.231.993-.562 1.26a1 1 0 0 1-.66.23c-.409 0-.646-.203-.687-.587l-.007-.132.124-.781s.652-2.611.769-2.957l.01-.039c-1.27.011-1.495 0-1.51-.02-.009.028-.04.19-.04.19l-.666 2.943-.057.25-.11.816c0 .242.047.44.142.607.303.53 1.168.609 1.657.609.63 0 1.222-.134 1.622-.378.694-.41.875-1.051 1.037-1.62l.075-.293s.672-2.712.786-3.065c.004-.02.006-.03.012-.039-.92.01-1.192 0-1.28-.02M11.798 14.319c-.45-.008-.61-.008-1.135.02l-.02-.04c.045-.2.095-.398.14-.6l.065-.275c.097-.425.191-.92.202-1.072.01-.09.042-.317-.218-.317-.109 0-.223.053-.339.107-.063.226-.19.863-.252 1.153-.13.61-.138.681-.197.983l-.038.041a12.946 12.946 0 0 0-1.159.02l-.024-.046c.089-.362.178-.728.263-1.091.224-.986.278-1.362.338-1.863l.044-.03c.52-.073.647-.088 1.21-.202l.048.053-.087.313c.096-.057.187-.114.283-.163.266-.13.562-.17.724-.17.248 0 .518.069.63.355.107.254.036.567-.104 1.184l-.072.316c-.144.686-.168.812-.25 1.283l-.052.041zM13.627 14.319c-.272-.002-.448-.008-.617-.002-.17.002-.335.01-.588.022l-.013-.022-.016-.024c.069-.26.106-.35.14-.443a3.13 3.13 0 0 0 .128-.449c.08-.345.128-.586.16-.797.037-.204.057-.378.085-.58l.02-.015.02-.02c.27-.037.442-.062.618-.09.177-.023.355-.06.635-.113l.01.024.008.025c-.052.214-.105.427-.156.643-.05.217-.103.43-.15.643-.101.453-.142.623-.166.745-.024.115-.03.178-.069.412l-.025.021-.024.02zM17.67 12.768c.159-.692.036-1.015-.119-1.212-.234-.3-.648-.396-1.078-.396-.258 0-.873.025-1.354.468-.345.32-.505.754-.6 1.17-.098.423-.21 1.186.492 1.47.216.093.528.118.73.118.513 0 1.04-.141 1.436-.561.305-.341.445-.848.494-1.057m-1.18-.05c-.022.117-.124.551-.262.736-.097.136-.21.219-.337.219-.037 0-.26 0-.264-.332-.002-.163.031-.33.072-.512.119-.524.258-.964.616-.964.28 0 .3.328.175.853M28.677 14.365c-.544-.004-.7-.004-1.202.017l-.031-.04c.135-.517.272-1.032.393-1.554.158-.678.194-.966.245-1.363l.041-.033c.54-.077.69-.099 1.252-.203l.016.047c-.103.426-.203.85-.304 1.278-.206.893-.281 1.346-.36 1.813l-.05.038z" fill="#FEFEFE"/><path d="M28.935 12.83c.158-.688-.479-.062-.58-.289-.154-.354-.058-1.072-.683-1.312-.24-.095-.804.027-1.29.469-.34.315-.504.747-.597 1.161-.098.418-.21 1.18.488 1.452.222.095.422.123.624.113.702-.038 1.236-1.098 1.633-1.516.305-.333.358.124.405-.079m-1.074-.05c-.027.112-.13.549-.268.732-.092.13-.311.211-.437.211-.036 0-.257 0-.264-.325a2.225 2.225 0 0 1 .073-.512c.12-.515.258-.95.616-.95.28 0 .4.316.28.843M20.746 14.319a12.427 12.427 0 0 0-1.134.02l-.02-.04c.046-.2.097-.398.144-.6l.061-.275c.099-.425.194-.92.203-1.072.01-.09.042-.317-.216-.317-.113 0-.225.053-.341.107-.062.226-.192.863-.255 1.153-.126.61-.136.681-.193.983l-.04.041a12.904 12.904 0 0 0-1.156.02l-.024-.046c.088-.362.177-.728.262-1.091.224-.986.276-1.362.339-1.863l.04-.03c.52-.073.648-.088 1.212-.202l.043.053-.08.313a4.81 4.81 0 0 1 .281-.163c.264-.13.562-.17.724-.17.244 0 .516.069.632.355.105.254.033.567-.108 1.184l-.07.316c-.15.686-.17.812-.25 1.283l-.054.041zM25.133 10.61c-.079.359-.312.66-.61.806-.247.124-.549.134-.86.134h-.201l.015-.08.37-1.608.011-.082.005-.063.149.015.782.067c.302.117.426.418.34.81m-.487-1.68l-.375.003c-.974.012-1.364.008-1.524-.011l-.04.197-.348 1.618-.874 3.597c.85-.01 1.199-.01 1.345.006.034-.161.23-1.121.232-1.121 0 0 .168-.704.178-.73 0 0 .053-.073.106-.102h.078c.732 0 1.56 0 2.209-.477.441-.328.743-.81.877-1.398.035-.144.061-.315.061-.487 0-.225-.045-.447-.176-.62-.33-.464-.99-.472-1.75-.476M33.124 11.185l-.043-.05c-.556.113-.656.131-1.167.2l-.038.038-.005.024-.002-.009c-.38.877-.37.688-.679 1.378l-.003-.084-.077-1.497-.05-.05c-.581.113-.595.131-1.133.2l-.041.038c-.006.017-.006.037-.01.059l.004.007c.067.344.05.267.118.809.032.266.073.533.105.796.053.44.083.656.147 1.327-.363.6-.449.826-.798 1.352l.022.049c.524-.02.646-.02 1.035-.02l.084-.096c.294-.633 2.531-4.47 2.531-4.47M14.12 11.556c.298-.207.335-.493.085-.641-.254-.15-.7-.102-1 .105-.3.203-.333.49-.08.642.25.146.697.103.994-.106" fill="#FEFEFE"/><path d="M30.554 15.709l-.437.75c-.139.256-.395.447-.803.448l-.696-.012.203-.674h.137c.07 0 .121-.003.16-.023.036-.012.062-.04.09-.08l.258-.409h1.088z" fill="#FEFEFE"/></g></svg>PK �������!< '"��"��+���chrome/content/third-party/cc-logo-visa.svg<svg width="44" height="30" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><path d="M22.8 9.786c-.025-1.96 1.765-3.053 3.113-3.703 1.385-.667 1.85-1.095 1.845-1.691-.01-.913-1.105-1.316-2.13-1.332-1.787-.027-2.826.478-3.652.86L21.332.938c.83-.378 2.364-.708 3.956-.722 3.735 0 6.18 1.824 6.193 4.653.014 3.59-5.02 3.79-4.985 5.395.012.486.481 1.005 1.51 1.138.508.066 1.914.117 3.506-.609l.626 2.884a9.623 9.623 0 0 1-3.329.605c-3.516 0-5.99-1.85-6.01-4.497m15.347 4.248a1.621 1.621 0 0 1-1.514-.998L31.296.428h3.733l.743 2.032h4.561l.431-2.032h3.29l-2.87 13.606h-3.038m.522-3.675l1.077-5.11h-2.95l1.873 5.11m-20.394 3.675L15.33.428h3.557l2.942 13.606h-3.556m-8.965-9.26L7.81 12.648c-.176.879-.87 1.386-1.64 1.386H.116l-.084-.395c1.242-.267 2.654-.697 3.51-1.157.523-.282.672-.527.844-1.196L7.224.428h3.76l5.763 13.606H13.01L9.31 4.774z" id="a"/><linearGradient x1="16.148%" y1="34.401%" x2="85.832%" y2="66.349%" id="b"><stop stop-color="#222357" offset="0%"/><stop stop-color="#254AA5" offset="100%"/></linearGradient></defs><g transform="matrix(1 0 0 -1 0 22.674)" fill="none" fill-rule="evenodd"><mask id="c" fill="#fff"><use xlink:href="#a"/></mask><path fill="url(#b)" fill-rule="nonzero" mask="url(#c)" d="M-4.669 12.849l44.237 16.12L49.63 1.929 5.395-14.19"/></g></svg>PK �������!<)��)�����chrome/res/FormAutofill.jsm/* 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 = ["FormAutofill"]; const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); XPCOMUtils.defineLazyModuleGetters(this, { Region: "resource://gre/modules/Region.jsm", }); const ADDRESSES_FIRST_TIME_USE_PREF = "extensions.formautofill.firstTimeUse"; const AUTOFILL_CREDITCARDS_AVAILABLE_PREF = "extensions.formautofill.creditCards.available"; const CREDITCARDS_USED_STATUS_PREF = "extensions.formautofill.creditCards.used"; const ENABLED_AUTOFILL_ADDRESSES_PREF = "extensions.formautofill.addresses.enabled"; const ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF = "extensions.formautofill.addresses.capture.enabled"; const ENABLED_AUTOFILL_CREDITCARDS_PREF = "extensions.formautofill.creditCards.enabled"; const ENABLED_AUTOFILL_CREDITCARDS_REAUTH_PREF = "extensions.formautofill.reauth.enabled"; const AUTOFILL_CREDITCARDS_HIDE_UI_PREF = "extensions.formautofill.creditCards.hideui"; const SUPPORTED_COUNTRIES_PREF = "extensions.formautofill.supportedCountries"; XPCOMUtils.defineLazyPreferenceGetter( this, "logLevel", "extensions.formautofill.loglevel", "Warn" ); // A logging helper for debug logging to avoid creating Console objects // or triggering expensive JS -> C++ calls when debug logging is not // enabled. // // Console objects, even natively-implemented ones, can consume a lot of // memory, and since this code may run in every content process, that // memory can add up quickly. And, even when debug-level messages are // being ignored, console.debug() calls can be expensive. // // This helper avoids both of those problems by never touching the // console object unless debug logging is enabled. function debug() { if (logLevel.toLowerCase() == "debug") { this.log.debug(...arguments); } } var FormAutofill = { ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF, ENABLED_AUTOFILL_CREDITCARDS_REAUTH_PREF, ADDRESSES_FIRST_TIME_USE_PREF, CREDITCARDS_USED_STATUS_PREF, get DEFAULT_REGION() { return Region.home || "US"; }, get isAutofillEnabled() { return ( FormAutofill.isAutofillAddressesEnabled || this.isAutofillCreditCardsEnabled ); }, get isAutofillCreditCardsEnabled() { return ( FormAutofill.isAutofillCreditCardsAvailable && FormAutofill._isAutofillCreditCardsEnabled ); }, defineLazyLogGetter(scope, logPrefix) { scope.debug = debug; XPCOMUtils.defineLazyGetter(scope, "log", () => { let ConsoleAPI = ChromeUtils.import( "resource://gre/modules/Console.jsm", {} ).ConsoleAPI; return new ConsoleAPI({ maxLogLevelPref: "extensions.formautofill.loglevel", prefix: logPrefix, }); }); }, }; XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, "isAutofillAddressesEnabled", ENABLED_AUTOFILL_ADDRESSES_PREF ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, "isAutofillAddressesCaptureEnabled", ENABLED_AUTOFILL_ADDRESSES_CAPTURE_PREF ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, "isAutofillCreditCardsAvailable", AUTOFILL_CREDITCARDS_AVAILABLE_PREF ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, "_isAutofillCreditCardsEnabled", ENABLED_AUTOFILL_CREDITCARDS_PREF ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, "isAutofillCreditCardsHideUI", AUTOFILL_CREDITCARDS_HIDE_UI_PREF ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, "isAutofillAddressesFirstTimeUse", ADDRESSES_FIRST_TIME_USE_PREF ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, "AutofillCreditCardsUsedStatus", CREDITCARDS_USED_STATUS_PREF ); XPCOMUtils.defineLazyPreferenceGetter( FormAutofill, "supportedCountries", SUPPORTED_COUNTRIES_PREF, null, null, val => val.split(",") ); // XXX: This should be invalidated on intl:app-locales-changed. XPCOMUtils.defineLazyGetter(FormAutofill, "countries", () => { let availableRegionCodes = Services.intl.getAvailableLocaleDisplayNames( "region" ); let displayNames = Services.intl.getRegionDisplayNames( undefined, availableRegionCodes ); let result = new Map(); for (let i = 0; i < availableRegionCodes.length; i++) { result.set(availableRegionCodes[i].toUpperCase(), displayNames[i]); } return result; }); PK �������!<-\#l��l�� ���chrome/res/FormAutofillChild.jsm/* 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 = ["FormAutofillChild"]; var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); ChromeUtils.defineModuleGetter( this, "setTimeout", "resource://gre/modules/Timer.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofill", "resource://formautofill/FormAutofill.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofillContent", "resource://formautofill/FormAutofillContent.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofillUtils", "resource://formautofill/FormAutofillUtils.jsm" ); ChromeUtils.defineModuleGetter( this, "AutoCompleteChild", "resource://gre/actors/AutoCompleteChild.jsm" ); /** * Handles content's interactions for the frame. */ class FormAutofillChild extends JSWindowActorChild { constructor() { super(); this._nextHandleElement = null; this._alreadyDOMContentLoaded = false; this._hasDOMContentLoadedHandler = false; this._hasPendingTask = false; this.testListener = null; AutoCompleteChild.addPopupStateListener(this); } didDestroy() { AutoCompleteChild.removePopupStateListener(this); } popupStateChanged(messageName, data, target) { let docShell; try { docShell = this.docShell; } catch (ex) { AutoCompleteChild.removePopupStateListener(this); return; } if (!FormAutofill.isAutofillEnabled) { return; } const { chromeEventHandler } = docShell; switch (messageName) { case "FormAutoComplete:PopupClosed": { FormAutofillContent.onPopupClosed(data.selectedRowStyle); Services.tm.dispatchToMainThread(() => { chromeEventHandler.removeEventListener( "keydown", FormAutofillContent._onKeyDown, true ); }); break; } case "FormAutoComplete:PopupOpened": { FormAutofillContent.onPopupOpened(); chromeEventHandler.addEventListener( "keydown", FormAutofillContent._onKeyDown, true ); break; } } } _doIdentifyAutofillFields() { if (this._hasPendingTask) { return; } this._hasPendingTask = true; setTimeout(() => { FormAutofillContent.identifyAutofillFields(this._nextHandleElement); this._hasPendingTask = false; this._nextHandleElement = null; // This is for testing purpose only which sends a notification to indicate that the // form has been identified, and ready to open popup. this.sendAsyncMessage("FormAutofill:FieldsIdentified"); FormAutofillContent.updateActiveInput(); }); } handleEvent(evt) { if (!evt.isTrusted) { return; } switch (evt.type) { case "focusin": { if (FormAutofill.isAutofillEnabled) { this.onFocusIn(evt); } break; } case "DOMFormBeforeSubmit": { if (FormAutofill.isAutofillEnabled) { this.onDOMFormBeforeSubmit(evt); } break; } default: { throw new Error("Unexpected event type"); } } } onFocusIn(evt) { FormAutofillContent.updateActiveInput(); let element = evt.target; if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) { return; } this._nextHandleElement = element; if (!this._alreadyDOMContentLoaded) { let doc = element.ownerDocument; if (doc.readyState === "loading") { if (!this._hasDOMContentLoadedHandler) { this._hasDOMContentLoadedHandler = true; doc.addEventListener( "DOMContentLoaded", () => this._doIdentifyAutofillFields(), { once: true } ); } return; } this._alreadyDOMContentLoaded = true; } this._doIdentifyAutofillFields(); } /** * Handle the DOMFormBeforeSubmit event. * @param {Event} evt */ onDOMFormBeforeSubmit(evt) { let formElement = evt.target; if (!FormAutofill.isAutofillEnabled) { return; } FormAutofillContent.formSubmitted(formElement); } receiveMessage(message) { if (!FormAutofill.isAutofillEnabled) { return; } const doc = this.document; switch (message.name) { case "FormAutofill:PreviewProfile": { FormAutofillContent.previewProfile(doc); break; } case "FormAutofill:ClearForm": { FormAutofillContent.clearForm(); break; } } } } PK �������!<Wuo��uo��"���chrome/res/FormAutofillContent.jsm/* 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/. */ /** * Form Autofill content process module. */ /* eslint-disable no-use-before-define */ "use strict"; var EXPORTED_SYMBOLS = ["FormAutofillContent"]; const Cm = Components.manager; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); ChromeUtils.defineModuleGetter( this, "AddressResult", "resource://formautofill/ProfileAutoCompleteResult.jsm" ); ChromeUtils.defineModuleGetter( this, "ComponentUtils", "resource://gre/modules/ComponentUtils.jsm" ); ChromeUtils.defineModuleGetter( this, "CreditCardResult", "resource://formautofill/ProfileAutoCompleteResult.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofill", "resource://formautofill/FormAutofill.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofillHandler", "resource://formautofill/FormAutofillHandler.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofillUtils", "resource://formautofill/FormAutofillUtils.jsm" ); ChromeUtils.defineModuleGetter( this, "FormLikeFactory", "resource://gre/modules/FormLikeFactory.jsm" ); ChromeUtils.defineModuleGetter( this, "InsecurePasswordUtils", "resource://gre/modules/InsecurePasswordUtils.jsm" ); ChromeUtils.defineModuleGetter( this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm" ); const formFillController = Cc[ "@mozilla.org/satchel/form-fill-controller;1" ].getService(Ci.nsIFormFillController); const autocompleteController = Cc[ "@mozilla.org/autocomplete/controller;1" ].getService(Ci.nsIAutoCompleteController); XPCOMUtils.defineLazyGetter( this, "ADDRESSES_COLLECTION_NAME", () => FormAutofillUtils.ADDRESSES_COLLECTION_NAME ); XPCOMUtils.defineLazyGetter( this, "CREDITCARDS_COLLECTION_NAME", () => FormAutofillUtils.CREDITCARDS_COLLECTION_NAME ); XPCOMUtils.defineLazyGetter( this, "FIELD_STATES", () => FormAutofillUtils.FIELD_STATES ); function getActorFromWindow(contentWindow, name = "FormAutofill") { // In unit tests, contentWindow isn't a real window. if (!contentWindow) { return null; } return contentWindow.windowGlobalChild ? contentWindow.windowGlobalChild.getActor(name) : null; } // Register/unregister a constructor as a factory. function AutocompleteFactory() {} AutocompleteFactory.prototype = { register(targetConstructor) { let proto = targetConstructor.prototype; this._classID = proto.classID; let factory = ComponentUtils._getFactory(targetConstructor); this._factory = factory; let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); registrar.registerFactory( proto.classID, proto.classDescription, proto.contractID, factory ); if (proto.classID2) { this._classID2 = proto.classID2; registrar.registerFactory( proto.classID2, proto.classDescription, proto.contractID2, factory ); } }, unregister() { let registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); registrar.unregisterFactory(this._classID, this._factory); if (this._classID2) { registrar.unregisterFactory(this._classID2, this._factory); } this._factory = null; }, }; /** * @constructor * * @implements {nsIAutoCompleteSearch} */ function AutofillProfileAutoCompleteSearch() { FormAutofill.defineLazyLogGetter(this, "AutofillProfileAutoCompleteSearch"); } AutofillProfileAutoCompleteSearch.prototype = { classID: Components.ID("4f9f1e4c-7f2c-439e-9c9e-566b68bc187d"), contractID: "@mozilla.org/autocomplete/search;1?name=autofill-profiles", classDescription: "AutofillProfileAutoCompleteSearch", QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteSearch"]), // Begin nsIAutoCompleteSearch implementation /** * Searches for a given string and notifies a listener (either synchronously * or asynchronously) of the result * * @param {string} searchString the string to search for * @param {string} searchParam * @param {Object} previousResult a previous result to use for faster searchinig * @param {Object} listener the listener to notify when the search is complete */ startSearch(searchString, searchParam, previousResult, listener) { let { activeInput, activeSection, activeFieldDetail, savedFieldNames, } = FormAutofillContent; this.forceStop = false; this.debug("startSearch: for", searchString, "with input", activeInput); let isAddressField = FormAutofillUtils.isAddressField( activeFieldDetail.fieldName ); const isCreditCardField = FormAutofillUtils.isCreditCardField( activeFieldDetail.fieldName ); let isInputAutofilled = activeFieldDetail.state == FIELD_STATES.AUTO_FILLED; let allFieldNames = activeSection.allFieldNames; let filledRecordGUID = activeSection.filledRecordGUID; let creditCardsEnabledAndVisible = FormAutofill.isAutofillCreditCardsEnabled && !FormAutofill.isAutofillCreditCardsHideUI; let searchPermitted = isAddressField ? FormAutofill.isAutofillAddressesEnabled : creditCardsEnabledAndVisible; let AutocompleteResult = isAddressField ? AddressResult : CreditCardResult; let isFormAutofillSearch = true; let pendingSearchResult = null; ProfileAutocomplete.lastProfileAutoCompleteFocusedInput = activeInput; // Fallback to form-history if ... // - specified autofill feature is pref off. // - no profile can fill the currently-focused input. // - the current form has already been populated and the field is not // an empty credit card field. // - (address only) less than 3 inputs are covered by all saved fields in the storage. if ( !searchPermitted || !savedFieldNames.has(activeFieldDetail.fieldName) || (!isInputAutofilled && filledRecordGUID && !(isCreditCardField && activeInput.value === "")) || (isAddressField && allFieldNames.filter(field => savedFieldNames.has(field)).length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD) ) { isFormAutofillSearch = false; if (activeInput.autocomplete == "off") { // Create a dummy result as an empty search result. pendingSearchResult = new AutocompleteResult("", "", [], [], {}); } else { pendingSearchResult = new Promise(resolve => { let formHistory = Cc[ "@mozilla.org/autocomplete/search;1?name=form-history" ].createInstance(Ci.nsIAutoCompleteSearch); formHistory.startSearch(searchString, searchParam, previousResult, { onSearchResult: (_, result) => resolve(result), }); }); } } else if (isInputAutofilled) { pendingSearchResult = new AutocompleteResult(searchString, "", [], [], { isInputAutofilled, }); } else { let infoWithoutElement = { ...activeFieldDetail }; delete infoWithoutElement.elementWeakRef; let data = { collectionName: isAddressField ? ADDRESSES_COLLECTION_NAME : CREDITCARDS_COLLECTION_NAME, info: infoWithoutElement, searchString, }; pendingSearchResult = this._getRecords(activeInput, data).then( records => { if (this.forceStop) { return null; } // Sort addresses by timeLastUsed for showing the lastest used address at top. records.sort((a, b) => b.timeLastUsed - a.timeLastUsed); let adaptedRecords = activeSection.getAdaptedProfiles(records); let handler = FormAutofillContent.activeHandler; let isSecure = InsecurePasswordUtils.isFormSecure(handler.form); return new AutocompleteResult( searchString, activeFieldDetail.fieldName, allFieldNames, adaptedRecords, { isSecure, isInputAutofilled } ); } ); } Promise.resolve(pendingSearchResult).then(result => { listener.onSearchResult(this, result); // Don't save cache results or reset state when returning non-autofill results such as the // form history fallback above. if (isFormAutofillSearch) { ProfileAutocomplete.lastProfileAutoCompleteResult = result; // Reset AutoCompleteController's state at the end of startSearch to ensure that // none of form autofill result will be cached in other places and make the // result out of sync. autocompleteController.resetInternalState(); } else { // Clear the cache so that we don't try to autofill from it after falling // back to form history. ProfileAutocomplete.lastProfileAutoCompleteResult = null; } }); }, /** * Stops an asynchronous search that is in progress */ stopSearch() { ProfileAutocomplete.lastProfileAutoCompleteResult = null; this.forceStop = true; }, /** * Get the records from parent process for AutoComplete result. * * @private * @param {Object} input * Input element for autocomplete. * @param {Object} data * Parameters for querying the corresponding result. * @param {string} data.collectionName * The name used to specify which collection to retrieve records. * @param {string} data.searchString * The typed string for filtering out the matched records. * @param {string} data.info * The input autocomplete property's information. * @returns {Promise} * Promise that resolves when addresses returned from parent process. */ _getRecords(input, data) { this.debug("_getRecords with data:", data); if (!input) { return []; } let actor = getActorFromWindow(input.ownerGlobal); return actor.sendQuery("FormAutofill:GetRecords", data); }, }; let ProfileAutocomplete = { QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), lastProfileAutoCompleteResult: null, lastProfileAutoCompleteFocusedInput: null, _registered: false, _factory: null, ensureRegistered() { if (this._registered) { return; } FormAutofill.defineLazyLogGetter(this, "ProfileAutocomplete"); this.debug("ensureRegistered"); this._factory = new AutocompleteFactory(); this._factory.register(AutofillProfileAutoCompleteSearch); this._registered = true; Services.obs.addObserver(this, "autocomplete-will-enter-text"); this.debug( "ensureRegistered. Finished with _registered:", this._registered ); }, ensureUnregistered() { if (!this._registered) { return; } this.debug("ensureUnregistered"); this._factory.unregister(); this._factory = null; this._registered = false; this._lastAutoCompleteResult = null; Services.obs.removeObserver(this, "autocomplete-will-enter-text"); }, async observe(subject, topic, data) { switch (topic) { case "autocomplete-will-enter-text": { if (!FormAutofillContent.activeInput) { // The observer notification is for autocomplete in a different process. break; } FormAutofillContent.autofillPending = true; Services.obs.notifyObservers(null, "autofill-fill-starting"); await this._fillFromAutocompleteRow(FormAutofillContent.activeInput); Services.obs.notifyObservers(null, "autofill-fill-complete"); FormAutofillContent.autofillPending = false; break; } } }, _getSelectedIndex(contentWindow) { let actor = getActorFromWindow(contentWindow, "AutoComplete"); if (!actor) { throw new Error("Invalid autocomplete selectedIndex"); } return actor.selectedIndex; }, async _fillFromAutocompleteRow(focusedInput) { this.debug("_fillFromAutocompleteRow:", focusedInput); let formDetails = FormAutofillContent.activeFormDetails; if (!formDetails) { // The observer notification is for a different frame. return; } let selectedIndex = this._getSelectedIndex(focusedInput.ownerGlobal); if ( selectedIndex == -1 || !this.lastProfileAutoCompleteResult || this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile" ) { return; } let profile = JSON.parse( this.lastProfileAutoCompleteResult.getCommentAt(selectedIndex) ); await FormAutofillContent.activeHandler.autofillFormFields(profile); }, _clearProfilePreview() { if ( !this.lastProfileAutoCompleteFocusedInput || !FormAutofillContent.activeSection ) { return; } FormAutofillContent.activeSection.clearPreviewedFormFields(); }, _previewSelectedProfile(selectedIndex) { if ( !FormAutofillContent.activeInput || !FormAutofillContent.activeFormDetails ) { // The observer notification is for a different process/frame. return; } if ( !this.lastProfileAutoCompleteResult || this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile" ) { return; } let profile = JSON.parse( this.lastProfileAutoCompleteResult.getCommentAt(selectedIndex) ); FormAutofillContent.activeSection.previewFormFields(profile); }, }; /** * Handles content's interactions for the process. * * NOTE: Declares it by "var" to make it accessible in unit tests. */ var FormAutofillContent = { /** * @type {WeakMap} mapping FormLike root HTML elements to FormAutofillHandler objects. */ _formsDetails: new WeakMap(), /** * @type {Set} Set of the fields with usable values in any saved profile. */ get savedFieldNames() { return Services.cpmm.sharedData.get("FormAutofill:savedFieldNames"); }, /** * @type {Object} The object where to store the active items, e.g. element, * handler, section, and field detail. */ _activeItems: {}, /** * @type {boolean} Flag indicating whether a focus action requiring * the popup to be active is pending. */ _popupPending: false, /** * @type {boolean} Flag indicating whether the form is waiting to be * filled by Autofill. */ _autofillPending: false, init() { FormAutofill.defineLazyLogGetter(this, "FormAutofillContent"); this.debug("init"); // eslint-disable-next-line mozilla/balanced-listeners Services.cpmm.sharedData.addEventListener("change", this); let autofillEnabled = Services.cpmm.sharedData.get("FormAutofill:enabled"); // If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure // autocomplete is registered before the focusin so register it in this case as long as the // pref is true. let shouldEnableAutofill = autofillEnabled === undefined && (FormAutofill.isAutofillAddressesEnabled || FormAutofill.isAutofillCreditCardsEnabled); if (autofillEnabled || shouldEnableAutofill) { ProfileAutocomplete.ensureRegistered(); } }, /** * Send the profile to parent for doorhanger and storage saving/updating. * * @param {Object} profile Submitted form's address/creditcard guid and record. * @param {Object} domWin Current content window. * @param {int} timeStartedFillingMS Time of form filling started. */ _onFormSubmit(profile, domWin, timeStartedFillingMS) { let actor = getActorFromWindow(domWin); actor.sendAsyncMessage("FormAutofill:OnFormSubmit", { profile, timeStartedFillingMS, }); }, /** * Handle a form submission and early return when: * 1. In private browsing mode. * 2. Could not map any autofill handler by form element. * 3. Number of filled fields is less than autofill threshold * * @param {HTMLElement} formElement Root element which receives submit event. * @param {Window} domWin Content window; passed for unit tests and when * invoked by the FormAutofillSection * @param {Object} handler FormAutofillHander, if known by caller */ formSubmitted( formElement, domWin = formElement.ownerGlobal, handler = undefined ) { this.debug("Handling form submission"); if (!FormAutofill.isAutofillEnabled) { this.debug("Form Autofill is disabled"); return; } // The `domWin` truthiness test is used by unit tests to bypass this check. if (domWin && PrivateBrowsingUtils.isContentWindowPrivate(domWin)) { this.debug("Ignoring submission in a private window"); return; } handler = handler ?? this._formsDetails.get(formElement); if (!handler) { this.debug("Form element could not map to an existing handler"); return; } let records = handler.createRecords(); if (!Object.values(records).some(typeRecords => typeRecords.length)) { return; } records.creditCard.forEach(record => { let extra = { // Fields which have been filled manually. fields_not_auto: "0", // Fields which have been autofilled. fields_auto: "0", // Fields which have been autofilled and then modified. fields_modified: "0", }; if (record.guid !== null) { // If the `guid` is not null, it means we're editing an existing record. // In that case, all fields in the record are autofilled, and fields in // `untouchedFields` are unmodified. let totalCount = handler.form.elements.length; let autofilledCount = Object.keys(record.record).length; let unmodifiedCount = record.untouchedFields.length; extra.fields_not_auto = (totalCount - autofilledCount).toString(); extra.fields_auto = autofilledCount.toString(); extra.fields_modified = (autofilledCount - unmodifiedCount).toString(); } else { // If the `guid` is null, we're filling a new form. // In that case, all not-null fields are manually filled. extra.fields_not_auto = Array.from(handler.form.elements) .filter(element => !!element.value.trim().length) .length.toString(); } Services.telemetry.recordEvent( "creditcard", "submitted", "cc_form", record.flowId, extra ); }); if (records.creditCard.length) { Services.telemetry.scalarAdd( "formautofill.creditCards.submitted_sections_count", records.creditCard.length ); } this._onFormSubmit(records, domWin, handler.timeStartedFillingMS); }, handleEvent(evt) { switch (evt.type) { case "change": { if (!evt.changedKeys.includes("FormAutofill:enabled")) { return; } if (Services.cpmm.sharedData.get("FormAutofill:enabled")) { ProfileAutocomplete.ensureRegistered(); if (this._popupPending) { this._popupPending = false; this.debug("handleEvent: Opening deferred popup"); formFillController.showPopup(); } } else { ProfileAutocomplete.ensureUnregistered(); } break; } } }, /** * Get the form's handler from cache which is created after page identified. * * @param {HTMLInputElement} element Focused input which triggered profile searching * @returns {Array<Object>|null} * Return target form's handler from content cache * (or return null if the information is not found in the cache). * */ _getFormHandler(element) { if (!element) { return null; } let rootElement = FormLikeFactory.findRootForField(element); return this._formsDetails.get(rootElement); }, /** * Get the active form's information from cache which is created after page * identified. * * @returns {Array<Object>|null} * Return target form's information from content cache * (or return null if the information is not found in the cache). * */ get activeFormDetails() { let formHandler = this.activeHandler; return formHandler ? formHandler.fieldDetails : null; }, /** * All active items should be updated according the active element of * `formFillController.focusedInput`. All of them including element, * handler, section, and field detail, can be retrieved by their own getters. * * @param {HTMLElement|null} element The active item should be updated based * on this or `formFillController.focusedInput` will be taken. */ updateActiveInput(element) { element = element || formFillController.focusedInput; if (!element) { this.debug("updateActiveElement: no element selected"); this._activeItems = {}; return; } this._activeItems = { elementWeakRef: Cu.getWeakReference(element), fieldDetail: null, }; this.debug("updateActiveElement: checking for popup-on-focus"); // We know this element just received focus. If it's a credit card field, // open its popup. if (this._autofillPending) { this.debug("updateActiveElement: skipping check; autofill is imminent"); } else if (element.value?.length !== 0) { this.debug( "updateActiveElement: Not opening popup because field is " + `not empty: element.value = "${element.value}"` ); } else { this.debug( "updateActiveElement: checking if empty field is cc-*: ", this.activeFieldDetail?.fieldName ); if (this.activeFieldDetail?.fieldName?.startsWith("cc-")) { if (Services.cpmm.sharedData.get("FormAutofill:enabled")) { this.debug("updateActiveElement: opening pop up"); formFillController.showPopup(); } else { this.debug( "updateActiveElement: Deferring pop-up until Autofill is ready" ); this._popupPending = true; } } } }, get activeInput() { let elementWeakRef = this._activeItems.elementWeakRef; return elementWeakRef ? elementWeakRef.get() : null; }, get activeHandler() { const activeInput = this.activeInput; if (!activeInput) { return null; } // XXX: We are recomputing the activeHandler every time to avoid keeping a // reference on the active element. This might be called quite frequently // so if _getFormHandler/findRootForField become more costly, we should // look into caching this result (eg by adding a weakmap). let handler = this._getFormHandler(activeInput); if (handler) { handler.focusedInput = activeInput; } return handler; }, get activeSection() { let formHandler = this.activeHandler; return formHandler ? formHandler.activeSection : null; }, /** * Get the active input's information from cache which is created after page * identified. * * @returns {Object|null} * Return the active input's information that cloned from content cache * (or return null if the information is not found in the cache). */ get activeFieldDetail() { if (!this._activeItems.fieldDetail) { let formDetails = this.activeFormDetails; if (!formDetails) { return null; } for (let detail of formDetails) { let detailElement = detail.elementWeakRef.get(); if (detailElement && this.activeInput == detailElement) { this._activeItems.fieldDetail = detail; break; } } } return this._activeItems.fieldDetail; }, set autofillPending(flag) { this.debug("Setting autofillPending to", flag); this._autofillPending = flag; }, identifyAutofillFields(element) { this.debug( "identifyAutofillFields:", String(element.ownerDocument.location) ); if (!this.savedFieldNames) { this.debug("identifyAutofillFields: savedFieldNames are not known yet"); let actor = getActorFromWindow(element.ownerGlobal); if (actor) { actor.sendAsyncMessage("FormAutofill:InitStorage"); } } let formHandler = this._getFormHandler(element); if (!formHandler) { let formLike = FormLikeFactory.createFromField(element); formHandler = new FormAutofillHandler( formLike, this.formSubmitted.bind(this) ); } else if (!formHandler.updateFormIfNeeded(element)) { this.debug("No control is removed or inserted since last collection."); return; } let validDetails = formHandler.collectFormFields(); this._formsDetails.set(formHandler.form.rootElement, formHandler); this.debug("Adding form handler to _formsDetails:", formHandler); validDetails.forEach(detail => this._markAsAutofillField(detail.elementWeakRef.get()) ); }, clearForm() { let focusedInput = this.activeInput || ProfileAutocomplete._lastAutoCompleteFocusedInput; if (!focusedInput) { return; } this.activeSection.clearPopulatedForm(); }, previewProfile(doc) { let docWin = doc.ownerGlobal; let selectedIndex = ProfileAutocomplete._getSelectedIndex(docWin); let lastAutoCompleteResult = ProfileAutocomplete.lastProfileAutoCompleteResult; let focusedInput = this.activeInput; let actor = getActorFromWindow(docWin); if ( selectedIndex === -1 || !focusedInput || !lastAutoCompleteResult || lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile" ) { actor.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {}); ProfileAutocomplete._clearProfilePreview(); } else { let focusedInputDetails = this.activeFieldDetail; let profile = JSON.parse( lastAutoCompleteResult.getCommentAt(selectedIndex) ); let allFieldNames = FormAutofillContent.activeSection.allFieldNames; let profileFields = allFieldNames.filter( fieldName => !!profile[fieldName] ); let focusedCategory = FormAutofillUtils.getCategoryFromFieldName( focusedInputDetails.fieldName ); let categories = FormAutofillUtils.getCategoriesFromFieldNames( profileFields ); actor.sendAsyncMessage("FormAutofill:UpdateWarningMessage", { focusedCategory, categories, }); ProfileAutocomplete._previewSelectedProfile(selectedIndex); } }, onPopupClosed(selectedRowStyle) { this.debug("Popup has closed."); ProfileAutocomplete._clearProfilePreview(); let lastAutoCompleteResult = ProfileAutocomplete.lastProfileAutoCompleteResult; let focusedInput = FormAutofillContent.activeInput; if ( lastAutoCompleteResult && FormAutofillContent._keyDownEnterForInput && focusedInput === FormAutofillContent._keyDownEnterForInput && focusedInput === ProfileAutocomplete.lastProfileAutoCompleteFocusedInput ) { if (selectedRowStyle == "autofill-footer") { let actor = getActorFromWindow(focusedInput.ownerGlobal); actor.sendAsyncMessage("FormAutofill:OpenPreferences"); } else if (selectedRowStyle == "autofill-clear-button") { FormAutofillContent.clearForm(); } } }, onPopupOpened() { this.debug( "Popup has opened, automatic =", formFillController.passwordPopupAutomaticallyOpened ); Services.telemetry.recordEvent( "creditcard", "popup_shown", "cc_form", this.activeSection.flowId ); }, _markAsAutofillField(field) { // Since Form Autofill popup is only for input element, any non-Input // element should be excluded here. if (!field || ChromeUtils.getClassName(field) !== "HTMLInputElement") { return; } formFillController.markAsAutofillField(field); }, _onKeyDown(e) { delete FormAutofillContent._keyDownEnterForInput; let lastAutoCompleteResult = ProfileAutocomplete.lastProfileAutoCompleteResult; let focusedInput = FormAutofillContent.activeInput; if ( e.keyCode != e.DOM_VK_RETURN || !lastAutoCompleteResult || !focusedInput || focusedInput != ProfileAutocomplete.lastProfileAutoCompleteFocusedInput ) { return; } FormAutofillContent._keyDownEnterForInput = focusedInput; }, }; FormAutofillContent.init(); PK �������!<L<��<��%���chrome/res/FormAutofillDoorhanger.jsm/* 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/. */ /* * Implements doorhanger singleton that wraps up the PopupNotifications and handles * the doorhager UI for formautofill related features. */ "use strict"; var EXPORTED_SYMBOLS = ["FormAutofillDoorhanger"]; const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { FormAutofill } = ChromeUtils.import( "resource://formautofill/FormAutofill.jsm" ); const { FormAutofillUtils } = ChromeUtils.import( "resource://formautofill/FormAutofillUtils.jsm" ); this.log = null; FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]); const GetStringFromName = FormAutofillUtils.stringBundle.GetStringFromName; const formatStringFromName = FormAutofillUtils.stringBundle.formatStringFromName; const brandShortName = FormAutofillUtils.brandBundle.GetStringFromName( "brandShortName" ); let changeAutofillOptsKey = "changeAutofillOptions"; let autofillOptsKey = "autofillOptionsLink"; if (AppConstants.platform == "macosx") { changeAutofillOptsKey += "OSX"; autofillOptsKey += "OSX"; } const CONTENT = { firstTimeUse: { notificationId: "autofill-address", message: formatStringFromName("saveAddressesMessage", [brandShortName]), anchor: { id: "autofill-address-notification-icon", URL: "chrome://formautofill/content/formfill-anchor.svg", tooltiptext: GetStringFromName("openAutofillMessagePanel"), }, mainAction: { label: GetStringFromName(changeAutofillOptsKey), accessKey: GetStringFromName("changeAutofillOptionsAccessKey"), callbackState: "open-pref", disableHighlight: true, }, options: { persistWhileVisible: true, popupIconURL: "chrome://formautofill/content/icon-address-save.svg", checkbox: { get checked() { return Services.prefs.getBoolPref("services.sync.engine.addresses"); }, get label() { // If sync account is not set, return null label to hide checkbox return Services.prefs.prefHasUserValue("services.sync.username") ? GetStringFromName("addressesSyncCheckbox") : null; }, callback(event) { let checked = event.target.checked; Services.prefs.setBoolPref("services.sync.engine.addresses", checked); log.debug("Set addresses sync to", checked); }, }, hideClose: true, }, }, updateAddress: { notificationId: "autofill-address", message: GetStringFromName("updateAddressMessage"), descriptionLabel: GetStringFromName("updateAddressDescriptionLabel"), descriptionIcon: false, linkMessage: GetStringFromName(autofillOptsKey), spotlightURL: "about:preferences#privacy-address-autofill", anchor: { id: "autofill-address-notification-icon", URL: "chrome://formautofill/content/formfill-anchor.svg", tooltiptext: GetStringFromName("openAutofillMessagePanel"), }, mainAction: { label: GetStringFromName("updateAddressLabel"), accessKey: GetStringFromName("updateAddressAccessKey"), callbackState: "update", }, secondaryActions: [ { label: GetStringFromName("createAddressLabel"), accessKey: GetStringFromName("createAddressAccessKey"), callbackState: "create", }, ], options: { persistWhileVisible: true, popupIconURL: "chrome://formautofill/content/icon-address-update.svg", hideClose: true, }, }, addCreditCard: { notificationId: "autofill-credit-card", message: formatStringFromName("saveCreditCardMessage", [brandShortName]), descriptionLabel: GetStringFromName("saveCreditCardDescriptionLabel"), descriptionIcon: true, linkMessage: GetStringFromName(autofillOptsKey), spotlightURL: "about:preferences#privacy-credit-card-autofill", anchor: { id: "autofill-credit-card-notification-icon", URL: "chrome://formautofill/content/formfill-anchor.svg", tooltiptext: GetStringFromName("openAutofillMessagePanel"), }, mainAction: { label: GetStringFromName("saveCreditCardLabel"), accessKey: GetStringFromName("saveCreditCardAccessKey"), callbackState: "save", }, secondaryActions: [ { label: GetStringFromName("cancelCreditCardLabel"), accessKey: GetStringFromName("cancelCreditCardAccessKey"), callbackState: "cancel", }, { label: GetStringFromName("neverSaveCreditCardLabel"), accessKey: GetStringFromName("neverSaveCreditCardAccessKey"), callbackState: "disable", }, ], options: { persistWhileVisible: true, popupIconURL: "chrome://formautofill/content/icon-credit-card.svg", hideClose: true, checkbox: { get checked() { return Services.prefs.getBoolPref("services.sync.engine.creditcards"); }, get label() { // Only set the label when the fallowing conditions existed: // - sync account is set // - credit card sync is disabled // - credit card sync is available // otherwise return null label to hide checkbox. return Services.prefs.prefHasUserValue("services.sync.username") && !Services.prefs.getBoolPref("services.sync.engine.creditcards") && Services.prefs.getBoolPref( "services.sync.engine.creditcards.available" ) ? GetStringFromName("creditCardsSyncCheckbox") : null; }, callback(event) { let { secondaryButton, menubutton, } = event.target.parentNode.parentNode.parentNode; let checked = event.target.checked; Services.prefs.setBoolPref( "services.sync.engine.creditcards", checked ); secondaryButton.disabled = checked; menubutton.disabled = checked; log.debug("Set creditCard sync to", checked); }, }, }, }, updateCreditCard: { notificationId: "autofill-credit-card", message: GetStringFromName("updateCreditCardMessage"), descriptionLabel: GetStringFromName("updateCreditCardDescriptionLabel"), descriptionIcon: true, linkMessage: GetStringFromName(autofillOptsKey), spotlightURL: "about:preferences#privacy-credit-card-autofill", anchor: { id: "autofill-credit-card-notification-icon", URL: "chrome://formautofill/content/formfill-anchor.svg", tooltiptext: GetStringFromName("openAutofillMessagePanel"), }, mainAction: { label: GetStringFromName("updateCreditCardLabel"), accessKey: GetStringFromName("updateCreditCardAccessKey"), callbackState: "update", }, secondaryActions: [ { label: GetStringFromName("createCreditCardLabel"), accessKey: GetStringFromName("createCreditCardAccessKey"), callbackState: "create", }, ], options: { persistWhileVisible: true, popupIconURL: "chrome://formautofill/content/icon-credit-card.svg", hideClose: true, }, }, }; let FormAutofillDoorhanger = { /** * Generate the main action and secondary actions from content parameters and * promise resolve. * * @private * @param {Object} mainActionParams * Parameters for main action. * @param {Array<Object>} secondaryActionParams * Array of the parameters for secondary actions. * @param {Function} resolve Should be called in action callback. * @returns {Array<Object>} Return the mainAction and secondary actions in an array for showing doorhanger */ _createActions(mainActionParams, secondaryActionParams, resolve) { if (!mainActionParams) { return [null, null]; } let { label, accessKey, disableHighlight, callbackState, } = mainActionParams; let callback = resolve.bind(null, callbackState); let mainAction = { label, accessKey, callback, disableHighlight }; if (!secondaryActionParams) { return [mainAction, null]; } let secondaryActions = []; for (let params of secondaryActionParams) { let cb = resolve.bind(null, params.callbackState); secondaryActions.push({ label: params.label, accessKey: params.accessKey, callback: cb, }); } return [mainAction, secondaryActions]; }, _getNotificationElm(browser, id) { let notificationId = id + "-notification"; let chromeDoc = browser.ownerDocument; return chromeDoc.getElementById(notificationId); }, /** * Append the link label element to the popupnotificationcontent. * @param {XULElement} content * popupnotificationcontent * @param {string} message * The localized string for link title. * @param {string} link * Makes it possible to open and highlight a section in preferences */ _appendPrivacyPanelLink(content, message, link) { let chromeDoc = content.ownerDocument; let privacyLinkElement = chromeDoc.createXULElement("label", { is: "text-link", }); privacyLinkElement.setAttribute("useoriginprincipal", true); privacyLinkElement.setAttribute( "href", link || "about:preferences#privacy-form-autofill" ); privacyLinkElement.setAttribute("value", message); content.appendChild(privacyLinkElement); }, /** * Append the description section to the popupnotificationcontent. * @param {XULElement} content * popupnotificationcontent * @param {string} descriptionLabel * The label showing above description. * @param {string} descriptionIcon * The src of description icon. */ _appendDescription(content, descriptionLabel, descriptionIcon) { let chromeDoc = content.ownerDocument; let docFragment = chromeDoc.createDocumentFragment(); let descriptionLabelElement = chromeDoc.createXULElement("label"); descriptionLabelElement.setAttribute("value", descriptionLabel); docFragment.appendChild(descriptionLabelElement); let descriptionWrapper = chromeDoc.createXULElement("hbox"); descriptionWrapper.className = "desc-message-box"; if (descriptionIcon) { let descriptionIconElement = chromeDoc.createXULElement("image"); descriptionWrapper.appendChild(descriptionIconElement); } let descriptionElement = chromeDoc.createXULElement("description"); descriptionWrapper.appendChild(descriptionElement); docFragment.appendChild(descriptionWrapper); content.appendChild(docFragment); }, _updateDescription(content, description) { content.querySelector("description").textContent = description; }, /** * Create an image element for notification anchor if it doesn't already exist. * @param {XULElement} browser * Target browser element for showing doorhanger. * @param {Object} anchor * Anchor options for setting the anchor element. * @param {string} anchor.id * ID of the anchor element. * @param {string} anchor.URL * Path of the icon asset. * @param {string} anchor.tooltiptext * Tooltip string for the anchor. */ _setAnchor(browser, anchor) { let chromeDoc = browser.ownerDocument; let { id, URL, tooltiptext } = anchor; let anchorEt = chromeDoc.getElementById(id); if (!anchorEt) { let notificationPopupBox = chromeDoc.getElementById( "notification-popup-box" ); // Icon shown on URL bar let anchorElement = chromeDoc.createXULElement("image"); anchorElement.id = id; anchorElement.setAttribute("src", URL); anchorElement.classList.add("notification-anchor-icon"); anchorElement.setAttribute("role", "button"); anchorElement.setAttribute("tooltiptext", tooltiptext); notificationPopupBox.appendChild(anchorElement); } }, _addCheckboxListener(browser, { notificationId, options }) { if (!options.checkbox) { return; } let { checkbox } = this._getNotificationElm(browser, notificationId); if (checkbox && !checkbox.hidden) { checkbox.addEventListener("command", options.checkbox.callback); } }, _removeCheckboxListener(browser, { notificationId, options }) { if (!options.checkbox) { return; } let { checkbox } = this._getNotificationElm(browser, notificationId); if (checkbox && !checkbox.hidden) { checkbox.removeEventListener("command", options.checkbox.callback); } }, /** * Show different types of doorhanger by leveraging PopupNotifications. * @param {XULElement} browser * Target browser element for showing doorhanger. * @param {string} type * The type of the doorhanger. There will have first time use/update/credit card. * @param {string} description * The message that provides more information on doorhanger. * @returns {Promise} Resolved with action type when action callback is triggered. */ async show(browser, type, description) { log.debug("show doorhanger with type:", type); return new Promise(resolve => { let { notificationId, message, descriptionLabel, descriptionIcon, linkMessage, spotlightURL, anchor, mainAction, secondaryActions, options, } = CONTENT[type]; const { ownerGlobal: chromeWin, ownerDocument: chromeDoc } = browser; options.eventCallback = topic => { log.debug("eventCallback:", topic); if (topic == "removed" || topic == "dismissed") { this._removeCheckboxListener(browser, { notificationId, options }); return; } // The doorhanger is customizable only when notification box is shown if (topic != "shown") { return; } this._addCheckboxListener(browser, { notificationId, options }); // There's no preferences link or other customization in first time use doorhanger. if (type == "firstTimeUse") { return; } const notificationElementId = notificationId + "-notification"; const notification = chromeDoc.getElementById(notificationElementId); const notificationContent = notification.querySelector("popupnotificationcontent") || chromeDoc.createXULElement("popupnotificationcontent"); if (!notification.contains(notificationContent)) { notificationContent.setAttribute("orient", "vertical"); this._appendDescription( notificationContent, descriptionLabel, descriptionIcon ); this._appendPrivacyPanelLink( notificationContent, linkMessage, spotlightURL ); notification.appendNotificationContent(notificationContent); } this._updateDescription(notificationContent, description); }; this._setAnchor(browser, anchor); chromeWin.PopupNotifications.show( browser, notificationId, message, anchor.id, ...this._createActions(mainAction, secondaryActions, resolve), options ); }); }, }; PK �������!<a=����"���chrome/res/FormAutofillHandler.jsm/* 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/. */ /* * Defines a handler object to represent forms that autofill can handle. */ "use strict"; var EXPORTED_SYMBOLS = ["FormAutofillHandler"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { AppConstants } = ChromeUtils.import( "resource://gre/modules/AppConstants.jsm" ); const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const { FormAutofill } = ChromeUtils.import( "resource://formautofill/FormAutofill.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofillUtils", "resource://formautofill/FormAutofillUtils.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofillHeuristics", "resource://formautofill/FormAutofillHeuristics.jsm" ); ChromeUtils.defineModuleGetter( this, "FormLikeFactory", "resource://gre/modules/FormLikeFactory.jsm" ); const formFillController = Cc[ "@mozilla.org/satchel/form-fill-controller;1" ].getService(Ci.nsIFormFillController); XPCOMUtils.defineLazyGetter(this, "reauthPasswordPromptMessage", () => { const brandShortName = FormAutofillUtils.brandBundle.GetStringFromName( "brandShortName" ); // The string name for Mac is changed because the value needed updating. const platform = AppConstants.platform.replace("macosx", "macos"); return FormAutofillUtils.stringBundle.formatStringFromName( `useCreditCardPasswordPrompt.${platform}`, [brandShortName] ); }); XPCOMUtils.defineLazyModuleGetters(this, { CreditCard: "resource://gre/modules/CreditCard.jsm", }); XPCOMUtils.defineLazyServiceGetters(this, { gUUIDGenerator: ["@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"], }); this.log = null; FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]); const { FIELD_STATES } = FormAutofillUtils; class FormAutofillSection { constructor(fieldDetails, winUtils) { this.fieldDetails = fieldDetails; this.filledRecordGUID = null; this.winUtils = winUtils; /** * Enum for form autofill MANUALLY_MANAGED_STATES values */ this._FIELD_STATE_ENUM = { // not themed [FIELD_STATES.NORMAL]: null, // highlighted [FIELD_STATES.AUTO_FILLED]: "autofill", // highlighted && grey color text [FIELD_STATES.PREVIEW]: "-moz-autofill-preview", }; if (!this.isValidSection()) { this.fieldDetails = []; log.debug( `Ignoring ${this.constructor.name} related fields since it is an invalid section` ); } this._cacheValue = { allFieldNames: null, matchingSelectOption: null, }; } /* * Examine the section is a valid section or not based on its fieldDetails or * other information. This method must be overrided. * * @returns {boolean} True for a valid section, otherwise false * */ isValidSection() { throw new TypeError("isValidSection method must be overrided"); } /* * Examine the section is an enabled section type or not based on its * preferences. This method must be overrided. * * @returns {boolean} True for an enabled section type, otherwise false * */ isEnabled() { throw new TypeError("isEnabled method must be overrided"); } /* * Examine the section is createable for storing the profile. This method * must be overrided. * * @param {Object} record The record for examining createable * @returns {boolean} True for the record is createable, otherwise false * */ isRecordCreatable(record) { throw new TypeError("isRecordCreatable method must be overrided"); } /** * Override this method if the profile is needed to apply some transformers. * * @param {Object} profile * A profile should be converted based on the specific requirement. */ applyTransformers(profile) {} /** * Override this method if the profile is needed to be customized for * previewing values. * * @param {Object} profile * A profile for pre-processing before previewing values. */ preparePreviewProfile(profile) {} /** * Override this method if the profile is needed to be customized for filling * values. * * @param {Object} profile * A profile for pre-processing before filling values. * @returns {boolean} Whether the profile should be filled. */ async prepareFillingProfile(profile) { return true; } /* * Override this methid if any data for `createRecord` is needed to be * normailized before submitting the record. * * @param {Object} profile * A record for normalization. */ normalizeCreatingRecord(data) {} /* * Override this method if there is any field value needs to compute for a * specific case. Return the original value in the default case. * @param {String} value * The original field value. * @param {Object} fieldDetail * A fieldDetail of the related element. * @param {HTMLElement} element * A element for checking converting value. * * @returns {String} * A string of the converted value. */ computeFillingValue(value, fieldName, element) { return value; } set focusedInput(element) { this._focusedDetail = this.getFieldDetailByElement(element); } getFieldDetailByElement(element) { return this.fieldDetails.find( detail => detail.elementWeakRef.get() == element ); } get allFieldNames() { if (!this._cacheValue.allFieldNames) { this._cacheValue.allFieldNames = this.fieldDetails.map( record => record.fieldName ); } return this._cacheValue.allFieldNames; } getFieldDetailByName(fieldName) { return this.fieldDetails.find(detail => detail.fieldName == fieldName); } matchSelectOptions(profile) { if (!this._cacheValue.matchingSelectOption) { this._cacheValue.matchingSelectOption = new WeakMap(); } for (let fieldName in profile) { let fieldDetail = this.getFieldDetailByName(fieldName); if (!fieldDetail) { continue; } let element = fieldDetail.elementWeakRef.get(); if (ChromeUtils.getClassName(element) !== "HTMLSelectElement") { continue; } let cache = this._cacheValue.matchingSelectOption.get(element) || {}; let value = profile[fieldName]; if (cache[value] && cache[value].get()) { continue; } let option = FormAutofillUtils.findSelectOption( element, profile, fieldName ); if (option) { cache[value] = Cu.getWeakReference(option); this._cacheValue.matchingSelectOption.set(element, cache); } else { if (cache[value]) { delete cache[value]; this._cacheValue.matchingSelectOption.set(element, cache); } // Delete the field so the phishing hint won't treat it as a "also fill" // field. delete profile[fieldName]; } } } adaptFieldMaxLength(profile) { for (let key in profile) { let detail = this.getFieldDetailByName(key); if (!detail) { continue; } let element = detail.elementWeakRef.get(); if (!element) { continue; } let maxLength = element.maxLength; if ( maxLength === undefined || maxLength < 0 || profile[key].toString().length <= maxLength ) { continue; } if (maxLength) { switch (typeof profile[key]) { case "string": // If this is an expiration field and our previous // adaptations haven't resulted in a string that is // short enough to satisfy the field length, and the // field is constrained to a length of 5, then we // assume it is intended to hold an expiration of the // form "MM/YY". if (key == "cc-exp" && maxLength == 5) { const month2Digits = ( "0" + profile["cc-exp-month"].toString() ).slice(-2); const year2Digits = profile["cc-exp-year"].toString().slice(-2); profile[key] = `${month2Digits}/${year2Digits}`; } else { profile[key] = profile[key].substr(0, maxLength); } break; case "number": // There's no way to truncate a number smaller than a // single digit. if (maxLength < 1) { maxLength = 1; } // The only numbers we store are expiration month/year, // and if they truncate, we want the final digits, not // the initial ones. profile[key] = profile[key] % Math.pow(10, maxLength); break; default: log.warn( "adaptFieldMaxLength: Don't know how to truncate", typeof profile[key], profile[key] ); } } else { delete profile[key]; } } } getAdaptedProfiles(originalProfiles) { for (let profile of originalProfiles) { this.applyTransformers(profile); } return originalProfiles; } /** * Processes form fields that can be autofilled, and populates them with the * profile provided by backend. * * @param {Object} profile * A profile to be filled in. * @returns {boolean} * True if successful, false if failed */ async autofillFields(profile) { let focusedDetail = this._focusedDetail; if (!focusedDetail) { throw new Error("No fieldDetail for the focused input."); } if (!(await this.prepareFillingProfile(profile))) { log.debug("profile cannot be filled", profile); return false; } log.debug("profile in autofillFields:", profile); let focusedInput = focusedDetail.elementWeakRef.get(); this.filledRecordGUID = profile.guid; for (let fieldDetail of this.fieldDetails) { // Avoid filling field value in the following cases: // 1. a non-empty input field for an unfocused input // 2. the invalid value set // 3. value already chosen in select element let element = fieldDetail.elementWeakRef.get(); if (!element) { continue; } element.previewValue = ""; let value = profile[fieldDetail.fieldName]; if (ChromeUtils.getClassName(element) === "HTMLInputElement" && value) { // For the focused input element, it will be filled with a valid value // anyway. // For the others, the fields should be only filled when their values // are empty or are the result of an earlier auto-fill. if ( element == focusedInput || (element != focusedInput && !element.value) || fieldDetail.state == FIELD_STATES.AUTO_FILLED ) { element.focus({ preventScroll: true }); element.setUserInput(value); this._changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED); } } else if (ChromeUtils.getClassName(element) === "HTMLSelectElement") { let cache = this._cacheValue.matchingSelectOption.get(element) || {}; let option = cache[value] && cache[value].get(); if (!option) { continue; } // Do not change value or dispatch events if the option is already selected. // Use case for multiple select is not considered here. if (!option.selected) { option.selected = true; element.focus({ preventScroll: true }); element.dispatchEvent( new element.ownerGlobal.Event("input", { bubbles: true }) ); element.dispatchEvent( new element.ownerGlobal.Event("change", { bubbles: true }) ); } // Autofill highlight appears regardless if value is changed or not this._changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED); } } focusedInput.focus({ preventScroll: true }); return true; } /** * Populates result to the preview layers with given profile. * * @param {Object} profile * A profile to be previewed with */ previewFormFields(profile) { log.debug("preview profile: ", profile); this.preparePreviewProfile(profile); for (let fieldDetail of this.fieldDetails) { let element = fieldDetail.elementWeakRef.get(); let value = profile[fieldDetail.fieldName] || ""; // Skip the field that is null if (!element) { continue; } if (ChromeUtils.getClassName(element) === "HTMLSelectElement") { // Unlike text input, select element is always previewed even if // the option is already selected. if (value) { let cache = this._cacheValue.matchingSelectOption.get(element) || {}; let option = cache[value] && cache[value].get(); if (option) { value = option.text || ""; } else { value = ""; } } } else if (element.value) { // Skip the field if it already has text entered. continue; } element.previewValue = value; this._changeFieldState( fieldDetail, value ? FIELD_STATES.PREVIEW : FIELD_STATES.NORMAL ); } } /** * Clear preview text and background highlight of all fields. */ clearPreviewedFormFields() { log.debug("clear previewed fields in:", this.form); for (let fieldDetail of this.fieldDetails) { let element = fieldDetail.elementWeakRef.get(); if (!element) { log.warn(fieldDetail.fieldName, "is unreachable"); continue; } element.previewValue = ""; // We keep the state if this field has // already been auto-filled. if (fieldDetail.state == FIELD_STATES.AUTO_FILLED) { continue; } this._changeFieldState(fieldDetail, FIELD_STATES.NORMAL); } } /** * Clear value and highlight style of all filled fields. */ clearPopulatedForm() { for (let fieldDetail of this.fieldDetails) { let element = fieldDetail.elementWeakRef.get(); if (!element) { log.warn(fieldDetail.fieldName, "is unreachable"); continue; } // Only reset value for input element. if ( fieldDetail.state == FIELD_STATES.AUTO_FILLED && ChromeUtils.getClassName(element) === "HTMLInputElement" ) { element.setUserInput(""); } } } /** * Change the state of a field to correspond with different presentations. * * @param {Object} fieldDetail * A fieldDetail of which its element is about to update the state. * @param {string} nextState * Used to determine the next state */ _changeFieldState(fieldDetail, nextState) { let element = fieldDetail.elementWeakRef.get(); if (!element) { log.warn(fieldDetail.fieldName, "is unreachable while changing state"); return; } if (!(nextState in this._FIELD_STATE_ENUM)) { log.warn( fieldDetail.fieldName, "is trying to change to an invalid state" ); return; } if (fieldDetail.state == nextState) { return; } for (let [state, mmStateValue] of Object.entries(this._FIELD_STATE_ENUM)) { // The NORMAL state is simply the absence of other manually // managed states so we never need to add or remove it. if (!mmStateValue) { continue; } if (state == nextState) { this.winUtils.addManuallyManagedState(element, mmStateValue); } else { this.winUtils.removeManuallyManagedState(element, mmStateValue); } } if (nextState == FIELD_STATES.AUTO_FILLED) { element.addEventListener("input", this, { mozSystemGroup: true }); } fieldDetail.state = nextState; } resetFieldStates() { for (let fieldDetail of this.fieldDetails) { const element = fieldDetail.elementWeakRef.get(); element.removeEventListener("input", this, { mozSystemGroup: true }); this._changeFieldState(fieldDetail, FIELD_STATES.NORMAL); } this.filledRecordGUID = null; } isFilled() { return !!this.filledRecordGUID; } /** * Return the record that is converted from `fieldDetails` and only valid * form record is included. * * @returns {Object|null} * A record object consists of three properties: * - guid: The id of the previously-filled profile or null if omitted. * - record: A valid record converted from details with trimmed result. * - untouchedFields: Fields that aren't touched after autofilling. * Return `null` for any uncreatable or invalid record. */ createRecord() { let details = this.fieldDetails; if (!this.isEnabled() || !details || !details.length) { return null; } let data = { guid: this.filledRecordGUID, record: {}, untouchedFields: [], }; if (this.flowId) { data.flowId = this.flowId; } details.forEach(detail => { let element = detail.elementWeakRef.get(); // Remove the unnecessary spaces let value = element && element.value.trim(); value = this.computeFillingValue(value, detail, element); if (!value || value.length > FormAutofillUtils.MAX_FIELD_VALUE_LENGTH) { // Keep the property and preserve more information for updating data.record[detail.fieldName] = ""; return; } data.record[detail.fieldName] = value; if (detail.state == FIELD_STATES.AUTO_FILLED) { data.untouchedFields.push(detail.fieldName); } }); this.normalizeCreatingRecord(data); if (!this.isRecordCreatable(data.record)) { return null; } return data; } handleEvent(event) { switch (event.type) { case "input": { if (!event.isTrusted) { return; } const target = event.target; const targetFieldDetail = this.getFieldDetailByElement(target); const isCreditCardField = FormAutofillUtils.isCreditCardField( targetFieldDetail.fieldName ); // If the user manually blanks a credit card field, then // we want the popup to be activated. if ( ChromeUtils.getClassName(target) !== "HTMLSelectElement" && isCreditCardField && target.value === "" ) { formFillController.showPopup(); } if (targetFieldDetail.state == FIELD_STATES.NORMAL) { return; } this._changeFieldState(targetFieldDetail, FIELD_STATES.NORMAL); if (isCreditCardField) { Services.telemetry.recordEvent( "creditcard", "filled_modified", "cc_form", this.flowId, { field_name: targetFieldDetail.fieldName, } ); } let isAutofilled = false; let dimFieldDetails = []; for (const fieldDetail of this.fieldDetails) { const element = fieldDetail.elementWeakRef.get(); if (ChromeUtils.getClassName(element) === "HTMLSelectElement") { // Dim fields are those we don't attempt to revert their value // when clear the target set, such as <select>. dimFieldDetails.push(fieldDetail); } else { isAutofilled |= fieldDetail.state == FIELD_STATES.AUTO_FILLED; } } if (!isAutofilled) { // Restore the dim fields to initial state as well once we knew // that user had intention to clear the filled form manually. for (const fieldDetail of dimFieldDetails) { this._changeFieldState(fieldDetail, FIELD_STATES.NORMAL); } this.filledRecordGUID = null; } break; } } } } class FormAutofillAddressSection extends FormAutofillSection { constructor(fieldDetails, winUtils) { super(fieldDetails, winUtils); this._cacheValue.oneLineStreetAddress = null; } isValidSection() { return ( this.fieldDetails.length >= FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD ); } isEnabled() { return FormAutofill.isAutofillAddressesEnabled; } isRecordCreatable(record) { if ( record.country && !FormAutofill.supportedCountries.includes(record.country) ) { // We don't want to save data in the wrong fields due to not having proper // heuristic regexes in countries we don't yet support. log.warn("isRecordCreatable: Country not supported:", record.country); return false; } let hasName = 0; let length = 0; for (let key of Object.keys(record)) { if (!record[key]) { continue; } if (FormAutofillUtils.getCategoryFromFieldName(key) == "name") { hasName = 1; continue; } length++; } return length + hasName >= FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD; } _getOneLineStreetAddress(address) { if (!this._cacheValue.oneLineStreetAddress) { this._cacheValue.oneLineStreetAddress = {}; } if (!this._cacheValue.oneLineStreetAddress[address]) { this._cacheValue.oneLineStreetAddress[ address ] = FormAutofillUtils.toOneLineAddress(address); } return this._cacheValue.oneLineStreetAddress[address]; } addressTransformer(profile) { if (profile["street-address"]) { // "-moz-street-address-one-line" is used by the labels in // ProfileAutoCompleteResult. profile["-moz-street-address-one-line"] = this._getOneLineStreetAddress( profile["street-address"] ); let streetAddressDetail = this.getFieldDetailByName("street-address"); if ( streetAddressDetail && ChromeUtils.getClassName(streetAddressDetail.elementWeakRef.get()) === "HTMLInputElement" ) { profile["street-address"] = profile["-moz-street-address-one-line"]; } let waitForConcat = []; for (let f of ["address-line3", "address-line2", "address-line1"]) { waitForConcat.unshift(profile[f]); if (this.getFieldDetailByName(f)) { if (waitForConcat.length > 1) { profile[f] = FormAutofillUtils.toOneLineAddress(waitForConcat); } waitForConcat = []; } } } } /** * Replace tel with tel-national if tel violates the input element's * restriction. * @param {Object} profile * A profile to be converted. */ telTransformer(profile) { if (!profile.tel || !profile["tel-national"]) { return; } let detail = this.getFieldDetailByName("tel"); if (!detail) { return; } let element = detail.elementWeakRef.get(); let _pattern; let testPattern = str => { if (!_pattern) { // The pattern has to match the entire value. _pattern = new RegExp("^(?:" + element.pattern + ")$", "u"); } return _pattern.test(str); }; if (element.pattern) { if (testPattern(profile.tel)) { return; } } else if (element.maxLength) { if ( detail._reason == "autocomplete" && profile.tel.length <= element.maxLength ) { return; } } if (detail._reason != "autocomplete") { // Since we only target people living in US and using en-US websites in // MVP, it makes more sense to fill `tel-national` instead of `tel` // if the field is identified by heuristics and no other clues to // determine which one is better. // TODO: [Bug 1407545] This should be improved once more countries are // supported. profile.tel = profile["tel-national"]; } else if (element.pattern) { if (testPattern(profile["tel-national"])) { profile.tel = profile["tel-national"]; } } else if (element.maxLength) { if (profile["tel-national"].length <= element.maxLength) { profile.tel = profile["tel-national"]; } } } /* * Apply all address related transformers. * * @param {Object} profile * A profile for adjusting address related value. * @override */ applyTransformers(profile) { this.addressTransformer(profile); this.telTransformer(profile); this.matchSelectOptions(profile); this.adaptFieldMaxLength(profile); } computeFillingValue(value, fieldDetail, element) { // Try to abbreviate the value of select element. if ( fieldDetail.fieldName == "address-level1" && ChromeUtils.getClassName(element) === "HTMLSelectElement" ) { // Don't save the record when the option value is empty *OR* there // are multiple options being selected. The empty option is usually // assumed to be default along with a meaningless text to users. if (!value || element.selectedOptions.length != 1) { // Keep the property and preserve more information for address updating value = ""; } else { let text = element.selectedOptions[0].text.trim(); value = FormAutofillUtils.getAbbreviatedSubregionName([value, text]) || text; } } return value; } normalizeCreatingRecord(address) { if (!address) { return; } // Normalize Country if (address.record.country) { let detail = this.getFieldDetailByName("country"); // Try identifying country field aggressively if it doesn't come from // @autocomplete. if (detail._reason != "autocomplete") { let countryCode = FormAutofillUtils.identifyCountryCode( address.record.country ); if (countryCode) { address.record.country = countryCode; } } } // Normalize Tel FormAutofillUtils.compressTel(address.record); if (address.record.tel) { let allTelComponentsAreUntouched = Object.keys(address.record) .filter( field => FormAutofillUtils.getCategoryFromFieldName(field) == "tel" ) .every(field => address.untouchedFields.includes(field)); if (allTelComponentsAreUntouched) { // No need to verify it if none of related fields are modified after autofilling. if (!address.untouchedFields.includes("tel")) { address.untouchedFields.push("tel"); } } else { let strippedNumber = address.record.tel.replace(/[\s\(\)-]/g, ""); // Remove "tel" if it contains invalid characters or the length of its // number part isn't between 5 and 15. // (The maximum length of a valid number in E.164 format is 15 digits // according to https://en.wikipedia.org/wiki/E.164 ) if (!/^(\+?)[\da-zA-Z]{5,15}$/.test(strippedNumber)) { address.record.tel = ""; } } } } } class FormAutofillCreditCardSection extends FormAutofillSection { /** * Credit Card Section Constructor * * @param {Object} fieldDetails * The fieldDetail objects for the fields in this section * @param {Object} winUtils * A WindowUtils reference for the Window the section appears in * @param {Object} handler * The FormAutofillHandler responsible for this section */ constructor(fieldDetails, winUtils, handler) { super(fieldDetails, winUtils); this.handler = handler; // Identifier used to correlate events relating to the same form this.flowId = gUUIDGenerator.generateUUID().toString(); log.debug("Creating new credit card section with flowId =", this.flowId); if (!this.isValidSection()) { return; } // Record which fields could be identified let identified = new Set(); fieldDetails.forEach(detail => identified.add(detail.fieldName)); Services.telemetry.recordEvent( "creditcard", "detected", "cc_form", this.flowId, { cc_name_found: identified.has("cc-name") ? "true" : "false", cc_number_found: identified.has("cc-number") ? "true" : "false", cc_exp_found: identified.has("cc-exp") || (identified.has("cc-exp-month") && identified.has("cc-exp-year")) ? "true" : "false", } ); Services.telemetry.scalarAdd( "formautofill.creditCards.detected_sections_count", 1 ); // Check whether the section is in an <iframe>; and, if so, // watch for the <iframe> to pagehide. if (handler.window.location != handler.window.parent?.location) { log.debug( "Credit card form is in an iframe -- watching for pagehide", fieldDetails ); handler.window.addEventListener( "pagehide", this._handlePageHide.bind(this) ); } } _handlePageHide(event) { this.handler.window.removeEventListener( "pagehide", this._handlePageHide.bind(this) ); log.debug("Credit card subframe is pagehideing", this.handler.form); this.handler.onFormSubmitted(); } isValidSection() { let ccNumberReason = ""; let hasCCNumber = false; let hasExpiryDate = false; let hasCCName = false; for (let detail of this.fieldDetails) { switch (detail.fieldName) { case "cc-number": hasCCNumber = true; ccNumberReason = detail._reason; break; case "cc-name": case "cc-given-name": case "cc-additional-name": case "cc-family-name": hasCCName = true; break; case "cc-exp": case "cc-exp-month": case "cc-exp-year": hasExpiryDate = true; break; } } return ( hasCCNumber && (ccNumberReason == "autocomplete" || hasExpiryDate || hasCCName) ); } isEnabled() { return FormAutofill.isAutofillCreditCardsEnabled; } isRecordCreatable(record) { return ( record["cc-number"] && FormAutofillUtils.isCCNumber(record["cc-number"]) ); } creditCardExpDateTransformer(profile) { if (!profile["cc-exp"]) { return; } let detail = this.getFieldDetailByName("cc-exp"); if (!detail) { return; } let element = detail.elementWeakRef.get(); if (element.tagName != "INPUT" || !element.placeholder) { return; } let result, ccExpMonth = profile["cc-exp-month"], ccExpYear = profile["cc-exp-year"], placeholder = element.placeholder; result = /(?:[^m]|\b)(m{1,2})\s*([-/\\]*)\s*(y{2,4})(?!y)/i.exec( placeholder ); if (result) { profile["cc-exp"] = String(ccExpMonth).padStart(result[1].length, "0") + result[2] + String(ccExpYear).substr(-1 * result[3].length); return; } result = /(?:[^y]|\b)(y{2,4})\s*([-/\\]*)\s*(m{1,2})(?!m)/i.exec( placeholder ); if (result) { profile["cc-exp"] = String(ccExpYear).substr(-1 * result[1].length) + result[2] + String(ccExpMonth).padStart(result[3].length, "0"); } } async _decrypt(cipherText, reauth) { // Get the window for the form field. let window; for (let fieldDetail of this.fieldDetails) { let element = fieldDetail.elementWeakRef.get(); if (element) { window = element.ownerGlobal; break; } } if (!window) { return null; } let actor = window.windowGlobalChild.getActor("FormAutofill"); return actor.sendQuery("FormAutofill:GetDecryptedString", { cipherText, reauth, }); } /* * Apply all credit card related transformers. * * @param {Object} profile * A profile for adjusting credit card related value. * @override */ applyTransformers(profile) { this.matchSelectOptions(profile); this.creditCardExpDateTransformer(profile); this.adaptFieldMaxLength(profile); } computeFillingValue(value, fieldDetail, element) { if ( fieldDetail.fieldName != "cc-type" || ChromeUtils.getClassName(element) !== "HTMLSelectElement" ) { return value; } if (CreditCard.isValidNetwork(value)) { return value; } // Don't save the record when the option value is empty *OR* there // are multiple options being selected. The empty option is usually // assumed to be default along with a meaningless text to users. if (value && element.selectedOptions.length == 1) { let selectedOption = element.selectedOptions[0]; let networkType = CreditCard.getNetworkFromName(selectedOption.text) ?? CreditCard.getNetworkFromName(selectedOption.value); if (networkType) { return networkType; } } // If we couldn't match the value to any network, we'll // strip this field when submitting. return value; } /** * Customize for previewing prorifle. * * @param {Object} profile * A profile for pre-processing before previewing values. * @override */ preparePreviewProfile(profile) { // Always show the decrypted credit card number when Master Password is // disabled. if (profile["cc-number-decrypted"]) { profile["cc-number"] = profile["cc-number-decrypted"]; } } /** * Customize for filling prorifle. * * @param {Object} profile * A profile for pre-processing before filling values. * @returns {boolean} Whether the profile should be filled. * @override */ async prepareFillingProfile(profile) { // Prompt the OS login dialog to get the decrypted credit // card number. if (profile["cc-number-encrypted"]) { let decrypted = await this._decrypt( profile["cc-number-encrypted"], reauthPasswordPromptMessage ); if (!decrypted) { // Early return if the decrypted is empty or undefined return false; } profile["cc-number"] = decrypted; } return true; } async autofillFields(profile) { if (!(await super.autofillFields(profile))) { return false; } // Calculate values for telemetry let extra = { cc_name: "unavailable", cc_number: "unavailable", cc_exp: "unavailable", }; for (let fieldDetail of this.fieldDetails) { let element = fieldDetail.elementWeakRef.get(); let state = profile[fieldDetail.fieldName] ? "filled" : "not_filled"; if ( fieldDetail.state == FIELD_STATES.NORMAL && (ChromeUtils.getClassName(element) == "HTMLSelectElement" || (ChromeUtils.getClassName(element) == "HTMLInputElement" && element.value.length)) ) { state = "user_filled"; } switch (fieldDetail.fieldName) { case "cc-name": extra.cc_name = state; break; case "cc-number": extra.cc_number = state; break; case "cc-exp": case "cc-exp-month": case "cc-exp-year": extra.cc_exp = state; break; } } Services.telemetry.recordEvent( "creditcard", "filled", "cc_form", this.flowId, extra ); return true; } } /** * Handles profile autofill for a DOM Form element. */ class FormAutofillHandler { /** * Initialize the form from `FormLike` object to handle the section or form * operations. * @param {FormLike} form Form that need to be auto filled * @param {function} onFormSubmitted Function that can be invoked * to simulate form submission. Function is passed * three arguments: (1) a FormLike for the form being * submitted, (2) the corresponding Window, and (3) the * responsible FormAutofillHandler. */ constructor(form, onFormSubmitted = () => {}) { this._updateForm(form); /** * The window to which this form belongs */ this.window = this.form.rootElement.ownerGlobal; /** * A WindowUtils reference of which Window the form belongs */ this.winUtils = this.window.windowUtils; /** * Time in milliseconds since epoch when a user started filling in the form. */ this.timeStartedFillingMS = null; /** * This function is used if the form handler (or one of its sections) * determines that it needs to act as if the form had been submitted. */ this.onFormSubmitted = () => { onFormSubmitted(this.form, this.window, this); }; } set focusedInput(element) { let section = this._sectionCache.get(element); if (!section) { section = this.sections.find(s => s.getFieldDetailByElement(element)); this._sectionCache.set(element, section); } this._focusedSection = section; if (section) { section.focusedInput = element; } } get activeSection() { return this._focusedSection; } /** * Check the form is necessary to be updated. This function should be able to * detect any changes including all control elements in the form. * @param {HTMLElement} element The element supposed to be in the form. * @returns {boolean} FormAutofillHandler.form is updated or not. */ updateFormIfNeeded(element) { // When the following condition happens, FormAutofillHandler.form should be // updated: // * The count of form controls is changed. // * When the element can not be found in the current form. // // However, we should improve the function to detect the element changes. // e.g. a tel field is changed from type="hidden" to type="tel". let _formLike; let getFormLike = () => { if (!_formLike) { _formLike = FormLikeFactory.createFromField(element); } return _formLike; }; let currentForm = element.form; if (!currentForm) { currentForm = getFormLike(); } if (currentForm.elements.length != this.form.elements.length) { log.debug("The count of form elements is changed."); this._updateForm(getFormLike()); return true; } if (!this.form.elements.includes(element)) { log.debug("The element can not be found in the current form."); this._updateForm(getFormLike()); return true; } return false; } /** * Update the form with a new FormLike, and the related fields should be * updated or clear to ensure the data consistency. * @param {FormLike} form a new FormLike to replace the original one. */ _updateForm(form) { /** * DOM Form element to which this object is attached. */ this.form = form; /** * Array of collected data about relevant form fields. Each item is an object * storing the identifying details of the field and a reference to the * originally associated element from the form. * * The "section", "addressType", "contactType", and "fieldName" values are * used to identify the exact field when the serializable data is received * from the backend. There cannot be multiple fields which have * the same exact combination of these values. * * A direct reference to the associated element cannot be sent to the user * interface because processing may be done in the parent process. */ this.fieldDetails = null; this.sections = []; this._sectionCache = new WeakMap(); } /** * Set fieldDetails from the form about fields that can be autofilled. * * @param {boolean} allowDuplicates * true to remain any duplicated field details otherwise to remove the * duplicated ones. * @returns {Array} The valid address and credit card details. */ collectFormFields(allowDuplicates = false) { let sections = FormAutofillHeuristics.getFormInfo( this.form, allowDuplicates ); let allValidDetails = []; for (let { fieldDetails, type } of sections) { let section; if (type == FormAutofillUtils.SECTION_TYPES.ADDRESS) { section = new FormAutofillAddressSection(fieldDetails, this.winUtils); } else if (type == FormAutofillUtils.SECTION_TYPES.CREDIT_CARD) { section = new FormAutofillCreditCardSection( fieldDetails, this.winUtils, this ); } else { throw new Error("Unknown field type."); } this.sections.push(section); allValidDetails.push(...section.fieldDetails); } for (let detail of allValidDetails) { let input = detail.elementWeakRef.get(); if (!input) { continue; } input.addEventListener("input", this, { mozSystemGroup: true }); } this.fieldDetails = allValidDetails; return allValidDetails; } _hasFilledSection() { return this.sections.some(section => section.isFilled()); } /** * Processes form fields that can be autofilled, and populates them with the * profile provided by backend. * * @param {Object} profile * A profile to be filled in. */ async autofillFormFields(profile) { let noFilledSectionsPreviously = !this._hasFilledSection(); await this.activeSection.autofillFields(profile); const onChangeHandler = e => { if (!e.isTrusted) { return; } if (e.type == "reset") { for (let section of this.sections) { section.resetFieldStates(); } } // Unregister listeners once no field is in AUTO_FILLED state. if (!this._hasFilledSection()) { this.form.rootElement.removeEventListener("input", onChangeHandler, { mozSystemGroup: true, }); this.form.rootElement.removeEventListener("reset", onChangeHandler, { mozSystemGroup: true, }); } }; if (noFilledSectionsPreviously) { // Handle the highlight style resetting caused by user's correction afterward. log.debug("register change handler for filled form:", this.form); this.form.rootElement.addEventListener("input", onChangeHandler, { mozSystemGroup: true, }); this.form.rootElement.addEventListener("reset", onChangeHandler, { mozSystemGroup: true, }); } } handleEvent(event) { switch (event.type) { case "input": if (!event.isTrusted) { return; } for (let detail of this.fieldDetails) { let input = detail.elementWeakRef.get(); if (!input) { continue; } input.removeEventListener("input", this, { mozSystemGroup: true }); } this.timeStartedFillingMS = Date.now(); break; } } /** * Collect the filled sections within submitted form and convert all the valid * field data into multiple records. * * @returns {Object} records * {Array.<Object>} records.address * {Array.<Object>} records.creditCard */ createRecords() { const records = { address: [], creditCard: [], }; for (const section of this.sections) { const secRecord = section.createRecord(); if (!secRecord) { continue; } if (section instanceof FormAutofillAddressSection) { records.address.push(secRecord); } else if (section instanceof FormAutofillCreditCardSection) { records.creditCard.push(secRecord); } else { throw new Error("Unknown section type"); } } log.debug("Create records:", records); return records; } } PK �������!<+;����%���chrome/res/FormAutofillHeuristics.jsm/* 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/. */ /* * Form Autofill field heuristics. */ "use strict"; var EXPORTED_SYMBOLS = ["FormAutofillHeuristics", "LabelUtils"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const { FormAutofill } = ChromeUtils.import( "resource://formautofill/FormAutofill.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofillUtils", "resource://formautofill/FormAutofillUtils.jsm" ); XPCOMUtils.defineLazyModuleGetters(this, { CreditCard: "resource://gre/modules/CreditCard.jsm", }); this.log = null; FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]); const PREF_HEURISTICS_ENABLED = "extensions.formautofill.heuristics.enabled"; const PREF_SECTION_ENABLED = "extensions.formautofill.section.enabled"; const DEFAULT_SECTION_NAME = "-moz-section-default"; /** * To help us classify sections, we want to know what fields can appear * multiple times in a row. * Such fields, like `address-line{X}`, should not break sections. */ const MULTI_FIELD_NAMES = [ "address-level3", "address-level2", "address-level1", "tel", "postal-code", "email", "street-address", ]; /** * A scanner for traversing all elements in a form and retrieving the field * detail with FormAutofillHeuristics.getInfo function. It also provides a * cursor (parsingIndex) to indicate which element is waiting for parsing. */ class FieldScanner { /** * Create a FieldScanner based on form elements with the existing * fieldDetails. * * @param {Array.DOMElement} elements * The elements from a form for each parser. */ constructor(elements, { allowDuplicates = false, sectionEnabled = true }) { this._elementsWeakRef = Cu.getWeakReference(elements); this.fieldDetails = []; this._parsingIndex = 0; this._sections = []; this._allowDuplicates = allowDuplicates; this._sectionEnabled = sectionEnabled; } get _elements() { return this._elementsWeakRef.get(); } /** * This cursor means the index of the element which is waiting for parsing. * * @returns {number} * The index of the element which is waiting for parsing. */ get parsingIndex() { return this._parsingIndex; } /** * Move the parsingIndex to the next elements. Any elements behind this index * means the parsing tasks are finished. * * @param {number} index * The latest index of elements waiting for parsing. */ set parsingIndex(index) { if (index > this._elements.length) { throw new Error("The parsing index is out of range."); } this._parsingIndex = index; } /** * Retrieve the field detail by the index. If the field detail is not ready, * the elements will be traversed until matching the index. * * @param {number} index * The index of the element that you want to retrieve. * @returns {Object} * The field detail at the specific index. */ getFieldDetailByIndex(index) { if (index >= this._elements.length) { throw new Error( `The index ${index} is out of range.(${this._elements.length})` ); } if (index < this.fieldDetails.length) { return this.fieldDetails[index]; } for (let i = this.fieldDetails.length; i < index + 1; i++) { this.pushDetail(); } return this.fieldDetails[index]; } get parsingFinished() { return this.parsingIndex >= this._elements.length; } _pushToSection(name, fieldDetail) { for (let section of this._sections) { if (section.name == name) { section.fieldDetails.push(fieldDetail); return; } } this._sections.push({ name, fieldDetails: [fieldDetail], }); } _classifySections() { let fieldDetails = this._sections[0].fieldDetails; this._sections = []; let seenTypes = new Set(); let previousType; let sectionCount = 0; for (let fieldDetail of fieldDetails) { if (!fieldDetail.fieldName) { continue; } if ( seenTypes.has(fieldDetail.fieldName) && (previousType != fieldDetail.fieldName || !MULTI_FIELD_NAMES.includes(fieldDetail.fieldName)) ) { seenTypes.clear(); sectionCount++; } previousType = fieldDetail.fieldName; seenTypes.add(fieldDetail.fieldName); this._pushToSection( DEFAULT_SECTION_NAME + "-" + sectionCount, fieldDetail ); } } /** * The result is an array contains the sections with its belonging field * details. If `this._sections` contains one section only with the default * section name (DEFAULT_SECTION_NAME), `this._classifySections` should be * able to identify all sections in the heuristic way. * * @returns {Array<Object>} * The array with the sections, and the belonging fieldDetails are in * each section. */ getSectionFieldDetails() { // When the section feature is disabled, `getSectionFieldDetails` should // provide a single address and credit card section result. if (!this._sectionEnabled) { return this._getFinalDetails(this.fieldDetails); } if (!this._sections.length) { return []; } if ( this._sections.length == 1 && this._sections[0].name == DEFAULT_SECTION_NAME ) { this._classifySections(); } return this._sections.reduce((sections, current) => { sections.push(...this._getFinalDetails(current.fieldDetails)); return sections; }, []); } /** * This function will prepare an autocomplete info object with getInfo * function and push the detail to fieldDetails property. * Any field will be pushed into `this._sections` based on the section name * in `autocomplete` attribute. * * Any element without the related detail will be used for adding the detail * to the end of field details. */ pushDetail() { let elementIndex = this.fieldDetails.length; if (elementIndex >= this._elements.length) { throw new Error("Try to push the non-existing element info."); } let element = this._elements[elementIndex]; let info = FormAutofillHeuristics.getInfo(element); let fieldInfo = { section: info ? info.section : "", addressType: info ? info.addressType : "", contactType: info ? info.contactType : "", fieldName: info ? info.fieldName : "", elementWeakRef: Cu.getWeakReference(element), }; if (info && info._reason) { fieldInfo._reason = info._reason; } this.fieldDetails.push(fieldInfo); this._pushToSection(this._getSectionName(fieldInfo), fieldInfo); } _getSectionName(info) { let names = []; if (info.section) { names.push(info.section); } if (info.addressType) { names.push(info.addressType); } return names.length ? names.join(" ") : DEFAULT_SECTION_NAME; } /** * When a field detail should be changed its fieldName after parsing, use * this function to update the fieldName which is at a specific index. * * @param {number} index * The index indicates a field detail to be updated. * @param {string} fieldName * The new fieldName */ updateFieldName(index, fieldName) { if (index >= this.fieldDetails.length) { throw new Error("Try to update the non-existing field detail."); } this.fieldDetails[index].fieldName = fieldName; } _isSameField(field1, field2) { return ( field1.section == field2.section && field1.addressType == field2.addressType && field1.fieldName == field2.fieldName ); } /** * Provide the final field details without invalid field name, and the * duplicated fields will be removed as well. For the debugging purpose, * the final `fieldDetails` will include the duplicated fields if * `_allowDuplicates` is true. * * Each item should contain one type of fields only, and the two valid types * are Address and CreditCard. * * @param {Array<Object>} fieldDetails * The field details for trimming. * @returns {Array<Object>} * The array with the field details without invalid field name and * duplicated fields. */ _getFinalDetails(fieldDetails) { let addressFieldDetails = []; let creditCardFieldDetails = []; for (let fieldDetail of fieldDetails) { let fieldName = fieldDetail.fieldName; if (FormAutofillUtils.isAddressField(fieldName)) { addressFieldDetails.push(fieldDetail); } else if (FormAutofillUtils.isCreditCardField(fieldName)) { creditCardFieldDetails.push(fieldDetail); } else { log.debug( "Not collecting a field with a unknown fieldName", fieldDetail ); } } return [ { type: FormAutofillUtils.SECTION_TYPES.ADDRESS, fieldDetails: addressFieldDetails, }, { type: FormAutofillUtils.SECTION_TYPES.CREDIT_CARD, fieldDetails: creditCardFieldDetails, }, ] .map(section => { if (this._allowDuplicates) { return section; } // Deduplicate each set of fieldDetails let details = section.fieldDetails; section.fieldDetails = details.filter((detail, index) => { let previousFields = details.slice(0, index); return !previousFields.find(f => this._isSameField(detail, f)); }); return section; }) .filter(section => !!section.fieldDetails.length); } elementExisting(index) { return index < this._elements.length; } } var LabelUtils = { // The tag name list is from Chromium except for "STYLE": // eslint-disable-next-line max-len // https://cs.chromium.org/chromium/src/components/autofill/content/renderer/form_autofill_util.cc?l=216&rcl=d33a171b7c308a64dc3372fac3da2179c63b419e EXCLUDED_TAGS: ["SCRIPT", "NOSCRIPT", "OPTION", "STYLE"], // A map object, whose keys are the id's of form fields and each value is an // array consisting of label elements correponding to the id. // @type {Map<string, array>} _mappedLabels: null, // An array consisting of label elements whose correponding form field doesn't // have an id attribute. // @type {Array<HTMLLabelElement>} _unmappedLabels: null, // A weak map consisting of label element and extracted strings pairs. // @type {WeakMap<HTMLLabelElement, array>} _labelStrings: null, /** * Extract all strings of an element's children to an array. * "element.textContent" is a string which is merged of all children nodes, * and this function provides an array of the strings contains in an element. * * @param {Object} element * A DOM element to be extracted. * @returns {Array} * All strings in an element. */ extractLabelStrings(element) { if (this._labelStrings.has(element)) { return this._labelStrings.get(element); } let strings = []; let _extractLabelStrings = el => { if (this.EXCLUDED_TAGS.includes(el.tagName)) { return; } if (el.nodeType == el.TEXT_NODE || !el.childNodes.length) { let trimmedText = el.textContent.trim(); if (trimmedText) { strings.push(trimmedText); } return; } for (let node of el.childNodes) { let nodeType = node.nodeType; if (nodeType != node.ELEMENT_NODE && nodeType != node.TEXT_NODE) { continue; } _extractLabelStrings(node); } }; _extractLabelStrings(element); this._labelStrings.set(element, strings); return strings; }, generateLabelMap(doc) { let mappedLabels = new Map(); let unmappedLabels = []; for (let label of doc.querySelectorAll("label")) { let id = label.htmlFor; if (!id) { let control = label.control; if (!control) { continue; } id = control.id; } if (id) { let labels = mappedLabels.get(id); if (labels) { labels.push(label); } else { mappedLabels.set(id, [label]); } } else { unmappedLabels.push(label); } } this._mappedLabels = mappedLabels; this._unmappedLabels = unmappedLabels; this._labelStrings = new WeakMap(); }, clearLabelMap() { this._mappedLabels = null; this._unmappedLabels = null; this._labelStrings = null; }, findLabelElements(element) { if (!this._mappedLabels) { this.generateLabelMap(element.ownerDocument); } let id = element.id; if (!id) { return this._unmappedLabels.filter(label => label.control == element); } return this._mappedLabels.get(id) || []; }, }; /** * Returns the autocomplete information of fields according to heuristics. */ this.FormAutofillHeuristics = { RULES: null, /** * Try to find a contiguous sub-array within an array. * * @param {Array} array * @param {Array} subArray * * @returns {boolean} * Return whether subArray was found within the array or not. */ _matchContiguousSubArray(array, subArray) { return array.some((elm, i) => subArray.every((sElem, j) => sElem == array[i + j]) ); }, /** * Try to find the field that is look like a month select. * * @param {DOMElement} element * @returns {boolean} * Return true if we observe the trait of month select in * the current element. */ _isExpirationMonthLikely(element) { if (ChromeUtils.getClassName(element) !== "HTMLSelectElement") { return false; } const options = [...element.options]; const desiredValues = Array(12) .fill(1) .map((v, i) => v + i); // The number of month options shouldn't be less than 12 or larger than 13 // including the default option. if (options.length < 12 || options.length > 13) { return false; } return ( this._matchContiguousSubArray( options.map(e => +e.value), desiredValues ) || this._matchContiguousSubArray( options.map(e => +e.label), desiredValues ) ); }, /** * Try to find the field that is look like a year select. * * @param {DOMElement} element * @returns {boolean} * Return true if we observe the trait of year select in * the current element. */ _isExpirationYearLikely(element) { if (ChromeUtils.getClassName(element) !== "HTMLSelectElement") { return false; } const options = [...element.options]; // A normal expiration year select should contain at least the last three years // in the list. const curYear = new Date().getFullYear(); const desiredValues = Array(3) .fill(0) .map((v, i) => v + curYear + i); return ( this._matchContiguousSubArray( options.map(e => +e.value), desiredValues ) || this._matchContiguousSubArray( options.map(e => +e.label), desiredValues ) ); }, /** * Try to match the telephone related fields to the grammar * list to see if there is any valid telephone set and correct their * field names. * * @param {FieldScanner} fieldScanner * The current parsing status for all elements * @returns {boolean} * Return true if there is any field can be recognized in the parser, * otherwise false. */ _parsePhoneFields(fieldScanner) { let matchingResult; const GRAMMARS = this.PHONE_FIELD_GRAMMARS; for (let i = 0; i < GRAMMARS.length; i++) { let detailStart = fieldScanner.parsingIndex; let ruleStart = i; for ( ; i < GRAMMARS.length && GRAMMARS[i][0] && fieldScanner.elementExisting(detailStart); i++, detailStart++ ) { let detail = fieldScanner.getFieldDetailByIndex(detailStart); if ( !detail || GRAMMARS[i][0] != detail.fieldName || (detail._reason && detail._reason == "autocomplete") ) { break; } let element = detail.elementWeakRef.get(); if (!element) { break; } if ( GRAMMARS[i][2] && (!element.maxLength || GRAMMARS[i][2] < element.maxLength) ) { break; } } if (i >= GRAMMARS.length) { break; } if (!GRAMMARS[i][0]) { matchingResult = { ruleFrom: ruleStart, ruleTo: i, }; break; } // Fast rewinding to the next rule. for (; i < GRAMMARS.length; i++) { if (!GRAMMARS[i][0]) { break; } } } let parsedField = false; if (matchingResult) { let { ruleFrom, ruleTo } = matchingResult; let detailStart = fieldScanner.parsingIndex; for (let i = ruleFrom; i < ruleTo; i++) { fieldScanner.updateFieldName(detailStart, GRAMMARS[i][1]); fieldScanner.parsingIndex++; detailStart++; parsedField = true; } } if (fieldScanner.parsingFinished) { return parsedField; } let nextField = fieldScanner.getFieldDetailByIndex( fieldScanner.parsingIndex ); if ( nextField && nextField._reason != "autocomplete" && fieldScanner.parsingIndex > 0 ) { const regExpTelExtension = new RegExp( "\\bext|ext\\b|extension|ramal", // pt-BR, pt-PT "iu" ); const previousField = fieldScanner.getFieldDetailByIndex( fieldScanner.parsingIndex - 1 ); const previousFieldType = FormAutofillUtils.getCategoryFromFieldName( previousField.fieldName ); if ( previousField && previousFieldType == "tel" && this._matchRegexp(nextField.elementWeakRef.get(), regExpTelExtension) ) { fieldScanner.updateFieldName( fieldScanner.parsingIndex, "tel-extension" ); fieldScanner.parsingIndex++; parsedField = true; } } return parsedField; }, /** * Try to find the correct address-line[1-3] sequence and correct their field * names. * * @param {FieldScanner} fieldScanner * The current parsing status for all elements * @returns {boolean} * Return true if there is any field can be recognized in the parser, * otherwise false. */ _parseAddressFields(fieldScanner) { let parsedFields = false; const addressLines = ["address-line1", "address-line2", "address-line3"]; // TODO: These address-line* regexps are for the lines with numbers, and // they are the subset of the regexps in `heuristicsRegexp.js`. We have to // find a better way to make them consistent. const addressLineRegexps = { "address-line1": new RegExp( "address[_-]?line(1|one)|address1|addr1" + "|addrline1|address_1" + // Extra rules by Firefox "|indirizzo1" + // it-IT "|住所1" + // ja-JP "|地址1" + // zh-CN "|주소.?1", // ko-KR "iu" ), "address-line2": new RegExp( "address[_-]?line(2|two)|address2|addr2" + "|addrline2|address_2" + // Extra rules by Firefox "|indirizzo2" + // it-IT "|住所2" + // ja-JP "|地址2" + // zh-CN "|주소.?2", // ko-KR "iu" ), "address-line3": new RegExp( "address[_-]?line(3|three)|address3|addr3" + "|addrline3|address_3" + // Extra rules by Firefox "|indirizzo3" + // it-IT "|住所3" + // ja-JP "|地址3" + // zh-CN "|주소.?3", // ko-KR "iu" ), }; while (!fieldScanner.parsingFinished) { let detail = fieldScanner.getFieldDetailByIndex( fieldScanner.parsingIndex ); if ( !detail || !addressLines.includes(detail.fieldName) || detail._reason == "autocomplete" ) { // When the field is not related to any address-line[1-3] fields or // determined by autocomplete attr, it means the parsing process can be // terminated. break; } const elem = detail.elementWeakRef.get(); for (let regexp of Object.keys(addressLineRegexps)) { if (this._matchRegexp(elem, addressLineRegexps[regexp])) { fieldScanner.updateFieldName(fieldScanner.parsingIndex, regexp); parsedFields = true; } } fieldScanner.parsingIndex++; } return parsedFields; }, /** * Try to look for expiration date fields and revise the field names if needed. * * @param {FieldScanner} fieldScanner * The current parsing status for all elements * @returns {boolean} * Return true if there is any field can be recognized in the parser, * otherwise false. */ _parseCreditCardFields(fieldScanner) { if (fieldScanner.parsingFinished) { return false; } const savedIndex = fieldScanner.parsingIndex; const detail = fieldScanner.getFieldDetailByIndex( fieldScanner.parsingIndex ); // Respect to autocomplete attr if (!detail || (detail._reason && detail._reason == "autocomplete")) { return false; } const monthAndYearFieldNames = ["cc-exp-month", "cc-exp-year"]; // Skip the uninteresting fields if ( !["cc-exp", "cc-type", ...monthAndYearFieldNames].includes( detail.fieldName ) ) { return false; } const element = detail.elementWeakRef.get(); // If we didn't auto-discover type field, check every select for options that // match credit card network names in value or label. if (ChromeUtils.getClassName(element) == "HTMLSelectElement") { for (let option of element.querySelectorAll("option")) { if ( CreditCard.getNetworkFromName(option.value) || CreditCard.getNetworkFromName(option.text) ) { fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-type"); fieldScanner.parsingIndex++; return true; } } } // If the input type is a month picker, then assume it's cc-exp. if (element.type == "month") { fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp"); fieldScanner.parsingIndex++; return true; } // Don't process the fields if expiration month and expiration year are already // matched by regex in correct order. if ( fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex++) .fieldName == "cc-exp-month" && !fieldScanner.parsingFinished && fieldScanner.getFieldDetailByIndex(fieldScanner.parsingIndex++) .fieldName == "cc-exp-year" ) { return true; } fieldScanner.parsingIndex = savedIndex; // Determine the field name by checking if the fields are month select and year select // likely. if (this._isExpirationMonthLikely(element)) { fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp-month"); fieldScanner.parsingIndex++; if (!fieldScanner.parsingFinished) { const nextDetail = fieldScanner.getFieldDetailByIndex( fieldScanner.parsingIndex ); const nextElement = nextDetail.elementWeakRef.get(); if (this._isExpirationYearLikely(nextElement)) { fieldScanner.updateFieldName( fieldScanner.parsingIndex, "cc-exp-year" ); fieldScanner.parsingIndex++; return true; } } } fieldScanner.parsingIndex = savedIndex; // Verify that the following consecutive two fields can match cc-exp-month and cc-exp-year // respectively. if (this._findMatchedFieldName(element, ["cc-exp-month"])) { fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp-month"); fieldScanner.parsingIndex++; if (!fieldScanner.parsingFinished) { const nextDetail = fieldScanner.getFieldDetailByIndex( fieldScanner.parsingIndex ); const nextElement = nextDetail.elementWeakRef.get(); if (this._findMatchedFieldName(nextElement, ["cc-exp-year"])) { fieldScanner.updateFieldName( fieldScanner.parsingIndex, "cc-exp-year" ); fieldScanner.parsingIndex++; return true; } } } fieldScanner.parsingIndex = savedIndex; // Look for MM and/or YY(YY). if (this._matchRegexp(element, /^mm$/gi)) { fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp-month"); fieldScanner.parsingIndex++; if (!fieldScanner.parsingFinished) { const nextDetail = fieldScanner.getFieldDetailByIndex( fieldScanner.parsingIndex ); const nextElement = nextDetail.elementWeakRef.get(); if (this._matchRegexp(nextElement, /^(yy|yyyy)$/)) { fieldScanner.updateFieldName( fieldScanner.parsingIndex, "cc-exp-year" ); fieldScanner.parsingIndex++; return true; } } } fieldScanner.parsingIndex = savedIndex; // Look for a cc-exp with 2-digit or 4-digit year. if ( this._matchRegexp( element, /(?:exp.*date[^y\\n\\r]*|mm\\s*[-/]?\\s*)yy(?:[^y]|$)/gi ) || this._matchRegexp( element, /(?:exp.*date[^y\\n\\r]*|mm\\s*[-/]?\\s*)yyyy(?:[^y]|$)/gi ) ) { fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp"); fieldScanner.parsingIndex++; return true; } fieldScanner.parsingIndex = savedIndex; // Match general cc-exp regexp at last. if (this._findMatchedFieldName(element, ["cc-exp"])) { fieldScanner.updateFieldName(fieldScanner.parsingIndex, "cc-exp"); fieldScanner.parsingIndex++; return true; } fieldScanner.parsingIndex = savedIndex; // Set current field name to null as it failed to match any patterns. fieldScanner.updateFieldName(fieldScanner.parsingIndex, null); fieldScanner.parsingIndex++; return true; }, /** * This function should provide all field details of a form which are placed * in the belonging section. The details contain the autocomplete info * (e.g. fieldName, section, etc). * * `allowDuplicates` is used for the xpcshell-test purpose currently because * the heuristics should be verified that some duplicated elements still can * be predicted correctly. * * @param {HTMLFormElement} form * the elements in this form to be predicted the field info. * @param {boolean} allowDuplicates * true to remain any duplicated field details otherwise to remove the * duplicated ones. * @returns {Array<Array<Object>>} * all sections within its field details in the form. */ getFormInfo(form, allowDuplicates = false) { const eligibleFields = Array.from(form.elements).filter(elem => FormAutofillUtils.isFieldEligibleForAutofill(elem) ); if (eligibleFields.length <= 0) { return []; } let fieldScanner = new FieldScanner(eligibleFields, { allowDuplicates, sectionEnabled: this._sectionEnabled, }); while (!fieldScanner.parsingFinished) { let parsedPhoneFields = this._parsePhoneFields(fieldScanner); let parsedAddressFields = this._parseAddressFields(fieldScanner); let parsedExpirationDateFields = this._parseCreditCardFields( fieldScanner ); // If there is no any field parsed, the parsing cursor can be moved // forward to the next one. if ( !parsedPhoneFields && !parsedAddressFields && !parsedExpirationDateFields ) { fieldScanner.parsingIndex++; } } LabelUtils.clearLabelMap(); return fieldScanner.getSectionFieldDetails(); }, _regExpTableHashValue(...signBits) { return signBits.reduce((p, c, i) => p | (!!c << i), 0); }, _setRegExpListCache(regexps, b0, b1, b2) { if (!this._regexpList) { this._regexpList = []; } this._regexpList[this._regExpTableHashValue(b0, b1, b2)] = regexps; }, _getRegExpListCache(b0, b1, b2) { if (!this._regexpList) { return null; } return this._regexpList[this._regExpTableHashValue(b0, b1, b2)] || null; }, _getRegExpList(isAutoCompleteOff, elementTagName) { let isSelectElem = elementTagName == "SELECT"; let regExpListCache = this._getRegExpListCache( isAutoCompleteOff, FormAutofill.isAutofillCreditCardsAvailable, isSelectElem ); if (regExpListCache) { return regExpListCache; } const FIELDNAMES_IGNORING_AUTOCOMPLETE_OFF = [ "cc-name", "cc-number", "cc-exp-month", "cc-exp-year", "cc-exp", "cc-type", ]; let regexps = isAutoCompleteOff ? FIELDNAMES_IGNORING_AUTOCOMPLETE_OFF : Object.keys(this.RULES); if (!FormAutofill.isAutofillCreditCardsAvailable) { regexps = regexps.filter( name => !FormAutofillUtils.isCreditCardField(name) ); } if (isSelectElem) { const FIELDNAMES_FOR_SELECT_ELEMENT = [ "address-level1", "address-level2", "country", "cc-exp-month", "cc-exp-year", "cc-exp", "cc-type", ]; regexps = regexps.filter(name => FIELDNAMES_FOR_SELECT_ELEMENT.includes(name) ); } this._setRegExpListCache( regexps, isAutoCompleteOff, FormAutofill.isAutofillCreditCardsAvailable, isSelectElem ); return regexps; }, getInfo(element) { let info = element.getAutocompleteInfo(); // An input[autocomplete="on"] will not be early return here since it stll // needs to find the field name. if ( info && info.fieldName && info.fieldName != "on" && info.fieldName != "off" ) { info._reason = "autocomplete"; return info; } if (!this._prefEnabled) { return null; } let isAutoCompleteOff = element.autocomplete == "off" || (element.form && element.form.autocomplete == "off"); // "email" type of input is accurate for heuristics to determine its Email // field or not. However, "tel" type is used for ZIP code for some web site // (e.g. HomeDepot, BestBuy), so "tel" type should be not used for "tel" // prediction. if (element.type == "email" && !isAutoCompleteOff) { return { fieldName: "email", section: "", addressType: "", contactType: "", }; } let regexps = this._getRegExpList(isAutoCompleteOff, element.tagName); if (!regexps.length) { return null; } let matchedFieldName = this._findMatchedFieldName(element, regexps); if (matchedFieldName) { return { fieldName: matchedFieldName, section: "", addressType: "", contactType: "", }; } return null; }, /** * @typedef ElementStrings * @type {object} * @yield {string} id - element id. * @yield {string} name - element name. * @yield {Array<string>} labels - extracted labels. */ /** * Extract all the signature strings of an element. * * @param {HTMLElement} element * @returns {ElementStrings} */ _getElementStrings(element) { return { *[Symbol.iterator]() { yield element.id; yield element.name; const labels = LabelUtils.findLabelElements(element); for (let label of labels) { yield* LabelUtils.extractLabelStrings(label); } }, }; }, /** * Find the first matched field name of the element wih given regex list. * * @param {HTMLElement} element * @param {Array<string>} regexps * The regex key names that correspond to pattern in the rule list. * @returns {?string} The first matched field name */ _findMatchedFieldName(element, regexps) { const getElementStrings = this._getElementStrings(element); for (let regexp of regexps) { for (let string of getElementStrings) { if (this.RULES[regexp].test(string)) { return regexp; } } } return null; }, /** * Determine whether the regexp can match any of element strings. * * @param {HTMLElement} element * @param {RegExp} regexp * * @returns {boolean} */ _matchRegexp(element, regexp) { const elemStrings = this._getElementStrings(element); for (const str of elemStrings) { if (regexp.test(str)) { return true; } } return false; }, /** * Phone field grammars - first matched grammar will be parsed. Grammars are * separated by { REGEX_SEPARATOR, FIELD_NONE, 0 }. Suffix and extension are * parsed separately unless they are necessary parts of the match. * The following notation is used to describe the patterns: * <cc> - country code field. * <ac> - area code field. * <phone> - phone or prefix. * <suffix> - suffix. * <ext> - extension. * :N means field is limited to N characters, otherwise it is unlimited. * (pattern <field>)? means pattern is optional and matched separately. * * This grammar list from Chromium will be enabled partially once we need to * support more cases of Telephone fields. */ PHONE_FIELD_GRAMMARS: [ // Country code: <cc> Area Code: <ac> Phone: <phone> (- <suffix> // (Ext: <ext>)?)? // {REGEX_COUNTRY, FIELD_COUNTRY_CODE, 0}, // {REGEX_AREA, FIELD_AREA_CODE, 0}, // {REGEX_PHONE, FIELD_PHONE, 0}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // \( <ac> \) <phone>:3 <suffix>:4 (Ext: <ext>)? // {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 3}, // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3}, // {REGEX_PHONE, FIELD_SUFFIX, 4}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: <cc> <ac>:3 - <phone>:3 - <suffix>:4 (Ext: <ext>)? // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0}, // {REGEX_PHONE, FIELD_AREA_CODE, 3}, // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3}, // {REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 4}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: <cc>:3 <ac>:3 <phone>:3 <suffix>:4 (Ext: <ext>)? ["tel", "tel-country-code", 3], ["tel", "tel-area-code", 3], ["tel", "tel-local-prefix", 3], ["tel", "tel-local-suffix", 4], [null, null, 0], // Area Code: <ac> Phone: <phone> (- <suffix> (Ext: <ext>)?)? // {REGEX_AREA, FIELD_AREA_CODE, 0}, // {REGEX_PHONE, FIELD_PHONE, 0}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: <ac> <phone>:3 <suffix>:4 (Ext: <ext>)? // {REGEX_PHONE, FIELD_AREA_CODE, 0}, // {REGEX_PHONE, FIELD_PHONE, 3}, // {REGEX_PHONE, FIELD_SUFFIX, 4}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: <cc> \( <ac> \) <phone> (- <suffix> (Ext: <ext>)?)? // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0}, // {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 0}, // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: \( <ac> \) <phone> (- <suffix> (Ext: <ext>)?)? // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0}, // {REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 0}, // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: <cc> - <ac> - <phone> - <suffix> (Ext: <ext>)? // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0}, // {REGEX_PREFIX_SEPARATOR, FIELD_AREA_CODE, 0}, // {REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0}, // {REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 0}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Area code: <ac>:3 Prefix: <prefix>:3 Suffix: <suffix>:4 (Ext: <ext>)? // {REGEX_AREA, FIELD_AREA_CODE, 3}, // {REGEX_PREFIX, FIELD_PHONE, 3}, // {REGEX_SUFFIX, FIELD_SUFFIX, 4}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: <ac> Prefix: <phone> Suffix: <suffix> (Ext: <ext>)? // {REGEX_PHONE, FIELD_AREA_CODE, 0}, // {REGEX_PREFIX, FIELD_PHONE, 0}, // {REGEX_SUFFIX, FIELD_SUFFIX, 0}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: <ac> - <phone>:3 - <suffix>:4 (Ext: <ext>)? ["tel", "tel-area-code", 0], ["tel", "tel-local-prefix", 3], ["tel", "tel-local-suffix", 4], [null, null, 0], // Phone: <cc> - <ac> - <phone> (Ext: <ext>)? // {REGEX_PHONE, FIELD_COUNTRY_CODE, 0}, // {REGEX_PREFIX_SEPARATOR, FIELD_AREA_CODE, 0}, // {REGEX_SUFFIX_SEPARATOR, FIELD_PHONE, 0}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: <ac> - <phone> (Ext: <ext>)? // {REGEX_AREA, FIELD_AREA_CODE, 0}, // {REGEX_PHONE, FIELD_PHONE, 0}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: <cc>:3 - <phone>:10 (Ext: <ext>)? // {REGEX_PHONE, FIELD_COUNTRY_CODE, 3}, // {REGEX_PHONE, FIELD_PHONE, 10}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Ext: <ext> // {REGEX_EXTENSION, FIELD_EXTENSION, 0}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, // Phone: <phone> (Ext: <ext>)? // {REGEX_PHONE, FIELD_PHONE, 0}, // {REGEX_SEPARATOR, FIELD_NONE, 0}, ], }; XPCOMUtils.defineLazyGetter(FormAutofillHeuristics, "RULES", () => { let sandbox = {}; const HEURISTICS_REGEXP = "chrome://formautofill/content/heuristicsRegexp.js"; Services.scriptloader.loadSubScript(HEURISTICS_REGEXP, sandbox); return sandbox.HeuristicsRegExp.RULES; }); XPCOMUtils.defineLazyGetter(FormAutofillHeuristics, "_prefEnabled", () => { return Services.prefs.getBoolPref(PREF_HEURISTICS_ENABLED); }); Services.prefs.addObserver(PREF_HEURISTICS_ENABLED, () => { FormAutofillHeuristics._prefEnabled = Services.prefs.getBoolPref( PREF_HEURISTICS_ENABLED ); }); XPCOMUtils.defineLazyGetter(FormAutofillHeuristics, "_sectionEnabled", () => { return Services.prefs.getBoolPref(PREF_SECTION_ENABLED); }); Services.prefs.addObserver(PREF_SECTION_ENABLED, () => { FormAutofillHeuristics._sectionEnabled = Services.prefs.getBoolPref( PREF_SECTION_ENABLED ); }); PK �������!<Sl*��l*��$���chrome/res/FormAutofillNameUtils.jsm/* 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 = ["FormAutofillNameUtils"]; // FormAutofillNameUtils is initially translated from // https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_data_util.cc?rcl=b861deff77abecff11ae6a9f6946e9cc844b9817 var FormAutofillNameUtils = { NAME_PREFIXES: [ "1lt", "1st", "2lt", "2nd", "3rd", "admiral", "capt", "captain", "col", "cpt", "dr", "gen", "general", "lcdr", "lt", "ltc", "ltg", "ltjg", "maj", "major", "mg", "mr", "mrs", "ms", "pastor", "prof", "rep", "reverend", "rev", "sen", "st", ], NAME_SUFFIXES: [ "b.a", "ba", "d.d.s", "dds", "i", "ii", "iii", "iv", "ix", "jr", "m.a", "m.d", "ma", "md", "ms", "ph.d", "phd", "sr", "v", "vi", "vii", "viii", "x", ], FAMILY_NAME_PREFIXES: [ "d'", "de", "del", "der", "di", "la", "le", "mc", "san", "st", "ter", "van", "von", ], // The common and non-ambiguous CJK surnames (last names) that have more than // one character. COMMON_CJK_MULTI_CHAR_SURNAMES: [ // Korean, taken from the list of surnames: // https://ko.wikipedia.org/wiki/%ED%95%9C%EA%B5%AD%EC%9D%98_%EC%84%B1%EC%94%A8_%EB%AA%A9%EB%A1%9D "남궁", "사공", "서문", "선우", "제갈", "황보", "독고", "망절", // Chinese, taken from the top 10 Chinese 2-character surnames: // https://zh.wikipedia.org/wiki/%E8%A4%87%E5%A7%93#.E5.B8.B8.E8.A6.8B.E7.9A.84.E8.A4.87.E5.A7.93 // Simplified Chinese (mostly mainland China) "欧阳", "令狐", "皇甫", "上官", "司徒", "诸葛", "司马", "宇文", "呼延", "端木", // Traditional Chinese (mostly Taiwan) "張簡", "歐陽", "諸葛", "申屠", "尉遲", "司馬", "軒轅", "夏侯", ], // All Korean surnames that have more than one character, even the // rare/ambiguous ones. KOREAN_MULTI_CHAR_SURNAMES: [ "강전", "남궁", "독고", "동방", "망절", "사공", "서문", "선우", "소봉", "어금", "장곡", "제갈", "황목", "황보", ], // The whitespace definition based on // https://cs.chromium.org/chromium/src/base/strings/string_util_constants.cc?l=9&rcl=b861deff77abecff11ae6a9f6946e9cc844b9817 WHITESPACE: [ "\u0009", // CHARACTER TABULATION "\u000A", // LINE FEED (LF) "\u000B", // LINE TABULATION "\u000C", // FORM FEED (FF) "\u000D", // CARRIAGE RETURN (CR) "\u0020", // SPACE "\u0085", // NEXT LINE (NEL) "\u00A0", // NO-BREAK SPACE "\u1680", // OGHAM SPACE MARK "\u2000", // EN QUAD "\u2001", // EM QUAD "\u2002", // EN SPACE "\u2003", // EM SPACE "\u2004", // THREE-PER-EM SPACE "\u2005", // FOUR-PER-EM SPACE "\u2006", // SIX-PER-EM SPACE "\u2007", // FIGURE SPACE "\u2008", // PUNCTUATION SPACE "\u2009", // THIN SPACE "\u200A", // HAIR SPACE "\u2028", // LINE SEPARATOR "\u2029", // PARAGRAPH SEPARATOR "\u202F", // NARROW NO-BREAK SPACE "\u205F", // MEDIUM MATHEMATICAL SPACE "\u3000", // IDEOGRAPHIC SPACE ], // The middle dot is used as a separator for foreign names in Japanese. MIDDLE_DOT: [ "\u30FB", // KATAKANA MIDDLE DOT "\u00B7", // A (common?) typo for "KATAKANA MIDDLE DOT" ], // The Unicode range is based on Wiki: // https://en.wikipedia.org/wiki/CJK_Unified_Ideographs // https://en.wikipedia.org/wiki/Hangul // https://en.wikipedia.org/wiki/Japanese_writing_system CJK_RANGE: [ "\u1100-\u11FF", // Hangul Jamo "\u3040-\u309F", // Hiragana "\u30A0-\u30FF", // Katakana "\u3105-\u312C", // Bopomofo "\u3130-\u318F", // Hangul Compatibility Jamo "\u31F0-\u31FF", // Katakana Phonetic Extensions "\u3200-\u32FF", // Enclosed CJK Letters and Months "\u3400-\u4DBF", // CJK unified ideographs Extension A "\u4E00-\u9FFF", // CJK Unified Ideographs "\uA960-\uA97F", // Hangul Jamo Extended-A "\uAC00-\uD7AF", // Hangul Syllables "\uD7B0-\uD7FF", // Hangul Jamo Extended-B "\uFF00-\uFFEF", // Halfwidth and Fullwidth Forms ], HANGUL_RANGE: [ "\u1100-\u11FF", // Hangul Jamo "\u3130-\u318F", // Hangul Compatibility Jamo "\uA960-\uA97F", // Hangul Jamo Extended-A "\uAC00-\uD7AF", // Hangul Syllables "\uD7B0-\uD7FF", // Hangul Jamo Extended-B ], _dataLoaded: false, // Returns true if |set| contains |token|, modulo a final period. _containsString(set, token) { let target = token.replace(/\.$/, "").toLowerCase(); return set.includes(target); }, // Removes common name prefixes from |name_tokens|. _stripPrefixes(nameTokens) { for (let i in nameTokens) { if (!this._containsString(this.NAME_PREFIXES, nameTokens[i])) { return nameTokens.slice(i); } } return []; }, // Removes common name suffixes from |name_tokens|. _stripSuffixes(nameTokens) { for (let i = nameTokens.length - 1; i >= 0; i--) { if (!this._containsString(this.NAME_SUFFIXES, nameTokens[i])) { return nameTokens.slice(0, i + 1); } } return []; }, _isCJKName(name) { // The name is considered to be a CJK name if it is only CJK characters, // spaces, and "middle dot" separators, with at least one CJK character, and // no more than 2 words. // // Chinese and Japanese names are usually spelled out using the Han // characters (logographs), which constitute the "CJK Unified Ideographs" // block in Unicode, also referred to as Unihan. Korean names are usually // spelled out in the Korean alphabet (Hangul), although they do have a Han // equivalent as well. if (!name) { return false; } let previousWasCJK = false; let wordCount = 0; for (let c of name) { let isMiddleDot = this.MIDDLE_DOT.includes(c); let isCJK = !isMiddleDot && this.reCJK.test(c); if (!isCJK && !isMiddleDot && !this.WHITESPACE.includes(c)) { return false; } if (isCJK && !previousWasCJK) { wordCount++; } previousWasCJK = isCJK; } return wordCount > 0 && wordCount < 3; }, // Tries to split a Chinese, Japanese, or Korean name into its given name & // surname parts. If splitting did not work for whatever reason, returns null. _splitCJKName(nameTokens) { // The convention for CJK languages is to put the surname (last name) first, // and the given name (first name) second. In a continuous text, there is // normally no space between the two parts of the name. When entering their // name into a field, though, some people add a space to disambiguate. CJK // names (almost) never have a middle name. let reHangulName = new RegExp( "^[" + this.HANGUL_RANGE.join("") + this.WHITESPACE.join("") + "]+$", "u" ); let nameParts = { given: "", middle: "", family: "", }; if (nameTokens.length == 1) { // There is no space between the surname and given name. Try to infer // where to separate between the two. Most Chinese and Korean surnames // have only one character, but there are a few that have 2. If the name // does not start with a surname from a known list, default to one // character. let name = nameTokens[0]; let isKorean = reHangulName.test(name); let surnameLength = 0; // 4-character Korean names are more likely to be 2/2 than 1/3, so use // the full list of Korean 2-char surnames. (instead of only the common // ones) let multiCharSurnames = isKorean && name.length > 3 ? this.KOREAN_MULTI_CHAR_SURNAMES : this.COMMON_CJK_MULTI_CHAR_SURNAMES; // Default to 1 character if the surname is not in the list. surnameLength = multiCharSurnames.some(surname => name.startsWith(surname) ) ? 2 : 1; nameParts.family = name.substr(0, surnameLength); nameParts.given = name.substr(surnameLength); } else if (nameTokens.length == 2) { // The user entered a space between the two name parts. This makes our job // easier. Family name first, given name second. nameParts.family = nameTokens[0]; nameParts.given = nameTokens[1]; } else { return null; } return nameParts; }, init() { if (this._dataLoaded) { return; } this._dataLoaded = true; this.reCJK = new RegExp("[" + this.CJK_RANGE.join("") + "]", "u"); }, splitName(name) { let nameParts = { given: "", middle: "", family: "", }; if (!name) { return nameParts; } let nameTokens = name.trim().split(/[ ,\u3000\u30FB\u00B7]+/); nameTokens = this._stripPrefixes(nameTokens); if (this._isCJKName(name)) { let parts = this._splitCJKName(nameTokens); if (parts) { return parts; } } // Don't assume "Ma" is a suffix in John Ma. if (nameTokens.length > 2) { nameTokens = this._stripSuffixes(nameTokens); } if (!nameTokens.length) { // Bad things have happened; just assume the whole thing is a given name. nameParts.given = name; return nameParts; } // Only one token, assume given name. if (nameTokens.length == 1) { nameParts.given = nameTokens[0]; return nameParts; } // 2 or more tokens. Grab the family, which is the last word plus any // recognizable family prefixes. let familyTokens = [nameTokens.pop()]; while (nameTokens.length) { let lastToken = nameTokens[nameTokens.length - 1]; if (!this._containsString(this.FAMILY_NAME_PREFIXES, lastToken)) { break; } familyTokens.unshift(lastToken); nameTokens.pop(); } nameParts.family = familyTokens.join(" "); // Take the last remaining token as the middle name (if there are at least 2 // tokens). if (nameTokens.length >= 2) { nameParts.middle = nameTokens.pop(); } // Remainder is given name. nameParts.given = nameTokens.join(" "); return nameParts; }, joinNameParts({ given, middle, family }) { if (this._isCJKName(given) && this._isCJKName(family) && !middle) { return family + given; } return [given, middle, family] .filter(part => part && part.length) .join(" "); }, }; FormAutofillNameUtils.init(); PK �������!<Vi"bf��bf��!���chrome/res/FormAutofillParent.jsm/* 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/. */ /* * Implements a service used to access storage and communicate with content. * * A "fields" array is used to communicate with FormAutofillContent. Each item * represents a single input field in the content page as well as its * @autocomplete properties. The schema is as below. Please refer to * FormAutofillContent.js for more details. * * [ * { * section, * addressType, * contactType, * fieldName, * value, * index * }, * { * // ... * } * ] */ "use strict"; // We expose a singleton from this module. Some tests may import the // constructor via a backstage pass. var EXPORTED_SYMBOLS = ["FormAutofillParent", "FormAutofillStatus"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const { FormAutofill } = ChromeUtils.import( "resource://formautofill/FormAutofill.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", FormAutofillUtils: "resource://formautofill/FormAutofillUtils.jsm", OSKeyStore: "resource://gre/modules/OSKeyStore.jsm", }); this.log = null; FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]); const { ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF, } = FormAutofill; const { ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME, } = FormAutofillUtils; let gMessageObservers = new Set(); let FormAutofillStatus = { _initialized: false, /** * Cache of the Form Autofill status (considering preferences and storage). */ _active: null, /** * Initializes observers and registers the message handler. */ init() { if (this._initialized) { return; } this._initialized = true; Services.obs.addObserver(this, "privacy-pane-loaded"); // Observing the pref and storage changes Services.prefs.addObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this); Services.obs.addObserver(this, "formautofill-storage-changed"); // Only listen to credit card related preference if it is available if (FormAutofill.isAutofillCreditCardsAvailable) { Services.prefs.addObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this); } for (let win of Services.wm.getEnumerator("navigator:browser")) { this.injectElements(win.document); } Services.wm.addListener(this); Services.telemetry.setEventRecordingEnabled("creditcard", true); }, /** * Uninitializes FormAutofillStatus. This is for testing only. * * @private */ uninit() { gFormAutofillStorage._saveImmediately(); if (!this._initialized) { return; } this._initialized = false; this._active = null; Services.obs.removeObserver(this, "privacy-pane-loaded"); Services.prefs.removeObserver(ENABLED_AUTOFILL_ADDRESSES_PREF, this); Services.wm.removeListener(this); if (FormAutofill.isAutofillCreditCardsAvailable) { Services.prefs.removeObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this); } }, get formAutofillStorage() { return gFormAutofillStorage; }, /** * Broadcast the status to frames when the form autofill status changes. */ onStatusChanged() { log.debug("onStatusChanged: Status changed to", this._active); Services.ppmm.sharedData.set("FormAutofill:enabled", this._active); // Sync autofill enabled to make sure the value is up-to-date // no matter when the new content process is initialized. Services.ppmm.sharedData.flush(); }, /** * Query preference and storage status to determine the overall status of the * form autofill feature. * * @returns {boolean} whether form autofill is active (enabled and has data) */ computeStatus() { const savedFieldNames = Services.ppmm.sharedData.get( "FormAutofill:savedFieldNames" ); return ( (Services.prefs.getBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF) || Services.prefs.getBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF)) && savedFieldNames && savedFieldNames.size > 0 ); }, /** * Update the status and trigger onStatusChanged, if necessary. */ updateStatus() { log.debug("updateStatus"); let wasActive = this._active; this._active = this.computeStatus(); if (this._active !== wasActive) { this.onStatusChanged(); } }, updateSavedFieldNames() { log.debug("updateSavedFieldNames"); let savedFieldNames; // Don't access the credit cards store unless it is enabled. if (FormAutofill.isAutofillCreditCardsAvailable) { savedFieldNames = new Set([ ...gFormAutofillStorage.addresses.getSavedFieldNames(), ...gFormAutofillStorage.creditCards.getSavedFieldNames(), ]); } else { savedFieldNames = gFormAutofillStorage.addresses.getSavedFieldNames(); } Services.ppmm.sharedData.set( "FormAutofill:savedFieldNames", savedFieldNames ); Services.ppmm.sharedData.flush(); this.updateStatus(); }, injectElements(doc) { Services.scriptloader.loadSubScript( "chrome://formautofill/content/customElements.js", doc.ownerGlobal ); }, onOpenWindow(xulWindow) { const win = xulWindow.docShell.domWindow; win.addEventListener( "load", () => { if ( win.document.documentElement.getAttribute("windowtype") == "navigator:browser" ) { this.injectElements(win.document); } }, { once: true } ); }, onCloseWindow() {}, observe(subject, topic, data) { log.debug("observe:", topic, "with data:", data); switch (topic) { case "privacy-pane-loaded": { let formAutofillPreferences = new FormAutofillPreferences(); let document = subject.document; let prefFragment = formAutofillPreferences.init(document); let formAutofillGroupBox = document.getElementById( "formAutofillGroupBox" ); formAutofillGroupBox.appendChild(prefFragment); break; } case "nsPref:changed": { // Observe pref changes and update _active cache if status is changed. this.updateStatus(); break; } case "formautofill-storage-changed": { // Early exit if only metadata is changed if (data == "notifyUsed") { break; } this.updateSavedFieldNames(); break; } default: { throw new Error( `FormAutofillStatus: Unexpected topic observed: ${topic}` ); } } }, }; // Lazily load the storage JSM to avoid disk I/O until absolutely needed. // Once storage is loaded we need to update saved field names and inform content processes. XPCOMUtils.defineLazyGetter(this, "gFormAutofillStorage", () => { let { formAutofillStorage } = ChromeUtils.import( "resource://formautofill/FormAutofillStorage.jsm" ); log.debug("Loading formAutofillStorage"); formAutofillStorage.initialize().then(() => { // Update the saved field names to compute the status and update child processes. FormAutofillStatus.updateSavedFieldNames(); }); return formAutofillStorage; }); class FormAutofillParent extends JSWindowActorParent { constructor() { super(); FormAutofillStatus.init(); } static addMessageObserver(observer) { gMessageObservers.add(observer); } static removeMessageObserver(observer) { gMessageObservers.delete(observer); } /** * Handles the message coming from FormAutofillContent. * * @param {string} message.name The name of the message. * @param {object} message.data The data of the message. */ async receiveMessage({ name, data }) { switch (name) { case "FormAutofill:InitStorage": { await gFormAutofillStorage.initialize(); break; } case "FormAutofill:GetRecords": { return FormAutofillParent._getRecords(data); } case "FormAutofill:OnFormSubmit": { this.notifyMessageObservers("onFormSubmitted", data); await this._onFormSubmit(data); break; } case "FormAutofill:OpenPreferences": { const win = BrowserWindowTracker.getTopWindow(); win.openPreferences("privacy-form-autofill"); break; } case "FormAutofill:GetDecryptedString": { let { cipherText, reauth } = data; if (!FormAutofillUtils._reauthEnabledByUser) { log.debug("Reauth is disabled"); reauth = false; } let string; try { string = await OSKeyStore.decrypt(cipherText, reauth); } catch (e) { if (e.result != Cr.NS_ERROR_ABORT) { throw e; } log.warn("User canceled encryption login"); } return string; } case "FormAutofill:UpdateWarningMessage": this.notifyMessageObservers("updateWarningNote", data); break; case "FormAutofill:FieldsIdentified": this.notifyMessageObservers("fieldsIdentified", data); break; // The remaining Save and Remove messages are invoked only by tests. case "FormAutofill:SaveAddress": { if (data.guid) { await gFormAutofillStorage.addresses.update(data.guid, data.address); } else { await gFormAutofillStorage.addresses.add(data.address); } break; } case "FormAutofill:SaveCreditCard": { if (!(await FormAutofillUtils.ensureLoggedIn()).authenticated) { log.warn("User canceled encryption login"); return undefined; } await gFormAutofillStorage.creditCards.add(data.creditcard); break; } case "FormAutofill:RemoveAddresses": { data.guids.forEach(guid => gFormAutofillStorage.addresses.remove(guid)); break; } case "FormAutofill:RemoveCreditCards": { data.guids.forEach(guid => gFormAutofillStorage.creditCards.remove(guid) ); break; } } return undefined; } notifyMessageObservers(callbackName, data) { for (let observer of gMessageObservers) { try { if (callbackName in observer) { observer[callbackName]( data, this.manager.browsingContext.topChromeWindow ); } } catch (ex) { Cu.reportError(ex); } } } /** * Get the records from profile store and return results back to content * process. It will decrypt the credit card number and append * "cc-number-decrypted" to each record if OSKeyStore isn't set. * * This is static as a unit test calls this. * * @private * @param {string} data.collectionName * The name used to specify which collection to retrieve records. * @param {string} data.searchString * The typed string for filtering out the matched records. * @param {string} data.info * The input autocomplete property's information. */ static async _getRecords({ collectionName, searchString, info }) { let collection = gFormAutofillStorage[collectionName]; if (!collection) { return []; } let recordsInCollection = await collection.getAll(); if (!info || !info.fieldName || !recordsInCollection.length) { return recordsInCollection; } let isCC = collectionName == CREDITCARDS_COLLECTION_NAME; // We don't filter "cc-number" if (isCC && info.fieldName == "cc-number") { recordsInCollection = recordsInCollection.filter( record => !!record["cc-number"] ); return recordsInCollection; } let records = []; let lcSearchString = searchString.toLowerCase(); for (let record of recordsInCollection) { let fieldValue = record[info.fieldName]; if (!fieldValue) { continue; } if ( collectionName == ADDRESSES_COLLECTION_NAME && record.country && !FormAutofill.supportedCountries.includes(record.country) ) { // Address autofill isn't supported for the record's country so we don't // want to attempt to potentially incorrectly fill the address fields. continue; } if ( lcSearchString && !String(fieldValue) .toLowerCase() .startsWith(lcSearchString) ) { continue; } records.push(record); } return records; } async _onAddressSubmit(address, browser, timeStartedFillingMS) { let showDoorhanger = null; if (!FormAutofill.isAutofillAddressesCaptureEnabled) { return showDoorhanger; } if (address.guid) { // Avoid updating the fields that users don't modify. let originalAddress = await gFormAutofillStorage.addresses.get( address.guid ); for (let field in address.record) { if (address.untouchedFields.includes(field) && originalAddress[field]) { address.record[field] = originalAddress[field]; } } if ( !(await gFormAutofillStorage.addresses.mergeIfPossible( address.guid, address.record, true )) ) { this._recordFormFillingTime( "address", "autofill-update", timeStartedFillingMS ); showDoorhanger = async () => { const description = FormAutofillUtils.getAddressLabel(address.record); const state = await FormAutofillDoorhanger.show( browser, "updateAddress", description ); let changedGUIDs = await gFormAutofillStorage.addresses.mergeToStorage( address.record, true ); switch (state) { case "create": if (!changedGUIDs.length) { changedGUIDs.push( await gFormAutofillStorage.addresses.add(address.record) ); } break; case "update": if (!changedGUIDs.length) { await gFormAutofillStorage.addresses.update( address.guid, address.record, true ); changedGUIDs.push(address.guid); } else { gFormAutofillStorage.addresses.remove(address.guid); } break; } changedGUIDs.forEach(guid => gFormAutofillStorage.addresses.notifyUsed(guid) ); }; // Address should be updated Services.telemetry.scalarAdd( "formautofill.addresses.fill_type_autofill_update", 1 ); } else { this._recordFormFillingTime( "address", "autofill", timeStartedFillingMS ); gFormAutofillStorage.addresses.notifyUsed(address.guid); // Address is merged successfully Services.telemetry.scalarAdd( "formautofill.addresses.fill_type_autofill", 1 ); } } else { let changedGUIDs = await gFormAutofillStorage.addresses.mergeToStorage( address.record ); if (!changedGUIDs.length) { changedGUIDs.push( await gFormAutofillStorage.addresses.add(address.record) ); } changedGUIDs.forEach(guid => gFormAutofillStorage.addresses.notifyUsed(guid) ); this._recordFormFillingTime("address", "manual", timeStartedFillingMS); // Show first time use doorhanger if (FormAutofill.isAutofillAddressesFirstTimeUse) { Services.prefs.setBoolPref( FormAutofill.ADDRESSES_FIRST_TIME_USE_PREF, false ); showDoorhanger = async () => { const description = FormAutofillUtils.getAddressLabel(address.record); const state = await FormAutofillDoorhanger.show( browser, "firstTimeUse", description ); if (state !== "open-pref") { return; } browser.ownerGlobal.openPreferences("privacy-address-autofill"); }; } else { // We want to exclude the first time form filling. Services.telemetry.scalarAdd( "formautofill.addresses.fill_type_manual", 1 ); } } return showDoorhanger; } async _onCreditCardSubmit(creditCard, browser, timeStartedFillingMS) { if (FormAutofill.isAutofillCreditCardsHideUI) { return false; } // Updates the used status for shield/heartbeat to recognize users who have // used Credit Card Autofill. let setUsedStatus = status => { if (FormAutofill.AutofillCreditCardsUsedStatus < status) { Services.prefs.setIntPref( FormAutofill.CREDITCARDS_USED_STATUS_PREF, status ); } }; // Remove invalid cc-type values if ( creditCard.record["cc-type"] && !CreditCard.isValidNetwork(creditCard.record["cc-type"]) ) { // Let's reset the credit card to empty, and then network auto-detect will // pick it up. creditCard.record["cc-type"] = ""; } // If `guid` is present, the form has been autofilled. if (creditCard.guid) { // Indicate that the user has used Credit Card Autofill to fill in a form. setUsedStatus(3); let originalCCData = await gFormAutofillStorage.creditCards.get( creditCard.guid ); let recordUnchanged = true; for (let field in creditCard.record) { if (creditCard.record[field] === "" && !originalCCData[field]) { continue; } // Avoid updating the fields that users don't modify, but skip number field // because we don't want to trigger decryption here. let untouched = creditCard.untouchedFields.includes(field); if (untouched && field !== "cc-number") { creditCard.record[field] = originalCCData[field]; } // recordUnchanged will be false if one of the field is changed. recordUnchanged &= untouched; } if (recordUnchanged) { gFormAutofillStorage.creditCards.notifyUsed(creditCard.guid); // Add probe to record credit card autofill(without modification). Services.telemetry.scalarAdd( "formautofill.creditCards.fill_type_autofill", 1 ); this._recordFormFillingTime( "creditCard", "autofill", timeStartedFillingMS ); return false; } // Add the probe to record credit card autofill with modification. Services.telemetry.scalarAdd( "formautofill.creditCards.fill_type_autofill_modified", 1 ); this._recordFormFillingTime( "creditCard", "autofill-update", timeStartedFillingMS ); } else { // Add the probe to record credit card manual filling. Services.telemetry.scalarAdd( "formautofill.creditCards.fill_type_manual", 1 ); this._recordFormFillingTime("creditCard", "manual", timeStartedFillingMS); let existingGuid = await gFormAutofillStorage.creditCards.getDuplicateGuid( creditCard.record ); if (existingGuid) { creditCard.guid = existingGuid; let originalCCData = await gFormAutofillStorage.creditCards.get( creditCard.guid ); gFormAutofillStorage.creditCards._normalizeRecord(creditCard.record); // If the credit card record is a duplicate, check if the fields match the // record. let recordUnchanged = true; for (let field in creditCard.record) { if (field == "cc-number") { continue; } if (creditCard.record[field] != originalCCData[field]) { recordUnchanged = false; break; } } if (recordUnchanged) { // Indicate that the user neither sees the doorhanger nor uses Autofill // but somehow has a duplicate record in the storage. Will be reset to 2 // if the doorhanger actually shows below. setUsedStatus(1); gFormAutofillStorage.creditCards.notifyUsed(creditCard.guid); return false; } } } // Indicate that the user has seen the doorhanger. setUsedStatus(2); return async () => { // Suppress the pending doorhanger from showing up if user disabled credit card in previous doorhanger. if (!FormAutofill.isAutofillCreditCardsEnabled) { return; } let number = creditCard.record["cc-number"] || creditCard.record["cc-number-decrypted"]; let name = creditCard.record["cc-name"]; const description = await CreditCard.getLabel({ name, number }); const telemetryObject = creditCard.guid ? "update_doorhanger" : "capture_doorhanger"; Services.telemetry.recordEvent( "creditcard", "show", telemetryObject, creditCard.flowId ); const state = await FormAutofillDoorhanger.show( browser, creditCard.guid ? "updateCreditCard" : "addCreditCard", description ); if (state == "cancel") { Services.telemetry.recordEvent( "creditcard", "cancel", telemetryObject, creditCard.flowId ); return; } if (state == "disable") { Services.prefs.setBoolPref( "extensions.formautofill.creditCards.enabled", false ); Services.telemetry.recordEvent( "creditcard", "disable", telemetryObject, creditCard.flowId ); return; } if (!(await FormAutofillUtils.ensureLoggedIn()).authenticated) { log.warn("User canceled encryption login"); return; } let changedGUIDs = []; if (creditCard.guid) { if (state == "update") { Services.telemetry.recordEvent( "creditcard", "update", telemetryObject, creditCard.flowId ); await gFormAutofillStorage.creditCards.update( creditCard.guid, creditCard.record, true ); changedGUIDs.push(creditCard.guid); } else if ("create") { Services.telemetry.recordEvent( "creditcard", "save", telemetryObject, creditCard.flowId ); changedGUIDs.push( await gFormAutofillStorage.creditCards.add(creditCard.record) ); } } else { changedGUIDs.push( ...(await gFormAutofillStorage.creditCards.mergeToStorage( creditCard.record )) ); if (!changedGUIDs.length) { Services.telemetry.recordEvent( "creditcard", "save", telemetryObject, creditCard.flowId ); changedGUIDs.push( await gFormAutofillStorage.creditCards.add(creditCard.record) ); } } changedGUIDs.forEach(guid => gFormAutofillStorage.creditCards.notifyUsed(guid) ); }; } async _onFormSubmit(data) { let { profile: { address, creditCard }, timeStartedFillingMS, } = data; // Don't record filling time if any type of records has more than one section being // populated. We've been recording the filling time, so the other cases that aren't // recorded on the same basis should be out of the data samples. E.g. Filling time of // populating one profile is different from populating two sections, therefore, we // shouldn't record the later to regress the representation of existing statistics. if (address.length > 1 || creditCard.length > 1) { timeStartedFillingMS = null; } let browser = this.manager.browsingContext.top.embedderElement; // Transmit the telemetry immediately in the meantime form submitted, and handle these pending // doorhangers at a later. await Promise.all( [ await Promise.all( address.map(addrRecord => this._onAddressSubmit(addrRecord, browser, timeStartedFillingMS) ) ), await Promise.all( creditCard.map(ccRecord => this._onCreditCardSubmit(ccRecord, browser, timeStartedFillingMS) ) ), ] .map(pendingDoorhangers => { return pendingDoorhangers.filter( pendingDoorhanger => !!pendingDoorhanger && typeof pendingDoorhanger == "function" ); }) .map(pendingDoorhangers => (async () => { for (const showDoorhanger of pendingDoorhangers) { await showDoorhanger(); } })() ) ); } /** * Set the probes for the filling time with specific filling type and form type. * * @private * @param {string} formType * 3 type of form (address/creditcard/address-creditcard). * @param {string} fillingType * 3 filling type (manual/autofill/autofill-update). * @param {int|null} startedFillingMS * Time that form started to filling in ms. Early return if start time is null. */ _recordFormFillingTime(formType, fillingType, startedFillingMS) { if (!startedFillingMS) { return; } let histogram = Services.telemetry.getKeyedHistogramById( "FORM_FILLING_REQUIRED_TIME_MS" ); histogram.add(`${formType}-${fillingType}`, Date.now() - startedFillingMS); } } PK �������!<qO{8��8��&���chrome/res/FormAutofillPreferences.jsm/* 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/. */ /** * Injects the form autofill section into about:preferences. */ "use strict"; var EXPORTED_SYMBOLS = ["FormAutofillPreferences"]; // Add addresses enabled flag in telemetry environment for recording the number of // users who disable/enable the address autofill feature. const BUNDLE_URI = "chrome://formautofill/locale/formautofill.properties"; const MANAGE_ADDRESSES_URL = "chrome://formautofill/content/manageAddresses.xhtml"; const MANAGE_CREDITCARDS_URL = "chrome://formautofill/content/manageCreditCards.xhtml"; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { FormAutofill } = ChromeUtils.import( "resource://formautofill/FormAutofill.jsm" ); const { FormAutofillUtils } = ChromeUtils.import( "resource://formautofill/FormAutofillUtils.jsm" ); ChromeUtils.defineModuleGetter( this, "AppConstants", "resource://gre/modules/AppConstants.jsm" ); ChromeUtils.defineModuleGetter( this, "OSKeyStore", "resource://gre/modules/OSKeyStore.jsm" ); const { ENABLED_AUTOFILL_ADDRESSES_PREF, ENABLED_AUTOFILL_CREDITCARDS_PREF, ENABLED_AUTOFILL_CREDITCARDS_REAUTH_PREF, } = FormAutofill; const { MANAGE_ADDRESSES_KEYWORDS, EDIT_ADDRESS_KEYWORDS, MANAGE_CREDITCARDS_KEYWORDS, EDIT_CREDITCARD_KEYWORDS, } = FormAutofillUtils; // Add credit card enabled flag in telemetry environment for recording the number of // users who disable/enable the credit card autofill feature. this.log = null; FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]); const HTML_NS = "http://www.w3.org/1999/xhtml"; function FormAutofillPreferences() { this.bundle = Services.strings.createBundle(BUNDLE_URI); } FormAutofillPreferences.prototype = { /** * Create the Form Autofill preference group. * * @param {HTMLDocument} document * @returns {XULElement} */ init(document) { this.createPreferenceGroup(document); this.attachEventListeners(); return this.refs.formAutofillFragment; }, /** * Remove event listeners and the preference group. */ uninit() { this.detachEventListeners(); this.refs.formAutofillGroup.remove(); }, /** * Create Form Autofill preference group * * @param {HTMLDocument} document */ createPreferenceGroup(document) { let addressLearnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "autofill-card-address"; let creditCardLearnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "credit-card-autofill"; let formAutofillFragment = document.createDocumentFragment(); let formAutofillGroupBoxLabel = document.createXULElement("label"); let formAutofillGroupBoxLabelHeading = document.createElementNS( HTML_NS, "h2" ); let formAutofillGroup = document.createXULElement("vbox"); let addressAutofill = document.createXULElement("hbox"); let addressAutofillCheckboxGroup = document.createXULElement("hbox"); let addressAutofillCheckbox = document.createXULElement("checkbox"); let addressAutofillLearnMore = document.createXULElement("label", { is: "text-link", }); let savedAddressesBtn = document.createXULElement("button", { is: "highlightable-button", }); // Wrappers are used to properly compute the search tooltip positions let savedAddressesBtnWrapper = document.createXULElement("hbox"); let savedCreditCardsBtnWrapper = document.createXULElement("hbox"); savedAddressesBtn.className = "accessory-button"; addressAutofillCheckbox.className = "tail-with-learn-more"; addressAutofillLearnMore.className = "learnMore"; formAutofillGroup.id = "formAutofillGroup"; addressAutofill.id = "addressAutofill"; addressAutofillLearnMore.id = "addressAutofillLearnMore"; formAutofillGroupBoxLabelHeading.textContent = this.bundle.GetStringFromName( "autofillHeader" ); addressAutofill.setAttribute("data-subcategory", "address-autofill"); addressAutofillCheckbox.setAttribute( "label", this.bundle.GetStringFromName("autofillAddressesCheckbox") ); addressAutofillLearnMore.textContent = this.bundle.GetStringFromName( "learnMoreLabel" ); savedAddressesBtn.setAttribute( "label", this.bundle.GetStringFromName("savedAddressesBtnLabel") ); // Align the start to keep the savedAddressesBtn as original size // when addressAutofillCheckboxGroup's height is changed by a longer l10n string savedAddressesBtnWrapper.setAttribute("align", "start"); addressAutofillLearnMore.setAttribute("href", addressLearnMoreURL); // Add preferences search support savedAddressesBtn.setAttribute( "searchkeywords", MANAGE_ADDRESSES_KEYWORDS.concat(EDIT_ADDRESS_KEYWORDS) .map(key => this.bundle.GetStringFromName(key)) .join("\n") ); // Manually set the checked state if (FormAutofill.isAutofillAddressesEnabled) { addressAutofillCheckbox.setAttribute("checked", true); } addressAutofillCheckboxGroup.setAttribute("align", "center"); addressAutofillCheckboxGroup.flex = 1; formAutofillGroupBoxLabel.appendChild(formAutofillGroupBoxLabelHeading); formAutofillFragment.appendChild(formAutofillGroupBoxLabel); formAutofillFragment.appendChild(formAutofillGroup); formAutofillGroup.appendChild(addressAutofill); addressAutofill.appendChild(addressAutofillCheckboxGroup); addressAutofillCheckboxGroup.appendChild(addressAutofillCheckbox); addressAutofillCheckboxGroup.appendChild(addressAutofillLearnMore); addressAutofill.appendChild(savedAddressesBtnWrapper); savedAddressesBtnWrapper.appendChild(savedAddressesBtn); this.refs = { formAutofillFragment, formAutofillGroup, addressAutofillCheckbox, savedAddressesBtn, }; if ( FormAutofill.isAutofillCreditCardsAvailable && !FormAutofill.isAutofillCreditCardsHideUI ) { let creditCardAutofill = document.createXULElement("hbox"); let creditCardAutofillCheckboxGroup = document.createXULElement("hbox"); let creditCardAutofillCheckbox = document.createXULElement("checkbox"); let creditCardAutofillLearnMore = document.createXULElement("label", { is: "text-link", }); let savedCreditCardsBtn = document.createXULElement("button", { is: "highlightable-button", }); savedCreditCardsBtn.className = "accessory-button"; creditCardAutofillCheckbox.className = "tail-with-learn-more"; creditCardAutofillLearnMore.className = "learnMore"; creditCardAutofill.id = "creditCardAutofill"; creditCardAutofillLearnMore.id = "creditCardAutofillLearnMore"; creditCardAutofill.setAttribute( "data-subcategory", "credit-card-autofill" ); creditCardAutofillCheckbox.setAttribute( "label", this.bundle.GetStringFromName("autofillCreditCardsCheckbox") ); creditCardAutofillLearnMore.textContent = this.bundle.GetStringFromName( "learnMoreLabel" ); savedCreditCardsBtn.setAttribute( "label", this.bundle.GetStringFromName("savedCreditCardsBtnLabel") ); // Align the start to keep the savedCreditCardsBtn as original size // when creditCardAutofillCheckboxGroup's height is changed by a longer l10n string savedCreditCardsBtnWrapper.setAttribute("align", "start"); creditCardAutofillLearnMore.setAttribute("href", creditCardLearnMoreURL); // Add preferences search support savedCreditCardsBtn.setAttribute( "searchkeywords", MANAGE_CREDITCARDS_KEYWORDS.concat(EDIT_CREDITCARD_KEYWORDS) .map(key => this.bundle.GetStringFromName(key)) .join("\n") ); // Manually set the checked state if (FormAutofill.isAutofillCreditCardsEnabled) { creditCardAutofillCheckbox.setAttribute("checked", true); } creditCardAutofillCheckboxGroup.setAttribute("align", "center"); creditCardAutofillCheckboxGroup.flex = 1; formAutofillGroup.appendChild(creditCardAutofill); creditCardAutofill.appendChild(creditCardAutofillCheckboxGroup); creditCardAutofillCheckboxGroup.appendChild(creditCardAutofillCheckbox); creditCardAutofillCheckboxGroup.appendChild(creditCardAutofillLearnMore); creditCardAutofill.appendChild(savedCreditCardsBtnWrapper); savedCreditCardsBtnWrapper.appendChild(savedCreditCardsBtn); this.refs.creditCardAutofillCheckbox = creditCardAutofillCheckbox; this.refs.savedCreditCardsBtn = savedCreditCardsBtn; if (OSKeyStore.canReauth()) { let reauthLearnMoreURL = `${creditCardLearnMoreURL}#w_require-authentication-for-autofill`; let reauth = document.createXULElement("hbox"); let reauthCheckboxGroup = document.createXULElement("hbox"); let reauthCheckbox = document.createXULElement("checkbox"); let reauthLearnMore = document.createXULElement("label", { is: "text-link", }); reauthCheckboxGroup.classList.add("indent"); reauthLearnMore.classList.add("learnMore"); reauthCheckbox.classList.add("tail-with-learn-more"); reauthCheckbox.disabled = !FormAutofill.isAutofillCreditCardsEnabled; reauth.id = "creditCardReauthenticate"; reauthLearnMore.id = "creditCardReauthenticateLearnMore"; reauth.setAttribute("data-subcategory", "reauth-credit-card-autofill"); let autofillReauthCheckboxLabel = "autofillReauthCheckbox"; // We reuse the if/else order from wizard markup to increase // odds of consistent behavior. if (AppConstants.platform == "macosx") { autofillReauthCheckboxLabel += "Mac"; } else if (AppConstants.platform == "linux") { autofillReauthCheckboxLabel += "Lin"; } else { autofillReauthCheckboxLabel += "Win"; } reauthCheckbox.setAttribute( "label", this.bundle.GetStringFromName(autofillReauthCheckboxLabel) ); reauthLearnMore.textContent = this.bundle.GetStringFromName( "learnMoreLabel" ); reauthLearnMore.setAttribute("href", reauthLearnMoreURL); // Manually set the checked state if (FormAutofillUtils._reauthEnabledByUser) { reauthCheckbox.setAttribute("checked", true); } reauthCheckboxGroup.setAttribute("align", "center"); reauthCheckboxGroup.flex = 1; formAutofillGroup.appendChild(reauth); reauth.appendChild(reauthCheckboxGroup); reauthCheckboxGroup.appendChild(reauthCheckbox); reauthCheckboxGroup.appendChild(reauthLearnMore); this.refs.reauthCheckbox = reauthCheckbox; } } }, /** * Handle events * * @param {DOMEvent} event */ async handleEvent(event) { switch (event.type) { case "command": { let target = event.target; if (target == this.refs.addressAutofillCheckbox) { // Set preference directly instead of relying on <Preference> Services.prefs.setBoolPref( ENABLED_AUTOFILL_ADDRESSES_PREF, target.checked ); } else if (target == this.refs.creditCardAutofillCheckbox) { Services.prefs.setBoolPref( ENABLED_AUTOFILL_CREDITCARDS_PREF, target.checked ); if (this.refs.reauthCheckbox) { this.refs.reauthCheckbox.disabled = !target.checked; } } else if (target == this.refs.reauthCheckbox) { if (!OSKeyStore.canReauth()) { break; } let messageTextId = "autofillReauthOSDialog"; // We reuse the if/else order from wizard markup to increase // odds of consistent behavior. if (AppConstants.platform == "macosx") { messageTextId += "Mac"; } else if (AppConstants.platform == "linux") { messageTextId += "Lin"; } else { messageTextId += "Win"; } let messageText = this.bundle.GetStringFromName(messageTextId); const brandBundle = Services.strings.createBundle( "chrome://branding/locale/brand.properties" ); let win = target.ownerGlobal.docShell.chromeEventHandler.ownerGlobal; let loggedIn = await OSKeyStore.ensureLoggedIn( messageText, brandBundle.GetStringFromName("brandFullName"), win, false ); if (!loggedIn.authenticated) { target.checked = !target.checked; break; } Services.prefs.setBoolPref( ENABLED_AUTOFILL_CREDITCARDS_REAUTH_PREF, target.checked ); } else if (target == this.refs.savedAddressesBtn) { target.ownerGlobal.gSubDialog.open(MANAGE_ADDRESSES_URL); } else if (target == this.refs.savedCreditCardsBtn) { target.ownerGlobal.gSubDialog.open(MANAGE_CREDITCARDS_URL); } break; } case "click": { let target = event.target; if (target == this.refs.addressAutofillCheckboxLabel) { let pref = FormAutofill.isAutofillAddressesEnabled; Services.prefs.setBoolPref(ENABLED_AUTOFILL_ADDRESSES_PREF, !pref); this.refs.addressAutofillCheckbox.checked = !pref; } else if (target == this.refs.creditCardAutofillCheckboxLabel) { let pref = FormAutofill.isAutofillCreditCardsEnabled; Services.prefs.setBoolPref(ENABLED_AUTOFILL_CREDITCARDS_PREF, !pref); this.refs.creditCardAutofillCheckbox.checked = !pref; this.refs.reauthCheckbox.disabled = pref; } break; } } }, /** * Attach event listener */ attachEventListeners() { this.refs.formAutofillGroup.addEventListener("command", this); this.refs.formAutofillGroup.addEventListener("click", this); }, /** * Remove event listener */ detachEventListeners() { this.refs.formAutofillGroup.removeEventListener("command", this); this.refs.formAutofillGroup.removeEventListener("click", this); }, }; PK �������!< : 0)��)�����chrome/res/FormAutofillSync.jsm/* 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 = ["AddressesEngine", "CreditCardsEngine"]; const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { Changeset, Store, SyncEngine, Tracker } = ChromeUtils.import( "resource://services-sync/engines.js" ); const { CryptoWrapper } = ChromeUtils.import( "resource://services-sync/record.js" ); const { Utils } = ChromeUtils.import("resource://services-sync/util.js"); const { SCORE_INCREMENT_XLARGE } = ChromeUtils.import( "resource://services-sync/constants.js" ); ChromeUtils.defineModuleGetter(this, "Log", "resource://gre/modules/Log.jsm"); ChromeUtils.defineModuleGetter( this, "formAutofillStorage", "resource://formautofill/FormAutofillStorage.jsm" ); // A helper to sanitize address and creditcard records suitable for logging. function sanitizeStorageObject(ob) { if (!ob) { return null; } const allowList = ["timeCreated", "timeLastUsed", "timeLastModified"]; let result = {}; for (let key of Object.keys(ob)) { let origVal = ob[key]; if (allowList.includes(key)) { result[key] = origVal; } else if (typeof origVal == "string") { result[key] = "X".repeat(origVal.length); } else { result[key] = typeof origVal; // *shrug* } } return result; } function AutofillRecord(collection, id) { CryptoWrapper.call(this, collection, id); } AutofillRecord.prototype = { __proto__: CryptoWrapper.prototype, toEntry() { return Object.assign( { guid: this.id, }, this.entry ); }, fromEntry(entry) { this.id = entry.guid; this.entry = entry; // The GUID is already stored in record.id, so we nuke it from the entry // itself to save a tiny bit of space. The formAutofillStorage clones profiles, // so nuking in-place is OK. delete this.entry.guid; }, cleartextToString() { // And a helper so logging a *Sync* record auto sanitizes. let record = this.cleartext; return JSON.stringify({ entry: sanitizeStorageObject(record.entry) }); }, }; // Profile data is stored in the "entry" object of the record. Utils.deferGetSet(AutofillRecord, "cleartext", ["entry"]); function FormAutofillStore(name, engine) { Store.call(this, name, engine); } FormAutofillStore.prototype = { __proto__: Store.prototype, _subStorageName: null, // overridden below. _storage: null, get storage() { if (!this._storage) { this._storage = formAutofillStorage[this._subStorageName]; } return this._storage; }, async getAllIDs() { let result = {}; for (let { guid } of await this.storage.getAll({ includeDeleted: true })) { result[guid] = true; } return result; }, async changeItemID(oldID, newID) { this.storage.changeGUID(oldID, newID); }, // Note: this function intentionally returns false in cases where we only have // a (local) tombstone - and formAutofillStorage.get() filters them for us. async itemExists(id) { return Boolean(await this.storage.get(id)); }, async applyIncoming(remoteRecord) { if (remoteRecord.deleted) { this._log.trace("Deleting record", remoteRecord); this.storage.remove(remoteRecord.id, { sourceSync: true }); return; } if (await this.itemExists(remoteRecord.id)) { // We will never get a tombstone here, so we are updating a real record. await this._doUpdateRecord(remoteRecord); return; } // No matching local record. Try to dedupe a NEW local record. let localDupeID = await this.storage.findDuplicateGUID( remoteRecord.toEntry() ); if (localDupeID) { this._log.trace( `Deduping local record ${localDupeID} to remote`, remoteRecord ); // Change the local GUID to match the incoming record, then apply the // incoming record. await this.changeItemID(localDupeID, remoteRecord.id); await this._doUpdateRecord(remoteRecord); return; } // We didn't find a dupe, either, so must be a new record (or possibly // a non-deleted version of an item we have a tombstone for, which add() // handles for us.) this._log.trace("Add record", remoteRecord); let entry = remoteRecord.toEntry(); await this.storage.add(entry, { sourceSync: true }); }, async createRecord(id, collection) { this._log.trace("Create record", id); let record = new AutofillRecord(collection, id); let entry = await this.storage.get(id, { rawData: true, }); if (entry) { record.fromEntry(entry); } else { // We should consider getting a more authortative indication it's actually deleted. this._log.debug( `Failed to get autofill record with id "${id}", assuming deleted` ); record.deleted = true; } return record; }, async _doUpdateRecord(record) { this._log.trace("Updating record", record); let entry = record.toEntry(); let { forkedGUID } = await this.storage.reconcile(entry); if (this._log.level <= Log.Level.Debug) { let forkedRecord = forkedGUID ? await this.storage.get(forkedGUID) : null; let reconciledRecord = await this.storage.get(record.id); this._log.debug("Updated local record", { forked: sanitizeStorageObject(forkedRecord), updated: sanitizeStorageObject(reconciledRecord), }); } }, // NOTE: Because we re-implement the incoming/reconcilliation logic we leave // the |create|, |remove| and |update| methods undefined - the base // implementation throws, which is what we want to happen so we can identify // any places they are "accidentally" called. }; function FormAutofillTracker(name, engine) { Tracker.call(this, name, engine); } FormAutofillTracker.prototype = { __proto__: Tracker.prototype, async observe(subject, topic, data) { if (topic != "formautofill-storage-changed") { return; } if ( subject && subject.wrappedJSObject && subject.wrappedJSObject.sourceSync ) { return; } switch (data) { case "add": case "update": case "remove": this.score += SCORE_INCREMENT_XLARGE; break; default: this._log.debug("unrecognized autofill notification", data); break; } }, onStart() { Services.obs.addObserver(this, "formautofill-storage-changed"); }, onStop() { Services.obs.removeObserver(this, "formautofill-storage-changed"); }, }; // This uses the same conventions as BookmarkChangeset in // services/sync/modules/engines/bookmarks.js. Specifically, // - "synced" means the item has already been synced (or we have another reason // to ignore it), and should be ignored in most methods. class AutofillChangeset extends Changeset { constructor() { super(); } getModifiedTimestamp(id) { throw new Error("Don't use timestamps to resolve autofill merge conflicts"); } has(id) { let change = this.changes[id]; if (change) { return !change.synced; } return false; } delete(id) { let change = this.changes[id]; if (change) { // Mark the change as synced without removing it from the set. We do this // so that we can update FormAutofillStorage in `trackRemainingChanges`. change.synced = true; } } } function FormAutofillEngine(service, name) { SyncEngine.call(this, name, service); } FormAutofillEngine.prototype = { __proto__: SyncEngine.prototype, // the priority for this engine is == addons, so will happen after bookmarks // prefs and tabs, but before forms, history, etc. syncPriority: 5, // We don't use SyncEngine.initialize() for this, as we initialize even if // the engine is disabled, and we don't want to be the loader of // FormAutofillStorage in this case. async _syncStartup() { await formAutofillStorage.initialize(); await SyncEngine.prototype._syncStartup.call(this); }, // We handle reconciliation in the store, not the engine. async _reconcile() { return true; }, emptyChangeset() { return new AutofillChangeset(); }, async _uploadOutgoing() { this._modified.replace(this._store.storage.pullSyncChanges()); await SyncEngine.prototype._uploadOutgoing.call(this); }, // Typically, engines populate the changeset before downloading records. // However, we handle conflict resolution in the store, so we can wait // to pull changes until we're ready to upload. async pullAllChanges() { return {}; }, async pullNewChanges() { return {}; }, async trackRemainingChanges() { this._store.storage.pushSyncChanges(this._modified.changes); }, _deleteId(id) { this._noteDeletedId(id); }, async _resetClient() { await formAutofillStorage.initialize(); this._store.storage.resetSync(); }, async _wipeClient() { await formAutofillStorage.initialize(); this._store.storage.removeAll({ sourceSync: true }); }, }; // The concrete engines function AddressesRecord(collection, id) { AutofillRecord.call(this, collection, id); } AddressesRecord.prototype = { __proto__: AutofillRecord.prototype, _logName: "Sync.Record.Addresses", }; function AddressesStore(name, engine) { FormAutofillStore.call(this, name, engine); } AddressesStore.prototype = { __proto__: FormAutofillStore.prototype, _subStorageName: "addresses", }; function AddressesEngine(service) { FormAutofillEngine.call(this, service, "Addresses"); } AddressesEngine.prototype = { __proto__: FormAutofillEngine.prototype, _trackerObj: FormAutofillTracker, _storeObj: AddressesStore, _recordObj: AddressesRecord, get prefName() { return "addresses"; }, }; function CreditCardsRecord(collection, id) { AutofillRecord.call(this, collection, id); } CreditCardsRecord.prototype = { __proto__: AutofillRecord.prototype, _logName: "Sync.Record.CreditCards", }; function CreditCardsStore(name, engine) { FormAutofillStore.call(this, name, engine); } CreditCardsStore.prototype = { __proto__: FormAutofillStore.prototype, _subStorageName: "creditCards", }; function CreditCardsEngine(service) { FormAutofillEngine.call(this, service, "CreditCards"); } CreditCardsEngine.prototype = { __proto__: FormAutofillEngine.prototype, _trackerObj: FormAutofillTracker, _storeObj: CreditCardsStore, _recordObj: CreditCardsRecord, get prefName() { return "creditcards"; }, }; PK �������!<v&���� ���chrome/res/FormAutofillUtils.jsm/* 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 = ["FormAutofillUtils", "AddressDataLoader"]; const ADDRESS_METADATA_PATH = "resource://formautofill/addressmetadata/"; const ADDRESS_REFERENCES = "addressReferences.js"; const ADDRESS_REFERENCES_EXT = "addressReferencesExt.js"; const ADDRESSES_COLLECTION_NAME = "addresses"; const CREDITCARDS_COLLECTION_NAME = "creditCards"; const MANAGE_ADDRESSES_KEYWORDS = [ "manageAddressesTitle", "addNewAddressTitle", ]; const EDIT_ADDRESS_KEYWORDS = [ "givenName", "additionalName", "familyName", "organization2", "streetAddress", "state", "province", "city", "country", "zip", "postalCode", "email", "tel", ]; const MANAGE_CREDITCARDS_KEYWORDS = [ "manageCreditCardsTitle", "addNewCreditCardTitle", ]; const EDIT_CREDITCARD_KEYWORDS = [ "cardNumber", "nameOnCard", "cardExpiresMonth", "cardExpiresYear", "cardNetwork", ]; const FIELD_STATES = { NORMAL: "NORMAL", AUTO_FILLED: "AUTO_FILLED", PREVIEW: "PREVIEW", }; const SECTION_TYPES = { ADDRESS: "address", CREDIT_CARD: "creditCard", }; // 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; const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); const { FormAutofill } = ChromeUtils.import( "resource://formautofill/FormAutofill.jsm" ); XPCOMUtils.defineLazyModuleGetters(this, { CreditCard: "resource://gre/modules/CreditCard.jsm", OSKeyStore: "resource://gre/modules/OSKeyStore.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, level1: new Set(), }, /** * Load address data and extension script into a sandbox from different paths. * @param {string} path * The path for address data and extension script. It could be root of the address * metadata folder(addressmetadata/) or under specific country(addressmetadata/TW/). * @returns {object} * A sandbox that contains address data object with properties from extension. */ _loadScripts(path) { let sandbox = {}; let extSandbox = {}; try { sandbox = FormAutofillUtils.loadDataFromScript(path + ADDRESS_REFERENCES); extSandbox = FormAutofillUtils.loadDataFromScript( path + ADDRESS_REFERENCES_EXT ); } catch (e) { // Will return only address references if extension loading failed or empty sandbox if // address references loading failed. return sandbox; } if (extSandbox.addressDataExt) { for (let key in extSandbox.addressDataExt) { let addressDataForKey = sandbox.addressData[key]; if (!addressDataForKey) { addressDataForKey = sandbox.addressData[key] = {}; } Object.assign(addressDataForKey, extSandbox.addressDataExt[key]); } } return sandbox; }, /** * Convert certain properties' string value into array. We should make sure * the cached data is parsed. * @param {object} data Original metadata from addressReferences. * @returns {object} parsed metadata with property value that converts to array. */ _parse(data) { if (!data) { return null; } const properties = [ "languages", "sub_keys", "sub_isoids", "sub_names", "sub_lnames", ]; for (let key of properties) { if (!data[key]) { continue; } // No need to normalize data if the value is array already. if (Array.isArray(data[key])) { return data; } data[key] = data[key].split("~"); } return data; }, /** * We'll cache addressData in the loader once the data loaded from scripts. * It'll become the example below after loading addressReferences with extension: * addressData: { * "data/US": {"lang": ["en"], ...// Data defined in libaddressinput metadata * "alternative_names": ... // Data defined in extension } * "data/CA": {} // Other supported country metadata * "data/TW": {} // Other supported country metadata * "data/TW/台北市": {} // Other supported country level 1 metadata * } * @param {string} country * @param {string?} level1 * @returns {object} Default locale metadata */ _loadData(country, level1 = null) { // Load the addressData if needed if (!this._dataLoaded.country) { this._addressData = this._loadScripts(ADDRESS_METADATA_PATH).addressData; this._dataLoaded.country = true; } if (!level1) { return this._parse(this._addressData[`data/${country}`]); } // If level1 is set, load addressReferences under country folder with specific // country/level 1 for level 2 information. if (!this._dataLoaded.level1.has(country)) { Object.assign( this._addressData, this._loadScripts(`${ADDRESS_METADATA_PATH}${country}/`).addressData ); this._dataLoaded.level1.add(country); } return this._parse(this._addressData[`data/${country}/${level1}`]); }, /** * Return the region metadata with default locale and other locales (if exists). * @param {string} country * @param {string?} level1 * @returns {object} Return default locale and other locales metadata. */ getData(country, level1 = null) { let defaultLocale = this._loadData(country, level1); if (!defaultLocale) { return null; } let countryData = this._parse(this._addressData[`data/${country}`]); let locales = []; // TODO: Should be able to support multi-locale level 1/ level 2 metadata query // in Bug 1421886 if (countryData.languages) { let list = countryData.languages.filter(key => key !== countryData.lang); locales = list.map(key => this._parse(this._addressData[`${defaultLocale.id}--${key}`]) ); } return { defaultLocale, locales }; }, }; this.FormAutofillUtils = { get AUTOFILL_FIELDS_THRESHOLD() { return 3; }, ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME, MANAGE_ADDRESSES_KEYWORDS, EDIT_ADDRESS_KEYWORDS, MANAGE_CREDITCARDS_KEYWORDS, EDIT_CREDITCARD_KEYWORDS, MAX_FIELD_VALUE_LENGTH, FIELD_STATES, SECTION_TYPES, _fieldNameInfo: { name: "name", "given-name": "name", "additional-name": "name", "family-name": "name", organization: "organization", "street-address": "address", "address-line1": "address", "address-line2": "address", "address-line3": "address", "address-level1": "address", "address-level2": "address", "postal-code": "address", country: "address", "country-name": "address", tel: "tel", "tel-country-code": "tel", "tel-national": "tel", "tel-area-code": "tel", "tel-local": "tel", "tel-local-prefix": "tel", "tel-local-suffix": "tel", "tel-extension": "tel", email: "email", "cc-name": "creditCard", "cc-given-name": "creditCard", "cc-additional-name": "creditCard", "cc-family-name": "creditCard", "cc-number": "creditCard", "cc-exp-month": "creditCard", "cc-exp-year": "creditCard", "cc-exp": "creditCard", "cc-type": "creditCard", }, _collators: {}, _reAlternativeCountryNames: {}, isAddressField(fieldName) { return ( !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName) ); }, isCreditCardField(fieldName) { return this._fieldNameInfo[fieldName] == "creditCard"; }, isCCNumber(ccNumber) { return CreditCard.isValidNumber(ccNumber); }, ensureLoggedIn(promptMessage) { return OSKeyStore.ensureLoggedIn( this._reauthEnabledByUser && promptMessage ? promptMessage : false ); }, /** * Get the array of credit card network ids ("types") we expect and offer as valid choices * * @returns {Array} */ getCreditCardNetworks() { return CreditCard.SUPPORTED_NETWORKS; }, getCategoryFromFieldName(fieldName) { return this._fieldNameInfo[fieldName]; }, getCategoriesFromFieldNames(fieldNames) { let categories = new Set(); for (let fieldName of fieldNames) { let info = this.getCategoryFromFieldName(fieldName); if (info) { categories.add(info); } } return Array.from(categories); }, getAddressSeparator() { // The separator should be based on the L10N address format, and using a // white space is a temporary solution. return " "; }, /** * Get address display label. It should display information separated * by a comma. * * @param {object} address * @param {string?} addressFields Override the fields which can be displayed, but not the order. * @returns {string} */ getAddressLabel(address, addressFields = null) { // TODO: Implement a smarter way for deciding what to display // as option text. Possibly improve the algorithm in // ProfileAutoCompleteResult.jsm and reuse it here. let fieldOrder = [ "name", "-moz-street-address-one-line", // Street address "address-level3", // Townland / Neighborhood / Village "address-level2", // City/Town "organization", // Company or organization name "address-level1", // Province/State (Standardized code if possible) "country-name", // Country name "postal-code", // Postal code "tel", // Phone number "email", // Email address ]; address = { ...address }; let parts = []; if (addressFields) { let requiredFields = addressFields.trim().split(/\s+/); fieldOrder = fieldOrder.filter(name => requiredFields.includes(name)); } if (address["street-address"]) { address["-moz-street-address-one-line"] = this.toOneLineAddress( address["street-address"] ); } for (const fieldName of fieldOrder) { let string = address[fieldName]; if (string) { parts.push(string); } if (parts.length == 2 && !addressFields) { break; } } return parts.join(", "); }, /** * Internal method to split an address to multiple parts per the provided delimiter, * removing blank parts. * @param {string} address The address the split * @param {string} [delimiter] The separator that is used between lines in the address * @returns {string[]} */ _toStreetAddressParts(address, delimiter = "\n") { let array = typeof address == "string" ? address.split(delimiter) : address; if (!Array.isArray(array)) { return []; } return array.map(s => (s ? s.trim() : "")).filter(s => s); }, /** * Converts a street address to a single line, removing linebreaks marked by the delimiter * @param {string} address The address the convert * @param {string} [delimiter] The separator that is used between lines in the address * @returns {string} */ toOneLineAddress(address, delimiter = "\n") { let addressParts = this._toStreetAddressParts(address, delimiter); return addressParts.join(this.getAddressSeparator()); }, /** * Compares two addresses, removing internal whitespace * @param {string} a The first address to compare * @param {string} b The second address to compare * @param {array} collators Search collators that will be used for comparison * @param {string} [delimiter="\n"] The separator that is used between lines in the address * @returns {boolean} True if the addresses are equal, false otherwise */ compareStreetAddress(a, b, collators, delimiter = "\n") { let oneLineA = this._toStreetAddressParts(a, delimiter) .map(p => p.replace(/\s/g, "")) .join(""); let oneLineB = this._toStreetAddressParts(b, delimiter) .map(p => p.replace(/\s/g, "")) .join(""); return this.strCompare(oneLineA, oneLineB, collators); }, /** * In-place concatenate tel-related components into a single "tel" field and * delete unnecessary fields. * @param {object} address An address record. */ compressTel(address) { let telCountryCode = address["tel-country-code"] || ""; let telAreaCode = address["tel-area-code"] || ""; if (!address.tel) { if (address["tel-national"]) { address.tel = telCountryCode + address["tel-national"]; } else if (address["tel-local"]) { address.tel = telCountryCode + telAreaCode + address["tel-local"]; } else if (address["tel-local-prefix"] && address["tel-local-suffix"]) { address.tel = telCountryCode + telAreaCode + address["tel-local-prefix"] + address["tel-local-suffix"]; } } for (let field in address) { if (field != "tel" && this.getCategoryFromFieldName(field) == "tel") { delete address[field]; } } }, autofillFieldSelector(doc) { return doc.querySelectorAll("input, select"); }, ALLOWED_TYPES: ["text", "email", "tel", "number", "month"], isFieldEligibleForAutofill(element) { let tagName = element.tagName; if (tagName == "INPUT") { // `element.type` can be recognized as `text`, if it's missing or invalid. if (!this.ALLOWED_TYPES.includes(element.type)) { return false; } } else if (tagName != "SELECT") { return false; } return true; }, loadDataFromScript(url, sandbox = {}) { Services.scriptloader.loadSubScript(url, sandbox); return sandbox; }, /** * Get country address data and fallback to US if not found. * See AddressDataLoader._loadData for more details of addressData structure. * @param {string} [country=FormAutofill.DEFAULT_REGION] * The country code for requesting specific country's metadata. It'll be * default region if parameter is not set. * @param {string} [level1=null] * Return address level 1/level 2 metadata if parameter is set. * @returns {object|null} * Return metadata of specific region with default locale and other supported * locales. We need to return a default country metadata for layout format * and collator, but for sub-region metadata we'll just return null if not found. */ getCountryAddressRawData( country = FormAutofill.DEFAULT_REGION, level1 = null ) { let metadata = AddressDataLoader.getData(country, level1); if (!metadata) { if (level1) { return null; } // Fallback to default region if we couldn't get data from given country. if (country != FormAutofill.DEFAULT_REGION) { metadata = AddressDataLoader.getData(FormAutofill.DEFAULT_REGION); } } // TODO: Now we fallback to US if we couldn't get data from default region, // but it could be removed in bug 1423464 if it's not necessary. if (!metadata) { metadata = AddressDataLoader.getData("US"); } return metadata; }, /** * Get country address data with default locale. * @param {string} country * @param {string} level1 * @returns {object|null} Return metadata of specific region with default locale. * NOTE: The returned data may be for a default region if the * specified one cannot be found. Callers who only want the specific * region should check the returned country code. */ getCountryAddressData(country, level1) { let metadata = this.getCountryAddressRawData(country, level1); return metadata && metadata.defaultLocale; }, /** * Get country address data with all locales. * @param {string} country * @param {string} level1 * @returns {array<object>|null} * Return metadata of specific region with all the locales. * NOTE: The returned data may be for a default region if the * specified one cannot be found. Callers who only want the specific * region should check the returned country code. */ getCountryAddressDataWithLocales(country, level1) { let metadata = this.getCountryAddressRawData(country, level1); return metadata && [metadata.defaultLocale, ...metadata.locales]; }, /** * Get the collators based on the specified country. * @param {string} country The specified country. * @returns {array} An array containing several collator objects. */ getSearchCollators(country) { // TODO: Only one language should be used at a time per country. The locale // of the page should be taken into account to do this properly. // We are going to support more countries in bug 1370193 and this // should be addressed when we start to implement that bug. if (!this._collators[country]) { let dataset = this.getCountryAddressData(country); let languages = dataset.languages || [dataset.lang]; let options = { ignorePunctuation: true, sensitivity: "base", usage: "search", }; this._collators[country] = languages.map( lang => new Intl.Collator(lang, options) ); } return this._collators[country]; }, // Based on the list of fields abbreviations in // https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata FIELDS_LOOKUP: { N: "name", O: "organization", A: "street-address", S: "address-level1", C: "address-level2", D: "address-level3", Z: "postal-code", n: "newLine", }, /** * Parse a country address format string and outputs an array of fields. * Spaces, commas, and other literals are ignored in this implementation. * For example, format string "%A%n%C, %S" should return: * [ * {fieldId: "street-address", newLine: true}, * {fieldId: "address-level2"}, * {fieldId: "address-level1"}, * ] * * @param {string} fmt Country address format string * @returns {array<object>} List of fields */ parseAddressFormat(fmt) { if (!fmt) { throw new Error("fmt string is missing."); } return fmt.match(/%[^%]/g).reduce((parsed, part) => { // Take the first letter of each segment and try to identify it let fieldId = this.FIELDS_LOOKUP[part[1]]; // Early return if cannot identify part. if (!fieldId) { return parsed; } // If a new line is detected, add an attribute to the previous field. if (fieldId == "newLine") { let size = parsed.length; if (size) { parsed[size - 1].newLine = true; } return parsed; } return parsed.concat({ fieldId }); }, []); }, /** * Used to populate dropdowns in the UI (e.g. FormAutofill preferences, Web Payments). * Use findAddressSelectOption for matching a value to a region. * * @param {string[]} subKeys An array of regionCode strings * @param {string[]} subIsoids An array of ISO ID strings, if provided will be preferred over the key * @param {string[]} subNames An array of regionName strings * @param {string[]} subLnames An array of latinised regionName strings * @returns {Map?} Returns null if subKeys or subNames are not truthy. * Otherwise, a Map will be returned mapping keys -> names. */ buildRegionMapIfAvailable(subKeys, subIsoids, subNames, subLnames) { // Not all regions have sub_keys. e.g. DE if ( !subKeys || !subKeys.length || (!subNames && !subLnames) || (subNames && subKeys.length != subNames.length) || (subLnames && subKeys.length != subLnames.length) ) { return null; } // Overwrite subKeys with subIsoids, when available if (subIsoids && subIsoids.length && subIsoids.length == subKeys.length) { for (let i = 0; i < subIsoids.length; i++) { if (subIsoids[i]) { subKeys[i] = subIsoids[i]; } } } // Apply sub_lnames if sub_names does not exist let names = subNames || subLnames; return new Map(subKeys.map((key, index) => [key, names[index]])); }, /** * Parse a require string and outputs an array of fields. * Spaces, commas, and other literals are ignored in this implementation. * For example, a require string "ACS" should return: * ["street-address", "address-level2", "address-level1"] * * @param {string} requireString Country address require string * @returns {array<string>} List of fields */ parseRequireString(requireString) { if (!requireString) { throw new Error("requireString string is missing."); } return requireString.split("").map(fieldId => this.FIELDS_LOOKUP[fieldId]); }, /** * Use alternative country name list to identify a country code from a * specified country name. * @param {string} countryName A country name to be identified * @param {string} [countrySpecified] A country code indicating that we only * search its alternative names if specified. * @returns {string} The matching country code. */ identifyCountryCode(countryName, countrySpecified) { let countries = countrySpecified ? [countrySpecified] : [...FormAutofill.countries.keys()]; for (let country of countries) { let collators = this.getSearchCollators(country); let metadata = this.getCountryAddressData(country); if (country != metadata.key) { // We hit the fallback logic in getCountryAddressRawData so ignore it as // it's not related to `country` and use the name from l10n instead. metadata = { id: `data/${country}`, key: country, name: FormAutofill.countries.get(country), }; } let alternativeCountryNames = metadata.alternative_names || [ metadata.name, ]; let reAlternativeCountryNames = this._reAlternativeCountryNames[country]; if (!reAlternativeCountryNames) { reAlternativeCountryNames = this._reAlternativeCountryNames[ country ] = []; } for (let i = 0; i < alternativeCountryNames.length; i++) { let name = alternativeCountryNames[i]; let reName = reAlternativeCountryNames[i]; if (!reName) { reName = reAlternativeCountryNames[i] = new RegExp( "\\b" + this.escapeRegExp(name) + "\\b", "i" ); } if ( this.strCompare(name, countryName, collators) || reName.test(countryName) ) { return country; } } } return null; }, findSelectOption(selectEl, record, fieldName) { if (this.isAddressField(fieldName)) { return this.findAddressSelectOption(selectEl, record, fieldName); } if (this.isCreditCardField(fieldName)) { return this.findCreditCardSelectOption(selectEl, record, fieldName); } return null; }, /** * Try to find the abbreviation of the given sub-region name * @param {string[]} subregionValues A list of inferable sub-region values. * @param {string} [country] A country name to be identified. * @returns {string} The matching sub-region abbreviation. */ getAbbreviatedSubregionName(subregionValues, country) { let values = Array.isArray(subregionValues) ? subregionValues : [subregionValues]; let collators = this.getSearchCollators(country); for (let metadata of this.getCountryAddressDataWithLocales(country)) { let { sub_keys: subKeys, sub_names: subNames, sub_lnames: subLnames, } = metadata; if (!subKeys) { // Not all regions have sub_keys. e.g. DE continue; } // Apply sub_lnames if sub_names does not exist subNames = subNames || subLnames; let speculatedSubIndexes = []; for (const val of values) { let identifiedValue = this.identifyValue( subKeys, subNames, val, collators ); if (identifiedValue) { return identifiedValue; } // Predict the possible state by partial-matching if no exact match. [subKeys, subNames].forEach(sub => { speculatedSubIndexes.push( sub.findIndex(token => { let pattern = new RegExp( "\\b" + this.escapeRegExp(token) + "\\b" ); return pattern.test(val); }) ); }); } let subKey = subKeys[speculatedSubIndexes.find(i => !!~i)]; if (subKey) { return subKey; } } return null; }, /** * Find the option element from select element. * 1. Try to find the locale using the country from address. * 2. First pass try to find exact match. * 3. Second pass try to identify values from address value and options, * and look for a match. * @param {DOMElement} selectEl * @param {object} address * @param {string} fieldName * @returns {DOMElement} */ findAddressSelectOption(selectEl, address, fieldName) { let value = address[fieldName]; if (!value) { return null; } let collators = this.getSearchCollators(address.country); for (let option of selectEl.options) { if ( this.strCompare(value, option.value, collators) || this.strCompare(value, option.text, collators) ) { return option; } } switch (fieldName) { case "address-level1": { let { country } = address; let identifiedValue = this.getAbbreviatedSubregionName( [value], country ); // No point going any further if we cannot identify value from address level 1 if (!identifiedValue) { return null; } for (let dataset of this.getCountryAddressDataWithLocales(country)) { let keys = dataset.sub_keys; if (!keys) { // Not all regions have sub_keys. e.g. DE continue; } // Apply sub_lnames if sub_names does not exist let names = dataset.sub_names || dataset.sub_lnames; // Go through options one by one to find a match. // Also check if any option contain the address-level1 key. let pattern = new RegExp( "\\b" + this.escapeRegExp(identifiedValue) + "\\b", "i" ); for (let option of selectEl.options) { let optionValue = this.identifyValue( keys, names, option.value, collators ); let optionText = this.identifyValue( keys, names, option.text, collators ); if ( identifiedValue === optionValue || identifiedValue === optionText || pattern.test(option.value) ) { return option; } } } break; } case "country": { if (this.getCountryAddressData(value).alternative_names) { for (let option of selectEl.options) { if ( this.identifyCountryCode(option.text, value) || this.identifyCountryCode(option.value, value) ) { return option; } } } break; } } return null; }, findCreditCardSelectOption(selectEl, creditCard, fieldName) { let oneDigitMonth = creditCard["cc-exp-month"] ? creditCard["cc-exp-month"].toString() : null; let twoDigitsMonth = oneDigitMonth ? oneDigitMonth.padStart(2, "0") : null; let fourDigitsYear = creditCard["cc-exp-year"] ? creditCard["cc-exp-year"].toString() : null; let twoDigitsYear = fourDigitsYear ? fourDigitsYear.substr(2, 2) : null; let options = Array.from(selectEl.options); switch (fieldName) { case "cc-exp-month": { if (!oneDigitMonth) { return null; } for (let option of options) { if ( [option.text, option.label, option.value].some(s => { let result = /[1-9]\d*/.exec(s); return result && result[0] == oneDigitMonth; }) ) { return option; } } break; } case "cc-exp-year": { if (!fourDigitsYear) { return null; } for (let option of options) { if ( [option.text, option.label, option.value].some( s => s == twoDigitsYear || s == fourDigitsYear ) ) { return option; } } break; } case "cc-exp": { if (!oneDigitMonth || !fourDigitsYear) { return null; } let patterns = [ oneDigitMonth + "/" + twoDigitsYear, // 8/22 oneDigitMonth + "/" + fourDigitsYear, // 8/2022 twoDigitsMonth + "/" + twoDigitsYear, // 08/22 twoDigitsMonth + "/" + fourDigitsYear, // 08/2022 oneDigitMonth + "-" + twoDigitsYear, // 8-22 oneDigitMonth + "-" + fourDigitsYear, // 8-2022 twoDigitsMonth + "-" + twoDigitsYear, // 08-22 twoDigitsMonth + "-" + fourDigitsYear, // 08-2022 twoDigitsYear + "-" + twoDigitsMonth, // 22-08 fourDigitsYear + "-" + twoDigitsMonth, // 2022-08 fourDigitsYear + "/" + oneDigitMonth, // 2022/8 twoDigitsMonth + twoDigitsYear, // 0822 twoDigitsYear + twoDigitsMonth, // 2208 ]; for (let option of options) { if ( [option.text, option.label, option.value].some(str => patterns.some(pattern => str.includes(pattern)) ) ) { return option; } } break; } case "cc-type": { let network = creditCard["cc-type"] || ""; for (let option of options) { if ( [option.text, option.label, option.value].some( s => s.trim().toLowerCase() == network ) ) { return option; } } break; } } return null; }, /** * Try to match value with keys and names, but always return the key. * @param {array<string>} keys * @param {array<string>} names * @param {string} value * @param {array} collators * @returns {string} */ identifyValue(keys, names, value, collators) { let resultKey = keys.find(key => this.strCompare(value, key, collators)); if (resultKey) { return resultKey; } let index = names.findIndex(name => this.strCompare(value, name, collators) ); if (index !== -1) { return keys[index]; } return null; }, /** * Compare if two strings are the same. * @param {string} a * @param {string} b * @param {array} collators * @returns {boolean} */ strCompare(a = "", b = "", collators) { return collators.some(collator => !collator.compare(a, b)); }, /** * Escaping user input to be treated as a literal string within a regular * expression. * @param {string} string * @returns {string} */ escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }, /** * Get formatting information of a given country * @param {string} country * @returns {object} * { * {string} addressLevel3Label * {string} addressLevel2Label * {string} addressLevel1Label * {string} postalCodeLabel * {object} fieldsOrder * {string} postalCodePattern * } */ getFormFormat(country) { let dataset = this.getCountryAddressData(country); // We hit a country fallback in `getCountryAddressRawData` but it's not relevant here. if (country != dataset.key) { // Use a sparse object so the below default values take effect. dataset = { /** * Even though data/ZZ only has address-level2, include the other levels * in case they are needed for unknown countries. Users can leave the * unnecessary fields blank which is better than forcing users to enter * the data in incorrect fields. */ fmt: "%N%n%O%n%A%n%C %S %Z", }; } return { // When particular values are missing for a country, the // data/ZZ value should be used instead: // https://chromium-i18n.appspot.com/ssl-aggregate-address/data/ZZ addressLevel3Label: dataset.sublocality_name_type || "suburb", addressLevel2Label: dataset.locality_name_type || "city", addressLevel1Label: dataset.state_name_type || "province", addressLevel1Options: this.buildRegionMapIfAvailable( dataset.sub_keys, dataset.sub_isoids, dataset.sub_names, dataset.sub_lnames ), countryRequiredFields: this.parseRequireString(dataset.require || "AC"), fieldsOrder: this.parseAddressFormat(dataset.fmt || "%N%n%O%n%A%n%C"), postalCodeLabel: dataset.zip_name_type || "postalCode", postalCodePattern: dataset.zip, }; }, /** * Localize "data-localization" or "data-localization-region" attributes. * @param {Element} element * @param {string} attributeName */ localizeAttributeForElement(element, attributeName) { switch (attributeName) { case "data-localization": { element.textContent = this.stringBundle.GetStringFromName( element.getAttribute(attributeName) ); element.removeAttribute(attributeName); break; } case "data-localization-region": { let regionCode = element.getAttribute(attributeName); element.textContent = Services.intl.getRegionDisplayNames(undefined, [ regionCode, ]); element.removeAttribute(attributeName); return; } default: throw new Error("Unexpected attributeName"); } }, /** * Localize elements with "data-localization" or "data-localization-region" attributes. * @param {Element} root */ localizeMarkup(root) { let elements = root.querySelectorAll("[data-localization]"); for (let element of elements) { this.localizeAttributeForElement(element, "data-localization"); } elements = root.querySelectorAll("[data-localization-region]"); for (let element of elements) { this.localizeAttributeForElement(element, "data-localization-region"); } }, }; this.log = null; FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]); XPCOMUtils.defineLazyGetter(FormAutofillUtils, "stringBundle", function() { return Services.strings.createBundle( "chrome://formautofill/locale/formautofill.properties" ); }); XPCOMUtils.defineLazyGetter(FormAutofillUtils, "brandBundle", function() { return Services.strings.createBundle( "chrome://branding/locale/brand.properties" ); }); XPCOMUtils.defineLazyPreferenceGetter( FormAutofillUtils, "_reauthEnabledByUser", "extensions.formautofill.reauth.enabled", false ); PK �������!<l=:��:��(���chrome/res/ProfileAutoCompleteResult.jsm/* 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 = ["AddressResult", "CreditCardResult"]; const { XPCOMUtils } = ChromeUtils.import( "resource://gre/modules/XPCOMUtils.jsm" ); const { FormAutofill } = ChromeUtils.import( "resource://formautofill/FormAutofill.jsm" ); ChromeUtils.defineModuleGetter( this, "FormAutofillUtils", "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; FormAutofill.defineLazyLogGetter(this, EXPORTED_SYMBOLS[0]); class ProfileAutoCompleteResult { constructor( searchString, focusedFieldName, allFieldNames, matchingProfiles, { resultCode = null, isSecure = true, isInputAutofilled = false } ) { log.debug("Constructing new ProfileAutoCompleteResult:", [...arguments]); // nsISupports this.QueryInterface = ChromeUtils.generateQI(["nsIAutoCompleteResult"]); // The user's query string this.searchString = searchString; // The field name of the focused input. this._focusedFieldName = focusedFieldName; // The matching profiles contains the information for filling forms. this._matchingProfiles = matchingProfiles; // The default item that should be entered if none is selected this.defaultIndex = 0; // The reason the search failed this.errorDescription = ""; // The value used to determine whether the form is secure or not. this._isSecure = isSecure; // The value to indicate whether the focused input has been autofilled or not. this._isInputAutofilled = isInputAutofilled; // All fillable field names in the form including the field name of the currently-focused input. this._allFieldNames = [ ...this._matchingProfiles.reduce((fieldSet, curProfile) => { for (let field of Object.keys(curProfile)) { fieldSet.add(field); } return fieldSet; }, new Set()), ].filter(field => allFieldNames.includes(field)); // Force return success code if the focused field is auto-filled in order // to show clear form button popup. if (isInputAutofilled) { resultCode = Ci.nsIAutoCompleteResult.RESULT_SUCCESS; } // The result code of this result object. if (resultCode) { this.searchResult = resultCode; } else if (matchingProfiles.length) { this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS; } else { this.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH; } // An array of primary and secondary labels for each profile. this._popupLabels = this._generateLabels( this._focusedFieldName, this._allFieldNames, this._matchingProfiles ); } /** * @returns {number} The number of results */ get matchCount() { return this._popupLabels.length; } _checkIndexBounds(index) { if (index < 0 || index >= this._popupLabels.length) { throw Components.Exception( "Index out of range.", Cr.NS_ERROR_ILLEGAL_VALUE ); } } /** * Get the secondary label based on the focused field name and related field names * in the same form. * @param {string} focusedFieldName The field name of the focused input * @param {Array<Object>} allFieldNames The field names in the same section * @param {object} profile The profile providing the labels to show. * @returns {string} The secondary label */ _getSecondaryLabel(focusedFieldName, allFieldNames, profile) { return ""; } _generateLabels(focusedFieldName, allFieldNames, profiles) {} /** * Get the value of the result at the given index. * * Always return empty string for form autofill feature to suppress * AutoCompleteController from autofilling, as we'll populate the * fields on our own. * * @param {number} index The index of the result requested * @returns {string} The result at the specified index */ getValueAt(index) { this._checkIndexBounds(index); return ""; } getLabelAt(index) { this._checkIndexBounds(index); let label = this._popupLabels[index]; if (typeof label == "string") { return label; } return JSON.stringify(label); } /** * Retrieves a comment (metadata instance) * @param {number} index The index of the comment requested * @returns {string} The comment at the specified index */ getCommentAt(index) { this._checkIndexBounds(index); return JSON.stringify(this._matchingProfiles[index]); } /** * Retrieves a style hint specific to a particular index. * @param {number} index The index of the style hint requested * @returns {string} The style hint at the specified index */ getStyleAt(index) { this._checkIndexBounds(index); if (index == this.matchCount - 1) { return "autofill-footer"; } if (this._isInputAutofilled) { return "autofill-clear-button"; } return "autofill-profile"; } /** * Retrieves an image url. * @param {number} index The index of the image url requested * @returns {string} The image url at the specified index */ getImageAt(index) { this._checkIndexBounds(index); return ""; } /** * Retrieves a result * @param {number} index The index of the result requested * @returns {string} The result at the specified index */ getFinalCompleteValueAt(index) { return this.getValueAt(index); } /** * Removes a result from the resultset * @param {number} index The index of the result to remove */ removeValueAt(index) { // There is no plan to support removing profiles via autocomplete. } } class AddressResult extends ProfileAutoCompleteResult { constructor(...args) { super(...args); } _getSecondaryLabel(focusedFieldName, allFieldNames, profile) { // We group similar fields into the same field name so we won't pick another // field in the same group as the secondary label. const GROUP_FIELDS = { name: ["name", "given-name", "additional-name", "family-name"], "street-address": [ "street-address", "address-line1", "address-line2", "address-line3", ], "country-name": ["country", "country-name"], tel: [ "tel", "tel-country-code", "tel-national", "tel-area-code", "tel-local", "tel-local-prefix", "tel-local-suffix", ], }; const secondaryLabelOrder = [ "street-address", // Street address "name", // Full name "address-level3", // Townland / Neighborhood / Village "address-level2", // City/Town "organization", // Company or organization name "address-level1", // Province/State (Standardized code if possible) "country-name", // Country name "postal-code", // Postal code "tel", // Phone number "email", // Email address ]; for (let field in GROUP_FIELDS) { if (GROUP_FIELDS[field].includes(focusedFieldName)) { focusedFieldName = field; break; } } for (const currentFieldName of secondaryLabelOrder) { if (focusedFieldName == currentFieldName || !profile[currentFieldName]) { continue; } let matching = GROUP_FIELDS[currentFieldName] ? allFieldNames.some(fieldName => GROUP_FIELDS[currentFieldName].includes(fieldName) ) : allFieldNames.includes(currentFieldName); if (matching) { if ( currentFieldName == "street-address" && profile["-moz-street-address-one-line"] ) { return profile["-moz-street-address-one-line"]; } return profile[currentFieldName]; } } return ""; // Nothing matched. } _generateLabels(focusedFieldName, allFieldNames, profiles) { if (this._isInputAutofilled) { return [ { primary: "", secondary: "" }, // Clear button { primary: "", secondary: "" }, // Footer ]; } // Skip results without a primary label. let labels = profiles .filter(profile => { return !!profile[focusedFieldName]; }) .map(profile => { let primaryLabel = profile[focusedFieldName]; if ( focusedFieldName == "street-address" && profile["-moz-street-address-one-line"] ) { primaryLabel = profile["-moz-street-address-one-line"]; } return { primary: primaryLabel, secondary: this._getSecondaryLabel( focusedFieldName, allFieldNames, profile ), }; }); // Add an empty result entry for footer. Its content will come from // the footer binding, so don't assign any value to it. // The additional properties: categories and focusedCategory are required of // the popup to generate autofill hint on the footer. labels.push({ primary: "", secondary: "", categories: FormAutofillUtils.getCategoriesFromFieldNames( this._allFieldNames ), focusedCategory: FormAutofillUtils.getCategoryFromFieldName( this._focusedFieldName ), }); return labels; } } class CreditCardResult extends ProfileAutoCompleteResult { constructor(...args) { super(...args); this._cardTypes = this._generateCardTypes( this._focusedFieldName, this._allFieldNames, this._matchingProfiles ); } _getSecondaryLabel(focusedFieldName, allFieldNames, profile) { const GROUP_FIELDS = { "cc-name": [ "cc-name", "cc-given-name", "cc-additional-name", "cc-family-name", ], "cc-exp": ["cc-exp", "cc-exp-month", "cc-exp-year"], }; const secondaryLabelOrder = [ "cc-number", // Credit card number "cc-name", // Full name "cc-exp", // Expiration date ]; for (let field in GROUP_FIELDS) { if (GROUP_FIELDS[field].includes(focusedFieldName)) { focusedFieldName = field; break; } } for (const currentFieldName of secondaryLabelOrder) { if (focusedFieldName == currentFieldName || !profile[currentFieldName]) { continue; } 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 } = CreditCard.formatMaskedNumber( profile[currentFieldName] ); return affix + label; } return profile[currentFieldName]; } } return ""; // Nothing matched. } _generateLabels(focusedFieldName, allFieldNames, profiles) { if (!this._isSecure) { if (!insecureWarningEnabled) { return []; } let brandName = FormAutofillUtils.brandBundle.GetStringFromName( "brandShortName" ); return [ FormAutofillUtils.stringBundle.formatStringFromName( "insecureFieldWarningDescription", [brandName] ), ]; } if (this._isInputAutofilled) { return [ { primary: "", secondary: "" }, // Clear button { primary: "", secondary: "" }, // Footer ]; } // 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 } = CreditCard.formatMaskedNumber(primary); primaryAffix = affix; primary = label; } const secondary = this._getSecondaryLabel( focusedFieldName, allFieldNames, profile ); // The card type is displayed visually using an image. For a11y, we need // to expose it as text. We do this using aria-label. However, // aria-label overrides the text content, so we must include that also. let ccTypeName; try { ccTypeName = FormAutofillUtils.stringBundle.GetStringFromName( `cardNetwork.${profile["cc-type"]}` ); } catch (e) { ccTypeName = null; // Unknown. } const ariaLabel = [ccTypeName, primaryAffix, primary, secondary] .filter(chunk => !!chunk) // Exclude empty chunks. .join(" "); return { primaryAffix, primary, secondary, ariaLabel, }; }); // Add an empty result entry for footer. labels.push({ primary: "", secondary: "" }); return labels; } // This method needs to return an array that parallels the // array returned by _generateLabels, above. As a consequence, // its logic follows very closely. _generateCardTypes(focusedFieldName, allFieldNames, profiles) { if (this._isInputAutofilled) { return [ "", // Clear button "", // Footer ]; } // Skip results without a primary label. let cardTypes = profiles .filter(profile => { return !!profile[focusedFieldName]; }) .map(profile => profile["cc-type"]); // Add an empty result entry for footer. cardTypes.push(""); return cardTypes; } getStyleAt(index) { this._checkIndexBounds(index); if (!this._isSecure && insecureWarningEnabled) { return "autofill-insecureWarning"; } return super.getStyleAt(index); } getImageAt(index) { const PATH = "chrome://formautofill/content/"; const THIRD_PARTY_PATH = PATH + "third-party/"; this._checkIndexBounds(index); switch (this._cardTypes[index]) { case "amex": return THIRD_PARTY_PATH + "cc-logo-amex.png"; case "cartebancaire": return THIRD_PARTY_PATH + "cc-logo-cartebancaire.png"; case "diners": return THIRD_PARTY_PATH + "cc-logo-diners.svg"; case "discover": return THIRD_PARTY_PATH + "cc-logo-discover.png"; case "jcb": return THIRD_PARTY_PATH + "cc-logo-jcb.svg"; case "mastercard": return THIRD_PARTY_PATH + "cc-logo-mastercard.svg"; case "mir": return THIRD_PARTY_PATH + "cc-logo-mir.svg"; case "unionpay": return THIRD_PARTY_PATH + "cc-logo-unionpay.svg"; case "visa": return THIRD_PARTY_PATH + "cc-logo-visa.svg"; default: return PATH + "icon-credit-card-generic.svg"; } } } PK �������!<Ҳ��/���chrome/res/addressmetadata/addressReferences.js/* 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/. */ /* exported addressData */ /* eslint max-len: 0 */ "use strict"; // The data below is initially copied from // https://chromium-i18n.appspot.com/ssl-aggregate-address // See https://github.com/googlei18n/libaddressinput/wiki/AddressValidationMetadata for // documentation on how to use the data. // WARNING: DO NOT change any value or add additional properties in addressData. // We only accept the metadata of the supported countries that is copied from libaddressinput directly. // Please edit addressReferencesExt.js instead if you want to add new property as complement // or overwrite the existing properties. var addressData = { "data/AD": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/AD", key: "AD", lang: "ca", languages: "ca", name: "ANDORRA", posturl: "http://www.correos.es/comun/CodigosPostales/1010_s-CodPostal.asp?Provincia=", sub_isoids: "07~02~03~08~04~05~06", sub_keys: "Parròquia d'Andorra la Vella~Canillo~Encamp~Escaldes-Engordany~La Massana~Ordino~Sant Julià de Lòria", sub_names: "Andorra la Vella~Canillo~Encamp~Escaldes-Engordany~La Massana~Ordino~Sant Julià de Lòria", sub_zipexs: "AD500~AD100~AD200~AD700~AD400~AD300~AD600", sub_zips: "AD50[01]~AD10[01]~AD20[01]~AD70[01]~AD40[01]~AD30[01]~AD60[01]", zip: "AD[1-7]0\\d", zipex: "AD100,AD501,AD700", }, "data/AE": { fmt: "%N%n%O%n%A%n%S", id: "data/AE", key: "AE", lang: "ar", languages: "ar", lfmt: "%N%n%O%n%A%n%S", name: "UNITED ARAB EMIRATES", require: "AS", state_name_type: "emirate", sub_isoids: "AZ~SH~FU~UQ~DU~RK~AJ", sub_keys: "أبو ظبي~إمارة الشارقةّ~الفجيرة~ام القيوين~إمارة دبيّ~إمارة رأس الخيمة~عجمان", sub_lnames: "Abu Dhabi~Sharjah~Fujairah~Umm Al Quwain~Dubai~Ras al Khaimah~Ajman", sub_names: "أبو ظبي~الشارقة~الفجيرة~ام القيوين~دبي~رأس الخيمة~عجمان", }, "data/AF": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/AF", key: "AF", name: "AFGHANISTAN", zip: "\\d{4}", zipex: "1001,2601,3801", }, "data/AG": { id: "data/AG", key: "AG", name: "ANTIGUA AND BARBUDA", require: "A", }, "data/AI": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/AI", key: "AI", name: "ANGUILLA", zip: "(?:AI-)?2640", zipex: "2640", }, "data/AL": { fmt: "%N%n%O%n%A%n%Z%n%C", id: "data/AL", key: "AL", name: "ALBANIA", zip: "\\d{4}", zipex: "1001,1017,3501", }, "data/AM": { fmt: "%N%n%O%n%A%n%Z%n%C%n%S", id: "data/AM", key: "AM", lang: "hy", languages: "hy", lfmt: "%N%n%O%n%A%n%Z%n%C%n%S", name: "ARMENIA", sub_isoids: "AG~AR~AV~GR~ER~LO~KT~SH~SU~VD~TV", sub_keys: "Արագածոտն~Արարատ~Արմավիր~Գեղարքունիք~Երևան~Լոռի~Կոտայք~Շիրակ~Սյունիք~Վայոց ձոր~Տավուշ", sub_lnames: "Aragatsotn~Ararat~Armavir~Gegharkunik~Yerevan~Lori~Kotayk~Shirak~Syunik~Vayots Dzor~Tavush", sub_zipexs: "0201,0514~0601,0823~0901,1149~1201,1626~0000,0099~1701,2117~2201,2506~2601,3126~3201,3519~3601,3810~3901,4216", sub_zips: "0[2-5]~0[6-8]~09|1[01]~1[2-6]~00~1[7-9]|2[01]~2[2-5]~2[6-9]|3[01]~3[2-5]~3[6-8]~39|4[0-2]", zip: "(?:37)?\\d{4}", zipex: "375010,0002,0010", }, "data/AO": { id: "data/AO", key: "AO", name: "ANGOLA" }, "data/AQ": { id: "data/AQ", key: "AQ", name: "ANTARCTICA" }, "data/AR": { fmt: "%N%n%O%n%A%n%Z %C%n%S", id: "data/AR", key: "AR", lang: "es", languages: "es", name: "ARGENTINA", posturl: "http://www.correoargentino.com.ar/formularios/cpa", sub_isoids: "B~K~H~U~C~X~W~E~P~Y~L~F~M~N~Q~R~A~J~D~Z~S~G~V~T", sub_keys: "Buenos Aires~Catamarca~Chaco~Chubut~Ciudad Autónoma de Buenos Aires~Córdoba~Corrientes~Entre Ríos~Formosa~Jujuy~La Pampa~La Rioja~Mendoza~Misiones~Neuquén~Río Negro~Salta~San Juan~San Luis~Santa Cruz~Santa Fe~Santiago del Estero~Tierra del Fuego~Tucumán", sub_names: "Buenos Aires~Catamarca~Chaco~Chubut~Ciudad Autónoma de Buenos Aires~Córdoba~Corrientes~Entre Ríos~Formosa~Jujuy~La Pampa~La Rioja~Mendoza~Misiones~Neuquén~Río Negro~Salta~San Juan~San Luis~Santa Cruz~Santa Fe~Santiago del Estero~Tierra del Fuego~Tucumán", sub_zips: "B?[1-36-8]~K?[45]~H?3~U?[89]~C?1~X?[235-8]~W?3~E?[1-3]~P?[37]~Y?4~L?[3568]~F?5~M?[56]~N?3~Q?[38]~R?[89]~A?[34]~J?5~D?[4-6]~Z?[89]~S?[2368]~G?[2-5]~V?9~T?[45]", upper: "ACZ", zip: "((?:[A-HJ-NP-Z])?\\d{4})([A-Z]{3})?", zipex: "C1070AAM,C1000WAM,B1000TBU,X5187XAB", }, "data/AS": { fmt: "%N%n%O%n%A%n%C %S %Z", id: "data/AS", key: "AS", name: "AMERICAN SAMOA", posturl: "http://zip4.usps.com/zip4/welcome.jsp", require: "ACSZ", state_name_type: "state", upper: "ACNOS", zip: "(96799)(?:[ \\-](\\d{4}))?", zip_name_type: "zip", zipex: "96799", }, "data/AT": { fmt: "%O%n%N%n%A%n%Z %C", id: "data/AT", key: "AT", name: "AUSTRIA", posturl: "http://www.post.at/post_subsite_postleitzahlfinder.php", require: "ACZ", zip: "\\d{4}", zipex: "1010,3741", }, "data/AU": { fmt: "%O%n%N%n%A%n%C %S %Z", id: "data/AU", key: "AU", lang: "en", languages: "en", locality_name_type: "suburb", name: "AUSTRALIA", posturl: "http://www1.auspost.com.au/postcodes/", require: "ACSZ", state_name_type: "state", sub_isoids: "ACT~NSW~NT~QLD~SA~TAS~VIC~WA", sub_keys: "ACT~NSW~NT~QLD~SA~TAS~VIC~WA", sub_names: "Australian Capital Territory~New South Wales~Northern Territory~Queensland~South Australia~Tasmania~Victoria~Western Australia", sub_zipexs: "0200,2540,2618,2999~1000,2888,3585,3707~0800,0999~4000,9999~5000~7000,7999~3000,8000~6000,0872", sub_zips: "29|2540|260|261[0-8]|02|2620~1|2[0-57-8]|26[2-9]|261[189]|3500|358[56]|3644|3707~0[89]~[49]~5|0872~7~[38]~6|0872", upper: "CS", zip: "\\d{4}", zipex: "2060,3171,6430,4000,4006,3001", }, "data/AW": { id: "data/AW", key: "AW", name: "ARUBA" }, "data/AZ": { fmt: "%N%n%O%n%A%nAZ %Z %C", id: "data/AZ", key: "AZ", name: "AZERBAIJAN", postprefix: "AZ ", zip: "\\d{4}", zipex: "1000", }, "data/BA": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/BA", key: "BA", name: "BOSNIA AND HERZEGOVINA", zip: "\\d{5}", zipex: "71000", }, "data/BB": { fmt: "%N%n%O%n%A%n%C, %S %Z", id: "data/BB", key: "BB", name: "BARBADOS", state_name_type: "parish", zip: "BB\\d{5}", zipex: "BB23026,BB22025", }, "data/BD": { fmt: "%N%n%O%n%A%n%C - %Z", id: "data/BD", key: "BD", name: "BANGLADESH", posturl: "http://www.bangladeshpost.gov.bd/PostCode.asp", zip: "\\d{4}", zipex: "1340,1000", }, "data/BE": { fmt: "%O%n%N%n%A%n%Z %C", id: "data/BE", key: "BE", name: "BELGIUM", posturl: "http://www.post.be/site/nl/residential/customerservice/search/postal_codes.html", require: "ACZ", zip: "\\d{4}", zipex: "4000,1000", }, "data/BF": { fmt: "%N%n%O%n%A%n%C %X", id: "data/BF", key: "BF", name: "BURKINA FASO", }, "data/BG": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/BG", key: "BG", name: "BULGARIA (REP.)", posturl: "http://www.bgpost.bg/?cid=5", zip: "\\d{4}", zipex: "1000,1700", }, "data/BH": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/BH", key: "BH", name: "BAHRAIN", zip: "(?:\\d|1[0-2])\\d{2}", zipex: "317", }, "data/BI": { id: "data/BI", key: "BI", name: "BURUNDI" }, "data/BJ": { id: "data/BJ", key: "BJ", name: "BENIN", upper: "AC" }, "data/BL": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/BL", key: "BL", name: "SAINT BARTHELEMY", posturl: "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal", require: "ACZ", upper: "ACX", zip: "9[78][01]\\d{2}", zipex: "97100", }, "data/BM": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/BM", key: "BM", name: "BERMUDA", posturl: "http://www.landvaluation.bm/", zip: "[A-Z]{2} ?[A-Z0-9]{2}", zipex: "FL 07,HM GX,HM 12", }, "data/BN": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/BN", key: "BN", name: "BRUNEI DARUSSALAM", posturl: "http://www.post.gov.bn/SitePages/postcodes.aspx", zip: "[A-Z]{2} ?\\d{4}", zipex: "BT2328,KA1131,BA1511", }, "data/BO": { id: "data/BO", key: "BO", name: "BOLIVIA", upper: "AC" }, "data/BQ": { id: "data/BQ", key: "BQ", name: "BONAIRE, SINT EUSTATIUS, AND SABA", }, "data/BR": { fmt: "%O%n%N%n%A%n%D%n%C-%S%n%Z", id: "data/BR", key: "BR", lang: "pt", languages: "pt", name: "BRAZIL", posturl: "http://www.buscacep.correios.com.br/", require: "ASCZ", state_name_type: "state", sub_isoids: "AC~AL~AP~AM~BA~CE~DF~ES~GO~MA~MT~MS~MG~PA~PB~PR~PE~PI~RJ~RN~RS~RO~RR~SC~SP~SE~TO", sub_keys: "AC~AL~AP~AM~BA~CE~DF~ES~GO~MA~MT~MS~MG~PA~PB~PR~PE~PI~RJ~RN~RS~RO~RR~SC~SP~SE~TO", sub_mores: "true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true", sub_names: "Acre~Alagoas~Amapá~Amazonas~Bahia~Ceará~Distrito Federal~Espírito Santo~Goiás~Maranhão~Mato Grosso~Mato Grosso do Sul~Minas Gerais~Pará~Paraíba~Paraná~Pernambuco~Piauí~Rio de Janeiro~Rio Grande do Norte~Rio Grande do Sul~Rondônia~Roraima~Santa Catarina~São Paulo~Sergipe~Tocantins", sub_zipexs: "69900-000,69999-999~57000-000,57999-999~68900-000,68999-999~69000-000,69400-123~40000-000,48999-999~60000-000,63999-999~70000-000,73500-123~29000-000,29999-999~72800-000,73700-123~65000-000,65999-999~78000-000,78899-999~79000-000,79999-999~30000-000,39999-999~66000-000,68899-999~58000-000,58999-999~80000-000,87999-999~50000-000,56999-999~64000-000,64999-999~20000-000,28999-999~59000-000,59999-999~90000-000,99999-999~76800-000,78900-000,78999-999~69300-000,69399-999~88000-000,89999-999~01000-000,13000-123~49000-000,49999-999~77000-000,77999-999", sub_zips: "699~57~689~69[0-24-8]~4[0-8]~6[0-3]~7[0-1]|72[0-7]|73[0-6]~29~72[89]|73[7-9]|7[4-6]~65~78[0-8]~79~3~6[6-7]|68[0-8]~58~8[0-7]~5[0-6]~64~2[0-8]~59~9~76[89]|789~693~8[89]~[01][1-9]~49~77", sublocality_name_type: "neighborhood", upper: "CS", zip: "\\d{5}-?\\d{3}", zipex: "40301-110,70002-900", }, "data/BS": { fmt: "%N%n%O%n%A%n%C, %S", id: "data/BS", key: "BS", lang: "en", languages: "en", name: "BAHAMAS", state_name_type: "island", sub_isoids: "~AK~~BY~BI~CI~~~EX~~HI~IN~LI~MG~~RI~RC~SS~SW", sub_keys: "Abaco~Acklins~Andros~Berry Islands~Bimini~Cat Island~Crooked Island~Eleuthera~Exuma~Grand Bahama~Harbour Island~Inagua~Long Island~Mayaguana~N.P.~Ragged Island~Rum Cay~San Salvador~Spanish Wells", sub_names: "Abaco Islands~Acklins~Andros Island~Berry Islands~Bimini~Cat Island~Crooked Island~Eleuthera~Exuma and Cays~Grand Bahama~Harbour Island~Inagua~Long Island~Mayaguana~New Providence~Ragged Island~Rum Cay~San Salvador~Spanish Wells", }, "data/BT": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/BT", key: "BT", name: "BHUTAN", posturl: "http://www.bhutanpost.bt/postcodes/", zip: "\\d{5}", zipex: "11001,31101,35003", }, "data/BV": { id: "data/BV", key: "BV", name: "BOUVET ISLAND" }, "data/BW": { id: "data/BW", key: "BW", name: "BOTSWANA" }, "data/BY": { fmt: "%S%n%Z %C%n%A%n%O%n%N", id: "data/BY", key: "BY", name: "BELARUS", posturl: "http://ex.belpost.by/addressbook/", zip: "\\d{6}", zipex: "223016,225860,220050", }, "data/BZ": { id: "data/BZ", key: "BZ", name: "BELIZE" }, "data/CA": { fmt: "%N%n%O%n%A%n%C %S %Z", id: "data/CA", key: "CA", lang: "en", languages: "en~fr", name: "CANADA", posturl: "https://www.canadapost.ca/cpo/mc/personal/postalcode/fpc.jsf", require: "ACSZ", sub_isoids: "AB~BC~MB~NB~NL~NT~NS~NU~ON~PE~QC~SK~YT", sub_keys: "AB~BC~MB~NB~NL~NT~NS~NU~ON~PE~QC~SK~YT", sub_names: "Alberta~British Columbia~Manitoba~New Brunswick~Newfoundland and Labrador~Northwest Territories~Nova Scotia~Nunavut~Ontario~Prince Edward Island~Quebec~Saskatchewan~Yukon", sub_zips: "T~V~R~E~A~X0E|X0G|X1A~B~X0A|X0B|X0C~K|L|M|N|P~C~G|H|J|K1A~S|R8A~Y", upper: "ACNOSZ", zip: "[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z] ?\\d[ABCEGHJ-NPRSTV-Z]\\d", zipex: "H3Z 2Y7,V8X 3X4,T0L 1K0,T0H 1A0,K1A 0B1", }, "data/CA--fr": { fmt: "%N%n%O%n%A%n%C %S %Z", id: "data/CA--fr", key: "CA", lang: "fr", name: "CANADA", posturl: "https://www.canadapost.ca/cpo/mc/personal/postalcode/fpc.jsf", require: "ACSZ", sub_isoids: "AB~BC~PE~MB~NB~NS~NU~ON~QC~SK~NL~NT~YT", sub_keys: "AB~BC~PE~MB~NB~NS~NU~ON~QC~SK~NL~NT~YT", sub_names: "Alberta~Colombie-Britannique~Île-du-Prince-Édouard~Manitoba~Nouveau-Brunswick~Nouvelle-Écosse~Nunavut~Ontario~Québec~Saskatchewan~Terre-Neuve-et-Labrador~Territoires du Nord-Ouest~Yukon", sub_zips: "T~V~C~R~E~B~X0A|X0B|X0C~K|L|M|N|P~G|H|J|K1A~S|R8A~A~X0E|X0G|X1A~Y", upper: "ACNOSZ", zip: "[ABCEGHJKLMNPRSTVXY]\\d[ABCEGHJ-NPRSTV-Z] ?\\d[ABCEGHJ-NPRSTV-Z]\\d", zipex: "H3Z 2Y7,V8X 3X4,T0L 1K0,T0H 1A0,K1A 0B1", }, "data/CC": { fmt: "%O%n%N%n%A%n%C %S %Z", id: "data/CC", key: "CC", name: "COCOS (KEELING) ISLANDS", upper: "CS", zip: "6799", zipex: "6799", }, "data/CD": { id: "data/CD", key: "CD", name: "CONGO (DEM. REP.)" }, "data/CF": { id: "data/CF", key: "CF", name: "CENTRAL AFRICAN REPUBLIC" }, "data/CG": { id: "data/CG", key: "CG", name: "CONGO (REP.)" }, "data/CH": { fmt: "%O%n%N%n%A%nCH-%Z %C", id: "data/CH", key: "CH", name: "SWITZERLAND", postprefix: "CH-", posturl: "http://www.post.ch/db/owa/pv_plz_pack/pr_main", require: "ACZ", upper: "", zip: "\\d{4}", zipex: "2544,1211,1556,3030", }, "data/CI": { fmt: "%N%n%O%n%X %A %C %X", id: "data/CI", key: "CI", name: "COTE D'IVOIRE", }, "data/CK": { id: "data/CK", key: "CK", name: "COOK ISLANDS" }, "data/CL": { fmt: "%N%n%O%n%A%n%Z %C%n%S", id: "data/CL", key: "CL", lang: "es", languages: "es", name: "CHILE", posturl: "http://www.correos.cl/SitePages/home.aspx", sub_isoids: "AN~AR~AP~AT~AI~BI~CO~LI~LL~LR~MA~ML~RM~TA~VS", sub_keys: "Antofagasta~Araucanía~Arica y Parinacota~Atacama~Aysén~Biobío~Coquimbo~O'Higgins~Los Lagos~Los Ríos~Magallanes~Maule~Región Metropolitana~Tarapacá~Valparaíso", sub_mores: "true~true~true~true~true~true~true~true~true~true~true~true~true~true~true", sub_names: "Antofagasta~Araucanía~Arica y Parinacota~Atacama~Aysén del General Carlos Ibáñez del Campo~Biobío~Coquimbo~Libertador General Bernardo O'Higgins~Los Lagos~Los Ríos~Magallanes y de la Antártica Chilena~Maule~Metropolitana de Santiago~Tarapacá~Valparaíso", zip: "\\d{7}", zipex: "8340457,8720019,1230000,8329100", }, "data/CM": { id: "data/CM", key: "CM", name: "CAMEROON" }, "data/CN": { fmt: "%Z%n%S%C%D%n%A%n%O%n%N", id: "data/CN", key: "CN", lang: "zh", languages: "zh", lfmt: "%N%n%O%n%A%n%D%n%C%n%S, %Z", name: "CHINA", posturl: "http://www.ems.com.cn/serviceguide/you_bian_cha_xun.html", require: "ACSZ", sub_isoids: "34~92~11~50~35~62~44~45~52~46~13~41~23~42~43~22~32~36~21~15~64~63~37~14~61~31~51~71~12~54~91~65~53~33", sub_keys: "安徽省~澳门~北京市~重庆市~福建省~甘肃省~广东省~广西壮族自治区~贵州省~海南省~河北省~河南省~黑龙江省~湖北省~湖南省~吉林省~江苏省~江西省~辽宁省~内蒙古自治区~宁夏回族自治区~青海省~山东省~山西省~陕西省~上海市~四川省~台湾~天津市~西藏自治区~香港~新疆维吾尔自治区~云南省~浙江省", sub_lnames: "Anhui Sheng~Macau~Beijing Shi~Chongqing Shi~Fujian Sheng~Gansu Sheng~Guangdong Sheng~Guangxi Zhuangzuzizhiqu~Guizhou Sheng~Hainan Sheng~Hebei Sheng~Henan Sheng~Heilongjiang Sheng~Hubei Sheng~Hunan Sheng~Jilin Sheng~Jiangsu Sheng~Jiangxi Sheng~Liaoning Sheng~Neimenggu Zizhiqu~Ningxia Huizuzizhiqu~Qinghai Sheng~Shandong Sheng~Shanxi Sheng~Shaanxi Sheng~Shanghai Shi~Sichuan Sheng~Taiwan~Tianjin Shi~Xizang Zizhiqu~Hong Kong~Xinjiang Weiwuerzizhiqu~Yunnan Sheng~Zhejiang Sheng", sub_mores: "true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true", sub_names: "安徽省~澳门~北京市~重庆市~福建省~甘肃省~广东省~广西~贵州省~海南省~河北省~河南省~黑龙江省~湖北省~湖南省~吉林省~江苏省~江西省~辽宁省~内蒙古~宁夏~青海省~山东省~山西省~陕西省~上海市~四川省~台湾~天津市~西藏~香港~新疆~云南省~浙江省", sub_xrequires: "~A~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ACS~~~", sub_xzips: "~999078~~~~~~~~~~~~~~~~~~~~~~~~~~\\d{3}(\\d{2})?~~~999077~~~", sublocality_name_type: "district", upper: "S", zip: "\\d{6}", zipex: "266033,317204,100096,100808", }, "data/CO": { fmt: "%N%n%O%n%A%n%C, %S, %Z", id: "data/CO", key: "CO", name: "COLOMBIA", posturl: "http://www.codigopostal.gov.co/", require: "AS", state_name_type: "department", zip: "\\d{6}", zipex: "111221,130001,760011", }, "data/CR": { fmt: "%N%n%O%n%A%n%S, %C%n%Z", id: "data/CR", key: "CR", name: "COSTA RICA", posturl: "https://www.correos.go.cr/nosotros/codigopostal/busqueda.html", require: "ACS", zip: "\\d{4,5}|\\d{3}-\\d{4}", zipex: "1000,2010,1001", }, "data/CU": { fmt: "%N%n%O%n%A%n%C %S%n%Z", id: "data/CU", key: "CU", lang: "es", languages: "es", name: "CUBA", sub_isoids: "15~09~08~06~12~14~11~99~03~10~04~16~01~07~13~05", sub_keys: "Artemisa~Camagüey~Ciego de Ávila~Cienfuegos~Granma~Guantánamo~Holguín~Isla de la Juventud~La Habana~Las Tunas~Matanzas~Mayabeque~Pinar del Río~Sancti Spíritus~Santiago de Cuba~Villa Clara", zip: "\\d{5}", zipex: "10700", }, "data/CV": { fmt: "%N%n%O%n%A%n%Z %C%n%S", id: "data/CV", key: "CV", lang: "pt", languages: "pt", name: "CAPE VERDE", state_name_type: "island", sub_isoids: "BV~BR~~MA~SL~~~~SV", sub_keys: "Boa Vista~Brava~Fogo~Maio~Sal~Santiago~Santo Antão~São Nicolau~São Vicente", zip: "\\d{4}", zipex: "7600", }, "data/CW": { id: "data/CW", key: "CW", name: "CURACAO" }, "data/CX": { fmt: "%O%n%N%n%A%n%C %S %Z", id: "data/CX", key: "CX", name: "CHRISTMAS ISLAND", upper: "CS", zip: "6798", zipex: "6798", }, "data/CY": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/CY", key: "CY", name: "CYPRUS", zip: "\\d{4}", zipex: "2008,3304,1900", }, "data/CZ": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/CZ", key: "CZ", name: "CZECH REP.", posturl: "http://psc.ceskaposta.cz/CleanForm.action", require: "ACZ", zip: "\\d{3} ?\\d{2}", zipex: "100 00,251 66,530 87,110 00,225 99", }, "data/DE": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/DE", key: "DE", name: "GERMANY", posturl: "http://www.postdirekt.de/plzserver/", require: "ACZ", zip: "\\d{5}", zipex: "26133,53225", }, "data/DJ": { id: "data/DJ", key: "DJ", name: "DJIBOUTI" }, "data/DK": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/DK", key: "DK", name: "DENMARK", posturl: "http://www.postdanmark.dk/da/Privat/Kundeservice/postnummerkort/Sider/Find-postnummer.aspx", require: "ACZ", zip: "\\d{4}", zipex: "8660,1566", }, "data/DM": { id: "data/DM", key: "DM", name: "DOMINICA" }, "data/DO": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/DO", key: "DO", name: "DOMINICAN REP.", posturl: "http://inposdom.gob.do/codigo-postal/", zip: "\\d{5}", zipex: "11903,10101", }, "data/DZ": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/DZ", key: "DZ", name: "ALGERIA", zip: "\\d{5}", zipex: "40304,16027", }, "data/EC": { fmt: "%N%n%O%n%A%n%Z%n%C", id: "data/EC", key: "EC", name: "ECUADOR", posturl: "http://www.codigopostal.gob.ec/", upper: "CZ", zip: "\\d{6}", zipex: "090105,092301", }, "data/EE": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/EE", key: "EE", name: "ESTONIA", posturl: "https://www.omniva.ee/era/sihtnumbrite_otsing", zip: "\\d{5}", zipex: "69501,11212", }, "data/EG": { fmt: "%N%n%O%n%A%n%C%n%S%n%Z", id: "data/EG", key: "EG", lang: "ar", languages: "ar", lfmt: "%N%n%O%n%A%n%C%n%S%n%Z", name: "EGYPT", sub_isoids: "ASN~AST~ALX~IS~LX~BA~BH~GZ~DK~SUZ~SHR~GH~FYM~C~KB~MNF~MN~WAD~BNS~PTS~JS~DT~SHG~SIN~KN~KFS~MT", sub_keys: "أسوان~أسيوط~الإسكندرية~الإسماعيلية~الأقصر~البحر الأحمر~البحيرة~الجيزة~الدقهلية~السويس~الشرقية~الغربية~الفيوم~القاهرة~القليوبية~المنوفية~المنيا~الوادي الجديد~بني سويف~بورسعيد~جنوب سيناء~دمياط~سوهاج~شمال سيناء~قنا~كفر الشيخ~مطروح", sub_lnames: "Aswan Governorate~Asyut Governorate~Alexandria Governorate~Ismailia Governorate~Luxor Governorate~Red Sea Governorate~El Beheira Governorate~Giza Governorate~Dakahlia Governorate~Suez Governorate~Ash Sharqia Governorate~Gharbia Governorate~Faiyum Governorate~Cairo Governorate~Qalyubia Governorate~Menofia Governorate~Menia Governorate~New Valley Governorate~Beni Suef Governorate~Port Said Governorate~South Sinai Governorate~Damietta Governorate~Sohag Governorate~North Sinai Governorate~Qena Governorate~Kafr El Sheikh Governorate~Matrouh Governorate", sub_zipexs: "81000~71000~21000,23000~41000~85000~84000~22000~12000~35000~43000~44000~31000~63000~11000~13000~32000~61000~72000~62000~42000~46000~34000~82000~45000~83000~33000~51000", sub_zips: "81~71~2[13]~41~85~84~22~12~35~43~44~31~63~11~13~32~61~72~62~42~46~34~82~45~83~33~51", zip: "\\d{5}", zipex: "12411,11599", }, "data/EH": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/EH", key: "EH", name: "WESTERN SAHARA", zip: "\\d{5}", zipex: "70000,72000", }, "data/ER": { id: "data/ER", key: "ER", name: "ERITREA" }, "data/ES": { fmt: "%N%n%O%n%A%n%Z %C %S", id: "data/ES", key: "ES", lang: "es", languages: "es~ca~gl~eu", name: "SPAIN", posturl: "http://www.correos.es/contenido/13-MenuRec2/04-MenuRec24/1010_s-CodPostal.asp", require: "ACSZ", sub_keys: "VI~AB~A~AL~O~AV~BA~B~BU~CC~CA~S~CS~CE~CR~CO~CU~GI~GR~GU~SS~H~HU~PM~J~C~LO~GC~LE~L~LU~M~MA~ML~MU~NA~OR~P~PO~SA~TF~SG~SE~SO~T~TE~TO~V~VA~BI~ZA~Z", sub_names: "Álava~Albacete~Alicante~Almería~Asturias~Ávila~Badajoz~Barcelona~Burgos~Cáceres~Cádiz~Cantabria~Castellón~Ceuta~Ciudad Real~Córdoba~Cuenca~Girona~Granada~Guadalajara~Guipúzcoa~Huelva~Huesca~Islas Baleares~Jaén~La Coruña~La Rioja~Las Palmas~León~Lérida~Lugo~Madrid~Málaga~Melilla~Murcia~Navarra~Ourense~Palencia~Pontevedra~Salamanca~Santa Cruz de Tenerife~Segovia~Sevilla~Soria~Tarragona~Teruel~Toledo~Valencia~Valladolid~Vizcaya~Zamora~Zaragoza", sub_zips: "01~02~03~04~33~05~06~08~09~10~11~39~12~51~13~14~16~17~18~19~20~21~22~07~23~15~26~35~24~25~27~28~29~52~30~31~32~34~36~37~38~40~41~42~43~44~45~46~47~48~49~50", upper: "CS", zip: "\\d{5}", zipex: "28039,28300,28070", }, "data/ES--ca": { fmt: "%N%n%O%n%A%n%Z %C %S", id: "data/ES--ca", key: "ES", lang: "ca", name: "SPAIN", posturl: "http://www.correos.es/contenido/13-MenuRec2/04-MenuRec24/1010_s-CodPostal.asp", require: "ACSZ", sub_keys: "A~AB~AL~VI~O~AV~BA~B~BI~BU~CC~CA~S~CS~CE~CR~CO~CU~GI~GR~GU~SS~H~HU~PM~J~C~LO~GC~LE~L~LU~M~MA~ML~MU~NA~OR~P~PO~SA~TF~SG~SE~SO~T~TE~TO~V~VA~ZA~Z", sub_names: "Alacant~Albacete~Almeria~Araba~Asturias~Àvila~Badajoz~Barcelona~Bizkaia~Burgos~Cáceres~Cadis~Cantabria~Castelló~Ceuta~Ciudad Real~Córdoba~Cuenca~Girona~Granada~Guadalajara~Guipúscoa~Huelva~Huesca~Illes Balears~Jaén~La Corunya~La Rioja~Las Palmas~León~Lleida~Lugo~Madrid~Málaga~Melilla~Murcia~Navarra~Ourense~Palencia~Pontevedra~Salamanca~Santa Cruz de Tenerife~Segovia~Sevilla~Soria~Tarragona~Teruel~Toledo~València~Valladolid~Zamora~Zaragoza", sub_zips: "03~02~04~01~33~05~06~08~48~09~10~11~39~12~51~13~14~16~17~18~19~20~21~22~07~23~15~26~35~24~25~27~28~29~52~30~31~32~34~36~37~38~40~41~42~43~44~45~46~47~49~50", upper: "CS", zip: "\\d{5}", zipex: "28039,28300,28070", }, "data/ES--eu": { fmt: "%N%n%O%n%A%n%Z %C %S", id: "data/ES--eu", key: "ES", lang: "eu", name: "SPAIN", posturl: "http://www.correos.es/contenido/13-MenuRec2/04-MenuRec24/1010_s-CodPostal.asp", require: "ACSZ", sub_keys: "A~AB~AL~VI~O~AV~BA~B~BI~BU~CC~CA~S~CS~CE~CR~C~CU~SS~GI~GR~GU~H~HU~PM~J~CO~LO~GC~LE~L~LU~M~MA~ML~MU~NA~OR~P~PO~SA~TF~SG~SE~SO~T~TE~TO~V~VA~ZA~Z", sub_names: "Alacant~Albacete~Almería~Araba~Asturias~Ávila~Badajoz~Barcelona~Bizkaia~Burgos~Cáceres~Cádiz~Cantabria~Castelló~Ceuta~Ciudad Real~Coruña~Cuenca~Gipuzkoa~Girona~Granada~Guadalajara~Huelva~Huesca~Illes Balears~Jaén~Kordoba~La Rioja~Las Palmas~León~Lleida~Lugo~Madrid~Málaga~Melilla~Murtzia~Nafarroa~Ourense~Palentzia~Pontevedra~Salamanca~Santa Cruz Tenerifekoa~Segovia~Sevilla~Soria~Tarragona~Teruel~Toledo~Valentzia~Valladolid~Zamora~Zaragoza", sub_zips: "03~02~04~01~33~05~06~08~48~09~10~11~39~12~51~13~15~16~20~17~18~19~21~22~07~23~14~26~35~24~25~27~28~29~52~30~31~32~34~36~37~38~40~41~42~43~44~45~46~47~49~50", upper: "CS", zip: "\\d{5}", zipex: "28039,28300,28070", }, "data/ES--gl": { fmt: "%N%n%O%n%A%n%Z %C %S", id: "data/ES--gl", key: "ES", lang: "gl", name: "SPAIN", posturl: "http://www.correos.es/contenido/13-MenuRec2/04-MenuRec24/1010_s-CodPostal.asp", require: "ACSZ", sub_keys: "C~A~VI~AB~AL~GC~O~AV~BA~B~BI~BU~CC~CA~S~CS~CE~CR~CO~CU~GR~GU~SS~H~HU~PM~LO~LE~L~LU~M~MA~ML~MU~NA~OR~P~PO~SA~TF~SG~SE~SO~T~TE~TO~V~VA~J~GI~ZA~Z", sub_names: "A Coruña~Alacant~Álava~Albacete~Almería~As Palmas~Asturias~Ávila~Badaxoz~Barcelona~Biscaia~Burgos~Cáceres~Cádiz~Cantabria~Castelló~Ceuta~Cidade Real~Córdoba~Cuenca~Granada~Guadalajara~Guipúscoa~Huelva~Huesca~Illas Baleares~La Rioja~León~Lleida~Lugo~Madrid~Málaga~Melilla~Murcia~Navarra~Ourense~Palencia~Pontevedra~Salamanca~Santa Cruz de Tenerife~Segovia~Sevilla~Soria~Tarragona~Teruel~Toledo~Valencia~Valladolid~Xaén~Xirona~Zamora~Zaragoza", sub_zips: "15~03~01~02~04~35~33~05~06~08~48~09~10~11~39~12~51~13~14~16~18~19~20~21~22~07~26~24~25~27~28~29~52~30~31~32~34~36~37~38~40~41~42~43~44~45~46~47~23~17~49~50", upper: "CS", zip: "\\d{5}", zipex: "28039,28300,28070", }, "data/ET": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/ET", key: "ET", name: "ETHIOPIA", zip: "\\d{4}", zipex: "1000", }, "data/FI": { fmt: "%O%n%N%n%A%nFI-%Z %C", id: "data/FI", key: "FI", name: "FINLAND", postprefix: "FI-", posturl: "http://www.verkkoposti.com/e3/postinumeroluettelo", require: "ACZ", zip: "\\d{5}", zipex: "00550,00011", }, "data/FJ": { id: "data/FJ", key: "FJ", name: "FIJI" }, "data/FK": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/FK", key: "FK", name: "FALKLAND ISLANDS (MALVINAS)", require: "ACZ", upper: "CZ", zip: "FIQQ 1ZZ", zipex: "FIQQ 1ZZ", }, "data/FM": { fmt: "%N%n%O%n%A%n%C %S %Z", id: "data/FM", key: "FM", name: "MICRONESIA (Federated State of)", posturl: "http://zip4.usps.com/zip4/welcome.jsp", require: "ACSZ", state_name_type: "state", upper: "ACNOS", zip: "(9694[1-4])(?:[ \\-](\\d{4}))?", zip_name_type: "zip", zipex: "96941,96944", }, "data/FO": { fmt: "%N%n%O%n%A%nFO%Z %C", id: "data/FO", key: "FO", name: "FAROE ISLANDS", postprefix: "FO", posturl: "http://www.postur.fo/", zip: "\\d{3}", zipex: "100", }, "data/FR": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/FR", key: "FR", name: "FRANCE", posturl: "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal", require: "ACZ", upper: "CX", zip: "\\d{2} ?\\d{3}", zipex: "33380,34092,33506", }, "data/GA": { id: "data/GA", key: "GA", name: "GABON" }, "data/GB": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/GB", key: "GB", locality_name_type: "post_town", name: "UNITED KINGDOM", posturl: "http://www.royalmail.com/postcode-finder", require: "ACZ", upper: "CZ", zip: "GIR ?0AA|(?:(?:AB|AL|B|BA|BB|BD|BF|BH|BL|BN|BR|BS|BT|BX|CA|CB|CF|CH|CM|CO|CR|CT|CV|CW|DA|DD|DE|DG|DH|DL|DN|DT|DY|E|EC|EH|EN|EX|FK|FY|G|GL|GY|GU|HA|HD|HG|HP|HR|HS|HU|HX|IG|IM|IP|IV|JE|KA|KT|KW|KY|L|LA|LD|LE|LL|LN|LS|LU|M|ME|MK|ML|N|NE|NG|NN|NP|NR|NW|OL|OX|PA|PE|PH|PL|PO|PR|RG|RH|RM|S|SA|SE|SG|SK|SL|SM|SN|SO|SP|SR|SS|ST|SW|SY|TA|TD|TF|TN|TQ|TR|TS|TW|UB|W|WA|WC|WD|WF|WN|WR|WS|WV|YO|ZE)(?:\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}))|BFPO ?\\d{1,4}", zipex: "EC1Y 8SY,GIR 0AA,M2 5BQ,M34 4AB,CR0 2YR,DN16 9AA,W1A 4ZZ,EC1A 1HQ,OX14 4PG,BS18 8HF,NR25 7HG,RH6 0NP,BH23 6AA,B6 5BA,SO23 9AP,PO1 3AX,BFPO 61", }, "data/GD": { id: "data/GD", key: "GD", name: "GRENADA (WEST INDIES)" }, "data/GE": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/GE", key: "GE", name: "GEORGIA", posturl: "http://www.georgianpost.ge/index.php?page=10", zip: "\\d{4}", zipex: "0101", }, "data/GF": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/GF", key: "GF", name: "FRENCH GUIANA", posturl: "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal", require: "ACZ", upper: "ACX", zip: "9[78]3\\d{2}", zipex: "97300", }, "data/GG": { fmt: "%N%n%O%n%A%n%C%nGUERNSEY%n%Z", id: "data/GG", key: "GG", name: "CHANNEL ISLANDS", posturl: "http://www.guernseypost.com/postcode_finder/", require: "ACZ", upper: "CZ", zip: "GY\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}", zipex: "GY1 1AA,GY2 2BT", }, "data/GH": { id: "data/GH", key: "GH", name: "GHANA" }, "data/GI": { fmt: "%N%n%O%n%A%nGIBRALTAR%n%Z", id: "data/GI", key: "GI", name: "GIBRALTAR", require: "A", zip: "GX11 1AA", zipex: "GX11 1AA", }, "data/GL": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/GL", key: "GL", name: "GREENLAND", require: "ACZ", zip: "39\\d{2}", zipex: "3900,3950,3911", }, "data/GM": { id: "data/GM", key: "GM", name: "GAMBIA" }, "data/GN": { fmt: "%N%n%O%n%Z %A %C", id: "data/GN", key: "GN", name: "GUINEA", zip: "\\d{3}", zipex: "001,200,100", }, "data/GP": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/GP", key: "GP", name: "GUADELOUPE", posturl: "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal", require: "ACZ", upper: "ACX", zip: "9[78][01]\\d{2}", zipex: "97100", }, "data/GQ": { id: "data/GQ", key: "GQ", name: "EQUATORIAL GUINEA" }, "data/GR": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/GR", key: "GR", name: "GREECE", posturl: "http://www.elta.gr/findapostcode.aspx", require: "ACZ", zip: "\\d{3} ?\\d{2}", zipex: "151 24,151 10,101 88", }, "data/GS": { fmt: "%N%n%O%n%A%n%n%C%n%Z", id: "data/GS", key: "GS", name: "SOUTH GEORGIA", require: "ACZ", upper: "CZ", zip: "SIQQ 1ZZ", zipex: "SIQQ 1ZZ", }, "data/GT": { fmt: "%N%n%O%n%A%n%Z- %C", id: "data/GT", key: "GT", name: "GUATEMALA", zip: "\\d{5}", zipex: "09001,01501", }, "data/GU": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/GU", key: "GU", name: "GUAM", posturl: "http://zip4.usps.com/zip4/welcome.jsp", require: "ACZ", upper: "ACNO", zip: "(969(?:[12]\\d|3[12]))(?:[ \\-](\\d{4}))?", zip_name_type: "zip", zipex: "96910,96931", }, "data/GW": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/GW", key: "GW", name: "GUINEA-BISSAU", zip: "\\d{4}", zipex: "1000,1011", }, "data/GY": { id: "data/GY", key: "GY", name: "GUYANA" }, "data/HK": { fmt: "%S%n%C%n%A%n%O%n%N", id: "data/HK", key: "HK", lang: "zh-Hant", languages: "zh-Hant~en", lfmt: "%N%n%O%n%A%n%C%n%S", locality_name_type: "district", name: "HONG KONG", require: "AS", state_name_type: "area", sub_keys: "Kowloon~Hong Kong Island~New Territories", sub_mores: "true~true~true", sub_names: "九龍~香港島~新界", upper: "S", }, "data/HK--en": { fmt: "%S%n%C%n%A%n%O%n%N", id: "data/HK--en", key: "HK", lang: "en", lfmt: "%N%n%O%n%A%n%C%n%S", locality_name_type: "district", name: "HONG KONG", require: "AS", state_name_type: "area", sub_keys: "Hong Kong Island~Kowloon~New Territories", sub_lnames: "Hong Kong Island~Kowloon~New Territories", sub_mores: "true~true~true", upper: "S", }, "data/HM": { fmt: "%O%n%N%n%A%n%C %S %Z", id: "data/HM", key: "HM", name: "HEARD AND MCDONALD ISLANDS", upper: "CS", zip: "\\d{4}", zipex: "7050", }, "data/HN": { fmt: "%N%n%O%n%A%n%C, %S%n%Z", id: "data/HN", key: "HN", name: "HONDURAS", require: "ACS", zip: "\\d{5}", zipex: "31301", }, "data/HR": { fmt: "%N%n%O%n%A%nHR-%Z %C", id: "data/HR", key: "HR", name: "CROATIA", postprefix: "HR-", posturl: "http://www.posta.hr/default.aspx?pretpum", zip: "\\d{5}", zipex: "10000,21001,10002", }, "data/HT": { fmt: "%N%n%O%n%A%nHT%Z %C", id: "data/HT", key: "HT", name: "HAITI", postprefix: "HT", zip: "\\d{4}", zipex: "6120,5310,6110,8510", }, "data/HU": { fmt: "%N%n%O%n%C%n%A%n%Z", id: "data/HU", key: "HU", name: "HUNGARY (Rep.)", posturl: "http://posta.hu/ugyfelszolgalat/iranyitoszam_kereso", require: "ACZ", upper: "ACNO", zip: "\\d{4}", zipex: "1037,2380,1540", }, "data/ID": { fmt: "%N%n%O%n%A%n%C%n%S %Z", id: "data/ID", key: "ID", lang: "id", languages: "id", name: "INDONESIA", require: "AS", sub_isoids: "AC~BA~BT~BE~YO~JK~GO~JA~JB~JT~JI~KB~KS~KT~KI~KU~BB~KR~LA~MA~MU~NB~NT~PA~PB~RI~SR~SN~ST~SG~SA~SB~SS~SU", sub_keys: "Aceh~Bali~Banten~Bengkulu~Daerah Istimewa Yogyakarta~DKI Jakarta~Gorontalo~Jambi~Jawa Barat~Jawa Tengah~Jawa Timur~Kalimantan Barat~Kalimantan Selatan~Kalimantan Tengah~Kalimantan Timur~Kalimantan Utara~Kepulauan Bangka Belitung~Kepulauan Riau~Lampung~Maluku~Maluku Utara~Nusa Tenggara Barat~Nusa Tenggara Timur~Papua~Papua Barat~Riau~Sulawesi Barat~Sulawesi Selatan~Sulawesi Tengah~Sulawesi Tenggara~Sulawesi Utara~Sumatera Barat~Sumatera Selatan~Sumatera Utara", zip: "\\d{5}", zipex: "40115", }, "data/IE": { fmt: "%N%n%O%n%A%n%D%n%C%n%S %Z", id: "data/IE", key: "IE", lang: "en", languages: "en", name: "IRELAND", posturl: "https://finder.eircode.ie", state_name_type: "county", sub_isoids: "CW~CN~CE~C~DL~D~G~KY~KE~KK~LS~LM~LK~LD~LH~MO~MH~MN~OY~RN~SO~TA~WD~WH~WX~WW", sub_keys: "Co. Carlow~Co. Cavan~Co. Clare~Co. Cork~Co. Donegal~Co. Dublin~Co. Galway~Co. Kerry~Co. Kildare~Co. Kilkenny~Co. Laois~Co. Leitrim~Co. Limerick~Co. Longford~Co. Louth~Co. Mayo~Co. Meath~Co. Monaghan~Co. Offaly~Co. Roscommon~Co. Sligo~Co. Tipperary~Co. Waterford~Co. Westmeath~Co. Wexford~Co. Wicklow", sublocality_name_type: "townland", zip: "[\\dA-Z]{3} ?[\\dA-Z]{4}", zip_name_type: "eircode", zipex: "A65 F4E2", }, "data/IL": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/IL", key: "IL", name: "ISRAEL", posturl: "http://www.israelpost.co.il/zipcode.nsf/demozip?openform", zip: "\\d{5}(?:\\d{2})?", zipex: "9614303", }, "data/IM": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/IM", key: "IM", name: "ISLE OF MAN", posturl: "https://www.iompost.com/tools-forms/postcode-finder/", require: "ACZ", upper: "CZ", zip: "IM\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}", zipex: "IM2 1AA,IM99 1PS", }, "data/IN": { fmt: "%N%n%O%n%A%n%C %Z%n%S", id: "data/IN", key: "IN", lang: "en", languages: "en~hi", name: "INDIA", posturl: "https://www.indiapost.gov.in/vas/pages/FindPinCode.aspx", require: "ACSZ", state_name_type: "state", sub_isoids: "AN~AP~AR~AS~BR~CH~CT~DN~DD~DL~GA~GJ~HR~HP~JK~JH~KA~KL~LD~MP~MH~MN~ML~MZ~NL~OR~PY~PB~RJ~SK~TN~TG~TR~UP~UT~WB", sub_keys: "Andaman and Nicobar Islands~Andhra Pradesh~Arunachal Pradesh~Assam~Bihar~Chandigarh~Chhattisgarh~Dadra and Nagar Haveli~Daman and Diu~Delhi~Goa~Gujarat~Haryana~Himachal Pradesh~Jammu and Kashmir~Jharkhand~Karnataka~Kerala~Lakshadweep~Madhya Pradesh~Maharashtra~Manipur~Meghalaya~Mizoram~Nagaland~Odisha~Puducherry~Punjab~Rajasthan~Sikkim~Tamil Nadu~Telangana~Tripura~Uttar Pradesh~Uttarakhand~West Bengal", sub_names: "Andaman & Nicobar~Andhra Pradesh~Arunachal Pradesh~Assam~Bihar~Chandigarh~Chhattisgarh~Dadra & Nagar Haveli~Daman & Diu~Delhi~Goa~Gujarat~Haryana~Himachal Pradesh~Jammu & Kashmir~Jharkhand~Karnataka~Kerala~Lakshadweep~Madhya Pradesh~Maharashtra~Manipur~Meghalaya~Mizoram~Nagaland~Odisha~Puducherry~Punjab~Rajasthan~Sikkim~Tamil Nadu~Telangana~Tripura~Uttar Pradesh~Uttarakhand~West Bengal", sub_zips: "744~5[0-3]~79[0-2]~78~8[0-5]~16|1440[3-9]~49~396~396~11~403~3[6-9]~1[23]~17~1[89]~81[4-9]|82|83[0-5]~5[4-9]|53[7-9]~6[7-9]|6010|607008|777~682~4[5-8]|490~4[0-4]~79[56]~79[34]~796~79[78]~7[5-7]~60[579]~1[456]~3[0-4]~737|750~6[0-6]|536~5[0-3]~799~2[0-35-8]|24[0-7]|26[12]~24[46-9]|254|26[23]~7[0-4]", zip: "\\d{6}", zip_name_type: "pin", zipex: "110034,110001", }, "data/IN--hi": { fmt: "%N%n%O%n%A%n%C %Z%n%S", id: "data/IN--hi", key: "IN", lang: "hi", name: "INDIA", posturl: "https://www.indiapost.gov.in/vas/pages/FindPinCode.aspx", require: "ACSZ", state_name_type: "state", sub_isoids: "AN~AR~AS~AP~UP~UT~OR~KA~KL~GJ~GA~CH~CT~JK~JH~TN~TG~TR~DD~DN~DL~NL~PB~WB~PY~BR~MN~MP~MH~MZ~ML~RJ~LD~SK~HR~HP", sub_keys: "Andaman & Nicobar~Arunachal Pradesh~Assam~Andhra Pradesh~Uttar Pradesh~Uttarakhand~Odisha~Karnataka~Kerala~Gujarat~Goa~Chandigarh~Chhattisgarh~Jammu & Kashmir~Jharkhand~Tamil Nadu~Telangana~Tripura~Daman & Diu~Dadra & Nagar Haveli~Delhi~Nagaland~Punjab~West Bengal~Puducherry~Bihar~Manipur~Madhya Pradesh~Maharashtra~Mizoram~Meghalaya~Rajasthan~Lakshadweep~Sikkim~Haryana~Himachal Pradesh", sub_names: "अंडमान और निकोबार द्वीपसमूह~अरुणाचल प्रदेश~असम~आंध्र प्रदेश~उत्तर प्रदेश~उत्तराखण्ड~ओड़िशा~कर्नाटक~केरल~गुजरात~गोआ~चंडीगढ़~छत्तीसगढ़~जम्मू और कश्मीर~झारखण्ड~तमिल नाडु~तेलंगाना~त्रिपुरा~दमन और दीव~दादरा और नगर हवेली~दिल्ली~नागालैंड~पंजाब~पश्चिम बंगाल~पांडिचेरी~बिहार~मणिपुर~मध्य प्रदेश~महाराष्ट्र~मिजोरम~मेघालय~राजस्थान~लक्षद्वीप~सिक्किम~हरियाणा~हिमाचल प्रदेश", sub_zips: "744~79[0-2]~78~5[0-3]~2[0-35-8]|24[0-7]|26[12]~24[46-9]|254|26[23]~7[5-7]~5[4-9]|53[7-9]~6[7-9]|6010|607008|777~3[6-9]~403~16|1440[3-9]~49~1[89]~81[4-9]|82|83[0-5]~6[0-6]|536~5[0-3]~799~396~396~11~79[78]~1[456]~7[0-4]~60[579]~8[0-5]~79[56]~4[5-8]|490~4[0-4]~796~79[34]~3[0-4]~682~737|750~1[23]~17", zip: "\\d{6}", zip_name_type: "pin", zipex: "110034,110001", }, "data/IO": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/IO", key: "IO", name: "BRITISH INDIAN OCEAN TERRITORY", require: "ACZ", upper: "CZ", zip: "BBND 1ZZ", zipex: "BBND 1ZZ", }, "data/IQ": { fmt: "%O%n%N%n%A%n%C, %S%n%Z", id: "data/IQ", key: "IQ", name: "IRAQ", require: "ACS", upper: "CS", zip: "\\d{5}", zipex: "31001", }, "data/IR": { fmt: "%O%n%N%n%S%n%C, %D%n%A%n%Z", id: "data/IR", key: "IR", lang: "fa", languages: "fa", name: "IRAN", sub_isoids: "01~02~03~04~32~05~06~07~08~29~30~31~10~11~12~13~14~28~26~16~15~17~18~27~19~20~21~22~23~24~25", sub_keys: "استان آذربایجان شرقی~استان آذربایجان غربی~استان اردبیل~استان اصفهان~استان البرز~استان ایلام~استان بوشهر~استان تهران~استان چهارمحال و بختیاری~استان خراسان جنوبی~استان خراسان رضوی~استان خراسان شمالی~استان خوزستان~استان زنجان~استان سمنان~استان سیستان و بلوچستان~استان فارس~استان قزوین~استان قم~استان کردستان~استان کرمان~استان کرمانشاه~استان کهگیلویه و بویراحمد~استان گلستان~استان گیلان~استان لرستان~استان مازندران~استان مرکزی~استان هرمزگان~استان همدان~استان یزد", sub_lnames: "East Azerbaijan Province~West Azerbaijan Province~Ardabil Province~Isfahan Province~Alborz Province~Ilam Province~Bushehr Province~Tehran Province~Chaharmahal and Bakhtiari Province~South Khorasan Province~Razavi Khorasan Province~North Khorasan Province~Khuzestan Province~Zanjan Province~Semnan Province~Sistan and Baluchestan Province~Fars Province~Qazvin Province~Qom Province~Kurdistan Province~Kerman Province~Kermanshah Province~Kohgiluyeh and Boyer-Ahmad Province~Golestan Province~Gilan Province~Lorestan Province~Mazandaran Province~Markazi Province~Hormozgan Province~Hamadan Province~Yazd Province", sublocality_name_type: "neighborhood", zip: "\\d{5}-?\\d{5}", zipex: "11936-12345", }, "data/IS": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/IS", key: "IS", name: "ICELAND", posturl: "http://www.postur.is/einstaklingar/posthus/postnumer/", zip: "\\d{3}", zipex: "320,121,220,110", }, "data/IT": { fmt: "%N%n%O%n%A%n%Z %C %S", id: "data/IT", key: "IT", lang: "it", languages: "it", name: "ITALY", posturl: "http://www.poste.it/online/cercacap/", require: "ACSZ", sub_isoids: "AG~AL~AN~AO~AR~AP~AT~AV~BA~BT~BL~BN~BG~BI~BO~BZ~BS~BR~CA~CL~CB~CI~CE~CT~CZ~CH~CO~CS~CR~KR~CN~EN~FM~FE~FI~FG~FC~FR~GE~GO~GR~IM~IS~AQ~SP~LT~LE~LC~LI~LO~LU~MC~MN~MS~MT~VS~ME~MI~MO~MB~NA~NO~NU~OG~OT~OR~PD~PA~PR~PV~PG~PU~PE~PC~PI~PT~PN~PZ~PO~RG~RA~RC~RE~RI~RN~RM~RO~SA~SS~SV~SI~SR~SO~TA~TE~TR~TO~TP~TN~TV~TS~UD~VA~VE~VB~VC~VR~VV~VI~VT", sub_keys: "AG~AL~AN~AO~AR~AP~AT~AV~BA~BT~BL~BN~BG~BI~BO~BZ~BS~BR~CA~CL~CB~CI~CE~CT~CZ~CH~CO~CS~CR~KR~CN~EN~FM~FE~FI~FG~FC~FR~GE~GO~GR~IM~IS~AQ~SP~LT~LE~LC~LI~LO~LU~MC~MN~MS~MT~VS~ME~MI~MO~MB~NA~NO~NU~OG~OT~OR~PD~PA~PR~PV~PG~PU~PE~PC~PI~PT~PN~PZ~PO~RG~RA~RC~RE~RI~RN~RM~RO~SA~SS~SV~SI~SR~SO~TA~TE~TR~TO~TP~TN~TV~TS~UD~VA~VE~VB~VC~VR~VV~VI~VT", sub_names: "Agrigento~Alessandria~Ancona~Aosta~Arezzo~Ascoli Piceno~Asti~Avellino~Bari~Barletta-Andria-Trani~Belluno~Benevento~Bergamo~Biella~Bologna~Bolzano~Brescia~Brindisi~Cagliari~Caltanissetta~Campobasso~Carbonia-Iglesias~Caserta~Catania~Catanzaro~Chieti~Como~Cosenza~Cremona~Crotone~Cuneo~Enna~Fermo~Ferrara~Firenze~Foggia~Forlì-Cesena~Frosinone~Genova~Gorizia~Grosseto~Imperia~Isernia~L'Aquila~La Spezia~Latina~Lecce~Lecco~Livorno~Lodi~Lucca~Macerata~Mantova~Massa-Carrara~Matera~Medio Campidano~Messina~Milano~Modena~Monza e Brianza~Napoli~Novara~Nuoro~Ogliastra~Olbia-Tempio~Oristano~Padova~Palermo~Parma~Pavia~Perugia~Pesaro e Urbino~Pescara~Piacenza~Pisa~Pistoia~Pordenone~Potenza~Prato~Ragusa~Ravenna~Reggio Calabria~Reggio Emilia~Rieti~Rimini~Roma~Rovigo~Salerno~Sassari~Savona~Siena~Siracusa~Sondrio~Taranto~Teramo~Terni~Torino~Trapani~Trento~Treviso~Trieste~Udine~Varese~Venezia~Verbano-Cusio-Ossola~Vercelli~Verona~Vibo Valentia~Vicenza~Viterbo", sub_zips: "92~15~60~11~52~63~14~83~70~76[01]~32~82~24~13[89]~40~39~25~72~0912[1-9]|0913[0-4]|0901[0289]|0902[03468]|0903[0234]|0904|0803[035]|08043~93~860[1-4]|86100~0901[013-7]~81~95~88[01]~66~22~87~26[01]~88[89]~12|18025~94~638|63900~44~50~71~47[015]~03~16~34[01]7~58~18~860[7-9]|86170~67~19~04~73~23[89]~57~26[89]~55~62~46~54~75~0902[012579]|0903[015-9]|09040~98~20~41~208|20900~80~28[01]~080[1-3]|08100~08037|0804[024-9]~08020|0702|0703[08]~090[7-9]|09170|0801[039]|0803[04]~35~90~43~27~06~61~65~29~56~51~330[7-9]|33170~85~59~97~48~89[01]~42~02~47[89]~00~45~84~070[14]|0703[0-79]|07100~17|12071~53~96~23[01]~74~64~05~10~91~38~31~3401|341[0-689]|34062~330[1-5]|33100~21~30~28[89]~13[01]~37~89[89]~36~01", upper: "CS", zip: "\\d{5}", zipex: "00144,47037,39049", }, "data/JE": { fmt: "%N%n%O%n%A%n%C%nJERSEY%n%Z", id: "data/JE", key: "JE", name: "CHANNEL ISLANDS", posturl: "http://www.jerseypost.com/tools/postcode-address-finder/", require: "ACZ", upper: "CZ", zip: "JE\\d[\\dA-Z]? ?\\d[ABD-HJLN-UW-Z]{2}", zipex: "JE1 1AA,JE2 2BT", }, "data/JM": { fmt: "%N%n%O%n%A%n%C%n%S %X", id: "data/JM", key: "JM", lang: "en", languages: "en", name: "JAMAICA", require: "ACS", state_name_type: "parish", sub_isoids: "13~09~01~12~04~02~06~14~11~08~05~03~07~10", sub_keys: "Clarendon~Hanover~Kingston~Manchester~Portland~St. Andrew~St. Ann~St. Catherine~St. Elizabeth~St. James~St. Mary~St. Thomas~Trelawny~Westmoreland", }, "data/JO": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/JO", key: "JO", name: "JORDAN", zip: "\\d{5}", zipex: "11937,11190", }, "data/JP": { fmt: "〒%Z%n%S%n%A%n%O%n%N", id: "data/JP", key: "JP", lang: "ja", languages: "ja", lfmt: "%N%n%O%n%A, %S%n%Z", name: "JAPAN", posturl: "http://www.post.japanpost.jp/zipcode/", require: "ASZ", state_name_type: "prefecture", sub_isoids: "01~02~03~04~05~06~07~08~09~10~11~12~13~14~15~16~17~18~19~20~21~22~23~24~25~26~27~28~29~30~31~32~33~34~35~36~37~38~39~40~41~42~43~44~45~46~47", sub_keys: "北海道~青森県~岩手県~宮城県~秋田県~山形県~福島県~茨城県~栃木県~群馬県~埼玉県~千葉県~東京都~神奈川県~新潟県~富山県~石川県~福井県~山梨県~長野県~岐阜県~静岡県~愛知県~三重県~滋賀県~京都府~大阪府~兵庫県~奈良県~和歌山県~鳥取県~島根県~岡山県~広島県~山口県~徳島県~香川県~愛媛県~高知県~福岡県~佐賀県~長崎県~熊本県~大分県~宮崎県~鹿児島県~沖縄県", sub_lnames: "Hokkaido~Aomori~Iwate~Miyagi~Akita~Yamagata~Fukushima~Ibaraki~Tochigi~Gunma~Saitama~Chiba~Tokyo~Kanagawa~Niigata~Toyama~Ishikawa~Fukui~Yamanashi~Nagano~Gifu~Shizuoka~Aichi~Mie~Shiga~Kyoto~Osaka~Hyogo~Nara~Wakayama~Tottori~Shimane~Okayama~Hiroshima~Yamaguchi~Tokushima~Kagawa~Ehime~Kochi~Fukuoka~Saga~Nagasaki~Kumamoto~Oita~Miyazaki~Kagoshima~Okinawa", sub_zips: "0[4-9]|00[1-7]~03|018~02~98~01~99~9[67]~3[01]~32|311|349~37|38[49]~3[3-6]~2[6-9]~1[0-8]|19[0-8]|20~2[1-5]|199~9[45]|389~93~92|939~91|922~40~3[89]|949~50~4[1-9]~4[4-9]|431~51|498|647~52~6[0-2]|520~5[3-9]|618|630~6[5-7]|563~63|64[78]~64|519~68~69|68[45]~7[01]~7[23]~7[45]~77~76~79~78~8[0-3]|871~84~85|81[17]|848~86~87|839~88~89~90", upper: "S", zip: "\\d{3}-?\\d{4}", zipex: "154-0023,350-1106,951-8073,112-0001,208-0032,231-0012", }, "data/KE": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/KE", key: "KE", name: "KENYA", zip: "\\d{5}", zipex: "20100,00100", }, "data/KG": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/KG", key: "KG", name: "KYRGYZSTAN", zip: "\\d{6}", zipex: "720001", }, "data/KH": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/KH", key: "KH", name: "CAMBODIA", zip: "\\d{5}", zipex: "12203,14206,12000", }, "data/KI": { fmt: "%N%n%O%n%A%n%S%n%C", id: "data/KI", key: "KI", name: "KIRIBATI", state_name_type: "island", upper: "ACNOS", }, "data/KM": { id: "data/KM", key: "KM", name: "COMOROS", upper: "AC" }, "data/KN": { fmt: "%N%n%O%n%A%n%C, %S", id: "data/KN", key: "KN", lang: "en", languages: "en", name: "SAINT KITTS AND NEVIS", require: "ACS", state_name_type: "island", sub_isoids: "N~K", sub_keys: "Nevis~St. Kitts", }, "data/KP": { fmt: "%Z%n%S%n%C%n%A%n%O%n%N", id: "data/KP", key: "KP", lang: "ko", languages: "ko", lfmt: "%N%n%O%n%A%n%C%n%S, %Z", name: "NORTH KOREA", sub_isoids: "07~13~10~04~02~03~01~08~09~05~06", sub_keys: "강원도~라선 특별시~량강도~자강도~평안 남도~평안 북도~평양 직할시~함경 남도~함경 북도~황해남도~황해북도", sub_lnames: "Kangwon~Rason~Ryanggang~Chagang~South Pyongan~North Pyongan~Pyongyang~South Hamgyong~North Hamgyong~South Hwanghae~North Hwanghae", }, "data/KR": { fmt: "%S %C%D%n%A%n%O%n%N%n%Z", id: "data/KR", key: "KR", lang: "ko", languages: "ko", lfmt: "%N%n%O%n%A%n%D%n%C%n%S%n%Z", name: "SOUTH KOREA", posturl: "http://www.epost.go.kr/search/zipcode/search5.jsp", require: "ACSZ", state_name_type: "do_si", sub_isoids: "42~41~48~47~29~27~30~26~11~50~31~28~46~45~49~44~43", sub_keys: "강원도~경기도~경상남도~경상북도~광주광역시~대구광역시~대전광역시~부산광역시~서울특별시~세종특별자치시~울산광역시~인천광역시~전라남도~전라북도~제주특별자치도~충청남도~충청북도", sub_lnames: "Gangwon-do~Gyeonggi-do~Gyeongsangnam-do~Gyeongsangbuk-do~Gwangju~Daegu~Daejeon~Busan~Seoul~Sejong~Ulsan~Incheon~Jeollanam-do~Jeollabuk-do~Jeju-do~Chungcheongnam-do~Chungcheongbuk-do", sub_mores: "true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true", sub_names: "강원~경기~경남~경북~광주~대구~대전~부산~서울~세종~울산~인천~전남~전북~제주~충남~충북", sub_zipexs: "25627~12410~53286~38540~62394~42456~34316~46706~06321~30065~44782~23024~59222~56445~63563~32832~28006", sub_zips: "2[456]\\d{2}~1[0-8]\\d{2}~5[0-3]\\d{2}~(?:3[6-9]|40)\\d{2}~6[12]\\d{2}~4[12]\\d{2}~3[45]\\d{2}~4[6-9]\\d{2}~0[1-8]\\d{2}~30[01]\\d~4[45]\\d{2}~2[1-3]\\d{2}~5[7-9]\\d{2}~5[4-6]\\d{2}~63[0-356]\\d~3[1-3]\\d{2}~2[789]\\d{2}", sublocality_name_type: "district", upper: "Z", zip: "\\d{5}", zipex: "03051", }, "data/KW": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/KW", key: "KW", name: "KUWAIT", zip: "\\d{5}", zipex: "54541,54551,54404,13009", }, "data/KY": { fmt: "%N%n%O%n%A%n%S %Z", id: "data/KY", key: "KY", lang: "en", languages: "en", name: "CAYMAN ISLANDS", posturl: "http://www.caymanpost.gov.ky/", require: "AS", state_name_type: "island", sub_keys: "Cayman Brac~Grand Cayman~Little Cayman", zip: "KY\\d-\\d{4}", zipex: "KY1-1100,KY1-1702,KY2-2101", }, "data/KZ": { fmt: "%Z%n%S%n%C%n%A%n%O%n%N", id: "data/KZ", key: "KZ", name: "KAZAKHSTAN", zip: "\\d{6}", zipex: "040900,050012", }, "data/LA": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/LA", key: "LA", name: "LAO (PEOPLE'S DEM. REP.)", zip: "\\d{5}", zipex: "01160,01000", }, "data/LB": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/LB", key: "LB", name: "LEBANON", zip: "(?:\\d{4})(?: ?(?:\\d{4}))?", zipex: "2038 3054,1107 2810,1000", }, "data/LC": { id: "data/LC", key: "LC", name: "SAINT LUCIA" }, "data/LI": { fmt: "%O%n%N%n%A%nFL-%Z %C", id: "data/LI", key: "LI", name: "LIECHTENSTEIN", postprefix: "FL-", posturl: "http://www.post.ch/db/owa/pv_plz_pack/pr_main", require: "ACZ", zip: "948[5-9]|949[0-8]", zipex: "9496,9491,9490,9485", }, "data/LK": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/LK", key: "LK", name: "SRI LANKA", posturl: "http://www.slpost.gov.lk/", zip: "\\d{5}", zipex: "20000,00100", }, "data/LR": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/LR", key: "LR", name: "LIBERIA", zip: "\\d{4}", zipex: "1000", }, "data/LS": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/LS", key: "LS", name: "LESOTHO", zip: "\\d{3}", zipex: "100", }, "data/LT": { fmt: "%O%n%N%n%A%nLT-%Z %C", id: "data/LT", key: "LT", name: "LITHUANIA", postprefix: "LT-", posturl: "http://www.post.lt/lt/?id=316", zip: "\\d{5}", zipex: "04340,03500", }, "data/LU": { fmt: "%O%n%N%n%A%nL-%Z %C", id: "data/LU", key: "LU", name: "LUXEMBOURG", postprefix: "L-", posturl: "https://www.post.lu/fr/grandes-entreprises/solutions-postales/rechercher-un-code-postal", require: "ACZ", zip: "\\d{4}", zipex: "4750,2998", }, "data/LV": { fmt: "%N%n%O%n%A%n%C, %Z", id: "data/LV", key: "LV", name: "LATVIA", posturl: "http://www.pasts.lv/lv/uzzinas/nodalas/", zip: "LV-\\d{4}", zipex: "LV-1073,LV-1000", }, "data/LY": { id: "data/LY", key: "LY", name: "LIBYA" }, "data/MA": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/MA", key: "MA", name: "MOROCCO", zip: "\\d{5}", zipex: "53000,10000,20050,16052", }, "data/MC": { fmt: "%N%n%O%n%A%nMC-%Z %C %X", id: "data/MC", key: "MC", name: "MONACO", postprefix: "MC-", zip: "980\\d{2}", zipex: "98000,98020,98011,98001", }, "data/MD": { fmt: "%N%n%O%n%A%nMD-%Z %C", id: "data/MD", key: "MD", name: "Rep. MOLDOVA", postprefix: "MD-", zip: "\\d{4}", zipex: "2012,2019", }, "data/ME": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/ME", key: "ME", name: "MONTENEGRO", zip: "8\\d{4}", zipex: "81257,81258,81217,84314,85366", }, "data/MF": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/MF", key: "MF", name: "SAINT MARTIN", posturl: "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal", require: "ACZ", upper: "ACX", zip: "9[78][01]\\d{2}", zipex: "97100", }, "data/MG": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/MG", key: "MG", name: "MADAGASCAR", zip: "\\d{3}", zipex: "501,101", }, "data/MH": { fmt: "%N%n%O%n%A%n%C %S %Z", id: "data/MH", key: "MH", name: "MARSHALL ISLANDS", posturl: "http://zip4.usps.com/zip4/welcome.jsp", require: "ACSZ", state_name_type: "state", upper: "ACNOS", zip: "(969[67]\\d)(?:[ \\-](\\d{4}))?", zip_name_type: "zip", zipex: "96960,96970", }, "data/MK": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/MK", key: "MK", name: "MACEDONIA", zip: "\\d{4}", zipex: "1314,1321,1443,1062", }, "data/ML": { id: "data/ML", key: "ML", name: "MALI" }, "data/MM": { fmt: "%N%n%O%n%A%n%C, %Z", id: "data/MM", key: "MM", name: "MYANMAR", zip: "\\d{5}", zipex: "11181", }, "data/MN": { fmt: "%N%n%O%n%A%n%C%n%S %Z", id: "data/MN", key: "MN", name: "MONGOLIA", posturl: "http://www.zipcode.mn/", zip: "\\d{5}", zipex: "65030,65270", }, "data/MO": { fmt: "%A%n%O%n%N", id: "data/MO", key: "MO", lfmt: "%N%n%O%n%A", name: "MACAO", require: "A", }, "data/MP": { fmt: "%N%n%O%n%A%n%C %S %Z", id: "data/MP", key: "MP", name: "NORTHERN MARIANA ISLANDS", posturl: "http://zip4.usps.com/zip4/welcome.jsp", require: "ACSZ", state_name_type: "state", upper: "ACNOS", zip: "(9695[012])(?:[ \\-](\\d{4}))?", zip_name_type: "zip", zipex: "96950,96951,96952", }, "data/MQ": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/MQ", key: "MQ", name: "MARTINIQUE", posturl: "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal", require: "ACZ", upper: "ACX", zip: "9[78]2\\d{2}", zipex: "97220", }, "data/MR": { id: "data/MR", key: "MR", name: "MAURITANIA", upper: "AC" }, "data/MS": { id: "data/MS", key: "MS", name: "MONTSERRAT" }, "data/MT": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/MT", key: "MT", name: "MALTA", posturl: "http://postcodes.maltapost.com/", upper: "CZ", zip: "[A-Z]{3} ?\\d{2,4}", zipex: "NXR 01,ZTN 05,GPO 01,BZN 1130,SPB 6031,VCT 1753", }, "data/MU": { fmt: "%N%n%O%n%A%n%Z%n%C", id: "data/MU", key: "MU", name: "MAURITIUS", upper: "CZ", zip: "\\d{3}(?:\\d{2}|[A-Z]{2}\\d{3})", zipex: "42602", }, "data/MV": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/MV", key: "MV", name: "MALDIVES", posturl: "http://www.maldivespost.com/?lid=10", zip: "\\d{5}", zipex: "20026", }, "data/MW": { fmt: "%N%n%O%n%A%n%C %X", id: "data/MW", key: "MW", name: "MALAWI", }, "data/MX": { fmt: "%N%n%O%n%A%n%D%n%Z %C, %S", id: "data/MX", key: "MX", lang: "es", languages: "es", name: "MEXICO", posturl: "http://www.correosdemexico.gob.mx/ServiciosLinea/Paginas/ccpostales.aspx", require: "ACZ", state_name_type: "state", sub_isoids: "AGU~BCN~BCS~CAM~CHP~CHH~CMX~COA~COL~DUR~MEX~GUA~GRO~HID~JAL~MIC~MOR~NAY~NLE~OAX~PUE~QUE~ROO~SLP~SIN~SON~TAB~TAM~TLA~VER~YUC~ZAC", sub_keys: "Ags.~B.C.~B.C.S.~Camp.~Chis.~Chih.~CDMX~Coah.~Col.~Dgo.~Méx.~Gto.~Gro.~Hgo.~Jal.~Mich.~Mor.~Nay.~N.L.~Oax.~Pue.~Qro.~Q.R.~S.L.P.~Sin.~Son.~Tab.~Tamps.~Tlax.~Ver.~Yuc.~Zac.", sub_names: "Aguascalientes~Baja California~Baja California Sur~Campeche~Chiapas~Chihuahua~Ciudad de México~Coahuila de Zaragoza~Colima~Durango~Estado de México~Guanajuato~Guerrero~Hidalgo~Jalisco~Michoacán~Morelos~Nayarit~Nuevo León~Oaxaca~Puebla~Querétaro~Quintana Roo~San Luis Potosí~Sinaloa~Sonora~Tabasco~Tamaulipas~Tlaxcala~Veracruz~Yucatán~Zacatecas", sub_zipexs: "20000,20999~21000,22999~23000,23999~24000,24999~29000,30999~31000,33999~00000,16999~25000,27999~28000,28999~34000,35999~50000,57999~36000,38999~39000,41999~42000,43999~44000,49999~58000,61999~62000,62999~63000,63999~64000,67999~68000,71999~72000,75999~76000,76999~77000,77999~78000,79999~80000,82999~83000,85999~86000,86999~87000,89999~90000,90999~91000,96999~97000,97999~98000,99999", sub_zips: "20~2[12]~23~24~29|30~3[1-3]~0|1[0-6]~2[5-7]~28~3[45]~5[0-7]~3[6-8]~39|4[01]~4[23]~4[4-9]~5[89]|6[01]~62~63~6[4-7]~6[89]|7[01]~7[2-5]~76~77~7[89]~8[0-2]~8[3-5]~86~8[7-9]~90~9[1-6]~97~9[89]", sublocality_name_type: "neighborhood", upper: "CSZ", zip: "\\d{5}", zipex: "02860,77520,06082", }, "data/MY": { fmt: "%N%n%O%n%A%n%D%n%Z %C%n%S", id: "data/MY", key: "MY", lang: "ms", languages: "ms", name: "MALAYSIA", posturl: "http://www.pos.com.my", require: "ACZ", state_name_type: "state", sub_isoids: "01~02~03~14~15~04~05~06~08~09~07~16~12~13~10~11", sub_keys: "Johor~Kedah~Kelantan~Kuala Lumpur~Labuan~Melaka~Negeri Sembilan~Pahang~Perak~Perlis~Pulau Pinang~Putrajaya~Sabah~Sarawak~Selangor~Terengganu", sub_zipexs: "79000,86999~05000,09999,34950~15000,18599~50000,60000~87000,87999~75000,78399~70000,73599~25000,28999,39000,49000,69000~30000,36899,39000~01000,02799~10000,14999~62000,62999~88000,91999~93000,98999~40000,48999,63000,68199~20000,24999", sub_zips: "79|8[0-6]~0[5-9]|34950~1[5-9]~5|60~87~7[5-8]~7[0-4]~2[5-8]|[346]9~3[0-6]|39000~0[12]~1[0-4]~62~8[89]|9[01]~9[3-8]~4[0-8]|6[3-8]~2[0-4]", sublocality_name_type: "village_township", upper: "CS", zip: "\\d{5}", zipex: "43000,50754,88990,50670", }, "data/MZ": { fmt: "%N%n%O%n%A%n%Z %C%S", id: "data/MZ", key: "MZ", lang: "pt", languages: "pt", name: "MOZAMBIQUE", sub_isoids: "P~MPM~G~I~B~L~N~A~S~T~Q", sub_keys: "Cabo Delgado~Cidade de Maputo~Gaza~Inhambane~Manica~Maputo~Nampula~Niassa~Sofala~Tete~Zambezia", zip: "\\d{4}", zipex: "1102,1119,3212", }, "data/NA": { id: "data/NA", key: "NA", name: "NAMIBIA" }, "data/NC": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/NC", key: "NC", name: "NEW CALEDONIA", posturl: "http://poste.opt.nc/index.php?option=com_content&view=article&id=80&Itemid=131", require: "ACZ", upper: "ACX", zip: "988\\d{2}", zipex: "98814,98800,98810", }, "data/NE": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/NE", key: "NE", name: "NIGER", zip: "\\d{4}", zipex: "8001", }, "data/NF": { fmt: "%O%n%N%n%A%n%C %S %Z", id: "data/NF", key: "NF", name: "NORFOLK ISLAND", upper: "CS", zip: "2899", zipex: "2899", }, "data/NG": { fmt: "%N%n%O%n%A%n%D%n%C %Z%n%S", id: "data/NG", key: "NG", lang: "en", languages: "en", name: "NIGERIA", posturl: "http://www.nigeriapostcodes.com/", state_name_type: "state", sub_isoids: "AB~AD~AK~AN~BA~BY~BE~BO~CR~DE~EB~ED~EK~EN~FC~GO~IM~JI~KD~KN~KT~KE~KO~KW~LA~NA~NI~OG~ON~OS~OY~PL~RI~SO~TA~YO~ZA", sub_keys: "Abia~Adamawa~Akwa Ibom~Anambra~Bauchi~Bayelsa~Benue~Borno~Cross River~Delta~Ebonyi~Edo~Ekiti~Enugu~Federal Capital Territory~Gombe~Imo~Jigawa~Kaduna~Kano~Katsina~Kebbi~Kogi~Kwara~Lagos~Nasarawa~Niger~Ogun State~Ondo~Osun~Oyo~Plateau~Rivers~Sokoto~Taraba~Yobe~Zamfara", upper: "CS", zip: "\\d{6}", zipex: "930283,300001,931104", }, "data/NI": { fmt: "%N%n%O%n%A%n%Z%n%C, %S", id: "data/NI", key: "NI", lang: "es", languages: "es", name: "NICARAGUA", posturl: "http://www.correos.gob.ni/index.php/codigo-postal-2", state_name_type: "department", sub_isoids: "BO~CA~CI~CO~ES~GR~JI~LE~MD~MN~MS~MT~NS~AN~AS~SJ~RI", sub_keys: "Boaco~Carazo~Chinandega~Chontales~Esteli~Granada~Jinotega~Leon~Madriz~Managua~Masaya~Matagalpa~Nueva Segovia~Raan~Raas~Rio San Juan~Rivas", sub_zips: "5[12]~4[56]~2[5-7]~5[56]~3[12]~4[34]~6[56]~2[12]~3[45]~1[0-6]~4[12]~6[1-3]~3[7-9]~7[12]~8[1-3]~9[12]~4[78]", upper: "CS", zip: "\\d{5}", zipex: "52000", }, "data/NL": { fmt: "%O%n%N%n%A%n%Z %C", id: "data/NL", key: "NL", name: "NETHERLANDS", posturl: "http://www.postnl.nl/voorthuis/", require: "ACZ", zip: "\\d{4} ?[A-Z]{2}", zipex: "1234 AB,2490 AA", }, "data/NO": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/NO", key: "NO", locality_name_type: "post_town", name: "NORWAY", posturl: "http://adressesok.posten.no/nb/postal_codes/search", require: "ACZ", zip: "\\d{4}", zipex: "0025,0107,6631", }, "data/NP": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/NP", key: "NP", name: "NEPAL", posturl: "http://www.gpo.gov.np/Home/Postalcode", zip: "\\d{5}", zipex: "44601", }, "data/NR": { fmt: "%N%n%O%n%A%n%S", id: "data/NR", key: "NR", lang: "en", languages: "en", name: "NAURU CENTRAL PACIFIC", require: "AS", state_name_type: "district", sub_isoids: "01~02~03~04~05~06~07~08~09~10~11~12~13~14", sub_keys: "Aiwo District~Anabar District~Anetan District~Anibare District~Baiti District~Boe District~Buada District~Denigomodu District~Ewa District~Ijuw District~Meneng District~Nibok District~Uaboe District~Yaren District", }, "data/NU": { id: "data/NU", key: "NU", name: "NIUE" }, "data/NZ": { fmt: "%N%n%O%n%A%n%D%n%C %Z", id: "data/NZ", key: "NZ", name: "NEW ZEALAND", posturl: "http://www.nzpost.co.nz/Cultures/en-NZ/OnlineTools/PostCodeFinder/", require: "ACZ", zip: "\\d{4}", zipex: "6001,6015,6332,8252,1030", }, "data/OM": { fmt: "%N%n%O%n%A%n%Z%n%C", id: "data/OM", key: "OM", name: "OMAN", zip: "(?:PC )?\\d{3}", zipex: "133,112,111", }, "data/PA": { fmt: "%N%n%O%n%A%n%C%n%S", id: "data/PA", key: "PA", name: "PANAMA (REP.)", upper: "CS", }, "data/PE": { fmt: "%N%n%O%n%A%n%C %Z%n%S", id: "data/PE", key: "PE", lang: "es", languages: "es", locality_name_type: "district", name: "PERU", posturl: "http://www.serpost.com.pe/cpostal/codigo", sub_isoids: "AMA~ANC~APU~ARE~AYA~CAJ~CAL~CUS~LIM~HUV~HUC~ICA~JUN~LAL~LAM~LOR~MDD~MOQ~LMA~PAS~PIU~PUN~SAM~TAC~TUM~UCA", sub_keys: "Amazonas~Áncash~Apurímac~Arequipa~Ayacucho~Cajamarca~Callao~Cuzco~Gobierno Regional de Lima~Huancavelica~Huánuco~Ica~Junín~La Libertad~Lambayeque~Loreto~Madre de Dios~Moquegua~Municipalidad Metropolitana de Lima~Pasco~Piura~Puno~San Martín~Tacna~Tumbes~Ucayali", zip: "(?:LIMA \\d{1,2}|CALLAO 0?\\d)|[0-2]\\d{4}", zipex: "LIMA 23,LIMA 42,CALLAO 2,02001", }, "data/PF": { fmt: "%N%n%O%n%A%n%Z %C %S", id: "data/PF", key: "PF", name: "FRENCH POLYNESIA", require: "ACSZ", state_name_type: "island", upper: "CS", zip: "987\\d{2}", zipex: "98709", }, "data/PG": { fmt: "%N%n%O%n%A%n%C %Z %S", id: "data/PG", key: "PG", name: "PAPUA NEW GUINEA", require: "ACS", zip: "\\d{3}", zipex: "111", }, "data/PH": { fmt: "%N%n%O%n%A%n%D, %C%n%Z %S", id: "data/PH", key: "PH", lang: "en", languages: "en", name: "PHILIPPINES", posturl: "http://www.philpost.gov.ph/", sub_isoids: "ABR~AGN~AGS~AKL~ALB~ANT~APA~AUR~BAS~BAN~BTN~BTG~BEN~BIL~BOH~BUK~BUL~CAG~CAN~CAS~CAM~CAP~CAT~CAV~CEB~COM~NCO~DAV~DAS~DVO~DAO~DIN~EAS~GUI~IFU~ILN~ILS~ILI~ISA~KAL~LUN~LAG~LAN~LAS~LEY~MAG~MAD~MAS~00~MDC~MDR~MSC~MSR~MOU~NEC~NER~NSA~NUE~NUV~PLW~PAM~PAN~QUE~QUI~RIZ~ROM~WSA~SAR~SIG~SOR~SCO~SLE~SUK~SLU~SUN~SUR~TAR~TAW~ZMB~ZAN~ZAS~ZSI", sub_keys: "Abra~Agusan del Norte~Agusan del Sur~Aklan~Albay~Antique~Apayao~Aurora~Basilan~Bataan~Batanes~Batangas~Benguet~Biliran~Bohol~Bukidnon~Bulacan~Cagayan~Camarines Norte~Camarines Sur~Camiguin~Capiz~Catanduanes~Cavite~Cebu~Compostela Valley~Cotabato~Davao del Norte~Davao del Sur~Davao Occidental~Davao Oriental~Dinagat Islands~Eastern Samar~Guimaras~Ifugao~Ilocos Norte~Ilocos Sur~Iloilo~Isabela~Kalinga~La Union~Laguna~Lanao del Norte~Lanao del Sur~Leyte~Maguindanao~Marinduque~Masbate~Metro Manila~Mindoro Occidental~Mindoro Oriental~Misamis Occidental~Misamis Oriental~Mountain Province~Negros Occidental~Negros Oriental~Northern Samar~Nueva Ecija~Nueva Vizcaya~Palawan~Pampanga~Pangasinan~Quezon Province~Quirino~Rizal~Romblon~Samar~Sarangani~Siquijor~Sorsogon~South Cotabato~Southern Leyte~Sultan Kudarat~Sulu~Surigao del Norte~Surigao del Sur~Tarlac~Tawi-Tawi~Zambales~Zamboanga del Norte~Zamboanga del Sur~Zamboanga Sibuguey", sub_zipexs: "2800,2826~8600,8611~8500,8513~5600,5616~4500,4517~5700,5717~3800,3806,3808~3200,3207~7300,7306~2100,2114~3900,3905~4200,4234~2600,2615~6543,6550~6300,6337~8700,8723~3000,3024~3500,3528~4600,4612~4400,4436~9100,9104~5800,5816~4800,4810~4100,4126~6000,6053~8800,8810~9400,9417~8100,8120~8000,8010~8015,8013~8200,8210~8426,8412~6800,6822~5044,5046~3600,3610~2900,2922~2700,2733~5000,5043~3300,3336~3807,3809,3814~2500,2520~4000,4033~9200,9223~9300,9321,9700,9716~6500,6542~9600,9619~4900,4905~5400,5421~~5100,5111~5200,5214~7200,7215~9000,9025~2616,2625~6100,6132~6200,6224~6400,6423~3100,3133~3700,3714~5300,5322~2000,2022~2400,2447~4300,4342~3400,3405~1850,1990~5500,5516~6700,6725~8015~6225,6230~4700,4715~9500,9513~6600,6613~9800,9811~7400,7416~8400,8425~8300,8319~2300,2318~7500,7509~2200,2213~7100,7124~7000,7043~7000,7043", sub_zips: "28[0-2]~86[01]~85[01]~56[01]~45[01]~57[01]~380[0-68]~320~730~21[01]~390~42[0-3]~26(0|1[0-5])~65(4[3-9]|5)~63[0-3]~87[0-2]~30[0-2]~35[0-2]~46[01]~44[0-3]~910~58[01]~48[01]~41[0-2]~60[0-5]~88[01]~94[01]~81[0-2]~80[01]~801[1-5]~82[01]~84[12]~68[0-2]~504[4-6]~36[01]~29[0-2]~27[0-3]~50([0-3]|4[0-3])~33[0-3]~38(0[79]|1[0-4])~25[0-2]~40[0-3]~92[0-2]~9(3[0-2]|7[01])~65([0-3]|4[0-2])~96[01]~490~54[0-2]~~51[01]~52[01]~72[01]~90[0-2]~26(1[6-9]|2[0-5])~61[0-3]~62[0-2]~64[0-2]~31[0-3]~37[01]~53[0-2]~20[0-2]~24[0-4]~43[0-4]~340~1[89]~55[01]~67[0-2]~8015~62(2[5-9]|30)~47[01]~95[01]~66[10]~98[01]~74[01]~84[0-2]~83[01]~23[01]~750~22[01]~71[0-2]~70[0-4]~70[0-4]", zip: "\\d{4}", zipex: "1008,1050,1135,1207,2000,1000", }, "data/PK": { fmt: "%N%n%O%n%A%n%C-%Z", id: "data/PK", key: "PK", name: "PAKISTAN", posturl: "http://www.pakpost.gov.pk/postcode.php", zip: "\\d{5}", zipex: "44000", }, "data/PL": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/PL", key: "PL", name: "POLAND", posturl: "http://kody.poczta-polska.pl/", require: "ACZ", zip: "\\d{2}-\\d{3}", zipex: "00-950,05-470,48-300,32-015,00-940", }, "data/PM": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/PM", key: "PM", name: "ST. PIERRE AND MIQUELON", require: "ACZ", upper: "ACX", zip: "9[78]5\\d{2}", zipex: "97500", }, "data/PN": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/PN", key: "PN", name: "PITCAIRN", require: "ACZ", upper: "CZ", zip: "PCRN 1ZZ", zipex: "PCRN 1ZZ", }, "data/PR": { fmt: "%N%n%O%n%A%n%C PR %Z", id: "data/PR", key: "PR", name: "PUERTO RICO", postprefix: "PR ", posturl: "http://zip4.usps.com/zip4/welcome.jsp", require: "ACZ", upper: "ACNO", zip: "(00[679]\\d{2})(?:[ \\-](\\d{4}))?", zip_name_type: "zip", zipex: "00930", }, "data/PT": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/PT", key: "PT", name: "PORTUGAL", posturl: "http://www.ctt.pt/feapl_2/app/open/tools.jspx?tool=1", require: "ACZ", zip: "\\d{4}-\\d{3}", zipex: "2725-079,1250-096,1201-950,2860-571,1208-148", }, "data/PW": { fmt: "%N%n%O%n%A%n%C %S %Z", id: "data/PW", key: "PW", name: "PALAU", posturl: "http://zip4.usps.com/zip4/welcome.jsp", require: "ACSZ", state_name_type: "state", upper: "ACNOS", zip: "(969(?:39|40))(?:[ \\-](\\d{4}))?", zip_name_type: "zip", zipex: "96940", }, "data/PY": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/PY", key: "PY", name: "PARAGUAY", zip: "\\d{4}", zipex: "1536,1538,1209", }, "data/QA": { id: "data/QA", key: "QA", name: "QATAR", upper: "AC" }, "data/RE": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/RE", key: "RE", name: "REUNION", posturl: "http://www.laposte.fr/Particulier/Utiliser-nos-outils-pratiques/Outils-et-documents/Trouvez-un-code-postal", require: "ACZ", upper: "ACX", zip: "9[78]4\\d{2}", zipex: "97400", }, "data/RO": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/RO", key: "RO", name: "ROMANIA", posturl: "http://www.posta-romana.ro/zip_codes", upper: "AC", zip: "\\d{6}", zipex: "060274,061357,200716", }, "data/RS": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/RS", key: "RS", name: "REPUBLIC OF SERBIA", posturl: "http://www.posta.rs/struktura/lat/aplikacije/pronadji/nadji-postu.asp", zip: "\\d{5,6}", zipex: "106314", }, "data/RU": { fmt: "%N%n%O%n%A%n%C%n%S%n%Z", id: "data/RU", key: "RU", lang: "ru", languages: "ru", lfmt: "%N%n%O%n%A%n%C%n%S%n%Z", name: "RUSSIAN FEDERATION", posturl: "http://info.russianpost.ru/servlet/department", require: "ACSZ", state_name_type: "oblast", sub_isoids: "ALT~AMU~ARK~AST~BEL~BRY~VLA~VGG~VLG~VOR~YEV~ZAB~IVA~IRK~KB~KGD~KLU~KAM~KC~KEM~KIR~KOS~KDA~KYA~KGN~KRS~LEN~LIP~MAG~MOW~MOS~MUR~NEN~NIZ~NGR~NVS~OMS~ORE~ORL~PNZ~PER~PRI~PSK~AD~AL~BA~BU~DA~IN~KL~KR~KO~~ME~MO~SA~SE~TA~TY~UD~KK~ROS~RYA~SAM~SPE~SAR~SAK~SVE~~SMO~STA~TAM~TVE~TOM~TUL~TYU~ULY~KHA~KHM~CHE~CE~CU~CHU~YAN~YAR", sub_keys: "Алтайский край~Амурская область~Архангельская область~Астраханская область~Белгородская область~Брянская область~Владимирская область~Волгоградская область~Вологодская область~Воронежская область~Еврейская автономная область~Забайкальский край~Ивановская область~Иркутская область~Кабардино-Балкарская Республика~Калининградская область~Калужская область~Камчатский край~Карачаево-Черкесская Республика~Кемеровская область~Кировская область~Костромская область~Краснодарский край~Красноярский край~Курганская область~Курская область~Ленинградская область~Липецкая область~Магаданская область~Москва~Московская область~Мурманская область~Ненецкий автономный округ~Нижегородская область~Новгородская область~Новосибирская область~Омская область~Оренбургская область~Орловская область~Пензенская область~Пермский край~Приморский край~Псковская область~Республика Адыгея~Республика Алтай~Республика Башкортостан~Республика Бурятия~Республика Дагестан~Республика Ингушетия~Республика Калмыкия~Республика Карелия~Республика Коми~Автономна Республіка Крим~Республика Марий Эл~Республика Мордовия~Республика Саха (Якутия)~Республика Северная Осетия-Алания~Республика Татарстан~Республика Тыва~Республика Удмуртия~Республика Хакасия~Ростовская область~Рязанская область~Самарская область~Санкт-Петербург~Саратовская область~Сахалинская область~Свердловская область~Севастополь~Смоленская область~Ставропольский край~Тамбовская область~Тверская область~Томская область~Тульская область~Тюменская область~Ульяновская область~Хабаровский край~Ханты-Мансийский автономный округ~Челябинская область~Чеченская Республика~Чувашская Республика~Чукотский автономный округ~Ямало-Ненецкий автономный округ~Ярославская область", sub_lnames: "Altayskiy kray~Amurskaya oblast'~Arkhangelskaya oblast'~Astrakhanskaya oblast'~Belgorodskaya oblast'~Bryanskaya oblast'~Vladimirskaya oblast'~Volgogradskaya oblast'~Vologodskaya oblast'~Voronezhskaya oblast'~Evreyskaya avtonomnaya oblast'~Zabaykalskiy kray~Ivanovskaya oblast'~Irkutskaya oblast'~Kabardino-Balkarskaya Republits~Kaliningradskaya oblast'~Kaluzhskaya oblast'~Kamchatskiy kray~Karachaevo-Cherkesskaya Republits~Kemerovskaya oblast'~Kirovskaya oblast'~Kostromskaya oblast'~Krasnodarskiy kray~Krasnoyarskiy kray~Kurganskaya oblast'~Kurskaya oblast'~Leningradskaya oblast'~Lipetskaya oblast'~Magadanskaya oblast'~Moskva~Moskovskaya oblast'~Murmanskaya oblast'~Nenetskiy~Nizhegorodskaya oblast'~Novgorodskaya oblast'~Novosibirskaya oblast'~Omskaya oblast'~Orenburgskaya oblast'~Orlovskaya oblast'~Penzenskaya oblast'~Permskiy kray~Primorskiy kray~Pskovskaya oblast'~Respublika Adygeya~Altay Republits~Bashkortostan Republits~Buryatiya Republits~Dagestan Republits~Ingushetiya Republits~Respublika Kalmykiya~Kareliya Republits~Komi Republits~Respublika Krym~Respublika Mariy El~Respublika Mordoviya~Sakha (Yakutiya) Republits~Respublika Severnaya Osetiya-Alaniya~Respublika Tatarstan~Tyva Republits~Respublika Udmurtiya~Khakasiya Republits~Rostovskaya oblast'~Ryazanskaya oblast'~Samarskaya oblast'~Sankt-Peterburg~Saratovskaya oblast'~Sakhalinskaya oblast'~Sverdlovskaya oblast'~Sevastopol'~Smolenskaya oblast'~Stavropolskiy kray~Tambovskaya oblast'~Tverskaya oblast'~Tomskaya oblast'~Tulskaya oblast'~Tyumenskaya oblast'~Ulyanovskaya oblast'~Khabarovskiy kray~Khanty-Mansiyskiy avtonomnyy okrug~Chelyabinskaya oblast'~Chechenskaya Republits~Chuvashia~Chukotskiy~Yamalo-Nenetskiy~Yaroslavskaya oblast'", sub_names: "Алтайский край~Амурская область~Архангельская область~Астраханская область~Белгородская область~Брянская область~Владимирская область~Волгоградская область~Вологодская область~Воронежская область~Еврейская автономная область~Забайкальский край~Ивановская область~Иркутская область~Кабардино-Балкарская Республика~Калининградская область~Калужская область~Камчатский край~Карачаево-Черкесская Республика~Кемеровская область~Кировская область~Костромская область~Краснодарский край~Красноярский край~Курганская область~Курская область~Ленинградская область~Липецкая область~Магаданская область~Москва~Московская область~Мурманская область~Ненецкий автономный округ~Нижегородская область~Новгородская область~Новосибирская область~Омская область~Оренбургская область~Орловская область~Пензенская область~Пермский край~Приморский край~Псковская область~Республика Адыгея~Республика Алтай~Республика Башкортостан~Республика Бурятия~Республика Дагестан~Республика Ингушетия~Республика Калмыкия~Республика Карелия~Республика Коми~Республика Крым~Республика Марий Эл~Республика Мордовия~Республика Саха (Якутия)~Республика Северная Осетия-Алания~Республика Татарстан~Республика Тыва~Республика Удмуртия~Республика Хакасия~Ростовская область~Рязанская область~Самарская область~Санкт-Петербург~Саратовская область~Сахалинская область~Свердловская область~Севастополь~Смоленская область~Ставропольский край~Тамбовская область~Тверская область~Томская область~Тульская область~Тюменская область~Ульяновская область~Хабаровский край~Ханты-Мансийский автономный округ~Челябинская область~Чеченская Республика~Чувашская Республика~Чукотский автономный округ~Ямало-Ненецкий автономный округ~Ярославская область", sub_zips: "65[6-9]~67[56]~16[3-5]~41[4-6]~30[89]~24[1-3]~60[0-2]~40[0-4]~16[0-2]~39[4-7]~679~6(?:7[2-4]|87)~15[3-5]~66[4-9]~36[01]~23[6-8]~24[89]~68[348]~369~65[0-4]~61[0-3]~15[67]~35[0-4]~6(?:6[0-3]|4[78])~64[01]~30[5-7]~18[78]~39[89]~68[56]~1(?:0[1-9]|1|2|3[0-5]|4[0-4])~14[0-4]~18[34]~166~60[3-7]~17[3-5]~63[0-3]~64[4-6]~46[0-2]~30[23]~44[0-2]~61[4-9]~69[0-2]~18[0-2]~385~649~45[0-3]~67[01]~36[78]~386~35[89]~18[56]~16[7-9]~29[5-8]~42[45]~43[01]~67[78]~36[23]~42[0-3]~66[78]~42[67]~655~34[4-7]~39[01]~44[3-6]~19~41[0-3]~69[34]~62[0-4]~299~21[4-6]~35[5-7]~39[23]~17[0-2]~63[4-6]~30[01]~62[5-7]~43[23]~68[0-2]~628~45[4-7]~36[4-6]~42[89]~689~629~15[0-2]", upper: "AC", zip: "\\d{6}", zipex: "247112,103375,188300", }, "data/RW": { id: "data/RW", key: "RW", name: "RWANDA", upper: "AC" }, "data/SA": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/SA", key: "SA", name: "SAUDI ARABIA", zip: "\\d{5}", zipex: "11564,11187,11142", }, "data/SB": { id: "data/SB", key: "SB", name: "SOLOMON ISLANDS" }, "data/SC": { fmt: "%N%n%O%n%A%n%C%n%S", id: "data/SC", key: "SC", name: "SEYCHELLES", state_name_type: "island", upper: "S", }, "data/SD": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/SD", key: "SD", locality_name_type: "district", name: "SUDAN", zip: "\\d{5}", zipex: "11042,11113", }, "data/SE": { fmt: "%O%n%N%n%A%nSE-%Z %C", id: "data/SE", key: "SE", locality_name_type: "post_town", name: "SWEDEN", postprefix: "SE-", posturl: "http://www.posten.se/sv/Kundservice/Sidor/Sok-postnummer-resultat.aspx", require: "ACZ", zip: "\\d{3} ?\\d{2}", zipex: "11455,12345,10500", }, "data/SG": { fmt: "%N%n%O%n%A%nSINGAPORE %Z", id: "data/SG", key: "SG", name: "REP. OF SINGAPORE", posturl: "https://www.singpost.com/find-postal-code", require: "AZ", zip: "\\d{6}", zipex: "546080,308125,408600", }, "data/SH": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/SH", key: "SH", name: "SAINT HELENA", require: "ACZ", upper: "CZ", zip: "(?:ASCN|STHL) 1ZZ", zipex: "STHL 1ZZ", }, "data/SI": { fmt: "%N%n%O%n%A%nSI-%Z %C", id: "data/SI", key: "SI", name: "SLOVENIA", postprefix: "SI-", zip: "\\d{4}", zipex: "4000,1001,2500", }, "data/SK": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/SK", key: "SK", name: "SLOVAKIA", posturl: "http://psc.posta.sk", require: "ACZ", zip: "\\d{3} ?\\d{2}", zipex: "010 01,023 14,972 48,921 01,975 99", }, "data/SL": { id: "data/SL", key: "SL", name: "SIERRA LEONE" }, "data/SM": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/SM", key: "SM", name: "SAN MARINO", posturl: "http://www.poste.it/online/cercacap/", require: "AZ", zip: "4789\\d", zipex: "47890,47891,47895,47899", }, "data/SN": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/SN", key: "SN", name: "SENEGAL", zip: "\\d{5}", zipex: "12500,46024,16556,10000", }, "data/SO": { fmt: "%N%n%O%n%A%n%C, %S %Z", id: "data/SO", key: "SO", lang: "so", languages: "so", name: "SOMALIA", require: "ACS", sub_isoids: "AW~BK~BN~BR~BY~GA~GE~HI~JD~JH~MU~NU~SA~SD~SH~SO~TO~WO", sub_keys: "AD~BK~BN~BR~BY~GG~GD~HR~JD~JH~MD~NG~SG~SD~SH~SL~TG~WG", sub_names: "Awdal~Bakool~Banaadir~Bari~Bay~Galguduud~Gedo~Hiiraan~Jubbada Dhexe~Jubbada Hoose~Mudug~Nugaal~Sanaag~Shabeellaha Dhexe~Shabeellaha Hoose~Sool~Togdheer~Woqooyi Galbeed", upper: "ACS", zip: "[A-Z]{2} ?\\d{5}", zipex: "JH 09010,AD 11010", }, "data/SR": { fmt: "%N%n%O%n%A%n%C%n%S", id: "data/SR", key: "SR", lang: "nl", languages: "nl", name: "SURINAME", sub_isoids: "BR~CM~CR~MA~NI~PR~PM~SA~SI~WA", sub_keys: "Brokopondo~Commewijne~Coronie~Marowijne~Nickerie~Para~Paramaribo~Saramacca~Sipaliwini~Wanica", upper: "AS", }, "data/SS": { id: "data/SS", key: "SS", name: "SOUTH SUDAN" }, "data/ST": { id: "data/ST", key: "ST", name: "SAO TOME AND PRINCIPE" }, "data/SV": { fmt: "%N%n%O%n%A%n%Z-%C%n%S", id: "data/SV", key: "SV", lang: "es", languages: "es", name: "EL SALVADOR", require: "ACS", sub_isoids: "AH~CA~CH~CU~LI~PA~UN~MO~SM~SS~SV~SA~SO~US", sub_keys: "Ahuachapan~Cabanas~Calatenango~Cuscatlan~La Libertad~La Paz~La Union~Morazan~San Miguel~San Salvador~San Vicente~Santa Ana~Sonsonate~Usulutan", sub_names: "Ahuachapán~Cabañas~Chalatenango~Cuscatlán~La Libertad~La Paz~La Unión~Morazán~San Miguel~San Salvador~San Vicente~Santa Ana~Sonsonate~Usulután", sub_zipexs: "CP 2101~CP 1201~CP 1301~CP 1401~CP 1501~CP 1601~CP 3101~CP 3201~CP 3301~CP 1101~CP 1701~CP 2201~CP 2301~CP 3401", sub_zips: "CP 21~CP 12~CP 13~CP 14~CP 15~CP 16~CP 31~CP 32~CP 33~CP 11~CP 17~CP 22~CP 23~CP 34", upper: "CSZ", zip: "CP [1-3][1-7][0-2]\\d", zipex: "CP 1101", }, "data/SX": { id: "data/SX", key: "SX", name: "SINT MAARTEN" }, "data/SY": { id: "data/SY", key: "SY", locality_name_type: "district", name: "SYRIA", }, "data/SZ": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/SZ", key: "SZ", name: "SWAZILAND", posturl: "http://www.sptc.co.sz/swazipost/codes/index.php", upper: "ACZ", zip: "[HLMS]\\d{3}", zipex: "H100", }, "data/TC": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/TC", key: "TC", name: "TURKS AND CAICOS ISLANDS", require: "ACZ", upper: "CZ", zip: "TKCA 1ZZ", zipex: "TKCA 1ZZ", }, "data/TD": { id: "data/TD", key: "TD", name: "CHAD" }, "data/TF": { id: "data/TF", key: "TF", name: "FRENCH SOUTHERN TERRITORIES" }, "data/TG": { id: "data/TG", key: "TG", name: "TOGO" }, "data/TH": { fmt: "%N%n%O%n%A%n%D %C%n%S %Z", id: "data/TH", key: "TH", lang: "th", languages: "th", lfmt: "%N%n%O%n%A%n%D, %C%n%S %Z", name: "THAILAND", sub_isoids: "81~10~71~46~62~40~38~22~24~20~18~36~86~57~50~92~23~63~26~73~48~30~80~60~12~96~55~31~13~77~25~94~14~56~82~93~66~65~76~67~54~83~44~49~58~35~95~45~85~21~70~16~52~51~42~33~47~90~91~11~75~74~27~19~17~64~72~84~32~43~39~15~37~41~53~61~34", sub_keys: "กระบี่~กรุงเทพมหานคร~กาญจนบุรี~กาฬสินธุ์~กำแพงเพชร~ขอนแก่น~จังหวัด บึงกาฬ~จันทบุรี~ฉะเชิงเทรา~ชลบุรี~ชัยนาท~ชัยภูมิ~ชุมพร~เชียงราย~เชียงใหม่~ตรัง~ตราด~ตาก~นครนายก~นครปฐม~นครพนม~นครราชสีมา~นครศรีธรรมราช~นครสวรรค์~นนทบุรี~นราธิวาส~น่าน~บุรีรัมย์~ปทุมธานี~ประจวบคีรีขันธ์~ปราจีนบุรี~ปัตตานี~พระนครศรีอยุธยา~พะเยา~พังงา~พัทลุง~พิจิตร~พิษณุโลก~เพชรบุรี~เพชรบูรณ์~แพร่~ภูเก็ต~มหาสารคาม~มุกดาหาร~แม่ฮ่องสอน~ยโสธร~ยะลา~ร้อยเอ็ด~ระนอง~ระยอง~ราชบุรี~ลพบุรี~ลำปาง~ลำพูน~เลย~ศรีสะเกษ~สกลนคร~สงขลา~สตูล~สมุทรปราการ~สมุทรสงคราม~สมุทรสาคร~สระแก้ว~สระบุรี~สิงห์บุรี~สุโขทัย~สุพรรณบุรี~สุราษฎร์ธานี~สุรินทร์~หนองคาย~หนองบัวลำภู~อ่างทอง~อำนาจเจริญ~อุดรธานี~อุตรดิตถ์~อุทัยธานี~อุบลราชธานี", sub_lnames: "Krabi~Bangkok~Kanchanaburi~Kalasin~Kamphaeng Phet~Khon Kaen~Bueng Kan~Chanthaburi~Chachoengsao~Chon Buri~Chai Nat~Chaiyaphum~Chumpon~Chiang Rai~Chiang Mai~Trang~Trat~Tak~Nakhon Nayok~Nakhon Pathom~Nakhon Phanom~Nakhon Ratchasima~Nakhon Si Thammarat~Nakhon Sawan~Nonthaburi~Narathiwat~Nan~Buri Ram~Pathum Thani~Prachuap Khiri Khan~Prachin Buri~Pattani~Phra Nakhon Si Ayutthaya~Phayao~Phang Nga~Phattalung~Phichit~Phitsanulok~Phetchaburi~Phetchabun~Phrae~Phuket~Maha Sarakham~Mukdahan~Mae Hong Son~Yasothon~Yala~Roi Et~Ranong~Rayong~Ratchaburi~Lop Buri~Lampang~Lamphun~Loei~Si Sa Ket~Sakon Nakhon~Songkhla~Satun~Samut Prakan~Samut Songkhram~Samut Sakhon~Sa Kaeo~Saraburi~Sing Buri~Sukhothai~Suphanburi~Surat Thani~Surin~Nong Khai~Nong Bua Lam Phu~Ang Thong~Amnat Charoen~Udon Thani~Uttaradit~Uthai Thani~Ubon Ratchathani", sub_zips: "81~10~71~46~62~40~~22~24~20~17~36~86~57~50~92~23~63~26~73~48~30~80~60~11~96~55~31~12~77~25~94~13~56~82~93~66~65~76~67~54~83~44~49~58~35~95~45~85~21~70~15~52~51~42~33~47~90~91~10~75~74~27~18~16~64~72~84~32~43~39~14~37~41~53~61~34", upper: "S", zip: "\\d{5}", zipex: "10150,10210", }, "data/TJ": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/TJ", key: "TJ", name: "TAJIKISTAN", zip: "\\d{6}", zipex: "735450,734025", }, "data/TK": { id: "data/TK", key: "TK", name: "TOKELAU" }, "data/TL": { id: "data/TL", key: "TL", name: "TIMOR-LESTE" }, "data/TM": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/TM", key: "TM", name: "TURKMENISTAN", zip: "\\d{6}", zipex: "744000", }, "data/TN": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/TN", key: "TN", name: "TUNISIA", posturl: "http://www.poste.tn/codes.php", zip: "\\d{4}", zipex: "1002,8129,3100,1030", }, "data/TO": { id: "data/TO", key: "TO", name: "TONGA" }, "data/TR": { fmt: "%N%n%O%n%A%n%Z %C/%S", id: "data/TR", key: "TR", lang: "tr", languages: "tr", locality_name_type: "district", name: "TURKEY", posturl: "http://postakodu.ptt.gov.tr/", require: "ACZ", sub_isoids: "01~02~03~04~68~05~06~07~75~08~09~10~74~72~69~11~12~13~14~15~16~17~18~19~20~21~81~22~23~24~25~26~27~28~29~30~31~76~32~34~35~46~78~70~36~37~38~71~39~40~79~41~42~43~44~45~47~33~48~49~50~51~52~80~53~54~55~56~57~58~63~73~59~60~61~62~64~65~77~66~67", sub_keys: "Adana~Adıyaman~Afyon~Ağrı~Aksaray~Amasya~Ankara~Antalya~Ardahan~Artvin~Aydın~Balıkesir~Bartın~Batman~Bayburt~Bilecik~Bingöl~Bitlis~Bolu~Burdur~Bursa~Çanakkale~Çankırı~Çorum~Denizli~Diyarbakır~Düzce~Edirne~Elazığ~Erzincan~Erzurum~Eskişehir~Gaziantep~Giresun~Gümüşhane~Hakkari~Hatay~Iğdır~Isparta~İstanbul~İzmir~Kahramanmaraş~Karabük~Karaman~Kars~Kastamonu~Kayseri~Kırıkkale~Kırklareli~Kırşehir~Kilis~Kocaeli~Konya~Kütahya~Malatya~Manisa~Mardin~Mersin~Muğla~Muş~Nevşehir~Niğde~Ordu~Osmaniye~Rize~Sakarya~Samsun~Siirt~Sinop~Sivas~Şanlıurfa~Şırnak~Tekirdağ~Tokat~Trabzon~Tunceli~Uşak~Van~Yalova~Yozgat~Zonguldak", sub_zips: "01~02~03~04~68~05~06~07~75~08~09~10~74~72~69~11~12~13~14~15~16~17~18~19~20~21~81~22~23~24~25~26~27~28~29~30~31~76~32~34~35~46~78~70~36~37~38~71~39~40~79~41~42~43~44~45~47~33~48~49~50~51~52~80~53~54~55~56~57~58~63~73~59~60~61~62~64~65~77~66~67", zip: "\\d{5}", zipex: "01960,06101", }, "data/TT": { id: "data/TT", key: "TT", name: "TRINIDAD AND TOBAGO" }, "data/TV": { fmt: "%N%n%O%n%A%n%C%n%S", id: "data/TV", key: "TV", lang: "tyv", languages: "tyv", name: "TUVALU", state_name_type: "island", sub_isoids: "FUN~NMG~NMA~~NIT~NUI~NKF~NKL~VAI", sub_keys: "Funafuti~Nanumanga~Nanumea~Niulakita~Niutao~Nui~Nukufetau~Nukulaelae~Vaitupu", upper: "ACS", }, "data/TW": { fmt: "%Z%n%S%C%n%A%n%O%n%N", id: "data/TW", key: "TW", lang: "zh-Hant", languages: "zh-Hant", lfmt: "%N%n%O%n%A%n%C, %S %Z", name: "TAIWAN", posturl: "http://www.post.gov.tw/post/internet/f_searchzone/index.jsp?ID=190102", require: "ACSZ", state_name_type: "county", sub_isoids: "TXG~TPE~TTT~TNN~ILA~HUA~~NAN~PIF~MIA~TAO~KHH~KEE~~YUN~NWT~HSZ~HSQ~CYI~CYQ~CHA~PEN", sub_keys: "台中市~台北市~台東縣~台南市~宜蘭縣~花蓮縣~金門縣~南投縣~屏東縣~苗栗縣~桃園市~高雄市~基隆市~連江縣~雲林縣~新北市~新竹市~新竹縣~嘉義市~嘉義縣~彰化縣~澎湖縣", sub_lnames: "Taichung City~Taipei City~Taitung County~Tainan City~Yilan County~Hualien County~Kinmen County~Nantou County~Pingtung County~Miaoli County~Taoyuan City~Kaohsiung City~Keelung City~Lienchiang County~Yunlin County~New Taipei City~Hsinchu City~Hsinchu County~Chiayi City~Chiayi County~Changhua County~Penghu County", sub_mores: "true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true~true", sub_zipexs: "400,408,411,439~100,119~950,966~700,745~260,272~970,983~890,896~540,558~900,947~350,369~320,338~800,815,817,852~200,206~209,212~630,655~207,208,220,253~~302,315~~602,625~500,530~880,885", sub_zips: "4[0-3]~1[01]~9[56]~7[0-4]~2[67]~9[78]~89~5[45]~9[0-4]~3[56]~3[23]~8[02-5]|81[1-579]~20[0-6]~209|21[012]~6[3-5]~20[78]|2[2345]~300~30[2-8]|31~600~60[1-9]|6[12]~5[0123]~88", zip: "\\d{3}(?:\\d{2})?", zipex: "104,106,10603,40867", }, "data/TZ": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/TZ", key: "TZ", name: "TANZANIA (UNITED REP.)", zip: "\\d{4,5}", zipex: "6090,34413", }, "data/UA": { fmt: "%N%n%O%n%A%n%C%n%S%n%Z", id: "data/UA", key: "UA", lang: "uk", languages: "uk", lfmt: "%N%n%O%n%A%n%C%n%S%n%Z", name: "UKRAINE", posturl: "http://services.ukrposhta.com/postindex_new/", require: "ACSZ", state_name_type: "oblast", sub_isoids: "43~05~07~12~14~18~21~23~26~30~32~35~09~46~48~51~53~56~40~59~61~63~65~68~71~77~74", sub_keys: "Автономна Республіка Крим~Вінницька область~Волинська область~Дніпропетровська область~Донецька область~Житомирська область~Закарпатська область~Запорізька область~Івано-Франківська область~місто Київ~Київська область~Кіровоградська область~Луганська область~Львівська область~Миколаївська область~Одеська область~Полтавська область~Рівненська область~місто Севастополь~Сумська область~Тернопільська область~Харківська область~Херсонська область~Хмельницька область~Черкаська область~Чернівецька область~Чернігівська область", sub_lnames: "Crimea~Vinnyts'ka oblast~Volyns'ka oblast~Dnipropetrovsk oblast~Donetsk oblast~Zhytomyrs'ka oblast~Zakarpats'ka oblast~Zaporiz'ka oblast~Ivano-Frankivs'ka oblast~Kyiv city~Kiev oblast~Kirovohrads'ka oblast~Luhans'ka oblast~Lviv oblast~Mykolaivs'ka oblast~Odessa oblast~Poltavs'ka oblast~Rivnens'ka oblast~Sevastopol' city~Sums'ka oblast~Ternopil's'ka oblast~Kharkiv oblast~Khersons'ka oblast~Khmel'nyts'ka oblast~Cherkas'ka oblast~Chernivets'ka oblast~Chernihivs'ka oblast", sub_names: "Автономна Республіка Крим~Вінницька область~Волинська область~Дніпропетровська область~Донецька область~Житомирська область~Закарпатська область~Запорізька область~Івано-Франківська область~Київ~Київська область~Кіровоградська область~Луганська область~Львівська область~Миколаївська область~Одеська область~Полтавська область~Рівненська область~Севастополь~Сумська область~Тернопільська область~Харківська область~Херсонська область~Хмельницька область~Черкаська область~Чернівецька область~Чернігівська область", sub_zips: "9[5-8]~2[1-4]~4[3-5]~49|5[0-3]~8[3-7]~1[0-3]~8[89]|90~69|7[0-2]~7[6-8]~0[1-6]~0[7-9]~2[5-8]~9[1-4]~79|8[0-2]~5[4-7]~6[5-8]~3[6-9]~3[3-5]~99~4[0-2]~4[6-8]~6[1-4]~7[3-5]~29|3[0-2]~1[89]|20~5[89]|60~1[4-7]", zip: "\\d{5}", zipex: "15432,01055,01001", }, "data/UG": { id: "data/UG", key: "UG", name: "UGANDA" }, "data/US": { fmt: "%N%n%O%n%A%n%C, %S %Z", id: "data/US", key: "US", lang: "en", languages: "en", name: "UNITED STATES", posturl: "https://tools.usps.com/go/ZipLookupAction!input.action", require: "ACSZ", state_name_type: "state", sub_isoids: "AL~AK~~AZ~AR~~~~CA~CO~CT~DE~DC~FL~GA~~HI~ID~IL~IN~IA~KS~KY~LA~ME~~MD~MA~MI~~MN~MS~MO~MT~NE~NV~NH~NJ~NM~NY~NC~ND~~OH~OK~OR~~PA~~RI~SC~SD~TN~TX~UT~VT~~VA~WA~WV~WI~WY", sub_keys: "AL~AK~AS~AZ~AR~AA~AE~AP~CA~CO~CT~DE~DC~FL~GA~GU~HI~ID~IL~IN~IA~KS~KY~LA~ME~MH~MD~MA~MI~FM~MN~MS~MO~MT~NE~NV~NH~NJ~NM~NY~NC~ND~MP~OH~OK~OR~PW~PA~PR~RI~SC~SD~TN~TX~UT~VT~VI~VA~WA~WV~WI~WY", sub_names: "Alabama~Alaska~American Samoa~Arizona~Arkansas~Armed Forces (AA)~Armed Forces (AE)~Armed Forces (AP)~California~Colorado~Connecticut~Delaware~District of Columbia~Florida~Georgia~Guam~Hawaii~Idaho~Illinois~Indiana~Iowa~Kansas~Kentucky~Louisiana~Maine~Marshall Islands~Maryland~Massachusetts~Michigan~Micronesia~Minnesota~Mississippi~Missouri~Montana~Nebraska~Nevada~New Hampshire~New Jersey~New Mexico~New York~North Carolina~North Dakota~Northern Mariana Islands~Ohio~Oklahoma~Oregon~Palau~Pennsylvania~Puerto Rico~Rhode Island~South Carolina~South Dakota~Tennessee~Texas~Utah~Vermont~Virgin Islands~Virginia~Washington~West Virginia~Wisconsin~Wyoming", sub_zipexs: "35000,36999~99500,99999~96799~85000,86999~71600,72999~34000,34099~09000,09999~96200,96699~90000,96199~80000,81999~06000,06999~19700,19999~20000,56999~32000,34999~30000,39901~96910,96932~96700,96899~83200,83999~60000,62999~46000,47999~50000,52999~66000,67999~40000,42799~70000,71599~03900,04999~96960,96979~20600,21999~01000,05544~48000,49999~96941,96944~55000,56799~38600,39799~63000,65999~59000,59999~68000,69999~88900,89999~03000,03899~07000,08999~87000,88499~10000,00544~27000,28999~58000,58999~96950,96952~43000,45999~73000,74999~97000,97999~96940~15000,19699~00600,00999~02800,02999~29000,29999~57000,57999~37000,38599~75000,73344~84000,84999~05000,05999~00800,00899~20100,24699~98000,99499~24700,26999~53000,54999~82000,83414", sub_zips: "3[56]~99[5-9]~96799~8[56]~71[6-9]|72~340~09~96[2-6]~9[0-5]|96[01]~8[01]~06~19[7-9]~20[02-5]|569~3[23]|34[1-9]~3[01]|398|39901~969([1-2]\\d|3[12])~967[0-8]|9679[0-8]|968~83[2-9]~6[0-2]~4[67]~5[0-2]~6[67]~4[01]|42[0-7]~70|71[0-5]~039|04~969[67]~20[6-9]|21~01|02[0-7]|05501|05544~4[89]~9694[1-4]~55|56[0-7]~38[6-9]|39[0-7]~6[3-5]~59~6[89]~889|89~03[0-8]~0[78]~87|88[0-4]~1[0-4]|06390|00501|00544~2[78]~58~9695[0-2]~4[3-5]~7[34]~97~969(39|40)~1[5-8]|19[0-6]~00[679]~02[89]~29~57~37|38[0-5]~7[5-9]|885|73301|73344~84~05~008~201|2[23]|24[0-6]~98|99[0-4]~24[7-9]|2[56]~5[34]~82|83[01]|83414", upper: "CS", zip: "(\\d{5})(?:[ \\-](\\d{4}))?", zip_name_type: "zip", zipex: "95014,22162-1010", }, "data/UY": { fmt: "%N%n%O%n%A%n%Z %C %S", id: "data/UY", key: "UY", lang: "es", languages: "es", name: "URUGUAY", posturl: "http://www.correo.com.uy/index.asp?codPag=codPost&switchMapa=codPost", sub_isoids: "AR~CA~CL~CO~DU~FS~FD~LA~MA~MO~PA~RN~RV~RO~SA~SJ~SO~TA~TT", sub_keys: "Artigas~Canelones~Cerro Largo~Colonia~Durazno~Flores~Florida~Lavalleja~Maldonado~Montevideo~Paysandú~Río Negro~Rivera~Rocha~Salto~San José~Soriano~Tacuarembó~Treinta y Tres", sub_zips: "55~9[01]|1[456]~37~70|75204~97~85~94|9060|97005~30~20~1|91600~60~65|60002~40~27~50~80~75|70003~45~33|30203|30204|30302|37007", upper: "CS", zip: "\\d{5}", zipex: "11600", }, "data/UZ": { fmt: "%N%n%O%n%A%n%Z %C%n%S", id: "data/UZ", key: "UZ", name: "UZBEKISTAN", posturl: "http://www.pochta.uz/ru/uslugi/indexsearch.html", upper: "CS", zip: "\\d{6}", zipex: "702100,700000", }, "data/VA": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/VA", key: "VA", name: "VATICAN", zip: "00120", zipex: "00120", }, "data/VC": { fmt: "%N%n%O%n%A%n%C %Z", id: "data/VC", key: "VC", name: "SAINT VINCENT AND THE GRENADINES (ANTILLES)", posturl: "http://www.svgpost.gov.vc/?option=com_content&view=article&id=3&Itemid=16", zip: "VC\\d{4}", zipex: "VC0100,VC0110,VC0400", }, "data/VE": { fmt: "%N%n%O%n%A%n%C %Z, %S", id: "data/VE", key: "VE", lang: "es", languages: "es", name: "VENEZUELA", posturl: "http://www.ipostel.gob.ve/index.php/oficinas-postales", require: "ACS", state_name_type: "state", sub_isoids: "Z~B~C~D~E~F~G~H~Y~W~A~I~J~K~L~M~N~O~P~R~S~T~X~U~V", sub_keys: "Amazonas~Anzoátegui~Apure~Aragua~Barinas~Bolívar~Carabobo~Cojedes~Delta Amacuro~Dependencias Federales~Distrito Federal~Falcón~Guárico~Lara~Mérida~Miranda~Monagas~Nueva Esparta~Portuguesa~Sucre~Táchira~Trujillo~Vargas~Yaracuy~Zulia", upper: "CS", zip: "\\d{4}", zipex: "1010,3001,8011,1020", }, "data/VG": { fmt: "%N%n%O%n%A%n%C%n%Z", id: "data/VG", key: "VG", name: "VIRGIN ISLANDS (BRITISH)", require: "A", zip: "VG\\d{4}", zipex: "VG1110,VG1150,VG1160", }, "data/VI": { fmt: "%N%n%O%n%A%n%C %S %Z", id: "data/VI", key: "VI", name: "VIRGIN ISLANDS (U.S.)", posturl: "http://zip4.usps.com/zip4/welcome.jsp", require: "ACSZ", state_name_type: "state", upper: "ACNOS", zip: "(008(?:(?:[0-4]\\d)|(?:5[01])))(?:[ \\-](\\d{4}))?", zip_name_type: "zip", zipex: "00802-1222,00850-9802", }, "data/VN": { fmt: "%N%n%O%n%A%n%C%n%S %Z", id: "data/VN", key: "VN", lang: "vi", languages: "vi", lfmt: "%N%n%O%n%A%n%C%n%S %Z", name: "VIET NAM", posturl: "http://postcode.vnpost.vn/services/search.aspx", sub_isoids: "44~43~55~54~53~56~50~57~31~58~40~59~04~CT~DN~33~72~71~39~45~30~03~63~HN~23~61~HP~73~14~66~34~47~28~01~09~02~35~41~67~22~18~36~68~32~24~27~29~13~25~52~05~37~20~69~21~SG~26~46~51~07~49~70~06", sub_keys: "An Giang~Bà Rịa–Vũng Tàu~Bạc Liêu~Bắc Giang~Bắc Kạn~Bắc Ninh~Bến Tre~Bình Dương~Bình Định~Bình Phước~Bình Thuận~Cà Mau~Cao Bằng~Cần Thơ~Đà Nẵng~Đắk Lắk~Đăk Nông~Điện Biên~Đồng Nai~Đồng Tháp~Gia Lai~Hà Giang~Hà Nam~Hà Nội~Hà Tĩnh~Hải Dương~Hải Phòng~Hậu Giang~Hòa Bình~Hưng Yên~Khánh Hòa~Kiên Giang~Kon Tum~Lai Châu~Lạng Sơn~Lào Cai~Lâm Đồng~Long An~Nam Định~Nghệ An~Ninh Bình~Ninh Thuận~Phú Thọ~Phú Yên~Quảng Bình~Quảng Nam~Quảng Ngãi~Quảng Ninh~Quảng Trị~Sóc Trăng~Sơn La~Tây Ninh~Thái Bình~Thái Nguyên~Thanh Hóa~Thành phố Hồ Chí Minh~Thừa Thiên–Huế~Tiền Giang~Trà Vinh~Tuyên Quang~Vĩnh Long~Vĩnh Phúc~Yên Bái", sub_lnames: "An Giang Province~Ba Ria-Vung Tau Province~Bac Lieu Province~Bac Giang Province~Bac Kan Province~Bac Ninh Province~Ben Tre Province~Binh Duong Province~Binh Dinh Province~Binh Phuoc Province~Binh Thuan Province~Ca Mau Province~Cao Bang Province~Can Tho City~Da Nang City~Dak Lak Province~Dak Nong Province~Dien Bien Province~Dong Nai Province~Dong Thap Province~Gia Lai Province~Ha Giang Province~Ha Nam Province~Hanoi City~Ha Tinh Province~Hai Duong Province~Haiphong City~Hau Giang Province~Hoa Binh Province~Hung Yen Province~Khanh Hoa Province~Kien Giang Province~Kon Tum Province~Lai Chau Province~Lang Song Province~Lao Cai Province~Lam Dong Province~Long An Province~Nam Dinh Province~Nghe An Province~Ninh Binh Province~Ninh Thuan Province~Phu Tho Province~Phu Yen Province~Quang Binh Province~Quang Nam Province~Quang Ngai Province~Quang Ninh Province~Quang Tri Province~Soc Trang Province~Son La Province~Tay Ninh Province~Thai Binh Province~Thai Nguyen Province~Thanh Hoa Province~Ho Chi Minh City~Thua Thien-Hue Province~Tien Giang Province~Tra Vinh Province~Tuyen Quang Province~Vinh Long Province~Vinh Phuc Province~Yen Bai Province", zip: "\\d{5}\\d?", zipex: "70010,55999", }, "data/VU": { id: "data/VU", key: "VU", name: "VANUATU" }, "data/WF": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/WF", key: "WF", name: "WALLIS AND FUTUNA ISLANDS", require: "ACZ", upper: "ACX", zip: "986\\d{2}", zipex: "98600", }, "data/WS": { id: "data/WS", key: "WS", name: "SAMOA" }, "data/XK": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/XK", key: "XK", name: "KOSOVO", zip: "[1-7]\\d{4}", zipex: "10000", }, "data/YE": { id: "data/YE", key: "YE", name: "YEMEN" }, "data/YT": { fmt: "%O%n%N%n%A%n%Z %C %X", id: "data/YT", key: "YT", name: "MAYOTTE", require: "ACZ", upper: "ACX", zip: "976\\d{2}", zipex: "97600", }, "data/ZA": { fmt: "%N%n%O%n%A%n%D%n%C%n%Z", id: "data/ZA", key: "ZA", name: "SOUTH AFRICA", posturl: "https://www.postoffice.co.za/Questions/postalcode.html", require: "ACZ", zip: "\\d{4}", zipex: "0083,1451,0001", }, "data/ZM": { fmt: "%N%n%O%n%A%n%Z %C", id: "data/ZM", key: "ZM", name: "ZAMBIA", zip: "\\d{5}", zipex: "50100,50101", }, "data/ZW": { id: "data/ZW", key: "ZW", name: "ZIMBABWE" }, }; PK �������!<%)GC��C��2���chrome/res/addressmetadata/addressReferencesExt.js/* 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/. */ /* exported addressDataExt */ /* eslint max-len: 0 */ "use strict"; // "addressDataExt" uses the same key as "addressData" in "addressReferences.js" and // contains the information we need but absent in "libaddressinput" such as alternative names. // TODO: We only support the alternative name of US in MVP. We are going to support more countries in // bug 1370193. var addressDataExt = { "data/US": { alternative_names: [ "US", "United States of America", "United States", "America", "U.S.", "USA", "U.S.A.", "U.S.A", ], fmt: "%N%n%A%n%C%S%n%Z%O", }, }; PK �������!<&o>��>��+���chrome/res/phonenumberutils/PhoneNumber.jsm/* This Source Code Form is subject to the terms of the Apache License, Version * 2.0. If a copy of the Apache License was not distributed with this file, You * can obtain one at https://www.apache.org/licenses/LICENSE-2.0 */ // This library came from https://github.com/andreasgal/PhoneNumber.js but will // be further maintained by our own in Form Autofill codebase. "use strict"; var EXPORTED_SYMBOLS = ["PhoneNumber"]; ChromeUtils.defineModuleGetter( this, "PHONE_NUMBER_META_DATA", "resource://formautofill/phonenumberutils/PhoneNumberMetaData.jsm" ); ChromeUtils.defineModuleGetter( this, "PhoneNumberNormalizer", "resource://formautofill/phonenumberutils/PhoneNumberNormalizer.jsm" ); var PhoneNumber = (function(dataBase) { const MAX_PHONE_NUMBER_LENGTH = 50; const NON_ALPHA_CHARS = /[^a-zA-Z]/g; const NON_DIALABLE_CHARS = /[^,#+\*\d]/g; const NON_DIALABLE_CHARS_ONCE = new RegExp(NON_DIALABLE_CHARS.source); const SPLIT_FIRST_GROUP = /^(\d+)(.*)$/; const LEADING_PLUS_CHARS_PATTERN = /^[+\uFF0B]+/g; // Format of the string encoded meta data. If the name contains "^" or "$" // we will generate a regular expression from the value, with those special // characters as prefix/suffix. const META_DATA_ENCODING = [ "region", "^(?:internationalPrefix)", "nationalPrefix", "^(?:nationalPrefixForParsing)", "nationalPrefixTransformRule", "nationalPrefixFormattingRule", "^possiblePattern$", "^nationalPattern$", "formats", ]; const FORMAT_ENCODING = [ "^pattern$", "nationalFormat", "^leadingDigits", "nationalPrefixFormattingRule", "internationalFormat", ]; let regionCache = Object.create(null); // Parse an array of strings into a convenient object. We store meta // data as arrays since thats much more compact than JSON. function ParseArray(array, encoding, obj) { for (let n = 0; n < encoding.length; ++n) { let value = array[n]; if (!value) { continue; } let field = encoding[n]; let fieldAlpha = field.replace(NON_ALPHA_CHARS, ""); if (field != fieldAlpha) { value = new RegExp(field.replace(fieldAlpha, value)); } obj[fieldAlpha] = value; } return obj; } // Parse string encoded meta data into a convenient object // representation. function ParseMetaData(countryCode, md) { let array = JSON.parse(md); md = ParseArray(array, META_DATA_ENCODING, { countryCode }); regionCache[md.region] = md; return md; } // Parse string encoded format data into a convenient object // representation. function ParseFormat(md) { let formats = md.formats; if (!formats) { return; } // Bail if we already parsed the format definitions. if (!Array.isArray(formats[0])) { return; } for (let n = 0; n < formats.length; ++n) { formats[n] = ParseArray(formats[n], FORMAT_ENCODING, {}); } } // Search for the meta data associated with a region identifier ("US") in // our database, which is indexed by country code ("1"). Since we have // to walk the entire database for this, we cache the result of the lookup // for future reference. function FindMetaDataForRegion(region) { // Check in the region cache first. This will find all entries we have // already resolved (parsed from a string encoding). let md = regionCache[region]; if (md) { return md; } for (let countryCode in dataBase) { let entry = dataBase[countryCode]; // Each entry is a string encoded object of the form '["US..', or // an array of strings. We don't want to parse the string here // to save memory, so we just substring the region identifier // and compare it. For arrays, we compare against all region // identifiers with that country code. We skip entries that are // of type object, because they were already resolved (parsed into // an object), and their country code should have been in the cache. if (Array.isArray(entry)) { for (let n = 0; n < entry.length; n++) { if (typeof entry[n] == "string" && entry[n].substr(2, 2) == region) { if (n > 0) { // Only the first entry has the formats field set. // Parse the main country if we haven't already and use // the formats field from the main country. if (typeof entry[0] == "string") { entry[0] = ParseMetaData(countryCode, entry[0]); } let formats = entry[0].formats; let current = ParseMetaData(countryCode, entry[n]); current.formats = formats; entry[n] = current; return entry[n]; } entry[n] = ParseMetaData(countryCode, entry[n]); return entry[n]; } } continue; } if (typeof entry == "string" && entry.substr(2, 2) == region) { dataBase[countryCode] = ParseMetaData(countryCode, entry); return dataBase[countryCode]; } } } // Format a national number for a given region. The boolean flag "intl" // indicates whether we want the national or international format. function FormatNumber(regionMetaData, number, intl) { // We lazily parse the format description in the meta data for the region, // so make sure to parse it now if we haven't already done so. ParseFormat(regionMetaData); let formats = regionMetaData.formats; if (!formats) { return null; } for (let n = 0; n < formats.length; ++n) { let format = formats[n]; // The leading digits field is optional. If we don't have it, just // use the matching pattern to qualify numbers. if (format.leadingDigits && !format.leadingDigits.test(number)) { continue; } if (!format.pattern.test(number)) { continue; } if (intl) { // If there is no international format, just fall back to the national // format. let internationalFormat = format.internationalFormat; if (!internationalFormat) { internationalFormat = format.nationalFormat; } // Some regions have numbers that can't be dialed from outside the // country, indicated by "NA" for the international format of that // number format pattern. if (internationalFormat == "NA") { return null; } // Prepend "+" and the country code. number = "+" + regionMetaData.countryCode + " " + number.replace(format.pattern, internationalFormat); } else { number = number.replace(format.pattern, format.nationalFormat); // The region has a national prefix formatting rule, and it can be overwritten // by each actual number format rule. let nationalPrefixFormattingRule = regionMetaData.nationalPrefixFormattingRule; if (format.nationalPrefixFormattingRule) { nationalPrefixFormattingRule = format.nationalPrefixFormattingRule; } if (nationalPrefixFormattingRule) { // The prefix formatting rule contains two magic markers, "$NP" and "$FG". // "$NP" will be replaced by the national prefix, and "$FG" with the // first group of numbers. let match = number.match(SPLIT_FIRST_GROUP); if (match) { let firstGroup = match[1]; let rest = match[2]; let prefix = nationalPrefixFormattingRule; prefix = prefix.replace("$NP", regionMetaData.nationalPrefix); prefix = prefix.replace("$FG", firstGroup); number = prefix + rest; } } } return number == "NA" ? null : number; } return null; } function NationalNumber(regionMetaData, number) { this.region = regionMetaData.region; this.regionMetaData = regionMetaData; this.number = number; } // NationalNumber represents the result of parsing a phone number. We have // three getters on the prototype that format the number in national and // international format. Once called, the getters put a direct property // onto the object, caching the result. NationalNumber.prototype = { // +1 949-726-2896 get internationalFormat() { let value = FormatNumber(this.regionMetaData, this.number, true); Object.defineProperty(this, "internationalFormat", { value, enumerable: true, }); return value; }, // (949) 726-2896 get nationalFormat() { let value = FormatNumber(this.regionMetaData, this.number, false); Object.defineProperty(this, "nationalFormat", { value, enumerable: true, }); return value; }, // +19497262896 get internationalNumber() { let value = this.internationalFormat ? this.internationalFormat.replace(NON_DIALABLE_CHARS, "") : null; Object.defineProperty(this, "internationalNumber", { value, enumerable: true, }); return value; }, // 9497262896 get nationalNumber() { let value = this.nationalFormat ? this.nationalFormat.replace(NON_DIALABLE_CHARS, "") : null; Object.defineProperty(this, "nationalNumber", { value, enumerable: true, }); return value; }, // country name 'US' get countryName() { let value = this.region ? this.region : null; Object.defineProperty(this, "countryName", { value, enumerable: true }); return value; }, // country code '+1' get countryCode() { let value = this.regionMetaData.countryCode ? "+" + this.regionMetaData.countryCode : null; Object.defineProperty(this, "countryCode", { value, enumerable: true }); return value; }, }; // Check whether the number is valid for the given region. function IsValidNumber(number, md) { return md.possiblePattern.test(number); } // Check whether the number is a valid national number for the given region. /* eslint-disable no-unused-vars */ function IsNationalNumber(number, md) { return IsValidNumber(number, md) && md.nationalPattern.test(number); } // Determine the country code a number starts with, or return null if // its not a valid country code. function ParseCountryCode(number) { for (let n = 1; n <= 3; ++n) { let cc = number.substr(0, n); if (dataBase[cc]) { return cc; } } return null; } // Parse a national number for a specific region. Return null if the // number is not a valid national number (it might still be a possible // number for parts of that region). function ParseNationalNumber(number, md) { if (!md.possiblePattern.test(number) || !md.nationalPattern.test(number)) { return null; } // Success. return new NationalNumber(md, number); } function ParseNationalNumberAndCheckNationalPrefix(number, md) { let ret; // This is not an international number. See if its a national one for // the current region. National numbers can start with the national // prefix, or without. if (md.nationalPrefixForParsing) { // Some regions have specific national prefix parse rules. Apply those. let withoutPrefix = number.replace( md.nationalPrefixForParsing, md.nationalPrefixTransformRule || "" ); ret = ParseNationalNumber(withoutPrefix, md); if (ret) { return ret; } } else { // If there is no specific national prefix rule, just strip off the // national prefix from the beginning of the number (if there is one). let nationalPrefix = md.nationalPrefix; if ( nationalPrefix && number.indexOf(nationalPrefix) == 0 && (ret = ParseNationalNumber(number.substr(nationalPrefix.length), md)) ) { return ret; } } ret = ParseNationalNumber(number, md); if (ret) { return ret; } } function ParseNumberByCountryCode(number, countryCode) { let ret; // Lookup the meta data for the region (or regions) and if the rest of // the number parses for that region, return the parsed number. let entry = dataBase[countryCode]; if (Array.isArray(entry)) { for (let n = 0; n < entry.length; ++n) { if (typeof entry[n] == "string") { entry[n] = ParseMetaData(countryCode, entry[n]); } if (n > 0) { entry[n].formats = entry[0].formats; } ret = ParseNationalNumberAndCheckNationalPrefix(number, entry[n]); if (ret) { return ret; } } return null; } if (typeof entry == "string") { entry = dataBase[countryCode] = ParseMetaData(countryCode, entry); } return ParseNationalNumberAndCheckNationalPrefix(number, entry); } // Parse an international number that starts with the country code. Return // null if the number is not a valid international number. function ParseInternationalNumber(number) { // Parse and strip the country code. let countryCode = ParseCountryCode(number); if (!countryCode) { return null; } number = number.substr(countryCode.length); return ParseNumberByCountryCode(number, countryCode); } // Parse a number and transform it into the national format, removing any // international dial prefixes and country codes. function ParseNumber(number, defaultRegion) { let ret; // Remove formating characters and whitespace. number = PhoneNumberNormalizer.Normalize(number); // If there is no defaultRegion or the defaultRegion is the global region, // we can't parse international access codes. if ((!defaultRegion || defaultRegion === "001") && number[0] !== "+") { return null; } // Detect and strip leading '+'. if (number[0] === "+") { return ParseInternationalNumber( number.replace(LEADING_PLUS_CHARS_PATTERN, "") ); } // If "defaultRegion" is a country code, use it to parse the number directly. let matches = String(defaultRegion).match(/^\+?(\d+)/); if (matches) { let countryCode = ParseCountryCode(matches[1]); if (!countryCode) { return null; } return ParseNumberByCountryCode(number, countryCode); } // Lookup the meta data for the given region. let md = FindMetaDataForRegion(defaultRegion.toUpperCase()); if (!md) { dump("Couldn't find Meta Data for region: " + defaultRegion + "\n"); return null; } // See if the number starts with an international prefix, and if the // number resulting from stripping the code is valid, then remove the // prefix and flag the number as international. if (md.internationalPrefix.test(number)) { let possibleNumber = number.replace(md.internationalPrefix, ""); ret = ParseInternationalNumber(possibleNumber); if (ret) { return ret; } } ret = ParseNationalNumberAndCheckNationalPrefix(number, md); if (ret) { return ret; } // Now lets see if maybe its an international number after all, but // without '+' or the international prefix. ret = ParseInternationalNumber(number); if (ret) { return ret; } // If the number matches the possible numbers of the current region, // return it as a possible number. if (md.possiblePattern.test(number)) { return new NationalNumber(md, number); } // We couldn't parse the number at all. return null; } function IsPlainPhoneNumber(number) { if (typeof number !== "string") { return false; } let length = number.length; let isTooLong = length > MAX_PHONE_NUMBER_LENGTH; let isEmpty = length === 0; return !(isTooLong || isEmpty || NON_DIALABLE_CHARS_ONCE.test(number)); } return { IsPlain: IsPlainPhoneNumber, Parse: ParseNumber, }; })(PHONE_NUMBER_META_DATA); PK �������!<g`R��3���chrome/res/phonenumberutils/PhoneNumberMetaData.jsm/* This Source Code Form is subject to the terms of the Apache License, Version * 2.0. If a copy of the Apache License was not distributed with this file, You * can obtain one at https://www.apache.org/licenses/LICENSE-2.0 */ /* * This data was generated base on libphonenumber v8.4.1 via the script in * https://github.com/andreasgal/PhoneNumber.js * * The XML format of libphonenumber has changed since v8.4.2 so we can only stay * in this version for now. */ var EXPORTED_SYMBOLS = ["PHONE_NUMBER_META_DATA"]; var PHONE_NUMBER_META_DATA = { "46": '["SE","00","0",null,null,"$NP$FG","\\\\d{6,12}","[1-35-9]\\\\d{5,11}|4\\\\d{6,8}",[["(8)(\\\\d{2,3})(\\\\d{2,3})(\\\\d{2})","$1-$2 $3 $4","8",null,"$1 $2 $3 $4"],["([1-69]\\\\d)(\\\\d{2,3})(\\\\d{2})(\\\\d{2})","$1-$2 $3 $4","1[013689]|2[0136]|3[1356]|4[0246]|54|6[03]|90",null,"$1 $2 $3 $4"],["([1-469]\\\\d)(\\\\d{3})(\\\\d{2})","$1-$2 $3","1[136]|2[136]|3[356]|4[0246]|6[03]|90",null,"$1 $2 $3"],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1-$2 $3 $4","1[2457]|2(?:[247-9]|5[0138])|3[0247-9]|4[1357-9]|5[0-35-9]|6(?:[124-689]|7[0-2])|9(?:[125-8]|3[0-5]|4[0-3])",null,"$1 $2 $3 $4"],["(\\\\d{3})(\\\\d{2,3})(\\\\d{2})","$1-$2 $3","1[2457]|2(?:[247-9]|5[0138])|3[0247-9]|4[1357-9]|5[0-35-9]|6(?:[124-689]|7[0-2])|9(?:[125-8]|3[0-5]|4[0-3])",null,"$1 $2 $3"],["(7\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1-$2 $3 $4","7",null,"$1 $2 $3 $4"],["(77)(\\\\d{2})(\\\\d{2})","$1-$2$3","7",null,"$1 $2 $3"],["(20)(\\\\d{2,3})(\\\\d{2})","$1-$2 $3","20",null,"$1 $2 $3"],["(9[034]\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1-$2 $3 $4","9[034]",null,"$1 $2 $3 $4"],["(9[034]\\\\d)(\\\\d{4})","$1-$2","9[034]",null,"$1 $2"],["(\\\\d{3})(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1-$2 $3 $4 $5","25[245]|67[3-6]",null,"$1 $2 $3 $4 $5"]]]', "299": '["GL","00",null,null,null,null,"\\\\d{6}","[1-689]\\\\d{5}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3",null,null]]]', "385": '["HR","00","0",null,null,"$NP$FG","\\\\d{6,9}","[1-7]\\\\d{5,8}|[89]\\\\d{6,8}",[["(1)(\\\\d{4})(\\\\d{3})","$1 $2 $3","1",null],["([2-5]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[2-5]",null],["(9\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","9",null],["(6[01])(\\\\d{2})(\\\\d{2,3})","$1 $2 $3","6[01]",null],["([67]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[67]",null],["(80[01])(\\\\d{2})(\\\\d{2,3})","$1 $2 $3","8",null],["(80[01])(\\\\d{3})(\\\\d{3})","$1 $2 $3","8",null]]]', "670": '["TL","00",null,null,null,null,"\\\\d{7,8}","[2-489]\\\\d{6}|7\\\\d{6,7}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[2-489]",null],["(\\\\d{4})(\\\\d{4})","$1 $2","7",null]]]', "258": '["MZ","00",null,null,null,null,"\\\\d{8,9}","[28]\\\\d{7,8}",[["([28]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","2|8[2-7]",null],["(80\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","80",null]]]', "359": '["BG","00","0",null,null,"$NP$FG","\\\\d{5,9}","[23567]\\\\d{5,7}|[489]\\\\d{6,8}",[["(2)(\\\\d)(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","2",null],["(2)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","2",null],["(\\\\d{3})(\\\\d{4})","$1 $2","43[124-7]|70[1-9]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{2})","$1 $2 $3","43[124-7]|70[1-9]",null],["(\\\\d{3})(\\\\d{2})(\\\\d{3})","$1 $2 $3","[78]00",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","999",null],["(\\\\d{2})(\\\\d{3})(\\\\d{2,3})","$1 $2 $3","[356]|4[124-7]|7[1-9]|8[1-6]|9[1-7]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","48|8[7-9]|9[08]",null]]]', "682": '["CK","00",null,null,null,null,"\\\\d{5}","[2-8]\\\\d{4}",[["(\\\\d{2})(\\\\d{3})","$1 $2",null,null]]]', "852": '["HK","00(?:[126-9]|30|5[09])?",null,null,null,null,"\\\\d{5,11}","[235-7]\\\\d{7}|8\\\\d{7,8}|9\\\\d{4,10}",[["(\\\\d{4})(\\\\d{4})","$1 $2","[235-7]|[89](?:0[1-9]|[1-9])",null],["(800)(\\\\d{3})(\\\\d{3})","$1 $2 $3","800",null],["(900)(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","900",null],["(900)(\\\\d{2,5})","$1 $2","900",null]]]', "998": '["UZ","810","8",null,null,"$NP $FG","\\\\d{7,9}","[679]\\\\d{8}",[["([679]\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "291": '["ER","00","0",null,null,"$NP$FG","\\\\d{6,7}","[178]\\\\d{6}",[["(\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,null]]]', "95": '["MM","00","0",null,null,"$NP$FG","\\\\d{5,10}","[1478]\\\\d{5,7}|[256]\\\\d{5,8}|9(?:[279]\\\\d{0,2}|[58]|[34]\\\\d{1,2}|6\\\\d?)\\\\d{6}",[["(\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","1|2[245]",null],["(2)(\\\\d{4})(\\\\d{4})","$1 $2 $3","251",null],["(\\\\d)(\\\\d{2})(\\\\d{3})","$1 $2 $3","16|2",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","67|81",null],["(\\\\d{2})(\\\\d{2})(\\\\d{3,4})","$1 $2 $3","[4-8]",null],["(9)(\\\\d{3})(\\\\d{4,6})","$1 $2 $3","9(?:2[0-4]|[35-9]|4[137-9])",null],["(9)([34]\\\\d{4})(\\\\d{4})","$1 $2 $3","9(?:3[0-36]|4[0-57-9])",null],["(9)(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","92[56]",null],["(9)(\\\\d{3})(\\\\d{3})(\\\\d{2})","$1 $2 $3 $4","93",null]]]', "266": '["LS","00",null,null,null,null,"\\\\d{8}","[2568]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]', "245": '["GW","00",null,null,null,null,"\\\\d{7,9}","(?:4(?:0\\\\d{5}|4\\\\d{7})|9\\\\d{8})",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","44|9[567]",null],["(\\\\d{3})(\\\\d{4})","$1 $2","40",null]]]', "374": '["AM","00","0",null,null,"($NP$FG)","\\\\d{5,8}","[1-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{6})","$1 $2","1|47",null],["(\\\\d{2})(\\\\d{6})","$1 $2","4[1349]|[5-7]|9[1-9]","$NP$FG"],["(\\\\d{3})(\\\\d{5})","$1 $2","[23]",null],["(\\\\d{3})(\\\\d{2})(\\\\d{3})","$1 $2 $3","8|90","$NP $FG"]]]', "61": ['["AU","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",null,null,null,"\\\\d{6,10}","1\\\\d{4,9}|[2-578]\\\\d{8}",[["([2378])(\\\\d{4})(\\\\d{4})","$1 $2 $3","[2378]","($NP$FG)"],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[45]|14","$NP$FG"],["(16)(\\\\d{3,4})","$1 $2","16","$NP$FG"],["(16)(\\\\d{3})(\\\\d{2,4})","$1 $2 $3","16","$NP$FG"],["(1[389]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","1(?:[38]0|90)","$FG"],["(180)(2\\\\d{3})","$1 $2","180","$FG"],["(19\\\\d)(\\\\d{3})","$1 $2","19[13]","$FG"],["(19\\\\d{2})(\\\\d{4})","$1 $2","19[679]","$FG"],["(13)(\\\\d{2})(\\\\d{2})","$1 $2 $3","13[1-9]","$FG"]]]','["CC","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",null,null,null,"\\\\d{6,10}","[1458]\\\\d{5,9}"]','["CX","(?:14(?:1[14]|34|4[17]|[56]6|7[47]|88))?001[14-689]","0",null,null,null,"\\\\d{6,10}","[1458]\\\\d{5,9}"]'], "500": '["FK","00",null,null,null,null,"\\\\d{5}","[2-7]\\\\d{4}"]', "261": '["MG","00","0",null,null,"$NP$FG","\\\\d{7,9}","[23]\\\\d{8}",[["([23]\\\\d)(\\\\d{2})(\\\\d{3})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "92": '["PK","00","0",null,null,"($NP$FG)","\\\\d{6,12}","1\\\\d{8}|[2-8]\\\\d{5,11}|9(?:[013-9]\\\\d{4,9}|2\\\\d(?:111\\\\d{6}|\\\\d{3,7}))",[["(\\\\d{2})(111)(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","(?:2[125]|4[0-246-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)1",null],["(\\\\d{3})(111)(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","2[349]|45|54|60|72|8[2-5]|9[2-9]",null],["(\\\\d{2})(\\\\d{7,8})","$1 $2","(?:2[125]|4[0-246-9]|5[1-35-7]|6[1-8]|7[14]|8[16]|91)[2-9]",null],["(\\\\d{3})(\\\\d{6,7})","$1 $2","2[349]|45|54|60|72|8[2-5]|9[2-9]",null],["(3\\\\d{2})(\\\\d{7})","$1 $2","3","$NP$FG"],["([15]\\\\d{3})(\\\\d{5,6})","$1 $2","58[12]|1",null],["(586\\\\d{2})(\\\\d{5})","$1 $2","586",null],["([89]00)(\\\\d{3})(\\\\d{2})","$1 $2 $3","[89]00","$NP$FG"]]]', "234": '["NG","009","0",null,null,"$NP$FG","\\\\d{5,14}","[1-6]\\\\d{5,8}|9\\\\d{5,9}|[78]\\\\d{5,13}",[["(\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[12]|9(?:0[3-9]|[1-9])",null],["(\\\\d{2})(\\\\d{3})(\\\\d{2,3})","$1 $2 $3","[3-6]|7(?:[1-79]|0[1-9])|8[2-9]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","70|8[01]|90[235-9]",null],["([78]00)(\\\\d{4})(\\\\d{4,5})","$1 $2 $3","[78]00",null],["([78]00)(\\\\d{5})(\\\\d{5,6})","$1 $2 $3","[78]00",null],["(78)(\\\\d{2})(\\\\d{3})","$1 $2 $3","78",null]]]', "350": '["GI","00",null,null,null,null,"\\\\d{8}","[2568]\\\\d{7}",[["(\\\\d{3})(\\\\d{5})","$1 $2","2",null]]]', "45": '["DK","00",null,null,null,null,"\\\\d{8}","[2-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "963": '["SY","00","0",null,null,"$NP$FG","\\\\d{6,9}","[1-59]\\\\d{7,8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[1-5]",null],["(9\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","9",null]]]', "226": '["BF","00",null,null,null,null,"\\\\d{8}","[25-7]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "974": '["QA","00",null,null,null,null,"\\\\d{7,8}","[2-8]\\\\d{6,7}",[["([28]\\\\d{2})(\\\\d{4})","$1 $2","[28]",null],["([3-7]\\\\d{3})(\\\\d{4})","$1 $2","[3-7]",null]]]', "218": '["LY","00","0",null,null,"$NP$FG","\\\\d{7,9}","[25679]\\\\d{8}",[["([25679]\\\\d)(\\\\d{7})","$1-$2",null,null]]]', "51": '["PE","19(?:1[124]|77|90)00","0",null,null,"($NP$FG)","\\\\d{6,9}","[14-9]\\\\d{7,8}",[["(1)(\\\\d{7})","$1 $2","1",null],["([4-8]\\\\d)(\\\\d{6})","$1 $2","[4-7]|8[2-4]",null],["(\\\\d{3})(\\\\d{5})","$1 $2","80",null],["(9\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","9","$FG"]]]', "62": '["ID","0(?:0[1789]|10(?:00|1[67]))","0",null,null,"$NP$FG","\\\\d{5,12}","(?:[1-79]\\\\d{6,10}|8\\\\d{7,11})",[["(\\\\d{2})(\\\\d{5,8})","$1 $2","2[124]|[36]1","($NP$FG)"],["(\\\\d{3})(\\\\d{5,8})","$1 $2","[4579]|2[035-9]|[36][02-9]","($NP$FG)"],["(8\\\\d{2})(\\\\d{3,4})(\\\\d{3})","$1-$2-$3","8[1-35-9]",null],["(8\\\\d{2})(\\\\d{4})(\\\\d{4,5})","$1-$2-$3","8[1-35-9]",null],["(1)(500)(\\\\d{3})","$1 $2 $3","15","$FG"],["(177)(\\\\d{6,8})","$1 $2","17",null],["(800)(\\\\d{5,7})","$1 $2","800",null],["(804)(\\\\d{3})(\\\\d{4})","$1 $2 $3","804",null],["(80\\\\d)(\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","80[79]",null]]]', "298": '["FO","00",null,"(10(?:01|[12]0|88))",null,null,"\\\\d{6}","[2-9]\\\\d{5}",[["(\\\\d{6})","$1",null,null]]]', "381": '["RS","00","0",null,null,"$NP$FG","\\\\d{5,12}","[126-9]\\\\d{4,11}|3(?:[0-79]\\\\d{3,10}|8[2-9]\\\\d{2,9})",[["([23]\\\\d{2})(\\\\d{4,9})","$1 $2","(?:2[389]|39)0",null],["([1-3]\\\\d)(\\\\d{5,10})","$1 $2","1|2(?:[0-24-7]|[389][1-9])|3(?:[0-8]|9[1-9])",null],["(6\\\\d)(\\\\d{6,8})","$1 $2","6",null],["([89]\\\\d{2})(\\\\d{3,9})","$1 $2","[89]",null],["(7[26])(\\\\d{4,9})","$1 $2","7[26]",null],["(7[08]\\\\d)(\\\\d{4,9})","$1 $2","7[08]",null]]]', "975": '["BT","00",null,null,null,null,"\\\\d{6,8}","[1-8]\\\\d{6,7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","1|77",null],["([2-8])(\\\\d{3})(\\\\d{3})","$1 $2 $3","[2-68]|7[246]",null]]]', "34": '["ES","00",null,null,null,null,"\\\\d{9}","[5-9]\\\\d{8}",[["([89]00)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[89]00",null],["([5-9]\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[568]|[79][0-8]",null]]]', "881": '["001",null,null,null,null,null,"\\\\d{9}","[67]\\\\d{8}",[["(\\\\d)(\\\\d{3})(\\\\d{5})","$1 $2 $3","[67]",null]]]', "855": '["KH","00[14-9]","0",null,null,null,"\\\\d{6,10}","[1-9]\\\\d{7,9}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","1\\\\d[1-9]|[2-9]","$NP$FG"],["(1[89]00)(\\\\d{3})(\\\\d{3})","$1 $2 $3","1[89]0",null]]]', "420": '["CZ","00",null,null,null,null,"\\\\d{9,12}","[2-8]\\\\d{8}|9\\\\d{8,11}",[["([2-9]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[2-8]|9[015-7]",null],["(96\\\\d)(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","96",null],["(9\\\\d)(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","9[36]",null]]]', "216": '["TN","00",null,null,null,null,"\\\\d{8}","[2-57-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,null]]]', "673": '["BN","00",null,null,null,null,"\\\\d{7}","[2-578]\\\\d{6}",[["([2-578]\\\\d{2})(\\\\d{4})","$1 $2",null,null]]]', "290": ['["SH","00",null,null,null,null,"\\\\d{4,5}","[256]\\\\d{4}"]','["TA","00",null,null,null,null,"\\\\d{4}","8\\\\d{3}"]'], "882": '["001",null,null,null,null,null,"\\\\d{7,12}","[13]\\\\d{6,11}",[["(\\\\d{2})(\\\\d{4})(\\\\d{3})","$1 $2 $3","3[23]",null],["(\\\\d{2})(\\\\d{5})","$1 $2","16|342",null],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1 $2 $3","34[57]",null],["(\\\\d{3})(\\\\d{4})(\\\\d{4})","$1 $2 $3","348",null],["(\\\\d{2})(\\\\d{2})(\\\\d{4})","$1 $2 $3","1",null],["(\\\\d{2})(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","16",null],["(\\\\d{2})(\\\\d{4,5})(\\\\d{5})","$1 $2 $3","16|39",null]]]', "267": '["BW","00",null,null,null,null,"\\\\d{7,8}","[2-79]\\\\d{6,7}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[2-6]",null],["(7\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","7",null],["(90)(\\\\d{5})","$1 $2","9",null]]]', "94": '["LK","00","0",null,null,"$NP$FG","\\\\d{7,9}","[1-9]\\\\d{8}",[["(\\\\d{2})(\\\\d{1})(\\\\d{6})","$1 $2 $3","[1-689]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","7",null]]]', "356": '["MT","00",null,null,null,null,"\\\\d{8}","[2357-9]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]', "375": '["BY","810","8","8?0?",null,null,"\\\\d{5,11}","[1-4]\\\\d{8}|800\\\\d{3,7}|[89]\\\\d{9,10}",[["(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2-$3-$4","17[0-3589]|2[4-9]|[34]","$NP 0$FG"],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2-$3-$4","1(?:5[24]|6[235]|7[467])|2(?:1[246]|2[25]|3[26])","$NP 0$FG"],["(\\\\d{4})(\\\\d{2})(\\\\d{3})","$1 $2-$3","1(?:5[169]|6[3-5]|7[179])|2(?:1[35]|2[34]|3[3-5])","$NP 0$FG"],["([89]\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","8[01]|9","$NP $FG"],["(82\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","82","$NP $FG"],["(800)(\\\\d{3})","$1 $2","800","$NP $FG"],["(800)(\\\\d{2})(\\\\d{2,4})","$1 $2 $3","800","$NP $FG"]]]', "690": '["TK","00",null,null,null,null,"\\\\d{4,7}","[2-47]\\\\d{3,6}"]', "507": '["PA","00",null,null,null,null,"\\\\d{7,8}","[1-9]\\\\d{6,7}",[["(\\\\d{3})(\\\\d{4})","$1-$2","[1-57-9]",null],["(\\\\d{4})(\\\\d{4})","$1-$2","6",null]]]', "692": '["MH","011","1",null,null,null,"\\\\d{7}","[2-6]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1-$2",null,null]]]', "250": '["RW","00","0",null,null,null,"\\\\d{8,9}","[027-9]\\\\d{7,8}",[["(2\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","2","$FG"],["([7-9]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[7-9]","$NP$FG"],["(0\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","0",null]]]', "81": '["JP","010","0",null,null,"$NP$FG","\\\\d{8,17}","[1-9]\\\\d{8,9}|00(?:[36]\\\\d{7,14}|7\\\\d{5,7}|8\\\\d{7})",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1-$2-$3","(?:12|57|99)0",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1-$2-$3","800",null],["(\\\\d{4})(\\\\d{4})","$1-$2","0077","$FG","NA"],["(\\\\d{4})(\\\\d{2})(\\\\d{3,4})","$1-$2-$3","0077","$FG","NA"],["(\\\\d{4})(\\\\d{2})(\\\\d{4})","$1-$2-$3","0088","$FG","NA"],["(\\\\d{4})(\\\\d{3})(\\\\d{3,4})","$1-$2-$3","00(?:37|66)","$FG","NA"],["(\\\\d{4})(\\\\d{4})(\\\\d{4,5})","$1-$2-$3","00(?:37|66)","$FG","NA"],["(\\\\d{4})(\\\\d{5})(\\\\d{5,6})","$1-$2-$3","00(?:37|66)","$FG","NA"],["(\\\\d{4})(\\\\d{6})(\\\\d{6,7})","$1-$2-$3","00(?:37|66)","$FG","NA"],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1-$2-$3","[2579]0|80[1-9]",null],["(\\\\d{4})(\\\\d)(\\\\d{4})","$1-$2-$3","1(?:26|3[79]|4[56]|5[4-68]|6[3-5])|5(?:76|97)|499|746|8(?:3[89]|63|47|51)|9(?:49|80|9[16])",null],["(\\\\d{3})(\\\\d{2})(\\\\d{4})","$1-$2-$3","1(?:2[3-6]|3[3-9]|4[2-6]|5[2-8]|[68][2-7]|7[2-689]|9[1-578])|2(?:2[03-689]|3[3-58]|4[0-468]|5[04-8]|6[013-8]|7[06-9]|8[02-57-9]|9[13])|4(?:2[28]|3[689]|6[035-7]|7[05689]|80|9[3-5])|5(?:3[1-36-9]|4[4578]|5[013-8]|6[1-9]|7[2-8]|8[14-7]|9[4-9])|7(?:2[15]|3[5-9]|4[02-9]|6[135-8]|7[0-4689]|9[014-9])|8(?:2[49]|3[3-8]|4[5-8]|5[2-9]|6[35-9]|7[579]|8[03-579]|9[2-8])|9(?:[23]0|4[02-46-9]|5[024-79]|6[4-9]|7[2-47-9]|8[02-7]|9[3-7])",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1-$2-$3","1|2(?:2[37]|5[5-9]|64|78|8[39]|91)|4(?:2[2689]|64|7[347])|5(?:[2-589]|39)|60|8(?:[46-9]|3[279]|2[124589])|9(?:[235-8]|93)",null],["(\\\\d{3})(\\\\d{2})(\\\\d{4})","$1-$2-$3","2(?:9[14-79]|74|[34]7|[56]9)|82|993",null],["(\\\\d)(\\\\d{4})(\\\\d{4})","$1-$2-$3","3|4(?:2[09]|7[01])|6[1-9]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1-$2-$3","[2479][1-9]",null]]]', "237": '["CM","00",null,null,null,null,"\\\\d{8,9}","[2368]\\\\d{7,8}",[["([26])(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4 $5","[26]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[23]|88",null],["(800)(\\\\d{2})(\\\\d{3})","$1 $2 $3","80",null]]]', "351": '["PT","00",null,null,null,null,"\\\\d{9}","[2-46-9]\\\\d{8}",[["(2\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","2[12]",null],["([2-46-9]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","2[3-9]|[346-9]",null]]]', "246": '["IO","00",null,null,null,null,"\\\\d{7}","3\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]', "227": '["NE","00",null,null,null,null,"\\\\d{8}","[0289]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[289]|09",null],["(08)(\\\\d{3})(\\\\d{3})","$1 $2 $3","08",null]]]', "27": '["ZA","00","0",null,null,"$NP$FG","\\\\d{5,9}","[1-79]\\\\d{8}|8\\\\d{4,8}",[["(860)(\\\\d{3})(\\\\d{3})","$1 $2 $3","860",null],["(\\\\d{2})(\\\\d{3,4})","$1 $2","8[1-4]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{2,3})","$1 $2 $3","8[1-4]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[1-79]|8(?:[0-57]|6[1-9])",null]]]', "962": '["JO","00","0",null,null,"$NP$FG","\\\\d{8,9}","[235-9]\\\\d{7,8}",[["(\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[2356]|87","($NP$FG)"],["(7)(\\\\d{4})(\\\\d{4})","$1 $2 $3","7[457-9]",null],["(\\\\d{3})(\\\\d{5,6})","$1 $2","70|8[0158]|9",null]]]', "387": '["BA","00","0",null,null,"$NP$FG","\\\\d{6,9}","[3-9]\\\\d{7,8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2-$3","[3-5]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","6[1-356]|[7-9]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3 $4","6[047]",null]]]', "33": '["FR","00","0",null,null,"$NP$FG","\\\\d{9}","[1-9]\\\\d{8}",[["([1-79])(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4 $5","[1-79]",null],["(1\\\\d{2})(\\\\d{3})","$1 $2","11","$FG","NA"],["(8\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","8","$NP $FG"]]]', "972": '["IL","0(?:0|1[2-9])","0",null,null,"$FG","\\\\d{4,12}","1\\\\d{6,11}|[2-589]\\\\d{3}(?:\\\\d{3,6})?|6\\\\d{3}|7\\\\d{6,9}",[["([2-489])(\\\\d{3})(\\\\d{4})","$1-$2-$3","[2-489]","$NP$FG"],["([57]\\\\d)(\\\\d{3})(\\\\d{4})","$1-$2-$3","[57]","$NP$FG"],["(153)(\\\\d{1,2})(\\\\d{3})(\\\\d{4})","$1 $2 $3 $4","153",null],["(1)([7-9]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1-$2-$3-$4","1[7-9]",null],["(1255)(\\\\d{3})","$1-$2","125",null],["(1200)(\\\\d{3})(\\\\d{3})","$1-$2-$3","120",null],["(1212)(\\\\d{2})(\\\\d{2})","$1-$2-$3","121",null],["(1599)(\\\\d{6})","$1-$2","15",null],["(\\\\d{4})","*$1","[2-689]",null]]]', "248": '["SC","0(?:[02]|10?)",null,null,null,null,"\\\\d{6,7}","[24689]\\\\d{5,6}",[["(\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[246]",null]]]', "297": '["AW","00",null,null,null,null,"\\\\d{7}","[25-9]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]', "421": '["SK","00","0",null,null,"$NP$FG","\\\\d{6,9}","(?:[2-68]\\\\d{5,8}|9\\\\d{6,8})",[["(2)(1[67])(\\\\d{3,4})","$1 $2 $3","21[67]",null],["([3-5]\\\\d)(1[67])(\\\\d{2,3})","$1 $2 $3","[3-5]",null],["(2)(\\\\d{3})(\\\\d{3})(\\\\d{2})","$1/$2 $3 $4","2",null],["([3-5]\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1/$2 $3 $4","[3-5]",null],["([689]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[689]",null],["(9090)(\\\\d{3})","$1 $2","9090",null]]]', "672": '["NF","00",null,null,null,null,"\\\\d{5,6}","[13]\\\\d{5}",[["(\\\\d{2})(\\\\d{4})","$1 $2","1",null],["(\\\\d)(\\\\d{5})","$1 $2","3",null]]]', "870": '["001",null,null,null,null,null,"\\\\d{9}","[35-7]\\\\d{8}",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,null]]]', "883": '["001",null,null,null,null,null,"\\\\d{9}(?:\\\\d{3})?","51\\\\d{7}(?:\\\\d{3})?",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","510",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","510",null],["(\\\\d{4})(\\\\d{4})(\\\\d{4})","$1 $2 $3","51[13]",null]]]', "264": '["NA","00","0",null,null,"$NP$FG","\\\\d{8,9}","[68]\\\\d{7,8}",[["(8\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","8[1235]",null],["(6\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","6",null],["(88)(\\\\d{3})(\\\\d{3})","$1 $2 $3","88",null],["(870)(\\\\d{3})(\\\\d{3})","$1 $2 $3","870",null]]]', "878": '["001",null,null,null,null,null,"\\\\d{12}","1\\\\d{11}",[["(\\\\d{2})(\\\\d{5})(\\\\d{5})","$1 $2 $3",null,null]]]', "239": '["ST","00",null,null,null,null,"\\\\d{7}","[29]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]', "357": '["CY","00",null,null,null,null,"\\\\d{8}","[257-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{6})","$1 $2",null,null]]]', "240": '["GQ","00",null,null,null,null,"\\\\d{9}","[23589]\\\\d{8}",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[235]",null],["(\\\\d{3})(\\\\d{6})","$1 $2","[89]",null]]]', "506": '["CR","00",null,"(19(?:0[012468]|1[09]|20|66|77|99))",null,null,"\\\\d{8,10}","[24-9]\\\\d{7,9}",[["(\\\\d{4})(\\\\d{4})","$1 $2","[24-7]|8[3-9]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1-$2-$3","[89]0",null]]]', "86": '["CN","(1(?:[129]\\\\d{3}|79\\\\d{2}))?00","0","(1(?:[129]\\\\d{3}|79\\\\d{2}))|0",null,null,"\\\\d{4,12}","[1-7]\\\\d{6,11}|8[0-357-9]\\\\d{6,9}|9\\\\d{7,10}",[["(80\\\\d{2})(\\\\d{4})","$1 $2","80[2678]","$NP$FG"],["([48]00)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[48]00",null],["(\\\\d{5,6})","$1","100|95",null,"NA"],["(\\\\d{2})(\\\\d{5,6})","$1 $2","(?:10|2\\\\d)[19]","$NP$FG"],["(\\\\d{3})(\\\\d{5,6})","$1 $2","[3-9]","$NP$FG"],["(\\\\d{3,4})(\\\\d{4})","$1 $2","[2-9]",null,"NA"],["(21)(\\\\d{4})(\\\\d{4,6})","$1 $2 $3","21","$NP$FG"],["([12]\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","10[1-9]|2[02-9]","$NP$FG"],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","3(?:1[02-9]|35|49|5|7[02-68]|9[1-68])|4(?:1[02-9]|2[179]|[35][2-9]|6[4789]|7\\\\d|8[23])|5(?:3[03-9]|4[36]|5[02-9]|6[1-46]|7[028]|80|9[2-46-9])|6(?:3[1-5]|6[0238]|9[12])|7(?:01|[1579]|2[248]|3[04-9]|4[3-6]|6[2368])|8(?:1[236-8]|2[5-7]|3|5[1-9]|7[02-9]|8[3678]|9[1-7])|9(?:0[1-3689]|1[1-79]|[379]|4[13]|5[1-5])","$NP$FG"],["(\\\\d{3})(\\\\d{4})(\\\\d{4})","$1 $2 $3","3(?:11|7[179])|4(?:[15]1|3[1-35])|5(?:1|2[37]|3[12]|51|7[13-79]|9[15])|7(?:31|5[457]|6[09]|91)|8(?:[57]1|98)","$NP$FG"],["(\\\\d{4})(\\\\d{3})(\\\\d{4})","$1 $2 $3","807","$NP$FG"],["(\\\\d{3})(\\\\d{4})(\\\\d{4})","$1 $2 $3","1[3-578]",null],["(10800)(\\\\d{3})(\\\\d{4})","$1 $2 $3","108",null],["(\\\\d{3})(\\\\d{7,8})","$1 $2","950",null]]]', "257": '["BI","00",null,null,null,null,"\\\\d{8}","[267]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "683": '["NU","00",null,null,null,null,"\\\\d{4}","[1-5]\\\\d{3}"]', "43": '["AT","00","0",null,null,"$NP$FG","\\\\d{3,13}","[1-9]\\\\d{3,12}",[["(116\\\\d{3})","$1","116","$FG"],["(1)(\\\\d{3,12})","$1 $2","1",null],["(5\\\\d)(\\\\d{3,5})","$1 $2","5[079]",null],["(5\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","5[079]",null],["(5\\\\d)(\\\\d{4})(\\\\d{4,7})","$1 $2 $3","5[079]",null],["(\\\\d{3})(\\\\d{3,10})","$1 $2","316|46|51|732|6(?:5[0-3579]|[6-9])|7(?:[28]0)|[89]",null],["(\\\\d{4})(\\\\d{3,9})","$1 $2","2|3(?:1[1-578]|[3-8])|4[2378]|5[2-6]|6(?:[12]|4[1-9]|5[468])|7(?:2[1-8]|35|4[1-8]|[5-79])",null]]]', "247": '["AC","00",null,null,null,null,"\\\\d{5,6}","[46]\\\\d{4}|[01589]\\\\d{5}"]', "675": '["PG","00",null,null,null,null,"\\\\d{7,8}","[1-9]\\\\d{6,7}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[13-689]|27",null],["(\\\\d{4})(\\\\d{4})","$1 $2","20|7",null]]]', "376": '["AD","00",null,null,null,null,"\\\\d{6,9}","[16]\\\\d{5,8}|[37-9]\\\\d{5}",[["(\\\\d{3})(\\\\d{3})","$1 $2","[137-9]|6[0-8]",null],["(\\\\d{4})(\\\\d{4})","$1 $2","180",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","690",null]]]', "63": '["PH","00","0",null,null,null,"\\\\d{5,13}","2\\\\d{5,7}|[3-9]\\\\d{7,9}|1800\\\\d{7,9}",[["(2)(\\\\d{3})(\\\\d{4})","$1 $2 $3","2","($NP$FG)"],["(2)(\\\\d{5})","$1 $2","2","($NP$FG)"],["(\\\\d{4})(\\\\d{4,6})","$1 $2","3(?:23|39|46)|4(?:2[3-6]|[35]9|4[26]|76)|5(?:22|44)|642|8(?:62|8[245])","($NP$FG)"],["(\\\\d{5})(\\\\d{4})","$1 $2","346|4(?:27|9[35])|883","($NP$FG)"],["([3-8]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[3-8]","($NP$FG)"],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","81|9","$NP$FG"],["(1800)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1",null],["(1800)(\\\\d{1,2})(\\\\d{3})(\\\\d{4})","$1 $2 $3 $4","1",null]]]', "236": '["CF","00",null,null,null,null,"\\\\d{8}","[278]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "590": ['["GP","00","0",null,null,"$NP$FG","\\\\d{9}","[56]\\\\d{8}",[["([56]90)(\\\\d{2})(\\\\d{4})","$1 $2-$3",null,null]]]','["BL","00","0",null,null,null,"\\\\d{9}","[56]\\\\d{8}"]','["MF","00","0",null,null,null,"\\\\d{9}","[56]\\\\d{8}"]'], "53": '["CU","119","0",null,null,"($NP$FG)","\\\\d{4,8}","[2-57]\\\\d{5,7}",[["(\\\\d)(\\\\d{6,7})","$1 $2","7",null],["(\\\\d{2})(\\\\d{4,6})","$1 $2","[2-4]",null],["(\\\\d)(\\\\d{7})","$1 $2","5","$NP$FG"]]]', "64": '["NZ","0(?:0|161)","0",null,null,"$NP$FG","\\\\d{7,11}","6[235-9]\\\\d{6}|[2-57-9]\\\\d{7,10}",[["([34679])(\\\\d{3})(\\\\d{4})","$1-$2 $3","[346]|7[2-57-9]|9[1-9]",null],["(24099)(\\\\d{3})","$1 $2","240",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","21",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3,5})","$1 $2 $3","2(?:1[1-9]|[69]|7[0-35-9])|70|86",null],["(2\\\\d)(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","2[028]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","2(?:10|74)|5|[89]0",null]]]', "965": '["KW","00",null,null,null,null,"\\\\d{7,8}","[12569]\\\\d{6,7}",[["(\\\\d{4})(\\\\d{3,4})","$1 $2","[16]|2(?:[0-35-9]|4[0-35-9])|9[024-9]|52[25]",null],["(\\\\d{3})(\\\\d{5})","$1 $2","244|5(?:[015]|66)",null]]]', "224": '["GN","00",null,null,null,null,"\\\\d{8,9}","[367]\\\\d{7,8}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","3",null],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[67]",null]]]', "973": '["BH","00",null,null,null,null,"\\\\d{8}","[136-9]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]', "32": '["BE","00","0",null,null,"$NP$FG","\\\\d{8,9}","[1-9]\\\\d{7,8}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","4[6-9]",null],["(\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[23]|4[23]|9[2-4]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[156]|7[018]|8(?:0[1-9]|[1-79])",null],["(\\\\d{3})(\\\\d{2})(\\\\d{3})","$1 $2 $3","(?:80|9)0",null]]]', "249": '["SD","00","0",null,null,"$NP$FG","\\\\d{9}","[19]\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3",null,null]]]', "678": '["VU","00",null,null,null,null,"\\\\d{5,7}","[2-57-9]\\\\d{4,6}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[579]",null]]]', "52": '["MX","0[09]","01","0[12]|04[45](\\\\d{10})","1$1","$NP $FG","\\\\d{7,11}","[1-9]\\\\d{9,10}",[["([358]\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","33|55|81",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[2467]|3[0-2457-9]|5[089]|8[02-9]|9[0-35-9]",null],["(1)([358]\\\\d)(\\\\d{4})(\\\\d{4})","044 $2 $3 $4","1(?:33|55|81)","$FG","$1 $2 $3 $4"],["(1)(\\\\d{3})(\\\\d{3})(\\\\d{4})","044 $2 $3 $4","1(?:[2467]|3[0-2457-9]|5[089]|8[2-9]|9[1-35-9])","$FG","$1 $2 $3 $4"]]]', "968": '["OM","00",null,null,null,null,"\\\\d{7,9}","(?:5|[279]\\\\d)\\\\d{6}|800\\\\d{5,6}",[["(2\\\\d)(\\\\d{6})","$1 $2","2",null],["([79]\\\\d{3})(\\\\d{4})","$1 $2","[79]",null],["([58]00)(\\\\d{4,6})","$1 $2","[58]",null]]]', "599": ['["CW","00",null,null,null,null,"\\\\d{7,8}","[169]\\\\d{6,7}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[13-7]",null],["(9)(\\\\d{3})(\\\\d{4})","$1 $2 $3","9",null]]]','["BQ","00",null,null,null,null,"\\\\d{7}","[347]\\\\d{6}"]'], "800": '["001",null,null,null,null,null,"\\\\d{8}","\\\\d{8}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]', "386": '["SI","00","0",null,null,"$NP$FG","\\\\d{5,8}","[1-7]\\\\d{6,7}|[89]\\\\d{4,7}",[["(\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[12]|3[24-8]|4[24-8]|5[2-8]|7[3-8]","($NP$FG)"],["([3-7]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[37][01]|4[0139]|51|6",null],["([89][09])(\\\\d{3,6})","$1 $2","[89][09]",null],["([58]\\\\d{2})(\\\\d{5})","$1 $2","59|8[1-3]",null]]]', "679": '["FJ","0(?:0|52)",null,null,null,null,"\\\\d{7}(?:\\\\d{4})?","[35-9]\\\\d{6}|0\\\\d{10}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[35-9]",null],["(\\\\d{4})(\\\\d{3})(\\\\d{4})","$1 $2 $3","0",null]]]', "238": '["CV","0",null,null,null,null,"\\\\d{7}","[259]\\\\d{6}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3",null,null]]]', "691": '["FM","00",null,null,null,null,"\\\\d{7}","[39]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]', "262": ['["RE","00","0",null,null,"$NP$FG","\\\\d{9}","[268]\\\\d{8}",[["([268]\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]','["YT","00","0",null,null,"$NP$FG","\\\\d{9}","[268]\\\\d{8}"]'], "241": '["GA","00",null,null,null,null,"\\\\d{7,8}","0?\\\\d{7}",[["(\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[2-7]","0$FG"],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","0",null]]]', "370": '["LT","00","8","[08]",null,"($NP-$FG)","\\\\d{8}","[3-9]\\\\d{7}",[["([34]\\\\d)(\\\\d{6})","$1 $2","37|4(?:1|5[45]|6[2-4])",null],["([3-6]\\\\d{2})(\\\\d{5})","$1 $2","3[148]|4(?:[24]|6[09])|528|6",null],["([7-9]\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3","[7-9]","$NP $FG"],["(5)(2\\\\d{2})(\\\\d{4})","$1 $2 $3","52[0-79]",null]]]', "256": '["UG","00[057]","0",null,null,"$NP$FG","\\\\d{5,9}","\\\\d{9}",[["(\\\\d{3})(\\\\d{6})","$1 $2","[7-9]|20(?:[013-8]|2[5-9])|4(?:6[45]|[7-9])",null],["(\\\\d{2})(\\\\d{7})","$1 $2","3|4(?:[1-5]|6[0-36-9])",null],["(2024)(\\\\d{5})","$1 $2","2024",null]]]', "677": '["SB","0[01]",null,null,null,null,"\\\\d{5,7}","[1-9]\\\\d{4,6}",[["(\\\\d{2})(\\\\d{5})","$1 $2","[7-9]",null]]]', "377": '["MC","00","0",null,null,"$NP$FG","\\\\d{8,9}","[34689]\\\\d{7,8}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[39]","$FG"],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","4",null],["(6)(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4 $5","6",null],["(\\\\d{3})(\\\\d{3})(\\\\d{2})","$1 $2 $3","8","$FG"]]]', "382": '["ME","00","0",null,null,"$NP$FG","\\\\d{6,9}","[2-9]\\\\d{7,8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[2-57-9]|6[036-9]",null]]]', "231": '["LR","00","0",null,null,"$NP$FG","\\\\d{7,9}","2\\\\d{7,8}|[378]\\\\d{8}|4\\\\d{6}|5\\\\d{6,8}",[["(2\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","2",null],["([4-5])(\\\\d{3})(\\\\d{3})","$1 $2 $3","[45]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[23578]",null]]]', "591": '["BO","00(1\\\\d)?","0","0(1\\\\d)?",null,null,"\\\\d{7,8}","[23467]\\\\d{7}",[["([234])(\\\\d{7})","$1 $2","[234]",null],["([67]\\\\d{7})","$1","[67]",null]]]', "808": '["001",null,null,null,null,null,"\\\\d{8}","\\\\d{8}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]', "964": '["IQ","00","0",null,null,"$NP$FG","\\\\d{6,10}","[1-7]\\\\d{7,9}",[["(1)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1",null],["([2-6]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[2-6]",null],["(7\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","7",null]]]', "225": '["CI","00",null,null,null,null,"\\\\d{8}","[02-8]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "992": '["TJ","810","8",null,null,"$FG","\\\\d{3,9}","[3-57-9]\\\\d{8}",[["([349]\\\\d{2})(\\\\d{2})(\\\\d{4})","$1 $2 $3","[34]7|91[78]",null],["([457-9]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","4[148]|[578]|9(?:1[59]|[0235-9])",null],["(331700)(\\\\d)(\\\\d{2})","$1 $2 $3","331",null],["(\\\\d{4})(\\\\d)(\\\\d{4})","$1 $2 $3","3[1-5]",null]]]', "55": '["BR","00(?:1[245]|2[1-35]|31|4[13]|[56]5|99)","0","(?:0|90)(?:(1[245]|2[135]|[34]1)(\\\\d{10,11}))?","$2",null,"\\\\d{8,11}","[1-46-9]\\\\d{7,10}|5(?:[0-4]\\\\d{7,9}|5(?:[2-8]\\\\d{7}|9\\\\d{7,8}))",[["(\\\\d{4})(\\\\d{4})","$1-$2","[2-9](?:[1-9]|0[1-9])","$FG","NA"],["(\\\\d{5})(\\\\d{4})","$1-$2","9(?:[1-9]|0[1-9])","$FG","NA"],["(\\\\d{3,5})","$1","1[125689]","$FG","NA"],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1 $2-$3","[1-9][1-9]","($FG)"],["(\\\\d{2})(\\\\d{5})(\\\\d{4})","$1 $2-$3","(?:[14689][1-9]|2[12478]|3[1-578]|5[1-5]|7[13-579])9","($FG)"],["(\\\\d{4})(\\\\d{4})","$1-$2","(?:300|40(?:0|20))",null],["([3589]00)(\\\\d{2,3})(\\\\d{4})","$1 $2 $3","[3589]00","$NP$FG"]]]', "674": '["NR","00",null,null,null,null,"\\\\d{7}","[458]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]', "967": '["YE","00","0",null,null,"$NP$FG","\\\\d{6,9}","[1-7]\\\\d{6,8}",[["([1-7])(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[1-6]|7[24-68]",null],["(7\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","7[0137]",null]]]', "49": '["DE","00","0",null,null,"$NP$FG","\\\\d{2,15}","[1-35-9]\\\\d{3,14}|4(?:[0-8]\\\\d{3,12}|9(?:[0-37]\\\\d|4(?:[1-35-8]|4\\\\d?)|5\\\\d{1,2}|6[1-8]\\\\d?)\\\\d{2,8})",[["(1\\\\d{2})(\\\\d{7,8})","$1 $2","1[67]",null],["(15\\\\d{3})(\\\\d{6})","$1 $2","15[0568]",null],["(1\\\\d{3})(\\\\d{7})","$1 $2","15",null],["(\\\\d{2})(\\\\d{3,11})","$1 $2","3[02]|40|[68]9",null],["(\\\\d{3})(\\\\d{3,11})","$1 $2","2(?:\\\\d1|0[2389]|1[24]|28|34)|3(?:[3-9][15]|40)|[4-8][1-9]1|9(?:06|[1-9]1)",null],["(\\\\d{4})(\\\\d{2,11})","$1 $2","[24-6]|[7-9](?:\\\\d[1-9]|[1-9]\\\\d)|3(?:[3569][02-46-9]|4[2-4679]|7[2-467]|8[2-46-8])",null],["(3\\\\d{4})(\\\\d{1,10})","$1 $2","3",null],["(800)(\\\\d{7,12})","$1 $2","800",null],["(\\\\d{3})(\\\\d)(\\\\d{4,10})","$1 $2 $3","(?:18|90)0|137",null],["(1\\\\d{2})(\\\\d{5,11})","$1 $2","181",null],["(18\\\\d{3})(\\\\d{6})","$1 $2","185",null],["(18\\\\d{2})(\\\\d{7})","$1 $2","18[68]",null],["(18\\\\d)(\\\\d{8})","$1 $2","18[2-579]",null],["(700)(\\\\d{4})(\\\\d{4})","$1 $2 $3","700",null],["(138)(\\\\d{4})","$1 $2","138",null],["(15[013-68])(\\\\d{2})(\\\\d{8})","$1 $2 $3","15[013-68]",null],["(15[279]\\\\d)(\\\\d{2})(\\\\d{7})","$1 $2 $3","15[279]",null],["(1[67]\\\\d)(\\\\d{2})(\\\\d{7,8})","$1 $2 $3","1(?:6[023]|7)",null]]]', "31": '["NL","00","0",null,null,"$NP$FG","\\\\d{5,10}","1\\\\d{4,8}|[2-7]\\\\d{8}|[89]\\\\d{6,9}",[["([1-578]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1[035]|2[0346]|3[03568]|4[0356]|5[0358]|7|8[4578]",null],["([1-5]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","1[16-8]|2[259]|3[124]|4[17-9]|5[124679]",null],["(6)(\\\\d{8})","$1 $2","6[0-57-9]",null],["(66)(\\\\d{7})","$1 $2","66",null],["(14)(\\\\d{3,4})","$1 $2","14","$FG"],["([89]0\\\\d)(\\\\d{4,7})","$1 $2","80|9",null]]]', "970": '["PS","00","0",null,null,"$NP$FG","\\\\d{4,10}","[24589]\\\\d{7,8}|1(?:[78]\\\\d{8}|[49]\\\\d{2,3})",[["([2489])(2\\\\d{2})(\\\\d{4})","$1 $2 $3","[2489]",null],["(5[69]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","5",null],["(1[78]00)(\\\\d{3})(\\\\d{3})","$1 $2 $3","1[78]","$FG"]]]', "58": '["VE","00","0",null,null,"$NP$FG","\\\\d{7,10}","[24589]\\\\d{9}",[["(\\\\d{3})(\\\\d{7})","$1-$2",null,null]]]', "856": '["LA","00","0",null,null,"$NP$FG","\\\\d{6,10}","[2-8]\\\\d{7,9}",[["(20)(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","20",null],["([2-8]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","2[13]|3[14]|[4-8]",null],["(30)(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3 $4","30",null]]]', "354": '["IS","1(?:0(?:01|10|20)|100)|00",null,null,null,null,"\\\\d{7,9}","[4-9]\\\\d{6}|38\\\\d{7}",[["(\\\\d{3})(\\\\d{4})","$1 $2","[4-9]",null],["(3\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","3",null]]]', "242": '["CG","00",null,null,null,null,"\\\\d{9}","[028]\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[02]",null],["(\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","8",null]]]', "423": '["LI","00","0","0|10(?:01|20|66)",null,null,"\\\\d{7,9}","6\\\\d{8}|[23789]\\\\d{6}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3","[23789]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","6[56]",null],["(69)(7\\\\d{2})(\\\\d{4})","$1 $2 $3","697",null]]]', "213": '["DZ","00","0",null,null,"$NP$FG","\\\\d{8,9}","(?:[1-4]|[5-9]\\\\d)\\\\d{7}",[["([1-4]\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[1-4]",null],["([5-8]\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[5-8]",null],["(9\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","9",null]]]', "371": '["LV","00",null,null,null,null,"\\\\d{8}","[2689]\\\\d{7}",[["([2689]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,null]]]', "503": '["SV","00",null,null,null,null,"\\\\d{7,8}|\\\\d{11}","[267]\\\\d{7}|[89]\\\\d{6}(?:\\\\d{4})?",[["(\\\\d{4})(\\\\d{4})","$1 $2","[267]",null],["(\\\\d{3})(\\\\d{4})","$1 $2","[89]",null],["(\\\\d{3})(\\\\d{4})(\\\\d{4})","$1 $2 $3","[89]",null]]]', "685": '["WS","0",null,null,null,null,"\\\\d{5,7}","[2-8]\\\\d{4,6}",[["(8\\\\d{2})(\\\\d{3,4})","$1 $2","8",null],["(7\\\\d)(\\\\d{5})","$1 $2","7",null],["(\\\\d{5})","$1","[2-6]",null]]]', "880": '["BD","00","0",null,null,"$NP$FG","\\\\d{6,10}","[2-79]\\\\d{5,9}|1\\\\d{9}|8[0-7]\\\\d{4,8}",[["(2)(\\\\d{7,8})","$1-$2","2",null],["(\\\\d{2})(\\\\d{4,6})","$1-$2","[3-79]1",null],["(\\\\d{4})(\\\\d{3,6})","$1-$2","1|3(?:0|[2-58]2)|4(?:0|[25]2|3[23]|[4689][25])|5(?:[02-578]2|6[25])|6(?:[0347-9]2|[26][25])|7[02-9]2|8(?:[023][23]|[4-7]2)|9(?:[02][23]|[458]2|6[016])",null],["(\\\\d{3})(\\\\d{3,7})","$1-$2","[3-79][2-9]|8",null]]]', "265": '["MW","00","0",null,null,"$NP$FG","\\\\d{7,9}","(?:1(?:\\\\d{2})?|[2789]\\\\d{2})\\\\d{6}",[["(\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","1",null],["(2\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","2",null],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[1789]",null]]]', "65": '["SG","0[0-3]\\\\d",null,null,null,null,"\\\\d{8,11}","[36]\\\\d{7}|[17-9]\\\\d{7,10}",[["([3689]\\\\d{3})(\\\\d{4})","$1 $2","[369]|8[1-9]",null],["(1[89]00)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1[89]",null],["(7000)(\\\\d{4})(\\\\d{3})","$1 $2 $3","70",null],["(800)(\\\\d{3})(\\\\d{4})","$1 $2 $3","80",null]]]', "504": '["HN","00",null,null,null,null,"\\\\d{8}","[237-9]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1-$2",null,null]]]', "688": '["TV","00",null,null,null,null,"\\\\d{5,7}","[279]\\\\d{4,6}"]', "84": '["VN","00","0",null,null,"$NP$FG","\\\\d{7,10}","[167]\\\\d{6,9}|[2-59]\\\\d{7,9}|8\\\\d{6,8}",[["([17]99)(\\\\d{4})","$1 $2","[17]99",null],["([48])(\\\\d{4})(\\\\d{4})","$1 $2 $3","4|8(?:[1-57]|6[0-79]|9[0-7])",null],["([235-7]\\\\d)(\\\\d{4})(\\\\d{3})","$1 $2 $3","2[025-79]|3[0136-9]|5[2-9]|6[0-46-8]|7[02-79]",null],["(80)(\\\\d{5})","$1 $2","80",null],["(69\\\\d)(\\\\d{4,5})","$1 $2","69",null],["([235-7]\\\\d{2})(\\\\d{4})(\\\\d{3})","$1 $2 $3","2[0-489]|3[25]|5[01]|65|7[18]",null],["([89]\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","8(?:68|8|9[89])|9",null],["(1[2689]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1(?:[26]|8[68]|99)",null],["(1[89]00)(\\\\d{4,6})","$1 $2","1[89]0","$FG"]]]', "255": '["TZ","00[056]","0",null,null,"$NP$FG","\\\\d{7,9}","\\\\d{9}",[["([24]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[24]",null],["([67]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[67]",null],["([89]\\\\d{2})(\\\\d{2})(\\\\d{4})","$1 $2 $3","[89]",null]]]', "222": '["MR","00",null,null,null,null,"\\\\d{8}","[2-48]\\\\d{7}",[["([2-48]\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "230": '["MU","0(?:0|[2-7]0|33)",null,null,null,null,"\\\\d{7,8}","[2-9]\\\\d{6,7}",[["([2-46-9]\\\\d{2})(\\\\d{4})","$1 $2","[2-46-9]",null],["(5\\\\d{3})(\\\\d{4})","$1 $2","5",null]]]', "592": '["GY","001",null,null,null,null,"\\\\d{7}","[2-46-9]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]', "41": '["CH","00","0",null,null,"$NP$FG","\\\\d{9}(?:\\\\d{3})?","[2-9]\\\\d{8}|860\\\\d{9}",[["([2-9]\\\\d)(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[2-7]|[89]1",null],["([89]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","8[047]|90",null],["(\\\\d{3})(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4 $5","860",null]]]', "39": ['["IT","00",null,null,null,null,"\\\\d{6,11}","[01589]\\\\d{5,10}|3(?:[12457-9]\\\\d{8}|[36]\\\\d{7,9})",[["(\\\\d{2})(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","0[26]|55",null],["(0[26])(\\\\d{4})(\\\\d{5})","$1 $2 $3","0[26]",null],["(0[26])(\\\\d{4,6})","$1 $2","0[26]",null],["(0\\\\d{2})(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","0[13-57-9][0159]",null],["(\\\\d{3})(\\\\d{3,6})","$1 $2","0[13-57-9][0159]|8(?:03|4[17]|9[245])",null],["(0\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","0[13-57-9][2-46-8]",null],["(0\\\\d{3})(\\\\d{2,6})","$1 $2","0[13-57-9][2-46-8]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[13]|8(?:00|4[08]|9[59])",null],["(\\\\d{4})(\\\\d{4})","$1 $2","894",null],["(\\\\d{3})(\\\\d{4})(\\\\d{4})","$1 $2 $3","3",null]]]','["VA","00",null,null,null,null,"\\\\d{6,11}","(?:0(?:878\\\\d{5}|6698\\\\d{5})|[1589]\\\\d{5,10}|3(?:[12457-9]\\\\d{8}|[36]\\\\d{7,9}))"]'], "993": '["TM","810","8",null,null,"($NP $FG)","\\\\d{8}","[1-6]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2-$3-$4","12",null],["(\\\\d{2})(\\\\d{6})","$1 $2","6","$NP $FG"],["(\\\\d{3})(\\\\d)(\\\\d{2})(\\\\d{2})","$1 $2-$3-$4","13|[2-5]",null]]]', "888": '["001",null,null,null,null,null,"\\\\d{11}","\\\\d{11}",[["(\\\\d{3})(\\\\d{3})(\\\\d{5})","$1 $2 $3",null,null]]]', "353": '["IE","00","0",null,null,"($NP$FG)","\\\\d{5,10}","[124-9]\\\\d{6,9}",[["(1)(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","1",null],["(\\\\d{2})(\\\\d{5})","$1 $2","2[24-9]|47|58|6[237-9]|9[35-9]",null],["(\\\\d{3})(\\\\d{5})","$1 $2","40[24]|50[45]",null],["(48)(\\\\d{4})(\\\\d{4})","$1 $2 $3","48",null],["(818)(\\\\d{3})(\\\\d{3})","$1 $2 $3","81",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[24-69]|7[14]",null],["([78]\\\\d)(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","76|8[35-9]","$NP$FG"],["(700)(\\\\d{3})(\\\\d{3})","$1 $2 $3","70","$NP$FG"],["(\\\\d{4})(\\\\d{3})(\\\\d{3})","$1 $2 $3","1(?:8[059]|5)","$FG"]]]', "966": '["SA","00","0",null,null,"$NP$FG","\\\\d{7,10}","1\\\\d{7,8}|(?:[2-467]|92)\\\\d{7}|5\\\\d{8}|8\\\\d{9}",[["([1-467])(\\\\d{3})(\\\\d{4})","$1 $2 $3","[1-467]",null],["(1\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1[1-467]",null],["(5\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","5",null],["(92\\\\d{2})(\\\\d{5})","$1 $2","92","$FG"],["(800)(\\\\d{3})(\\\\d{4})","$1 $2 $3","80","$FG"],["(811)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","81",null]]]', "380": '["UA","00","0",null,null,"$NP$FG","\\\\d{5,9}","[3-9]\\\\d{8}",[["([3-9]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[38]9|4(?:[45][0-5]|87)|5(?:0|6[37]|7[37])|6[36-8]|7|9[1-9]",null],["([3-689]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","3[1-8]2|4[13678]2|5(?:[12457]2|6[24])|6(?:[49]2|[12][29]|5[24])|8[0-8]|90",null],["([3-6]\\\\d{3})(\\\\d{5})","$1 $2","3(?:5[013-9]|[1-46-8])|4(?:[137][013-9]|6|[45][6-9]|8[4-6])|5(?:[1245][013-9]|6[0135-9]|3|7[4-6])|6(?:[49][013-9]|5[0135-9]|[12][13-8])",null]]]', "98": '["IR","00","0",null,null,"$NP$FG","\\\\d{4,10}","[1-8]\\\\d{9}|9(?:[0-4]\\\\d{8}|9\\\\d{2,8})",[["(21)(\\\\d{3,5})","$1 $2","21",null],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1 $2 $3","[1-8]",null],["(\\\\d{3})(\\\\d{3})","$1 $2","9",null],["(\\\\d{3})(\\\\d{2})(\\\\d{2,3})","$1 $2 $3","9",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","9",null]]]', "971": '["AE","00","0",null,null,"$NP$FG","\\\\d{5,12}","[2-79]\\\\d{7,8}|800\\\\d{2,9}",[["([2-4679])(\\\\d{3})(\\\\d{4})","$1 $2 $3","[2-4679][2-8]",null],["(5\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","5",null],["([479]00)(\\\\d)(\\\\d{5})","$1 $2 $3","[479]0","$FG"],["([68]00)(\\\\d{2,9})","$1 $2","60|8","$FG"]]]', "30": '["GR","00",null,null,null,null,"\\\\d{10}","[26-9]\\\\d{9}",[["([27]\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","21|7",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","2[2-9]1|[689]",null],["(2\\\\d{3})(\\\\d{6})","$1 $2","2[2-9][02-9]",null]]]', "228": '["TG","00",null,null,null,null,"\\\\d{8}","[29]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[29]",null]]]', "48": '["PL","00",null,null,null,null,"\\\\d{6,9}","[12]\\\\d{6,8}|[3-57-9]\\\\d{8}|6\\\\d{5,8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[14]|2[0-57-9]|3[2-4]|5[24-689]|6[1-3578]|7[14-7]|8[1-79]|9[145]",null],["(\\\\d{2})(\\\\d{1})(\\\\d{4})","$1 $2 $3","[12]2",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","26|39|5[0137]|6[0469]|7[02389]|8[08]",null],["(\\\\d{3})(\\\\d{2})(\\\\d{2,3})","$1 $2 $3","64",null],["(\\\\d{3})(\\\\d{3})","$1 $2","64",null]]]', "886": '["TW","0(?:0[25679]|19)","0",null,null,"$NP$FG","\\\\d{7,10}","2\\\\d{6,8}|[3-689]\\\\d{7,8}|7\\\\d{7,9}",[["(20)(\\\\d)(\\\\d{4})","$1 $2 $3","202",null],["(20)(\\\\d{3})(\\\\d{4})","$1 $2 $3","20[013-9]",null],["([2-8])(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","2[23-8]|[3-6]|[78][1-9]",null],["([89]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","80|9",null],["(70)(\\\\d{4})(\\\\d{4})","$1 $2 $3","70",null]]]', "212": ['["MA","00","0",null,null,"$NP$FG","\\\\d{9}","[5-9]\\\\d{8}",[["([5-7]\\\\d{2})(\\\\d{6})","$1-$2","5(?:2[015-7]|3[0-4])|[67]",null],["([58]\\\\d{3})(\\\\d{5})","$1-$2","5(?:2[2-489]|3[5-9]|92)|892",null],["(5\\\\d{4})(\\\\d{4})","$1-$2","5(?:29|38)",null],["([5]\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","5(?:4[067]|5[03])",null],["(8[09])(\\\\d{7})","$1-$2","8(?:0|9[013-9])",null]]]','["EH","00","0",null,null,"$NP$FG","\\\\d{9}","[5-9]\\\\d{8}"]'], "372": '["EE","00",null,null,null,null,"\\\\d{4,10}","1\\\\d{3,4}|[3-9]\\\\d{6,7}|800\\\\d{6,7}",[["([3-79]\\\\d{2})(\\\\d{4})","$1 $2","[369]|4[3-8]|5(?:[0-2]|5[0-478]|6[45])|7[1-9]",null],["(70)(\\\\d{2})(\\\\d{4})","$1 $2 $3","70",null],["(8000)(\\\\d{3})(\\\\d{3})","$1 $2 $3","800",null],["([458]\\\\d{3})(\\\\d{3,4})","$1 $2","40|5|8(?:00|[1-5])",null]]]', "598": '["UY","0(?:1[3-9]\\\\d|0)","0",null,null,null,"\\\\d{7,8}","[2489]\\\\d{6,7}",[["(\\\\d{4})(\\\\d{4})","$1 $2","[24]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","9[1-9]","$NP$FG"],["(\\\\d{3})(\\\\d{4})","$1 $2","[89]0","$NP$FG"]]]', "502": '["GT","00",null,null,null,null,"\\\\d{8}(?:\\\\d{3})?","[2-7]\\\\d{7}|1[89]\\\\d{9}",[["(\\\\d{4})(\\\\d{4})","$1 $2","[2-7]",null],["(\\\\d{4})(\\\\d{3})(\\\\d{4})","$1 $2 $3","1",null]]]', "82": '["KR","00(?:[124-68]|3\\\\d{2}|7(?:[0-8]\\\\d|9[0-79]))","0","0(8[1-46-8]|85\\\\d{2})?",null,"$NP$FG","\\\\d{3,14}","007\\\\d{9,11}|[1-7]\\\\d{3,9}|8\\\\d{8}",[["(\\\\d{5})(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","00798","$FG","NA"],["(\\\\d{5})(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3 $4","00798","$FG","NA"],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1-$2-$3","1(?:0|1[19]|[69]9|5[458])|[57]0",null],["(\\\\d{2})(\\\\d{3,4})(\\\\d{4})","$1-$2-$3","1(?:[01]|5[1-4]|6[2-8]|[7-9])|[68]0|[3-6][1-9][1-9]",null],["(\\\\d{3})(\\\\d)(\\\\d{4})","$1-$2-$3","131",null],["(\\\\d{3})(\\\\d{2})(\\\\d{4})","$1-$2-$3","131",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1-$2-$3","13[2-9]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1-$2-$3-$4","30",null],["(\\\\d)(\\\\d{3,4})(\\\\d{4})","$1-$2-$3","2[1-9]",null],["(\\\\d)(\\\\d{3,4})","$1-$2","21[0-46-9]",null],["(\\\\d{2})(\\\\d{3,4})","$1-$2","[3-6][1-9]1",null],["(\\\\d{4})(\\\\d{4})","$1-$2","1(?:5[246-9]|6[04678]|8[03579])","$FG"]]]', "253": '["DJ","00",null,null,null,null,"\\\\d{8}","[27]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "91": '["IN","00","0",null,null,"$NP$FG","\\\\d{6,13}","008\\\\d{9}|1\\\\d{7,12}|[2-9]\\\\d{9,10}",[["(\\\\d{5})(\\\\d{5})","$1 $2","600|7(?:[02-8]|19|9[037-9])|8(?:0[015-9]|[1-9]|20)|9",null],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1 $2 $3","11|2[02]|33|4[04]|79[1-9]|80[2-46]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","1(?:2[0-249]|3[0-25]|4[145]|[59][14]|7[1257]|[68][1-9])|2(?:1[257]|3[013]|4[01]|5[0137]|6[0158]|78|8[1568]|9[14])|3(?:26|4[1-3]|5[34]|6[01489]|7[02-46]|8[159])|4(?:1[36]|2[1-47]|3[15]|5[12]|6[0-26-9]|7[0-24-9]|8[013-57]|9[014-7])|5(?:1[025]|[36][25]|22|4[28]|5[12]|[78]1|9[15])|6(?:12|[2-4]1|5[17]|6[13]|7[14]|80)|7(?:12|2[14]|3[134]|4[47]|5[15]|[67]1|88)|8(?:16|2[014]|3[126]|6[136]|7[078]|8[34]|91)",null],["(\\\\d{4})(\\\\d{3})(\\\\d{3})","$1 $2 $3","1(?:[23579]|[468][1-9])|[2-8]",null],["(\\\\d{2})(\\\\d{3})(\\\\d{4})(\\\\d{3})","$1 $2 $3 $4","008",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","140","$FG"],["(\\\\d{4})(\\\\d{2})(\\\\d{4})","$1 $2 $3","160","$FG"],["(\\\\d{4})(\\\\d{4,5})","$1 $2","180","$FG"],["(\\\\d{4})(\\\\d{2,4})(\\\\d{4})","$1 $2 $3","180","$FG"],["(\\\\d{4})(\\\\d{3,4})(\\\\d{4})","$1 $2 $3","186","$FG"],["(\\\\d{4})(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3 $4","18[06]","$FG"]]]', "389": '["MK","00","0",null,null,"$NP$FG","\\\\d{6,8}","[2-578]\\\\d{7}",[["(2)(\\\\d{3})(\\\\d{4})","$1 $2 $3","2",null],["([347]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[347]",null],["([58]\\\\d{2})(\\\\d)(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[58]",null]]]', "1": ['["US","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2-9]\\\\d{9}",[["(\\\\d{3})(\\\\d{4})","$1-$2",null,null,"NA"],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","($1) $2-$3",null,null,"$1-$2-$3"]]]','["AI","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2589]\\\\d{9}"]','["AS","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5689]\\\\d{9}"]','["BB","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2589]\\\\d{9}"]','["BM","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[4589]\\\\d{9}"]','["BS","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2589]\\\\d{9}"]','["CA","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2-9]\\\\d{9}|3\\\\d{6}"]','["DM","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[57-9]\\\\d{9}"]','["DO","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[589]\\\\d{9}"]','["GD","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[4589]\\\\d{9}"]','["GU","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5689]\\\\d{9}"]','["JM","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[589]\\\\d{9}"]','["KN","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[589]\\\\d{9}"]','["KY","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[3589]\\\\d{9}"]','["LC","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5789]\\\\d{9}"]','["MP","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5689]\\\\d{9}"]','["MS","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5689]\\\\d{9}"]','["PR","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5789]\\\\d{9}"]','["SX","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5789]\\\\d{9}"]','["TC","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5689]\\\\d{9}"]','["TT","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[589]\\\\d{9}"]','["AG","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2589]\\\\d{9}"]','["VC","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[5789]\\\\d{9}"]','["VG","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[2589]\\\\d{9}"]','["VI","011","1",null,null,null,"\\\\d{7}(?:\\\\d{3})?","[3589]\\\\d{9}"]'], "60": '["MY","00","0",null,null,null,"\\\\d{6,10}","[13-9]\\\\d{7,9}",[["([4-79])(\\\\d{3})(\\\\d{4})","$1-$2 $3","[4-79]","$NP$FG"],["(3)(\\\\d{4})(\\\\d{4})","$1-$2 $3","3","$NP$FG"],["([18]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1-$2 $3","1[02-46-9][1-9]|8","$NP$FG"],["(1)([36-8]00)(\\\\d{2})(\\\\d{4})","$1-$2-$3-$4","1[36-8]0",null],["(11)(\\\\d{4})(\\\\d{4})","$1-$2 $3","11","$NP$FG"],["(15[49])(\\\\d{3})(\\\\d{4})","$1-$2 $3","15","$NP$FG"]]]', "355": '["AL","00","0",null,null,"$NP$FG","\\\\d{5,9}","[2-57]\\\\d{7}|6\\\\d{8}|8\\\\d{5,7}|9\\\\d{5}",[["(4)(\\\\d{3})(\\\\d{4})","$1 $2 $3","4[0-6]",null],["(6\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","6",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[2358][2-5]|4[7-9]",null],["(\\\\d{3})(\\\\d{3,5})","$1 $2","[235][16-9]|8[016-9]|[79]",null]]]', "254": '["KE","000","0","005|0",null,"$NP$FG","\\\\d{7,10}","20\\\\d{6,7}|[4-9]\\\\d{6,9}",[["(\\\\d{2})(\\\\d{5,7})","$1 $2","[24-6]",null],["(\\\\d{3})(\\\\d{6})","$1 $2","7",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[89]",null]]]', "223": '["ML","00",null,null,null,null,"\\\\d{8}","[246-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[246-9]",null],["(\\\\d{4})","$1","67|74",null,"NA"]]]', "686": '["KI","00",null,"0",null,null,"\\\\d{5,8}","[2458]\\\\d{4}|3\\\\d{4,7}|7\\\\d{7}"]', "994": '["AZ","00","0",null,null,"($NP$FG)","\\\\d{7,9}","[1-9]\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","(?:1[28]|2(?:[45]2|[0-36])|365)",null],["(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[4-8]","$NP$FG"],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","9","$NP$FG"]]]', "979": '["001",null,null,null,null,null,"\\\\d{9}","\\\\d{9}",[["(\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3",null,null]]]', "66": '["TH","00","0",null,null,"$NP$FG","\\\\d{4}|\\\\d{8,10}","[2-9]\\\\d{7,8}|1\\\\d{3}(?:\\\\d{5,6})?",[["(2)(\\\\d{3})(\\\\d{4})","$1 $2 $3","2",null],["([13-9]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","14|[3-9]",null],["(1[89]00)(\\\\d{3})(\\\\d{3})","$1 $2 $3","1","$FG"]]]', "233": '["GH","00","0",null,null,"$NP$FG","\\\\d{7,9}","[235]\\\\d{8}|8\\\\d{7}",[["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[235]",null],["(\\\\d{3})(\\\\d{5})","$1 $2","8",null]]]', "593": '["EC","00","0",null,null,"($NP$FG)","\\\\d{7,11}","1\\\\d{9,10}|[2-8]\\\\d{7}|9\\\\d{8}",[["(\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2-$3","[247]|[356][2-8]",null,"$1-$2-$3"],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","9","$NP$FG"],["(1800)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","1","$FG"]]]', "509": '["HT","00",null,null,null,null,"\\\\d{8}","[2-489]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{4})","$1 $2 $3",null,null]]]', "54": '["AR","00","0","0?(?:(11|2(?:2(?:02?|[13]|2[13-79]|4[1-6]|5[2457]|6[124-8]|7[1-4]|8[13-6]|9[1267])|3(?:02?|1[467]|2[03-6]|3[13-8]|[49][2-6]|5[2-8]|[67])|4(?:7[3-578]|9)|6(?:[0136]|2[24-6]|4[6-8]?|5[15-8])|80|9(?:0[1-3]|[19]|2\\\\d|3[1-6]|4[02568]?|5[2-4]|6[2-46]|72?|8[23]?))|3(?:3(?:2[79]|6|8[2578])|4(?:0[0-24-9]|[12]|3[5-8]?|4[24-7]|5[4-68]?|6[02-9]|7[126]|8[2379]?|9[1-36-8])|5(?:1|2[1245]|3[237]?|4[1-46-9]|6[2-4]|7[1-6]|8[2-5]?)|6[24]|7(?:[069]|1[1568]|2[15]|3[145]|4[13]|5[14-8]|7[2-57]|8[126])|8(?:[01]|2[15-7]|3[2578]?|4[13-6]|5[4-8]?|6[1-357-9]|7[36-8]?|8[5-8]?|9[124])))?15)?","9$1","$NP$FG","\\\\d{6,11}","11\\\\d{8}|[2368]\\\\d{9}|9\\\\d{10}",[["([68]\\\\d{2})(\\\\d{3})(\\\\d{4})","$1-$2-$3","[68]",null],["(\\\\d{2})(\\\\d{4})","$1-$2","[2-9]","$FG","NA"],["(\\\\d{3})(\\\\d{4})","$1-$2","[2-9]","$FG","NA"],["(\\\\d{4})(\\\\d{4})","$1-$2","[2-9]","$FG","NA"],["(9)(11)(\\\\d{4})(\\\\d{4})","$2 15-$3-$4","911",null,"$1 $2 $3-$4"],["(9)(\\\\d{3})(\\\\d{3})(\\\\d{4})","$2 15-$3-$4","9(?:2[234689]|3[3-8])",null,"$1 $2 $3-$4"],["(9)(\\\\d{4})(\\\\d{2})(\\\\d{4})","$2 15-$3-$4","9[23]",null,"$1 $2 $3-$4"],["(11)(\\\\d{4})(\\\\d{4})","$1 $2-$3","1",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2-$3","2(?:2[013]|3[067]|49|6[01346]|80|9[147-9])|3(?:36|4[1-358]|5[138]|6[24]|7[069]|8[013578])",null],["(\\\\d{4})(\\\\d{2})(\\\\d{4})","$1 $2-$3","[23]",null],["(\\\\d{3})","$1","1[012]|911","$FG","NA"]]]', "57": '["CO","00(?:4(?:[14]4|56)|[579])","0","0([3579]|4(?:44|56))?",null,null,"\\\\d{7,11}","(?:[13]\\\\d{0,3}|[24-8])\\\\d{7}",[["(\\\\d)(\\\\d{7})","$1 $2","1(?:8[2-9]|9[0-3]|[2-7])|[24-8]","($FG)"],["(\\\\d{3})(\\\\d{7})","$1 $2","3",null],["(1)(\\\\d{3})(\\\\d{7})","$1-$2-$3","1(?:80|9[04])","$NP$FG","$1 $2 $3"]]]', "597": '["SR","00",null,null,null,null,"\\\\d{6,7}","[2-8]\\\\d{5,6}",[["(\\\\d{3})(\\\\d{3})","$1-$2","[2-4]|5[2-58]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1-$2-$3","56",null],["(\\\\d{3})(\\\\d{4})","$1-$2","[6-8]",null]]]', "676": '["TO","00",null,null,null,null,"\\\\d{5,7}","[02-8]\\\\d{4,6}",[["(\\\\d{2})(\\\\d{3})","$1-$2","[1-6]|7[0-4]|8[05]",null],["(\\\\d{3})(\\\\d{4})","$1 $2","7[5-9]|8[47-9]",null],["(\\\\d{4})(\\\\d{3})","$1 $2","0",null]]]', "505": '["NI","00",null,null,null,null,"\\\\d{8}","[12578]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1 $2",null,null]]]', "850": '["KP","00|99","0",null,null,"$NP$FG","\\\\d{6,8}|\\\\d{10}","1\\\\d{9}|[28]\\\\d{7}",[["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","1",null],["(\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","2",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","8",null]]]', "7": ['["RU","810","8",null,null,"$NP ($FG)","\\\\d{10}","[3489]\\\\d{9}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1-$2-$3","[1-79]","$FG","NA"],["([3489]\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2-$3-$4","[34689]",null],["(7\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","7",null]]]','["KZ","810","8",null,null,null,"\\\\d{10}","(?:33\\\\d|7\\\\d{2}|80[09])\\\\d{7}"]'], "268": '["SZ","00",null,null,null,null,"\\\\d{8}","[027]\\\\d{7}",[["(\\\\d{4})(\\\\d{4})","$1 $2","[027]",null]]]', "501": '["BZ","00",null,null,null,null,"\\\\d{7}(?:\\\\d{4})?","[2-8]\\\\d{6}|0\\\\d{10}",[["(\\\\d{3})(\\\\d{4})","$1-$2","[2-8]",null],["(0)(800)(\\\\d{4})(\\\\d{3})","$1-$2-$3-$4","0",null]]]', "252": '["SO","00","0",null,null,null,"\\\\d{6,9}","[1-9]\\\\d{5,8}",[["(\\\\d{6})","$1","[134]",null],["(\\\\d)(\\\\d{6})","$1 $2","2[0-79]|[13-5]",null],["(\\\\d)(\\\\d{7})","$1 $2","24|[67]",null],["(\\\\d{2})(\\\\d{4})","$1 $2","8[125]",null],["(\\\\d{2})(\\\\d{5,7})","$1 $2","15|28|6[1-35-9]|799|9[2-9]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","3[59]|4[89]|6[24-6]|79|8[08]|90",null]]]', "229": '["BJ","00",null,null,null,null,"\\\\d{4,8}","[2689]\\\\d{7}|7\\\\d{3}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "680": '["PW","01[12]",null,null,null,null,"\\\\d{7}","[2-8]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]', "263": '["ZW","00","0",null,null,"$NP$FG","\\\\d{3,10}","2(?:[012457-9]\\\\d{3,8}|6(?:[14]\\\\d{7}|\\\\d{4}))|[13-79]\\\\d{4,9}|8[06]\\\\d{8}",[["([49])(\\\\d{3})(\\\\d{2,4})","$1 $2 $3","4|9[2-9]",null],["(7\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","7",null],["(86\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","86[24]",null],["([2356]\\\\d{2})(\\\\d{3,5})","$1 $2","2(?:0[45]|2[278]|[49]8|[78])|3(?:08|17|3[78]|7[1569]|8[37]|98)|5[15][78]|6(?:[29]8|[38]7|6[78]|75|[89]8)",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","2(?:1[39]|2[0157]|6[14]|7[35]|84)|329",null],["([1-356]\\\\d)(\\\\d{3,5})","$1 $2","1[3-9]|2[0569]|3[0-69]|5[05689]|6[0-46-9]",null],["([235]\\\\d)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[23]9|54",null],["([25]\\\\d{3})(\\\\d{3,5})","$1 $2","(?:25|54)8",null],["(8\\\\d{3})(\\\\d{6})","$1 $2","86",null],["(80\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","80",null]]]', "90": '["TR","00","0",null,null,null,"\\\\d{7,10}","[2-589]\\\\d{9}|444\\\\d{4}",[["(\\\\d{3})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[23]|4(?:[0-35-9]|4[0-35-9])","($NP$FG)"],["(\\\\d{3})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","5[02-69]","$NP$FG"],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","51|[89]","$NP$FG"],["(444)(\\\\d{1})(\\\\d{3})","$1 $2 $3","444",null]]]', "352": '["LU","00",null,"(15(?:0[06]|1[12]|35|4[04]|55|6[26]|77|88|99)\\\\d)",null,null,"\\\\d{4,11}","[24-9]\\\\d{3,10}|3(?:[0-46-9]\\\\d{2,9}|5[013-9]\\\\d{1,8})",[["(\\\\d{2})(\\\\d{3})","$1 $2","[2-5]|7[1-9]|[89](?:[1-9]|0[2-9])",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3","[2-5]|7[1-9]|[89](?:[1-9]|0[2-9])",null],["(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3","20",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{1,2})","$1 $2 $3 $4","2(?:[0367]|4[3-8])",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3 $4","20",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{1,2})","$1 $2 $3 $4 $5","2(?:[0367]|4[3-8])",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{1,4})","$1 $2 $3 $4","2(?:[12589]|4[12])|[3-5]|7[1-9]|8(?:[1-9]|0[2-9])|9(?:[1-9]|0[2-46-9])",null],["(\\\\d{3})(\\\\d{2})(\\\\d{3})","$1 $2 $3","70|80[01]|90[015]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","6",null]]]', "47": ['["NO","00",null,null,null,null,"\\\\d{5}(?:\\\\d{3})?","0\\\\d{4}|[2-9]\\\\d{7}",[["([489]\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3","[489]",null],["([235-7]\\\\d)(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[235-7]",null]]]','["SJ","00",null,null,null,null,"\\\\d{5}(?:\\\\d{3})?","0\\\\d{4}|[45789]\\\\d{7}"]'], "243": '["CD","00","0",null,null,"$NP$FG","\\\\d{7,9}","[2-6]\\\\d{6}|[18]\\\\d{6,8}|9\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","12",null],["([89]\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","8[0-2459]|9",null],["(\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3","88",null],["(\\\\d{2})(\\\\d{5})","$1 $2","[1-6]",null]]]', "220": '["GM","00",null,null,null,null,"\\\\d{7}","[2-9]\\\\d{6}",[["(\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]', "687": '["NC","00",null,null,null,null,"\\\\d{6}","[2-57-9]\\\\d{5}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1.$2.$3","[2-46-9]|5[0-4]",null]]]', "995": '["GE","00","0",null,null,null,"\\\\d{6,9}","[34578]\\\\d{8}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[348]","$NP$FG"],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","7","$NP$FG"],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","5","$FG"]]]', "961": '["LB","00","0",null,null,null,"\\\\d{7,8}","[13-9]\\\\d{6,7}",[["(\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[13-6]|7(?:[2-57]|62|8[0-7]|9[04-9])|8[02-9]|9","$NP$FG"],["([7-9]\\\\d)(\\\\d{3})(\\\\d{3})","$1 $2 $3","[89][01]|7(?:[01]|6[013-9]|8[89]|9[1-3])",null]]]', "40": '["RO","00","0",null,null,"$NP$FG","\\\\d{6,9}","[23]\\\\d{5,8}|[7-9]\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[23]1",null],["(\\\\d{2})(\\\\d{4})","$1 $2","[23]1",null],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[23][3-7]|[7-9]",null],["(2\\\\d{2})(\\\\d{3})","$1 $2","2[3-6]",null]]]', "232": '["SL","00","0",null,null,"($NP$FG)","\\\\d{6,8}","[2-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{6})","$1 $2",null,null]]]', "594": '["GF","00","0",null,null,"$NP$FG","\\\\d{9}","[56]\\\\d{8}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "976": '["MN","001","0",null,null,"$NP$FG","\\\\d{6,10}","[12]\\\\d{7,9}|[57-9]\\\\d{7}",[["([12]\\\\d)(\\\\d{2})(\\\\d{4})","$1 $2 $3","[12]1",null],["([12]2\\\\d)(\\\\d{5,6})","$1 $2","[12]2[1-3]",null],["([12]\\\\d{3})(\\\\d{5})","$1 $2","[12](?:27|[3-5])",null],["(\\\\d{4})(\\\\d{4})","$1 $2","[57-9]","$FG"],["([12]\\\\d{4})(\\\\d{4,5})","$1 $2","[12](?:27|[3-5])",null]]]', "20": '["EG","00","0",null,null,"$NP$FG","\\\\d{5,10}","1\\\\d{4,9}|[2456]\\\\d{8}|3\\\\d{7}|[89]\\\\d{8,9}",[["(\\\\d)(\\\\d{7,8})","$1 $2","[23]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","1[012]|[89]00",null],["(\\\\d{2})(\\\\d{6,7})","$1 $2","1[35]|[4-6]|[89][2-9]",null]]]', "689": '["PF","00",null,null,null,null,"\\\\d{6}(?:\\\\d{2})?","4\\\\d{5,7}|8\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","4[09]|8[79]",null],["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3","44",null]]]', "56": '["CL","(?:0|1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))0","0","0|(1(?:1[0-69]|2[0-57]|5[13-58]|69|7[0167]|8[018]))",null,"$NP$FG","\\\\d{7,11}","(?:[2-9]|600|123)\\\\d{7,8}",[["(\\\\d)(\\\\d{4})(\\\\d{4})","$1 $2 $3","2[23]","($FG)"],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[357]|4[1-35]|6[13-57]","($FG)"],["(9)(\\\\d{4})(\\\\d{4})","$1 $2 $3","9",null],["(44)(\\\\d{3})(\\\\d{4})","$1 $2 $3","44",null],["([68]00)(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","60|8","$FG"],["(600)(\\\\d{3})(\\\\d{2})(\\\\d{3})","$1 $2 $3 $4","60","$FG"],["(1230)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1","$FG"],["(\\\\d{5})(\\\\d{4})","$1 $2","219","($FG)"],["(\\\\d{4,5})","$1","[1-9]","$FG","NA"]]]', "596": '["MQ","00","0",null,null,"$NP$FG","\\\\d{9}","[56]\\\\d{8}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "508": '["PM","00","0",null,null,"$NP$FG","\\\\d{6}","[45]\\\\d{5}",[["([45]\\\\d)(\\\\d{2})(\\\\d{2})","$1 $2 $3",null,null]]]', "269": '["KM","00",null,null,null,null,"\\\\d{7}","[3478]\\\\d{6}",[["(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3",null,null]]]', "358": ['["FI","00|99(?:[02469]|5(?:11|33|5[59]|88|9[09]))","0",null,null,"$NP$FG","\\\\d{5,12}","1\\\\d{4,11}|[2-9]\\\\d{4,10}",[["(\\\\d{3})(\\\\d{3,7})","$1 $2","(?:[1-3]00|[6-8]0)",null],["(116\\\\d{3})","$1","116","$FG"],["(\\\\d{2})(\\\\d{4,10})","$1 $2","[14]|2[09]|50|7[135]",null],["(\\\\d)(\\\\d{4,11})","$1 $2","[25689][1-8]|3",null]]]','["AX","00|99(?:[02469]|5(?:11|33|5[59]|88|9[09]))","0",null,null,"$NP$FG","\\\\d{5,12}","1\\\\d{5,11}|[35]\\\\d{5,9}|[27]\\\\d{4,9}|4\\\\d{5,10}|6\\\\d{7,9}|8\\\\d{6,9}"]'], "251": '["ET","00","0",null,null,"$NP$FG","\\\\d{7,9}","[1-59]\\\\d{8}",[["([1-59]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3",null,null]]]', "681": '["WF","00",null,null,null,null,"\\\\d{6}","[4-8]\\\\d{5}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3",null,null]]]', "853": '["MO","00",null,null,null,null,"\\\\d{8}","[268]\\\\d{7}",[["([268]\\\\d{3})(\\\\d{4})","$1 $2",null,null]]]', "44": ['["GB","00","0",null,null,"$NP$FG","\\\\d{4,10}","\\\\d{7,10}",[["(7\\\\d{3})(\\\\d{6})","$1 $2","7(?:[1-5789]|62)",null],["(\\\\d{2})(\\\\d{4})(\\\\d{4})","$1 $2 $3","2|5[56]|7[06]",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","1(?:1|\\\\d1)|3|9[018]",null],["(\\\\d{5})(\\\\d{4,5})","$1 $2","1(?:38|5[23]|69|76|94)",null],["(1\\\\d{3})(\\\\d{5,6})","$1 $2","1",null],["(800)(\\\\d{4})","$1 $2","800",null],["(845)(46)(4\\\\d)","$1 $2 $3","845",null],["(8\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","8(?:4[2-5]|7[0-3])",null],["(80\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","80",null],["([58]00)(\\\\d{6})","$1 $2","[58]00",null]]]','["GG","00","0",null,null,"$NP$FG","\\\\d{6,10}","[135789]\\\\d{6,9}"]','["IM","00","0",null,null,"$NP$FG","\\\\d{6,10}","[135789]\\\\d{6,9}"]','["JE","00","0",null,null,"$NP$FG","\\\\d{6,10}","[135789]\\\\d{6,9}"]'], "244": '["AO","00",null,null,null,null,"\\\\d{9}","[29]\\\\d{8}",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,null]]]', "211": '["SS","00","0",null,null,null,"\\\\d{9}","[19]\\\\d{8}",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3",null,"$NP$FG"]]]', "373": '["MD","00","0",null,null,"$NP$FG","\\\\d{8}","[235-9]\\\\d{7}",[["(\\\\d{2})(\\\\d{3})(\\\\d{3})","$1 $2 $3","22|3",null],["([25-7]\\\\d{2})(\\\\d{2})(\\\\d{3})","$1 $2 $3","2[13-9]|[5-7]",null],["([89]\\\\d{2})(\\\\d{5})","$1 $2","[89]",null]]]', "996": '["KG","00","0",null,null,"$NP$FG","\\\\d{5,10}","[235-8]\\\\d{8,9}",[["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[25-7]|31[25]",null],["(\\\\d{4})(\\\\d{5})","$1 $2","3(?:1[36]|[2-9])",null],["(\\\\d{3})(\\\\d{3})(\\\\d)(\\\\d{3})","$1 $2 $3 $4","8",null]]]', "93": '["AF","00","0",null,null,"$NP$FG","\\\\d{7,9}","[2-7]\\\\d{8}",[["([2-7]\\\\d)(\\\\d{3})(\\\\d{4})","$1 $2 $3","[2-7]",null]]]', "260": '["ZM","00","0",null,null,"$NP$FG","\\\\d{9}","[289]\\\\d{8}",[["([29]\\\\d)(\\\\d{7})","$1 $2","[29]",null],["(800)(\\\\d{3})(\\\\d{3})","$1 $2 $3","8",null]]]', "378": '["SM","00",null,"(?:0549)?([89]\\\\d{5})","0549$1",null,"\\\\d{6,10}","[05-7]\\\\d{7,9}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[5-7]",null],["(0549)(\\\\d{6})","$1 $2","0",null,"($1) $2"],["(\\\\d{6})","0549 $1","[89]",null,"(0549) $1"]]]', "235": '["TD","00|16",null,null,null,null,"\\\\d{8}","[2679]\\\\d{7}",[["(\\\\d{2})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4",null,null]]]', "960": '["MV","0(?:0|19)",null,null,null,null,"\\\\d{7,10}","[346-8]\\\\d{6,9}|9(?:00\\\\d{7}|\\\\d{6})",[["(\\\\d{3})(\\\\d{4})","$1-$2","[3467]|9(?:[1-9]|0[1-9])",null],["(\\\\d{3})(\\\\d{3})(\\\\d{4})","$1 $2 $3","[89]00",null]]]', "221": '["SN","00",null,null,null,null,"\\\\d{9}","[3789]\\\\d{8}",[["(\\\\d{2})(\\\\d{3})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","[379]",null],["(\\\\d{3})(\\\\d{2})(\\\\d{2})(\\\\d{2})","$1 $2 $3 $4","8",null]]]', "595": '["PY","00","0",null,null,null,"\\\\d{5,9}","5[0-5]\\\\d{4,7}|[2-46-9]\\\\d{5,8}",[["(\\\\d{2})(\\\\d{5})","$1 $2","(?:[26]1|3[289]|4[124678]|7[123]|8[1236])","($NP$FG)"],["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","(?:[26]1|3[289]|4[124678]|7[123]|8[1236])","($NP$FG)"],["(\\\\d{3})(\\\\d{3,6})","$1 $2","[2-9]0","$NP$FG"],["(\\\\d{3})(\\\\d{6})","$1 $2","9[1-9]","$NP$FG"],["(\\\\d{2})(\\\\d{3})(\\\\d{4})","$1 $2 $3","8700",null],["(\\\\d{3})(\\\\d{4,5})","$1 $2","[2-8][1-9]","($NP$FG)"],["(\\\\d{3})(\\\\d{3})(\\\\d{3})","$1 $2 $3","[2-8][1-9]","$NP$FG"]]]', "977": '["NP","00","0",null,null,"$NP$FG","\\\\d{6,10}","[1-8]\\\\d{7}|9(?:[1-69]\\\\d{6,8}|7[2-6]\\\\d{5,7}|8\\\\d{8})",[["(1)(\\\\d{7})","$1-$2","1[2-6]",null],["(\\\\d{2})(\\\\d{6})","$1-$2","1[01]|[2-8]|9(?:[1-69]|7[15-9])",null],["(9\\\\d{2})(\\\\d{7})","$1-$2","9(?:6[013]|7[245]|8)","$FG"]]]', "36": '["HU","00","06",null,null,"($FG)","\\\\d{6,9}","[1-9]\\\\d{7,8}",[["(1)(\\\\d{3})(\\\\d{4})","$1 $2 $3","1",null],["(\\\\d{2})(\\\\d{3})(\\\\d{3,4})","$1 $2 $3","[2-9]",null]]]', }; PK �������!<j����5���chrome/res/phonenumberutils/PhoneNumberNormalizer.jsm/* This Source Code Form is subject to the terms of the Apache License, Version * 2.0. If a copy of the Apache License was not distributed with this file, You * can obtain one at https://www.apache.org/licenses/LICENSE-2.0 */ // This library came from https://github.com/andreasgal/PhoneNumber.js but will // be further maintained by our own in Form Autofill codebase. "use strict"; var EXPORTED_SYMBOLS = ["PhoneNumberNormalizer"]; var PhoneNumberNormalizer = (function() { const UNICODE_DIGITS = /[\uFF10-\uFF19\u0660-\u0669\u06F0-\u06F9]/g; const VALID_ALPHA_PATTERN = /[a-zA-Z]/g; const LEADING_PLUS_CHARS_PATTERN = /^[+\uFF0B]+/g; const NON_DIALABLE_CHARS = /[^,#+\*\d]/g; // Map letters to numbers according to the ITU E.161 standard let E161 = { a: 2, b: 2, c: 2, d: 3, e: 3, f: 3, g: 4, h: 4, i: 4, j: 5, k: 5, l: 5, m: 6, n: 6, o: 6, p: 7, q: 7, r: 7, s: 7, t: 8, u: 8, v: 8, w: 9, x: 9, y: 9, z: 9, }; // Normalize a number by converting unicode numbers and symbols to their // ASCII equivalents and removing all non-dialable characters. function NormalizeNumber(number, numbersOnly) { if (typeof number !== "string") { return ""; } number = number.replace(UNICODE_DIGITS, function(ch) { return String.fromCharCode(48 + (ch.charCodeAt(0) & 0xf)); }); if (!numbersOnly) { number = number.replace(VALID_ALPHA_PATTERN, function(ch) { return String(E161[ch.toLowerCase()] || 0); }); } number = number.replace(LEADING_PLUS_CHARS_PATTERN, "+"); number = number.replace(NON_DIALABLE_CHARS, ""); return number; } return { Normalize: NormalizeNumber, }; })(); PK �������!<1Z`6��6��*���es-ES/locale/es-ES/formautofill.properties# 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/. # LOCALIZATION NOTE (saveAddressesMessage): %S is brandShortName. This string is used on the doorhanger to # notify users that addresses are saved. saveAddressesMessage = %S ahora guarda las direcciones para que pueda rellenar los formularios más rápido. # LOCALIZATION NOTE (autofillOptionsLink, autofillOptionsLinkOSX): These strings are used in the doorhanger for # updating addresses. The link leads users to Form Autofill browser preferences. autofillOptionsLink = Opciones de autocompletado de formularios autofillOptionsLinkOSX = Preferencias de autocompletado de formularios # LOCALIZATION NOTE (autofillSecurityOptionsLink, autofillSecurityOptionsLinkOSX): These strings are used # in the doorhanger for saving credit card info. The link leads users to Form Autofill browser preferences. autofillSecurityOptionsLink = Opciones de autocompletado y seguridad de formularios autofillSecurityOptionsLinkOSX = Preferencias de autocompletado y seguridad de formularios # LOCALIZATION NOTE (changeAutofillOptions, changeAutofillOptionsOSX): These strings are used on the doorhanger # that notifies users that addresses are saved. The button leads users to Form Autofill browser preferences. changeAutofillOptions = Cambiar opciones del autocompletado de formularios changeAutofillOptionsOSX = Cambiar preferencias del autocompletado de formularios changeAutofillOptionsAccessKey = C # LOCALIZATION NOTE (addressesSyncCheckbox): If Sync is enabled, this checkbox is displayed on the doorhanger # shown when saving addresses. addressesSyncCheckbox = Compartir direcciones con dispositivos sincronizados # LOCALIZATION NOTE (creditCardsSyncCheckbox): If Sync is enabled and credit card sync is available, # this checkbox is displayed on the doorhanger shown when saving credit card. creditCardsSyncCheckbox = Compartir tarjetas de crédito con dispositivos sincronizados # LOCALIZATION NOTE (updateAddressMessage, updateAddressDescriptionLabel, createAddressLabel, updateAddressLabel): # Used on the doorhanger when an address change is detected. updateAddressMessage = ¿Le gustaría actualizar su dirección con esta nueva información? updateAddressDescriptionLabel = Dirección que desea actualizar: createAddressLabel = Crear nueva dirección createAddressAccessKey = C updateAddressLabel = Actualizar domicilio updateAddressAccessKey = U # LOCALIZATION NOTE (saveCreditCardMessage, saveCreditCardDescriptionLabel, saveCreditCardLabel, cancelCreditCardLabel, neverSaveCreditCardLabel): # Used on the doorhanger when users submit payment with credit card. # LOCALIZATION NOTE (saveCreditCardMessage): %S is brandShortName. saveCreditCardMessage = ¿Desea que %S guarde esta tarjeta de crédito (el código de seguridad no se guardará)? saveCreditCardDescriptionLabel = Tarjeta de crédito que desea guardar: saveCreditCardLabel = Guardar tarjeta de crédito saveCreditCardAccessKey = T cancelCreditCardLabel = No guardar cancelCreditCardAccessKey = N neverSaveCreditCardLabel = No guardar nunca tarjetas de crédito neverSaveCreditCardAccessKey = N # LOCALIZATION NOTE (updateCreditCardMessage, updateCreditCardDescriptionLabel, createCreditCardLabel, updateCreditCardLabel): # Used on the doorhanger when an credit card change is detected. updateCreditCardMessage = ¿Le gustaría actualizar su tarjeta de crédito con esta nueva información? updateCreditCardDescriptionLabel = Tarjeta de crédito que desea actualizar: createCreditCardLabel = Crear nueva tarjea de crédito createCreditCardAccessKey = C updateCreditCardLabel = Actualizar tarjeta de crédito updateCreditCardAccessKey = U # LOCALIZATION NOTE (openAutofillMessagePanel): Tooltip label for Form Autofill doorhanger icon on address bar. openAutofillMessagePanel = Abrir panel de mensajes del autocompletado de formularios # LOCALIZATION NOTE ( (autocompleteFooterOptionShort, autocompleteFooterOptionOSXShort): Used as a label for the button, # displayed at the bottom of the dropdown suggestion, to open Form Autofill browser preferences. autocompleteFooterOptionShort = Más opciones autocompleteFooterOptionOSXShort = Preferencias # LOCALIZATION NOTE (autocompleteFooterOption2): # Used as a label for the button, displayed at the bottom of the dropdown suggestion, to open Form Autofill browser preferences. autocompleteFooterOption2 = Opciones de autocompletado de formularios # LOCALIZATION NOTE (autocompleteFooterOptionOSX2): # Used as a label for the button, displayed at the bottom of the dropdown suggestion, to open Form Autofill browser preferences. autocompleteFooterOptionOSX2 = Preferencias de autocompletado de formularios # LOCALIZATION NOTE (autocompleteFooterOptionShort2): # Used as a label for the button, displayed at the bottom of the dropdown suggestion, to open Form Autofill browser preferences. # The short version is used for inputs below a certain width (e.g. 150px). autocompleteFooterOptionShort2 = Opciones de autocompletado # LOCALIZATION NOTE (autocompleteFooterOptionOSXShort2): # Used as a label for the button, displayed at the bottom of the dropdown suggestion, to open Form Autofill browser preferences. # The short version is used for inputs below a certain width (e.g. 150px). autocompleteFooterOptionOSXShort2 = Preferencias de autocompletado # LOCALIZATION NOTE (category.address, category.name, category.organization2, category.tel, category.email): # Used in autofill drop down suggestion to indicate what other categories Form Autofill will attempt to fill. category.address = dirección category.name = nombre category.organization2 = organización category.tel = teléfono category.email = email # LOCALIZATION NOTE (fieldNameSeparator): This is used as a separator between categories. fieldNameSeparator = ,\u0020 # LOCALIZATION NOTE (phishingWarningMessage, phishingWarningMessage2): The warning # text that is displayed for informing users what categories are about to be filled. # "%S" will be replaced with a list generated from the pre-defined categories. # The text would be e.g. Also autofills organization, phone, email. phishingWarningMessage = También autocompletará %S phishingWarningMessage2 = Autocompletará %S # LOCALIZATION NOTE (insecureFieldWarningDescription): %S is brandShortName. This string is used in drop down # suggestion when users try to autofill credit card on an insecure website (without https). insecureFieldWarningDescription = %S ha detectado un sitio inseguro. El autocompletado de formularios está desactivado temporalmente. # LOCALIZATION NOTE (clearFormBtnLabel2): Label for the button in the dropdown menu that used to clear the populated # form. clearFormBtnLabel2 = Limpiar autocompletado de formulario autofillHeader = Formularios y autocompletado # LOCALIZATION NOTE (autofillAddressesCheckbox): Label for the checkbox that enables autofilling addresses. autofillAddressesCheckbox = Autocompletar direcciones # LOCALIZATION NOTE (learnMoreLabel): Label for the link that leads users to the Form Autofill SUMO page. learnMoreLabel = Saber más # LOCALIZATION NOTE (savedAddressesBtnLabel): Label for the button that opens a dialog that shows the # list of saved addresses. savedAddressesBtnLabel = Direcciones guardadas… # LOCALIZATION NOTE (autofillCreditCardsCheckbox): Label for the checkbox that enables autofilling credit cards. autofillCreditCardsCheckbox = Autocompletar tarjetas de crédito # LOCALIZATION NOTE (savedCreditCardsBtnLabel): Label for the button that opens a dialog that shows the list # of saved credit cards. savedCreditCardsBtnLabel = Tarjetas de crédito guardadas… autofillReauthCheckboxMac = Requerir autenticación de macOS para autocompletar, ver o editar tarjetas de crédito almacenadas. autofillReauthCheckboxWin = Requerir autenticación de Windows para autocompletar, ver o editar tarjetas de crédito almacenadas. autofillReauthCheckboxLin = Requerir autenticación de Linux para autocompletar, ver o editar tarjetas de crédito almacenadas. # LOCALIZATION NOTE (autofillReauthOSDialogMac): This string is # preceded by the operating system (macOS) with "Firefox is trying to ", and # has a period added to its end. Make sure to test in your locale. autofillReauthOSDialogMac = cambiar la configuración de autenticación autofillReauthOSDialogWin = Para cambiar la configuración de autenticación, ingrese sus credenciales de inicio de sesión de Windows. autofillReauthOSDialogLin = Para cambiar la configuración de autenticación, ingrese sus credenciales de inicio de sesión de Linux. # LOCALIZATION NOTE (manageAddressesTitle, manageCreditCardsTitle): The dialog title for the list of addresses or # credit cards in browser preferences. manageAddressesTitle = Direcciones guardadas manageCreditCardsTitle = Tarjetas de crédito guardadas # LOCALIZATION NOTE (addressesListHeader, creditCardsListHeader): The header for the list of addresses or credit cards # in browser preferences. addressesListHeader = Direcciones creditCardsListHeader = Tarjetas de crédito removeBtnLabel = Eliminar addBtnLabel = Añadir… editBtnLabel = Editar… # LOCALIZATION NOTE (manageDialogsWidth): This strings sets the default width for windows used to manage addresses and # credit cards. manageDialogsWidth = 560px # LOCALIZATION NOTE (addNewAddressTitle, editAddressTitle): The dialog title for creating or editing addresses # in browser preferences. addNewAddressTitle = Añadir nueva dirección editAddressTitle = Editar dirección givenName = Nombre de pila additionalName = Segundo nombre de pila familyName = Apellidos organization2 = Organización streetAddress = Domicilio ## address-level-3 (Sublocality) names # LOCALIZATION NOTE (neighborhood): Used in IR, MX neighborhood = Vecindario # LOCALIZATION NOTE (village_township): Used in MY village_township = Pueblo o municipio island = Isla # LOCALIZATION NOTE (townland): Used in IE townland = Townland ## address-level-2 names city = Ciudad # LOCALIZATION NOTE (district): Used in HK, SD, SY, TR as Address Level-2 # and used in KR as Sublocality. district = Distrito # LOCALIZATION NOTE (post_town): Used in GB, NO, SE post_town = Ciudad postal # LOCALIZATION NOTE (suburb): Used in AU as Address Level-2 # and used in ZZ as Sublocality. suburb = Barrio # address-level-1 names province = Provincia state = Estado county = Condado # LOCALIZATION NOTE (parish): Used in BB, JM parish = Parroquia # LOCALIZATION NOTE (prefecture): Used in JP prefecture = Prefectura # LOCALIZATION NOTE (area): Used in HK area = Área # LOCALIZATION NOTE (do_si): Used in KR do_si = Do/Si # LOCALIZATION NOTE (department): Used in NI, CO department = Departamento # LOCALIZATION NOTE (emirate): Used in AE emirate = Emirato # LOCALIZATION NOTE (oblast): Used in RU and UA oblast = Región # LOCALIZATION NOTE (pin, postalCode, zip, eircode): Postal code name types # LOCALIZATION NOTE (pin): Used in IN pin = Pin postalCode = Código postal zip = Código postal # LOCALIZATION NOTE (eircode): Used in IE eircode = Eircode country = País o región tel = Teléfono email = Email cancelBtnLabel = Cancelar saveBtnLabel = Guardar countryWarningMessage2 = El autocompletado de formularios solo está disponible para ciertos países. # LOCALIZATION NOTE (addNewCreditCardTitle, editCreditCardTitle): The dialog title for creating or editing # credit cards in browser preferences. addNewCreditCardTitle = Añadir nueva tarjeta de crédito editCreditCardTitle = Editar tarjeta de crédito cardNumber = Número de tarjeta invalidCardNumber = Introduzca un número de tarjeta válido nameOnCard = Nombre en la tarjeta cardExpiresMonth = Mes cad. cardExpiresYear = Año cad. billingAddress = Dirección de facturación cardNetwork = Tipo de tarjeta # LOCALIZATION NOTE (cardCVV): Credit card security code https://en.wikipedia.org/wiki/Card_security_code cardCVV = CVV # LOCALIZATION NOTE: (cardNetwork.*): These are brand names and should only be translated when a locale-specific name for that brand is in common use cardNetwork.amex = American Express cardNetwork.cartebancaire = Carte Bancaire cardNetwork.diners = Diners Club cardNetwork.discover = Discover cardNetwork.jcb = JCB cardNetwork.mastercard = MasterCard cardNetwork.mir = MIR cardNetwork.unionpay = Union Pay cardNetwork.visa = Visa # LOCALIZATION NOTE (editCreditCardPasswordPrompt.*, useCreditCardPasswordPrompt.*): %S is brandShortName. editCreditCardPasswordPrompt.win = %S está intentando mostrar la información de la tarjeta de crédito. Confirme a continuación el acceso de esta cuenta de Windows. editCreditCardPasswordPrompt.macosx = %S está intentando mostrar la información de la tarjeta de crédito. # LOCALIZATION NOTE (editCreditCardPasswordPrompt.macos): This string is # preceded by the operating system (macOS) with "Firefox is trying to ", and # has a period added to its end. Make sure to test in your locale. editCreditCardPasswordPrompt.macos = mostrar información de la tarjeta de crédito editCreditCardPasswordPrompt.linux = %S está intentando mostrar la información de la tarjeta de crédito. useCreditCardPasswordPrompt.win = %S está intentando usar la información de la tarjeta de crédito almacenada. Confirme el acceso a la cuenta de Windows que se muestra a continuación. useCreditCardPasswordPrompt.macosx = %S está intentando usar la información de la tarjeta de crédito almacenada. # LOCALIZATION NOTE (useCreditCardPasswordPrompt.macos): This string is # preceded by the operating system (macOS) with "Firefox is trying to ", and # has a period added to its end. Make sure to test in your locale. useCreditCardPasswordPrompt.macos = usar la información almacenada de la tarjeta de crédito useCreditCardPasswordPrompt.linux = %S está intentando usar la información de la tarjeta de crédito almacenada. PK �������!<~���� ���manifest.json{ "manifest_version": 2, "name": "Form Autofill", "version": "1.0", "applications": { "gecko": { "id": "formautofill@mozilla.org" } }, "background": { "scripts": ["background.js"] }, "experiment_apis": { "formautofill": { "schema": "schema.json", "parent": { "scopes": ["addon_parent"], "script": "api.js", "events": ["startup"] } } } } PK �������!<Dhp������ ���schema.json[] PK����;�;��������