From a5e396eead2954b06d434c74089f018323b88d22 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Thu, 22 Jan 2026 18:03:04 +0600 Subject: [PATCH] (gui): fix security and stability issues - add null-safety for path variables (USER_WORK_PATH, DATA_PATH) - fix potential crash in handle_process_output with uninitialized process - improve env vars and executable path handling - enhance .desktop files and menu processing --- winehelper_gui.py | 118 +++++++++++++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 43 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 5b96682..02e3b08 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -1310,7 +1310,8 @@ class CreatePrefixDialog(QDialog): ErrorReporter.show_error(self, "Ошибка", error_msg) return - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) if os.path.exists(prefix_path): error_msg = f"Префикс с именем '{prefix_name}' уже существует." ErrorReporter.show_error(self, "Ошибка", error_msg) @@ -1589,7 +1590,7 @@ class WineHelperGUI(QMainWindow): """ # Основные переменные - self.winehelper_path: str = Var.RUN_SCRIPT + self.winehelper_path: str = Var.RUN_SCRIPT or "" self.process: Optional[QProcess] = None self.current_script: Optional[str] = None self.install_process: Optional[QProcess] = None @@ -2051,7 +2052,8 @@ class WineHelperGUI(QMainWindow): """ button_index = 0 for script in scripts_list: - script_path = os.path.join(Var.DATA_PATH, script_folder, script) + data_path = Var.DATA_PATH or "" + script_path = os.path.join(data_path, script_folder, script) prog_name = ScriptParser.extract_prog_name_from_script(script_path) # Создаем кнопку, только если для скрипта указано имя программы @@ -2059,7 +2061,8 @@ class WineHelperGUI(QMainWindow): continue icon_names = ScriptParser.extract_icons_from_script(script_path) - icon_paths = [os.path.join(Var.DATA_PATH, "image", f"{name}.png") for name in icon_names] + data_path = Var.DATA_PATH or "" + icon_paths = [os.path.join(data_path, "image", f"{name}.png") for name in icon_names] # Выбираем стиль в зависимости от папки if script_folder == 'testinstall': @@ -2486,7 +2489,8 @@ class WineHelperGUI(QMainWindow): def _get_current_prefixes(self): """Возвращает множество имен существующих префиксов.""" - prefixes_root_path = os.path.join(Var.USER_WORK_PATH, "prefixes") + user_work_path = Var.USER_WORK_PATH or "" + prefixes_root_path = os.path.join(user_work_path, "prefixes") if not os.path.isdir(prefixes_root_path): return set() try: @@ -2506,7 +2510,8 @@ class WineHelperGUI(QMainWindow): def _load_created_prefixes(self): """Загружает и обновляет список созданных префиксов в выпадающем списке.""" - prefixes_root_path = os.path.join(Var.USER_WORK_PATH, "prefixes") + user_work_path = Var.USER_WORK_PATH or "" + prefixes_root_path = os.path.join(user_work_path, "prefixes") has_prefixes_dir = os.path.isdir(prefixes_root_path) if not has_prefixes_dir: return @@ -2667,7 +2672,8 @@ class WineHelperGUI(QMainWindow): if not prefix_name: return - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) if os.path.isdir(prefix_path): try: subprocess.Popen(['xdg-open', prefix_path]) @@ -2706,7 +2712,8 @@ class WineHelperGUI(QMainWindow): self.fsync_button.setChecked(False) return - last_conf_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name, "last.conf") + user_work_path = Var.USER_WORK_PATH or "" + last_conf_path = os.path.join(user_work_path, "prefixes", prefix_name, "last.conf") if not os.path.exists(last_conf_path): self.prefix_info_display.setHtml(f"

Файл конфигурации last.conf не найден для префикса '{prefix_name}'.

