From 296780d5cc422b24ddd3167091465dc4e68d8abf Mon Sep 17 00:00:00 2001 From: Corry Haines Date: Wed, 28 Dec 2022 10:39:40 -0800 Subject: [PATCH] Use cache-busting URLs for proxied files (#294) Migrates (in a backwards-compatible way) from `/proxy/identity_image/271/` to `/proxy/identity_image/271/f5d8e72f2b/`. dently). --- activities/models/emoji.py | 4 +++- activities/models/post_attachment.py | 8 ++++++-- core/uris.py | 14 +++++++++++++- docker/nginx.conf | 2 -- mediaproxy/views.py | 6 ++++-- takahe/urls.py | 16 ++++++++-------- users/models/identity.py | 8 ++++++-- 7 files changed, 40 insertions(+), 18 deletions(-) diff --git a/activities/models/emoji.py b/activities/models/emoji.py index 32d586b..373577b 100644 --- a/activities/models/emoji.py +++ b/activities/models/emoji.py @@ -169,7 +169,9 @@ class Emoji(StatorModel): if self.file: return AutoAbsoluteUrl(self.file.url) elif self.remote_url: - return AutoAbsoluteUrl(f"/proxy/emoji/{self.pk}/") + return AutoAbsoluteUrl( + f"/proxy/emoji/{self.pk}/", hash_tail_input=self.remote_url + ) return StaticAbsoluteUrl("img/blank-emoji-128.png") def as_html(self): diff --git a/activities/models/post_attachment.py b/activities/models/post_attachment.py index 2dfbcb9..51860b5 100644 --- a/activities/models/post_attachment.py +++ b/activities/models/post_attachment.py @@ -81,13 +81,17 @@ class PostAttachment(StatorModel): elif self.file: return RelativeAbsoluteUrl(self.file.url) else: - return AutoAbsoluteUrl(f"/proxy/post_attachment/{self.pk}/") + return AutoAbsoluteUrl( + f"/proxy/post_attachment/{self.pk}/", hash_tail_input=self.remote_url + ) def full_url(self): if self.file: return RelativeAbsoluteUrl(self.file.url) else: - return AutoAbsoluteUrl(f"/proxy/post_attachment/{self.pk}/") + return AutoAbsoluteUrl( + f"/proxy/post_attachment/{self.pk}/", hash_tail_input=self.remote_url + ) ### ActivityPub ### diff --git a/core/uris.py b/core/uris.py index b55514f..8d9d891 100644 --- a/core/uris.py +++ b/core/uris.py @@ -1,3 +1,4 @@ +import hashlib import sys from urllib.parse import urljoin @@ -27,8 +28,19 @@ class AutoAbsoluteUrl(RelativeAbsoluteUrl): or a passed identity's URI domain. """ - def __init__(self, relative: str, identity=None): + def __init__( + 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: diff --git a/docker/nginx.conf b/docker/nginx.conf index d06adde..3be6ad5 100644 --- a/docker/nginx.conf +++ b/docker/nginx.conf @@ -104,8 +104,6 @@ http { proxy_cache_valid 301 307 12h; proxy_cache_valid 500 502 503 504 0s; proxy_cache_valid any 72h; - proxy_hide_header Cache-Control; - add_header Cache-Control "public, max-age=3600"; add_header X-Cache $upstream_cache_status; } diff --git a/mediaproxy/views.py b/mediaproxy/views.py index 62a5b85..f2b4cb0 100644 --- a/mediaproxy/views.py +++ b/mediaproxy/views.py @@ -31,7 +31,7 @@ class BaseProxyView(View): headers={ "X-Accel-Redirect": "/__takahe_accel__/", "X-Takahe-RealUri": remote_url, - "Cache-Control": "public, max-age=3600", + "Cache-Control": "public", }, ) else: @@ -52,7 +52,9 @@ class BaseProxyView(View): "Content-Type": remote_response.headers.get( "Content-Type", "application/octet-stream" ), - "Cache-Control": "public, max-age=3600", + "Cache-Control": remote_response.headers.get( + "Cache-Control", "public, max-age=3600" + ), }, ) diff --git a/takahe/urls.py b/takahe/urls.py index b12f0a5..6a471d8 100644 --- a/takahe/urls.py +++ b/takahe/urls.py @@ -211,23 +211,23 @@ urlpatterns = [ path("debug/500/", debug.ServerError.as_view()), path("debug/oauth_authorize/", debug.OauthAuthorize.as_view()), # Media/image proxy - path( - "proxy/identity_icon//", + re_path( + "^proxy/identity_icon/(?P[^/]+)/((?P[^/]+)/)?$", mediaproxy.IdentityIconCacheView.as_view(), name="proxy_identity_icon", ), - path( - "proxy/identity_image//", + re_path( + "^proxy/identity_image/(?P[^/]+)/((?P[^/]+)/)?$", mediaproxy.IdentityImageCacheView.as_view(), name="proxy_identity_image", ), - path( - "proxy/post_attachment//", + re_path( + "^proxy/post_attachment/(?P[^/]+)/((?P[^/]+)/)?$", mediaproxy.PostAttachmentCacheView.as_view(), name="proxy_post_attachment", ), - path( - "proxy/emoji//", + re_path( + "^proxy/emoji/(?P[^/]+)/((?P[^/]+)/)?$", mediaproxy.EmojiCacheView.as_view(), name="proxy_emoji", ), diff --git a/users/models/identity.py b/users/models/identity.py index 545872a..f75a1e3 100644 --- a/users/models/identity.py +++ b/users/models/identity.py @@ -268,7 +268,9 @@ class Identity(StatorModel): if self.icon: return RelativeAbsoluteUrl(self.icon.url) elif self.icon_uri: - return AutoAbsoluteUrl(f"/proxy/identity_icon/{self.pk}/") + return AutoAbsoluteUrl( + f"/proxy/identity_icon/{self.pk}/", hash_tail_input=self.icon_uri + ) else: return StaticAbsoluteUrl("img/unknown-icon-128.png") @@ -279,7 +281,9 @@ class Identity(StatorModel): if self.image: return AutoAbsoluteUrl(self.image.url) elif self.image_uri: - return AutoAbsoluteUrl(f"/proxy/identity_image/{self.pk}/") + return AutoAbsoluteUrl( + f"/proxy/identity_image/{self.pk}/", hash_tail_input=self.image_uri + ) return None @property