4chan-XZ/src/platform/CrossOrigin.js
2023-04-16 00:08:22 +02:00

290 lines
8.3 KiB
JavaScript

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) {
if (platform === 'userscript') {
return CrossOrigin.file(url, cb)
}
return eventPageRequest({ type: 'cache', url }, function (result) {
if (result) {
return cb(result)
} else {
return cb(null)
}
})
},
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