UserScript: add csp bypassing fetch (#284)
This commit is contained in:
		
							parent
							
								
									53d0a55561
								
							
						
					
					
						commit
						8a5a5c7d1e
					
				
					 3 changed files with 114 additions and 2 deletions
				
			
		
							
								
								
									
										107
									
								
								browser/GMPolyfill.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								browser/GMPolyfill.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -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; | ||||||
|  | @ -7,7 +7,7 @@ | ||||||
| // @supportURL      https://github.com/Vendicated/Vencord
 | // @supportURL      https://github.com/Vendicated/Vencord
 | ||||||
| // @license         GPL-3.0
 | // @license         GPL-3.0
 | ||||||
| // @match           *://*.discord.com/*
 | // @match           *://*.discord.com/*
 | ||||||
| // @grant           none
 | // @grant           GM_xmlhttpRequest
 | ||||||
| // @run-at          document-start
 | // @run-at          document-start
 | ||||||
| // @compatible      chrome Chrome + Tampermonkey or Violentmonkey
 | // @compatible      chrome Chrome + Tampermonkey or Violentmonkey
 | ||||||
| // @compatible      firefox Firefox Tampermonkey
 | // @compatible      firefox Firefox Tampermonkey
 | ||||||
|  |  | ||||||
|  | @ -60,13 +60,18 @@ await Promise.all( | ||||||
|         }), |         }), | ||||||
|         esbuild.build({ |         esbuild.build({ | ||||||
|             ...commonOptions, |             ...commonOptions, | ||||||
|  |             inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], | ||||||
|  |             define: { | ||||||
|  |                 "window": "unsafeWindow", | ||||||
|  |                 ...(commonOptions?.define) | ||||||
|  |             }, | ||||||
|             outfile: "dist/Vencord.user.js", |             outfile: "dist/Vencord.user.js", | ||||||
|             banner: { |             banner: { | ||||||
|                 js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${PackageJSON.version}.${new Date().getTime()}`) |                 js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", `${PackageJSON.version}.${new Date().getTime()}`) | ||||||
|             }, |             }, | ||||||
|             footer: { |             footer: { | ||||||
|                 // UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
 |                 // 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});" | ||||||
|             }, |             }, | ||||||
|         }) |         }) | ||||||
|     ] |     ] | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue