aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCloudburst <18114966+C10udburst@users.noreply.github.com>2022-12-04 13:58:29 +0100
committerGitHub <noreply@github.com>2022-12-04 13:58:29 +0100
commit8a5a5c7d1e6ab733254be6a7e6f46a4f5fab45fb (patch)
treeeee3a214c6c6c6f15a12ff04adcc42879a9a0d20
parent53d0a555618bd3f67aca281284b62a3d20080fe6 (diff)
downloadVencord-8a5a5c7d1e6ab733254be6a7e6f46a4f5fab45fb.tar.gz
Vencord-8a5a5c7d1e6ab733254be6a7e6f46a4f5fab45fb.tar.bz2
Vencord-8a5a5c7d1e6ab733254be6a7e6f46a4f5fab45fb.zip
UserScript: add csp bypassing fetch (#284)
-rw-r--r--browser/GMPolyfill.js107
-rw-r--r--browser/userscript.meta.js2
-rwxr-xr-xscripts/build/buildWeb.mjs7
3 files changed, 114 insertions, 2 deletions
diff --git a/browser/GMPolyfill.js b/browser/GMPolyfill.js
new file mode 100644
index 0000000..3e0606d
--- /dev/null
+++ b/browser/GMPolyfill.js
@@ -0,0 +1,107 @@
+/*
+ * Vencord, a modification for Discord's desktop app
+ * Copyright (c) 2022 Vendicated and contributors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+*/
+
+function fetchOptions(url) {
+ return new Promise((resolve, reject) => {
+ const opt = {
+ method: "OPTIONS",
+ url: url,
+ };
+ opt.onload = resp => resolve(resp.responseHeaders);
+ opt.ontimeout = () => reject("fetch timeout");
+ opt.onerror = () => reject("fetch error");
+ opt.onabort = () => reject("fetch abort");
+ GM_xmlhttpRequest(opt);
+ });
+}
+
+function parseHeaders(headers) {
+ if (!headers)
+ return {};
+ const result = {};
+ const headersArr = headers.trim().split("\n");
+ for (var i = 0; i < headersArr.length; i++) {
+ var row = headersArr[i];
+ var index = row.indexOf(":")
+ , key = row.slice(0, index).trim().toLowerCase()
+ , value = row.slice(index + 1).trim();
+
+ if (result[key] === undefined) {
+ result[key] = value;
+ } else if (Array.isArray(result[key])) {
+ result[key].push(value);
+ } else {
+ result[key] = [result[key], value];
+ }
+ }
+ return result;
+}
+
+// returns true if CORS permits request
+async function checkCors(url, method) {
+ const headers = parseHeaders(await fetchOptions(url));
+
+ const origin = headers["access-control-allow-origin"];
+ if (origin !== "*" && origin !== window.location.origin) return false;
+
+ const methods = headers["access-control-allow-methods"]?.split(/,\s/g);
+ if (methods && !methods.includes(method)) return false;
+
+ return true;
+}
+
+function blobTo(to, blob) {
+ if (to === "arrayBuffer" && blob.arrayBuffer) return blob.arrayBuffer();
+ return new Promise((resolve, reject) => {
+ var fileReader = new FileReader();
+ fileReader.onload = event => resolve(event.target.result);
+ if (to === "arrayBuffer") fileReader.readAsArrayBuffer(blob);
+ else if (to === "text") fileReader.readAsText(blob, "utf-8");
+ else reject("unknown to");
+ });
+}
+
+function GM_fetch(url, opt) {
+ return new Promise((resolve, reject) => {
+ checkCors(url, opt?.method || "GET")
+ .then(can => {
+ if (can) {
+ // https://www.tampermonkey.net/documentation.php?ext=dhdg#GM_xmlhttpRequest
+ const options = opt || {};
+ options.url = url;
+ options.data = options.body;
+ options.responseType = "blob";
+ options.onload = resp => {
+ var blob = resp.response;
+ resp.blob = () => Promise.resolve(blob);
+ resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
+ resp.text = () => blobTo("text", blob);
+ resp.json = async () => JSON.parse(await blobTo("text", blob));
+ resolve(resp);
+ };
+ options.ontimeout = () => reject("fetch timeout");
+ options.onerror = () => reject("fetch error");
+ options.onabort = () => reject("fetch abort");
+ GM_xmlhttpRequest(options);
+ } else {
+ reject("CORS issue");
+ }
+ });
+ });
+}
+export const fetch = GM_fetch;
diff --git a/browser/userscript.meta.js b/browser/userscript.meta.js
index 81cf3e7..5b2a39b 100644
--- a/browser/userscript.meta.js
+++ b/browser/userscript.meta.js
@@ -7,7 +7,7 @@
// @supportURL https://github.com/Vendicated/Vencord
// @license GPL-3.0
// @match *://*.discord.com/*
-// @grant none
+// @grant GM_xmlhttpRequest
// @run-at document-start
// @compatible chrome Chrome + Tampermonkey or Violentmonkey
// @compatible firefox Firefox Tampermonkey
diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs
index 7508937..c85d8aa 100755
--- a/scripts/build/buildWeb.mjs
+++ b/scripts/build/buildWeb.mjs
@@ -60,13 +60,18 @@ await Promise.all(
}),
esbuild.build({
...commonOptions,
+ inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
+ define: {
+ "window": "unsafeWindow",
+ ...(commonOptions?.define)
+ },
outfile: "dist/Vencord.user.js",
banner: {
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${PackageJSON.version}.${new Date().getTime()}`)
},
footer: {
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
- js: "Object.defineProperty(window,'Vencord',{get:()=>Vencord});"
+ js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
},
})
]