]> git.gir.st - subscriptionfeed.git/blob - app/proxy/__init__.py
allow limiting proxy to authenticated users
[subscriptionfeed.git] / app / proxy / __init__.py
1 import re
2 import requests, requests_cache
3 from flask import Flask, Blueprint, request, Response, g
4 from flask_login import current_user
5 from werkzeug.exceptions import Forbidden
6
7 from ..common.common import cf
8
9 frontend = Blueprint('proxy', __name__)
10
11 def app():
12 app = Flask(__name__)
13 app.register_blueprint(frontend)
14 return app
15
16 # https://github.com/psf/requests/issues/5612
17 def silence_errors(generator):
18 try:
19 yield from generator
20 except requests.exceptions.RequestException:
21 return None
22
23 @frontend.route("/videoplayback/<path:path>")
24 @frontend.route("/videoplayback")
25 def videoplayback(path=None):
26 if path is not None: # seg.ts URL
27 args = path.split("/")[0:-1]
28 host = dict(zip(args[::2], args[1::2])).get('hls_chunk_host')
29 path = "/" + path.replace(';','%3B').replace('=','%3D')
30 else:
31 fvip = request.args.get("fvip", "3")
32 mn = request.args.get("mn", "").split(",")[-1]
33 host = f"r{fvip}---{mn}.googlevideo.com"
34 path = ""
35
36 REQUEST_HEADERS_WHITELIST = {
37 "accept",
38 "accept-encoding",
39 "cache-control",
40 "range",
41 }
42 RESPONSE_HEADERS_WHITELIST = {
43 "accept-ranges",
44 "cache-control",
45 "content-length",
46 "content-range",
47 "content-type",
48 "expires",
49 "x-content-type-options",
50 }
51
52 req_headers = {
53 k:v for k,v in request.headers
54 if k.lower() in REQUEST_HEADERS_WHITELIST
55 }
56 with requests_cache.disabled():
57 try:
58 r = requests.get(f"https://{host}/videoplayback{path}",
59 request.args.to_dict(),
60 headers=req_headers,
61 stream=True)
62 except requests.exceptions.ConnectionError:
63 return f"unable to connect to {host}", 502
64
65 resp_headers = {
66 k:v for k,v in r.headers.items()
67 if k.lower() in RESPONSE_HEADERS_WHITELIST
68 }
69 response = Response(
70 silence_errors(r.iter_content(chunk_size=8192)),
71 status=r.status_code, headers=resp_headers)
72 response.call_on_close(lambda: r.close())
73 response.headers['Access-Control-Allow-Origin'] = cors_origin()
74 return response
75
76 @frontend.route("/api/manifest/hls_playlist/<path:path>")
77 @frontend.route("/api/manifest/hls_variant/<path:path>")
78 def hls_manifest(path):
79 path_fixed = request.path.replace(';','%3B').replace('=','%3D')
80 with requests_cache.disabled():
81 r = requests.get(f"https://manifest.googlevideo.com{path_fixed}")
82 if not r.ok:
83 return "", r.status_code
84
85 rv = re.sub(r"^https://[a-z0-9-]+\.googlevideo\.com", "", r.text, flags=re.M)
86 if "/api/manifest/hls_variant" in request.path:
87 rv = rv + "\n#EXT-X-ENDLIST"
88 return rv, {
89 'Content-Type': 'application/x-mpegURL',
90 'Access-Control-Allow-Origin': cors_origin()
91 }
92
93 def cors_origin():
94 return f"{request.environ.get('wsgi.url_scheme')}://{request.host}"
95
96 def proxy_allowed():
97 """ the proxy can be restricted to logged-in users by a config flag """
98 require_auth = cf.getboolean('proxy', 'require_auth', fallback=False)
99 is_authd = not current_user.is_anonymous
100 return is_authd or not require_auth
101
102 @frontend.before_request
103 def check_auth():
104 if not proxy_allowed():
105 raise Forbidden("limited to authenticated users")
106
107 @frontend.before_app_request
108 def propagate_auth_requirement():
109 g.proxy_on = proxy_allowed()
110
111
112 if __name__ == '__main__':
113 app().run(debug=True)
Imprint / Impressum