mirror of
https://github.com/zulip/zulip.git
synced 2025-10-27 10:03:56 +00:00
Apparently, our change in b8a1050fc4 to
stop caching responses on API endpoints accidentally ended up
affecting uploaded files as well.
Fix this by explicitly setting a Cache-Control header in our Sendfile
responses, as well as changing our outer API caching code to only set
the never cache headers if the view function didn't explicitly specify
them itself.
This is not directly related to #13088, as that is a similar issue
with the S3 backend.
Thanks to Gert Burger for the report.
78 lines
3.6 KiB
Python
78 lines
3.6 KiB
Python
from django.http import HttpRequest, HttpResponse, HttpResponseForbidden, \
|
|
HttpResponseNotFound
|
|
from django.shortcuts import redirect
|
|
from django.utils.cache import patch_cache_control
|
|
from django.utils.translation import ugettext as _
|
|
|
|
from zerver.lib.response import json_success, json_error
|
|
from zerver.lib.upload import upload_message_image_from_request, get_local_file_path, \
|
|
get_signed_upload_url, check_upload_within_quota, INLINE_MIME_TYPES
|
|
from zerver.models import UserProfile, validate_attachment_request
|
|
from django.conf import settings
|
|
from sendfile import sendfile
|
|
from mimetypes import guess_type
|
|
|
|
def serve_s3(request: HttpRequest, url_path: str) -> HttpResponse:
|
|
uri = get_signed_upload_url(url_path)
|
|
return redirect(uri)
|
|
|
|
def serve_local(request: HttpRequest, path_id: str) -> HttpResponse:
|
|
local_path = get_local_file_path(path_id)
|
|
if local_path is None:
|
|
return HttpResponseNotFound('<p>File not found</p>')
|
|
|
|
# Here we determine whether a browser should treat the file like
|
|
# an attachment (and thus clicking a link to it should download)
|
|
# or like a link (and thus clicking a link to it should display it
|
|
# in a browser tab). This is controlled by the
|
|
# Content-Disposition header; `django-sendfile2` sends the
|
|
# attachment-style version of that header if and only if the
|
|
# attachment argument is passed to it. For attachments,
|
|
# django-sendfile2 sets the response['Content-disposition'] like
|
|
# this: `attachment; filename="zulip.txt"; filename*=UTF-8''zulip.txt`.
|
|
# The filename* parameter is omitted for ASCII filenames like this one.
|
|
#
|
|
# The "filename" field (used to name the file when downloaded) is
|
|
# unreliable because it doesn't have a well-defined encoding; the
|
|
# newer filename* field takes precedence, since it uses a
|
|
# consistent format (urlquoted). For more details on filename*
|
|
# and filename, see the below docs:
|
|
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
|
|
mimetype, encoding = guess_type(local_path)
|
|
attachment = mimetype not in INLINE_MIME_TYPES
|
|
|
|
response = sendfile(request, local_path, attachment=attachment,
|
|
mimetype=mimetype, encoding=encoding)
|
|
patch_cache_control(response, private=True, immutable=True)
|
|
return response
|
|
|
|
def serve_file_backend(request: HttpRequest, user_profile: UserProfile,
|
|
realm_id_str: str, filename: str) -> HttpResponse:
|
|
path_id = "%s/%s" % (realm_id_str, filename)
|
|
is_authorized = validate_attachment_request(user_profile, path_id)
|
|
|
|
if is_authorized is None:
|
|
return HttpResponseNotFound(_("<p>File not found.</p>"))
|
|
if not is_authorized:
|
|
return HttpResponseForbidden(_("<p>You are not authorized to view this file.</p>"))
|
|
if settings.LOCAL_UPLOADS_DIR is not None:
|
|
return serve_local(request, path_id)
|
|
|
|
return serve_s3(request, path_id)
|
|
|
|
def upload_file_backend(request: HttpRequest, user_profile: UserProfile) -> HttpResponse:
|
|
if len(request.FILES) == 0:
|
|
return json_error(_("You must specify a file to upload"))
|
|
if len(request.FILES) != 1:
|
|
return json_error(_("You may only upload one file at a time"))
|
|
|
|
user_file = list(request.FILES.values())[0]
|
|
file_size = user_file._get_size()
|
|
if settings.MAX_FILE_UPLOAD_SIZE * 1024 * 1024 < file_size:
|
|
return json_error(_("Uploaded file is larger than the allowed limit of %s MB") % (
|
|
settings.MAX_FILE_UPLOAD_SIZE))
|
|
check_upload_within_quota(user_profile.realm, file_size)
|
|
|
|
uri = upload_message_image_from_request(request, user_file, user_profile)
|
|
return json_success({'uri': uri})
|