diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index de4375ae6c..02cf453d2b 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -756,6 +756,13 @@ class File(Document): self.save_file(content=optimized_content, overwrite=True) self.save() + @property + def unique_url(self) -> str: + """Unique URL contains file ID in URL to speed up permisison checks.""" + from urllib.parse import urlencode + + return self.file_url + "?" + urlencode({"fid": self.name}) + @staticmethod def zip_files(files): zip_file = io.BytesIO() diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 72a2dfce82..74d7f84dac 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -673,7 +673,7 @@ class InboundMail(Email): content = self.content for file in attachments: if file.name in self.cid_map and self.cid_map[file.name]: - content = content.replace(f"cid:{self.cid_map[file.name]}", file.file_url) + content = content.replace(f"cid:{self.cid_map[file.name]}", file.unique_url) return content def is_notification(self): diff --git a/frappe/tests/test_api.py b/frappe/tests/test_api.py index 8d3065982b..8dd68dd644 100644 --- a/frappe/tests/test_api.py +++ b/frappe/tests/test_api.py @@ -15,7 +15,7 @@ from werkzeug.test import TestResponse import frappe from frappe.installer import update_site_config from frappe.tests.utils import FrappeTestCase, patch_hooks -from frappe.utils import cint, get_test_client, get_url +from frappe.utils import cint, get_site_url, get_test_client, get_url try: _site = frappe.local.site @@ -432,6 +432,21 @@ class TestResponse(FrappeAPITestCase): self.assertGreater(cint(response.headers["content-length"]), 0) self.assertEqual(response.headers["content-disposition"], f'filename="{encoded_filename}"') + def test_download_private_file_with_unique_url(self): + test_content = frappe.generate_hash() + file = frappe.get_doc( + { + "doctype": "File", + "file_name": test_content, + "content": test_content, + "is_private": 1, + } + ) + file.insert() + + self.assertEqual(self.get(file.unique_url, {"sid": self.sid}).text, test_content) + self.assertEqual(self.get(file.file_url, {"sid": self.sid}).text, test_content) + def generate_admin_keys(): from frappe.core.doctype.user.user import generate_keys diff --git a/frappe/utils/response.py b/frappe/utils/response.py index 5be201499b..116f3cd611 100644 --- a/frappe/utils/response.py +++ b/frappe/utils/response.py @@ -268,7 +268,12 @@ def download_private_file(path: str) -> Response: if frappe.session.user == "Guest": raise Forbidden(_("You don't have permission to access this file")) - files = frappe.get_all("File", filters={"file_url": path}, fields="*") + filters = {"file_url": path} + if frappe.form_dict.fid: + filters["name"] = str(frappe.form_dict.fid) + + files = frappe.get_all("File", filters=filters, fields="*") + # this file might be attached to multiple documents # if the file is accessible from any one of those documents # then it should be downloadable