Skip to content

Commit

Permalink
Merge pull request #163 from plone/canonical
Browse files Browse the repository at this point in the history
feat: set canonical header for file
  • Loading branch information
davisagli authored Nov 21, 2024
2 parents 436edad + 2e54f71 commit db74ee2
Show file tree
Hide file tree
Showing 4 changed files with 26 additions and 3 deletions.
1 change: 1 addition & 0 deletions news/163.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Set `Link` header with `rel="canonical"` for file downloads. @mamico
13 changes: 11 additions & 2 deletions plone/namedfile/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ def handle_request_range(self, file):
except ValueError:
return default

def get_canonical(self, file):
filename = getattr(file, "filename", None)
if filename is None:
return f"{self.context.absolute_url()}/@@download/{self.fieldname}"
else:
return f"{self.context.absolute_url()}/@@download/{self.fieldname}/{filename}"

def set_headers(self, file):
# With filename None, set_headers will not add the download headers.
if not self.filename:
Expand All @@ -135,7 +142,8 @@ def set_headers(self, file):
self.filename = self.fieldname
if self.filename is None:
self.filename = "file.ext"
set_headers(file, self.request.response, filename=self.filename)
canonical = self.get_canonical(file)
set_headers(file, self.request.response, filename=self.filename, canonical=canonical)

def _getFile(self):
if not self.fieldname:
Expand Down Expand Up @@ -185,4 +193,5 @@ def set_headers(self, file):
if mimetype not in self.allowed_inline_mimetypes:
# Let the Download view handle this.
return super().set_headers(file)
set_headers(file, self.request.response)
canonical = self.get_canonical(file)
set_headers(file, self.request.response, canonical=canonical)
11 changes: 11 additions & 0 deletions plone/namedfile/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ These store data with the following types::
... self.image = namedfile.NamedImage()
... self.blob = namedfile.NamedBlobFile()
... self.blobimage = namedfile.NamedBlobImage()
...
... def absolute_url(self):
... return "http://foo/bar"


File data and content type
Expand Down Expand Up @@ -226,6 +229,8 @@ We will test this with a dummy request, faking traversal::
'text/plain'
>>> request.response.getHeader('Content-Disposition')
"attachment; filename*=UTF-8''test.txt"
>>> request.response.getHeader('Link')
'<http://foo/bar/@@download/simple/test.txt>; rel="canonical"'

>>> request = TestRequest()
>>> download = Download(container, request).publishTraverse(request, 'blob')
Expand All @@ -238,6 +243,8 @@ We will test this with a dummy request, faking traversal::
'text/plain'
>>> request.response.getHeader('Content-Disposition')
"attachment; filename*=UTF-8''test.txt"
>>> request.response.getHeader('Link')
'<http://foo/bar/@@download/blob/test.txt>; rel="canonical"'

>>> request = TestRequest()
>>> download = Download(container, request).publishTraverse(request, 'image')
Expand All @@ -250,6 +257,8 @@ We will test this with a dummy request, faking traversal::
'image/foo'
>>> request.response.getHeader('Content-Disposition')
"attachment; filename*=UTF-8''zpt.gif"
>>> request.response.getHeader('Link')
'<http://foo/bar/@@download/image/zpt.gif>; rel="canonical"'

>>> request = TestRequest()
>>> download = Download(container, request).publishTraverse(request, 'blobimage')
Expand All @@ -262,6 +271,8 @@ We will test this with a dummy request, faking traversal::
'image/foo'
>>> request.response.getHeader('Content-Disposition')
"attachment; filename*=UTF-8''zpt.gif"
>>> request.response.getHeader('Link')
'<http://foo/bar/@@download/blobimage/zpt.gif>; rel="canonical"'

Range support
-------------
Expand Down
4 changes: 3 additions & 1 deletion plone/namedfile/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def get_contenttype(file=None, filename=None, default="application/octet-stream"
return default


def set_headers(file, response, filename=None):
def set_headers(file, response, filename=None, canonical=None):
"""Set response headers for the given file. If filename is given, set
the Content-Disposition to attachment.
"""
Expand All @@ -149,6 +149,8 @@ def set_headers(file, response, filename=None):
"Content-Disposition", f"attachment; filename*=UTF-8''{filename}"
)

if canonical is not None:
response.setHeader("Link", f'<{quote(canonical, safe="/:&?=@")}>; rel="canonical"')

def stream_data(file, start=0, end=None):
"""Return the given file as a stream if possible."""
Expand Down

0 comments on commit db74ee2

Please sign in to comment.