-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcws_pattern.js
More file actions
352 lines (321 loc) · 13.5 KB
/
Copy pathcws_pattern.js
File metadata and controls
352 lines (321 loc) · 13.5 KB
1
2
3
4
5
6
7
8
9
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
/**
* Source by Subhan
* All rights reserved. */
/* globals location, getPlatformInfo, navigator */
/* exported cws_match_patterns, mea_match_pattern, ows_match_pattern, amo_match_patterns, atn_match_patterns */
/* exported cws_pattern, mea_pattern, ows_pattern, amo_pattern, atn_pattern */
/* exported can_viewsource_crx_url */
/* exported get_crx_url, get_webstore_url, get_zip_name, is_not_crx_url, getParam */
/* exported is_crx_download_url, is_webstore_url */
/* exported get_amo_domain, get_amo_slug */
/* exported get_equivalent_download_url */
/* exported encodeQueryString */
'use strict';
// cws_pattern[1] = extensionID
var cws_pattern = /^https?:\/\/(?:chrome.google.com\/webstore|chromewebstore.google.com)\/.+?\/([a-z]{32})(?=[\/#?]|$)/;
var cws_download_pattern = /^https?:\/\/clients2\.google\.com\/service\/update2\/crx\b.*?(?:%3D|=)([a-z]{32})(?:%26|&)uc/i;
// match pattern per Chrome spec
var cws_match_patterns = [
'*://chrome.google.com/webstore/detail/*',
'*://chromewebstore.google.com/detail/*',
];
// Microsoft Edge Addons Store
var mea_pattern = /^https?:\/\/microsoftedge.microsoft.com\/addons\/.+?\/([a-z]{32})(?=[\/#?]|$)/;
var mea_download_pattern = /^https?:\/\/edge\.microsoft\.com\/extensionwebstorebase\/v1\/crx\b.*?(?:%3D|=)([a-z]{32})(?:%26|&)/i;
var mea_match_pattern = '*://microsoftedge.microsoft.com/addons/detail/*';
// Opera add-on gallery
var ows_pattern = /^https?:\/\/addons.opera.com\/.*?extensions\/(?:details|download)\/([^\/?#]+)/i;
// The gallery links to addons.opera.com/extensions/download and redirects to addons-extensions.operacdn.com.
var ows_download_pattern = /^https?:\/\/addons.opera.com\/extensions\/download\/([^\/]+)/;
var ows_match_pattern = '*://addons.opera.com/*extensions/details/*';
// Firefox addon gallery
var amo_pattern = /^https?:\/\/((?:reviewers\.)?(?:addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org))\/.*?(?:addon|review)\/([^/<>"'?#]+)/;
var amo_download_pattern = /^https?:\/\/(addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org)\/[^?#]*\/downloads\/latest\/([^/?#]+)/;
var amo_domain_pattern = /^https?:\/\/(addons\.mozilla\.org|addons(?:-dev)?\.allizom\.org)\//;
var amo_match_patterns = [
'*://addons.mozilla.org/*addon/*',
'*://*.addons.mozilla.org/*review/*',
'*://addons.allizom.org/*addon/*',
'*://*.addons.allizom.org/*review/*',
'*://addons-dev.allizom.org/*addon/*',
'*://*.addons-dev.allizom.org/*review/*',
];
// Depends on: [Reference]
var amo_xpi_cdn_pattern = /^https?:\/\/(?:addons\.cdn\.mozilla\.net|addons-dev-cdn\.allizom\.org)\/user-media\/addons\//;
// Thunderbird
var atn_pattern = /^https?:\/\/((?:addons|addons-stage)\.thunderbird\.net)\/.*?\/addon\/([^/?#]+)/;
var atn_download_pattern = /^https?:\/\/((?:addons|addons-stage)\.thunderbird\.net)\/[^?#]*\/downloads\/latest\/([^/?#]+)/;
var atn_match_patterns = [
'*://addons.thunderbird.net/*addon/*',
'*://addons-stage.thunderbird.net/*addon/*',
];
// page_action.show_matches (in manifest_firefox.json) uses:
// cws_match_patterns, mea_match_pattern, ows_match_pattern, amo_match_patterns
//
// declarativeContent (in background.js) uses the same patterns, translated to a UrlFilter.
//
// popup.js uses can_viewsource_crx_url to determine whether the URL can actually be opened,
// which use regexps that may be stricter than the match patterns.
// string extensionID if valid URL
// null otherwise
function get_extensionID(url) {
var match = cws_pattern.exec(url);
if (match) return match[1];
match = cws_download_pattern.exec(url);
return match && match[1];
}
function get_xpi_url(amoDomain, addonSlug) {
// "[Reference]" is suggested by TheOne:
// [Reference]
// This did not always work. I figured that some packages are platform-specific, so they need
// extra juice, so add it in the mix!
// [Reference]
var platformId;
var ua = navigator.userAgent;
if (ua.includes('Mac')) {
platformId = 3;
} else if (ua.includes('Win')) {
platformId = 5;
} else if (ua.includes('Android')) {
platformId = 7;
} else { // Assume Linux.
platformId = 2;
}
var url = 'https://' + amoDomain + '/firefox/downloads/latest/';
url += addonSlug;
url += '/platform:' + platformId;
url += '/';
// We can put anything here, but it is usually the desired file name for the XPI file.
url += addonSlug + '.xpi';
return url;
}
// Returns location of CRX file for a given extensionID or CWS url or Opera add-on URL
// or Firefox addon URL or Microsoft Edge addon URL.
// Unrecognized values are returned as-is.
// If the input is potentially a CWS URL, ensure that getPlatformInfoAsync() has been called before,
// which results in a CRX URL with richer version information.
function get_crx_url(extensionID_or_url) {
var url;
var match = ows_pattern.exec(extensionID_or_url);
if (match) {
url = 'https://addons.opera.com/extensions/download/';
url += match[1];
url += '/';
return url;
}
match = amo_pattern.exec(extensionID_or_url);
if (match) {
return get_xpi_url(match[1], match[2]);
}
match = atn_pattern.exec(extensionID_or_url);
if (match) {
// Although /firefox/ works too, let's prefer /thunderbird/.
return get_xpi_url(match[1], match[2]).replace('/firefox/', '/thunderbird/');
}
match = mea_pattern.exec(extensionID_or_url) || mea_download_pattern.exec(extensionID_or_url);
if (match) {
return 'https://edge.microsoft.com/extensionwebstorebase/v1/crx?response=redirect&x=id%3D' + match[1] + '%26installsource%3Dondemand%26uc';
}
// Chrome Web Store
match = get_extensionID(extensionID_or_url);
var extensionID = match ? match : extensionID_or_url;
if (!/^[a-z]{32}$/.test(extensionID)) {
return extensionID_or_url;
}
// Note: To avoid the fallback, ensure that getPlatformInfoAsync() has been called before.
var platformInfo = getPlatformInfo();
// Omitting this value is allowed, but add it just in case.
// Source: [Reference]
var product_id = isChromeNotChromium() ? 'chromecrx' : 'chromiumcrx';
// Channel is "unknown" on Chromium on ArchLinux, so using "unknown" will probably be fine for everyone.
var product_channel = 'unknown';
// As of July, the Chrome Web Store sends 204 responses to user agents when their
// Chrome/Chromium version is older than version 31.0.1609.0
var product_version = '9999.0.9999.0';
// Try to detect the Chrome version, and if it is lower than 31.0.1609.0, use a very high version.
// $1 = m.0.r.p // major.minor.revision.patch where minor is always 0 for some reason.
// $2 = m
// $3 = r
var cr_version = /Chrome\/((\d+)\.0\.(\d+)\.\d+)/.exec(navigator.userAgent);
if (cr_version && +cr_version[2] >= 31 && +cr_version[3] >= 1609) {
product_version = cr_version[1];
}
url = 'https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3';
url += '&os=' + platformInfo.os;
url += '&arch=' + platformInfo.arch;
url += '&os_arch=' + platformInfo.arch; // crbug.com/709147 - should be archName of chrome.system.cpu.getInfo
url += '&nacl_arch=' + platformInfo.nacl_arch;
url += '&prod=' + product_id;
url += '&prodchannel=' + product_channel;
url += '&prodversion=' + product_version;
url += '&acceptformat=crx2,crx3';
url += '&x=id%3D' + extensionID;
url += '%26uc';
return url;
}
// Weak detection of whether the user is using Chrome instead of Chromium/Opera/RockMelt/whatever.
function isChromeNotChromium() {
try {
// Chrome ships with a PDF Viewer by default, Chromium does not.
return null !== navigator.plugins.namedItem('Chrome PDF Viewer');
} catch (e) {
// Just in case.
return false;
}
}
// Get location of addon gallery for a given extension
function get_webstore_url(url) {
// Keep logic in sync with is_webstore_url.
var cws = cws_pattern.exec(url) || cws_download_pattern.exec(url);
if (cws) {
return 'https://chrome.google.com/webstore/detail/' + cws[1];
}
var mea = mea_pattern.exec(url) || mea_download_pattern.exec(url);
if (mea) {
return 'https://microsoftedge.microsoft.com/addons/detail/' + mea[1];
}
var ows = ows_pattern.exec(url) || ows_download_pattern.exec(url);
if (ows) {
return 'https://addons.opera.com/extensions/details/' + ows[1];
}
var amo = get_amo_slug(url);
if (amo) {
return 'https://' + get_amo_domain(url) + '/firefox/addon/' + amo;
}
var atn = atn_pattern.exec(url) || atn_download_pattern.exec(url);
if (atn) {
return 'https://' + atn[1] + '/thunderbird/addon/' + atn[2];
}
}
// Return the suggested name of the zip file.
function get_zip_name(url, /*optional*/filename) {
if (!filename) {
var extensionID = get_extensionID(url);
if (!extensionID) {
extensionID = mea_pattern.exec(url) || mea_download_pattern.exec(url);
extensionID = extensionID && extensionID[1];
}
if (extensionID) {
filename = extensionID;
} else {
// [Reference]
// AMO: Lots of different formats, but usually ending with .xpi?....
url = url.split(/[?#]/, 1)[0];
filename = /([^\/]+?)\/*$/.exec(url)[1];
}
}
return filename.replace(/\.(crx|jar|nex|xpi|zip)$/i, '') + '.zip';
}
function get_amo_domain(url) {
var match = amo_domain_pattern.exec(url);
return match ? match[1] : 'addons.mozilla.org';
}
function get_amo_slug(url) {
var match = amo_pattern.exec(url) || amo_download_pattern.exec(url);
if (match) {
return match[2];
}
}
function is_cors_enabled_download_url(url) {
if (
// We're only interested in XPI files from AMO,
// which supports CORS as of March 2020:
// [Reference]
// The following matches the whole AMO domain, including non-CORS
// endpoints. That's fine since we only care about XPI URLs.
amo_domain_pattern.test(url) ||
// The full redirect chain should also allow CORS, including the CDN:
// [Reference]
amo_xpi_cdn_pattern.test(url)
) {
return true;
}
return false;
}
// Some environments enforce restrictions on the URLs that can be accessed.
// This function rewrites the input URL to one that should serve exactly the
// same result as the requested URL, sans restrictions.
function get_equivalent_download_url(url) {
var requestUrl = url;
return requestUrl;
}
// Whether the URL is supported by crxviewer (used in the popup).
function can_viewsource_crx_url(url) {
return is_crx_download_url(url) || is_webstore_url(url);
}
// Whether the given URL is not a CRX file, with certainty.
// Used to determine whether a file should pass through openCRXasZip (of lib/package-to-zip.js).
function is_not_crx_url(url) {
// Chromium-based browsers use CRX with certainty.
if (
cws_pattern.test(url) || cws_download_pattern.test(url) ||
mea_pattern.test(url) || mea_download_pattern.test(url) ||
ows_pattern.test(url) || ows_download_pattern.test(url) ||
/\.(crx|nex)\b/.test(url)
) {
return false;
}
// Firefox-based browsers use XPI, which is not a CRX with certainty.
if (
amo_pattern.test(url) || amo_domain_pattern.test(url) ||
atn_pattern.test(url) || atn_download_pattern.test(url) ||
/\.xpi([#?]|$)/.test(url)
) {
return true;
}
// Unsure: maybe CRX, maybe not.
return false;
}
// Whether the given URL is from a URL that is expected to serve the extension file.
function is_crx_download_url(url) {
return cws_download_pattern.test(url) ||
mea_download_pattern.test(url) ||
ows_download_pattern.test(url) ||
amo_download_pattern.test(url) ||
atn_download_pattern.test(url) ||
/\.(crx|nex|xpi)\b/.test(url);
}
function is_webstore_url(url) {
// Keep logic in sync with get_webstore_url.
return cws_pattern.test(url) ||
mea_pattern.test(url) ||
ows_pattern.test(url) ||
amo_pattern.test(url) ||
atn_pattern.test(url);
}
// |name| should not contain special RegExp characters, except possibly maybe a '[]' at the end.
// If |name| ends with a '[]', then the return value is an array. Otherwise the first match is
// returned.
function getParam(name, querystring) { // Assume name contains no RegEx-specific char
var haystack = querystring || location.search || location.hash;
var pattern, needle, match;
if (name.slice(-2, name.length) === '[]') {
pattern = new RegExp('[&?#]' + name.slice(0, -2) + '\\[\\]=([^&]*)', 'g');
var needles = [];
while ((match = pattern.exec(haystack)) !== null) {
needles.push(decodeURIComponent(match[1]));
}
return needles;
}
pattern = new RegExp('[&?#]' + name + '=([^&]*)');
needle = pattern.exec(haystack);
return needle && decodeURIComponent(needle[1]);
}
function encodeQueryString(params) {
var parts = [];
Object.keys(params).forEach(function (key) {
var value = params[key];
if (Array.isArray(value)) {
value.forEach(function (value2) {
parts.push(encodeQueryStringPart(key + '[]', value2));
});
} else if (value !== void 0) {
parts.push(encodeQueryStringPart(key, value));
}
});
return parts.join('&');
function encodeQueryStringPart(key, value) {
value = encodeURIComponent(value);
return key + '=' + value;
}
}