-
Notifications
You must be signed in to change notification settings - Fork 13
Simplify the command type #183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 15 commits
add8d84
7db7345
e82cced
6af3586
82ea996
0c1d393
2185416
81bcb03
a0bb171
8257bb3
4f99c98
3ab6771
cdac722
f6551d8
c7994de
4436471
eef0287
f1cbabf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -752,41 +752,39 @@ def __repr__(self) -> str: | |
|
|
||
|
|
||
| class command(FieldType): | ||
| executable: path | None = None | ||
| args: list[str] | None = None | ||
| """The command fieldtype splits a command string into an ``executable`` and its arguments. | ||
|
|
||
| _path_type: type[path] = None | ||
| _posix: bool | ||
| Args: | ||
| value: the string that contains the command and arguments | ||
| path_type: When specified it forces the command to use a specific path type | ||
|
|
||
| def __new__(cls, value: str): | ||
| if cls is not command: | ||
| return super().__new__(cls) | ||
| Example: | ||
|
|
||
| if not isinstance(value, str): | ||
| raise TypeError(f"Expected a value of type 'str' not {type(value)}") | ||
| .. code-block:: text | ||
|
|
||
| 'c:\\windows\\malware.exe /info' -> windows_path('c:\\windows\\malware.exe) ['/info'] | ||
| '/usr/bin/env bash' -> posix_path('/usr/bin/env') ['bash'] | ||
|
|
||
| # pre checking for windows like paths | ||
| # This checks for windows like starts of a path: | ||
| # an '%' for an environment variable | ||
| # r'\\' for a UNC path | ||
| # the strip and check for ":" on the second line is for `<drive_letter>:` | ||
| stripped_value = value.lstrip("\"'") | ||
| windows = value.startswith((r"\\", "%")) or (len(stripped_value) >= 2 and stripped_value[1] == ":") | ||
| # In this situation, the executable path needs to be quoted. | ||
| 'c:\\user\\John Doe\\malware.exe /all /the /things' -> windows_path('c:\\user\\John') | ||
| ['Doe\\malware.exe /all /the /things'] | ||
| """ | ||
|
|
||
| cls = windows_command if windows else posix_command | ||
| return super().__new__(cls) | ||
| __executable: path | ||
| __args: tuple[str, ...] | ||
|
|
||
| def __init__(self, value: str | tuple[str, tuple[str]] | None): | ||
| if value is None: | ||
| return | ||
| __path_type: type[path] | ||
|
|
||
| if isinstance(value, str): | ||
| self.executable, self.args = self._split(value) | ||
| return | ||
| def __init__(self, value: str = "", *, path_type: type[path] | None = None): | ||
| if not isinstance(value, str): | ||
| raise TypeError(f"Expected a value of type 'str' not {type(value)}") | ||
|
|
||
| raw = value.strip() | ||
|
|
||
| executable, self.args = value | ||
| self.executable = self._path_type(executable) | ||
| self.args = list(self.args) | ||
| # Detect the kind of path from value if not specified | ||
| self.__path_type = path_type or type(path(raw.lstrip("\"'"))) | ||
|
|
||
| self.executable, self.args = self._split(raw) | ||
|
|
||
| def __repr__(self) -> str: | ||
| return f"(executable={self.executable!r}, args={self.args})" | ||
|
|
@@ -795,66 +793,78 @@ def __eq__(self, other: object) -> bool: | |
| if isinstance(other, command): | ||
| return self.executable == other.executable and self.args == other.args | ||
| if isinstance(other, str): | ||
| return self._join() == other | ||
| return self.raw == other | ||
| if isinstance(other, (tuple, list)): | ||
| return self.executable == other[0] and self.args == list(other[1:]) | ||
| return self.executable == other[0] and self.args == (*other[1:],) | ||
|
|
||
| return False | ||
|
|
||
| def _split(self, value: str) -> tuple[str, list[str]]: | ||
| executable, *args = shlex.split(value, posix=self._posix) | ||
| executable = executable.strip("'\" ") | ||
| def _split(self, value: str) -> tuple[str, tuple[str, ...]]: | ||
| if not (value): | ||
| return "", () | ||
|
|
||
| return self._path_type(executable), args | ||
| executable, *args = shlex.split(value, posix=self.__path_type is posix_path) | ||
| return executable.strip("'\" "), (*args,) | ||
|
|
||
| def _join(self) -> str: | ||
| return shlex.join([str(self.executable), *self.args]) | ||
|
|
||
| def _pack(self) -> tuple[tuple[str, list], str]: | ||
| command_type = TYPE_WINDOWS if isinstance(self, windows_command) else TYPE_POSIX | ||
| if self.executable: | ||
| _exec, _ = self.executable._pack() | ||
| return ((_exec, self.args), command_type) | ||
| return (None, command_type) | ||
| return self.raw | ||
|
|
||
| @classmethod | ||
| def _unpack(cls, data: tuple[tuple[str, tuple] | None, int]) -> command: | ||
| _value, _type = data | ||
| if _type == TYPE_WINDOWS: | ||
| return windows_command(_value) | ||
|
|
||
| return posix_command(_value) | ||
| def _pack(self) -> tuple[str, int]: | ||
| path_type = TYPE_WINDOWS if self.__path_type is windows_path else TYPE_POSIX | ||
| return self.raw, path_type | ||
|
|
||
| @classmethod | ||
| def from_posix(cls, value: str) -> command: | ||
| return posix_command(value) | ||
| def _unpack(cls, data: tuple[str, int]) -> command: | ||
| raw_str, path_type = data | ||
| if path_type == TYPE_POSIX: | ||
| return command(raw_str, path_type=posix_path) | ||
| if path_type == TYPE_WINDOWS: | ||
| return command(raw_str, path_type=windows_path) | ||
| # default, infer type of path from str | ||
| return command(raw_str) | ||
|
|
||
| @classmethod | ||
| def from_windows(cls, value: str) -> command: | ||
| return windows_command(value) | ||
| @property | ||
| def executable(self) -> path: | ||
| return self.__executable | ||
|
|
||
| @property | ||
| def args(self) -> tuple[str, ...]: | ||
| return self.__args | ||
|
|
||
| class posix_command(command): | ||
| _posix = True | ||
| _path_type = posix_path | ||
| @executable.setter | ||
| def executable(self, val: str | path | None) -> None: | ||
| self.__executable = self.__path_type(val) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. with the latest merged PR #200 we should be able to directly initialize path with the value to determine the correct path type. Then you can als get rid of
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean removing the whole If you just mean this specific case, this could still be an issue because the whole type can change from windows to posix path and vise versa. Tho I already have an idea on how to fix that specific issue.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah wait I thought __path_type was a function to determine the path type.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, no. It is a |
||
|
|
||
| @args.setter | ||
| def args(self, val: str | tuple[str, ...] | list[str] | None) -> None: | ||
| if val is None: | ||
| self.__args = () | ||
| elif isinstance(val, (tuple, list)): | ||
| if val and self.__path_type is windows_path: | ||
| val = (" ".join(val),) | ||
| self.__args = (*val,) | ||
| else: | ||
| self.__args = tuple(shlex.split(val, posix=self.__path_type is posix_path)) | ||
|
|
||
| class windows_command(command): | ||
| _posix = False | ||
| _path_type = windows_path | ||
| @property | ||
| def raw(self) -> str: | ||
| exe = str(self.executable) | ||
|
|
||
| def _split(self, value: str) -> tuple[str, list[str]]: | ||
| executable, args = super()._split(value) | ||
| if args: | ||
| args = [" ".join(args)] | ||
| if " " in exe: | ||
| exe = shlex.quote(exe) | ||
|
|
||
| return executable, args | ||
| result = [exe] | ||
| if self.__path_type is posix_path: | ||
| result.extend(shlex.quote(part) if " " in part else part for part in self.args) | ||
| else: | ||
| result.extend(self.args) | ||
|
|
||
| def _join(self) -> str: | ||
| arg = f" {self.args[0]}" if self.args else "" | ||
| executable_str = str(self.executable) | ||
| return " ".join(result) | ||
|
|
||
| if " " in executable_str: | ||
| return f"'{executable_str}'{arg}" | ||
| @classmethod | ||
| def from_posix(cls, value: str) -> command: | ||
| return command(value, path_type=posix_path) | ||
|
|
||
| return f"{executable_str}{arg}" | ||
| @classmethod | ||
| def from_windows(cls, value: str) -> command: | ||
| return command(value, path_type=windows_path) | ||
Uh oh!
There was an error while loading. Please reload this page.