D api/captions.js => api/captions.js +0 -27
@@ 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
- }
- })
- })
- }
- }
-]
A api/proxy.js => api/proxy.js +27 -0
@@ 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), {}))
+ }
+ }
+]
M api/video.js => api/video.js +5 -0
@@ 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