jazzband / django-downloadview

Serve files with Django.
https://django-downloadview.readthedocs.io
Other
374 stars 57 forks source link

404 for files with non-latin filenames with StorageDownloadView and nginx optimization #110

Open vmspike opened 8 years ago

vmspike commented 8 years ago

I'm tried extremely simple configuration based on examples from manual. Files with latin filenames served well but for files like ИП_ТУ.odt (Cyrillic) code 404 returned. Some useful logs:

nginx error.log:
2015/09/03 00:28:37 [error] 14503#0: *7 open() "/full/path/%D0%98%D0%9F_%D0%A2%D0%A3.odt" failed (2: No such file or directory), client: 192.168.2.2, server: server, request: "GET /path/%D0%98%D0%9F_%D0%A2%D0%A3.odt HTTP/1.1", upstream: "uwsgi://unix:///some.sock", host: "server"

uwsgi emperror.log:
[pid: 14496|app: 0|req: 1/5] 192.168.2.2 () {44 vars in 814 bytes} [Thu Sep  3 00:28:37 2015] GET /path/%D0%98%D0%9F_%D0%A2%D0%A3.odt => generated 0 bytes in 131 msecs (HTTP/1.1 200) 5 headers in 424 bytes (1 switches on core 0) 

So the problem is quoted %XX-symbols, nginx trying to find files with exactly path (with %XX-symbols). Probably conversion is required before passing filename to web-server.

Also FileNotFoundError exception doesn't catch up if file not found.

  File "/myapp/venv/lib/python3.4/site-packages/django_downloadview/response.py", line 182, in default_headers
    headers['Content-Length'] = self.file.size
  File "/myapp/venv/lib/python3.4/site-packages/django_downloadview/files.py", line 107, in size
    return self.storage.size(self.name)
  File "/myapp/venv/lib/python3.4/site-packages/django/core/files/storage.py", line 311, in size
    return os.path.getsize(self.path(name))
  File "/myapp/venv/lib/python3.4/genericpath.py", line 50, in getsize
    return os.stat(filename).st_size
FileNotFoundError: [Errno 2] No such file or directory: '/full/path/ИП_ТУ.odttt'
vmspike commented 8 years ago

For catch FileNotFoundError exception you may change BaseDownloadView get() method to

class BaseDownloadView(DownloadMixin, View):
    """A base :class:`DownloadMixin` that implements :meth:`get`."""
    def get(self, request, *args, **kwargs):
        """Handle GET requests: stream a file."""
        try:
            return self.render_to_response()
        except FileNotFoundError:
            return self.file_not_found_response()
vmspike commented 8 years ago

As for quoting unicode issue temporary solution can be modifying XAccelRedirectResponse class this way:

from django.utils.encoding import uri_to_iri
class XAccelRedirectResponse(ProxiedDownloadResponse):
...
        # self['X-Accel-Redirect'] = redirect_url
        self['X-Accel-Redirect'] = bytes(uri_to_iri(redirect_url), 'utf-8')

This hack allow download files with non-ACSII name, but proposing filename for saving is %-quoted which is ugly. For make request for non US-ACSII filename correct need to change content_disposition() function to use filename instead of utf8_filename:

def content_disposition(filename):
    # Fix incorrect filename propose for download
    if not filename:
        return 'attachment'
    ascii_filename = encode_basename_ascii(filename)
    utf8_filename = encode_basename_utf8(filename)
    if ascii_filename == utf8_filename:  # ASCII only.
        return "attachment; filename=\"{ascii}\"".format(ascii=ascii_filename)
    else:
        return "attachment; filename=\"{ascii}\"; filename*=UTF-8''{utf8}".format(
                    ascii=ascii_filename,
                    # utf8=utf8_filename)
                    utf8=filename)