53 lines
1.7 KiB
Python
53 lines
1.7 KiB
Python
"""Storage-backed private media responses; callers MUST authorize the resource."""
|
|
|
|
import mimetypes
|
|
from pathlib import Path
|
|
from urllib.parse import quote
|
|
|
|
from django.conf import settings
|
|
from django.http import FileResponse, Http404, HttpResponse
|
|
from django.utils.http import content_disposition_header
|
|
|
|
|
|
def protected_file_response(
|
|
attachment,
|
|
*,
|
|
filename,
|
|
storage_directory,
|
|
internal_prefix,
|
|
use_x_accel=False,
|
|
as_attachment=True,
|
|
):
|
|
local_path = None
|
|
if use_x_accel:
|
|
try:
|
|
local_path = attachment.path
|
|
except NotImplementedError:
|
|
pass
|
|
if local_path is not None:
|
|
try:
|
|
root = (Path(settings.MEDIA_ROOT) / storage_directory).resolve(strict=True)
|
|
path = Path(local_path).resolve(strict=True)
|
|
relative = path.relative_to(root)
|
|
if not path.is_file():
|
|
raise ValueError("not a file")
|
|
except (OSError, ValueError) as exc:
|
|
raise Http404("Файл недоступен") from exc
|
|
response = HttpResponse(
|
|
content_type=mimetypes.guess_type(filename)[0] or "application/octet-stream"
|
|
)
|
|
response["X-Accel-Redirect"] = internal_prefix + quote(
|
|
relative.as_posix(), safe="/"
|
|
)
|
|
response["Content-Disposition"] = content_disposition_header(
|
|
as_attachment, filename
|
|
)
|
|
else:
|
|
response = FileResponse(
|
|
attachment.open("rb"), as_attachment=as_attachment, filename=filename
|
|
)
|
|
response["Cache-Control"] = "private, no-store"
|
|
response["X-Content-Type-Options"] = "nosniff"
|
|
response["Content-Security-Policy"] = "sandbox"
|
|
return response
|