") @@ -2751,7 +2758,8 @@ class WineHelperGUI(QMainWindow): self.fsync_button.blockSignals(False) # --- Чтение и отображение установленных компонентов Winetricks --- - winetricks_log_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name, "winetricks.log") + user_work_path = Var.USER_WORK_PATH or "" + winetricks_log_path = os.path.join(user_work_path, "prefixes", prefix_name, "winetricks.log") installed_verbs = [] if os.path.exists(winetricks_log_path): try: @@ -2872,7 +2880,8 @@ class WineHelperGUI(QMainWindow): if not prefix_name: return None - last_conf_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name, "last.conf") + user_work_path = Var.USER_WORK_PATH or "" + last_conf_path = os.path.join(user_work_path, "prefixes", prefix_name, "last.conf") if not os.path.exists(last_conf_path): return None @@ -2953,7 +2962,8 @@ class WineHelperGUI(QMainWindow): return # Определяем архитектуру префикса - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) last_conf_path = os.path.join(prefix_path, "last.conf") architecture = "win64" # По умолчанию if os.path.exists(last_conf_path): @@ -3009,7 +3019,8 @@ class WineHelperGUI(QMainWindow): ) env = QProcessEnvironment.systemEnvironment() - env.insert("WINEPREFIX", os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)) + user_work_path = Var.USER_WORK_PATH or "" + env.insert("WINEPREFIX", os.path.join(user_work_path, "prefixes", prefix_name)) self.command_process.setProcessEnvironment(env) args = ["change-wine", new_version] @@ -3019,7 +3030,8 @@ class WineHelperGUI(QMainWindow): def run_component_install_command(self, prefix_name, command, version): """Выполняет команду установки компонента (DXVK/VKD3D) через winehelper.""" - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) self.command_dialog = QDialog(self) self.command_dialog.setWindowTitle(f"Выполнение: {command} {version}") @@ -3106,7 +3118,8 @@ class WineHelperGUI(QMainWindow): def run_update_associations_command(self, prefix_name, new_associations): """Выполняет команду обновления ассоциаций файлов.""" # --- Прямое редактирование last.conf, чтобы обойти перезапись переменных в winehelper --- - last_conf_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name, "last.conf") + user_work_path = Var.USER_WORK_PATH or "" + last_conf_path = os.path.join(user_work_path, "prefixes", prefix_name, "last.conf") if not os.path.exists(last_conf_path): QMessageBox.critical(self, "Ошибка", f"Файл конфигурации last.conf не найден для префикса '{prefix_name}'.") return @@ -3139,7 +3152,8 @@ class WineHelperGUI(QMainWindow): QMessageBox.critical(self, "Ошибка записи", f"Не удалось обновить файл last.conf: {str(e)}") return - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) def on_finished(exit_code, exit_status): self._handle_component_install_finished(prefix_name, exit_code, exit_status) @@ -3167,7 +3181,8 @@ class WineHelperGUI(QMainWindow): QMessageBox.warning(self, "Ошибка", "Сначала выберите префикс.") return - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) drive_c_path = os.path.join(prefix_path, "drive_c") if not os.path.isdir(drive_c_path): @@ -3402,7 +3417,8 @@ class WineHelperGUI(QMainWindow): if self.created_prefix_selector.count() > 0: self.created_prefix_selector.setCurrentIndex(-1) - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) self.command_dialog = QDialog(self) self.command_dialog.setWindowTitle(f"Создание префикса: {prefix_name}") @@ -3486,12 +3502,13 @@ class WineHelperGUI(QMainWindow): widget.deleteLater() self.installed_buttons.clear() - if not os.path.exists(Var.USER_WORK_PATH): - os.makedirs(Var.USER_WORK_PATH, exist_ok=True) + user_work_path = Var.USER_WORK_PATH or "" + if not os.path.exists(user_work_path): + os.makedirs(user_work_path, exist_ok=True) return desktop_files = [] - for entry in os.scandir(Var.USER_WORK_PATH): + for entry in os.scandir(user_work_path): if entry.is_file() and entry.name.endswith('.desktop'): desktop_files.append(entry.path) @@ -3829,13 +3846,14 @@ class WineHelperGUI(QMainWindow): ErrorReporter.show_error(self, "Менеджер Winetricks", error_msg) return - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) if not os.path.isdir(prefix_path): error_msg = f"Каталог префикса не найден:\n{prefix_path}" ErrorReporter.show_error(self, "Ошибка", error_msg) return - winetricks_path = Var.WH_WINETRICKS + winetricks_path = Var.WH_WINETRICKS or "" wine_executable = self._get_wine_executable_for_prefix(prefix_name) dialog = WinetricksManagerDialog(prefix_path, winetricks_path, self, wine_executable=wine_executable) dialog.installation_complete.connect(lambda: self.update_prefix_info_display(prefix_name)) @@ -3843,7 +3861,8 @@ class WineHelperGUI(QMainWindow): def _get_wine_executable_for_prefix(self, prefix_name): """Определяет и возвращает путь к исполняемому файлу wine для указанного префикса.""" - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) last_conf_path = os.path.join(prefix_path, "last.conf") wh_wine_use = None @@ -3866,7 +3885,8 @@ class WineHelperGUI(QMainWindow): print(f"Предупреждение: не удалось прочитать или обработать {last_conf_path}: {e}") if wh_wine_use and not wh_wine_use.startswith('system'): - local_wine_path = os.path.join(Var.USER_WORK_PATH, "dist", wh_wine_use, "bin", "wine") + user_work_path = Var.USER_WORK_PATH or "" + local_wine_path = os.path.join(user_work_path, "dist", wh_wine_use, "bin", "wine") if os.path.exists(local_wine_path): return local_wine_path QMessageBox.warning(self, "Предупреждение", @@ -3880,7 +3900,8 @@ class WineHelperGUI(QMainWindow): if not prefix_name: return - last_conf_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name, "last.conf") + user_work_path = Var.USER_WORK_PATH or "" + last_conf_path = os.path.join(user_work_path, "prefixes", prefix_name, "last.conf") if not os.path.exists(last_conf_path): error_msg = f"Файл last.conf не найден для префикса '{prefix_name}'." ErrorReporter.show_error(self, "Ошибка", error_msg) @@ -3928,7 +3949,8 @@ class WineHelperGUI(QMainWindow): ErrorReporter.show_error(self, "Ошибка", error_msg) return - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) if not os.path.isdir(prefix_path): error_msg = f"Каталог префикса не найден:\n{prefix_path}" ErrorReporter.show_error(self, "Ошибка", error_msg) @@ -3993,7 +4015,8 @@ class WineHelperGUI(QMainWindow): process.kill() return - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) wine_executable = self._get_wine_executable_for_prefix(prefix_name) wineserver_path = os.path.join(os.path.dirname(wine_executable), "wineserver") @@ -4256,7 +4279,8 @@ class WineHelperGUI(QMainWindow): if msg_box.clickedButton() == yes_button: try: # Полный путь к префиксу - prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) + user_work_path = Var.USER_WORK_PATH or "" + prefix_path = os.path.join(user_work_path, "prefixes", prefix_name) # 1. Сначала собираем ВСЕ .desktop файлы, связанные с этим префиксом all_desktop_files = set() @@ -4265,7 +4289,7 @@ class WineHelperGUI(QMainWindow): all_desktop_files.add(self.current_selected_app['desktop_path']) desktop_locations = [ - Var.USER_WORK_PATH, + user_work_path, os.path.join(os.path.expanduser("~"), ".local/share/applications/WineHelper"), os.path.join(os.path.expanduser("~"), "Desktop"), os.path.join(os.path.expanduser("~"), "Рабочий стол"), @@ -4324,16 +4348,16 @@ class WineHelperGUI(QMainWindow): ".local/share/desktop-directories/WineHelper.directory"), os.path.join(os.path.expanduser("~"), ".config/menus/applications-merged/WineHelper.menu") ] - for f in menu_files: - if os.path.exists(f): + for menu_file in menu_files: + if os.path.exists(menu_file): try: - os.remove(f) + os.remove(menu_file) except FileNotFoundError: - print(f"Файл меню не найден для удаления: {f}") + print(f"Файл меню не найден для удаления: {menu_file}") except PermissionError: - print(f"Нет доступа к файлу меню для удаления: {f}") + print(f"Нет доступа к файлу меню для удаления: {menu_file}") except Exception as e: - print(f"Ошибка удаления файла меню {f}: {str(e)}") + print(f"Ошибка удаления файла меню {menu_file}: {str(e)}") except FileNotFoundError: print(f"Директория меню не найдена для удаления: {menu_dir}") except PermissionError: @@ -4418,13 +4442,16 @@ class WineHelperGUI(QMainWindow): # Определяем виджеты и действия в зависимости от типа скрипта if script_name in self.autoinstall_scripts: - script_path = os.path.join(Var.DATA_PATH, "autoinstall", script_name) + data_path = Var.DATA_PATH or "" + script_path = os.path.join(data_path, "autoinstall", script_name) tab_type = 'auto' if not os.path.exists(script_path): # Проверяем в testinstall, если не нашли в autoinstall - script_path = os.path.join(Var.DATA_PATH, "testinstall", script_name) + data_path = Var.DATA_PATH or "" + script_path = os.path.join(data_path, "testinstall", script_name) self.manual_install_path_widget.setVisible(False) else: - script_path = os.path.join(Var.DATA_PATH, "manualinstall", script_name) + data_path = Var.DATA_PATH or "" + script_path = os.path.join(data_path, "manualinstall", script_name) tab_type = 'manual' self.manual_install_path_widget.setVisible(True) @@ -4454,7 +4481,8 @@ class WineHelperGUI(QMainWindow): if icon_names: # Для заголовка используем первую иконку из списка - icon_path = os.path.join(Var.DATA_PATH, "image", f"{icon_names[0]}.png") + data_path = Var.DATA_PATH or "" + icon_path = os.path.join(data_path, "image", f"{icon_names[0]}.png") if os.path.exists(icon_path): self.script_title.setPixmap(QPixmap(icon_path).scaled(64, 64, Qt.KeepAspectRatio)) else: @@ -4592,11 +4620,13 @@ class WineHelperGUI(QMainWindow): self._reset_log_state() # Сбрасываем состояние для обработки лога winehelper_path = self.winehelper_path - script_path = os.path.join(Var.DATA_PATH, - "autoinstall" if os.path.exists(os.path.join(Var.DATA_PATH, "autoinstall", self.current_script)) - else "testinstall" if os.path.exists(os.path.join(Var.DATA_PATH, "testinstall", self.current_script)) + data_path = Var.DATA_PATH or "" + current_script = self.current_script or "" + script_path = os.path.join(data_path, + "autoinstall" if os.path.exists(os.path.join(data_path, "autoinstall", current_script)) + else "testinstall" if os.path.exists(os.path.join(data_path, "testinstall", current_script)) else "manualinstall", - self.current_script) + current_script) if not os.path.exists(winehelper_path): error_msg = f"winehelper не найден по пути:\n{winehelper_path}" @@ -4769,6 +4799,8 @@ class WineHelperGUI(QMainWindow): def handle_process_output(self): """Обрабатывает вывод процесса, корректно отображая однострочный прогресс.""" + if self.install_process is None: + return new_data = self.install_process.readAllStandardOutput().data().decode('utf-8', errors='ignore') self.output_buffer += new_data