diff --git a/files/usr/share/cinnamon/cinnamon-settings/modules/cs_themes.py b/files/usr/share/cinnamon/cinnamon-settings/modules/cs_themes.py index 2ea4f641cf..0834f33584 100755 --- a/files/usr/share/cinnamon/cinnamon-settings/modules/cs_themes.py +++ b/files/usr/share/cinnamon/cinnamon-settings/modules/cs_themes.py @@ -15,6 +15,7 @@ from pathlib import Path import config +from icon_theme_cache import read_icon_cache ICON_SIZE = 48 @@ -718,13 +719,7 @@ def refresh_chooser(self, chooser, path_suffix, themes, callback): else: read_path = '/usr/share/cinnamon/cinnamon-settings/icons' - icon_paths = {} - with open(read_path, 'r') as cache_file: - for line in cache_file: - theme_name, icon_path = line.strip().split(':') - icon_paths[theme_name] = icon_path - - dump = False + icon_paths, dump = read_icon_cache(read_path) # Collect all themes with their categories and variants categorized_themes = [] diff --git a/files/usr/share/cinnamon/cinnamon-settings/modules/icon_theme_cache.py b/files/usr/share/cinnamon/cinnamon-settings/modules/icon_theme_cache.py new file mode 100644 index 0000000000..1ec977f583 --- /dev/null +++ b/files/usr/share/cinnamon/cinnamon-settings/modules/icon_theme_cache.py @@ -0,0 +1,24 @@ +def read_icon_cache(path): + """Read valid theme icon entries and report whether the cache was damaged.""" + icon_paths = {} + damaged = False + + with open(path, "r", encoding="utf-8", errors="replace") as cache_file: + for line in cache_file: + entry = line.strip() + if "\ufffd" in entry or any(ord(char) < 32 or ord(char) == 127 for char in entry): + damaged = True + continue + + if ":" not in entry: + damaged = True + continue + + theme_name, icon_path = entry.split(":", 1) + if not theme_name or not icon_path: + damaged = True + continue + + icon_paths[theme_name] = icon_path + + return icon_paths, damaged diff --git a/tests/test_icon_theme_cache.py b/tests/test_icon_theme_cache.py new file mode 100644 index 0000000000..848c36028d --- /dev/null +++ b/tests/test_icon_theme_cache.py @@ -0,0 +1,52 @@ +import sys +import tempfile +import unittest +from pathlib import Path + +MODULES = ( + Path(__file__).resolve().parents[1] + / "files/usr/share/cinnamon/cinnamon-settings/modules" +) +sys.path.insert(0, str(MODULES)) + +from icon_theme_cache import read_icon_cache + + +class ReadIconCacheTest(unittest.TestCase): + def test_skips_corrupt_lines_and_preserves_colons_in_paths(self): + with tempfile.TemporaryDirectory() as temp_dir: + cache = Path(temp_dir) / "icons" + cache.write_bytes( + b"valid:icons/valid.png\n" + b"missing-separator\n" + b"\xffinvalid:icons/invalid.png\n" + b"binary\x00theme:icons/binary.png\n" + b"colon:path:with:colons.png\n" + b":missing-name\n" + b"empty-path:\n" + ) + + icon_paths, damaged = read_icon_cache(cache) + + self.assertTrue(damaged) + self.assertEqual( + icon_paths, + { + "valid": "icons/valid.png", + "colon": "path:with:colons.png", + }, + ) + + def test_valid_cache_is_not_marked_damaged(self): + with tempfile.TemporaryDirectory() as temp_dir: + cache = Path(temp_dir) / "icons" + cache.write_text("theme:icons/folder.svg\n", encoding="utf-8") + + icon_paths, damaged = read_icon_cache(cache) + + self.assertFalse(damaged) + self.assertEqual(icon_paths, {"theme": "icons/folder.svg"}) + + +if __name__ == "__main__": + unittest.main()