Coverage for sm/views_avatars.py: 75%

36 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-24 19:06 +0000

1import logging 

2import re 

3 

4import requests 

5from django.http import HttpResponse, HttpResponseRedirect, HttpResponseBadRequest 

6from django.core.cache import cache 

7 

8logger = logging.getLogger(__name__) 

9 

10# libravatar only serves md5 email hashes (32 lowercase hex chars). 

11_HASH_RE = re.compile(r"^[0-9a-f]{32}$") 

12_MAX_SIZE = 512 

13 

14 

15def _validate_size(raw): 

16 try: 

17 size = int(raw) 

18 except (TypeError, ValueError): 

19 size = 80 

20 return min(max(size, 1), _MAX_SIZE) 

21 

22 

23def avatar_proxy(request, email_hash): 

24 """ 

25 Proxies libravatar images to allow for aggressive browser caching. 

26 

27 The ``email_hash`` must be a valid 32-char md5 hex digest and ``s`` is 

28 validated to a bounded integer so the endpoint can't be used to probe 

29 arbitrary libravatar URLs. 

30 """ 

31 if not _HASH_RE.match(email_hash.lower()): 

32 return HttpResponseBadRequest("invalid email hash") 

33 

34 size = _validate_size(request.GET.get("s", 80)) 

35 cache_key = f"avatar_{email_hash}_{size}" 

36 cached_avatar = cache.get(cache_key) 

37 

38 if cached_avatar: 

39 response = HttpResponse( 

40 cached_avatar["content"], content_type=cached_avatar["content_type"] 

41 ) 

42 response["Cache-Control"] = "public, max-age=604800, immutable" 

43 return response 

44 

45 url = f"https://cdn.libravatar.org/avatar/{email_hash}?s={size}&d=mm" 

46 

47 try: 

48 res = requests.get(url, timeout=5) 

49 if res.status_code == 200: 

50 content_type = res.headers.get("Content-Type", "image/png") 

51 cache.set( 

52 cache_key, {"content": res.content, "content_type": content_type}, 86400 

53 ) 

54 

55 response = HttpResponse(res.content, content_type=content_type) 

56 response["Cache-Control"] = "public, max-age=604800, immutable" 

57 return response 

58 except Exception: 

59 logger.exception("Avatar proxy: failed to fetch %s", email_hash) 

60 

61 return HttpResponseRedirect(url)