4chan-XZ/src/platform/CrossOrigin.ts
2023-04-27 22:28:26 +02:00

234 lines
7.6 KiB
TypeScript

import QR from "../Posting/QR";
import $ from "./$";
import { dict, platform } from "./helpers";
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS206: Consider reworking classes to avoid initClass
* DS207: Consider shorter variations of null checks
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
let eventPageRequest;
if (platform === 'crx') {
eventPageRequest = (function () {
const callbacks = [];
chrome.runtime.onMessage.addListener(function(response) {
callbacks[response.id](response.data);
return delete callbacks[response.id];});
return (params, cb) => chrome.runtime.sendMessage(params, id => callbacks[id] = cb);
})();
}
var CrossOrigin = {
binary(url, cb, headers = dict()) {
// XXX https://forums.lanik.us/viewtopic.php?f=64&t=24173&p=78310
url = url.replace(/^((?:https?:)?\/\/(?:\w+\.)?(?:4chan|4channel|4cdn)\.org)\/adv\//, '$1//adv/');
if (platform === 'crx') {
eventPageRequest({type: 'ajax', url, headers, responseType: 'arraybuffer'}, function({response, responseHeaderString}) {
if (response) { response = new Uint8Array(response); }
return cb(response, responseHeaderString);
});
} else {
const fallback = function() {
return $.ajax(url, {
headers,
responseType: 'arraybuffer',
onloadend() {
if (this.status && this.response) {
return cb(new Uint8Array(this.response), this.getAllResponseHeaders());
} else {
return cb(null);
}
}
});
};
if ((typeof window.GM_xmlhttpRequest === 'undefined' || window.GM_xmlhttpRequest === null)) {
fallback();
return;
}
const gmOptions = {
method: "GET",
url,
headers,
responseType: 'arraybuffer',
overrideMimeType: 'text/plain; charset=x-user-defined',
onload(xhr) {
let data;
if (xhr.response instanceof ArrayBuffer) {
data = new Uint8Array(xhr.response);
} else {
const r = xhr.responseText;
data = new Uint8Array(r.length);
let i = 0;
while (i < r.length) {
data[i] = r.charCodeAt(i);
i++;
}
}
return cb(data, xhr.responseHeaders);
},
onerror() {
return cb(null);
},
onabort() {
return cb(null);
}
};
try {
return (GM?.xmlHttpRequest || GM_xmlhttpRequest)(gmOptions);
} catch (error) {
return fallback();
}
}
},
file(url, cb) {
return CrossOrigin.binary(url, function(data, headers) {
if (data == null) { return cb(null); }
let name = url.match(/([^\/?#]+)\/*(?:$|[?#])/)?.[1];
const contentType = headers.match(/Content-Type:\s*(.*)/i)?.[1];
const contentDisposition = headers.match(/Content-Disposition:\s*(.*)/i)?.[1];
let mime = contentType?.match(/[^;]*/)[0] || 'application/octet-stream';
const match =
contentDisposition?.match(/\bfilename\s*=\s*"((\\"|[^"])+)"/i)?.[1] ||
contentType?.match(/\bname\s*=\s*"((\\"|[^"])+)"/i)?.[1];
if (match) {
name = match.replace(/\\"/g, '"');
}
if (/^text\/plain;\s*charset=x-user-defined$/i.test(mime)) {
// In JS Blocker (Safari) content type comes back as 'text/plain; charset=x-user-defined'; guess from filename instead.
mime = $.getOwn(QR.typeFromExtension, name.match(/[^.]*$/)[0].toLowerCase()) || 'application/octet-stream';
}
const blob = new Blob([data], {type: mime});
blob.name = name;
return cb(blob);
});
},
Request: (function() {
const Request = class Request {
static initClass() {
this.prototype.status = 0;
this.prototype.statusText = '';
this.prototype.response = null;
this.prototype.responseHeaderString = null;
}
getResponseHeader(headerName) {
if ((this.responseHeaders == null) && (this.responseHeaderString != null)) {
this.responseHeaders = dict();
for (var header of this.responseHeaderString.split('\r\n')) {
var i;
if ((i = header.indexOf(':')) >= 0) {
var key = header.slice(0, i).trim().toLowerCase();
var val = header.slice(i+1).trim();
this.responseHeaders[key] = val;
}
}
}
return this.responseHeaders?.[headerName.toLowerCase()] ?? null;
}
abort() {}
onloadend() {}
};
Request.initClass();
return Request;
})(),
// Attempts to fetch `url` using cross-origin privileges, if available.
// Interface is a subset of that of $.ajax.
// Options:
// `onloadend` - called with the returned object as `this` on success or error/abort/timeout.
// `timeout` - time limit for request
// `responseType` - expected response type, 'json' by default; 'json' and 'text' supported
// `headers` - request headers
// Returned object properties:
// `status` - HTTP status (0 if connection not successful)
// `statusText` - HTTP status text
// `response` - decoded response body
// `abort` - function for aborting the request (silently fails on some platforms)
// `getResponseHeader` - function for reading response headers
ajax(url, options={}) {
let gmReq;
let {onloadend, timeout, responseType, headers} = options;
if (responseType == null) { responseType = 'json'; }
if ((window.GM?.xmlHttpRequest == null) && (typeof window.GM_xmlhttpRequest === 'undefined' || window.GM_xmlhttpRequest === null)) {
return $.ajax(url, options);
}
const req = new CrossOrigin.Request();
req.onloadend = onloadend;
if (platform === 'userscript') {
const gmOptions = {
method: 'GET',
url,
headers,
timeout,
onload(xhr) {
try {
const response = (() => { switch (responseType) {
case 'json':
if (xhr.responseText) { return JSON.parse(xhr.responseText); } else { return null; }
default:
return xhr.responseText;
} })();
$.extend(req, {
response,
status: xhr.status,
statusText: xhr.statusText,
responseHeaderString: xhr.responseHeaders
});
} catch (error) {}
return req.onloadend();
},
onerror() { return req.onloadend(); },
onabort() { return req.onloadend(); },
ontimeout() { return req.onloadend(); }
};
try {
gmReq = (GM?.xmlHttpRequest || GM_xmlhttpRequest)(gmOptions);
} catch (error) {
return $.ajax(url, options);
}
if (gmReq && (typeof gmReq.abort === 'function')) {
req.abort = function() {
try {
return gmReq.abort();
} catch (error1) {}
};
}
} else {
eventPageRequest({type: 'ajax', url, responseType, headers, timeout}, function(result) {
if (result.status) {
$.extend(req, result);
}
return req.onloadend();
});
}
return req;
},
cache(url, cb) {
return $.cache(url, cb,
{ajax: CrossOrigin.ajax});
},
permission(cb, cbFail, origins) {
if (platform === 'crx') {
return eventPageRequest({type: 'permission', origins}, function(result) {
if (result) {
return cb();
} else {
return cbFail();
}
});
}
return cb();
},
};
export default CrossOrigin;