diff --git a/anonstream/__init__.py b/anonstream/__init__.py index 69e85db..09bb394 100644 --- a/anonstream/__init__.py +++ b/anonstream/__init__.py @@ -12,7 +12,7 @@ from anonstream.quart import Quart compress = Compress() def create_app(toml_config): - app = Quart('anonstream') + app = Quart('anonstream', static_folder=None) app.jinja_options['trim_blocks'] = True app.jinja_options['lstrip_blocks'] = True diff --git a/anonstream/routes/core.py b/anonstream/routes/core.py index ad15dbe..00ef969 100644 --- a/anonstream/routes/core.py +++ b/anonstream/routes/core.py @@ -3,16 +3,18 @@ import math -from quart import current_app, request, render_template, abort, make_response, redirect, url_for, abort +from quart import current_app, request, render_template, abort, make_response, redirect, url_for, abort, send_from_directory from werkzeug.exceptions import TooManyRequests from anonstream.captcha import get_captcha_image from anonstream.segments import segments, StopSendingSegments from anonstream.stream import is_online, get_stream_uptime from anonstream.user import watching, create_eyes, renew_eyes, EyesException, RatelimitedEyes -from anonstream.routes.wrappers import with_user_from, auth_required +from anonstream.routes.wrappers import with_user_from, auth_required, clean_cache_headers from anonstream.utils.security import generate_csp +STATIC_DIRECTORY = current_app.root_path / 'static' + @current_app.route('/') @with_user_from(request) async def home(user): @@ -64,3 +66,9 @@ async def captcha(user): return abort(410) else: return image, {'Content-Type': 'image/jpeg'} + +@current_app.route('/static/') +@with_user_from(request) +@clean_cache_headers +async def static(user, filename): + return await send_from_directory(STATIC_DIRECTORY, filename) diff --git a/anonstream/routes/wrappers.py b/anonstream/routes/wrappers.py index 9bc0da9..4fd4ff6 100644 --- a/anonstream/routes/wrappers.py +++ b/anonstream/routes/wrappers.py @@ -127,3 +127,28 @@ async def render_template_with_etag(template, deferred_kwargs, **kwargs): **kwargs, ) return rendered_template, {'ETag': etag} + +def clean_cache_headers(f): + @wraps(f) + async def wrapper(*args, **kwargs): + response = await f(*args, **kwargs) + + # Remove Last-Modified + try: + response.headers.pop('Last-Modified') + except KeyError: + pass + + # Obfuscate ETag + try: + original_etag = response.headers['ETag'] + except KeyError: + pass + else: + parts = CONFIG['SECRET_KEY'] + b'etag\0' + original_etag.encode() + tag = hashlib.sha256(parts).hexdigest() + response.headers['ETag'] = f'"{tag}"' + + return response + + return wrapper