diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce28a00..d9744c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Status of the `main` branch. Changes prior to the next official version change w at the Yarn-generated SDK. - `SvelteLanguageServer`: Fix diagnostics requests for TypeScript/JavaScript files incorrectly being processed by the Svelte LS instead of the TypeScript LS. + - Improve quoting of arguments in shell executions * Dashboard: - Make list of trusted hosts configurable, fixing host validation introduced in v1.5.2 allowing only diff --git a/src/solidlsp/language_servers/common.py b/src/solidlsp/language_servers/common.py index 16a97411..dc38076d 100644 --- a/src/solidlsp/language_servers/common.py +++ b/src/solidlsp/language_servers/common.py @@ -10,7 +10,7 @@ from dataclasses import dataclass, replace from typing import Any, cast from solidlsp.ls_utils import FileUtils, PlatformUtils -from solidlsp.util.subprocess_util import subprocess_kwargs +from solidlsp.util import subprocess_util log = logging.getLogger(__name__) @@ -101,18 +101,13 @@ class RuntimeDependencyCollection: @staticmethod def _run_command(command: str | list[str], cwd: str) -> None: - kwargs = subprocess_kwargs() + kwargs = subprocess_util.subprocess_kwargs() if not PlatformUtils.get_platform_id().is_windows(): import pwd kwargs["user"] = pwd.getpwuid(os.getuid()).pw_name # type: ignore - is_windows = platform.system() == "Windows" - if not isinstance(command, str) and not is_windows: - # Since we are using the shell, we need to convert the command list to a single string - # on Linux/macOS - command = " ".join(command) - + command = subprocess_util.convert_shell_cmd(command) log.info("Running command %s in '%s'", f"'{command}'" if isinstance(command, str) else command, cwd) completed_process = subprocess.run( diff --git a/src/solidlsp/ls_process.py b/src/solidlsp/ls_process.py index 5ac06c0e..820947af 100644 --- a/src/solidlsp/ls_process.py +++ b/src/solidlsp/ls_process.py @@ -2,7 +2,6 @@ import asyncio import json import logging import os -import platform import socket import subprocess import threading @@ -33,7 +32,7 @@ from solidlsp.lsp_protocol_handler.server import ( make_request, make_response, ) -from solidlsp.util.subprocess_util import quote_arg, subprocess_kwargs, terminate_process_tree_with_kill_fallback +from solidlsp.util import subprocess_util log = logging.getLogger(__name__) @@ -459,14 +458,9 @@ class StdioLanguageServer(LanguageServerInterface): child_proc_env = os.environ.copy() child_proc_env.update(self.process_launch_info.env) - cmd = self.process_launch_info.cmd - is_windows = platform.system() == "Windows" - if not isinstance(cmd, str) and not is_windows: - # Since we are using the shell, we need to convert the command list to a single string - # on Linux/macOS - cmd = " ".join(map(quote_arg, cmd)) + cmd = subprocess_util.convert_shell_cmd(self.process_launch_info.cmd) log.info("Starting language server process via command: %s", self.process_launch_info.cmd) - kwargs = subprocess_kwargs() + kwargs = subprocess_util.subprocess_kwargs() kwargs["start_new_session"] = self.start_independent_lsp_process self.process = subprocess.Popen( cmd, @@ -513,7 +507,9 @@ class StdioLanguageServer(LanguageServerInterface): log.debug(f"Exception during graceful shutdown: {e}") # Ignore errors here, we are proceeding to terminate anyway. # terminate the process - terminate_process_tree_with_kill_fallback(self.process, terminate_timeout=timeout, process_name=f"LS[{self.language.value}]") + subprocess_util.terminate_process_tree_with_kill_fallback( + self.process, terminate_timeout=timeout, process_name=f"LS[{self.language.value}]" + ) finally: self.process = None diff --git a/src/solidlsp/util/subprocess_util.py b/src/solidlsp/util/subprocess_util.py index 25dc2963..9d13773a 100644 --- a/src/solidlsp/util/subprocess_util.py +++ b/src/solidlsp/util/subprocess_util.py @@ -19,19 +19,19 @@ def subprocess_kwargs() -> dict: return kwargs -def quote_arg(arg: str) -> str: +def convert_shell_cmd(cmd: str | list[str]) -> str | list[str]: """ - Quotes a shell argument to prevent interpretation of metacharacters. + Converts a command (specified as a list or string) to a format supported by subprocess calls with shell=True on the current platform. + List format must be converted to string format on POSIX systems, quoting arguments appropriately, + while it can be used as-is on Windows. - Uses :func:`shlex.quote` on POSIX systems for proper escaping of all - shell-special characters. On Windows, wraps arguments containing spaces - in double quotes (Windows shell does not interpret single-quoted strings). + :param cmd: the command to convert, specified as a list of arguments + :return: a suitable representation of the command for subprocess calls on the current platform """ - if platform.system() == "Windows": - if " " not in arg: - return arg - return f'"{arg}"' - return shlex.quote(arg) + if isinstance(cmd, list) and platform.system() != "Windows": + return " ".join(shlex.quote(arg) for arg in cmd) + else: + return cmd def _signal_process_tree(process: subprocess.Popen[bytes], terminate: bool = True) -> None: