From 76c38030a83b0108eef16cd22bf585bf0a3cdc3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20Sch=C3=A4fer?= Date: Sat, 7 Mar 2020 13:12:30 +0100 Subject: [PATCH] Improve avatar route handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix etag attaching (add_etag is actually a coroutine which hashes the data payload) - Add expires header (with now + 1800s default) so that we don’t get hit with an avatar request on each load -- also helps with page responsiveness. - Proper handling for HEAD requests. - CSP to prevent funny SVG attacks. --- snikket_web/__init__.py | 40 ++++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/snikket_web/__init__.py b/snikket_web/__init__.py index c890f43..9e2617f 100644 --- a/snikket_web/__init__.py +++ b/snikket_web/__init__.py @@ -1,10 +1,13 @@ import base64 import binascii +from datetime import datetime, timedelta + import quart.flask_patch from quart import ( - Quart, session, request, render_template, redirect, url_for, Response + Quart, session, request, render_template, redirect, url_for, Response, + current_app, ) from . import colour @@ -66,20 +69,33 @@ async def avatar(from_, code): bin_hash = binascii.a2b_hex(info["sha1"]) new_etag = base64.urlsafe_b64encode(bin_hash).decode("ascii").rstrip("=") - headers = { - "ETag": new_etag, - } + cache_ttl = timedelta(seconds=current_app.config.get( + "AVATAR_CACHE_TTL", + 300, + )) - if etag is not None: - if new_etag == etag: - return Response( - [], - 304, - content_type=info["type"], headers=headers - ) + response = Response(None, mimetype=info["type"]) + response.headers["etag"] = new_etag + # XXX: It seems to me that quart expects localtime(?!) in this field... + response.expires = datetime.now() + cache_ttl + response.headers["Content-Security-Policy"] = \ + "frame-ancestors 'none'; default-src 'none'; style-src 'unsafe-inline'" + + if etag is not None and new_etag == etag: + response.status_code = 304 + response.set_data("") + return response data = await client.get_avatar_data(address, info["sha1"]) - return Response(data, content_type=info["type"], headers=headers) + response.status_code = 200 + + if request.method == "HEAD": + response.content_length = len(data) + response.set_data("") + return response + + response.set_data(data) + return response @app.context_processor