Ticket #34194: 0001-Extract-function-for-generating-a-proper-Content-Dis.patch

File 0001-Extract-function-for-generating-a-proper-Content-Dis.patch, 4.9 KB (added by Alex Vandiver, 18 months ago)

Implementation, with tests

  • django/http/response.py

    From a1a382b44a33d8112996f15c1dd573bd2b1a039b Mon Sep 17 00:00:00 2001
    From: Alex Vandiver <alex@chmrr.net>
    Date: Wed, 30 Nov 2022 15:09:49 -0500
    Subject: [PATCH] Extract function for generating a proper Content-Disposition
     header.
    
    ---
     django/http/response.py        | 20 ++++----------------
     django/utils/http.py           | 22 ++++++++++++++++++++++
     tests/utils_tests/test_http.py | 16 ++++++++++++++++
     3 files changed, 42 insertions(+), 16 deletions(-)
    
    diff --git django/http/response.py django/http/response.py
    index bb94e81263..242a146aa2 100644
    import sys  
    88import time
    99from email.header import Header
    1010from http.client import responses
    11 from urllib.parse import quote, urlparse
     11from urllib.parse import urlparse
    1212
    1313from django.conf import settings
    1414from django.core import signals, signing
    from django.http.cookie import SimpleCookie  
    1818from django.utils import timezone
    1919from django.utils.datastructures import CaseInsensitiveMapping
    2020from django.utils.encoding import iri_to_uri
    21 from django.utils.http import http_date
     21from django.utils.http import content_disposition_header, http_date
    2222from django.utils.regex_helper import _lazy_re_compile
    2323
    2424_charset_from_content_type_re = _lazy_re_compile(
    class FileResponse(StreamingHttpResponse):  
    569569            else:
    570570                self.headers["Content-Type"] = "application/octet-stream"
    571571
    572         if filename:
    573             disposition = "attachment" if self.as_attachment else "inline"
    574             try:
    575                 filename.encode("ascii")
    576                 file_expr = 'filename="{}"'.format(
    577                     filename.replace("\\", "\\\\").replace('"', r"\"")
    578                 )
    579             except UnicodeEncodeError:
    580                 file_expr = "filename*=utf-8''{}".format(quote(filename))
    581             self.headers["Content-Disposition"] = "{}; {}".format(
    582                 disposition, file_expr
    583             )
    584         elif self.as_attachment:
    585             self.headers["Content-Disposition"] = "attachment"
     572        if self.as_attachment or filename:
     573            self.headers["Content-Disposition"] = content_disposition_header(self.as_attachment, filename)
    586574
    587575
    588576class HttpResponseRedirectBase(HttpResponse):
  • django/utils/http.py

    diff --git django/utils/http.py django/utils/http.py
    index db4dee2f27..c5f2870458 100644
    from urllib.parse import (  
    1010    _coerce_args,
    1111    _splitnetloc,
    1212    _splitparams,
     13    quote,
    1314    scheme_chars,
    1415    unquote,
    1516)
    def parse_header_parameters(line):  
    425426                value = unquote(value, encoding=encoding)
    426427            pdict[name] = value
    427428    return key, pdict
     429
     430def content_disposition_header(as_attachment, filename):
     431    """
     432    Construct a Content-Disposition header value.
     433    """
     434    if filename:
     435        disposition = "attachment" if as_attachment else "inline"
     436        try:
     437            filename.encode("ascii")
     438            file_expr = 'filename="{}"'.format(
     439                filename.replace("\\", "\\\\").replace('"', r"\"")
     440            )
     441        except UnicodeEncodeError:
     442            file_expr = "filename*=utf-8''{}".format(quote(filename))
     443        return "{}; {}".format(
     444            disposition, file_expr
     445        )
     446    elif as_attachment:
     447        return "attachment"
     448    else:
     449        return None
  • tests/utils_tests/test_http.py

    diff --git tests/utils_tests/test_http.py tests/utils_tests/test_http.py
    index add9625685..e028a90594 100644
    from django.test import SimpleTestCase  
    77from django.utils.datastructures import MultiValueDict
    88from django.utils.http import (
    99    base36_to_int,
     10    content_disposition_header,
    1011    escape_leading_slashes,
    1112    http_date,
    1213    int_to_base36,
    class ParseHeaderParameterTests(unittest.TestCase):  
    511512        for raw_line, expected_title in test_data:
    512513            parsed = parse_header_parameters(raw_line)
    513514            self.assertEqual(parsed[1]["title"], expected_title)
     515
     516class ContentDispositionHeaderTests(unittest.TestCase):
     517    def test(self):
     518        tests = (
     519            ((False, None), None),
     520            ((False, "example"), 'inline; filename="example"'),
     521            ((True, None), "attachment"),
     522            ((True, "example"), 'attachment; filename="example"'),
     523            ((True, '"example" file\\name'), 'attachment; filename="\\\"example\\\" file\\\\name"'),
     524            ((True, "espécimen"), 'attachment; filename*=utf-8\'\'esp%C3%A9cimen'),
     525            ((True, '"espécimen" filename'), 'attachment; filename*=utf-8\'\'%22esp%C3%A9cimen%22%20filename'),
     526        )
     527        for (is_attachment, filename), expected in tests:
     528            with self.subTest(is_attachment=is_attachment, filename=filename):
     529                self.assertEqual(content_disposition_header(is_attachment, filename), expected)
Back to Top