From 5cb92b8d96fc5ea7a0ed21729b22564240a70858 Mon Sep 17 00:00:00 2001 From: davisagli Date: Sun, 24 Nov 2024 15:02:01 -0800 Subject: [PATCH] [fc] Repository: plone.restapi Branch: refs/heads/main Date: 2024-11-24T15:02:01-08:00 Author: Steve Piercy (stevepiercy) Commit: https://github.com/plone/plone.restapi/commit/9403ec481704d89b96d072e85134e132ff6d80a8 Fix linkcheck (#1846) * Fix linkcheck - html_use_opensearch value must not have a trailing slash - Clean up comments * news Files changed: A news/1846.documentation M docs/source/conf.py --- last_commit.txt | 135 +++++------------------------------------------- 1 file changed, 14 insertions(+), 121 deletions(-) diff --git a/last_commit.txt b/last_commit.txt index 34c2489412..bdac74ae44 100644 --- a/last_commit.txt +++ b/last_commit.txt @@ -1,129 +1,22 @@ -Repository: plone.namedfile +Repository: plone.restapi -Branch: refs/heads/master -Date: 2024-10-28T01:28:15+01:00 -Author: Mauro Amico (mamico) -Commit: https://github.com/plone/plone.namedfile/commit/d72e456918db99ee8ca78e92bb2c7b9ea273270f +Branch: refs/heads/main +Date: 2024-11-24T15:02:01-08:00 +Author: Steve Piercy (stevepiercy) +Commit: https://github.com/plone/plone.restapi/commit/9403ec481704d89b96d072e85134e132ff6d80a8 -feat: set canonical header for file +Fix linkcheck (#1846) -Files changed: -M plone/namedfile/browser.py -M plone/namedfile/tests/test_display_file.py -M plone/namedfile/utils/__init__.py - -b'diff --git a/plone/namedfile/browser.py b/plone/namedfile/browser.py\nindex 0930b75..0b753cb 100644\n--- a/plone/namedfile/browser.py\n+++ b/plone/namedfile/browser.py\n@@ -127,6 +127,13 @@ def handle_request_range(self, file):\n except ValueError:\n return default\n \n+ def get_canonical(self, file):\n+ filename = getattr(file, "filename", None)\n+ if filename is None:\n+ return f"{self.context.absolute_url()}/@@download/{self.fieldname}"\n+ else:\n+ return f"{self.context.absolute_url()}/@@download/{self.fieldname}/filename"\n+\n def set_headers(self, file):\n # With filename None, set_headers will not add the download headers.\n if not self.filename:\n@@ -135,7 +142,8 @@ def set_headers(self, file):\n self.filename = self.fieldname\n if self.filename is None:\n self.filename = "file.ext"\n- set_headers(file, self.request.response, filename=self.filename)\n+ canonical = self.get_canonical(file)\n+ set_headers(file, self.request.response, filename=self.filename, canonical=canonical)\n \n def _getFile(self):\n if not self.fieldname:\n@@ -185,4 +193,5 @@ def set_headers(self, file):\n if mimetype not in self.allowed_inline_mimetypes:\n # Let the Download view handle this.\n return super().set_headers(file)\n- set_headers(file, self.request.response)\n+ canonical = self.get_canonical(file)\n+ set_headers(file, self.request.response, canonical=canonical)\ndiff --git a/plone/namedfile/tests/test_display_file.py b/plone/namedfile/tests/test_display_file.py\nindex 4ab467d..88e4151 100644\n--- a/plone/namedfile/tests/test_display_file.py\n+++ b/plone/namedfile/tests/test_display_file.py\n@@ -49,6 +49,15 @@ def get_disposition_header(browser):\n return browser.headers.get(name, None)\n \n \n+def get_canonical_header(browser):\n+ name = "Link"\n+ for header, value in browser.headers.items():\n+ if header == name or header == name.lower():\n+ if \'rel="canonical"\' in map(str.strip, value.split(";")):\n+ return value\n+ return None\n+\n+\n def custom_available_sizes():\n # Define available image scales.\n return {"custom": (10, 10)}\n@@ -96,12 +105,16 @@ def assert_download_works(self, base_url):\n self.assertIsNotNone(header)\n self.assertIn("attachment", header)\n self.assertIn("filename", header)\n+ header = get_canonical_header(browser)\n+ self.assertTrue(header.startswith(f"<{base_url}/@@download/{self.field_name}>"))\n \n def assert_display_inline_works(self, base_url):\n # Test that displaying this file inline works.\n browser = self.get_anon_browser()\n browser.open(base_url + f"/@@display-file/{self.field_name}")\n self.assertIsNone(get_disposition_header(browser))\n+ header = get_canonical_header(browser)\n+ self.assertTrue(header.startswith(f"<{base_url}/@@download/{self.field_name}>"))\n \n def assert_display_inline_is_download(self, base_url):\n # Test that displaying this file inline turns into a download.\n@@ -111,6 +124,8 @@ def assert_display_inline_is_download(self, base_url):\n self.assertIsNotNone(header)\n self.assertIn("attachment", header)\n self.assertIn("filename", header)\n+ header = get_canonical_header(browser)\n+ self.assertTrue(header.startswith(f"<{base_url}/@@download/{self.field_name}>"))\n \n def assert_scale_view_works(self, base_url):\n # Test that accessing a scale view shows the image inline.\ndiff --git a/plone/namedfile/utils/__init__.py b/plone/namedfile/utils/__init__.py\nindex 2f17ead..6509aec 100644\n--- a/plone/namedfile/utils/__init__.py\n+++ b/plone/namedfile/utils/__init__.py\n@@ -130,7 +130,7 @@ def get_contenttype(file=None, filename=None, default="application/octet-stream"\n return default\n \n \n-def set_headers(file, response, filename=None):\n+def set_headers(file, response, filename=None, canonical=None):\n """Set response headers for the given file. If filename is given, set\n the Content-Disposition to attachment.\n """\n@@ -149,6 +149,8 @@ def set_headers(file, response, filename=None):\n "Content-Disposition", f"attachment; filename*=UTF-8\'\'{filename}"\n )\n \n+ if canonical is not None:\n+ response.setHeader("Link", f\'<{canonical}>; rel="canonical"\')\n \n def stream_data(file, start=0, end=None):\n """Return the given file as a stream if possible."""\n' - -Repository: plone.namedfile - - -Branch: refs/heads/master -Date: 2024-10-28T01:35:22+01:00 -Author: Mauro Amico (mamico) -Commit: https://github.com/plone/plone.namedfile/commit/5a9fc2f369e50c826ada3939294900325d30ea83 - -fix test - -Files changed: -M plone/namedfile/usage.rst - -b'diff --git a/plone/namedfile/usage.rst b/plone/namedfile/usage.rst\nindex 759afe3..bec7325 100644\n--- a/plone/namedfile/usage.rst\n+++ b/plone/namedfile/usage.rst\n@@ -32,6 +32,9 @@ These store data with the following types::\n ... self.image = namedfile.NamedImage()\n ... self.blob = namedfile.NamedBlobFile()\n ... self.blobimage = namedfile.NamedBlobImage()\n+ ...\n+ ... def absolute_url(self):\n+ ... return "http://foo/bar"\n \n \n File data and content type\n' - -Repository: plone.namedfile - - -Branch: refs/heads/master -Date: 2024-10-28T01:48:30+01:00 -Author: Mauro Amico (mamico) -Commit: https://github.com/plone/plone.namedfile/commit/36fe1a40779787bbd025b4ce82b792473a7014f9 - -fix test - -Files changed: -M plone/namedfile/browser.py -M plone/namedfile/tests/test_display_file.py -M plone/namedfile/usage.rst - -b'diff --git a/plone/namedfile/browser.py b/plone/namedfile/browser.py\nindex 0b753cb..538b5d8 100644\n--- a/plone/namedfile/browser.py\n+++ b/plone/namedfile/browser.py\n@@ -132,7 +132,7 @@ def get_canonical(self, file):\n if filename is None:\n return f"{self.context.absolute_url()}/@@download/{self.fieldname}"\n else:\n- return f"{self.context.absolute_url()}/@@download/{self.fieldname}/filename"\n+ return f"{self.context.absolute_url()}/@@download/{self.fieldname}/{filename}"\n \n def set_headers(self, file):\n # With filename None, set_headers will not add the download headers.\ndiff --git a/plone/namedfile/tests/test_display_file.py b/plone/namedfile/tests/test_display_file.py\nindex 88e4151..4ab467d 100644\n--- a/plone/namedfile/tests/test_display_file.py\n+++ b/plone/namedfile/tests/test_display_file.py\n@@ -49,15 +49,6 @@ def get_disposition_header(browser):\n return browser.headers.get(name, None)\n \n \n-def get_canonical_header(browser):\n- name = "Link"\n- for header, value in browser.headers.items():\n- if header == name or header == name.lower():\n- if \'rel="canonical"\' in map(str.strip, value.split(";")):\n- return value\n- return None\n-\n-\n def custom_available_sizes():\n # Define available image scales.\n return {"custom": (10, 10)}\n@@ -105,16 +96,12 @@ def assert_download_works(self, base_url):\n self.assertIsNotNone(header)\n self.assertIn("attachment", header)\n self.assertIn("filename", header)\n- header = get_canonical_header(browser)\n- self.assertTrue(header.startswith(f"<{base_url}/@@download/{self.field_name}>"))\n \n def assert_display_inline_works(self, base_url):\n # Test that displaying this file inline works.\n browser = self.get_anon_browser()\n browser.open(base_url + f"/@@display-file/{self.field_name}")\n self.assertIsNone(get_disposition_header(browser))\n- header = get_canonical_header(browser)\n- self.assertTrue(header.startswith(f"<{base_url}/@@download/{self.field_name}>"))\n \n def assert_display_inline_is_download(self, base_url):\n # Test that displaying this file inline turns into a download.\n@@ -124,8 +111,6 @@ def assert_display_inline_is_download(self, base_url):\n self.assertIsNotNone(header)\n self.assertIn("attachment", header)\n self.assertIn("filename", header)\n- header = get_canonical_header(browser)\n- self.assertTrue(header.startswith(f"<{base_url}/@@download/{self.field_name}>"))\n \n def assert_scale_view_works(self, base_url):\n # Test that accessing a scale view shows the image inline.\ndiff --git a/plone/namedfile/usage.rst b/plone/namedfile/usage.rst\nindex bec7325..c18cf07 100644\n--- a/plone/namedfile/usage.rst\n+++ b/plone/namedfile/usage.rst\n@@ -229,6 +229,8 @@ We will test this with a dummy request, faking traversal::\n \'text/plain\'\n >>> request.response.getHeader(\'Content-Disposition\')\n "attachment; filename*=UTF-8\'\'test.txt"\n+ >>> request.response.getHeader(\'Link\')\n+ \'; rel="canonical"\'\n \n >>> request = TestRequest()\n >>> download = Download(container, request).publishTraverse(request, \'blob\')\n@@ -241,6 +243,8 @@ We will test this with a dummy request, faking traversal::\n \'text/plain\'\n >>> request.response.getHeader(\'Content-Disposition\')\n "attachment; filename*=UTF-8\'\'test.txt"\n+ >>> request.response.getHeader(\'Link\')\n+ \'; rel="canonical"\'\n \n >>> request = TestRequest()\n >>> download = Download(container, request).publishTraverse(request, \'image\')\n@@ -253,6 +257,8 @@ We will test this with a dummy request, faking traversal::\n \'image/foo\'\n >>> request.response.getHeader(\'Content-Disposition\')\n "attachment; filename*=UTF-8\'\'zpt.gif"\n+ >>> request.response.getHeader(\'Link\')\n+ \'; rel="canonical"\'\n \n >>> request = TestRequest()\n >>> download = Download(container, request).publishTraverse(request, \'blobimage\')\n@@ -265,6 +271,8 @@ We will test this with a dummy request, faking traversal::\n \'image/foo\'\n >>> request.response.getHeader(\'Content-Disposition\')\n "attachment; filename*=UTF-8\'\'zpt.gif"\n+ >>> request.response.getHeader(\'Link\')\n+ \'; rel="canonical"\'\n \n Range support\n -------------\n' - -Repository: plone.namedfile - - -Branch: refs/heads/master -Date: 2024-10-28T14:13:31+01:00 -Author: Mauro Amico (mamico) -Commit: https://github.com/plone/plone.namedfile/commit/5a808ba73b98be70ce0786f5f4fe87937248761c - -changelog - -Files changed: -A news/163.feature - -b'diff --git a/news/163.feature b/news/163.feature\nnew file mode 100644\nindex 00000000..08cf3f9e\n--- /dev/null\n+++ b/news/163.feature\n@@ -0,0 +1 @@\n+set canonical header for file. @mamico\n' - -Repository: plone.namedfile - - -Branch: refs/heads/master -Date: 2024-10-28T21:59:49-07:00 -Author: David Glick (davisagli) -Commit: https://github.com/plone/plone.namedfile/commit/6296627099f9524d545d4e2de8d195a6bddea6d6 - -Update news/163.feature - -Files changed: -M news/163.feature - -b'diff --git a/news/163.feature b/news/163.feature\nindex 08cf3f9..3891be3 100644\n--- a/news/163.feature\n+++ b/news/163.feature\n@@ -1 +1 @@\n-set canonical header for file. @mamico\n+Set `Link` header with `rel="canonical"` for file downloads. @mamico\n' - -Repository: plone.namedfile - - -Branch: refs/heads/master -Date: 2024-11-16T23:50:52+01:00 -Author: Mauro Amico (mamico) -Commit: https://github.com/plone/plone.namedfile/commit/39abbc0d7af268fe443603886ce8bf24335b2963 - -The URI (absolute or relative) must percent-encode character codes greater than 255 (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Link) - -Files changed: -M plone/namedfile/utils/__init__.py - -b'diff --git a/plone/namedfile/utils/__init__.py b/plone/namedfile/utils/__init__.py\nindex 6509aec..dd9da38 100644\n--- a/plone/namedfile/utils/__init__.py\n+++ b/plone/namedfile/utils/__init__.py\n@@ -150,7 +150,7 @@ def set_headers(file, response, filename=None, canonical=None):\n )\n \n if canonical is not None:\n- response.setHeader("Link", f\'<{canonical}>; rel="canonical"\')\n+ response.setHeader("Link", f\'<{quote(canonical, safe=\'\')}>; rel="canonical"\')\n \n def stream_data(file, start=0, end=None):\n """Return the given file as a stream if possible."""\n' - -Repository: plone.namedfile - - -Branch: refs/heads/master -Date: 2024-11-16T23:50:52+01:00 -Author: Mauro Amico (mamico) -Commit: https://github.com/plone/plone.namedfile/commit/2e54f717898c9b717a5100682e3cb0f43a91b68f - -fix quote - -Files changed: -M plone/namedfile/utils/__init__.py - -b'diff --git a/plone/namedfile/utils/__init__.py b/plone/namedfile/utils/__init__.py\nindex dd9da38..a2ccf85 100644\n--- a/plone/namedfile/utils/__init__.py\n+++ b/plone/namedfile/utils/__init__.py\n@@ -150,7 +150,7 @@ def set_headers(file, response, filename=None, canonical=None):\n )\n \n if canonical is not None:\n- response.setHeader("Link", f\'<{quote(canonical, safe=\'\')}>; rel="canonical"\')\n+ response.setHeader("Link", f\'<{quote(canonical, safe="/:&?=@")}>; rel="canonical"\')\n \n def stream_data(file, start=0, end=None):\n """Return the given file as a stream if possible."""\n' - -Repository: plone.namedfile - - -Branch: refs/heads/master -Date: 2024-11-21T11:17:36-08:00 -Author: David Glick (davisagli) -Commit: https://github.com/plone/plone.namedfile/commit/db74ee2200c9f4d2332d930bc5c1c00478e50592 - -Merge pull request #163 from plone/canonical - -feat: set canonical header for file +* Fix linkcheck +- html_use_opensearch value must not have a trailing slash +- Clean up comments + +* news Files changed: -A news/163.feature -M plone/namedfile/browser.py -M plone/namedfile/usage.rst -M plone/namedfile/utils/__init__.py +A news/1846.documentation +M docs/source/conf.py -b'diff --git a/news/163.feature b/news/163.feature\nnew file mode 100644\nindex 00000000..3891be30\n--- /dev/null\n+++ b/news/163.feature\n@@ -0,0 +1 @@\n+Set `Link` header with `rel="canonical"` for file downloads. @mamico\ndiff --git a/plone/namedfile/browser.py b/plone/namedfile/browser.py\nindex 0930b752..538b5d86 100644\n--- a/plone/namedfile/browser.py\n+++ b/plone/namedfile/browser.py\n@@ -127,6 +127,13 @@ def handle_request_range(self, file):\n except ValueError:\n return default\n \n+ def get_canonical(self, file):\n+ filename = getattr(file, "filename", None)\n+ if filename is None:\n+ return f"{self.context.absolute_url()}/@@download/{self.fieldname}"\n+ else:\n+ return f"{self.context.absolute_url()}/@@download/{self.fieldname}/{filename}"\n+\n def set_headers(self, file):\n # With filename None, set_headers will not add the download headers.\n if not self.filename:\n@@ -135,7 +142,8 @@ def set_headers(self, file):\n self.filename = self.fieldname\n if self.filename is None:\n self.filename = "file.ext"\n- set_headers(file, self.request.response, filename=self.filename)\n+ canonical = self.get_canonical(file)\n+ set_headers(file, self.request.response, filename=self.filename, canonical=canonical)\n \n def _getFile(self):\n if not self.fieldname:\n@@ -185,4 +193,5 @@ def set_headers(self, file):\n if mimetype not in self.allowed_inline_mimetypes:\n # Let the Download view handle this.\n return super().set_headers(file)\n- set_headers(file, self.request.response)\n+ canonical = self.get_canonical(file)\n+ set_headers(file, self.request.response, canonical=canonical)\ndiff --git a/plone/namedfile/usage.rst b/plone/namedfile/usage.rst\nindex 759afe38..c18cf075 100644\n--- a/plone/namedfile/usage.rst\n+++ b/plone/namedfile/usage.rst\n@@ -32,6 +32,9 @@ These store data with the following types::\n ... self.image = namedfile.NamedImage()\n ... self.blob = namedfile.NamedBlobFile()\n ... self.blobimage = namedfile.NamedBlobImage()\n+ ...\n+ ... def absolute_url(self):\n+ ... return "http://foo/bar"\n \n \n File data and content type\n@@ -226,6 +229,8 @@ We will test this with a dummy request, faking traversal::\n \'text/plain\'\n >>> request.response.getHeader(\'Content-Disposition\')\n "attachment; filename*=UTF-8\'\'test.txt"\n+ >>> request.response.getHeader(\'Link\')\n+ \'; rel="canonical"\'\n \n >>> request = TestRequest()\n >>> download = Download(container, request).publishTraverse(request, \'blob\')\n@@ -238,6 +243,8 @@ We will test this with a dummy request, faking traversal::\n \'text/plain\'\n >>> request.response.getHeader(\'Content-Disposition\')\n "attachment; filename*=UTF-8\'\'test.txt"\n+ >>> request.response.getHeader(\'Link\')\n+ \'; rel="canonical"\'\n \n >>> request = TestRequest()\n >>> download = Download(container, request).publishTraverse(request, \'image\')\n@@ -250,6 +257,8 @@ We will test this with a dummy request, faking traversal::\n \'image/foo\'\n >>> request.response.getHeader(\'Content-Disposition\')\n "attachment; filename*=UTF-8\'\'zpt.gif"\n+ >>> request.response.getHeader(\'Link\')\n+ \'; rel="canonical"\'\n \n >>> request = TestRequest()\n >>> download = Download(container, request).publishTraverse(request, \'blobimage\')\n@@ -262,6 +271,8 @@ We will test this with a dummy request, faking traversal::\n \'image/foo\'\n >>> request.response.getHeader(\'Content-Disposition\')\n "attachment; filename*=UTF-8\'\'zpt.gif"\n+ >>> request.response.getHeader(\'Link\')\n+ \'; rel="canonical"\'\n \n Range support\n -------------\ndiff --git a/plone/namedfile/utils/__init__.py b/plone/namedfile/utils/__init__.py\nindex 2f17ead9..a2ccf851 100644\n--- a/plone/namedfile/utils/__init__.py\n+++ b/plone/namedfile/utils/__init__.py\n@@ -130,7 +130,7 @@ def get_contenttype(file=None, filename=None, default="application/octet-stream"\n return default\n \n \n-def set_headers(file, response, filename=None):\n+def set_headers(file, response, filename=None, canonical=None):\n """Set response headers for the given file. If filename is given, set\n the Content-Disposition to attachment.\n """\n@@ -149,6 +149,8 @@ def set_headers(file, response, filename=None):\n "Content-Disposition", f"attachment; filename*=UTF-8\'\'{filename}"\n )\n \n+ if canonical is not None:\n+ response.setHeader("Link", f\'<{quote(canonical, safe="/:&?=@")}>; rel="canonical"\')\n \n def stream_data(file, start=0, end=None):\n """Return the given file as a stream if possible."""\n' +b'diff --git a/docs/source/conf.py b/docs/source/conf.py\nindex 5bc2f084a2..4282d26b9f 100644\n--- a/docs/source/conf.py\n+++ b/docs/source/conf.py\n@@ -290,7 +290,7 @@ def patch_pygments_to_highlight_jsonschema():\n # base URL from which the finished HTML is served.\n # Announce that we have an opensearch plugin\n # https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_use_opensearch\n-html_use_opensearch = "https://plonerestapi.readthedocs.org/"\n+html_use_opensearch = "https://plonerestapi.readthedocs.org"\n \n \n # This is the file name suffix for HTML files (e.g. ".xhtml").\ndiff --git a/news/1846.documentation b/news/1846.documentation\nnew file mode 100644\nindex 0000000000..d46a5b6816\n--- /dev/null\n+++ b/news/1846.documentation\n@@ -0,0 +1 @@\n+`html_use_opensearch` value must not have a trailing slash. Clean up comments. @stevepiercy\n'