From 6137149f5563468916f65b18a7fd58d4c4e618a0 Mon Sep 17 00:00:00 2001 From: Andrew Godwin Date: Wed, 28 Dec 2022 11:57:54 -0700 Subject: [PATCH] Improve cache busting proxy URLs with file exts Fixes #287. --- activities/models/emoji.py | 12 ++++++++--- activities/models/post_attachment.py | 12 ++++++----- core/uris.py | 32 ++++++++++++++++++++++------ takahe/urls.py | 8 +++---- users/models/identity.py | 17 ++++++++++----- 5 files changed, 57 insertions(+), 24 deletions(-) diff --git a/activities/models/emoji.py b/activities/models/emoji.py index 373577b..f657337 100644 --- a/activities/models/emoji.py +++ b/activities/models/emoji.py @@ -17,7 +17,12 @@ from core.html import strip_html from core.ld import format_ld_date from core.models import Config from core.uploads import upload_emoji_namer -from core.uris import AutoAbsoluteUrl, RelativeAbsoluteUrl, StaticAbsoluteUrl +from core.uris import ( + AutoAbsoluteUrl, + ProxyAbsoluteUrl, + RelativeAbsoluteUrl, + StaticAbsoluteUrl, +) from stator.models import State, StateField, StateGraph, StatorModel from users.models import Domain @@ -169,8 +174,9 @@ class Emoji(StatorModel): if self.file: return AutoAbsoluteUrl(self.file.url) elif self.remote_url: - return AutoAbsoluteUrl( - f"/proxy/emoji/{self.pk}/", hash_tail_input=self.remote_url + return ProxyAbsoluteUrl( + f"/proxy/emoji/{self.pk}/", + remote_url=self.remote_url, ) return StaticAbsoluteUrl("img/blank-emoji-128.png") diff --git a/activities/models/post_attachment.py b/activities/models/post_attachment.py index 51860b5..2a140fd 100644 --- a/activities/models/post_attachment.py +++ b/activities/models/post_attachment.py @@ -3,7 +3,7 @@ from functools import partial from django.db import models from core.uploads import upload_namer -from core.uris import AutoAbsoluteUrl, RelativeAbsoluteUrl +from core.uris import ProxyAbsoluteUrl, RelativeAbsoluteUrl from stator.models import State, StateField, StateGraph, StatorModel @@ -81,16 +81,18 @@ class PostAttachment(StatorModel): elif self.file: return RelativeAbsoluteUrl(self.file.url) else: - return AutoAbsoluteUrl( - f"/proxy/post_attachment/{self.pk}/", hash_tail_input=self.remote_url + return ProxyAbsoluteUrl( + f"/proxy/post_attachment/{self.pk}/", + remote_url=self.remote_url, ) def full_url(self): if self.file: return RelativeAbsoluteUrl(self.file.url) else: - return AutoAbsoluteUrl( - f"/proxy/post_attachment/{self.pk}/", hash_tail_input=self.remote_url + return ProxyAbsoluteUrl( + f"/proxy/post_attachment/{self.pk}/", + remote_url=self.remote_url, ) ### ActivityPub ### diff --git a/core/uris.py b/core/uris.py index 8d9d891..e686108 100644 --- a/core/uris.py +++ b/core/uris.py @@ -32,15 +32,8 @@ class AutoAbsoluteUrl(RelativeAbsoluteUrl): self, relative: str, identity=None, - hash_tail_input: str | None = None, - hash_tail_length: int = 10, ): self.relative = relative - if hash_tail_input: - # When provided, attach a hash of the input (typically the proxied URL) - # SHA1 chosen as it generally has the best performance in modern python, and security is not a concern - # Hash truncation is generally fine, as in the typical use case the hash is scoped to the identity PK - self.relative += f"{hashlib.sha1(hash_tail_input.encode('ascii')).hexdigest()[:hash_tail_length]}/" if identity: absolute_prefix = f"https://{identity.domain.uri_domain}/" else: @@ -48,6 +41,31 @@ class AutoAbsoluteUrl(RelativeAbsoluteUrl): self.absolute = urljoin(absolute_prefix, self.relative) +class ProxyAbsoluteUrl(AutoAbsoluteUrl): + """ + AutoAbsoluteUrl variant for proxy paths, that also attaches a remote URI hash + plus extension to the end if it can. + """ + + def __init__( + self, + relative: str, + identity=None, + remote_url: str | None = None, + ): + if remote_url: + # See if there is a file extension we can grab + extension = "bin" + remote_filename = remote_url.split("/")[-1] + if "." in remote_filename: + extension = remote_filename.split(".")[-1] + # When provided, attach a hash of the remote URL + # SHA1 chosen as it generally has the best performance in modern python, and security is not a concern + # Hash truncation is generally fine, as in the typical use case the hash is scoped to the identity PK. + relative += f"{hashlib.sha1(remote_url.encode('ascii')).hexdigest()[:10]}.{extension}" + super().__init__(relative, identity) + + class StaticAbsoluteUrl(RelativeAbsoluteUrl): """ Creates static URLs given only the static-relative path diff --git a/takahe/urls.py b/takahe/urls.py index 6a471d8..1421d44 100644 --- a/takahe/urls.py +++ b/takahe/urls.py @@ -212,22 +212,22 @@ urlpatterns = [ path("debug/oauth_authorize/", debug.OauthAuthorize.as_view()), # Media/image proxy re_path( - "^proxy/identity_icon/(?P[^/]+)/((?P[^/]+)/)?$", + "^proxy/identity_icon/(?P[^/]+)/((?P[^/]+))?$", mediaproxy.IdentityIconCacheView.as_view(), name="proxy_identity_icon", ), re_path( - "^proxy/identity_image/(?P[^/]+)/((?P[^/]+)/)?$", + "^proxy/identity_image/(?P[^/]+)/((?P[^/]+))?$", mediaproxy.IdentityImageCacheView.as_view(), name="proxy_identity_image", ), re_path( - "^proxy/post_attachment/(?P[^/]+)/((?P[^/]+)/)?$", + "^proxy/post_attachment/(?P[^/]+)/((?P[^/]+))?$", mediaproxy.PostAttachmentCacheView.as_view(), name="proxy_post_attachment", ), re_path( - "^proxy/emoji/(?P[^/]+)/((?P[^/]+)/)?$", + "^proxy/emoji/(?P[^/]+)/((?P[^/]+))?$", mediaproxy.EmojiCacheView.as_view(), name="proxy_emoji", ), diff --git a/users/models/identity.py b/users/models/identity.py index f75a1e3..cce2a2a 100644 --- a/users/models/identity.py +++ b/users/models/identity.py @@ -23,7 +23,12 @@ from core.ld import ( from core.models import Config from core.signatures import HttpSignature, RsaKeys from core.uploads import upload_namer -from core.uris import AutoAbsoluteUrl, RelativeAbsoluteUrl, StaticAbsoluteUrl +from core.uris import ( + AutoAbsoluteUrl, + ProxyAbsoluteUrl, + RelativeAbsoluteUrl, + StaticAbsoluteUrl, +) from stator.models import State, StateField, StateGraph, StatorModel from users.models.domain import Domain from users.models.system_actor import SystemActor @@ -268,8 +273,9 @@ class Identity(StatorModel): if self.icon: return RelativeAbsoluteUrl(self.icon.url) elif self.icon_uri: - return AutoAbsoluteUrl( - f"/proxy/identity_icon/{self.pk}/", hash_tail_input=self.icon_uri + return ProxyAbsoluteUrl( + f"/proxy/identity_icon/{self.pk}/", + remote_url=self.icon_uri, ) else: return StaticAbsoluteUrl("img/unknown-icon-128.png") @@ -281,8 +287,9 @@ class Identity(StatorModel): if self.image: return AutoAbsoluteUrl(self.image.url) elif self.image_uri: - return AutoAbsoluteUrl( - f"/proxy/identity_image/{self.pk}/", hash_tail_input=self.image_uri + return ProxyAbsoluteUrl( + f"/proxy/identity_image/{self.pk}/", + remote_url=self.image_uri, ) return None