Welcome to django-sendfile2’s documentation!

This is a wrapper around web-server specific methods for sending files to web clients. This is useful when Django needs to check permissions associated files, but does not want to serve the actual bytes of the file itself. i.e. as serving large files is not what Django is made for.

Note this should not be used for regular file serving (e.g. CSS etc), only for cases where you need Django to do some work before serving the actual file.

Supported Python Versions

Python 3.7, 3.8, 3.9, and 3.10 are currently supported by this library.

Supported Django Versions

Django 2.2, 3.2, and 4.0 are currently supported by this library.

Fork

This project is a fork of django-sendfile. The original project appears mostly dead and has a number of outstanding bugs (especially with Python 3).

Funding

If you have found django-sendfile2 to be useful and would like to see its continued development, please consider buying me a coffee.

Getting Started

Installation

Install via pip:

pip install django-sendfile2

And then add django_sendfile to INSTALLED_APPS in your settings module.

Note

It is not strictly nessessary to have django_sendfile in INSTALLED_APPS, but this may change in future.

You will need to have the following set in your settings module:

  • SENDFILE_BACKEND - the dotted module notation of the backend you wish to use

  • SENDFILE_ROOT - the directory you wish to serve files from

Additionally, you may need to set SENDFILE_URL . See the Backends documentation for more details.

Use In Views

Use the sendfile() function instead of the usual HttpResponse function:

from django_sendfile import sendfile

@login_required
def my_secret_view(request):
    return sendfile(request, "/opt/my_secret.txt", mimetype="text/plain")

Alternatively, if you prefer class based views something like this would be required:

from django_sendfile import sendfile

class MySecretView(LoginRequiredMixin, View):
    def render_to_response(self, context):
        return sendfile

Backends

Backends are specified by setting SENDFILE_BACKEND to the dotted path of the backend you wish to use. E.g.:

settings.py
SENDFILE_BACKEND = "django_sendfile.backends.simple"

Development backend

django_sendfile.backends.development

The Development backend is only meant for use while writing code. It uses Django’s static file serving code to do the job, which is only meant for development. It reads the whole file into memory and the sends it down the wire - not good for big files, but OK when you are just testing things out.

It will work with the Django dev server and anywhere else you can run Django.

Simple backend

django_sendfile.backends.simple

This backend is one step up from the development backend. It uses Django’s django.core.files.base.File class to try and stream files from disk. However some middleware (e.g. GzipMiddleware) that rewrites content will causes the entire file to be loaded into memory. So only use this backend if you are not using middleware that rewrites content or you only have very small files.

mod_wsgi backend

django_sendfile.backends.mod_wsgi

The mod_wsgi backend will only work when using mod_wsgi in daemon mode, not in embedded mode. It requires a bit more work to get it to do the same job as xsendfile though. However some may find it easier to setup, as they don’t need to compile and install mod_xsendfile.

Firstly there one more Django setting that needs to be given:

  • SENDFILE_URL - internal URL prefix for all files served via sendfile

These settings are needed as this backend makes mod_wsgi send an internal redirect, so we have to convert a file path into a URL. This means that the files are visible via Apache by default too. So we need to get Apache to hide those files from anything that’s not an internal redirect. To so this we can use some mod_rewrite magic along these lines:

RewriteEngine On
# see if we're on an internal redirect or not
RewriteCond %{THE_REQUEST} ^[\S]+\ /private/
RewriteRule ^/private/ - [F]

Alias /private/ /home/john/Development/myapp/private/
<Directory /home/john/Development/myapp/private/>
    Order deny,allow
    Allow from all
</Directory>

In this case I have also set:

settings.py
SENDFILE_ROOT = '/home/john/Development/myapp/private/'
SENDFILE_URL = '/private'

All files are stored in a folder called ‘private’. We forbid access to this folder (RewriteRule ^/private/ - [F]) if someone tries to access it directly (RewriteCond %{THE_REQUEST} ^[\S]+\ /private/) by checking the original request (THE_REQUEST).

Allegedly IS_SUBREQ can be used to perform the same job, but I was unable to get this working.

Nginx backend

django_sendfile.backends.nginx

As with the mod_wsgi backend you need to set an extra settings:

  • SENDFILE_URL - internal URL prefix for all files served via sendfile

You then need to configure Nginx to only allow internal access to the files you wish to serve. More details on this are here.

For example though, if I use the Django settings:

settings.py
SENDFILE_ROOT = '/home/john/Development/django-sendfile/examples/protected_downloads/protected'
SENDFILE_URL = '/protected'

Then the matching location block in nginx.conf would be:

location /protected/ {
  internal;
  root   /home/john/Development/django-sendfile/examples/protected_downloads;
}

