Files @ 94c56bb468cb
Branch filter:

Location: website/conservancy/views.py

bsturmfels
Rewrite the `index` view to avoid risk of path traversal

I've simplified this view by removing the custom HTTP error handlers, Python 3.5
exception handling and adding documentation.
import mimetypes

from django.conf import settings
from django.http import Http404
from django.http import FileResponse
from django.template.response import TemplateResponse

from .local_context_processors import fundgoal_lookup

def index(request, *args, **kwargs):
    """Faux CMS: bulk website content stored in templates and document files.

    Rationale: Many websites have a CMS and store the majority of their website
    content in a relational database eg. WordPress or Wagtail. That's useful
    because various people can easily be given access to edit the website. The
    downside is that is application complexity - the management of who change
    what, when it changed and what changed becomes an application concern. At
    the other end of the spectrum, we have files that are checked into a Git
    repository - we get the precise who/what/when out of the box with Git, but
    require you to have some technical knowledge and appropriate access to
    commit. Since you're committing to a code repository, this also opens up the
    possibility to break things you couldn't break via a CMS.

    This view serves most of the textual pages and documents on
    sfconservancy.org. It works a little like Apache serving mixed PHP/static
    files - it looks at the URL and tries to find a matching file on the
    filesystem. If it finds a template, it renders it via Django's template
    infrastructure. If it finds a file but it's not a template, it will serve
    the file as-is.
    """
    # The name "static" has no connection to Django staticfiles.
    base_path = settings.BASE_DIR / 'static'
    path = request.path.lstrip('/')
    if path.endswith('/'):
        path += 'index.html'
    full_path = (base_path / path).resolve()
    safe_from_path_traversal = full_path.is_relative_to(base_path)
    if not full_path.exists() or not safe_from_path_traversal:
        raise Http404()
    is_template = mimetypes.guess_type(full_path)[0] == 'text/html'
    if not is_template:
        return FileResponse(open(full_path, 'rb'))
    else:
        context = kwargs.copy()
        try:
            context['fundgoal'] = fundgoal_lookup(kwargs['fundraiser_sought'])
        except KeyError:
            pass
        # Maybe this should open() the template file directly so that these
        # don't have to be included in the global template TEMPLATES.DIRS?
        return TemplateResponse(request, path, context)