fix missing headers in video get requests
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
from flask import Flask, request, Response, send_from_directory, jsonify
|
from flask import Flask, request, Response, send_from_directory, jsonify
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import requests
|
import requests
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
@@ -9,6 +10,44 @@ import yt_dlp
|
|||||||
import io
|
import io
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
|
|
||||||
|
# Stream params that have dedicated meaning and must never be treated as headers.
|
||||||
|
STREAM_RESERVED_PARAMS = {'url'}
|
||||||
|
# Headers that affect the transport layer rather than the resource itself; allowing
|
||||||
|
# these to be forwarded could enable request smuggling or vhost-routing abuse.
|
||||||
|
STREAM_DISALLOWED_HEADER_NAMES = {'host', 'content-length', 'transfer-encoding', 'connection', 'expect'}
|
||||||
|
# RFC 7230 token charset for header field-names.
|
||||||
|
HEADER_NAME_RE = re.compile(r"^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$")
|
||||||
|
# Reject control characters (CR/LF/NUL etc.) that could be used for header injection.
|
||||||
|
HEADER_VALUE_BAD_CHARS_RE = re.compile(r'[\x00-\x08\x0a-\x1f\x7f]')
|
||||||
|
MAX_HEADER_VALUE_LENGTH = 4096
|
||||||
|
|
||||||
|
|
||||||
|
def collect_passthrough_headers(source):
|
||||||
|
"""Treat any request param other than the reserved ones as an HTTP header to
|
||||||
|
forward to yt-dlp/upstream. Validates names and values to prevent header
|
||||||
|
injection (CRLF splitting) and disallows transport-level headers."""
|
||||||
|
headers = {}
|
||||||
|
if not source:
|
||||||
|
return headers
|
||||||
|
for key in source:
|
||||||
|
if key.lower() in STREAM_RESERVED_PARAMS:
|
||||||
|
continue
|
||||||
|
if key.lower() in STREAM_DISALLOWED_HEADER_NAMES:
|
||||||
|
continue
|
||||||
|
if not HEADER_NAME_RE.match(key):
|
||||||
|
continue
|
||||||
|
value = source.get(key)
|
||||||
|
if value is None:
|
||||||
|
continue
|
||||||
|
value = str(value)
|
||||||
|
if not value or len(value) > MAX_HEADER_VALUE_LENGTH:
|
||||||
|
continue
|
||||||
|
if HEADER_VALUE_BAD_CHARS_RE.search(value):
|
||||||
|
continue
|
||||||
|
header_name = 'Referer' if key.lower() == 'referer' else key
|
||||||
|
headers[header_name] = value
|
||||||
|
return headers
|
||||||
|
|
||||||
# Serve frontend static files under `/static` to avoid colliding with API routes
|
# Serve frontend static files under `/static` to avoid colliding with API routes
|
||||||
app = Flask(__name__, static_folder='../frontend', static_url_path='/static')
|
app = Flask(__name__, static_folder='../frontend', static_url_path='/static')
|
||||||
app.url_map.strict_slashes = False
|
app.url_map.strict_slashes = False
|
||||||
@@ -503,14 +542,11 @@ def stream_video():
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
referer_url = ""
|
passthrough_source = request.json if request.method == 'POST' else request.args
|
||||||
if request.method == 'POST':
|
passthrough_headers = collect_passthrough_headers(passthrough_source)
|
||||||
referer_url = request.json.get('referer')
|
dbg(f"passthrough_headers={list(passthrough_headers.keys())}")
|
||||||
else:
|
ydl_opts['http_headers'].update(passthrough_headers)
|
||||||
referer_url = request.args.get('referer')
|
|
||||||
if len(referer_url) > 0:
|
|
||||||
ydl_opts['http_headers']["Referer"] = referer_url
|
|
||||||
|
|
||||||
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
||||||
# Extract the info
|
# Extract the info
|
||||||
info = ydl.extract_info(video_url, download=False)
|
info = ydl.extract_info(video_url, download=False)
|
||||||
|
|||||||
@@ -100,7 +100,8 @@ App.feed = App.feed || {};
|
|||||||
const resolved = App.videos.resolveStreamSource(videoData);
|
const resolved = App.videos.resolveStreamSource(videoData);
|
||||||
if (!resolved.url) return;
|
if (!resolved.url) return;
|
||||||
const refererParam = resolved.referer ? `&referer=${encodeURIComponent(resolved.referer)}` : '';
|
const refererParam = resolved.referer ? `&referer=${encodeURIComponent(resolved.referer)}` : '';
|
||||||
const streamUrl = `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}`;
|
const userAgentParam = resolved.userAgent ? `&User-Agent=${encodeURIComponent(resolved.userAgent)}` : '';
|
||||||
|
const streamUrl = `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}${userAgentParam}`;
|
||||||
const isHls = /\.m3u8($|\?)/i.test(resolved.url);
|
const isHls = /\.m3u8($|\?)/i.test(resolved.url);
|
||||||
const canUseHls = !!(window.Hls && window.Hls.isSupported());
|
const canUseHls = !!(window.Hls && window.Hls.isSupported());
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ App.player = App.player || {};
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const refererParam = resolved.referer ? `&referer=${encodeURIComponent(resolved.referer)}` : '';
|
const refererParam = resolved.referer ? `&referer=${encodeURIComponent(resolved.referer)}` : '';
|
||||||
const streamUrl = `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}`;
|
const userAgentParam = resolved.userAgent ? `&User-Agent=${encodeURIComponent(resolved.userAgent)}` : '';
|
||||||
|
const streamUrl = `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}${userAgentParam}`;
|
||||||
let isHls = /\.m3u8($|\?)/i.test(resolved.url);
|
let isHls = /\.m3u8($|\?)/i.test(resolved.url);
|
||||||
let isDirectMedia = /\.(mp4|m4v|m4s|webm|ts|mov)($|\?)/i.test(resolved.url);
|
let isDirectMedia = /\.(mp4|m4v|m4s|webm|ts|mov)($|\?)/i.test(resolved.url);
|
||||||
|
|
||||||
|
|||||||
@@ -573,6 +573,7 @@ App.videos = App.videos || {};
|
|||||||
const applyPreferredQuality = !options || options.applyPreferredQuality !== false;
|
const applyPreferredQuality = !options || options.applyPreferredQuality !== false;
|
||||||
let sourceUrl = '';
|
let sourceUrl = '';
|
||||||
let referer = '';
|
let referer = '';
|
||||||
|
let userAgent = '';
|
||||||
if (typeof videoOrUrl === 'string') {
|
if (typeof videoOrUrl === 'string') {
|
||||||
sourceUrl = videoOrUrl;
|
sourceUrl = videoOrUrl;
|
||||||
} else if (videoOrUrl && typeof videoOrUrl === 'object') {
|
} else if (videoOrUrl && typeof videoOrUrl === 'object') {
|
||||||
@@ -589,10 +590,16 @@ App.videos = App.videos || {};
|
|||||||
if (best.http_headers && (best.http_headers.Referer || best.http_headers.referer)) {
|
if (best.http_headers && (best.http_headers.Referer || best.http_headers.referer)) {
|
||||||
referer = best.http_headers.Referer || best.http_headers.referer;
|
referer = best.http_headers.Referer || best.http_headers.referer;
|
||||||
}
|
}
|
||||||
|
if (best.http_headers && (best.http_headers['User-Agent'] || best.http_headers['user-agent'])) {
|
||||||
|
userAgent = best.http_headers['User-Agent'] || best.http_headers['user-agent'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!referer && meta.http_headers && (meta.http_headers.Referer || meta.http_headers.referer)) {
|
if (!referer && meta.http_headers && (meta.http_headers.Referer || meta.http_headers.referer)) {
|
||||||
referer = meta.http_headers.Referer || meta.http_headers.referer;
|
referer = meta.http_headers.Referer || meta.http_headers.referer;
|
||||||
}
|
}
|
||||||
|
if (!userAgent && meta.http_headers && (meta.http_headers['User-Agent'] || meta.http_headers['user-agent'])) {
|
||||||
|
userAgent = meta.http_headers['User-Agent'] || meta.http_headers['user-agent'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!referer && sourceUrl) {
|
if (!referer && sourceUrl) {
|
||||||
try {
|
try {
|
||||||
@@ -601,15 +608,17 @@ App.videos = App.videos || {};
|
|||||||
referer = '';
|
referer = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { url: sourceUrl, referer };
|
return { url: sourceUrl, referer, userAgent };
|
||||||
};
|
};
|
||||||
|
|
||||||
// Builds a proxied stream URL with an optional referer parameter.
|
// Builds a proxied stream URL. Extra params other than `url` are forwarded
|
||||||
|
// by the backend as request headers, so use real header names here.
|
||||||
App.videos.buildStreamUrl = function(videoOrUrl, options) {
|
App.videos.buildStreamUrl = function(videoOrUrl, options) {
|
||||||
const resolved = App.videos.resolveStreamSource(videoOrUrl, options);
|
const resolved = App.videos.resolveStreamSource(videoOrUrl, options);
|
||||||
if (!resolved.url) return '';
|
if (!resolved.url) return '';
|
||||||
const refererParam = resolved.referer ? `&referer=${encodeURIComponent(resolved.referer)}` : '';
|
const refererParam = resolved.referer ? `&referer=${encodeURIComponent(resolved.referer)}` : '';
|
||||||
return `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}`;
|
const userAgentParam = resolved.userAgent ? `&User-Agent=${encodeURIComponent(resolved.userAgent)}` : '';
|
||||||
|
return `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}${userAgentParam}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
App.videos.downloadVideo = function(video) {
|
App.videos.downloadVideo = function(video) {
|
||||||
|
|||||||
Reference in New Issue
Block a user