2021-02-18 10:41:06 +00:00
|
|
|
const gm = require("gm")
|
2020-01-12 12:50:21 +00:00
|
|
|
const constants = require("../../lib/constants")
|
2020-04-04 14:57:31 +00:00
|
|
|
const collectors = require("../../lib/collectors")
|
2020-01-12 12:50:21 +00:00
|
|
|
const {request} = require("../../lib/utils/request")
|
2020-06-19 05:57:34 +00:00
|
|
|
const {verifyURL} = require("../../lib/utils/proxyurl")
|
2020-04-04 14:57:31 +00:00
|
|
|
const db = require("../../lib/db")
|
2020-06-19 05:57:34 +00:00
|
|
|
require("../../lib/testimports")(constants, request, db, verifyURL)
|
2020-01-12 12:50:21 +00:00
|
|
|
|
2020-04-04 14:57:31 +00:00
|
|
|
|
|
|
|
function statusCodeIsAcceptable(status) {
|
|
|
|
return (status >= 200 && status < 300) || status === 304
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {string} url
|
|
|
|
*/
|
|
|
|
async function proxyResource(url, suggestedHeaders = {}, refreshCallback = null) {
|
|
|
|
// console.log(`Asked to proxy ${url}\n`, suggestedHeaders)
|
|
|
|
const headersToSend = {}
|
|
|
|
for (const key of ["accept", "accept-encoding", "accept-language", "range"]) {
|
|
|
|
if (suggestedHeaders[key]) headersToSend[key] = suggestedHeaders[key]
|
|
|
|
}
|
2020-04-16 13:13:55 +00:00
|
|
|
const sent = request(url, {headers: headersToSend}, {log: false})
|
2020-04-04 14:57:31 +00:00
|
|
|
const stream = await sent.stream()
|
|
|
|
const response = await sent.response()
|
|
|
|
// console.log(response.status, response.headers)
|
|
|
|
if (statusCodeIsAcceptable(response.status)) {
|
|
|
|
const headersToReturn = {}
|
2020-04-18 09:50:23 +00:00
|
|
|
for (const key of ["content-type", "date", "last-modified", "expires", "cache-control", "accept-ranges", "content-range", "origin", "etag", "content-length", "transfer-encoding"]) {
|
2020-04-04 14:57:31 +00:00
|
|
|
headersToReturn[key] = response.headers.get(key)
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
statusCode: response.status,
|
|
|
|
headers: headersToReturn,
|
|
|
|
stream: stream
|
|
|
|
}
|
2020-04-12 15:23:19 +00:00
|
|
|
} else if (refreshCallback && [410, 404, 403].includes(response.status)) { // profile picture has since changed
|
2020-04-04 14:57:31 +00:00
|
|
|
return refreshCallback()
|
|
|
|
} else {
|
|
|
|
return {
|
|
|
|
statusCode: 502,
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "text/plain; charset=UTF-8"
|
|
|
|
},
|
|
|
|
content: `Instagram returned HTTP status ${response.status}, which is not a success code.`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-12 12:50:21 +00:00
|
|
|
module.exports = [
|
2020-01-29 15:20:20 +00:00
|
|
|
{
|
|
|
|
route: "/imageproxy", methods: ["GET"], code: async (input) => {
|
|
|
|
const verifyResult = verifyURL(input.url)
|
|
|
|
if (verifyResult.status !== "ok") return verifyResult.value
|
2022-02-17 23:04:00 +00:00
|
|
|
if (!["png", "jpg", "webp"].some(ext => verifyResult.url.pathname.endsWith(ext))) return [400, "URL extension is not allowed"]
|
2020-01-29 15:20:20 +00:00
|
|
|
const params = input.url.searchParams
|
|
|
|
const width = +params.get("width")
|
|
|
|
if (typeof width === "number" && !isNaN(width) && width > 0) {
|
|
|
|
/*
|
2021-02-18 10:41:06 +00:00
|
|
|
This uses graphicsmagick to force crop the image to a
|
|
|
|
square. Some thumbnails aren't square and would be
|
|
|
|
stretched on the page without this. If I cropped the
|
|
|
|
images client side, it would have to be done with CSS
|
|
|
|
background-image, which means no <img srcset>.
|
2020-01-29 15:20:20 +00:00
|
|
|
*/
|
2020-03-15 06:50:29 +00:00
|
|
|
return request(verifyResult.url, {}, {log: false}).stream().then(body => {
|
2021-02-18 10:41:06 +00:00
|
|
|
const image = gm(body).gravity("Center").crop(width, width, 0, 0).repage("+")
|
|
|
|
const stream = image.stream("jpg")
|
2020-01-29 15:20:20 +00:00
|
|
|
return {
|
|
|
|
statusCode: 200,
|
|
|
|
contentType: "image/jpeg",
|
|
|
|
headers: {
|
|
|
|
"Cache-Control": constants.caching.image_cache_control
|
|
|
|
},
|
2021-02-18 10:41:06 +00:00
|
|
|
stream
|
2020-01-29 15:20:20 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// No specific size was requested, so just stream proxy the file directly.
|
2020-04-04 14:57:31 +00:00
|
|
|
if (params.has("userID")) {
|
2021-02-18 10:41:06 +00:00
|
|
|
/*
|
|
|
|
Users get special handling, because we need to update
|
|
|
|
their profile picture if an expired version is cached
|
|
|
|
*/
|
2020-04-04 14:57:31 +00:00
|
|
|
return proxyResource(verifyResult.url.toString(), input.req.headers, () => {
|
|
|
|
// If we get here, we got HTTP 410 GONE.
|
|
|
|
const userID = params.get("userID")
|
|
|
|
const storedProfilePicURL = db.prepare("SELECT profile_pic_url FROM Users WHERE user_id = ?").pluck().get(userID)
|
|
|
|
if (storedProfilePicURL === verifyResult.url.toString()) {
|
|
|
|
// Everything looks fine, find out what the new URL for the provided user ID is and store it.
|
|
|
|
return collectors.updateProfilePictureFromReel(userID).then(url => {
|
|
|
|
// Updated. Return the new picture (without recursing)
|
|
|
|
return proxyResource(url, input.req.headers)
|
|
|
|
}).catch(error => {
|
|
|
|
console.error(error)
|
|
|
|
return {
|
|
|
|
statusCode: 500,
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "text/plain; charset=UTF-8"
|
|
|
|
},
|
|
|
|
content: String(error)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
// The request is a lie!
|
|
|
|
return {
|
|
|
|
statusCode: 400,
|
|
|
|
headers: {
|
|
|
|
"Content-Type": "text/plain; charset=UTF-8"
|
|
|
|
},
|
|
|
|
content: "Profile picture must be refreshed, but provided userID parameter does not match the stored profile_pic_url."
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
return proxyResource(verifyResult.url.toString(), input.req.headers)
|
|
|
|
}
|
2020-01-29 15:20:20 +00:00
|
|
|
}
|
2020-01-12 12:50:21 +00:00
|
|
|
}
|
2020-01-29 15:20:20 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
route: "/videoproxy", methods: ["GET"], code: async (input) => {
|
|
|
|
const verifyResult = verifyURL(input.url)
|
|
|
|
if (verifyResult.status !== "ok") return verifyResult.value
|
|
|
|
const url = verifyResult.url
|
|
|
|
if (!["mp4"].some(ext => url.pathname.endsWith(ext))) return [400, "URL extension is not allowed"]
|
2020-04-04 14:57:31 +00:00
|
|
|
return proxyResource(url.toString(), input.req.headers)
|
2020-01-12 12:50:21 +00:00
|
|
|
}
|
2020-01-29 15:20:20 +00:00
|
|
|
}
|
2020-01-12 12:50:21 +00:00
|
|
|
]
|