serve.py 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. from django.core.exceptions import ImproperlyConfigured, PermissionDenied
  2. from django.http import FileResponse, HttpResponse
  3. from django.shortcuts import get_object_or_404, redirect
  4. from django.urls import reverse
  5. from django.utils.decorators import classonlymethod, method_decorator
  6. from django.views.decorators.cache import cache_control
  7. from django.views.generic import View
  8. from wagtail.images import get_image_model
  9. from wagtail.images.exceptions import InvalidFilterSpecError
  10. from wagtail.images.models import SourceImageIOError
  11. from wagtail.images.utils import generate_signature, verify_signature
  12. from wagtail.utils.sendfile import sendfile
  13. def generate_image_url(image, filter_spec, viewname="wagtailimages_serve", key=None):
  14. signature = generate_signature(image.id, filter_spec, key)
  15. url = reverse(viewname, args=(signature, image.id, filter_spec))
  16. url += image.file.name[len("original_images/") :]
  17. return url
  18. class ServeView(View):
  19. model = get_image_model()
  20. action = "serve"
  21. key = None
  22. @classonlymethod
  23. def as_view(cls, **initkwargs):
  24. if "action" in initkwargs:
  25. if initkwargs["action"] not in ["serve", "redirect"]:
  26. raise ImproperlyConfigured(
  27. "ServeView action must be either 'serve' or 'redirect'"
  28. )
  29. return super().as_view(**initkwargs)
  30. @method_decorator(cache_control(max_age=3600, public=True))
  31. def get(self, request, signature, image_id, filter_spec, filename=None):
  32. if not verify_signature(
  33. signature.encode(), image_id, filter_spec, key=self.key
  34. ):
  35. raise PermissionDenied
  36. image = get_object_or_404(self.model, id=image_id)
  37. # Get/generate the rendition
  38. try:
  39. rendition = image.get_rendition(filter_spec)
  40. except SourceImageIOError:
  41. return HttpResponse(
  42. "Source image file not found", content_type="text/plain", status=410
  43. )
  44. except InvalidFilterSpecError:
  45. return HttpResponse(
  46. "Invalid filter spec: " + filter_spec,
  47. content_type="text/plain",
  48. status=400,
  49. )
  50. return getattr(self, self.action)(rendition)
  51. def serve(self, rendition):
  52. with rendition.get_willow_image() as willow_image:
  53. mime_type = willow_image.mime_type
  54. # Serve the file
  55. rendition.file.open("rb")
  56. response = FileResponse(rendition.file, content_type=mime_type)
  57. # Add a CSP header to prevent inline execution
  58. response["Content-Security-Policy"] = "default-src 'none'"
  59. # Prevent browsers from auto-detecting the content-type of a document
  60. response["X-Content-Type-Options"] = "nosniff"
  61. return response
  62. def redirect(self, rendition):
  63. # Redirect to the file's public location
  64. return redirect(rendition.url)
  65. serve = ServeView.as_view()
  66. class SendFileView(ServeView):
  67. backend = None
  68. def serve(self, rendition):
  69. response = sendfile(self.request, rendition.file.path, backend=self.backend)
  70. # Add a CSP header to prevent inline execution
  71. response["Content-Security-Policy"] = "default-src 'none'"
  72. # Prevent browsers from auto-detecting the content-type of a document
  73. response["X-Content-Type-Options"] = "nosniff"
  74. return response