Repository: sissbruecker/linkding

Issue:
Failed to create HTML snapshot if url too long
The version I found the problem with is 1.27.0 plus 

I found the problem when creating a snapshot for the webpage when the URL is too long. so I try to run the app in my local.

For example, if the URL that will be snapshotted is

```
https://github.com/sissbruecker/linkding?url=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
```

the error will be

```sh
INFO 2024-04-03 14:40:04,816 tasks: Create HTML snapshot for bookmark. url=https://github.com/sissbruecker/linkding?url=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
ENAMETOOLONG: name too long, open '/workspaces/linkding/data/assets/snapshot_2024-04-03_144004_https___github.com_sissbruecker_linkding_url_000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.html.gz.tmp' URL: https://github.com/sissbruecker/linkding?url=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Stack: Error: ENAMETOOLONG: name too long, open '/workspaces/linkding/data/assets/snapshot_2024-04-03_144004_https___github.com_sissbruecker_linkding_url_000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.html.gz.tmp'
ERROR 2024-04-03 14:40:14,904 tasks: Failed to create HTML snapshot for bookmark. url=https://github.com/sissbruecker/linkding?url=000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
Traceback (most recent call last):
  File "/workspaces/linkding/bookmarks/services/tasks.py", line 248, in _create_html_snapshot_task
    singlefile.create_snapshot(asset.bookmark.url, filepath)
  File "/workspaces/linkding/bookmarks/services/singlefile.py", line 25, in create_snapshot
    raise SingeFileError("Failed to create snapshot")
bookmarks.services.singlefile.SingeFileError: Failed to create snapshot
```

From the log above, the cause is the length of the snapshot file's name exceeds the OS limit.



Discussion:
Looks like the URL should be truncated to some reasonable length here:
https://github.com/sissbruecker/linkding/blob/bb6c5ca29e3b66a70c2ff1751ea6183c7011d4ae/bookmarks/services/tasks.py#L228-L230



Patch:
diff --git a/bookmarks/services/tasks.py b/bookmarks/services/tasks.py
index 2f1caade..6d24ba28 100644
--- a/bookmarks/services/tasks.py
+++ b/bookmarks/services/tasks.py
@@ -239,6 +239,9 @@ def create_html_snapshot(bookmark: Bookmark):
     asset.save()
 
 
+MAX_SNAPSHOT_FILENAME_LENGTH = 192
+
+
 def _generate_snapshot_filename(asset: BookmarkAsset) -> str:
     def sanitize_char(char):
         if char.isalnum() or char in ("-", "_", "."):
@@ -249,6 +252,13 @@ def sanitize_char(char):
     formatted_datetime = asset.date_created.strftime("%Y-%m-%d_%H%M%S")
     sanitized_url = "".join(sanitize_char(char) for char in asset.bookmark.url)
 
+    # Calculate the length of the non-URL parts of the filename
+    non_url_length = len(f"{asset.asset_type}{formatted_datetime}__.html.gz")
+    # Calculate the maximum length for the URL part
+    max_url_length = MAX_SNAPSHOT_FILENAME_LENGTH - non_url_length
+    # Truncate the URL if necessary
+    sanitized_url = sanitized_url[:max_url_length]
+
     return f"{asset.asset_type}_{formatted_datetime}_{sanitized_url}.html.gz"
 
 


Test patch:
diff --git a/bookmarks/tests/test_bookmarks_tasks.py b/bookmarks/tests/test_bookmarks_tasks.py
index e36183ed..5040caaf 100644
--- a/bookmarks/tests/test_bookmarks_tasks.py
+++ b/bookmarks/tests/test_bookmarks_tasks.py
@@ -556,6 +556,21 @@ def test_create_html_snapshot_should_update_file_info(self):
             self.assertEqual(asset.file, expected_filename)
             self.assertTrue(asset.gzip)
 
+    @override_settings(LD_ENABLE_SNAPSHOTS=True)
+    def test_create_html_snapshot_truncate_filename(self):
+        # Create a bookmark with a very long URL
+        long_url = "http://" + "a" * 300 + ".com"
+        bookmark = self.setup_bookmark(url=long_url)
+
+        tasks.create_html_snapshot(bookmark)
+        BookmarkAsset.objects.get(bookmark=bookmark)
+
+        # Run periodic task to process the snapshot
+        tasks._schedule_html_snapshots_task()
+
+        asset = BookmarkAsset.objects.get(bookmark=bookmark)
+        self.assertEqual(len(asset.file), 192)
+
     @override_settings(LD_ENABLE_SNAPSHOTS=True)
     def test_create_html_snapshot_should_handle_error(self):
         bookmark = self.setup_bookmark(url="https://example.com")