casdoor 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +1 -0
- package/.github/workflows/release.yaml +8 -4
- package/README.md +15 -3
- package/manifest.json +14 -15
- package/package.json +6 -6
- package/src/background.js +333 -77
- package/src/content-script.js +311 -110
- package/src/match.js +92 -0
- package/src/popup.css +133 -77
- package/src/popup.html +31 -18
- package/src/popup.js +101 -65
- package/src/config.js +0 -47
- package/src/oauth.js +0 -219
- package/src/sdk.js +0 -138
package/src/content-script.js
CHANGED
|
@@ -12,127 +12,328 @@
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
.evaluate(submitXpath, document)
|
|
63
|
-
.iterateNext()
|
|
64
|
-
.click();
|
|
65
|
-
}
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function autoFillCasdoorConfig() {
|
|
74
|
-
const applicationNameXpath = "//div[@id='CasdoorApplicationName']";
|
|
75
|
-
waitForElementToDisplay(
|
|
76
|
-
applicationNameXpath,
|
|
77
|
-
updateTime,
|
|
78
|
-
() => {
|
|
79
|
-
const applicationNameDom = document.evaluate(applicationNameXpath, document).iterateNext();
|
|
80
|
-
if (applicationNameDom) {
|
|
81
|
-
const applicationName = applicationNameDom.getAttribute("value");
|
|
82
|
-
if (applicationName) {
|
|
83
|
-
chrome.storage.sync.set({applicationName});
|
|
84
|
-
chrome.storage.sync.set({endpoint: window.location.origin});
|
|
85
|
-
}
|
|
15
|
+
// Fills the sign-in form of a legacy system with the user's managed account, and offers to save the account that the
|
|
16
|
+
// user signs in with by hand. The page never sees the account: it is asked for only when the form is filled.
|
|
17
|
+
|
|
18
|
+
(() => {
|
|
19
|
+
if (window.casdoorFormFillLoaded) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
window.casdoorFormFillLoaded = true;
|
|
23
|
+
|
|
24
|
+
const isZh = (navigator.language || "").toLowerCase().startsWith("zh");
|
|
25
|
+
const strings = isZh
|
|
26
|
+
? {
|
|
27
|
+
filled: "Casdoor 已填入 {app} 的账号 {user}",
|
|
28
|
+
switchAccount: "换成",
|
|
29
|
+
retry: "上次自动登录后又回到了登录页,这次没有自动提交,请检查账号是否正确",
|
|
30
|
+
failed: "Casdoor 代填失败:{error}",
|
|
31
|
+
askSave: "把 {app} 的账号 {user} 保存到 Casdoor,下次自动登录?",
|
|
32
|
+
save: "保存",
|
|
33
|
+
skip: "不用了",
|
|
34
|
+
saved: "已保存到 Casdoor",
|
|
35
|
+
close: "关闭",
|
|
36
|
+
}
|
|
37
|
+
: {
|
|
38
|
+
filled: "Casdoor filled in the account {user} of {app}",
|
|
39
|
+
switchAccount: "Use",
|
|
40
|
+
retry: "The last automatic sign-in came back to the sign-in page, so it was not submitted this time. Please check the account",
|
|
41
|
+
failed: "Casdoor could not fill the form: {error}",
|
|
42
|
+
askSave: "Save the account {user} of {app} to Casdoor and sign in automatically next time?",
|
|
43
|
+
save: "Save",
|
|
44
|
+
skip: "No thanks",
|
|
45
|
+
saved: "Saved to Casdoor",
|
|
46
|
+
close: "Close",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
function format(text, values) {
|
|
50
|
+
return text.replace(/\{(\w+)\}/g, (_, key) => values[key] ?? "");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function send(message) {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
try {
|
|
56
|
+
chrome.runtime.sendMessage(message, (response) => {
|
|
57
|
+
resolve(chrome.runtime.lastError ? null : response);
|
|
58
|
+
});
|
|
59
|
+
} catch (error) {
|
|
60
|
+
// the extension was reloaded and this script is orphaned
|
|
61
|
+
resolve(null);
|
|
86
62
|
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function sleep(ms) {
|
|
67
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isVisible(element) {
|
|
71
|
+
if (!element || element.disabled || element.readOnly) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
const style = window.getComputedStyle(element);
|
|
75
|
+
if (style.visibility === "hidden" || style.display === "none") {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return element.getClientRects().length > 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function query(selector) {
|
|
82
|
+
if (!selector) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
return document.querySelector(selector);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return null;
|
|
87
89
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const usernameHints = /user|login|account|email|mail|phone|mobile|tel|name|uid|uname|loginid|工号|账号|帳號|用户|用戶|手机|邮箱/i;
|
|
93
|
+
const submitHints = /^\s*(登\s*录|登\s*入|登\s*陆|log\s*in|sign\s*in|login|signin|submit|确\s*定|提\s*交|进入系统)\s*$/i;
|
|
90
94
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (
|
|
94
|
-
|
|
95
|
+
function findPassword(site) {
|
|
96
|
+
const configured = query(site.passwordSelector);
|
|
97
|
+
if (configured) {
|
|
98
|
+
return configured;
|
|
95
99
|
}
|
|
96
|
-
|
|
97
|
-
}
|
|
100
|
+
return [...document.querySelectorAll("input[type=password]")].find(isVisible) || null;
|
|
101
|
+
}
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
autoLogin(managedAccount.username, managedAccount.password);
|
|
103
|
+
function isTextInput(input) {
|
|
104
|
+
const type = (input.getAttribute("type") || "text").toLowerCase();
|
|
105
|
+
return ["text", "email", "tel", "number", ""].includes(type);
|
|
103
106
|
}
|
|
104
|
-
});
|
|
105
107
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
function findUsername(site, password) {
|
|
109
|
+
const configured = query(site.usernameSelector);
|
|
110
|
+
if (configured) {
|
|
111
|
+
return configured;
|
|
112
|
+
}
|
|
113
|
+
const scope = (password && password.form) || document;
|
|
114
|
+
const inputs = [...scope.querySelectorAll("input")].filter((input) => isTextInput(input) && isVisible(input));
|
|
115
|
+
const before = password
|
|
116
|
+
? inputs.filter((input) => input.compareDocumentPosition(password) & Node.DOCUMENT_POSITION_FOLLOWING)
|
|
117
|
+
: inputs;
|
|
118
|
+
const candidates = before.length > 0 ? before : inputs;
|
|
119
|
+
const hinted = candidates.filter((input) => usernameHints.test(`${input.name} ${input.id} ${input.placeholder} ${input.getAttribute("autocomplete") || ""} ${input.getAttribute("aria-label") || ""}`));
|
|
120
|
+
const list = hinted.length > 0 ? hinted : candidates;
|
|
121
|
+
return list.length > 0 ? list[list.length - 1] : null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function findSubmit(site, password) {
|
|
125
|
+
const configured = query(site.submitSelector);
|
|
126
|
+
if (configured) {
|
|
127
|
+
return configured;
|
|
128
|
+
}
|
|
129
|
+
const form = password && password.form;
|
|
130
|
+
if (form) {
|
|
131
|
+
const button = [...form.querySelectorAll("button, input[type=submit], input[type=image]")].find((element) => {
|
|
132
|
+
const type = (element.getAttribute("type") || "submit").toLowerCase();
|
|
133
|
+
return isVisible(element) && (type === "submit" || type === "image");
|
|
134
|
+
});
|
|
135
|
+
if (button) {
|
|
136
|
+
return button;
|
|
113
137
|
}
|
|
114
|
-
}
|
|
115
|
-
|
|
138
|
+
}
|
|
139
|
+
const clickable = [...document.querySelectorAll("button, input[type=button], input[type=submit], a, [role=button], div[onclick], span[onclick]")];
|
|
140
|
+
return clickable.find((element) => isVisible(element) && submitHints.test(element.value || element.textContent || "")) || null;
|
|
116
141
|
}
|
|
117
|
-
});
|
|
118
142
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
});
|
|
124
|
-
|
|
143
|
+
function setValue(input, value) {
|
|
144
|
+
input.focus();
|
|
145
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value").set;
|
|
146
|
+
setter.call(input, value);
|
|
147
|
+
input.dispatchEvent(new Event("input", {bubbles: true}));
|
|
148
|
+
input.dispatchEvent(new Event("change", {bubbles: true}));
|
|
149
|
+
input.dispatchEvent(new KeyboardEvent("keyup", {bubbles: true}));
|
|
150
|
+
input.blur();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function pressEnter(input) {
|
|
154
|
+
for (const type of ["keydown", "keypress", "keyup"]) {
|
|
155
|
+
input.dispatchEvent(new KeyboardEvent(type, {key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true}));
|
|
156
|
+
}
|
|
157
|
+
if (input.form) {
|
|
158
|
+
input.form.requestSubmit();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function waitForPassword(site, timeout) {
|
|
163
|
+
const start = Date.now();
|
|
164
|
+
while (Date.now() - start < timeout) {
|
|
165
|
+
const password = findPassword(site);
|
|
166
|
+
if (password && isVisible(password)) {
|
|
167
|
+
return password;
|
|
168
|
+
}
|
|
169
|
+
await sleep(300);
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
125
172
|
}
|
|
126
|
-
});
|
|
127
173
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
174
|
+
// the notices live in a shadow root so that the page styles leave them alone
|
|
175
|
+
let noticeHost = null;
|
|
176
|
+
|
|
177
|
+
function showNotice(text, buttons = [], timeout = 8000) {
|
|
178
|
+
if (noticeHost) {
|
|
179
|
+
noticeHost.remove();
|
|
180
|
+
}
|
|
181
|
+
noticeHost = document.createElement("div");
|
|
182
|
+
noticeHost.style.cssText = "position:fixed;top:16px;right:16px;z-index:2147483647;";
|
|
183
|
+
const root = noticeHost.attachShadow({mode: "open"});
|
|
184
|
+
const box = document.createElement("div");
|
|
185
|
+
box.style.cssText = "max-width:360px;padding:12px 14px;border-radius:10px;background:#fff;color:#1f2328;box-shadow:0 6px 24px rgba(0,0,0,.18);font:14px/1.5 -apple-system,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif;border:1px solid #e5e7eb;";
|
|
186
|
+
const title = document.createElement("div");
|
|
187
|
+
title.style.cssText = "font-weight:600;font-size:12px;color:#5c6ac4;margin-bottom:4px;";
|
|
188
|
+
title.textContent = "Casdoor";
|
|
189
|
+
const body = document.createElement("div");
|
|
190
|
+
body.textContent = text;
|
|
191
|
+
box.append(title, body);
|
|
192
|
+
if (buttons.length > 0) {
|
|
193
|
+
const row = document.createElement("div");
|
|
194
|
+
row.style.cssText = "display:flex;flex-wrap:wrap;gap:8px;margin-top:10px;";
|
|
195
|
+
buttons.forEach((item, index) => {
|
|
196
|
+
const button = document.createElement("button");
|
|
197
|
+
button.textContent = item.label;
|
|
198
|
+
button.style.cssText = index === 0
|
|
199
|
+
? "border:0;border-radius:6px;padding:4px 12px;background:#5c6ac4;color:#fff;cursor:pointer;font:inherit;"
|
|
200
|
+
: "border:1px solid #d0d7de;border-radius:6px;padding:4px 12px;background:#fff;color:#1f2328;cursor:pointer;font:inherit;";
|
|
201
|
+
button.addEventListener("click", () => {
|
|
202
|
+
noticeHost.remove();
|
|
203
|
+
noticeHost = null;
|
|
204
|
+
item.onClick();
|
|
205
|
+
});
|
|
206
|
+
row.append(button);
|
|
134
207
|
});
|
|
135
|
-
|
|
136
|
-
|
|
208
|
+
box.append(row);
|
|
209
|
+
}
|
|
210
|
+
root.append(box);
|
|
211
|
+
document.documentElement.append(noticeHost);
|
|
212
|
+
if (timeout > 0) {
|
|
213
|
+
const host = noticeHost;
|
|
214
|
+
setTimeout(() => host.remove(), timeout);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function fill(info, username) {
|
|
219
|
+
const site = info.site;
|
|
220
|
+
// a page of the system without a sign-in form, e.g. when the URLs end with "*"
|
|
221
|
+
const password = await waitForPassword(site, 15000);
|
|
222
|
+
if (!password) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const usernameInput = findUsername(site, password);
|
|
226
|
+
const account = await send({type: "getAccount", application: site.application, username, autoSubmit: site.autoSubmit});
|
|
227
|
+
if (!account || account.error) {
|
|
228
|
+
showNotice(format(strings.failed, {error: (account && account.error) || ""}));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (usernameInput) {
|
|
232
|
+
setValue(usernameInput, account.username);
|
|
233
|
+
}
|
|
234
|
+
setValue(password, account.password);
|
|
235
|
+
|
|
236
|
+
const others = info.accounts.filter((item) => item !== account.username);
|
|
237
|
+
const buttons = others.slice(0, 3).map((item) => ({label: `${strings.switchAccount} ${item}`, onClick: () => fill(info, item)}));
|
|
238
|
+
if (info.recentlySubmitted && info.accounts.length > 0) {
|
|
239
|
+
showNotice(strings.retry, buttons, 0);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
showNotice(format(strings.filled, {app: site.displayName, user: account.username}), buttons);
|
|
243
|
+
|
|
244
|
+
if (site.autoSubmit && !username) {
|
|
245
|
+
await sleep(400);
|
|
246
|
+
const submit = findSubmit(site, password);
|
|
247
|
+
if (submit) {
|
|
248
|
+
submit.click();
|
|
249
|
+
} else {
|
|
250
|
+
pressEnter(password);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// remembers the account typed into the sign-in form, to offer saving it once the user is signed in
|
|
256
|
+
function watchCapture(site) {
|
|
257
|
+
if (window.casdoorCaptureSite === site.application) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
window.casdoorCaptureSite = site.application;
|
|
261
|
+
const grab = () => {
|
|
262
|
+
const password = findPassword(site);
|
|
263
|
+
if (!password || !password.value) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
const usernameInput = findUsername(site, password);
|
|
267
|
+
const username = usernameInput ? usernameInput.value.trim() : "";
|
|
268
|
+
if (!username) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
send({type: "capture", application: site.application, username, password: password.value});
|
|
272
|
+
// a single-page app signs in without leaving the page
|
|
273
|
+
setTimeout(checkPending, 3000);
|
|
274
|
+
};
|
|
275
|
+
document.addEventListener("submit", grab, true);
|
|
276
|
+
document.addEventListener("click", (event) => {
|
|
277
|
+
const target = event.target instanceof Element ? event.target.closest("button, input[type=submit], input[type=button], a, [role=button]") : null;
|
|
278
|
+
if (target) {
|
|
279
|
+
grab();
|
|
280
|
+
}
|
|
281
|
+
}, true);
|
|
282
|
+
document.addEventListener("keydown", (event) => {
|
|
283
|
+
if (event.key === "Enter" && event.target instanceof HTMLInputElement) {
|
|
284
|
+
grab();
|
|
285
|
+
}
|
|
286
|
+
}, true);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let pendingShown = false;
|
|
290
|
+
|
|
291
|
+
async function checkPending() {
|
|
292
|
+
if (pendingShown) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const pending = await send({type: "getPending"});
|
|
296
|
+
if (!pending) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// still on a sign-in form: the password was wrong, or the page has not moved on yet
|
|
300
|
+
const password = [...document.querySelectorAll("input[type=password]")].find(isVisible);
|
|
301
|
+
if (password) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
pendingShown = true;
|
|
305
|
+
showNotice(format(strings.askSave, {app: pending.displayName, user: pending.username}), [
|
|
306
|
+
{
|
|
307
|
+
label: strings.save,
|
|
308
|
+
onClick: async() => {
|
|
309
|
+
const res = await send({type: "resolvePending", save: true});
|
|
310
|
+
showNotice(res && res.saved ? strings.saved : format(strings.failed, {error: (res && res.error) || ""}));
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{label: strings.skip, onClick: () => send({type: "resolvePending", save: false})},
|
|
314
|
+
], 0);
|
|
137
315
|
}
|
|
138
|
-
|
|
316
|
+
|
|
317
|
+
let checkedUrl = null;
|
|
318
|
+
|
|
319
|
+
async function check() {
|
|
320
|
+
if (checkedUrl === location.href) {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
checkedUrl = location.href;
|
|
324
|
+
checkPending();
|
|
325
|
+
const info = await send({type: "getFill"});
|
|
326
|
+
if (!info || !info.site) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (info.accounts.length > 0) {
|
|
330
|
+
fill(info);
|
|
331
|
+
} else if (info.canSave) {
|
|
332
|
+
watchCapture(info.site);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
check();
|
|
337
|
+
// single-page apps change the address without loading a page
|
|
338
|
+
setInterval(check, 1000);
|
|
339
|
+
})();
|
package/src/match.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright 2026 The Casdoor Authors. All Rights Reserved.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
// The sign-in page patterns of form autofill, matched the same way as Casdoor matches them (object/form_fill.go):
|
|
16
|
+
// the host has to be the same, "*.example.com" covers the subdomains, and a path without "*" is a prefix.
|
|
17
|
+
|
|
18
|
+
function splitFormFillUrl(value) {
|
|
19
|
+
value = (value || "").trim();
|
|
20
|
+
let scheme = "";
|
|
21
|
+
const schemeMatch = value.match(/^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//);
|
|
22
|
+
if (schemeMatch) {
|
|
23
|
+
scheme = schemeMatch[0].slice(0, -3).toLowerCase();
|
|
24
|
+
value = value.slice(schemeMatch[0].length);
|
|
25
|
+
}
|
|
26
|
+
let host = value;
|
|
27
|
+
let rest = "/";
|
|
28
|
+
const index = value.search(/[/?#]/);
|
|
29
|
+
if (index >= 0) {
|
|
30
|
+
host = value.slice(0, index);
|
|
31
|
+
rest = value.slice(index);
|
|
32
|
+
if (!rest.startsWith("/")) {
|
|
33
|
+
rest = "/" + rest;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return [scheme, host.toLowerCase(), rest];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function matchFormFillHost(pattern, host) {
|
|
40
|
+
if (pattern === host) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
if (pattern.startsWith("*.")) {
|
|
44
|
+
return host.endsWith(pattern.slice(1));
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function escapeRegex(value) {
|
|
50
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function matchFormFillUrl(pattern, pageUrl) {
|
|
54
|
+
pattern = (pattern || "").trim();
|
|
55
|
+
if (!pattern) {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
const [patternScheme, patternHost, patternPath] = splitFormFillUrl(pattern);
|
|
59
|
+
const [pageScheme, pageHost, fullPagePath] = splitFormFillUrl(pageUrl);
|
|
60
|
+
if (pageScheme !== "http" && pageScheme !== "https") {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
if (patternScheme && patternScheme !== pageScheme) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
if (!matchFormFillHost(patternHost, pageHost)) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
const pagePath = fullPagePath.split("#")[0];
|
|
70
|
+
if (!patternPath.includes("*")) {
|
|
71
|
+
return pagePath.startsWith(patternPath);
|
|
72
|
+
}
|
|
73
|
+
const expression = "^" + escapeRegex(patternPath).replace(/\\\*/g, ".*") + "$";
|
|
74
|
+
return new RegExp(expression).test(pagePath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function matchFormFillSite(site, pageUrl) {
|
|
78
|
+
return (site.urls || []).some((pattern) => matchFormFillUrl(pattern, pageUrl));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// the sign-in URL of a managed account before form autofill, a regular expression
|
|
82
|
+
function matchLegacySigninUrl(pattern, pageUrl) {
|
|
83
|
+
try {
|
|
84
|
+
return new RegExp(pattern).test(pageUrl);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (typeof module !== "undefined") {
|
|
91
|
+
module.exports = {matchFormFillUrl, matchFormFillSite, matchLegacySigninUrl};
|
|
92
|
+
}
|