RestrictedPython 7.2a1.dev0__py3-none-any.whl → 7.4__py3-none-any.whl
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.
- RestrictedPython/Guards.py +17 -1
- RestrictedPython/Utilities.py +5 -1
- RestrictedPython/_compat.py +0 -1
- RestrictedPython/transformer.py +30 -80
- {RestrictedPython-7.2a1.dev0.dist-info → RestrictedPython-7.4.dist-info}/METADATA +40 -12
- RestrictedPython-7.4.dist-info/RECORD +14 -0
- {RestrictedPython-7.2a1.dev0.dist-info → RestrictedPython-7.4.dist-info}/WHEEL +1 -1
- RestrictedPython-7.2a1.dev0.dist-info/RECORD +0 -14
- {RestrictedPython-7.2a1.dev0.dist-info → RestrictedPython-7.4.dist-info}/LICENSE.txt +0 -0
- {RestrictedPython-7.2a1.dev0.dist-info → RestrictedPython-7.4.dist-info}/top_level.txt +0 -0
RestrictedPython/Guards.py
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import builtins
|
|
19
19
|
|
|
20
20
|
from RestrictedPython._compat import IS_PY311_OR_GREATER
|
|
21
|
+
from RestrictedPython.transformer import INSPECT_ATTRIBUTES
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
safe_builtins = {}
|
|
@@ -239,6 +240,9 @@ def guarded_delattr(object, name):
|
|
|
239
240
|
safe_builtins['delattr'] = guarded_delattr
|
|
240
241
|
|
|
241
242
|
|
|
243
|
+
raise_ = object()
|
|
244
|
+
|
|
245
|
+
|
|
242
246
|
def safer_getattr(object, name, default=None, getattr=getattr):
|
|
243
247
|
"""Getattr implementation which prevents using format on string objects.
|
|
244
248
|
|
|
@@ -246,22 +250,34 @@ def safer_getattr(object, name, default=None, getattr=getattr):
|
|
|
246
250
|
http://lucumr.pocoo.org/2016/12/29/careful-with-str-format/
|
|
247
251
|
|
|
248
252
|
"""
|
|
253
|
+
if type(name) is not str:
|
|
254
|
+
raise TypeError('type(name) must be str')
|
|
249
255
|
if name in ('format', 'format_map') and (
|
|
250
256
|
isinstance(object, str) or
|
|
251
257
|
(isinstance(object, type) and issubclass(object, str))):
|
|
252
258
|
raise NotImplementedError(
|
|
253
259
|
'Using the format*() methods of `str` is not safe')
|
|
260
|
+
if name in INSPECT_ATTRIBUTES:
|
|
261
|
+
raise AttributeError(
|
|
262
|
+
f'"{name}" is a restricted name,'
|
|
263
|
+
' that is forbidden to access in RestrictedPython.')
|
|
254
264
|
if name.startswith('_'):
|
|
255
265
|
raise AttributeError(
|
|
256
266
|
'"{name}" is an invalid attribute name because it '
|
|
257
267
|
'starts with "_"'.format(name=name)
|
|
258
268
|
)
|
|
259
|
-
|
|
269
|
+
args = (object, name) + (() if default is raise_ else (default,))
|
|
270
|
+
return getattr(*args)
|
|
260
271
|
|
|
261
272
|
|
|
262
273
|
safe_builtins['_getattr_'] = safer_getattr
|
|
263
274
|
|
|
264
275
|
|
|
276
|
+
def safer_getattr_raise(object, name, default=raise_):
|
|
277
|
+
"""like ``safer_getattr`` but raising ``AttributeError`` if failing."""
|
|
278
|
+
return safer_getattr(object, name, default)
|
|
279
|
+
|
|
280
|
+
|
|
265
281
|
def guarded_iter_unpack_sequence(it, spec, _getiter_):
|
|
266
282
|
"""Protect sequence unpacking of targets in a 'for loop'.
|
|
267
283
|
|
RestrictedPython/Utilities.py
CHANGED
|
@@ -29,7 +29,11 @@ class _AttributeDelegator:
|
|
|
29
29
|
if attr in self.__excludes:
|
|
30
30
|
raise NotImplementedError(
|
|
31
31
|
f"{self.__mod.__name__}.{attr} is not safe")
|
|
32
|
-
|
|
32
|
+
try:
|
|
33
|
+
return getattr(self.__mod, attr)
|
|
34
|
+
except AttributeError as e:
|
|
35
|
+
e.obj = self
|
|
36
|
+
raise
|
|
33
37
|
|
|
34
38
|
|
|
35
39
|
utility_builtins['string'] = _AttributeDelegator(string, "Formatter")
|
RestrictedPython/_compat.py
CHANGED
|
@@ -3,7 +3,6 @@ import sys
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
_version = sys.version_info
|
|
6
|
-
IS_PY38_OR_GREATER = _version.major == 3 and _version.minor >= 8
|
|
7
6
|
IS_PY310_OR_GREATER = _version.major == 3 and _version.minor >= 10
|
|
8
7
|
IS_PY311_OR_GREATER = _version.major == 3 and _version.minor >= 11
|
|
9
8
|
IS_PY312_OR_GREATER = _version.major == 3 and _version.minor >= 12
|
RestrictedPython/transformer.py
CHANGED
|
@@ -22,16 +22,6 @@ import ast
|
|
|
22
22
|
import contextlib
|
|
23
23
|
import textwrap
|
|
24
24
|
|
|
25
|
-
from ._compat import IS_PY38_OR_GREATER
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
# Avoid DeprecationWarnings under Python 3.12 and up
|
|
29
|
-
if IS_PY38_OR_GREATER:
|
|
30
|
-
astStr = ast.Constant
|
|
31
|
-
astNum = ast.Constant
|
|
32
|
-
else: # pragma: no cover
|
|
33
|
-
astStr = ast.Str
|
|
34
|
-
astNum = ast.Num
|
|
35
25
|
|
|
36
26
|
# For AugAssign the operator must be converted to a string.
|
|
37
27
|
IOPERATOR_TO_STR = {
|
|
@@ -127,16 +117,14 @@ def copy_locations(new_node, old_node):
|
|
|
127
117
|
assert 'lineno' in new_node._attributes
|
|
128
118
|
new_node.lineno = old_node.lineno
|
|
129
119
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
new_node.end_lineno = old_node.end_lineno
|
|
120
|
+
assert 'end_lineno' in new_node._attributes
|
|
121
|
+
new_node.end_lineno = old_node.end_lineno
|
|
133
122
|
|
|
134
123
|
assert 'col_offset' in new_node._attributes
|
|
135
124
|
new_node.col_offset = old_node.col_offset
|
|
136
125
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
new_node.end_col_offset = old_node.end_col_offset
|
|
126
|
+
assert 'end_col_offset' in new_node._attributes
|
|
127
|
+
new_node.end_col_offset = old_node.end_col_offset
|
|
140
128
|
|
|
141
129
|
ast.fix_missing_locations(new_node)
|
|
142
130
|
|
|
@@ -280,7 +268,7 @@ class RestrictingNodeTransformer(ast.NodeTransformer):
|
|
|
280
268
|
"""
|
|
281
269
|
spec = ast.Dict(keys=[], values=[])
|
|
282
270
|
|
|
283
|
-
spec.keys.append(
|
|
271
|
+
spec.keys.append(ast.Constant('childs'))
|
|
284
272
|
spec.values.append(ast.Tuple([], ast.Load()))
|
|
285
273
|
|
|
286
274
|
# starred elements in a sequence do not contribute into the min_len.
|
|
@@ -300,12 +288,12 @@ class RestrictingNodeTransformer(ast.NodeTransformer):
|
|
|
300
288
|
|
|
301
289
|
elif isinstance(val, ast.Tuple):
|
|
302
290
|
el = ast.Tuple([], ast.Load())
|
|
303
|
-
el.elts.append(
|
|
291
|
+
el.elts.append(ast.Constant(idx - offset))
|
|
304
292
|
el.elts.append(self.gen_unpack_spec(val))
|
|
305
293
|
spec.values[0].elts.append(el)
|
|
306
294
|
|
|
307
|
-
spec.keys.append(
|
|
308
|
-
spec.values.append(
|
|
295
|
+
spec.keys.append(ast.Constant('min_len'))
|
|
296
|
+
spec.values.append(ast.Constant(min_len))
|
|
309
297
|
|
|
310
298
|
return spec
|
|
311
299
|
|
|
@@ -492,9 +480,8 @@ class RestrictingNodeTransformer(ast.NodeTransformer):
|
|
|
492
480
|
if isinstance(node, ast.Module):
|
|
493
481
|
_print.lineno = position
|
|
494
482
|
_print.col_offset = position
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
_print.end_col_offset = position
|
|
483
|
+
_print.end_lineno = position
|
|
484
|
+
_print.end_col_offset = position
|
|
498
485
|
ast.fix_missing_locations(_print)
|
|
499
486
|
else:
|
|
500
487
|
copy_locations(_print, node)
|
|
@@ -535,63 +522,26 @@ class RestrictingNodeTransformer(ast.NodeTransformer):
|
|
|
535
522
|
|
|
536
523
|
# ast for Literals
|
|
537
524
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
def visit_Constant(self, node):
|
|
541
|
-
"""Allow constant literals with restriction for Ellipsis.
|
|
542
|
-
|
|
543
|
-
Constant replaces Num, Str, Bytes, NameConstant and Ellipsis in
|
|
544
|
-
Python 3.8+.
|
|
545
|
-
:see: https://docs.python.org/dev/whatsnew/3.8.html#deprecated
|
|
546
|
-
"""
|
|
547
|
-
if node.value is Ellipsis:
|
|
548
|
-
# Deny using `...`.
|
|
549
|
-
# Special handling necessary as ``self.not_allowed(node)``
|
|
550
|
-
# would return the Error Message:
|
|
551
|
-
# 'Constant statements are not allowed.'
|
|
552
|
-
# which is only partial true.
|
|
553
|
-
self.error(node, 'Ellipsis statements are not allowed.')
|
|
554
|
-
return
|
|
555
|
-
return self.node_contents_visit(node)
|
|
556
|
-
|
|
557
|
-
else:
|
|
558
|
-
|
|
559
|
-
def visit_Num(self, node):
|
|
560
|
-
"""Allow integer numbers without restrictions.
|
|
561
|
-
|
|
562
|
-
Replaced by Constant in Python 3.8.
|
|
563
|
-
"""
|
|
564
|
-
return self.node_contents_visit(node)
|
|
565
|
-
|
|
566
|
-
def visit_Str(self, node):
|
|
567
|
-
"""Allow string literals without restrictions.
|
|
568
|
-
|
|
569
|
-
Replaced by Constant in Python 3.8.
|
|
570
|
-
"""
|
|
571
|
-
return self.node_contents_visit(node)
|
|
525
|
+
def visit_Constant(self, node):
|
|
526
|
+
"""Allow constant literals with restriction for Ellipsis.
|
|
572
527
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
def visit_NameConstant(self, node):
|
|
588
|
-
"""Allow constant literals (True, False, None) without ...
|
|
589
|
-
|
|
590
|
-
restrictions.
|
|
528
|
+
Constant replaces Num, Str, Bytes, NameConstant and Ellipsis in
|
|
529
|
+
Python 3.8+.
|
|
530
|
+
:see: https://docs.python.org/dev/whatsnew/3.8.html#deprecated
|
|
531
|
+
"""
|
|
532
|
+
if node.value is Ellipsis:
|
|
533
|
+
# Deny using `...`.
|
|
534
|
+
# Special handling necessary as ``self.not_allowed(node)``
|
|
535
|
+
# would return the Error Message:
|
|
536
|
+
# 'Constant statements are not allowed.'
|
|
537
|
+
# which is only partial true.
|
|
538
|
+
self.error(node, 'Ellipsis statements are not allowed.')
|
|
539
|
+
return
|
|
540
|
+
return self.node_contents_visit(node)
|
|
591
541
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
542
|
+
def visit_Interactive(self, node):
|
|
543
|
+
"""Allow single mode without restrictions."""
|
|
544
|
+
return self.node_contents_visit(node)
|
|
595
545
|
|
|
596
546
|
def visit_List(self, node):
|
|
597
547
|
"""Allow list literals without restrictions."""
|
|
@@ -911,7 +861,7 @@ class RestrictingNodeTransformer(ast.NodeTransformer):
|
|
|
911
861
|
node = self.node_contents_visit(node)
|
|
912
862
|
new_node = ast.Call(
|
|
913
863
|
func=ast.Name('_getattr_', ast.Load()),
|
|
914
|
-
args=[node.value,
|
|
864
|
+
args=[node.value, ast.Constant(node.attr)],
|
|
915
865
|
keywords=[])
|
|
916
866
|
|
|
917
867
|
copy_locations(new_node, node)
|
|
@@ -1115,7 +1065,7 @@ class RestrictingNodeTransformer(ast.NodeTransformer):
|
|
|
1115
1065
|
value=ast.Call(
|
|
1116
1066
|
func=ast.Name('_inplacevar_', ast.Load()),
|
|
1117
1067
|
args=[
|
|
1118
|
-
|
|
1068
|
+
ast.Constant(IOPERATOR_TO_STR[type(node.op)]),
|
|
1119
1069
|
ast.Name(node.target.id, ast.Load()),
|
|
1120
1070
|
node.value
|
|
1121
1071
|
],
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: RestrictedPython
|
|
3
|
-
Version: 7.
|
|
3
|
+
Version: 7.4
|
|
4
4
|
Summary: RestrictedPython is a defined subset of the Python language which allows to provide a program input into a trusted environment.
|
|
5
5
|
Home-page: https://github.com/zopefoundation/RestrictedPython
|
|
6
6
|
Author: Zope Foundation and Contributors
|
|
7
|
-
Author-email: zope-dev@zope.
|
|
7
|
+
Author-email: zope-dev@zope.dev
|
|
8
8
|
License: ZPL 2.1
|
|
9
9
|
Project-URL: Documentation, https://restrictedpython.readthedocs.io/
|
|
10
10
|
Project-URL: Source, https://github.com/zopefoundation/RestrictedPython
|
|
@@ -15,23 +15,23 @@ Classifier: License :: OSI Approved :: Zope Public License
|
|
|
15
15
|
Classifier: Programming Language :: Python
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
17
|
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.7
|
|
19
18
|
Classifier: Programming Language :: Python :: 3.8
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.9
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
24
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
25
25
|
Classifier: Topic :: Security
|
|
26
|
-
Requires-Python: >=3.
|
|
26
|
+
Requires-Python: >=3.8, <3.14
|
|
27
27
|
Description-Content-Type: text/x-rst
|
|
28
28
|
License-File: LICENSE.txt
|
|
29
29
|
Provides-Extra: docs
|
|
30
|
-
Requires-Dist: Sphinx
|
|
31
|
-
Requires-Dist:
|
|
30
|
+
Requires-Dist: Sphinx; extra == "docs"
|
|
31
|
+
Requires-Dist: furo; extra == "docs"
|
|
32
32
|
Provides-Extra: test
|
|
33
|
-
Requires-Dist: pytest
|
|
34
|
-
Requires-Dist: pytest-mock
|
|
33
|
+
Requires-Dist: pytest; extra == "test"
|
|
34
|
+
Requires-Dist: pytest-mock; extra == "test"
|
|
35
35
|
|
|
36
36
|
.. image:: https://github.com/zopefoundation/RestrictedPython/actions/workflows/tests.yml/badge.svg
|
|
37
37
|
:target: https://github.com/zopefoundation/RestrictedPython/actions/workflows/tests.yml
|
|
@@ -124,11 +124,39 @@ the documentation `Contributing page
|
|
|
124
124
|
Changes
|
|
125
125
|
=======
|
|
126
126
|
|
|
127
|
-
7.
|
|
128
|
-
|
|
127
|
+
7.4 (2024-10-09)
|
|
128
|
+
----------------
|
|
129
|
+
|
|
130
|
+
- Allow to use the package with Python 3.13.
|
|
131
|
+
|
|
132
|
+
- Drop support for Python 3.7.
|
|
133
|
+
|
|
134
|
+
- Provide new function ``RestrictedPython.Guards.safer_getattr_raise``.
|
|
135
|
+
It is similar to ``safer_getattr`` but handles its parameter
|
|
136
|
+
``default`` like ``getattr``, i.e. it raises ``AttributeError``
|
|
137
|
+
if the attribute lookup fails and this parameter is not provided,
|
|
138
|
+
fixes `#287 <https://github.com/zopefoundation/RestrictedPython/issues/287>`_.
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
7.3 (2024-09-30)
|
|
142
|
+
----------------
|
|
143
|
+
|
|
144
|
+
- Increase the safety level of ``safer_getattr`` allowing applications to use
|
|
145
|
+
it as ``getattr`` implementation. Such use should now follow the same policy
|
|
146
|
+
and give the same level of protection as direct attribute access in an
|
|
147
|
+
environment based on ``RestrictedPython``'s ``safe_builtints``.
|
|
148
|
+
- Prevent information leakage via ``AttributeError.obj``
|
|
149
|
+
and the ``string`` module. (CVE-2024-47532)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
7.2 (2024-08-02)
|
|
153
|
+
----------------
|
|
129
154
|
|
|
130
|
-
-
|
|
131
|
-
|
|
155
|
+
- Remove unneeded setuptools fossils that may cause installation problems
|
|
156
|
+
with recent setuptools versions.
|
|
157
|
+
- Add support for single mode statements / execution.
|
|
158
|
+
- Fix a potential breakout capability in the provided ``safer_getattr`` method
|
|
159
|
+
that is part of the ``safer_builtins``.
|
|
132
160
|
|
|
133
161
|
|
|
134
162
|
7.1 (2024-03-14)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
RestrictedPython/Eval.py,sha256=pa79tc-JsT7xfzwg0ceMkxyioIEnFbNHc_PsKUhkkj8,3201
|
|
2
|
+
RestrictedPython/Guards.py,sha256=Ls4eJa94nj6z6L65faEBCX8m7dHr3iF4n8-HlFpMniE,8217
|
|
3
|
+
RestrictedPython/Limits.py,sha256=dORpuly21vSjy8gzNac9IYfIXMMWRVFvqUiKKIeZ3OM,1866
|
|
4
|
+
RestrictedPython/PrintCollector.py,sha256=bBCpnUPOuKz1wJDuSgh7wo2aoKfcTJeeT8OYnM-K9F8,1137
|
|
5
|
+
RestrictedPython/Utilities.py,sha256=u4HUdyjGawaeHyXSakyt4gRT17BZietXnF5WqicujjE,3033
|
|
6
|
+
RestrictedPython/__init__.py,sha256=qB_s6zDxuXPAGMoKYKBMc-xZ0gTnQ0ZvtY5FxdAG3aM,1862
|
|
7
|
+
RestrictedPython/_compat.py,sha256=2Mew5xHBB0Lg3YfhbFyTdOSt4TQCWnEBGQ1SNFeR8a0,318
|
|
8
|
+
RestrictedPython/compile.py,sha256=IhcF733t-bkPcvfQ2_NyBeCbSIPtHYxR-GQNNHnaMHM,6727
|
|
9
|
+
RestrictedPython/transformer.py,sha256=qpvsswxFDkVTra48qxQivnAbBoVdmEVLgdDKCwwt2cI,41418
|
|
10
|
+
RestrictedPython-7.4.dist-info/LICENSE.txt,sha256=PmcdsR32h1FswdtbPWXkqjg-rKPCDOo_r1Og9zNdCjw,2070
|
|
11
|
+
RestrictedPython-7.4.dist-info/METADATA,sha256=oU8AXsXRUKtaYTnteX0ulTyVZQVM2gQBdXeRDGwKLHg,13448
|
|
12
|
+
RestrictedPython-7.4.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
|
|
13
|
+
RestrictedPython-7.4.dist-info/top_level.txt,sha256=E1-3ARWcduVJnQAScms0FgqnBx_PovrzYsNMYuLGwa0,17
|
|
14
|
+
RestrictedPython-7.4.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
RestrictedPython/Eval.py,sha256=pa79tc-JsT7xfzwg0ceMkxyioIEnFbNHc_PsKUhkkj8,3201
|
|
2
|
-
RestrictedPython/Guards.py,sha256=XXupE0TwcWdb6qAsSexWUYEIeT3bVLTdOVvn94eoqs0,7646
|
|
3
|
-
RestrictedPython/Limits.py,sha256=dORpuly21vSjy8gzNac9IYfIXMMWRVFvqUiKKIeZ3OM,1866
|
|
4
|
-
RestrictedPython/PrintCollector.py,sha256=bBCpnUPOuKz1wJDuSgh7wo2aoKfcTJeeT8OYnM-K9F8,1137
|
|
5
|
-
RestrictedPython/Utilities.py,sha256=7R6Op1Oqw9-fSxy_eWVa6ioZPx-SAxa7OolDObTWRkU,2937
|
|
6
|
-
RestrictedPython/__init__.py,sha256=qB_s6zDxuXPAGMoKYKBMc-xZ0gTnQ0ZvtY5FxdAG3aM,1862
|
|
7
|
-
RestrictedPython/_compat.py,sha256=nacdAJi4E8GKhkR99_BAxMA0AtK2FQnvrqZbG8hGofc,383
|
|
8
|
-
RestrictedPython/compile.py,sha256=IhcF733t-bkPcvfQ2_NyBeCbSIPtHYxR-GQNNHnaMHM,6727
|
|
9
|
-
RestrictedPython/transformer.py,sha256=AsPDuEN42OrBZBDtDCOFcWssNZrvm8PBGku8IvREXFg,42766
|
|
10
|
-
RestrictedPython-7.2a1.dev0.dist-info/LICENSE.txt,sha256=PmcdsR32h1FswdtbPWXkqjg-rKPCDOo_r1Og9zNdCjw,2070
|
|
11
|
-
RestrictedPython-7.2a1.dev0.dist-info/METADATA,sha256=V23yHfhhH7oSZr2ClO4fp6hkl0XVT5Xr1bveMPANdfw,12397
|
|
12
|
-
RestrictedPython-7.2a1.dev0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
13
|
-
RestrictedPython-7.2a1.dev0.dist-info/top_level.txt,sha256=E1-3ARWcduVJnQAScms0FgqnBx_PovrzYsNMYuLGwa0,17
|
|
14
|
-
RestrictedPython-7.2a1.dev0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|