diff --git a/api/captions.js b/api/captions.js deleted file mode 100644 index f44b921..0000000 --- a/api/captions.js +++ /dev/null @@ -1,27 +0,0 @@ -/** @type {import("node-fetch").default} */ -// @ts-ignore -const fetch = require("node-fetch") -const {getUser} = require("../utils/getuser") -const constants = require("../utils/constants.js") - -module.exports = [ - { - route: `/api/v1/captions/(${constants.regex.video_id})`, methods: ["GET"], code: async ({req, fill, url}) => { - const instanceOrigin = getUser(req).getSettingsOrDefaults().instance - const fetchURL = new URL(`${url.pathname}${url.search}`, instanceOrigin) - return fetch(fetchURL.toString()).then(res => { - return res.text().then(text => { - if (res.status === 200) { - // Remove the position annotations that youtube unhelpfully provides - text = text.replace(/(--> \S+).*/g, "$1") - } - return { - statusCode: res.status, - contentType: res.headers.get("content-type"), - content: text - } - }) - }) - } - } -] diff --git a/api/proxy.js b/api/proxy.js new file mode 100644 index 0000000..a6fecbe --- /dev/null +++ b/api/proxy.js @@ -0,0 +1,27 @@ +const {proxy} = require("pinski/plugins") +const {getUser} = require("../utils/getuser") +const constants = require("../utils/constants.js") + +// list of paths relative to the backend this route is authorized to serve +const authorizedPaths = [`/api/v1/captions/(${constants.regex.video_id})`] + +// headers relayed as-is from the proxied backend to the client +const proxiedHeaders = ["content-type", "date", "last-modified", "expires", "cache-control", "accept-ranges", "content-range", "origin", "etag", "content-length", "transfer-encoding"] + +module.exports = [ + { + route: `/proxy`, methods: ["GET"], code: async ({req, fill, url}) => { + const instanceOrigin = getUser(req).getSettingsOrDefaults().instance + const remotePath = url.searchParams.get("url") + const fetchURL = new URL(remotePath, instanceOrigin) + if (!fetchURL.toString().startsWith(instanceOrigin) || !authorizedPaths.some(element => fetchURL.pathname.match(new RegExp(`^${element}$`)))) { + return { + statusCode: 401, + content: "CloudTube: Unauthorized", + contentType: "text/plain" + } + } + return proxy(fetchURL, {}, (h) => Object.keys(h).filter(key => proxiedHeaders.includes(key)).reduce((res, key) => (res[key] = h[key], res), {})) + } + } +] diff --git a/api/video.js b/api/video.js index 1e0445b..d60f52d 100644 --- a/api/video.js +++ b/api/video.js @@ -174,6 +174,11 @@ module.exports = [ // rewrite description video.descriptionHtml = converters.rewriteVideoDescription(video.descriptionHtml, id) + // rewrite captions urls so they are served on the same domain via the /proxy route + for (const caption of video.captions) { + caption.url = `/proxy?${new URLSearchParams({"url": caption.url})}` + } + return render(200, "pug/video.pug", { url, video, formats, subscribed, instanceOrigin, mediaFragment, autoplay, continuous, sessionWatched, sessionWatchedNext