Commit 611a8f15 authored by Jérome Perrin's avatar Jérome Perrin Committed by Arnaud Fontaine

DownloadableMixin: encode content disposition as RFC 6266

Previously, we were using an encoded string, which in practice worked,
but with python3, WSGI server will encode as latin1 (because WSGI is
latin1) and already with Zope4 on python2 we have issues with testing,
as functional testing fake WSGI server only accepts ascii headers [1]

1: https://github.com/zopefoundation/Zope/blob/cddecf7e/src/Testing/ZopeTestCase/functional.py#L125-L126
parent 1f9397e9
...@@ -629,6 +629,7 @@ class TestDocument(TestDocumentMixin): ...@@ -629,6 +629,7 @@ class TestDocument(TestDocumentMixin):
response.headers['content-type']) response.headers['content-type'])
self.assertEqual('attachment; filename="TEST-en-002.doc"', self.assertEqual('attachment; filename="TEST-en-002.doc"',
response.headers['content-disposition']) response.headers['content-disposition'])
response = self.publish('%s/OOoDocument_getOOoFile' % doc.getPath(), response = self.publish('%s/OOoDocument_getOOoFile' % doc.getPath(),
basic='member_user1:secret') basic='member_user1:secret')
self.assertEqual('application/vnd.oasis.opendocument.text', self.assertEqual('application/vnd.oasis.opendocument.text',
...@@ -636,6 +637,15 @@ class TestDocument(TestDocumentMixin): ...@@ -636,6 +637,15 @@ class TestDocument(TestDocumentMixin):
self.assertEqual('attachment; filename="TEST-en-002.odt"', self.assertEqual('attachment; filename="TEST-en-002.odt"',
response.headers['content-disposition']) response.headers['content-disposition'])
# Non ascii filenames are encoded as https://www.rfc-editor.org/rfc/rfc6266#appendix-D
doc.setFilename('テスト-jp-002.doc')
self.tic()
response = self.publish('%s/Base_download' % doc.getPath(), basic='member_user1:secret')
self.assertEqual(
response.headers['content-disposition'],
'attachment; filename="-jp-002.doc"; filename*=UTF-8\'\'%E3%83%86%E3%82%B9%E3%83%88-jp-002.doc',
)
def test_Member_download_pdf_format(self): def test_Member_download_pdf_format(self):
# tests that members can download OOo documents in pdf format (at least in # tests that members can download OOo documents in pdf format (at least in
# published state), and all headers (including filenames) are set correctly # published state), and all headers (including filenames) are set correctly
...@@ -671,6 +681,15 @@ class TestDocument(TestDocumentMixin): ...@@ -671,6 +681,15 @@ class TestDocument(TestDocumentMixin):
self.assertEqual('attachment; filename="import.file.with.dot.in.filename.pdf"', self.assertEqual('attachment; filename="import.file.with.dot.in.filename.pdf"',
response.headers['content-disposition']) response.headers['content-disposition'])
# Non ascii filenames are encoded as https://www.rfc-editor.org/rfc/rfc6266#appendix-D
doc.setFilename('PDFファイル.ods')
self.tic()
response = self.publish('%s?format=pdf' % doc.getPath(), basic='member_user2:secret')
self.assertEqual(
response.headers['content-disposition'],
'attachment; filename="PDF.pdf"; filename*=UTF-8\'\'PDF%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.pdf',
)
def test_05_getCreationDate(self): def test_05_getCreationDate(self):
""" """
Check getCreationDate on all document types. Check getCreationDate on all document types.
......
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# #
############################################################################## ##############################################################################
import six
from six.moves.urllib.parse import quote
from AccessControl import ClassSecurityInfo from AccessControl import ClassSecurityInfo
from Products.ERP5Type.Globals import InitializeClass from Products.ERP5Type.Globals import InitializeClass
from Products.ERP5Type import Permissions from Products.ERP5Type import Permissions
...@@ -35,6 +37,41 @@ from Products.CMFCore.utils import getToolByName, _checkConditionalGET, _setCach ...@@ -35,6 +37,41 @@ from Products.CMFCore.utils import getToolByName, _checkConditionalGET, _setCach
import warnings import warnings
from zExceptions import Forbidden from zExceptions import Forbidden
try:
# Zope5
from ZPublisher.HTTPResponse import make_content_disposition # pylint:disable=no-name-in-module
except ImportError:
# BBB backport https://github.com/zopefoundation/Zope/pull/893 with py2 support
def make_content_disposition(disposition, file_name):
if six.PY2 and not isinstance(file_name, unicode):
file_name = file_name.decode('utf-8')
try:
file_name.encode('us-ascii')
except UnicodeEncodeError:
# the file cannot be encoded using the `us-ascii` encoding
# which is advocated by RFC 7230 - 7237
#
# a special header has to be crafted
# also see https://tools.ietf.org/html/rfc6266#appendix-D
encoded_file_name = file_name.encode('us-ascii', errors='ignore')
if six.PY2:
quoted_file_name = quote(file_name.encode('utf-8'))
else:
quoted_file_name = quote(file_name)
return '{disposition}; '\
'filename="{encoded_file_name}"; '\
'filename*=UTF-8\'\'{quoted_file_name}'.format(
disposition=disposition,
encoded_file_name=encoded_file_name,
quoted_file_name=quoted_file_name)
else:
return '{disposition}; '\
'filename="{file_name}"'.format(
disposition=disposition,
file_name=file_name)
_MARKER = object() _MARKER = object()
class DownloadableMixin: class DownloadableMixin:
...@@ -124,8 +161,9 @@ class DownloadableMixin: ...@@ -124,8 +161,9 @@ class DownloadableMixin:
filename = self.getStandardFilename(format=format) filename = self.getStandardFilename(format=format)
# workaround for IE's bug to download files over SSL # workaround for IE's bug to download files over SSL
RESPONSE.setHeader('Pragma', '') RESPONSE.setHeader('Pragma', '')
RESPONSE.setHeader('Content-Disposition', RESPONSE.setHeader(
'attachment; filename="%s"' % filename) 'Content-Disposition',
make_content_disposition('attachment', filename))
RESPONSE.setHeader('Accept-Ranges', 'bytes') RESPONSE.setHeader('Accept-Ranges', 'bytes')
else: else:
RESPONSE.setHeader('Content-Disposition', 'inline') RESPONSE.setHeader('Content-Disposition', 'inline')
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment