From be432467ecec44524a73d050002f682f26a3db0d Mon Sep 17 00:00:00 2001 From: Sergey Skripnick Date: Tue, 31 Oct 2017 19:02:08 +0200 Subject: [PATCH] Content-Disposition fast access in ClientResponse Add content_disposition and content_disposition_dict properties Partially implements #1670 --- aiohttp/helpers.py | 22 ++++++++++++++++++++++ docs/client_reference.rst | 19 +++++++++++++++++++ tests/test_client_response.py | 25 +++++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/aiohttp/helpers.py b/aiohttp/helpers.py index 8c1824fdf91..e3fe9a49aaf 100644 --- a/aiohttp/helpers.py +++ b/aiohttp/helpers.py @@ -703,6 +703,7 @@ class HeadersMixin: _content_type = None _content_dict = None + _stored_content_disposition = None _stored_content_type = sentinel def _parse_content_type(self, raw): @@ -714,6 +715,13 @@ def _parse_content_type(self, raw): else: self._content_type, self._content_dict = cgi.parse_header(raw) + def _parse_content_disposition(self, _CONTENT_DISPOSITION): + raw = self._headers.get(_CONTENT_DISPOSITION) + if raw is not None: + self._stored_content_disposition = cgi.parse_header(raw) + else: + self._stored_content_disposition = (None, {}) + @property def content_type(self, *, _CONTENT_TYPE=hdrs.CONTENT_TYPE): """The value of content part for Content-Type HTTP header.""" @@ -737,3 +745,17 @@ def content_length(self, *, _CONTENT_LENGTH=hdrs.CONTENT_LENGTH): if content_length: return int(content_length) + + @property + def content_disposition(self, *, header=hdrs.CONTENT_DISPOSITION): + """The value of Content-Disposition HTTP header.""" + if self._stored_content_disposition is None: + self._parse_content_disposition(header) + return self._stored_content_disposition[0] + + @property + def content_disposition_dict(self, *, header=hdrs.CONTENT_DISPOSITION): + """Parameters of Content-Disposition HTTP header.""" + if self._stored_content_disposition is None: + self._parse_content_disposition(header) + return self._stored_content_disposition[1] diff --git a/docs/client_reference.rst b/docs/client_reference.rst index 101aa3163d7..d6b6d7b27f8 100644 --- a/docs/client_reference.rst +++ b/docs/client_reference.rst @@ -1116,6 +1116,25 @@ Response object Returns :class:`str` like ``'utf-8'`` or ``None`` if no *Content-Type* header present in HTTP headers or it has no charset information. + .. attribute:: content_disposition + + Read-only property that specified the *Content-Disposition* HTTP header. + + Returns :class:`str` like ``'attachment'`` or ``None`` if no + *Content-Disposition* header present in HTTP headers. + + .. seealso:: :attr:`content_disposition_dict` + + .. attribute:: content_disposition_dict + + Read-only property that specified the *Content-Disposition* dictionary of parameters. + + Returns :class:`dict` like ``{'filename': 'archive.tar.gz'}`` or empty dict if no + *Content-Disposition* header present in HTTP headers. + + .. note:: Using ``filename`` parameter directly may be not safe because it may + contain file path components, e.g. ``/home/user/.bashrc``. + .. attribute:: history A :class:`~collections.abc.Sequence` of :class:`ClientResponse` diff --git a/tests/test_client_response.py b/tests/test_client_response.py index b0a08845747..35d72bfd773 100644 --- a/tests/test_client_response.py +++ b/tests/test_client_response.py @@ -458,6 +458,31 @@ def test_charset_no_charset(): assert response.charset is None +def test_content_disposition_full(): + response = ClientResponse('get', URL('http://def-cl-resp.org')) + response.headers = {'Content-Disposition': + 'attachment; filename="archive.tar.gz"'} + + assert 'attachment' == response.content_disposition + assert 'archive.tar.gz' == response.content_disposition_dict["filename"] + + +def test_content_disposition_no_dict(): + response = ClientResponse('get', URL('http://def-cl-resp.org')) + response.headers = {'Content-Disposition': 'attachment'} + + assert 'attachment' == response.content_disposition + assert {} == response.content_disposition_dict + + +def test_content_disposition_no_header(): + response = ClientResponse('get', URL('http://def-cl-resp.org')) + response.headers = {} + + assert response.content_disposition is None + assert {} == response.content_disposition_dict + + def test_response_request_info(): url = 'http://def-cl-resp.org' headers = {'Content-Type': 'application/json;charset=cp1251'}