Daniel Brötzmann pushed to branch sanitize-filenames at gajim / gajim
Commits: f9a3eaad by wurstsalat at 2022-08-26T17:38:27+02:00 fix: Chatstates: Don’t sent chatstate delayed to ourself
- - - - - cb93756a by wurstsalat at 2022-08-26T19:55:04+02:00 fix: Preview: Sanitize filename from disallowed chars
Fixes #11105, #10752
- - - - -
4 changed files:
- gajim/common/helpers.py - gajim/common/modules/chatstates.py - gajim/common/preview_helpers.py - + test/no_gui/test_sanitize_filename.py
Changes:
===================================== gajim/common/helpers.py ===================================== @@ -37,7 +37,6 @@ import re import os import subprocess -import base64 import hashlib import shlex import socket @@ -58,10 +57,10 @@ from datetime import timedelta from urllib.parse import unquote from urllib.parse import urlparse -from encodings.punycode import punycode_encode from functools import wraps from pathlib import Path from packaging.version import Version as V +import unicodedata
from nbxmpp.namespaces import Namespace from nbxmpp.const import Role @@ -358,26 +357,35 @@ def get_file_path_from_dnd_dropped_uri(uri: str) -> str:
def sanitize_filename(filename: str) -> str: ''' - Make sure the filename we will write does contain only acceptable and latin - characters, and is not too long (in that case hash it) + Sanitize filename of elements not allowed on Windows + https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file ''' - # 48 is the limit - if len(filename) > 48: - hash_ = hashlib.md5(filename.encode('utf-8')) - filename = base64.b64encode(hash_.digest()).decode('utf-8') - - # make it latin chars only - filename = punycode_encode(filename).decode('utf-8') - filename = filename.replace('/', '_') - if os.name == 'nt': - filename = filename.replace('?', '_')\ - .replace(':', '_')\ - .replace('\', '_')\ - .replace('"', "'")\ - .replace('|', '_')\ - .replace('*', '_')\ - .replace('<', '_')\ - .replace('>', '_') + blacklist = ['\', '/', ':', '*', '?', '"', '<', '>', '|', '\0'] + reserved_filenames = [ + 'CON', 'PRN', 'AUX', 'NUL', + 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', + 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9', + ] + filename = ''.join(char for char in filename if char not in blacklist) + + filename = ''.join(char for char in filename if 31 < ord(char)) + + filename = unicodedata.normalize('NFKD', filename) + filename = filename.rstrip('. ') + filename = filename.strip() + + if all([char == '.' for char in filename]): + filename = f'__{filename}' + if filename in reserved_filenames: + filename = f'__{filename}' + if len(filename) == 0: + filename = '__' + if len(filename) > 50: + # Many Filesystems have a limit on filename length + # Most have 255, some encrypted ones only 143 + # We add around 50 chars for the hash, + # so the filename should not exceed 50 + filename = filename[:50]
return filename
===================================== gajim/common/modules/chatstates.py ===================================== @@ -253,6 +253,11 @@ def set_chatstate_delayed(self, # Used when we go from Composing -> Active after deleting all text # from the Textview. We delay the Active state because maybe the # User starts writing again. + + # Don’t send chatstates to ourself + if self._con.get_own_jid().bare_match(contact.jid): + return + self.remove_delay_timeout(contact) self._delay_timeout_ids[contact.jid] = GLib.timeout_add_seconds( 2, self.set_chatstate, contact, state)
===================================== gajim/common/preview_helpers.py ===================================== @@ -45,6 +45,7 @@ from cryptography.hazmat.primitives.ciphers import algorithms from cryptography.hazmat.primitives.ciphers.modes import GCM
+from gajim.common.helpers import sanitize_filename from gajim.common.i18n import _
log = logging.getLogger('gajim.c.preview_helpers') @@ -308,12 +309,7 @@ def get_image_paths(uri: str, web_stem = path.stem extension = path.suffix
- if len(web_stem) > 90: - # Many Filesystems have a limit on filename length - # Most have 255, some encrypted ones only 143 - # We add around 50 chars for the hash, - # so the filename should not exceed 90 - web_stem = web_stem[:90] + web_stem = sanitize_filename(web_stem)
name_hash = hashlib.sha1(str(uri).encode()).hexdigest()
===================================== test/no_gui/test_sanitize_filename.py ===================================== @@ -0,0 +1,41 @@ +import unittest + +from gajim.common.helpers import sanitize_filename + + +class SanitizeTest(unittest.TestCase): + '''Tests for the sanitize_filename function.''' + + def test_invalid_chars(self): + '''Make sure invalid characters are removed in filenames.''' + self.assertEqual(sanitize_filename('A/B/C'), 'ABC') + self.assertEqual(sanitize_filename('A*C.d'), 'AC.d') + self.assertEqual(sanitize_filename('A?C.d'), 'AC.d') + + def test_invalid_suffix(self): + '''Dots are not allowed at the end.''' + self.assertEqual(sanitize_filename('def.'), 'def') + self.assertEqual(sanitize_filename('def.ghi'), 'def.ghi') + self.assertTrue(sanitize_filename('X' * 1000 + '.').endswith('X')) + + def test_reserved_words(self): + '''Make sure reserved Windows words are prefixed.''' + self.assertEqual(sanitize_filename('NUL'), '__NUL') + self.assertEqual(sanitize_filename('..'), '__') + + def test_long_names(self): + '''Make sure long names are truncated.''' + self.assertEqual(len(sanitize_filename('X' * 300)), 50) + self.assertEqual(len(sanitize_filename( + '.'.join(['X' * 100, 'X' * 100, 'X' * 100]))), 50) + self.assertEqual(len(sanitize_filename( + '.'.join(['X' * 300, 'X' * 300, 'X' * 300]))), 50) + self.assertEqual(len(sanitize_filename('.' * 300 + '.txt')), 50) + + def test_unicode_normalization(self): + '''Names should be NFKD normalized.''' + self.assertEqual(sanitize_filename('ў'), chr(1091) + chr(774)) + + +if __name__ == '__main__': + unittest.main()
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/9c89b3ef64e0189df0ead1d855ca45f6...