Improve avatar route handler

- 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.
This commit is contained in:
Jonas Schäfer
2020-03-07 13:12:30 +01:00
parent 56b0b7b669
commit 76c38030a8

View File

@@ -1,10 +1,13 @@
import base64 import base64
import binascii import binascii
from datetime import datetime, timedelta
import quart.flask_patch import quart.flask_patch
from quart import ( 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 from . import colour
@@ -66,20 +69,33 @@ async def avatar(from_, code):
bin_hash = binascii.a2b_hex(info["sha1"]) bin_hash = binascii.a2b_hex(info["sha1"])
new_etag = base64.urlsafe_b64encode(bin_hash).decode("ascii").rstrip("=") new_etag = base64.urlsafe_b64encode(bin_hash).decode("ascii").rstrip("=")
headers = { cache_ttl = timedelta(seconds=current_app.config.get(
"ETag": new_etag, "AVATAR_CACHE_TTL",
} 300,
))
if etag is not None: response = Response(None, mimetype=info["type"])
if new_etag == etag: response.headers["etag"] = new_etag
return Response( # XXX: It seems to me that quart expects localtime(?!) in this field...
[], response.expires = datetime.now() + cache_ttl
304, response.headers["Content-Security-Policy"] = \
content_type=info["type"], headers=headers "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"]) 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 @app.context_processor