Repository: openfun/marsha

Issue:
(deposit) support apostrophe in filenames
## Bug Report

**Problematic Behavior**
Loading a file with a typographic apostrophe (or curly quote) in the filename generates an error during the download process

``` xml
<Error>
<Code>InvalidArgument</Code>
<Message>Header value cannot be represented using ISO-8859-1.</Message>
<ArgumentName>response-content-disposition</ArgumentName>
<ArgumentValue>attachment; filename=Capture d’écran du 2023-07-05 17-55-04.png</ArgumentValue>
<RequestId>7576DY31G7YEKEHW</RequestId>
<HostId>20bDgURy8zglt3nW4UJbIbH0G5+c+DOmSeRKq/K2YNwP6rZMMHzS4h0Af91vioEWtbJsRurDzP0=</HostId>
</Error>
```

**Steps to Reproduce**
1. Add a Marsha Deposit XBlock on Studio
2. On student view, upload a file with a typographic apostrophe on its name 
3. Try to download the file

**Environment**
- Marsha version: production
- Platform: Open edX

**Possible Solution**
Use a more extensive character set like UTF-8?
Otherwise, escape apostrophes in filenames or replace it by right single quotation marks?

**Additional context/Screenshots**

- OK : https://www.compart.com/fr/unicode/U+0027
- KO : https://www.compart.com/fr/unicode/U+2019

[deposit-typographic-apostrophe-bug.webm](https://github.com/openfun/marsha/assets/23249365/ee43dd63-9b1a-43a7-912c-6ed84f07404c)


Discussion:

Resolving patch:
diff --git a/src/backend/marsha/deposit/serializers.py b/src/backend/marsha/deposit/serializers.py
--- a/src/backend/marsha/deposit/serializers.py
+++ b/src/backend/marsha/deposit/serializers.py
@@ -1,7 +1,7 @@
 """Structure of deposit related models API responses with Django Rest Framework serializers."""
 import mimetypes
 from os.path import splitext
-from urllib.parse import quote_plus
+from urllib.parse import quote, quote_plus
 
 from django.conf import settings
 from django.urls import reverse
@@ -148,9 +148,12 @@ def get_url(self, obj):
             f"{time_utils.to_timestamp(obj.uploaded_on)}"
         )
 
+        response_content_disposition = quote_plus(
+            "attachment; filename=" + quote(obj.filename)
+        )
         url = (
             f"{base:s}{self._get_extension_string(obj)}?"
-            f"response-content-disposition={quote_plus('attachment; filename=' + obj.filename)}"
+            f"response-content-disposition={response_content_disposition}"
         )
 
         if settings.CLOUDFRONT_SIGNED_URLS_ACTIVE:


Test patch:
diff --git a/src/backend/marsha/deposit/tests/api/depositedfiles/test_list.py b/src/backend/marsha/deposit/tests/api/depositedfiles/test_list.py
--- a/src/backend/marsha/deposit/tests/api/depositedfiles/test_list.py
+++ b/src/backend/marsha/deposit/tests/api/depositedfiles/test_list.py
@@ -302,6 +302,9 @@ def test_api_file_depository_list_deposited_files_instructor_signed_urls(self):
         deposited_files = DepositedFileFactory.create_batch(
             3, file_depository=file_depository, uploaded_on=now
         )
+        unicode_deposited_file = DepositedFileFactory(
+            file_depository=file_depository, uploaded_on=now, filename="test’.pdf"
+        )
         jwt_token = InstructorOrAdminLtiTokenFactory(playlist=file_depository.playlist)
 
         now = datetime(2021, 11, 30, tzinfo=baseTimezone.utc)
@@ -328,10 +331,26 @@ def test_api_file_depository_list_deposited_files_instructor_signed_urls(self):
         self.assertEqual(
             response.json(),
             {
-                "count": 3,
+                "count": 4,
                 "next": None,
                 "previous": None,
                 "results": [
+                    {
+                        "author_name": unicode_deposited_file.author_name,
+                        "file_depository_id": str(file_depository.id),
+                        "filename": unicode_deposited_file.filename,
+                        "id": str(unicode_deposited_file.id),
+                        "read": False,
+                        "size": unicode_deposited_file.size,
+                        "upload_state": "pending",
+                        "uploaded_on": "2018-08-08T00:00:00Z",
+                        "url": (
+                            f"https://abc.cloudfront.net/{file_depository.id}/depositedfile/"
+                            f"{unicode_deposited_file.id}/1533686400?response-content-disposition"
+                            "=attachment%3B+filename%3Dtest%25E2%2580%2599.pdf"
+                            f"&{expected_cloudfront_signature}"
+                        ),
+                    },
                     {
                         "author_name": deposited_files[2].author_name,
                         "file_depository_id": str(file_depository.id),