Skip to content

Commit

Permalink
Refactor - auto update credential provider script (#22606)
Browse files Browse the repository at this point in the history
* Refactor - auto update credential provider script

* fix: pull missing file AddressParser.sys.mjs

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Issam Mani <[email protected]>
  • Loading branch information
github-actions[bot] and issammani authored Oct 17, 2024
1 parent f387704 commit 64c86e7
Show file tree
Hide file tree
Showing 7 changed files with 375 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ export const AddressMetaDataExtension = {
},
"data/DE": {
alpha_3_code: "DEU",
address_reversed: true,
},
"data/GH": {
alpha_3_code: "GHA",
Expand Down
316 changes: 316 additions & 0 deletions firefox-ios/Client/Assets/CC_Script/AddressParser.sys.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
/* eslint-disable no-useless-concat */
/* 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/. */

// NamedCaptureGroup class represents a named capturing group in a regular expression
class NamedCaptureGroup {
// The named of this capturing group
#name = null;

// The capturing group
#capture = null;

// The matched result
#match = null;

constructor(name, capture) {
this.#name = name;
this.#capture = capture;
}

get name() {
return this.#name;
}

get capture() {
return this.#capture;
}

get match() {
return this.#match;
}

// Setter for the matched result based on the match groups
setMatch(matchGroups) {
this.#match = matchGroups[this.#name];
}
}

// Base class for different part of a street address regular expression.
// The regular expression is constructed with prefix, pattern, suffix
// and separator to extract "value" part.
// For examplem, when we write "apt 4." to for floor number, its prefix is `apt`,
// suffix is `.` and value to represent apartment number is `4`.
class StreetAddressPartRegExp extends NamedCaptureGroup {
constructor(name, prefix, pattern, suffix, sep, optional = false) {
prefix = prefix ?? "";
suffix = suffix ?? "";
super(
name,
`((?:${prefix})(?<${name}>${pattern})(?:${suffix})(?:${sep})+)${
optional ? "?" : ""
}`
);
}
}

// A regular expression to match the street number portion of a street address,
class StreetNumberRegExp extends StreetAddressPartRegExp {
static PREFIX = "((no|°|º|number)(\\.|-|\\s)*)?"; // From chromium source

static PATTERN = "\\d+\\w?";

// TODO: possible suffix : (th\\.|\\.)?
static SUFFIX = null;

constructor(sep, optional) {
super(
StreetNumberRegExp.name,
StreetNumberRegExp.PREFIX,
StreetNumberRegExp.PATTERN,
StreetNumberRegExp.SUFFIX,
sep,
optional
);
}
}

// A regular expression to match the street name portion of a street address,
class StreetNameRegExp extends StreetAddressPartRegExp {
static PREFIX = null;

static PATTERN = "(?:[^\\s,]+(?:[^\\S\\r\\n]+[^\\s,]+)*?)"; // From chromium source

// TODO: Should we consider suffix like (ave|st)?
static SUFFIX = null;

constructor(sep, optional) {
super(
StreetNameRegExp.name,
StreetNameRegExp.PREFIX,
StreetNameRegExp.PATTERN,
StreetNameRegExp.SUFFIX,
sep,
optional
);
}
}

// A regular expression to match the apartment number portion of a street address,
class ApartmentNumberRegExp extends StreetAddressPartRegExp {
static keyword = "apt|apartment|wohnung|apto|-" + "|unit|suite|ste|#|room"; // From chromium source // Firefox specific
static PREFIX = `(${ApartmentNumberRegExp.keyword})(\\.|\\s|-)*`;

static PATTERN = "\\w*([-|\\/]\\w*)?";

static SUFFIX = "(\\.|\\s|-)*(ª)?"; // From chromium source

constructor(sep, optional) {
super(
ApartmentNumberRegExp.name,
ApartmentNumberRegExp.PREFIX,
ApartmentNumberRegExp.PATTERN,
ApartmentNumberRegExp.SUFFIX,
sep,
optional
);
}
}

// A regular expression to match the floor number portion of a street address,
class FloorNumberRegExp extends StreetAddressPartRegExp {
static keyword =
"floor|flur|fl|og|obergeschoss|ug|untergeschoss|geschoss|andar|piso|º" + // From chromium source
"|level|lvl"; // Firefox specific
static PREFIX = `(${FloorNumberRegExp.keyword})?(\\.|\\s|-)*`; // TODO
static PATTERN = "\\d{1,3}\\w?";
static SUFFIX = `(st|nd|rd|th)?(\\.|\\s|-)*(${FloorNumberRegExp.keyword})?`; // TODO

constructor(sep, optional) {
super(
FloorNumberRegExp.name,
FloorNumberRegExp.PREFIX,
FloorNumberRegExp.PATTERN,
FloorNumberRegExp.SUFFIX,
sep,
optional
);
}
}

/**
* Class represents a street address with the following fields:
* - street number
* - street name
* - apartment number
* - floor number
*/
export class StructuredStreetAddress {
#street_number = null;
#street_name = null;
#apartment_number = null;
#floor_number = null;

// If name_first is true, then the street name is given first,
// otherwise the street number is given first.
constructor(
name_first,
street_number,
street_name,
apartment_number,
floor_number
) {
this.#street_number = name_first
? street_name?.toString()
: street_number?.toString();
this.#street_name = name_first
? street_number?.toString()
: street_name?.toString();
this.#apartment_number = apartment_number?.toString();
this.#floor_number = floor_number?.toString();
}

get street_number() {
return this.#street_number;
}

get street_name() {
return this.#street_name;
}

get apartment_number() {
return this.#apartment_number;
}

get floor_number() {
return this.#floor_number;
}

toString() {
return `
street number: ${this.#street_number}\n
street name: ${this.#street_name}\n
apartment number: ${this.#apartment_number}\n
floor number: ${this.#floor_number}\n
`;
}
}

export class AddressParser {
/**
* Parse street address with the following pattern.
* street number, street name, apartment number(optional), floor number(optional)
* For example, 2 Harrison St #175 floor 2
*
* @param {string} address The street address to be parsed.
* @returns {StructuredStreetAddress}
*/
static parseStreetAddress(address) {
if (!address) {
return null;
}

const separator = "(\\s|,|$)";

const regexpes = [
new StreetNumberRegExp(separator),
new StreetNameRegExp(separator),
new ApartmentNumberRegExp(separator, true),
new FloorNumberRegExp(separator, true),
];

if (AddressParser.parse(address, regexpes)) {
return new StructuredStreetAddress(
false,
...regexpes.map(regexp => regexp.match)
);
}

// Swap the street number and name.
const regexpesReverse = [
regexpes[1],
regexpes[0],
regexpes[2],
regexpes[3],
];

if (AddressParser.parse(address, regexpesReverse)) {
return new StructuredStreetAddress(
true,
...regexpesReverse.map(regexp => regexp.match)
);
}

return null;
}

static parse(address, regexpes) {
const options = {
trim: true,
merge_whitespace: true,
};
address = AddressParser.normalizeString(address, options);

const match = address.match(
new RegExp(`^(${regexpes.map(regexp => regexp.capture).join("")})$`, "i")
);
if (!match) {
return null;
}

regexpes.forEach(regexp => regexp.setMatch(match.groups));
return regexpes.reduce((acc, current) => {
return { ...acc, [current.name]: current.match };
}, {});
}

static normalizeString(s, options) {
if (typeof s != "string") {
return s;
}

if (options.ignore_case) {
s = s.toLowerCase();
}

// process punctuation before whitespace because if a punctuation
// is replaced with whitespace, we might want to merge it later
if (options.remove_punctuation) {
s = AddressParser.replacePunctuation(s, "");
} else if ("replace_punctuation" in options) {
const replace = options.replace_punctuation;
s = AddressParser.replacePunctuation(s, replace);
}

// process whitespace
if (options.merge_whitespace) {
s = AddressParser.mergeWhitespace(s);
} else if (options.remove_whitespace) {
s = AddressParser.removeWhitespace(s);
}

return s.trim();
}

static replacePunctuation(s, replace) {
const regex = /\p{Punctuation}/gu;
return s?.replace(regex, replace);
}

static removePunctuation(s) {
return s?.replace(/[.,\/#!$%\^&\*;:{}=\-_~()]/g, "");
}

static replaceControlCharacters(s) {
return s?.replace(/[\t\n\r]/g, " ");
}

static removeWhitespace(s) {
return s?.replace(/[\s]/g, "");
}

static mergeWhitespace(s) {
return s?.replace(/\s{2,}/g, " ");
}
}
1 change: 1 addition & 0 deletions firefox-ios/Client/Assets/CC_Script/CC_Python_Update.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"toolkit/components/formautofill/shared/AddressMetaDataExtension.sys.mjs",
"toolkit/components/formautofill/shared/AddressMetaDataLoader.sys.mjs",
"toolkit/components/formautofill/shared/AddressRecord.sys.mjs",
"toolkit/components/formautofill/shared/AddressParser.sys.mjs",
"toolkit/components/formautofill/shared/AutofillTelemetry.sys.mjs",
"toolkit/components/formautofill/shared/FormAutofillHandler.sys.mjs",
"toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs",
Expand Down
16 changes: 16 additions & 0 deletions firefox-ios/Client/Assets/CC_Script/FormAutofillHandler.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUti

const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AddressParser: "resource://gre/modules/shared/AddressParser.sys.mjs",
AutofillFormFactory:
"resource://gre/modules/shared/AutofillFormFactory.sys.mjs",
CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
Expand Down Expand Up @@ -823,6 +824,21 @@ export class FormAutofillHandler {
}
}
}

// If a house number field exists, split the address up into house number
// and street name.
if (this.getFieldDetailByName("address-housenumber")) {
let address = lazy.AddressParser.parseStreetAddress(
profile["street-address"]
);
if (address) {
profile["address-housenumber"] = address.street_number;
let field = this.getFieldDetailByName("address-line1")
? "address-line1"
: "street-address";
profile[field] = address.street_name;
}
}
}

/**
Expand Down
Loading

0 comments on commit 64c86e7

Please sign in to comment.