From c073870f243e383f4b4d6c1bc77be818240cc4df Mon Sep 17 00:00:00 2001 From: Peng-YM <1048217874pengym@gmail.com> Date: Thu, 11 Aug 2022 01:07:16 +0800 Subject: [PATCH] perf: Add support for sending http requests using specific nodes Only supported on Loon & Surge --- backend/package.json | 3 +- backend/pnpm-lock.yaml | 87 ++++++++++ .../src/core/proxy-utils/processors/index.js | 6 +- backend/src/vendor/open-api.js | 11 ++ scripts/ip-flag.js | 151 ++++++++++++++++++ 5 files changed, 256 insertions(+), 2 deletions(-) create mode 100644 scripts/ip-flag.js diff --git a/backend/package.json b/backend/package.json index 5448f4c..573176a 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "sub-store", - "version": "2.12.4", + "version": "2.12.5", "description": "Advanced Subscription Manager for QX, Loon, Surge, Stash and ShadowRocket.", "main": "src/main.js", "scripts": { @@ -19,6 +19,7 @@ "js-base64": "^3.7.2", "lodash": "^4.17.21", "request": "^2.88.2", + "requests": "^0.3.0", "semver": "^7.3.7", "static-js-yaml": "^1.0.0", "uuid": "^8.3.2" diff --git a/backend/pnpm-lock.yaml b/backend/pnpm-lock.yaml index 405ee06..3a30c63 100644 --- a/backend/pnpm-lock.yaml +++ b/backend/pnpm-lock.yaml @@ -31,6 +31,7 @@ specifiers: prettier: 2.6.2 prettier-plugin-sort-imports: ^1.6.1 request: ^2.88.2 + requests: ^0.3.0 semver: ^7.3.7 static-js-yaml: ^1.0.0 tinyify: ^3.0.0 @@ -43,6 +44,7 @@ dependencies: js-base64: registry.npmmirror.com/js-base64/3.7.2 lodash: registry.npmmirror.com/lodash/4.17.21 request: registry.npmmirror.com/request/2.88.2 + requests: registry.npmmirror.com/requests/0.3.0 semver: registry.npmmirror.com/semver/7.3.7 static-js-yaml: registry.npmmirror.com/static-js-yaml/1.0.0 uuid: registry.npmmirror.com/uuid/8.3.2 @@ -2250,6 +2252,12 @@ packages: follow-redirects: registry.npmmirror.com/follow-redirects/1.13.0 dev: true + registry.npmmirror.com/axo/0.0.2: + resolution: {integrity: sha512-8CC4Mb+OhK97UEng0PgiqUDNZjzVcWDsV+G2vLYCQn1jEL7y6VqiRVlZlRu+aA/ckSznmNzW6X1I6nj2As/haQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/axo/-/axo-0.0.2.tgz} + name: axo + version: 0.0.2 + dev: false + registry.npmmirror.com/babel-plugin-dynamic-import-node/2.3.3: resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz} name: babel-plugin-dynamic-import-node @@ -4296,6 +4304,12 @@ packages: es5-ext: registry.npmmirror.com/es5-ext/0.10.61 dev: true + registry.npmmirror.com/eventemitter3/4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz} + name: eventemitter3 + version: 4.0.7 + dev: false + registry.npmmirror.com/events/3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/events/-/events-3.3.0.tgz} name: events @@ -4410,6 +4424,12 @@ packages: name: extend version: 3.0.2 + registry.npmmirror.com/extendible/0.1.1: + resolution: {integrity: sha512-AglckQA0TJV8/ZmhQcNmaaFcFFPXFIoZbfuoQOlGDK7Jh/roWotYzJ7ik1FBBCHBr8n7CgTR8lXXPAN8Rfb7rw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/extendible/-/extendible-0.1.1.tgz} + name: extendible + version: 0.1.1 + dev: false + registry.npmmirror.com/extglob/2.0.4: resolution: {integrity: sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/extglob/-/extglob-2.0.4.tgz} name: extglob @@ -4435,6 +4455,12 @@ packages: engines: {'0': node >=0.6.0} dev: false + registry.npmmirror.com/failure/1.1.1: + resolution: {integrity: sha512-lzrrk0NUfjVeU3jLmfU01zP5bfg4XVFxHREYGvgJowaCqHLSQtqIGENH/CU+oSs6yfYObdSM7b9UY/3p2VJOSg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/failure/-/failure-1.1.1.tgz} + name: failure + version: 1.1.1 + dev: false + registry.npmmirror.com/falafel/2.2.5: resolution: {integrity: sha512-HuC1qF9iTnHDnML9YZAdCDQwT0yKl/U55K4XSUXqGAA2GLoafFgWRqdAbhWJxXaYD4pyoVxAJ8wH670jMpI9DQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/falafel/-/falafel-2.2.5.tgz} name: falafel @@ -5219,6 +5245,12 @@ packages: glogg: registry.npmmirror.com/glogg/1.0.2 dev: true + registry.npmmirror.com/hang/1.0.0: + resolution: {integrity: sha512-vtBz98Bt/Tbm03cZO5Ymc7ZL8ead/jIx9T5Wg/xuz+9BXPAJNJSdGQW63LoaesogUQKTpHyal339hxTaTf/APg==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/hang/-/hang-1.0.0.tgz} + name: hang + version: 1.0.0 + dev: false + registry.npmmirror.com/har-schema/2.0.0: resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/har-schema/-/har-schema-2.0.0.tgz} name: har-schema @@ -6336,6 +6368,17 @@ packages: strip-bom: registry.npmmirror.com/strip-bom/2.0.0 dev: true + registry.npmmirror.com/loads/0.0.4: + resolution: {integrity: sha512-XjPzzYIHkuMNqYyvh6AECQAHi682nyKO9TMdMYnaz7QbPDI/KIeSIjRhAlXIbRMPYAgtLUYgPlD3mtKZ4Q8SYA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/loads/-/loads-0.0.4.tgz} + name: loads + version: 0.0.4 + dependencies: + failure: registry.npmmirror.com/failure/1.1.1 + one-time: registry.npmmirror.com/one-time/0.0.4 + xhr-response: registry.npmmirror.com/xhr-response/1.0.1 + xhr-status: registry.npmmirror.com/xhr-status/1.0.1 + dev: false + registry.npmmirror.com/locate-path/3.0.0: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/locate-path/-/locate-path-3.0.0.tgz} name: locate-path @@ -6876,6 +6919,12 @@ packages: semver: registry.npmmirror.com/semver/5.7.1 dev: true + registry.npmmirror.com/node-http-xhr/1.3.4: + resolution: {integrity: sha512-0bA08/2RKWxw6pMkOVd3KP+0F5+ifQLMMTDxrCgxlgkoU1N8DhCbCSAYEqpgaVYM2smvbVVewiXjW+8AyoLfxQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/node-http-xhr/-/node-http-xhr-1.3.4.tgz} + name: node-http-xhr + version: 1.3.4 + dev: false + registry.npmmirror.com/node-releases/2.0.4: resolution: {integrity: sha512-gbMzqQtTtDz/00jQzZ21PQzdI9PyLYqUSvD0p3naOhX4odFji0ZxYdnVwPTxmSwkmxhcFImpozceidSG+AgoPQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/node-releases/-/node-releases-2.0.4.tgz} name: node-releases @@ -7110,6 +7159,12 @@ packages: wrappy: registry.npmmirror.com/wrappy/1.0.2 dev: true + registry.npmmirror.com/one-time/0.0.4: + resolution: {integrity: sha512-qAMrwuk2xLEutlASoiPiAMW3EN3K96Ka/ilSXYr6qR1zSVXw2j7+yDSqGTC4T9apfLYxM3tLLjKvgPdAUK7kYQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/one-time/-/one-time-0.0.4.tgz} + name: one-time + version: 0.0.4 + dev: false + registry.npmmirror.com/optionator/0.8.3: resolution: {integrity: sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/optionator/-/optionator-0.8.3.tgz} name: optionator @@ -8057,6 +8112,20 @@ packages: uuid: registry.npmmirror.com/uuid/3.4.0 dev: false + registry.npmmirror.com/requests/0.3.0: + resolution: {integrity: sha512-1B6nkiHjC1O1cSgFhEwkc+xd8vuj04h7xSmCg5yI8nmhCIKbPkX47od8erQ2pokBt5qxUO7dwP4jplXD6k6ISA==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/requests/-/requests-0.3.0.tgz} + name: requests + version: 0.3.0 + dependencies: + axo: registry.npmmirror.com/axo/0.0.2 + eventemitter3: registry.npmmirror.com/eventemitter3/4.0.7 + extendible: registry.npmmirror.com/extendible/0.1.1 + hang: registry.npmmirror.com/hang/1.0.0 + loads: registry.npmmirror.com/loads/0.0.4 + node-http-xhr: registry.npmmirror.com/node-http-xhr/1.3.4 + xhr-send: registry.npmmirror.com/xhr-send/1.0.0 + dev: false + registry.npmmirror.com/require-directory/2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz} name: require-directory @@ -9731,6 +9800,24 @@ packages: engines: {node: '>=8'} dev: true + registry.npmmirror.com/xhr-response/1.0.1: + resolution: {integrity: sha512-m2FlVRCl3VqDcpc8UaWZJpwuHpFR2vYeXv6ipXU2Uuu4vNKFYVEFI0emuJN370Fge+JCbiAnS+JJmSoWVmWrjQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xhr-response/-/xhr-response-1.0.1.tgz} + name: xhr-response + version: 1.0.1 + dev: false + + registry.npmmirror.com/xhr-send/1.0.0: + resolution: {integrity: sha512-789EG4qW6Z0nPvG74AV3WWQCnBG5HxJXNiBsnEivZ8OpbvVA0amH0+g+MNT99o5kt/XLdRezm5KS1wJzcGJztw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xhr-send/-/xhr-send-1.0.0.tgz} + name: xhr-send + version: 1.0.0 + dev: false + + registry.npmmirror.com/xhr-status/1.0.1: + resolution: {integrity: sha512-VF0WSqtmkf56OmF26LCWsWvRb1a+WYGdHDoQnPPCVUQTM8CVUAOBcUDsm7nP7SQcgEEdrvF4DmhEADuXdGieyw==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xhr-status/-/xhr-status-1.0.1.tgz} + name: xhr-status + version: 1.0.1 + dev: false + registry.npmmirror.com/xtend/2.1.2: resolution: {integrity: sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==, registry: http://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/xtend/-/xtend-2.1.2.tgz} name: xtend diff --git a/backend/src/core/proxy-utils/processors/index.js b/backend/src/core/proxy-utils/processors/index.js index 9aeffcd..3d0ac20 100644 --- a/backend/src/core/proxy-utils/processors/index.js +++ b/backend/src/core/proxy-utils/processors/index.js @@ -5,6 +5,7 @@ import { getFlag } from '@/utils/geo'; import lodash from 'lodash'; import $ from '@/core/app'; import { hex_md5 } from '@/vendor/md5'; +import {ProxyUtils} from '@/core/proxy-utils'; /** The rule "(name CONTAINS "🇨🇳") AND (port IN [80, 443])" can be expressed as follows: @@ -631,6 +632,7 @@ function createDynamicFunction(name, script, $arguments) { '$persistentStore', '$httpClient', '$notification', + 'ProxyUtils', `${script}\n return ${name}`, )( $arguments, @@ -642,13 +644,15 @@ function createDynamicFunction(name, script, $arguments) { $httpClient, // eslint-disable-next-line no-undef $notification, + ProxyUtils ); } else { return new Function( '$arguments', '$substore', 'lodash', + 'ProxyUtils', `${script}\n return ${name}`, - )($arguments, $, lodash); + )($arguments, $, lodash, ProxyUtils); } } diff --git a/backend/src/vendor/open-api.js b/backend/src/vendor/open-api.js index 3f9f2cd..929d928 100644 --- a/backend/src/vendor/open-api.js +++ b/backend/src/vendor/open-api.js @@ -253,6 +253,17 @@ export function HTTP(defaultOptions = { baseURL: '' }) { events.onRequest(method, options); + if (options.node) { + // Surge & Loon allow connecting to a server using a specified proxy node + if (isSurge) { + const build = $environment['surge-build']; + if (build && parseInt(build) >= 2407) { + options['policy-descriptor'] = options.node; + delete options.node; + } + } + } + let worker; if (isQX) { worker = $task.fetch({ diff --git a/scripts/ip-flag.js b/scripts/ip-flag.js new file mode 100644 index 0000000..1ecb546 --- /dev/null +++ b/scripts/ip-flag.js @@ -0,0 +1,151 @@ +const RESOURCE_CACHE_KEY = '#sub-store-cached-resource'; +const CACHE_EXPIRATION_TIME_MS = 10 * 60 * 1000; +const $ = $substore; + +class ResourceCache { + constructor(expires) { + this.expires = expires; + if (!$.read(RESOURCE_CACHE_KEY)) { + $.write('{}', RESOURCE_CACHE_KEY); + } + this.resourceCache = JSON.parse($.read(RESOURCE_CACHE_KEY)); + this._cleanup(); + } + + _cleanup() { + // clear obsolete cached resource + let clear = false; + Object.entries(this.resourceCache).forEach((entry) => { + const [id, updated] = entry; + if (new Date().getTime() - updated > this.expires) { + $.delete(`#${id}`); + delete this.resourceCache[id]; + clear = true; + } + }); + if (clear) this._persist(); + } + + revokeAll() { + Object.keys(this.resourceCache).forEach((id) => { + $.delete(`#${id}`); + }); + this.resourceCache = {}; + this._persist(); + } + + _persist() { + $.write(JSON.stringify(this.resourceCache), RESOURCE_CACHE_KEY); + } + + get(id) { + const updated = this.resourceCache[id]; + if (updated && new Date().getTime() - updated <= this.expires) { + return $.read(`#${id}`); + } + return null; + } + + set(id, value) { + this.resourceCache[id] = new Date().getTime(); + this._persist(); + $.write(value, `#${id}`); + } +} + +const resourceCache = new ResourceCache(CACHE_EXPIRATION_TIME_MS); + +async function operator(proxies) { + const { isLoon, isSurge } = $substore.env; + let support = true; + if (isLoon) { + support = true; + } else if (isSurge) { + const build = $environment['surge-build']; + if (build && parseInt(build) >= 2407) { + support = true; + } + } + + if (support) { + await Promise.all(proxies.map(async proxy => { + try { + // remove the original flag + let proxyName = removeFlag(proxy.name); + + // query ip-api + const countryCode = await queryIpApi(proxy); + + proxyName = getFlagEmoji(countryCode) + ' ' + proxyName; + proxy.name = proxyName; + } catch (err) { + // TODO: + } + })); + } else { + $.error(`IP Flag only supports Loon and Surge!`); + } + return proxies; +} + +const tasks = new Map(); +async function queryIpApi(proxy) { + const id = getId(proxy); + if (tasks.has(id)) { + return tasks.get(id); + } + + const ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:78.0) Gecko/20100101 Firefox/78.0"; + const headers = { + "User-Agent": ua + }; + const { isLoon } = $substore.env; + const target = isLoon ? "Loon" : "Surge"; + const result = new Promise((resolve, reject) => { + const cached = resourceCache.get(id); + if (cached) { + resolve(cached); + } + const url = `http://ip-api.com/json`; + const node = ProxyUtils.produce([proxy], target); + $.http.get({ + url, + headers, + node + }).then(resp => { + const body = resp.body; + const data = JSON.parse(body); + if (data.status === "success") { + resourceCache.set(id, data.countryCode); + resolve(data.countryCode); + } else { + reject(new Error(data.message)); + } + }).catch(err => { + console.log(err); + reject(err); + }); + }); + tasks.set(id, result); + return result; +} + +function getId(proxy) { + return MD5(`IP-FLAG-${proxy.server}-${proxy.port}`); +} + +function getFlagEmoji(countryCode) { + const codePoints = countryCode + .toUpperCase() + .split('') + .map(char => 127397 + char.charCodeAt()); + return String.fromCodePoint(...codePoints); +} + +function removeFlag(str) { + return str + .replace(/[\uD83C][\uDDE6-\uDDFF][\uD83C][\uDDE6-\uDDFF]/g, '') + .trim(); +} + +var MD5 = function (d) { var r = M(V(Y(X(d), 8 * d.length))); return r.toLowerCase() }; function M(d) { for (var _, m = "0123456789ABCDEF", f = "", r = 0; r < d.length; r++)_ = d.charCodeAt(r), f += m.charAt(_ >>> 4 & 15) + m.charAt(15 & _); return f } function X(d) { for (var _ = Array(d.length >> 2), m = 0; m < _.length; m++)_[m] = 0; for (m = 0; m < 8 * d.length; m += 8)_[m >> 5] |= (255 & d.charCodeAt(m / 8)) << m % 32; return _ } function V(d) { for (var _ = "", m = 0; m < 32 * d.length; m += 8)_ += String.fromCharCode(d[m >> 5] >>> m % 32 & 255); return _ } function Y(d, _) { d[_ >> 5] |= 128 << _ % 32, d[14 + (_ + 64 >>> 9 << 4)] = _; for (var m = 1732584193, f = -271733879, r = -1732584194, i = 271733878, n = 0; n < d.length; n += 16) { var h = m, t = f, g = r, e = i; f = md5_ii(f = md5_ii(f = md5_ii(f = md5_ii(f = md5_hh(f = md5_hh(f = md5_hh(f = md5_hh(f = md5_gg(f = md5_gg(f = md5_gg(f = md5_gg(f = md5_ff(f = md5_ff(f = md5_ff(f = md5_ff(f, r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 0], 7, -680876936), f, r, d[n + 1], 12, -389564586), m, f, d[n + 2], 17, 606105819), i, m, d[n + 3], 22, -1044525330), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 4], 7, -176418897), f, r, d[n + 5], 12, 1200080426), m, f, d[n + 6], 17, -1473231341), i, m, d[n + 7], 22, -45705983), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 8], 7, 1770035416), f, r, d[n + 9], 12, -1958414417), m, f, d[n + 10], 17, -42063), i, m, d[n + 11], 22, -1990404162), r = md5_ff(r, i = md5_ff(i, m = md5_ff(m, f, r, i, d[n + 12], 7, 1804603682), f, r, d[n + 13], 12, -40341101), m, f, d[n + 14], 17, -1502002290), i, m, d[n + 15], 22, 1236535329), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 1], 5, -165796510), f, r, d[n + 6], 9, -1069501632), m, f, d[n + 11], 14, 643717713), i, m, d[n + 0], 20, -373897302), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 5], 5, -701558691), f, r, d[n + 10], 9, 38016083), m, f, d[n + 15], 14, -660478335), i, m, d[n + 4], 20, -405537848), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 9], 5, 568446438), f, r, d[n + 14], 9, -1019803690), m, f, d[n + 3], 14, -187363961), i, m, d[n + 8], 20, 1163531501), r = md5_gg(r, i = md5_gg(i, m = md5_gg(m, f, r, i, d[n + 13], 5, -1444681467), f, r, d[n + 2], 9, -51403784), m, f, d[n + 7], 14, 1735328473), i, m, d[n + 12], 20, -1926607734), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 5], 4, -378558), f, r, d[n + 8], 11, -2022574463), m, f, d[n + 11], 16, 1839030562), i, m, d[n + 14], 23, -35309556), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 1], 4, -1530992060), f, r, d[n + 4], 11, 1272893353), m, f, d[n + 7], 16, -155497632), i, m, d[n + 10], 23, -1094730640), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 13], 4, 681279174), f, r, d[n + 0], 11, -358537222), m, f, d[n + 3], 16, -722521979), i, m, d[n + 6], 23, 76029189), r = md5_hh(r, i = md5_hh(i, m = md5_hh(m, f, r, i, d[n + 9], 4, -640364487), f, r, d[n + 12], 11, -421815835), m, f, d[n + 15], 16, 530742520), i, m, d[n + 2], 23, -995338651), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 0], 6, -198630844), f, r, d[n + 7], 10, 1126891415), m, f, d[n + 14], 15, -1416354905), i, m, d[n + 5], 21, -57434055), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 12], 6, 1700485571), f, r, d[n + 3], 10, -1894986606), m, f, d[n + 10], 15, -1051523), i, m, d[n + 1], 21, -2054922799), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 8], 6, 1873313359), f, r, d[n + 15], 10, -30611744), m, f, d[n + 6], 15, -1560198380), i, m, d[n + 13], 21, 1309151649), r = md5_ii(r, i = md5_ii(i, m = md5_ii(m, f, r, i, d[n + 4], 6, -145523070), f, r, d[n + 11], 10, -1120210379), m, f, d[n + 2], 15, 718787259), i, m, d[n + 9], 21, -343485551), m = safe_add(m, h), f = safe_add(f, t), r = safe_add(r, g), i = safe_add(i, e) } return Array(m, f, r, i) } function md5_cmn(d, _, m, f, r, i) { return safe_add(bit_rol(safe_add(safe_add(_, d), safe_add(f, i)), r), m) } function md5_ff(d, _, m, f, r, i, n) { return md5_cmn(_ & m | ~_ & f, d, _, r, i, n) } function md5_gg(d, _, m, f, r, i, n) { return md5_cmn(_ & f | m & ~f, d, _, r, i, n) } function md5_hh(d, _, m, f, r, i, n) { return md5_cmn(_ ^ m ^ f, d, _, r, i, n) } function md5_ii(d, _, m, f, r, i, n) { return md5_cmn(m ^ (_ | ~f), d, _, r, i, n) } function safe_add(d, _) { var m = (65535 & d) + (65535 & _); return (d >> 16) + (_ >> 16) + (m >> 16) << 16 | 65535 & m } function bit_rol(d, _) { return d << _ | d >>> 32 - _ } \ No newline at end of file