IncludeCPP 4.6.0__tar.gz → 4.6.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/PKG-INFO +1 -1
- {includecpp-4.6.0 → includecpp-4.6.4}/PKG-INFO +1 -1
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/__init__.py +1 -1
- includecpp-4.6.4/includecpp/core/cssl/cpp/build/api.pyd +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_builtins.py +171 -49
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_parser.py +204 -8
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_runtime.py +199 -15
- includecpp-4.6.4/includecpp/core/cssl/cssl_syntax.py +661 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_types.py +1225 -346
- {includecpp-4.6.0 → includecpp-4.6.4}/pyproject.toml +1 -1
- includecpp-4.6.0/includecpp/core/cssl/cpp/build/api.pyd +0 -0
- includecpp-4.6.0/includecpp/core/cssl/cssl_syntax.py +0 -589
- {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/SOURCES.txt +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/dependency_links.txt +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/entry_points.txt +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/requires.txt +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/IncludeCPP.egg-info/top_level.txt +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/LICENSE +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/MANIFEST.in +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/README.md +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/CHANGELOG.md +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/DOCUMENTATION.md +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/__init__.pyi +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/__main__.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/cli/__init__.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/cli/commands.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/cli/config_parser.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/__init__.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/ai_integration.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/build_manager.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cpp_api.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cpp_api.pyi +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cpp_api_extensions.pyi +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cppy_converter.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/CSSL_DOCUMENTATION.md +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/CSSL_DOCUMENTATION_NEW.md +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/__init__.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/build/cssl_core.pyi +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/build/libgcc_s_seh-1.dll +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/build/libstdc++-6.dll +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/build/libwinpthread-1.dll +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/cssl_core.cp +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cpp/cssl_lexer.hpp +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_builtins.pyi +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_compiler.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_events.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_languages.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_modules.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl/cssl_optimizer.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl_bridge.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/cssl_bridge.pyi +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/error_catalog.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/error_formatter.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/exceptions.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/path_discovery.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/project_ui.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/core/settings_ui.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/generator/__init__.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/generator/parser.cpp +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/generator/parser.h +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/generator/type_resolver.cpp +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/generator/type_resolver.h +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/py.typed +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/templates/cpp.proj.template +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/__init__.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/__init__.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/extension.js +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/images/cssl.png +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/images/cssl_pl.png +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/language-configuration.json +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/package.json +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/snippets/cssl.snippets.json +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/includecpp/vscode/cssl/syntaxes/cssl.tmLanguage.json +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/requirements.txt +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/setup.cfg +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/setup.py +0 -0
- {includecpp-4.6.0 → includecpp-4.6.4}/tests/test_multilang.py +0 -0
|
Binary file
|
|
@@ -1017,18 +1017,53 @@ class CSSLBuiltins:
|
|
|
1017
1017
|
|
|
1018
1018
|
# ============= File I/O Functions =============
|
|
1019
1019
|
|
|
1020
|
+
# v4.7.1: Path validation helper to prevent directory traversal attacks
|
|
1021
|
+
def _validate_path(self, path: str, allow_write: bool = False) -> str:
|
|
1022
|
+
"""Validate file path for security.
|
|
1023
|
+
|
|
1024
|
+
v4.7.1: Prevents directory traversal attacks by checking for '..' sequences
|
|
1025
|
+
and ensuring paths are within reasonable bounds.
|
|
1026
|
+
|
|
1027
|
+
Args:
|
|
1028
|
+
path: The file path to validate
|
|
1029
|
+
allow_write: If True, allows write operations
|
|
1030
|
+
|
|
1031
|
+
Returns:
|
|
1032
|
+
The normalized absolute path
|
|
1033
|
+
|
|
1034
|
+
Raises:
|
|
1035
|
+
CSSLBuiltinError: If path is invalid or attempts traversal
|
|
1036
|
+
"""
|
|
1037
|
+
if not isinstance(path, str) or not path:
|
|
1038
|
+
raise CSSLBuiltinError("Invalid path: path must be a non-empty string")
|
|
1039
|
+
|
|
1040
|
+
# Normalize the path
|
|
1041
|
+
normalized = os.path.normpath(path)
|
|
1042
|
+
|
|
1043
|
+
# Check for directory traversal attempts
|
|
1044
|
+
if '..' in normalized:
|
|
1045
|
+
raise CSSLBuiltinError("Directory traversal not allowed in path")
|
|
1046
|
+
|
|
1047
|
+
return normalized
|
|
1048
|
+
|
|
1020
1049
|
def builtin_read(self, path: str, encoding: str = 'utf-8') -> str:
|
|
1021
1050
|
"""Read entire file content.
|
|
1022
1051
|
Usage: read('/path/to/file.txt')
|
|
1052
|
+
v4.7.1: Added path validation
|
|
1023
1053
|
"""
|
|
1024
|
-
|
|
1054
|
+
validated_path = self._validate_path(path)
|
|
1055
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
1025
1056
|
return f.read()
|
|
1026
1057
|
|
|
1027
1058
|
def builtin_readline(self, line: int, path: str, encoding: str = 'utf-8') -> str:
|
|
1028
1059
|
"""Read specific line from file (1-indexed).
|
|
1029
1060
|
Usage: readline(5, '/path/to/file.txt') -> returns line 5
|
|
1061
|
+
v4.7.1: Added path validation and line number validation
|
|
1030
1062
|
"""
|
|
1031
|
-
|
|
1063
|
+
if not isinstance(line, int) or line < 1:
|
|
1064
|
+
raise CSSLBuiltinError("Line number must be a positive integer")
|
|
1065
|
+
validated_path = self._validate_path(path)
|
|
1066
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
1032
1067
|
for i, file_line in enumerate(f, 1):
|
|
1033
1068
|
if i == line:
|
|
1034
1069
|
return file_line.rstrip('\n\r')
|
|
@@ -1037,18 +1072,25 @@ class CSSLBuiltins:
|
|
|
1037
1072
|
def builtin_write(self, path: str, content: str, encoding: str = 'utf-8') -> int:
|
|
1038
1073
|
"""Write content to file, returns chars written.
|
|
1039
1074
|
Usage: write('/path/to/file.txt', 'Hello World')
|
|
1075
|
+
v4.7.1: Added path validation
|
|
1040
1076
|
"""
|
|
1041
|
-
|
|
1042
|
-
|
|
1077
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1078
|
+
with open(validated_path, 'w', encoding=encoding) as f:
|
|
1079
|
+
return f.write(str(content) if content is not None else '')
|
|
1043
1080
|
|
|
1044
1081
|
def builtin_writeline(self, line: int, content: str, path: str, encoding: str = 'utf-8') -> bool:
|
|
1045
1082
|
"""Write/replace specific line in file (1-indexed).
|
|
1046
1083
|
Usage: writeline(5, 'New content', '/path/to/file.txt')
|
|
1084
|
+
v4.7.1: Added path and line number validation
|
|
1047
1085
|
"""
|
|
1086
|
+
if not isinstance(line, int) or line < 1:
|
|
1087
|
+
raise CSSLBuiltinError("Line number must be a positive integer")
|
|
1088
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1089
|
+
|
|
1048
1090
|
# Read all lines
|
|
1049
1091
|
lines = []
|
|
1050
|
-
if os.path.exists(
|
|
1051
|
-
with open(
|
|
1092
|
+
if os.path.exists(validated_path):
|
|
1093
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
1052
1094
|
lines = f.readlines()
|
|
1053
1095
|
|
|
1054
1096
|
# Ensure we have enough lines
|
|
@@ -1056,67 +1098,81 @@ class CSSLBuiltins:
|
|
|
1056
1098
|
lines.append('\n')
|
|
1057
1099
|
|
|
1058
1100
|
# Replace the specific line (1-indexed)
|
|
1059
|
-
if not
|
|
1060
|
-
|
|
1061
|
-
|
|
1101
|
+
content_str = str(content) if content is not None else ''
|
|
1102
|
+
if not content_str.endswith('\n'):
|
|
1103
|
+
content_str = content_str + '\n'
|
|
1104
|
+
lines[line - 1] = content_str
|
|
1062
1105
|
|
|
1063
1106
|
# Write back
|
|
1064
|
-
with open(
|
|
1107
|
+
with open(validated_path, 'w', encoding=encoding) as f:
|
|
1065
1108
|
f.writelines(lines)
|
|
1066
1109
|
return True
|
|
1067
1110
|
|
|
1068
1111
|
def builtin_readfile(self, path: str, encoding: str = 'utf-8') -> str:
|
|
1069
|
-
"""Read entire file content"""
|
|
1070
|
-
|
|
1112
|
+
"""Read entire file content. v4.7.1: Added path validation"""
|
|
1113
|
+
validated_path = self._validate_path(path)
|
|
1114
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
1071
1115
|
return f.read()
|
|
1072
1116
|
|
|
1073
1117
|
def builtin_writefile(self, path: str, content: str, encoding: str = 'utf-8') -> int:
|
|
1074
|
-
"""Write content to file, returns bytes written"""
|
|
1075
|
-
|
|
1076
|
-
|
|
1118
|
+
"""Write content to file, returns bytes written. v4.7.1: Added path validation"""
|
|
1119
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1120
|
+
with open(validated_path, 'w', encoding=encoding) as f:
|
|
1121
|
+
return f.write(str(content) if content is not None else '')
|
|
1077
1122
|
|
|
1078
1123
|
def builtin_appendfile(self, path: str, content: str, encoding: str = 'utf-8') -> int:
|
|
1079
|
-
"""Append content to file, returns bytes written"""
|
|
1080
|
-
|
|
1081
|
-
|
|
1124
|
+
"""Append content to file, returns bytes written. v4.7.1: Added path validation"""
|
|
1125
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1126
|
+
with open(validated_path, 'a', encoding=encoding) as f:
|
|
1127
|
+
return f.write(str(content) if content is not None else '')
|
|
1082
1128
|
|
|
1083
1129
|
def builtin_readlines(self, path: str, encoding: str = 'utf-8') -> list:
|
|
1084
|
-
"""Read file lines into list"""
|
|
1085
|
-
|
|
1130
|
+
"""Read file lines into list. v4.7.1: Added path validation"""
|
|
1131
|
+
validated_path = self._validate_path(path)
|
|
1132
|
+
with open(validated_path, 'r', encoding=encoding) as f:
|
|
1086
1133
|
return f.readlines()
|
|
1087
1134
|
|
|
1088
1135
|
def builtin_listdir(self, path: str = '.') -> list:
|
|
1089
|
-
"""List directory contents"""
|
|
1090
|
-
|
|
1136
|
+
"""List directory contents. v4.7.1: Added path validation"""
|
|
1137
|
+
validated_path = self._validate_path(path) if path != '.' else '.'
|
|
1138
|
+
return os.listdir(validated_path)
|
|
1091
1139
|
|
|
1092
1140
|
def builtin_makedirs(self, path: str, exist_ok: bool = True) -> bool:
|
|
1093
|
-
"""Create directories recursively"""
|
|
1094
|
-
|
|
1141
|
+
"""Create directories recursively. v4.7.1: Added path validation"""
|
|
1142
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1143
|
+
os.makedirs(validated_path, exist_ok=exist_ok)
|
|
1095
1144
|
return True
|
|
1096
1145
|
|
|
1097
1146
|
def builtin_removefile(self, path: str) -> bool:
|
|
1098
|
-
"""Remove a file"""
|
|
1099
|
-
|
|
1147
|
+
"""Remove a file. v4.7.1: Added path validation"""
|
|
1148
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1149
|
+
os.remove(validated_path)
|
|
1100
1150
|
return True
|
|
1101
1151
|
|
|
1102
1152
|
def builtin_removedir(self, path: str) -> bool:
|
|
1103
|
-
"""Remove an empty directory"""
|
|
1104
|
-
|
|
1153
|
+
"""Remove an empty directory. v4.7.1: Added path validation"""
|
|
1154
|
+
validated_path = self._validate_path(path, allow_write=True)
|
|
1155
|
+
os.rmdir(validated_path)
|
|
1105
1156
|
return True
|
|
1106
1157
|
|
|
1107
1158
|
def builtin_copyfile(self, src: str, dst: str) -> str:
|
|
1108
|
-
"""Copy a file, returns destination path"""
|
|
1159
|
+
"""Copy a file, returns destination path. v4.7.1: Added path validation"""
|
|
1109
1160
|
import shutil
|
|
1110
|
-
|
|
1161
|
+
validated_src = self._validate_path(src)
|
|
1162
|
+
validated_dst = self._validate_path(dst, allow_write=True)
|
|
1163
|
+
return shutil.copy2(validated_src, validated_dst)
|
|
1111
1164
|
|
|
1112
1165
|
def builtin_movefile(self, src: str, dst: str) -> str:
|
|
1113
|
-
"""Move a file, returns destination path"""
|
|
1166
|
+
"""Move a file, returns destination path. v4.7.1: Added path validation"""
|
|
1114
1167
|
import shutil
|
|
1115
|
-
|
|
1168
|
+
validated_src = self._validate_path(src, allow_write=True)
|
|
1169
|
+
validated_dst = self._validate_path(dst, allow_write=True)
|
|
1170
|
+
return shutil.move(validated_src, validated_dst)
|
|
1116
1171
|
|
|
1117
1172
|
def builtin_filesize(self, path: str) -> int:
|
|
1118
|
-
"""Get file size in bytes"""
|
|
1119
|
-
|
|
1173
|
+
"""Get file size in bytes. v4.7.1: Added path validation"""
|
|
1174
|
+
validated_path = self._validate_path(path)
|
|
1175
|
+
return os.path.getsize(validated_path)
|
|
1120
1176
|
|
|
1121
1177
|
# ============= JSON Functions =============
|
|
1122
1178
|
|
|
@@ -1558,26 +1614,57 @@ class CSSLBuiltins:
|
|
|
1558
1614
|
"""Delay execution by milliseconds"""
|
|
1559
1615
|
time.sleep(ms / 1000.0)
|
|
1560
1616
|
|
|
1617
|
+
# v4.7.1: Safe modules whitelist for pyimport
|
|
1618
|
+
SAFE_MODULES = {
|
|
1619
|
+
'math', 'json', 'datetime', 'random', 're', 'collections', 'itertools',
|
|
1620
|
+
'functools', 'operator', 'string', 'textwrap', 'unicodedata', 'difflib',
|
|
1621
|
+
'time', 'calendar', 'decimal', 'fractions', 'statistics', 'copy',
|
|
1622
|
+
'pprint', 'enum', 'dataclasses', 'typing', 'abc', 'contextlib',
|
|
1623
|
+
'base64', 'binascii', 'hashlib', 'hmac', 'secrets',
|
|
1624
|
+
'html', 'urllib.parse', 'uuid', 'pathlib',
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1561
1627
|
def builtin_pyimport(self, module_name: str) -> Any:
|
|
1562
1628
|
"""
|
|
1563
1629
|
Import a Python module for use in CSSL.
|
|
1564
1630
|
|
|
1631
|
+
v4.7.1: Restricted to safe modules only for security.
|
|
1565
1632
|
v4.1.1: Fixed to use importlib.import_module for proper nested module support.
|
|
1566
1633
|
|
|
1634
|
+
Safe modules: math, json, datetime, random, re, collections, itertools,
|
|
1635
|
+
functools, operator, string, time, calendar, decimal, etc.
|
|
1636
|
+
|
|
1567
1637
|
Usage:
|
|
1568
|
-
|
|
1569
|
-
result =
|
|
1638
|
+
@math = pyimport("math");
|
|
1639
|
+
result = math.sqrt(16);
|
|
1570
1640
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1641
|
+
@json = pyimport("json");
|
|
1642
|
+
data = json.loads('{"key": "value"}');
|
|
1573
1643
|
"""
|
|
1574
1644
|
import importlib
|
|
1645
|
+
# Check if module is in safe list
|
|
1646
|
+
base_module = module_name.split('.')[0]
|
|
1647
|
+
if base_module not in self.SAFE_MODULES and module_name not in self.SAFE_MODULES:
|
|
1648
|
+
raise RuntimeError(
|
|
1649
|
+
f"Module '{module_name}' is not allowed. "
|
|
1650
|
+
f"Safe modules: {', '.join(sorted(self.SAFE_MODULES))}"
|
|
1651
|
+
)
|
|
1575
1652
|
return importlib.import_module(module_name)
|
|
1576
1653
|
|
|
1577
1654
|
# ============= Extended String Functions =============
|
|
1578
1655
|
|
|
1579
1656
|
def builtin_sprintf(self, fmt: str, *args) -> str:
|
|
1580
|
-
"""C-style format string
|
|
1657
|
+
"""C-style format string with validation.
|
|
1658
|
+
|
|
1659
|
+
v4.7.1: Added format specifier validation for security.
|
|
1660
|
+
"""
|
|
1661
|
+
import re
|
|
1662
|
+
# Count format specifiers (excluding %%)
|
|
1663
|
+
specifiers = re.findall(r'%(?!%)[#0\- +]*\d*\.?\d*[hlL]?[diouxXeEfFgGcrsab]', fmt)
|
|
1664
|
+
if len(specifiers) != len(args):
|
|
1665
|
+
raise ValueError(
|
|
1666
|
+
f"Format string has {len(specifiers)} specifiers but {len(args)} arguments provided"
|
|
1667
|
+
)
|
|
1581
1668
|
return fmt % args
|
|
1582
1669
|
|
|
1583
1670
|
def builtin_chars(self, s: str) -> list:
|
|
@@ -1849,27 +1936,60 @@ class CSSLBuiltins:
|
|
|
1849
1936
|
|
|
1850
1937
|
def builtin_initsh(self, path: str, *args) -> int:
|
|
1851
1938
|
"""
|
|
1852
|
-
Execute a shell script
|
|
1853
|
-
|
|
1939
|
+
Execute a shell script from the 'scripts' directory only.
|
|
1940
|
+
|
|
1941
|
+
v4.7.1: Restricted to 'scripts/' directory for security.
|
|
1942
|
+
|
|
1943
|
+
Usage: initsh('myscript.sh') // Runs scripts/myscript.sh
|
|
1854
1944
|
"""
|
|
1855
1945
|
import subprocess
|
|
1856
1946
|
|
|
1857
|
-
|
|
1858
|
-
|
|
1947
|
+
# v4.7.1: Security - prevent directory traversal
|
|
1948
|
+
if '..' in path or path.startswith('/') or path.startswith('\\'):
|
|
1949
|
+
raise CSSLBuiltinError(
|
|
1950
|
+
f"Invalid script path: '{path}'. "
|
|
1951
|
+
"Scripts must be relative paths without '..' and must be in 'scripts/' directory."
|
|
1952
|
+
)
|
|
1859
1953
|
|
|
1860
|
-
|
|
1861
|
-
|
|
1954
|
+
# Determine base directory
|
|
1955
|
+
if self.runtime and self.runtime.service_engine:
|
|
1956
|
+
base_dir = self.runtime.service_engine.KernelClient.RootDirectory
|
|
1957
|
+
else:
|
|
1958
|
+
base_dir = os.getcwd()
|
|
1959
|
+
|
|
1960
|
+
# Force scripts to be in 'scripts' subdirectory
|
|
1961
|
+
scripts_dir = os.path.join(base_dir, 'scripts')
|
|
1962
|
+
full_path = os.path.normpath(os.path.join(scripts_dir, path))
|
|
1963
|
+
|
|
1964
|
+
# Verify path is within scripts directory (prevent traversal)
|
|
1965
|
+
if not full_path.startswith(os.path.normpath(scripts_dir)):
|
|
1966
|
+
raise CSSLBuiltinError(
|
|
1967
|
+
f"Security: Script path '{path}' escapes the scripts directory."
|
|
1968
|
+
)
|
|
1969
|
+
|
|
1970
|
+
if not os.path.exists(full_path):
|
|
1971
|
+
raise CSSLBuiltinError(f"Shell script not found: {full_path}")
|
|
1972
|
+
|
|
1973
|
+
# Validate file extension
|
|
1974
|
+
valid_extensions = {'.sh', '.bat', '.cmd', '.ps1'}
|
|
1975
|
+
_, ext = os.path.splitext(full_path)
|
|
1976
|
+
if ext.lower() not in valid_extensions:
|
|
1977
|
+
raise CSSLBuiltinError(
|
|
1978
|
+
f"Invalid script type: '{ext}'. Allowed: {', '.join(valid_extensions)}"
|
|
1979
|
+
)
|
|
1862
1980
|
|
|
1863
1981
|
try:
|
|
1864
1982
|
# Determine shell based on platform
|
|
1865
1983
|
import platform
|
|
1866
1984
|
if platform.system() == 'Windows':
|
|
1867
|
-
|
|
1868
|
-
|
|
1985
|
+
if ext.lower() == '.ps1':
|
|
1986
|
+
cmd = ['powershell', '-ExecutionPolicy', 'Bypass', '-File', full_path] + list(args)
|
|
1987
|
+
else:
|
|
1988
|
+
cmd = ['cmd', '/c', full_path] + list(args)
|
|
1869
1989
|
else:
|
|
1870
|
-
cmd = ['bash',
|
|
1990
|
+
cmd = ['bash', full_path] + list(args)
|
|
1871
1991
|
|
|
1872
|
-
result = subprocess.run(cmd, capture_output=True, text=True)
|
|
1992
|
+
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
1873
1993
|
|
|
1874
1994
|
if result.stdout:
|
|
1875
1995
|
print(result.stdout)
|
|
@@ -1878,6 +1998,8 @@ class CSSLBuiltins:
|
|
|
1878
1998
|
|
|
1879
1999
|
return result.returncode
|
|
1880
2000
|
|
|
2001
|
+
except subprocess.TimeoutExpired:
|
|
2002
|
+
raise CSSLBuiltinError(f"Script execution timeout (60s): {path}")
|
|
1881
2003
|
except Exception as e:
|
|
1882
2004
|
print(f"Shell execution error [{path}]: {e}")
|
|
1883
2005
|
raise CSSLBuiltinError(f"initsh failed: {e}")
|