zexus 1.6.2
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.
- package/LICENSE +0 -0
- package/README.md +2513 -0
- package/bin/zexus +2 -0
- package/bin/zpics +2 -0
- package/bin/zpm +2 -0
- package/bin/zx +2 -0
- package/bin/zx-deploy +2 -0
- package/bin/zx-dev +2 -0
- package/bin/zx-run +2 -0
- package/package.json +66 -0
- package/scripts/README.md +24 -0
- package/scripts/postinstall.js +44 -0
- package/shared_config.json +24 -0
- package/src/README.md +1525 -0
- package/src/tests/run_zexus_tests.py +117 -0
- package/src/tests/test_all_phases.zx +346 -0
- package/src/tests/test_blockchain_features.zx +306 -0
- package/src/tests/test_complexity_features.zx +321 -0
- package/src/tests/test_core_integration.py +185 -0
- package/src/tests/test_phase10_ecosystem.zx +177 -0
- package/src/tests/test_phase1_modifiers.zx +87 -0
- package/src/tests/test_phase2_plugins.zx +80 -0
- package/src/tests/test_phase3_security.zx +97 -0
- package/src/tests/test_phase4_vfs.zx +116 -0
- package/src/tests/test_phase5_types.zx +117 -0
- package/src/tests/test_phase6_metaprogramming.zx +125 -0
- package/src/tests/test_phase7_optimization.zx +132 -0
- package/src/tests/test_phase9_advanced_types.zx +157 -0
- package/src/tests/test_security_features.py +419 -0
- package/src/tests/test_security_features.zx +276 -0
- package/src/tests/test_simple_zx.zx +1 -0
- package/src/tests/test_verification_simple.zx +69 -0
- package/src/zexus/__init__.py +28 -0
- package/src/zexus/__main__.py +5 -0
- package/src/zexus/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/advanced_types.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/builtin_modules.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/capability_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/complexity_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/concurrency_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/config.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/dependency_injection.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/ecosystem.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/environment.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/error_reporter.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/hybrid_orchestrator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/metaprogramming.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/module_cache.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/object.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/optimization.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/plugin_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/policy_engine.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/security.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/stdlib_integration.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/strategy_recovery.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/syntax_validator.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/type_system.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/virtual_filesystem.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/__pycache__/zexus_token.cpython-312.pyc +0 -0
- package/src/zexus/advanced_types.py +401 -0
- package/src/zexus/blockchain/__init__.py +40 -0
- package/src/zexus/blockchain/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/crypto.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/ledger.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/__pycache__/transaction.cpython-312.pyc +0 -0
- package/src/zexus/blockchain/crypto.py +463 -0
- package/src/zexus/blockchain/ledger.py +255 -0
- package/src/zexus/blockchain/transaction.py +267 -0
- package/src/zexus/builtin_modules.py +284 -0
- package/src/zexus/builtin_plugins.py +317 -0
- package/src/zexus/capability_system.py +372 -0
- package/src/zexus/cli/__init__.py +2 -0
- package/src/zexus/cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/cli/__pycache__/main.cpython-312.pyc +0 -0
- package/src/zexus/cli/main.py +707 -0
- package/src/zexus/cli/zpm.py +203 -0
- package/src/zexus/compare_interpreter_compiler.py +146 -0
- package/src/zexus/compiler/__init__.py +169 -0
- package/src/zexus/compiler/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/lexer.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/compiler/__pycache__/zexus_ast.cpython-312.pyc +0 -0
- package/src/zexus/compiler/bytecode.py +266 -0
- package/src/zexus/compiler/compat_runtime.py +277 -0
- package/src/zexus/compiler/lexer.py +257 -0
- package/src/zexus/compiler/parser.py +779 -0
- package/src/zexus/compiler/semantic.py +118 -0
- package/src/zexus/compiler/zexus_ast.py +454 -0
- package/src/zexus/complexity_system.py +575 -0
- package/src/zexus/concurrency_system.py +493 -0
- package/src/zexus/config.py +201 -0
- package/src/zexus/crypto_bridge.py +19 -0
- package/src/zexus/dependency_injection.py +423 -0
- package/src/zexus/ecosystem.py +434 -0
- package/src/zexus/environment.py +101 -0
- package/src/zexus/environment_manager.py +119 -0
- package/src/zexus/error_reporter.py +314 -0
- package/src/zexus/evaluator/__init__.py +12 -0
- package/src/zexus/evaluator/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/bytecode_compiler.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/core.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/expressions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/functions.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/integration.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/statements.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/__pycache__/utils.cpython-312.pyc +0 -0
- package/src/zexus/evaluator/bytecode_compiler.py +700 -0
- package/src/zexus/evaluator/core.py +891 -0
- package/src/zexus/evaluator/expressions.py +827 -0
- package/src/zexus/evaluator/functions.py +3989 -0
- package/src/zexus/evaluator/integration.py +396 -0
- package/src/zexus/evaluator/statements.py +4303 -0
- package/src/zexus/evaluator/utils.py +126 -0
- package/src/zexus/evaluator_original.py +2041 -0
- package/src/zexus/external_bridge.py +16 -0
- package/src/zexus/find_affected_imports.sh +155 -0
- package/src/zexus/hybrid_orchestrator.py +152 -0
- package/src/zexus/input_validation.py +259 -0
- package/src/zexus/lexer.py +571 -0
- package/src/zexus/logging.py +89 -0
- package/src/zexus/lsp/__init__.py +9 -0
- package/src/zexus/lsp/completion_provider.py +207 -0
- package/src/zexus/lsp/definition_provider.py +22 -0
- package/src/zexus/lsp/hover_provider.py +71 -0
- package/src/zexus/lsp/server.py +269 -0
- package/src/zexus/lsp/symbol_provider.py +31 -0
- package/src/zexus/metaprogramming.py +321 -0
- package/src/zexus/module_cache.py +89 -0
- package/src/zexus/module_manager.py +107 -0
- package/src/zexus/object.py +973 -0
- package/src/zexus/optimization.py +424 -0
- package/src/zexus/parser/__init__.py +31 -0
- package/src/zexus/parser/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/parser.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_context.cpython-312.pyc +0 -0
- package/src/zexus/parser/__pycache__/strategy_structural.cpython-312.pyc +0 -0
- package/src/zexus/parser/integration.py +86 -0
- package/src/zexus/parser/parser.py +3977 -0
- package/src/zexus/parser/strategy_context.py +7254 -0
- package/src/zexus/parser/strategy_structural.py +1033 -0
- package/src/zexus/persistence.py +391 -0
- package/src/zexus/plugin_system.py +290 -0
- package/src/zexus/policy_engine.py +365 -0
- package/src/zexus/profiler/__init__.py +5 -0
- package/src/zexus/profiler/profiler.py +233 -0
- package/src/zexus/purity_system.py +398 -0
- package/src/zexus/runtime/__init__.py +20 -0
- package/src/zexus/runtime/async_runtime.py +324 -0
- package/src/zexus/search_old_imports.sh +65 -0
- package/src/zexus/security.py +1407 -0
- package/src/zexus/stack_trace.py +233 -0
- package/src/zexus/stdlib/__init__.py +27 -0
- package/src/zexus/stdlib/blockchain.py +341 -0
- package/src/zexus/stdlib/compression.py +167 -0
- package/src/zexus/stdlib/crypto.py +124 -0
- package/src/zexus/stdlib/datetime.py +163 -0
- package/src/zexus/stdlib/db_mongo.py +199 -0
- package/src/zexus/stdlib/db_mysql.py +162 -0
- package/src/zexus/stdlib/db_postgres.py +163 -0
- package/src/zexus/stdlib/db_sqlite.py +133 -0
- package/src/zexus/stdlib/encoding.py +230 -0
- package/src/zexus/stdlib/fs.py +195 -0
- package/src/zexus/stdlib/http.py +219 -0
- package/src/zexus/stdlib/http_server.py +248 -0
- package/src/zexus/stdlib/json_module.py +61 -0
- package/src/zexus/stdlib/math.py +360 -0
- package/src/zexus/stdlib/os_module.py +265 -0
- package/src/zexus/stdlib/regex.py +148 -0
- package/src/zexus/stdlib/sockets.py +253 -0
- package/src/zexus/stdlib/test_framework.zx +208 -0
- package/src/zexus/stdlib/test_runner.zx +119 -0
- package/src/zexus/stdlib_integration.py +341 -0
- package/src/zexus/strategy_recovery.py +256 -0
- package/src/zexus/syntax_validator.py +356 -0
- package/src/zexus/testing/zpics.py +407 -0
- package/src/zexus/testing/zpics_runtime.py +369 -0
- package/src/zexus/type_system.py +374 -0
- package/src/zexus/validation_system.py +569 -0
- package/src/zexus/virtual_filesystem.py +355 -0
- package/src/zexus/vm/__init__.py +8 -0
- package/src/zexus/vm/__pycache__/__init__.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/async_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/bytecode.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/cache.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/jit.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/memory_manager.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/memory_pool.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/parallel_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/peephole_optimizer.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/profiler.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/register_allocator.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/register_vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/ssa_converter.cpython-312.pyc +0 -0
- package/src/zexus/vm/__pycache__/vm.cpython-312.pyc +0 -0
- package/src/zexus/vm/async_optimizer.py +420 -0
- package/src/zexus/vm/bytecode.py +428 -0
- package/src/zexus/vm/bytecode_converter.py +297 -0
- package/src/zexus/vm/cache.py +532 -0
- package/src/zexus/vm/jit.py +720 -0
- package/src/zexus/vm/memory_manager.py +520 -0
- package/src/zexus/vm/memory_pool.py +511 -0
- package/src/zexus/vm/optimizer.py +478 -0
- package/src/zexus/vm/parallel_vm.py +899 -0
- package/src/zexus/vm/peephole_optimizer.py +452 -0
- package/src/zexus/vm/profiler.py +527 -0
- package/src/zexus/vm/register_allocator.py +462 -0
- package/src/zexus/vm/register_vm.py +520 -0
- package/src/zexus/vm/ssa_converter.py +757 -0
- package/src/zexus/vm/vm.py +1392 -0
- package/src/zexus/zexus_ast.py +1782 -0
- package/src/zexus/zexus_token.py +253 -0
- package/src/zexus/zpm/__init__.py +15 -0
- package/src/zexus/zpm/installer.py +116 -0
- package/src/zexus/zpm/package_manager.py +208 -0
- package/src/zexus/zpm/publisher.py +98 -0
- package/src/zexus/zpm/registry.py +110 -0
- package/src/zexus.egg-info/PKG-INFO +2235 -0
- package/src/zexus.egg-info/SOURCES.txt +876 -0
- package/src/zexus.egg-info/dependency_links.txt +1 -0
- package/src/zexus.egg-info/entry_points.txt +3 -0
- package/src/zexus.egg-info/not-zip-safe +1 -0
- package/src/zexus.egg-info/requires.txt +14 -0
- package/src/zexus.egg-info/top_level.txt +2 -0
- package/zexus.json +14 -0
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data Validation & Sanitization System for Zexus.
|
|
3
|
+
|
|
4
|
+
Provides built-in validation and sanitization primitives to prevent
|
|
5
|
+
common security vulnerabilities like injection attacks, type mismatches,
|
|
6
|
+
and malformed input.
|
|
7
|
+
|
|
8
|
+
Key Features:
|
|
9
|
+
- Schema validation (JSON-like schemas with type checking)
|
|
10
|
+
- Input sanitization (remove/escape dangerous characters)
|
|
11
|
+
- Common validators (email, URL, phone, IP, range, length)
|
|
12
|
+
- Custom validator registration
|
|
13
|
+
- Encoding-aware sanitization (HTML, SQL, JavaScript, URL)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Any, Dict, List, Optional, Callable, Pattern, Union
|
|
17
|
+
import re
|
|
18
|
+
import json
|
|
19
|
+
import html
|
|
20
|
+
import urllib.parse
|
|
21
|
+
from abc import ABC, abstractmethod
|
|
22
|
+
from enum import Enum
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ValidationError(Exception):
|
|
26
|
+
"""Exception raised when validation fails."""
|
|
27
|
+
def __init__(self, message: str, field: str = "", value: Any = None):
|
|
28
|
+
self.message = message
|
|
29
|
+
self.field = field
|
|
30
|
+
self.value = value
|
|
31
|
+
super().__init__(f"Validation error in '{field}': {message}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SanitizationError(Exception):
|
|
35
|
+
"""Exception raised when sanitization fails."""
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Encoding(Enum):
|
|
40
|
+
"""Common encoding types for sanitization."""
|
|
41
|
+
HTML = "html" # HTML entity encoding
|
|
42
|
+
URL = "url" # URL percent encoding
|
|
43
|
+
SQL = "sql" # SQL escape sequences
|
|
44
|
+
JAVASCRIPT = "javascript" # JavaScript string escaping
|
|
45
|
+
CSV = "csv" # CSV field escaping
|
|
46
|
+
NONE = "none" # No encoding
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Validator(ABC):
|
|
50
|
+
"""Base class for custom validators."""
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def validate(self, value: Any) -> bool:
|
|
54
|
+
"""Return True if valid, False otherwise."""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def get_error_message(self) -> str:
|
|
59
|
+
"""Return human-readable error message."""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RegexValidator(Validator):
|
|
64
|
+
"""Validate against a regex pattern."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, pattern: Union[str, Pattern], message: str = ""):
|
|
67
|
+
self.pattern = pattern if isinstance(pattern, Pattern) else re.compile(pattern)
|
|
68
|
+
self.message = message or f"Value does not match pattern: {pattern}"
|
|
69
|
+
|
|
70
|
+
def validate(self, value: Any) -> bool:
|
|
71
|
+
if not isinstance(value, str):
|
|
72
|
+
return False
|
|
73
|
+
return self.pattern.match(str(value)) is not None
|
|
74
|
+
|
|
75
|
+
def get_error_message(self) -> str:
|
|
76
|
+
return self.message
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class RangeValidator(Validator):
|
|
80
|
+
"""Validate numeric ranges."""
|
|
81
|
+
|
|
82
|
+
def __init__(self, min_val: Optional[float] = None,
|
|
83
|
+
max_val: Optional[float] = None):
|
|
84
|
+
self.min_val = min_val
|
|
85
|
+
self.max_val = max_val
|
|
86
|
+
|
|
87
|
+
def validate(self, value: Any) -> bool:
|
|
88
|
+
try:
|
|
89
|
+
num = float(value)
|
|
90
|
+
if self.min_val is not None and num < self.min_val:
|
|
91
|
+
return False
|
|
92
|
+
if self.max_val is not None and num > self.max_val:
|
|
93
|
+
return False
|
|
94
|
+
return True
|
|
95
|
+
except (TypeError, ValueError):
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
def get_error_message(self) -> str:
|
|
99
|
+
if self.min_val is not None and self.max_val is not None:
|
|
100
|
+
return f"Value must be between {self.min_val} and {self.max_val}"
|
|
101
|
+
elif self.min_val is not None:
|
|
102
|
+
return f"Value must be >= {self.min_val}"
|
|
103
|
+
elif self.max_val is not None:
|
|
104
|
+
return f"Value must be <= {self.max_val}"
|
|
105
|
+
return "Invalid range"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class LengthValidator(Validator):
|
|
109
|
+
"""Validate string/list length."""
|
|
110
|
+
|
|
111
|
+
def __init__(self, min_len: Optional[int] = None,
|
|
112
|
+
max_len: Optional[int] = None):
|
|
113
|
+
self.min_len = min_len
|
|
114
|
+
self.max_len = max_len
|
|
115
|
+
|
|
116
|
+
def validate(self, value: Any) -> bool:
|
|
117
|
+
try:
|
|
118
|
+
length = len(value)
|
|
119
|
+
if self.min_len is not None and length < self.min_len:
|
|
120
|
+
return False
|
|
121
|
+
if self.max_len is not None and length > self.max_len:
|
|
122
|
+
return False
|
|
123
|
+
return True
|
|
124
|
+
except (TypeError, AttributeError):
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
def get_error_message(self) -> str:
|
|
128
|
+
if self.min_len is not None and self.max_len is not None:
|
|
129
|
+
return f"Length must be between {self.min_len} and {self.max_len}"
|
|
130
|
+
elif self.min_len is not None:
|
|
131
|
+
return f"Length must be >= {self.min_len}"
|
|
132
|
+
elif self.max_len is not None:
|
|
133
|
+
return f"Length must be <= {self.max_len}"
|
|
134
|
+
return "Invalid length"
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
class ChoiceValidator(Validator):
|
|
138
|
+
"""Validate against allowed choices."""
|
|
139
|
+
|
|
140
|
+
def __init__(self, choices: List[Any]):
|
|
141
|
+
self.choices = set(choices)
|
|
142
|
+
|
|
143
|
+
def validate(self, value: Any) -> bool:
|
|
144
|
+
return value in self.choices
|
|
145
|
+
|
|
146
|
+
def get_error_message(self) -> str:
|
|
147
|
+
return f"Value must be one of: {self.choices}"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class TypeValidator(Validator):
|
|
151
|
+
"""Validate type."""
|
|
152
|
+
|
|
153
|
+
def __init__(self, expected_type: Union[type, tuple]):
|
|
154
|
+
self.expected_type = expected_type
|
|
155
|
+
|
|
156
|
+
def validate(self, value: Any) -> bool:
|
|
157
|
+
return isinstance(value, self.expected_type)
|
|
158
|
+
|
|
159
|
+
def get_error_message(self) -> str:
|
|
160
|
+
return f"Value must be of type {self.expected_type}"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class CompositeValidator(Validator):
|
|
164
|
+
"""Combine multiple validators (all must pass)."""
|
|
165
|
+
|
|
166
|
+
def __init__(self, validators: List[Validator]):
|
|
167
|
+
self.validators = validators
|
|
168
|
+
|
|
169
|
+
def validate(self, value: Any) -> bool:
|
|
170
|
+
return all(v.validate(value) for v in self.validators)
|
|
171
|
+
|
|
172
|
+
def get_error_message(self) -> str:
|
|
173
|
+
return " AND ".join(v.get_error_message() for v in self.validators)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class ValidationSchema:
|
|
177
|
+
"""Define validation rules for data structures."""
|
|
178
|
+
|
|
179
|
+
def __init__(self, rules: Dict[str, Union[Validator, type, List[Validator]]]):
|
|
180
|
+
"""
|
|
181
|
+
Initialize schema with validation rules.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
rules: Dict mapping field names to validators or types
|
|
185
|
+
- str value: check isinstance(value, str)
|
|
186
|
+
- Validator instance: use custom validator
|
|
187
|
+
- List[Validator]: use CompositeValidator
|
|
188
|
+
"""
|
|
189
|
+
self.rules: Dict[str, Validator] = {}
|
|
190
|
+
|
|
191
|
+
for field, rule in rules.items():
|
|
192
|
+
if isinstance(rule, Validator):
|
|
193
|
+
self.rules[field] = rule
|
|
194
|
+
elif isinstance(rule, type):
|
|
195
|
+
self.rules[field] = TypeValidator(rule)
|
|
196
|
+
elif isinstance(rule, list):
|
|
197
|
+
self.rules[field] = CompositeValidator(rule)
|
|
198
|
+
else:
|
|
199
|
+
raise ValueError(f"Invalid rule for field {field}: {rule}")
|
|
200
|
+
|
|
201
|
+
def validate(self, data: Dict[str, Any]) -> bool:
|
|
202
|
+
"""Validate data against schema."""
|
|
203
|
+
for field, validator in self.rules.items():
|
|
204
|
+
if field not in data:
|
|
205
|
+
raise ValidationError(f"Missing required field: {field}", field)
|
|
206
|
+
|
|
207
|
+
value = data[field]
|
|
208
|
+
if not validator.validate(value):
|
|
209
|
+
raise ValidationError(
|
|
210
|
+
validator.get_error_message(),
|
|
211
|
+
field,
|
|
212
|
+
value
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
def validate_partial(self, data: Dict[str, Any],
|
|
218
|
+
required_fields: Optional[List[str]] = None) -> bool:
|
|
219
|
+
"""Validate only specified fields (partial validation)."""
|
|
220
|
+
fields_to_check = required_fields or list(self.rules.keys())
|
|
221
|
+
|
|
222
|
+
for field in fields_to_check:
|
|
223
|
+
if field not in self.rules:
|
|
224
|
+
continue
|
|
225
|
+
|
|
226
|
+
if field not in data:
|
|
227
|
+
raise ValidationError(f"Missing required field: {field}", field)
|
|
228
|
+
|
|
229
|
+
value = data[field]
|
|
230
|
+
validator = self.rules[field]
|
|
231
|
+
if not validator.validate(value):
|
|
232
|
+
raise ValidationError(
|
|
233
|
+
validator.get_error_message(),
|
|
234
|
+
field,
|
|
235
|
+
value
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return True
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class StandardValidators:
|
|
242
|
+
"""Collection of standard, reusable validators."""
|
|
243
|
+
|
|
244
|
+
# Email validation
|
|
245
|
+
EMAIL = RegexValidator(
|
|
246
|
+
r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
|
|
247
|
+
"Invalid email format"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# URL validation
|
|
251
|
+
URL = RegexValidator(
|
|
252
|
+
r'^https?://[^\s/$.?#].[^\s]*$',
|
|
253
|
+
"Invalid URL format"
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# IPv4 address
|
|
257
|
+
IPV4 = RegexValidator(
|
|
258
|
+
r'^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$',
|
|
259
|
+
"Invalid IPv4 address"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# IPv6 address
|
|
263
|
+
IPV6 = RegexValidator(
|
|
264
|
+
r'^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4})$',
|
|
265
|
+
"Invalid IPv6 address"
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Phone number (basic US format)
|
|
269
|
+
PHONE = RegexValidator(
|
|
270
|
+
r'^(\+?1)?[-.\s]?\(?[0-9]{3}\)?[-.\s]?[0-9]{3}[-.\s]?[0-9]{4}$',
|
|
271
|
+
"Invalid phone number"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
# UUID
|
|
275
|
+
UUID = RegexValidator(
|
|
276
|
+
r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
|
|
277
|
+
"Invalid UUID format"
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# Alphanumeric only
|
|
281
|
+
ALPHANUMERIC = RegexValidator(
|
|
282
|
+
r'^[a-zA-Z0-9]+$',
|
|
283
|
+
"Must contain only alphanumeric characters"
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Positive integer
|
|
287
|
+
POSITIVE_INT = CompositeValidator([
|
|
288
|
+
TypeValidator(int),
|
|
289
|
+
RangeValidator(min_val=0)
|
|
290
|
+
])
|
|
291
|
+
|
|
292
|
+
# Non-empty string
|
|
293
|
+
NON_EMPTY_STRING = CompositeValidator([
|
|
294
|
+
TypeValidator(str),
|
|
295
|
+
LengthValidator(min_len=1)
|
|
296
|
+
])
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class Sanitizer:
|
|
300
|
+
"""Sanitize untrusted input to prevent injection attacks."""
|
|
301
|
+
|
|
302
|
+
# HTML injection patterns
|
|
303
|
+
DANGEROUS_HTML_TAGS = {
|
|
304
|
+
'script', 'iframe', 'embed', 'object', 'link', 'style', 'meta'
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
DANGEROUS_ATTRIBUTES = {
|
|
308
|
+
'onclick', 'onload', 'onerror', 'onmouseover', 'onmouseout',
|
|
309
|
+
'onchange', 'onfocus', 'onblur', 'onfocus', 'onsubmit'
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
# SQL keywords for detection
|
|
313
|
+
SQL_KEYWORDS = {
|
|
314
|
+
'SELECT', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'UNION',
|
|
315
|
+
'FROM', 'WHERE', 'OR', 'AND'
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@staticmethod
|
|
319
|
+
def sanitize_string(value: str, encoding: Encoding = Encoding.HTML) -> str:
|
|
320
|
+
"""
|
|
321
|
+
Sanitize a string value.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
value: String to sanitize
|
|
325
|
+
encoding: Type of encoding to apply
|
|
326
|
+
|
|
327
|
+
Returns:
|
|
328
|
+
Sanitized string
|
|
329
|
+
"""
|
|
330
|
+
if not isinstance(value, str):
|
|
331
|
+
raise SanitizationError(f"Expected string, got {type(value)}")
|
|
332
|
+
|
|
333
|
+
if encoding == Encoding.HTML:
|
|
334
|
+
return Sanitizer._sanitize_html(value)
|
|
335
|
+
elif encoding == Encoding.URL:
|
|
336
|
+
return urllib.parse.quote(value, safe='')
|
|
337
|
+
elif encoding == Encoding.SQL:
|
|
338
|
+
return Sanitizer._sanitize_sql(value)
|
|
339
|
+
elif encoding == Encoding.JAVASCRIPT:
|
|
340
|
+
return Sanitizer._sanitize_javascript(value)
|
|
341
|
+
elif encoding == Encoding.CSV:
|
|
342
|
+
return Sanitizer._sanitize_csv(value)
|
|
343
|
+
elif encoding == Encoding.NONE:
|
|
344
|
+
return value
|
|
345
|
+
else:
|
|
346
|
+
raise SanitizationError(f"Unknown encoding: {encoding}")
|
|
347
|
+
|
|
348
|
+
@staticmethod
|
|
349
|
+
def _sanitize_html(value: str) -> str:
|
|
350
|
+
"""Remove dangerous HTML tags and attributes."""
|
|
351
|
+
# Escape HTML entities
|
|
352
|
+
value = html.escape(value, quote=True)
|
|
353
|
+
|
|
354
|
+
# Additional cleanup for remaining tags
|
|
355
|
+
value = re.sub(r'<script[^>]*>.*?</script>', '', value, flags=re.IGNORECASE)
|
|
356
|
+
|
|
357
|
+
return value
|
|
358
|
+
|
|
359
|
+
@staticmethod
|
|
360
|
+
def _sanitize_sql(value: str) -> str:
|
|
361
|
+
"""Escape SQL special characters."""
|
|
362
|
+
# Escape single quotes by doubling them
|
|
363
|
+
return value.replace("'", "''")
|
|
364
|
+
|
|
365
|
+
@staticmethod
|
|
366
|
+
def _sanitize_javascript(value: str) -> str:
|
|
367
|
+
"""Escape JavaScript special characters."""
|
|
368
|
+
replacements = {
|
|
369
|
+
'"': '\\"',
|
|
370
|
+
"'": "\\'",
|
|
371
|
+
"\n": "\\n",
|
|
372
|
+
"\r": "\\r",
|
|
373
|
+
"\t": "\\t",
|
|
374
|
+
"\b": "\\b",
|
|
375
|
+
"\f": "\\f"
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
for char, escaped in replacements.items():
|
|
379
|
+
value = value.replace(char, escaped)
|
|
380
|
+
|
|
381
|
+
return value
|
|
382
|
+
|
|
383
|
+
@staticmethod
|
|
384
|
+
def _sanitize_csv(value: str) -> str:
|
|
385
|
+
"""Escape CSV field."""
|
|
386
|
+
if '"' in value or ',' in value or '\n' in value:
|
|
387
|
+
value = '"' + value.replace('"', '""') + '"'
|
|
388
|
+
return value
|
|
389
|
+
|
|
390
|
+
@staticmethod
|
|
391
|
+
def sanitize_dict(data: Dict[str, Any],
|
|
392
|
+
encoding: Encoding = Encoding.HTML) -> Dict[str, Any]:
|
|
393
|
+
"""Sanitize all string values in a dictionary."""
|
|
394
|
+
result = {}
|
|
395
|
+
|
|
396
|
+
for key, value in data.items():
|
|
397
|
+
if isinstance(value, str):
|
|
398
|
+
result[key] = Sanitizer.sanitize_string(value, encoding)
|
|
399
|
+
elif isinstance(value, dict):
|
|
400
|
+
result[key] = Sanitizer.sanitize_dict(value, encoding)
|
|
401
|
+
elif isinstance(value, list):
|
|
402
|
+
result[key] = Sanitizer.sanitize_list(value, encoding)
|
|
403
|
+
else:
|
|
404
|
+
result[key] = value
|
|
405
|
+
|
|
406
|
+
return result
|
|
407
|
+
|
|
408
|
+
@staticmethod
|
|
409
|
+
def sanitize_list(data: List[Any],
|
|
410
|
+
encoding: Encoding = Encoding.HTML) -> List[Any]:
|
|
411
|
+
"""Sanitize all string values in a list."""
|
|
412
|
+
result = []
|
|
413
|
+
|
|
414
|
+
for value in data:
|
|
415
|
+
if isinstance(value, str):
|
|
416
|
+
result.append(Sanitizer.sanitize_string(value, encoding))
|
|
417
|
+
elif isinstance(value, dict):
|
|
418
|
+
result.append(Sanitizer.sanitize_dict(value, encoding))
|
|
419
|
+
elif isinstance(value, list):
|
|
420
|
+
result.append(Sanitizer.sanitize_list(value, encoding))
|
|
421
|
+
else:
|
|
422
|
+
result.append(value)
|
|
423
|
+
|
|
424
|
+
return result
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
class ValidationManager:
|
|
428
|
+
"""
|
|
429
|
+
Central validation/sanitization manager for the interpreter.
|
|
430
|
+
|
|
431
|
+
Provides a unified interface for:
|
|
432
|
+
- Registering custom validators
|
|
433
|
+
- Validating data against schemas
|
|
434
|
+
- Sanitizing untrusted input
|
|
435
|
+
- Tracking validation/sanitization history
|
|
436
|
+
"""
|
|
437
|
+
|
|
438
|
+
def __init__(self):
|
|
439
|
+
self.custom_validators: Dict[str, Validator] = {}
|
|
440
|
+
self.schemas: Dict[str, ValidationSchema] = {}
|
|
441
|
+
self.history: List[Dict[str, Any]] = []
|
|
442
|
+
|
|
443
|
+
# Initialize built-in schemas for common types
|
|
444
|
+
self._register_builtin_schemas()
|
|
445
|
+
|
|
446
|
+
def _register_builtin_schemas(self):
|
|
447
|
+
"""Register built-in schemas for common data types."""
|
|
448
|
+
# String type schema
|
|
449
|
+
self.register_schema("string", ValidationSchema({
|
|
450
|
+
"_type": TypeValidator(str)
|
|
451
|
+
}))
|
|
452
|
+
|
|
453
|
+
# Integer type schema
|
|
454
|
+
self.register_schema("integer", ValidationSchema({
|
|
455
|
+
"_type": TypeValidator(int)
|
|
456
|
+
}))
|
|
457
|
+
|
|
458
|
+
# Number type schema (int or float)
|
|
459
|
+
self.register_schema("number", ValidationSchema({
|
|
460
|
+
"_type": TypeValidator((int, float))
|
|
461
|
+
}))
|
|
462
|
+
|
|
463
|
+
# Boolean type schema
|
|
464
|
+
self.register_schema("boolean", ValidationSchema({
|
|
465
|
+
"_type": TypeValidator(bool)
|
|
466
|
+
}))
|
|
467
|
+
|
|
468
|
+
# Email schema (uses standard validator)
|
|
469
|
+
self.register_schema("email", ValidationSchema({
|
|
470
|
+
"_value": StandardValidators.EMAIL
|
|
471
|
+
}))
|
|
472
|
+
|
|
473
|
+
# URL schema (uses standard validator)
|
|
474
|
+
self.register_schema("url", ValidationSchema({
|
|
475
|
+
"_value": StandardValidators.URL
|
|
476
|
+
}))
|
|
477
|
+
|
|
478
|
+
# Phone schema (uses standard validator)
|
|
479
|
+
self.register_schema("phone", ValidationSchema({
|
|
480
|
+
"_value": StandardValidators.PHONE
|
|
481
|
+
}))
|
|
482
|
+
|
|
483
|
+
# UUID schema (uses standard validator)
|
|
484
|
+
self.register_schema("uuid", ValidationSchema({
|
|
485
|
+
"_value": StandardValidators.UUID
|
|
486
|
+
}))
|
|
487
|
+
|
|
488
|
+
# IPv4 schema
|
|
489
|
+
self.register_schema("ipv4", ValidationSchema({
|
|
490
|
+
"_value": StandardValidators.IPV4
|
|
491
|
+
}))
|
|
492
|
+
|
|
493
|
+
# IPv6 schema
|
|
494
|
+
self.register_schema("ipv6", ValidationSchema({
|
|
495
|
+
"_value": StandardValidators.IPV6
|
|
496
|
+
}))
|
|
497
|
+
|
|
498
|
+
def register_validator(self, name: str, validator: Validator):
|
|
499
|
+
"""Register a custom validator."""
|
|
500
|
+
self.custom_validators[name] = validator
|
|
501
|
+
|
|
502
|
+
def get_validator(self, name: str) -> Optional[Validator]:
|
|
503
|
+
"""Get a registered validator by name."""
|
|
504
|
+
return self.custom_validators.get(name)
|
|
505
|
+
|
|
506
|
+
def register_schema(self, name: str, schema: ValidationSchema):
|
|
507
|
+
"""Register a validation schema."""
|
|
508
|
+
self.schemas[name] = schema
|
|
509
|
+
|
|
510
|
+
def get_schema(self, name: str) -> Optional[ValidationSchema]:
|
|
511
|
+
"""Get a registered schema by name."""
|
|
512
|
+
return self.schemas.get(name)
|
|
513
|
+
|
|
514
|
+
def validate(self, value: Any, validator_name: str) -> bool:
|
|
515
|
+
"""Validate a value using a registered validator."""
|
|
516
|
+
validator = self.get_validator(validator_name)
|
|
517
|
+
if not validator:
|
|
518
|
+
raise ValueError(f"Unknown validator: {validator_name}")
|
|
519
|
+
|
|
520
|
+
self._record_validation(validator_name, value, validator.validate(value))
|
|
521
|
+
return validator.validate(value)
|
|
522
|
+
|
|
523
|
+
def validate_schema(self, data: Dict[str, Any], schema_name: str) -> bool:
|
|
524
|
+
"""Validate data against a registered schema."""
|
|
525
|
+
schema = self.get_schema(schema_name)
|
|
526
|
+
if not schema:
|
|
527
|
+
raise ValueError(f"Unknown schema: {schema_name}")
|
|
528
|
+
|
|
529
|
+
return schema.validate(data)
|
|
530
|
+
|
|
531
|
+
def sanitize(self, value: str, encoding: Encoding = Encoding.HTML) -> str:
|
|
532
|
+
"""Sanitize a string value."""
|
|
533
|
+
result = Sanitizer.sanitize_string(value, encoding)
|
|
534
|
+
self._record_sanitization(value, result, encoding.value)
|
|
535
|
+
return result
|
|
536
|
+
|
|
537
|
+
def _record_validation(self, validator: str, value: Any, result: bool):
|
|
538
|
+
"""Record validation operation."""
|
|
539
|
+
self.history.append({
|
|
540
|
+
"type": "validation",
|
|
541
|
+
"validator": validator,
|
|
542
|
+
"value": str(value)[:100], # Truncate long values
|
|
543
|
+
"result": result
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
def _record_sanitization(self, original: str, sanitized: str, encoding: str):
|
|
547
|
+
"""Record sanitization operation."""
|
|
548
|
+
self.history.append({
|
|
549
|
+
"type": "sanitization",
|
|
550
|
+
"encoding": encoding,
|
|
551
|
+
"original": original[:50],
|
|
552
|
+
"sanitized": sanitized[:50],
|
|
553
|
+
"length": len(original)
|
|
554
|
+
})
|
|
555
|
+
|
|
556
|
+
def get_history(self, limit: Optional[int] = None) -> List[Dict[str, Any]]:
|
|
557
|
+
"""Get validation/sanitization history."""
|
|
558
|
+
if limit:
|
|
559
|
+
return self.history[-limit:]
|
|
560
|
+
return self.history.copy()
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
# Global instance
|
|
564
|
+
_validation_manager = ValidationManager()
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def get_validation_manager() -> ValidationManager:
|
|
568
|
+
"""Get the global validation manager instance."""
|
|
569
|
+
return _validation_manager
|