clue-api 1.5.0.dev244__tar.gz → 1.5.0.dev251__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.
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/PKG-INFO +1 -1
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/plugin/helpers/email_render.py +137 -6
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/pyproject.toml +1 -1
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/LICENSE +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/README.md +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/.gitignore +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/base.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/v1/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/v1/actions.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/v1/auth.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/v1/configs.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/v1/fetchers.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/v1/lookup.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/v1/registration.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/v1/static.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/api/v1/sync.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/app.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/cache/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/bytes_utils.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/classification.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/classification.yml +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/dict_utils.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/exceptions.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/forge.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/json_utils.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/list_utils.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/logging/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/logging/audit.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/logging/format.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/regex.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/str_utils.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/swagger.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/common/uid.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/config.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/constants/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/constants/env.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/constants/supported_types.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/cronjobs/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/cronjobs/plugins.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/error.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/extensions/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/extensions/config.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/gunicorn_config.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/healthz.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/helper/discover.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/helper/headers.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/helper/oauth.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/actions.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/config.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/fetchers.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/graph.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/model_list.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/network.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/results/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/results/base.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/results/file.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/results/graph.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/results/image.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/results/status.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/results/validation.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/schema.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/selector.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/sync.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/models/validators.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/patched.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/plugin/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/plugin/celery_app.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/plugin/helpers/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/plugin/helpers/central_server.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/plugin/helpers/token.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/plugin/helpers/trino.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/plugin/models.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/plugin/utils.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/py.typed +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/remote/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/remote/datatypes/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/remote/datatypes/cache.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/remote/datatypes/events.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/remote/datatypes/hash.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/remote/datatypes/queues/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/remote/datatypes/queues/comms.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/remote/datatypes/set.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/remote/datatypes/user_quota_tracker.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/security/__init__.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/security/obo.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/security/utils.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/services/action_service.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/services/auth_service.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/services/config_service.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/services/fetcher_service.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/services/jwt_service.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/services/lookup_service.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/services/mongo_service.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/services/type_service.py +0 -0
- {clue_api-1.5.0.dev244 → clue_api-1.5.0.dev251}/clue/services/user_service.py +0 -0
|
@@ -93,7 +93,98 @@ def filter_elements(payload: str) -> str:
|
|
|
93
93
|
return cast(str, soup.prettify())
|
|
94
94
|
|
|
95
95
|
|
|
96
|
-
def
|
|
96
|
+
def _render_simplified_part(payload: str, output_path: str, imgkit_options: dict, viewport_width: int) -> None:
|
|
97
|
+
"Render a text MIME part in simplified mode: no word-wrap, overflow clipped, with a truncation badge if needed."
|
|
98
|
+
probe_html = f"""
|
|
99
|
+
<html>
|
|
100
|
+
<head>
|
|
101
|
+
<style>
|
|
102
|
+
body * {{ white-space: nowrap !important; word-wrap: normal !important;
|
|
103
|
+
word-break: normal !important; }}
|
|
104
|
+
</style>
|
|
105
|
+
</head>
|
|
106
|
+
<body>
|
|
107
|
+
{payload}
|
|
108
|
+
</body>
|
|
109
|
+
</html>
|
|
110
|
+
"""
|
|
111
|
+
probe_fd, probe_path = tempfile.mkstemp(suffix=".jpeg")
|
|
112
|
+
os.close(probe_fd)
|
|
113
|
+
probe_width: int | None = None
|
|
114
|
+
overflows = False
|
|
115
|
+
try:
|
|
116
|
+
imgkit.from_string(probe_html, probe_path, options=imgkit_options)
|
|
117
|
+
try:
|
|
118
|
+
with Image.open(probe_path) as probe_img:
|
|
119
|
+
probe_width = probe_img.size[0]
|
|
120
|
+
except Image.DecompressionBombError:
|
|
121
|
+
logger.warning(
|
|
122
|
+
"Probe image exceeded PIL decompression-bomb limits; treating payload as overflowed",
|
|
123
|
+
)
|
|
124
|
+
overflows = True
|
|
125
|
+
finally:
|
|
126
|
+
if os.path.exists(probe_path):
|
|
127
|
+
os.remove(probe_path)
|
|
128
|
+
|
|
129
|
+
if probe_width is not None:
|
|
130
|
+
overflows = probe_width > viewport_width
|
|
131
|
+
if overflows:
|
|
132
|
+
if probe_width is not None:
|
|
133
|
+
logger.warning(
|
|
134
|
+
"Payload overflowed viewport (%dpx > %dpx), adding truncation indicator",
|
|
135
|
+
probe_width,
|
|
136
|
+
viewport_width,
|
|
137
|
+
)
|
|
138
|
+
else:
|
|
139
|
+
logger.warning(
|
|
140
|
+
"Payload may overflow viewport; adding truncation indicator",
|
|
141
|
+
)
|
|
142
|
+
truncation_open = (
|
|
143
|
+
"<div style='border: 2px dashed red; padding: 4px;'>"
|
|
144
|
+
"<div style='color: red; font-size: 24px;'><b>[Content truncated / Contenu tronqué]</b></div>"
|
|
145
|
+
"<div style='color: red; font-size: 12px;'>Use 'Full' mode to see full content / "
|
|
146
|
+
"Utilisez le mode « Full » pour afficher l'intégralité du contenu</div>"
|
|
147
|
+
if overflows
|
|
148
|
+
else ""
|
|
149
|
+
)
|
|
150
|
+
truncation_close = "</div>" if overflows else ""
|
|
151
|
+
final_html = f"""
|
|
152
|
+
<html>
|
|
153
|
+
<head>
|
|
154
|
+
<style>
|
|
155
|
+
body * {{ max-width: {viewport_width}px !important; overflow: hidden !important;
|
|
156
|
+
white-space: nowrap !important; word-wrap: normal !important;
|
|
157
|
+
word-break: normal !important; }}
|
|
158
|
+
</style>
|
|
159
|
+
</head>
|
|
160
|
+
<body style="max-width: {viewport_width}px; overflow: hidden;">
|
|
161
|
+
{truncation_open}
|
|
162
|
+
{payload}
|
|
163
|
+
{truncation_close}
|
|
164
|
+
</body>
|
|
165
|
+
</html>
|
|
166
|
+
"""
|
|
167
|
+
imgkit.from_string(final_html, output_path, options=imgkit_options)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _render_full_part(payload: str, output_path: str, imgkit_options: dict, viewport_width: int) -> None:
|
|
171
|
+
"Render a text MIME part in full mode: word-wrap enabled, no clipping."
|
|
172
|
+
html = f"""
|
|
173
|
+
<html>
|
|
174
|
+
<head>
|
|
175
|
+
<style>
|
|
176
|
+
body * {{ max-width: {viewport_width}px; word-wrap: break-word; }}
|
|
177
|
+
</style>
|
|
178
|
+
</head>
|
|
179
|
+
<body style="max-width: {viewport_width}px;">
|
|
180
|
+
{payload}
|
|
181
|
+
</body>
|
|
182
|
+
</html>
|
|
183
|
+
"""
|
|
184
|
+
imgkit.from_string(html, output_path, options=imgkit_options)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def process_eml(data, output_dir, load_images=False, mode="simplified"): # noqa: C901
|
|
97
188
|
"Process the email (bytes), extract MIME parts and useful headers. Generate a JPEG picture of the mail"
|
|
98
189
|
logger.debug("Beginning eml processing")
|
|
99
190
|
|
|
@@ -105,7 +196,8 @@ def process_eml(data, output_dir, load_images=False): # noqa: C901
|
|
|
105
196
|
subject_field = get_header_data(msg, "Subject")
|
|
106
197
|
id_field = get_header_data(msg, "Message-Id")
|
|
107
198
|
|
|
108
|
-
|
|
199
|
+
viewport_width = 2048
|
|
200
|
+
imgkit_options = {"load-error-handling": "skip", "no-images": None, "width": viewport_width}
|
|
109
201
|
|
|
110
202
|
images_list = []
|
|
111
203
|
|
|
@@ -155,7 +247,10 @@ def process_eml(data, output_dir, load_images=False): # noqa: C901
|
|
|
155
247
|
|
|
156
248
|
try:
|
|
157
249
|
payload_path = NamedTemporaryFile(suffix=".jpeg").name
|
|
158
|
-
|
|
250
|
+
if mode == "simplified":
|
|
251
|
+
_render_simplified_part(payload, payload_path, imgkit_options, viewport_width)
|
|
252
|
+
else:
|
|
253
|
+
_render_full_part(payload, payload_path, imgkit_options, viewport_width)
|
|
159
254
|
logger.info("Decoded %s" % payload_path)
|
|
160
255
|
images_list.append(payload_path)
|
|
161
256
|
except Exception as e:
|
|
@@ -175,8 +270,43 @@ def process_eml(data, output_dir, load_images=False): # noqa: C901
|
|
|
175
270
|
|
|
176
271
|
result_image = os.path.join(output_dir, "output.jpeg")
|
|
177
272
|
if len(images_list) > 0:
|
|
178
|
-
|
|
179
|
-
|
|
273
|
+
# Open images with decompression bomb protection
|
|
274
|
+
opened_images = []
|
|
275
|
+
for image_path in images_list:
|
|
276
|
+
try:
|
|
277
|
+
with Image.open(image_path) as img:
|
|
278
|
+
# Force load and convert to RGB to catch decompression bombs
|
|
279
|
+
rgb_img = img.convert("RGB")
|
|
280
|
+
opened_images.append(rgb_img)
|
|
281
|
+
except Image.DecompressionBombError:
|
|
282
|
+
logger.warning(f"Image too large (decompression bomb): {image_path}. Creating placeholder.")
|
|
283
|
+
# Create a placeholder HTML message
|
|
284
|
+
placeholder_html = """
|
|
285
|
+
<html>
|
|
286
|
+
<body style="background-color: #ffebee; padding: 20px; text-align: center;">
|
|
287
|
+
<h2 style="color: #c62828;">Component Too Large / Composant trop volumineux</h2>
|
|
288
|
+
<p>This image exceeds the maximum allowed size and cannot be displayed. /
|
|
289
|
+
Ce fichier joint dépasse la taille maximale autorisée et ne peut pas être affiché. </p>
|
|
290
|
+
</body>
|
|
291
|
+
</html>
|
|
292
|
+
"""
|
|
293
|
+
try:
|
|
294
|
+
placeholder_fd, placeholder_path = tempfile.mkstemp(suffix=".jpeg")
|
|
295
|
+
os.close(placeholder_fd)
|
|
296
|
+
try:
|
|
297
|
+
imgkit.from_string(placeholder_html, placeholder_path, options=imgkit_options)
|
|
298
|
+
with Image.open(placeholder_path) as placeholder_img:
|
|
299
|
+
# Force load and convert to RGB
|
|
300
|
+
rgb_placeholder = placeholder_img.convert("RGB")
|
|
301
|
+
opened_images.append(rgb_placeholder)
|
|
302
|
+
finally:
|
|
303
|
+
if os.path.exists(placeholder_path):
|
|
304
|
+
os.remove(placeholder_path)
|
|
305
|
+
except Exception:
|
|
306
|
+
logger.exception("Failed to create placeholder image")
|
|
307
|
+
# Continue without this image
|
|
308
|
+
|
|
309
|
+
combo = append_images(opened_images)
|
|
180
310
|
combo.save(result_image)
|
|
181
311
|
# Clean up temporary images
|
|
182
312
|
for i in images_list:
|
|
@@ -189,7 +319,7 @@ def process_eml(data, output_dir, load_images=False): # noqa: C901
|
|
|
189
319
|
raise ClueException("Error when processing email") from e
|
|
190
320
|
|
|
191
321
|
|
|
192
|
-
def render(email_path: str, cart_buffer: io.BytesIO) -> ImageResult | None:
|
|
322
|
+
def render(email_path: str, cart_buffer: io.BytesIO, mode: str = "simplified") -> ImageResult | None:
|
|
193
323
|
"Helper function that, given a buffer containing a carted email, returns an image rendering of it."
|
|
194
324
|
cart_buffer.seek(0)
|
|
195
325
|
buf = io.BytesIO()
|
|
@@ -203,6 +333,7 @@ def render(email_path: str, cart_buffer: io.BytesIO) -> ImageResult | None:
|
|
|
203
333
|
process_eml(
|
|
204
334
|
buf.read(),
|
|
205
335
|
tmp_dir,
|
|
336
|
+
mode=mode,
|
|
206
337
|
)
|
|
207
338
|
|
|
208
339
|
error = None
|
|
@@ -141,7 +141,7 @@ log_cli_level = "WARN"
|
|
|
141
141
|
[tool.poetry]
|
|
142
142
|
package-mode = true
|
|
143
143
|
name = "clue-api"
|
|
144
|
-
version = "1.5.0.
|
|
144
|
+
version = "1.5.0.dev251"
|
|
145
145
|
description = "Clue distributed enrichment service"
|
|
146
146
|
authors = ["Canadian Centre for Cyber Security <contact@cyber.gc.ca>"]
|
|
147
147
|
license = "MIT"
|
|
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
|
|
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
|