You need to pay attention to whether you have trailing slashes or not on the SENDFILE_URL and SENDFILE_ROOT values, otherwise you may not get the right URL being sent to Nginx and you may get 404s. You should be able to see what file Nginx is trying to load in the error.log if this happens. From there it should be fairly easy to work out what the right settings are.

xsendfile backend

django_sendfile.backends.xsendfile

Install either mod_xsendfile in Apache or use Lighthttpd. You may need to configure mod_xsendfile, but that should be as simple as:

XSendFile On

In your virtualhost file/conf file.

Custom Backend

A django-sendfile2 backend is simply a Python module that contains a callable named sendfile, for example:

myModule.py
def sendfile(request, filename, **kwargs):
    response = HttpResponse()
    response["X-My-Custom-Header"] = filename
    return response

Assuming the module is in your Python path and named myModule, you’d set SENDILE_BACKEND like so:

settings.py
SENDFILE_BACKEND = "myModule"

…and use django-sendfile2 in your views as you would normally.

Warning

Don’t get confused between this sendfile callable and sendfile() used in your views. The latter accepts slightly different arguments and takes care of various Content-* headers.

django_sendfile

django_sendfile package

Subpackages

django_sendfile.backends package
Submodules
django_sendfile.backends.development module
django_sendfile.backends.development.sendfile(request, filename, **kwargs)[source]

Send file using Django dev static file server.

Warning

Do not use in production. This is only to be used when developing and is provided for convenience only

django_sendfile.backends.mod_wsgi module
django_sendfile.backends.mod_wsgi.sendfile(request, filename, **kwargs)[source]
django_sendfile.backends.nginx module
django_sendfile.backends.nginx.sendfile(request, filename, **kwargs)[source]
django_sendfile.backends.simple module
django_sendfile.backends.simple.sendfile(request, filepath, **kwargs)[source]

Use the SENDFILE_ROOT value composed with the path arrived as argument to build an absolute path with which resolve and return the file contents.

If the path points to a file out of the root directory (should cover both situations with ‘..’ and symlinks) then a 404 is raised.

django_sendfile.backends.simple.was_modified_since(header=None, mtime=0, size=0)[source]

Was something modified since the user last downloaded it?

header

This is the value of the If-Modified-Since header. If this is None, I’ll just return True.

mtime

This is the modification time of the item we’re talking about.

size

This is the size of the item we’re talking about.

django_sendfile.backends.xsendfile module
django_sendfile.backends.xsendfile.sendfile(request, filename, **kwargs)[source]
Module contents

Submodules

django_sendfile.utils module
django_sendfile.utils.sendfile(request, filename, attachment=False, attachment_filename=None, mimetype=None, encoding=None)[source]

Create a response to send file using backend configured in SENDFILE_BACKEND

filename is the absolute path to the file to send.

If attachment is True the Content-Disposition header will be set accordingly. This will typically prompt the user to download the file, rather than view it. But even if False, the user may still be prompted, depending on the browser capabilities and configuration.

The Content-Disposition filename depends on the value of attachment_filename:

None (default): Same as filename False: No Content-Disposition filename String: Value used as filename

If neither mimetype or encoding are specified, then they will be guessed via the filename (using the standard Python mimetypes module)

Module contents

Security

If you find a security issue with django-sendfile2, email security@moggers87.co.uk. If you want to send an encrypted report, then please use key id 0x878B5A2A1D47C084.

django-sendfile2 follows the same security reporting model that has worked for other open source projects: If you report a security vulnerability, it will be acted on immediately and a fix with complete full disclosure will go out to everyone at the same time.

Changelog

0.7.0

release-date

2022-08-08

  • Fix reflected file download vulnerability

  • Add support for spaces in filenames

0.6.1

release-date

2021-01-18

  • Fixed Django 4.0 compatibility

  • Add support for Python 3.10

  • Remove support for Python 3.5 and 3.6

  • Remove support for Django 3.1

0.6.0

release-date

2020-06-17

  • Fixed issue where django-sendfile could serve any file, even if it was outside SENDFILE_ROOT. SENDFILE_ROOT is now required for all backends.

0.5.1

release-date

2019-12-30

  • Fix issue with versioneer not being updated about the package name change
    • tox now does a proper sdist and install to avoid this in future

0.5.0

release-date

2019-12-30

  • Rename Python package from sendfile to django_sendfile

    • This will require changing SENDFILE_BACKEND, INSTALLED_APPS, and any imports

  • Remove code used to support Python 2.7

  • Add support for the latest versions of Django and Python

Earlier Releases

Sorry, we didn’t keep a changelog prior to version 0.5.0!

Indices and tables