scythe-ttp 0.15.10__tar.gz → 0.17.0__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.
Potentially problematic release.
This version of scythe-ttp might be problematic. Click here for more details.
- {scythe_ttp-0.15.10/scythe_ttp.egg-info → scythe_ttp-0.17.0}/PKG-INFO +2 -1
- scythe_ttp-0.17.0/VERSION +1 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/pyproject.toml +3 -2
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/cli/main.py +135 -5
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/journeys/actions.py +29 -7
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0/scythe_ttp.egg-info}/PKG-INFO +2 -1
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe_ttp.egg-info/requires.txt +1 -0
- scythe_ttp-0.15.10/VERSION +0 -1
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/LICENSE +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/MANIFEST.in +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/README.md +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/requirements.txt +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/__init__.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/auth/__init__.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/auth/base.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/auth/basic.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/auth/bearer.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/auth/cookie_jwt.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/behaviors/__init__.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/behaviors/base.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/behaviors/default.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/behaviors/human.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/behaviors/machine.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/behaviors/stealth.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/cli/__init__.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/core/__init__.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/core/executor.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/core/headers.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/core/ttp.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/journeys/__init__.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/journeys/base.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/journeys/executor.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/orchestrators/__init__.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/orchestrators/base.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/orchestrators/batch.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/orchestrators/distributed.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/orchestrators/scale.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/payloads/__init__.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/payloads/generators.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/ttps/__init__.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/ttps/web/__init__.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/ttps/web/login_bruteforce.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/ttps/web/sql_injection.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe/ttps/web/uuid_guessing.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe_ttp.egg-info/SOURCES.txt +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe_ttp.egg-info/dependency_links.txt +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe_ttp.egg-info/entry_points.txt +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/scythe_ttp.egg-info/top_level.txt +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/setup.cfg +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/tests/test_api_models.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/tests/test_authentication.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/tests/test_behaviors.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/tests/test_cli.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/tests/test_cookie_jwt_auth.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/tests/test_expected_results.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/tests/test_feature_completeness.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/tests/test_header_extraction.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/tests/test_journeys.py +0 -0
- {scythe_ttp-0.15.10 → scythe_ttp-0.17.0}/tests/test_orchestrators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scythe-ttp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: An extensible framework for emulating attacker TTPs with Selenium.
|
|
5
5
|
Author-email: EpykLab <cyber@epyklab.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -35,6 +35,7 @@ Requires-Dist: urllib3==2.4.0
|
|
|
35
35
|
Requires-Dist: websocket-client==1.8.0
|
|
36
36
|
Requires-Dist: wsproto==1.2.0
|
|
37
37
|
Requires-Dist: typer
|
|
38
|
+
Requires-Dist: shellingham
|
|
38
39
|
Dynamic: license-file
|
|
39
40
|
|
|
40
41
|
<h1 align="center">Scythe</h1>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.17.0
|
|
@@ -40,7 +40,8 @@ dependencies = [
|
|
|
40
40
|
"urllib3==2.4.0",
|
|
41
41
|
"websocket-client==1.8.0",
|
|
42
42
|
"wsproto==1.2.0",
|
|
43
|
-
"typer"
|
|
43
|
+
"typer",
|
|
44
|
+
"shellingham"
|
|
44
45
|
]
|
|
45
46
|
# Remove the static version line entirely
|
|
46
47
|
|
|
@@ -52,4 +53,4 @@ packages = {find = {exclude = ["tests*", "examples*"]}}
|
|
|
52
53
|
|
|
53
54
|
[tool.setuptools.dynamic]
|
|
54
55
|
version = {file = "VERSION"} # Fix this to point to your actual VERSION file
|
|
55
|
-
dependencies = {file = "requirements.txt"} # Keep if using; otherwise remove if sticking with static deps list
|
|
56
|
+
dependencies = {file = "requirements.txt"} # Keep if using; otherwise remove if sticking with static deps list
|
|
@@ -83,6 +83,138 @@ def main():
|
|
|
83
83
|
dest='gate_versions',
|
|
84
84
|
help='Gate versions to test against')
|
|
85
85
|
|
|
86
|
+
# Core Application Parameters
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
'--protocol',
|
|
89
|
+
default='https',
|
|
90
|
+
choices=['http', 'https'],
|
|
91
|
+
help='Protocol to use (http/https, default: https)')
|
|
92
|
+
parser.add_argument(
|
|
93
|
+
'--port',
|
|
94
|
+
type=int,
|
|
95
|
+
help='Port number for the target application')
|
|
96
|
+
|
|
97
|
+
# Authentication Parameters
|
|
98
|
+
parser.add_argument(
|
|
99
|
+
'--username',
|
|
100
|
+
help='Username for authentication')
|
|
101
|
+
parser.add_argument(
|
|
102
|
+
'--password',
|
|
103
|
+
help='Password for authentication')
|
|
104
|
+
parser.add_argument(
|
|
105
|
+
'--token',
|
|
106
|
+
help='Bearer token or API key')
|
|
107
|
+
parser.add_argument(
|
|
108
|
+
'--auth-type',
|
|
109
|
+
choices=['basic', 'bearer', 'form'],
|
|
110
|
+
help='Authentication method (basic, bearer, form, etc.)')
|
|
111
|
+
parser.add_argument(
|
|
112
|
+
'--credentials-file',
|
|
113
|
+
help='Path to file containing multiple user credentials')
|
|
114
|
+
|
|
115
|
+
# Test Data Parameters
|
|
116
|
+
parser.add_argument(
|
|
117
|
+
'--users-file',
|
|
118
|
+
help='Path to CSV file containing user data')
|
|
119
|
+
parser.add_argument(
|
|
120
|
+
'--emails-file',
|
|
121
|
+
help='Path to text file containing email addresses')
|
|
122
|
+
parser.add_argument(
|
|
123
|
+
'--payload-file',
|
|
124
|
+
help='Path to file containing test payloads')
|
|
125
|
+
parser.add_argument(
|
|
126
|
+
'--data-file',
|
|
127
|
+
help='Generic path to test data file')
|
|
128
|
+
|
|
129
|
+
# Execution Control Parameters
|
|
130
|
+
parser.add_argument(
|
|
131
|
+
'--batch-size',
|
|
132
|
+
type=int,
|
|
133
|
+
default=10,
|
|
134
|
+
help='Number of operations per batch (default: 10)')
|
|
135
|
+
parser.add_argument(
|
|
136
|
+
'--max-batches',
|
|
137
|
+
type=int,
|
|
138
|
+
help='Maximum number of batches to run')
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
'--workers',
|
|
141
|
+
type=int,
|
|
142
|
+
help='Number of concurrent workers/threads')
|
|
143
|
+
parser.add_argument(
|
|
144
|
+
'--replications',
|
|
145
|
+
type=int,
|
|
146
|
+
help='Number of test replications for load testing')
|
|
147
|
+
parser.add_argument(
|
|
148
|
+
'--timeout',
|
|
149
|
+
type=int,
|
|
150
|
+
help='Request timeout in seconds')
|
|
151
|
+
parser.add_argument(
|
|
152
|
+
'--delay',
|
|
153
|
+
type=float,
|
|
154
|
+
help='Delay between requests in seconds')
|
|
155
|
+
|
|
156
|
+
# Browser/Execution Parameters
|
|
157
|
+
parser.add_argument(
|
|
158
|
+
'--headless',
|
|
159
|
+
action='store_true',
|
|
160
|
+
help='Run browser in headless mode (flag)')
|
|
161
|
+
parser.add_argument(
|
|
162
|
+
'--browser',
|
|
163
|
+
choices=['chrome', 'firefox', 'safari', 'edge'],
|
|
164
|
+
help='Browser type (chrome, firefox, etc.)')
|
|
165
|
+
parser.add_argument(
|
|
166
|
+
'--user-agent',
|
|
167
|
+
help='Custom user agent string')
|
|
168
|
+
parser.add_argument(
|
|
169
|
+
'--proxy',
|
|
170
|
+
help='Proxy server URL')
|
|
171
|
+
parser.add_argument(
|
|
172
|
+
'--proxy-file',
|
|
173
|
+
help='Path to file containing proxy list')
|
|
174
|
+
|
|
175
|
+
# Output and Reporting Parameters
|
|
176
|
+
parser.add_argument(
|
|
177
|
+
'--output-dir',
|
|
178
|
+
help='Directory for output files')
|
|
179
|
+
parser.add_argument(
|
|
180
|
+
'--report-format',
|
|
181
|
+
choices=['json', 'csv', 'html'],
|
|
182
|
+
help='Report format (json, csv, html)')
|
|
183
|
+
parser.add_argument(
|
|
184
|
+
'--log-level',
|
|
185
|
+
choices=['debug', 'info', 'warning', 'error'],
|
|
186
|
+
help='Logging level (debug, info, warning, error)')
|
|
187
|
+
parser.add_argument(
|
|
188
|
+
'--verbose',
|
|
189
|
+
action='store_true',
|
|
190
|
+
help='Enable verbose output (flag)')
|
|
191
|
+
parser.add_argument(
|
|
192
|
+
'--silent',
|
|
193
|
+
action='store_true',
|
|
194
|
+
help='Suppress output except errors (flag)')
|
|
195
|
+
|
|
196
|
+
# Test Control Parameters
|
|
197
|
+
parser.add_argument(
|
|
198
|
+
'--fail-fast',
|
|
199
|
+
action='store_true',
|
|
200
|
+
help='Stop immediately on first failure (flag)')
|
|
201
|
+
parser.add_argument(
|
|
202
|
+
'--dry-run',
|
|
203
|
+
action='store_true',
|
|
204
|
+
help='Validate configuration without executing tests (flag)')
|
|
205
|
+
parser.add_argument(
|
|
206
|
+
'--test-type',
|
|
207
|
+
choices=['load', 'security', 'functional'],
|
|
208
|
+
help='Type of test to run (load, security, functional)')
|
|
209
|
+
parser.add_argument(
|
|
210
|
+
'--iterations',
|
|
211
|
+
type=int,
|
|
212
|
+
help='Number of test iterations')
|
|
213
|
+
parser.add_argument(
|
|
214
|
+
'--duration',
|
|
215
|
+
type=int,
|
|
216
|
+
help='Test duration in seconds')
|
|
217
|
+
|
|
86
218
|
args = parser.parse_args()
|
|
87
219
|
|
|
88
220
|
if check_url_available(args.url):
|
|
@@ -226,10 +358,8 @@ def _create_test(project_root: str, name: str) -> str:
|
|
|
226
358
|
|
|
227
359
|
return filepath
|
|
228
360
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
_DETECTED_LIST_RE = re.compile(r"Detected target versions: \[?([^\]]*)\]?")
|
|
232
|
-
|
|
361
|
+
_VERSION_RE = re.compile(r"['\"]?X-Scythe-Target-Version['\"]?\s*:\s*['\"]?([\w.-]+)['\"]?")
|
|
362
|
+
_DETECTED_LIST_RE = re.compile(r"Target versions detected:\s*\[?([^]]*)\]?")
|
|
233
363
|
|
|
234
364
|
def _parse_version_from_output(output: str) -> Optional[str]:
|
|
235
365
|
m = _VERSION_RE.search(output)
|
|
@@ -240,7 +370,7 @@ def _parse_version_from_output(output: str) -> Optional[str]:
|
|
|
240
370
|
if m:
|
|
241
371
|
inner = m.group(1)
|
|
242
372
|
# extract first version-like token
|
|
243
|
-
mv = re.search(r"
|
|
373
|
+
mv = re.search(r"\d+(?:\.[\w\-]+)+", inner)
|
|
244
374
|
if mv:
|
|
245
375
|
return mv.group(0)
|
|
246
376
|
return None
|
|
@@ -659,6 +659,7 @@ class ApiRequestAction(Action):
|
|
|
659
659
|
def __init__(self,
|
|
660
660
|
method: str,
|
|
661
661
|
url: str,
|
|
662
|
+
flush: bool = False,
|
|
662
663
|
params: Optional[Dict[str, Any]] = None,
|
|
663
664
|
body_json: Optional[Dict[str, Any]] = None,
|
|
664
665
|
data: Optional[Dict[str, Any]] = None,
|
|
@@ -673,6 +674,7 @@ class ApiRequestAction(Action):
|
|
|
673
674
|
fail_on_validation_error: bool = False):
|
|
674
675
|
self.method = method.upper()
|
|
675
676
|
self.url = url
|
|
677
|
+
self.flush = flush
|
|
676
678
|
self.params = params or {}
|
|
677
679
|
self.body_json = body_json
|
|
678
680
|
self.data = data
|
|
@@ -692,15 +694,15 @@ class ApiRequestAction(Action):
|
|
|
692
694
|
if session is None:
|
|
693
695
|
session = requests.Session()
|
|
694
696
|
context['requests_session'] = session
|
|
695
|
-
|
|
696
|
-
# Build headers: auth headers from context
|
|
697
|
+
|
|
698
|
+
# Build headers: auth headers from context and action headers (action overrides)
|
|
697
699
|
final_headers = {}
|
|
698
700
|
auth_headers = context.get('auth_headers', {}) or {}
|
|
699
701
|
if auth_headers:
|
|
700
702
|
final_headers.update(auth_headers)
|
|
701
703
|
if self.headers:
|
|
702
704
|
final_headers.update(self.headers)
|
|
703
|
-
|
|
705
|
+
|
|
704
706
|
# Simple masking for sensitive headers
|
|
705
707
|
def _mask_headers(headers: Dict[str, Any]) -> Dict[str, Any]:
|
|
706
708
|
masked = {}
|
|
@@ -713,7 +715,7 @@ class ApiRequestAction(Action):
|
|
|
713
715
|
else:
|
|
714
716
|
masked[k] = v
|
|
715
717
|
return masked
|
|
716
|
-
|
|
718
|
+
|
|
717
719
|
# Resolve URL: absolute or join with target_url from context
|
|
718
720
|
from urllib.parse import urljoin
|
|
719
721
|
from ..core.headers import HeaderExtractor
|
|
@@ -725,7 +727,7 @@ class ApiRequestAction(Action):
|
|
|
725
727
|
resolved_url = self.url
|
|
726
728
|
else:
|
|
727
729
|
resolved_url = urljoin(base_url, self.url)
|
|
728
|
-
|
|
730
|
+
|
|
729
731
|
# Store request details early
|
|
730
732
|
self.store_result('request_method', self.method)
|
|
731
733
|
self.store_result('url', resolved_url)
|
|
@@ -736,7 +738,7 @@ class ApiRequestAction(Action):
|
|
|
736
738
|
if self.data is not None:
|
|
737
739
|
self.store_result('request_data', self.data)
|
|
738
740
|
self.store_result('request_headers', _mask_headers(final_headers))
|
|
739
|
-
|
|
741
|
+
|
|
740
742
|
logger = logging.getLogger("Journey.ApiRequestAction")
|
|
741
743
|
# Honor any pending rate-limit resume time set by previous actions/steps
|
|
742
744
|
try:
|
|
@@ -861,14 +863,25 @@ class ApiRequestAction(Action):
|
|
|
861
863
|
|
|
862
864
|
# Determine success (status-based by default)
|
|
863
865
|
if self.expected_status is not None:
|
|
864
|
-
http_ok = (
|
|
866
|
+
http_ok = (status_code == self.expected_status)
|
|
867
|
+
if not http_ok:
|
|
868
|
+
self.store_result('status_mismatch', f"Expected status {self.expected_status}, got {status_code}")
|
|
869
|
+
logger.warning(f"API request status mismatch: expected {self.expected_status}, got {status_code}")
|
|
865
870
|
else:
|
|
866
871
|
http_ok = bool(getattr(response, 'ok', False))
|
|
867
872
|
|
|
873
|
+
# Store the final status check result
|
|
874
|
+
self.store_result('http_status_ok', http_ok)
|
|
875
|
+
|
|
868
876
|
# Optionally fail on validation error
|
|
869
877
|
if self.response_model is not None and self.fail_on_validation_error and self.get_result('response_validation_error'):
|
|
870
878
|
return False
|
|
871
879
|
|
|
880
|
+
if self.flush:
|
|
881
|
+
clear_requests_session(context)
|
|
882
|
+
|
|
883
|
+
# Return False if status doesn't match expected, regardless of expected_result
|
|
884
|
+
# The framework will then compare this with expected_result to determine test outcome
|
|
872
885
|
return http_ok
|
|
873
886
|
except Exception as e:
|
|
874
887
|
last_exception = e
|
|
@@ -878,3 +891,12 @@ class ApiRequestAction(Action):
|
|
|
878
891
|
|
|
879
892
|
# If we got here and had an exception or no return, fail
|
|
880
893
|
return False
|
|
894
|
+
|
|
895
|
+
def clear_requests_session(context: Dict[str, Any]):
|
|
896
|
+
"""Clear the request session from the context."""
|
|
897
|
+
logger = logging.getLogger("Journey.ApiRequestAction")
|
|
898
|
+
session = context.get('requests_session')
|
|
899
|
+
if session is not None:
|
|
900
|
+
session.close()
|
|
901
|
+
context['requests_session'] = None
|
|
902
|
+
logger.info("Cleared requests session from context")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scythe-ttp
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: An extensible framework for emulating attacker TTPs with Selenium.
|
|
5
5
|
Author-email: EpykLab <cyber@epyklab.com>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -35,6 +35,7 @@ Requires-Dist: urllib3==2.4.0
|
|
|
35
35
|
Requires-Dist: websocket-client==1.8.0
|
|
36
36
|
Requires-Dist: wsproto==1.2.0
|
|
37
37
|
Requires-Dist: typer
|
|
38
|
+
Requires-Dist: shellingham
|
|
38
39
|
Dynamic: license-file
|
|
39
40
|
|
|
40
41
|
<h1 align="center">Scythe</h1>
|
scythe_ttp-0.15.10/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.15.10
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|