koichi12 commited on
Commit
5dbe6b9
·
verified ·
1 Parent(s): 6853990

Add files using upload-large-folder tool

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .venv/lib/python3.11/site-packages/jinja2/__init__.py +38 -0
  2. .venv/lib/python3.11/site-packages/jinja2/__pycache__/async_utils.cpython-311.pyc +0 -0
  3. .venv/lib/python3.11/site-packages/jinja2/__pycache__/bccache.cpython-311.pyc +0 -0
  4. .venv/lib/python3.11/site-packages/jinja2/__pycache__/defaults.cpython-311.pyc +0 -0
  5. .venv/lib/python3.11/site-packages/jinja2/__pycache__/environment.cpython-311.pyc +0 -0
  6. .venv/lib/python3.11/site-packages/jinja2/__pycache__/exceptions.cpython-311.pyc +0 -0
  7. .venv/lib/python3.11/site-packages/jinja2/__pycache__/lexer.cpython-311.pyc +0 -0
  8. .venv/lib/python3.11/site-packages/jinja2/__pycache__/meta.cpython-311.pyc +0 -0
  9. .venv/lib/python3.11/site-packages/jinja2/__pycache__/nativetypes.cpython-311.pyc +0 -0
  10. .venv/lib/python3.11/site-packages/jinja2/__pycache__/parser.cpython-311.pyc +0 -0
  11. .venv/lib/python3.11/site-packages/jinja2/_identifier.py +6 -0
  12. .venv/lib/python3.11/site-packages/jinja2/bccache.py +408 -0
  13. .venv/lib/python3.11/site-packages/jinja2/constants.py +20 -0
  14. .venv/lib/python3.11/site-packages/jinja2/environment.py +1672 -0
  15. .venv/lib/python3.11/site-packages/jinja2/ext.py +870 -0
  16. .venv/lib/python3.11/site-packages/jinja2/lexer.py +868 -0
  17. .venv/lib/python3.11/site-packages/jinja2/loaders.py +693 -0
  18. .venv/lib/python3.11/site-packages/jinja2/optimizer.py +48 -0
  19. .venv/lib/python3.11/site-packages/jinja2/parser.py +1049 -0
  20. .venv/lib/python3.11/site-packages/rsa/__init__.py +60 -0
  21. .venv/lib/python3.11/site-packages/rsa/__pycache__/__init__.cpython-311.pyc +0 -0
  22. .venv/lib/python3.11/site-packages/rsa/__pycache__/asn1.cpython-311.pyc +0 -0
  23. .venv/lib/python3.11/site-packages/rsa/__pycache__/cli.cpython-311.pyc +0 -0
  24. .venv/lib/python3.11/site-packages/rsa/__pycache__/common.cpython-311.pyc +0 -0
  25. .venv/lib/python3.11/site-packages/rsa/__pycache__/core.cpython-311.pyc +0 -0
  26. .venv/lib/python3.11/site-packages/rsa/__pycache__/key.cpython-311.pyc +0 -0
  27. .venv/lib/python3.11/site-packages/rsa/__pycache__/parallel.cpython-311.pyc +0 -0
  28. .venv/lib/python3.11/site-packages/rsa/__pycache__/pem.cpython-311.pyc +0 -0
  29. .venv/lib/python3.11/site-packages/rsa/__pycache__/pkcs1.cpython-311.pyc +0 -0
  30. .venv/lib/python3.11/site-packages/rsa/__pycache__/pkcs1_v2.cpython-311.pyc +0 -0
  31. .venv/lib/python3.11/site-packages/rsa/__pycache__/prime.cpython-311.pyc +0 -0
  32. .venv/lib/python3.11/site-packages/rsa/__pycache__/randnum.cpython-311.pyc +0 -0
  33. .venv/lib/python3.11/site-packages/rsa/__pycache__/transform.cpython-311.pyc +0 -0
  34. .venv/lib/python3.11/site-packages/rsa/__pycache__/util.cpython-311.pyc +0 -0
  35. .venv/lib/python3.11/site-packages/rsa/asn1.py +52 -0
  36. .venv/lib/python3.11/site-packages/rsa/cli.py +321 -0
  37. .venv/lib/python3.11/site-packages/rsa/common.py +184 -0
  38. .venv/lib/python3.11/site-packages/rsa/core.py +53 -0
  39. .venv/lib/python3.11/site-packages/rsa/key.py +858 -0
  40. .venv/lib/python3.11/site-packages/rsa/parallel.py +96 -0
  41. .venv/lib/python3.11/site-packages/rsa/pem.py +134 -0
  42. .venv/lib/python3.11/site-packages/rsa/pkcs1.py +485 -0
  43. .venv/lib/python3.11/site-packages/rsa/pkcs1_v2.py +100 -0
  44. .venv/lib/python3.11/site-packages/rsa/prime.py +198 -0
  45. .venv/lib/python3.11/site-packages/rsa/py.typed +1 -0
  46. .venv/lib/python3.11/site-packages/rsa/transform.py +72 -0
  47. .venv/lib/python3.11/site-packages/rsa/util.py +97 -0
  48. .venv/lib/python3.11/site-packages/uvicorn/__init__.py +5 -0
  49. .venv/lib/python3.11/site-packages/uvicorn/__main__.py +4 -0
  50. .venv/lib/python3.11/site-packages/uvicorn/_subprocess.py +84 -0
.venv/lib/python3.11/site-packages/jinja2/__init__.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Jinja is a template engine written in pure Python. It provides a
2
+ non-XML syntax that supports inline expressions and an optional
3
+ sandboxed environment.
4
+ """
5
+
6
+ from .bccache import BytecodeCache as BytecodeCache
7
+ from .bccache import FileSystemBytecodeCache as FileSystemBytecodeCache
8
+ from .bccache import MemcachedBytecodeCache as MemcachedBytecodeCache
9
+ from .environment import Environment as Environment
10
+ from .environment import Template as Template
11
+ from .exceptions import TemplateAssertionError as TemplateAssertionError
12
+ from .exceptions import TemplateError as TemplateError
13
+ from .exceptions import TemplateNotFound as TemplateNotFound
14
+ from .exceptions import TemplateRuntimeError as TemplateRuntimeError
15
+ from .exceptions import TemplatesNotFound as TemplatesNotFound
16
+ from .exceptions import TemplateSyntaxError as TemplateSyntaxError
17
+ from .exceptions import UndefinedError as UndefinedError
18
+ from .loaders import BaseLoader as BaseLoader
19
+ from .loaders import ChoiceLoader as ChoiceLoader
20
+ from .loaders import DictLoader as DictLoader
21
+ from .loaders import FileSystemLoader as FileSystemLoader
22
+ from .loaders import FunctionLoader as FunctionLoader
23
+ from .loaders import ModuleLoader as ModuleLoader
24
+ from .loaders import PackageLoader as PackageLoader
25
+ from .loaders import PrefixLoader as PrefixLoader
26
+ from .runtime import ChainableUndefined as ChainableUndefined
27
+ from .runtime import DebugUndefined as DebugUndefined
28
+ from .runtime import make_logging_undefined as make_logging_undefined
29
+ from .runtime import StrictUndefined as StrictUndefined
30
+ from .runtime import Undefined as Undefined
31
+ from .utils import clear_caches as clear_caches
32
+ from .utils import is_undefined as is_undefined
33
+ from .utils import pass_context as pass_context
34
+ from .utils import pass_environment as pass_environment
35
+ from .utils import pass_eval_context as pass_eval_context
36
+ from .utils import select_autoescape as select_autoescape
37
+
38
+ __version__ = "3.1.5"
.venv/lib/python3.11/site-packages/jinja2/__pycache__/async_utils.cpython-311.pyc ADDED
Binary file (5.59 kB). View file
 
.venv/lib/python3.11/site-packages/jinja2/__pycache__/bccache.cpython-311.pyc ADDED
Binary file (20.9 kB). View file
 
.venv/lib/python3.11/site-packages/jinja2/__pycache__/defaults.cpython-311.pyc ADDED
Binary file (1.71 kB). View file
 
.venv/lib/python3.11/site-packages/jinja2/__pycache__/environment.cpython-311.pyc ADDED
Binary file (80.6 kB). View file
 
.venv/lib/python3.11/site-packages/jinja2/__pycache__/exceptions.cpython-311.pyc ADDED
Binary file (8.6 kB). View file
 
.venv/lib/python3.11/site-packages/jinja2/__pycache__/lexer.cpython-311.pyc ADDED
Binary file (35.7 kB). View file
 
.venv/lib/python3.11/site-packages/jinja2/__pycache__/meta.cpython-311.pyc ADDED
Binary file (5.69 kB). View file
 
.venv/lib/python3.11/site-packages/jinja2/__pycache__/nativetypes.cpython-311.pyc ADDED
Binary file (7.95 kB). View file
 
.venv/lib/python3.11/site-packages/jinja2/__pycache__/parser.cpython-311.pyc ADDED
Binary file (60 kB). View file
 
.venv/lib/python3.11/site-packages/jinja2/_identifier.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ import re
2
+
3
+ # generated by scripts/generate_identifier_pattern.py
4
+ pattern = re.compile(
5
+ r"[\w·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-ٰٟۖ-ۜ۟-۪ۤۧۨ-ܑۭܰ-݊ަ-ް߫-߽߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛࣓-ࣣ࣡-ःऺ-़ा-ॏ॑-ॗॢॣঁ-ঃ়া-ৄেৈো-্ৗৢৣ৾ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑੰੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣૺ-૿ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣஂா-ூெ-ைொ-்ௗఀ-ఄా-ౄె-ైొ-్ౕౖౢౣಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣഀ-ഃ഻഼ാ-ൄെ-ൈൊ-്ൗൢൣංඃ්ා-ුූෘ-ෟෲෳัิ-ฺ็-๎ັິ-ູົຼ່-ໍ༹༘༙༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏႚ-ႝ፝-፟ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝᠋-᠍ᢅᢆᢩᤠ-ᤫᤰ-᤻ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼᪰-᪽ᬀ-ᬄ᬴-᭄᭫-᭳ᮀ-ᮂᮡ-ᮭ᯦-᯳ᰤ-᰷᳐-᳔᳒-᳨᳭ᳲ-᳴᳷-᳹᷀-᷹᷻-᷿‿⁀⁔⃐-⃥⃜⃡-⃰℘℮⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꙯ꙴ-꙽ꚞꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-ꣅ꣠-꣱ꣿꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀ꧥꨩ-ꨶꩃꩌꩍꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭ﬞ︀-️︠-︯︳︴﹍-﹏_𐇽𐋠𐍶-𐍺𐨁-𐨃𐨅𐨆𐨌-𐨏𐨸-𐨿𐨺𐫦𐫥𐴤-𐽆𐴧-𐽐𑀀-𑀂𑀸-𑁆𑁿-𑂂𑂰-𑂺𑄀-𑄂𑄧-𑄴𑅅𑅆𑅳𑆀-𑆂𑆳-𑇀𑇉-𑇌𑈬-𑈷𑈾𑋟-𑋪𑌀-𑌃𑌻𑌼𑌾-𑍄𑍇𑍈𑍋-𑍍𑍗𑍢𑍣𑍦-𑍬𑍰-𑍴𑐵-𑑆𑑞𑒰-𑓃𑖯-𑖵𑖸-𑗀𑗜𑗝𑘰-𑙀𑚫-𑚷𑜝-𑜫𑠬-𑠺𑨁-𑨊𑨳-𑨹𑨻-𑨾𑩇𑩑-𑩛𑪊-𑪙𑰯-𑰶𑰸-𑰿𑲒-𑲧𑲩-𑲶𑴱-𑴶𑴺𑴼𑴽𑴿-𑵅𑵇𑶊-𑶎𑶐𑶑𑶓-𑶗𑻳-𑻶𖫰-𖫴𖬰-𖬶𖽑-𖽾𖾏-𖾒𛲝𛲞𝅥-𝅩𝅭-𝅲𝅻-𝆂𝆅-𝆋𝆪-𝆭𝉂-𝉄𝨀-𝨶𝨻-𝩬𝩵𝪄𝪛-𝪟𝪡-𝪯𞀀-𞀆𞀈-𞀘𞀛-𞀡𞀣𞀤𞀦-𞣐𞀪-𞣖𞥄-𞥊󠄀-󠇯]+" # noqa: B950
6
+ )
.venv/lib/python3.11/site-packages/jinja2/bccache.py ADDED
@@ -0,0 +1,408 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The optional bytecode cache system. This is useful if you have very
2
+ complex template situations and the compilation of all those templates
3
+ slows down your application too much.
4
+
5
+ Situations where this is useful are often forking web applications that
6
+ are initialized on the first request.
7
+ """
8
+
9
+ import errno
10
+ import fnmatch
11
+ import marshal
12
+ import os
13
+ import pickle
14
+ import stat
15
+ import sys
16
+ import tempfile
17
+ import typing as t
18
+ from hashlib import sha1
19
+ from io import BytesIO
20
+ from types import CodeType
21
+
22
+ if t.TYPE_CHECKING:
23
+ import typing_extensions as te
24
+
25
+ from .environment import Environment
26
+
27
+ class _MemcachedClient(te.Protocol):
28
+ def get(self, key: str) -> bytes: ...
29
+
30
+ def set(
31
+ self, key: str, value: bytes, timeout: t.Optional[int] = None
32
+ ) -> None: ...
33
+
34
+
35
+ bc_version = 5
36
+ # Magic bytes to identify Jinja bytecode cache files. Contains the
37
+ # Python major and minor version to avoid loading incompatible bytecode
38
+ # if a project upgrades its Python version.
39
+ bc_magic = (
40
+ b"j2"
41
+ + pickle.dumps(bc_version, 2)
42
+ + pickle.dumps((sys.version_info[0] << 24) | sys.version_info[1], 2)
43
+ )
44
+
45
+
46
+ class Bucket:
47
+ """Buckets are used to store the bytecode for one template. It's created
48
+ and initialized by the bytecode cache and passed to the loading functions.
49
+
50
+ The buckets get an internal checksum from the cache assigned and use this
51
+ to automatically reject outdated cache material. Individual bytecode
52
+ cache subclasses don't have to care about cache invalidation.
53
+ """
54
+
55
+ def __init__(self, environment: "Environment", key: str, checksum: str) -> None:
56
+ self.environment = environment
57
+ self.key = key
58
+ self.checksum = checksum
59
+ self.reset()
60
+
61
+ def reset(self) -> None:
62
+ """Resets the bucket (unloads the bytecode)."""
63
+ self.code: t.Optional[CodeType] = None
64
+
65
+ def load_bytecode(self, f: t.BinaryIO) -> None:
66
+ """Loads bytecode from a file or file like object."""
67
+ # make sure the magic header is correct
68
+ magic = f.read(len(bc_magic))
69
+ if magic != bc_magic:
70
+ self.reset()
71
+ return
72
+ # the source code of the file changed, we need to reload
73
+ checksum = pickle.load(f)
74
+ if self.checksum != checksum:
75
+ self.reset()
76
+ return
77
+ # if marshal_load fails then we need to reload
78
+ try:
79
+ self.code = marshal.load(f)
80
+ except (EOFError, ValueError, TypeError):
81
+ self.reset()
82
+ return
83
+
84
+ def write_bytecode(self, f: t.IO[bytes]) -> None:
85
+ """Dump the bytecode into the file or file like object passed."""
86
+ if self.code is None:
87
+ raise TypeError("can't write empty bucket")
88
+ f.write(bc_magic)
89
+ pickle.dump(self.checksum, f, 2)
90
+ marshal.dump(self.code, f)
91
+
92
+ def bytecode_from_string(self, string: bytes) -> None:
93
+ """Load bytecode from bytes."""
94
+ self.load_bytecode(BytesIO(string))
95
+
96
+ def bytecode_to_string(self) -> bytes:
97
+ """Return the bytecode as bytes."""
98
+ out = BytesIO()
99
+ self.write_bytecode(out)
100
+ return out.getvalue()
101
+
102
+
103
+ class BytecodeCache:
104
+ """To implement your own bytecode cache you have to subclass this class
105
+ and override :meth:`load_bytecode` and :meth:`dump_bytecode`. Both of
106
+ these methods are passed a :class:`~jinja2.bccache.Bucket`.
107
+
108
+ A very basic bytecode cache that saves the bytecode on the file system::
109
+
110
+ from os import path
111
+
112
+ class MyCache(BytecodeCache):
113
+
114
+ def __init__(self, directory):
115
+ self.directory = directory
116
+
117
+ def load_bytecode(self, bucket):
118
+ filename = path.join(self.directory, bucket.key)
119
+ if path.exists(filename):
120
+ with open(filename, 'rb') as f:
121
+ bucket.load_bytecode(f)
122
+
123
+ def dump_bytecode(self, bucket):
124
+ filename = path.join(self.directory, bucket.key)
125
+ with open(filename, 'wb') as f:
126
+ bucket.write_bytecode(f)
127
+
128
+ A more advanced version of a filesystem based bytecode cache is part of
129
+ Jinja.
130
+ """
131
+
132
+ def load_bytecode(self, bucket: Bucket) -> None:
133
+ """Subclasses have to override this method to load bytecode into a
134
+ bucket. If they are not able to find code in the cache for the
135
+ bucket, it must not do anything.
136
+ """
137
+ raise NotImplementedError()
138
+
139
+ def dump_bytecode(self, bucket: Bucket) -> None:
140
+ """Subclasses have to override this method to write the bytecode
141
+ from a bucket back to the cache. If it unable to do so it must not
142
+ fail silently but raise an exception.
143
+ """
144
+ raise NotImplementedError()
145
+
146
+ def clear(self) -> None:
147
+ """Clears the cache. This method is not used by Jinja but should be
148
+ implemented to allow applications to clear the bytecode cache used
149
+ by a particular environment.
150
+ """
151
+
152
+ def get_cache_key(
153
+ self, name: str, filename: t.Optional[t.Union[str]] = None
154
+ ) -> str:
155
+ """Returns the unique hash key for this template name."""
156
+ hash = sha1(name.encode("utf-8"))
157
+
158
+ if filename is not None:
159
+ hash.update(f"|{filename}".encode())
160
+
161
+ return hash.hexdigest()
162
+
163
+ def get_source_checksum(self, source: str) -> str:
164
+ """Returns a checksum for the source."""
165
+ return sha1(source.encode("utf-8")).hexdigest()
166
+
167
+ def get_bucket(
168
+ self,
169
+ environment: "Environment",
170
+ name: str,
171
+ filename: t.Optional[str],
172
+ source: str,
173
+ ) -> Bucket:
174
+ """Return a cache bucket for the given template. All arguments are
175
+ mandatory but filename may be `None`.
176
+ """
177
+ key = self.get_cache_key(name, filename)
178
+ checksum = self.get_source_checksum(source)
179
+ bucket = Bucket(environment, key, checksum)
180
+ self.load_bytecode(bucket)
181
+ return bucket
182
+
183
+ def set_bucket(self, bucket: Bucket) -> None:
184
+ """Put the bucket into the cache."""
185
+ self.dump_bytecode(bucket)
186
+
187
+
188
+ class FileSystemBytecodeCache(BytecodeCache):
189
+ """A bytecode cache that stores bytecode on the filesystem. It accepts
190
+ two arguments: The directory where the cache items are stored and a
191
+ pattern string that is used to build the filename.
192
+
193
+ If no directory is specified a default cache directory is selected. On
194
+ Windows the user's temp directory is used, on UNIX systems a directory
195
+ is created for the user in the system temp directory.
196
+
197
+ The pattern can be used to have multiple separate caches operate on the
198
+ same directory. The default pattern is ``'__jinja2_%s.cache'``. ``%s``
199
+ is replaced with the cache key.
200
+
201
+ >>> bcc = FileSystemBytecodeCache('/tmp/jinja_cache', '%s.cache')
202
+
203
+ This bytecode cache supports clearing of the cache using the clear method.
204
+ """
205
+
206
+ def __init__(
207
+ self, directory: t.Optional[str] = None, pattern: str = "__jinja2_%s.cache"
208
+ ) -> None:
209
+ if directory is None:
210
+ directory = self._get_default_cache_dir()
211
+ self.directory = directory
212
+ self.pattern = pattern
213
+
214
+ def _get_default_cache_dir(self) -> str:
215
+ def _unsafe_dir() -> "te.NoReturn":
216
+ raise RuntimeError(
217
+ "Cannot determine safe temp directory. You "
218
+ "need to explicitly provide one."
219
+ )
220
+
221
+ tmpdir = tempfile.gettempdir()
222
+
223
+ # On windows the temporary directory is used specific unless
224
+ # explicitly forced otherwise. We can just use that.
225
+ if os.name == "nt":
226
+ return tmpdir
227
+ if not hasattr(os, "getuid"):
228
+ _unsafe_dir()
229
+
230
+ dirname = f"_jinja2-cache-{os.getuid()}"
231
+ actual_dir = os.path.join(tmpdir, dirname)
232
+
233
+ try:
234
+ os.mkdir(actual_dir, stat.S_IRWXU)
235
+ except OSError as e:
236
+ if e.errno != errno.EEXIST:
237
+ raise
238
+ try:
239
+ os.chmod(actual_dir, stat.S_IRWXU)
240
+ actual_dir_stat = os.lstat(actual_dir)
241
+ if (
242
+ actual_dir_stat.st_uid != os.getuid()
243
+ or not stat.S_ISDIR(actual_dir_stat.st_mode)
244
+ or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
245
+ ):
246
+ _unsafe_dir()
247
+ except OSError as e:
248
+ if e.errno != errno.EEXIST:
249
+ raise
250
+
251
+ actual_dir_stat = os.lstat(actual_dir)
252
+ if (
253
+ actual_dir_stat.st_uid != os.getuid()
254
+ or not stat.S_ISDIR(actual_dir_stat.st_mode)
255
+ or stat.S_IMODE(actual_dir_stat.st_mode) != stat.S_IRWXU
256
+ ):
257
+ _unsafe_dir()
258
+
259
+ return actual_dir
260
+
261
+ def _get_cache_filename(self, bucket: Bucket) -> str:
262
+ return os.path.join(self.directory, self.pattern % (bucket.key,))
263
+
264
+ def load_bytecode(self, bucket: Bucket) -> None:
265
+ filename = self._get_cache_filename(bucket)
266
+
267
+ # Don't test for existence before opening the file, since the
268
+ # file could disappear after the test before the open.
269
+ try:
270
+ f = open(filename, "rb")
271
+ except (FileNotFoundError, IsADirectoryError, PermissionError):
272
+ # PermissionError can occur on Windows when an operation is
273
+ # in progress, such as calling clear().
274
+ return
275
+
276
+ with f:
277
+ bucket.load_bytecode(f)
278
+
279
+ def dump_bytecode(self, bucket: Bucket) -> None:
280
+ # Write to a temporary file, then rename to the real name after
281
+ # writing. This avoids another process reading the file before
282
+ # it is fully written.
283
+ name = self._get_cache_filename(bucket)
284
+ f = tempfile.NamedTemporaryFile(
285
+ mode="wb",
286
+ dir=os.path.dirname(name),
287
+ prefix=os.path.basename(name),
288
+ suffix=".tmp",
289
+ delete=False,
290
+ )
291
+
292
+ def remove_silent() -> None:
293
+ try:
294
+ os.remove(f.name)
295
+ except OSError:
296
+ # Another process may have called clear(). On Windows,
297
+ # another program may be holding the file open.
298
+ pass
299
+
300
+ try:
301
+ with f:
302
+ bucket.write_bytecode(f)
303
+ except BaseException:
304
+ remove_silent()
305
+ raise
306
+
307
+ try:
308
+ os.replace(f.name, name)
309
+ except OSError:
310
+ # Another process may have called clear(). On Windows,
311
+ # another program may be holding the file open.
312
+ remove_silent()
313
+ except BaseException:
314
+ remove_silent()
315
+ raise
316
+
317
+ def clear(self) -> None:
318
+ # imported lazily here because google app-engine doesn't support
319
+ # write access on the file system and the function does not exist
320
+ # normally.
321
+ from os import remove
322
+
323
+ files = fnmatch.filter(os.listdir(self.directory), self.pattern % ("*",))
324
+ for filename in files:
325
+ try:
326
+ remove(os.path.join(self.directory, filename))
327
+ except OSError:
328
+ pass
329
+
330
+
331
+ class MemcachedBytecodeCache(BytecodeCache):
332
+ """This class implements a bytecode cache that uses a memcache cache for
333
+ storing the information. It does not enforce a specific memcache library
334
+ (tummy's memcache or cmemcache) but will accept any class that provides
335
+ the minimal interface required.
336
+
337
+ Libraries compatible with this class:
338
+
339
+ - `cachelib <https://github.com/pallets/cachelib>`_
340
+ - `python-memcached <https://pypi.org/project/python-memcached/>`_
341
+
342
+ (Unfortunately the django cache interface is not compatible because it
343
+ does not support storing binary data, only text. You can however pass
344
+ the underlying cache client to the bytecode cache which is available
345
+ as `django.core.cache.cache._client`.)
346
+
347
+ The minimal interface for the client passed to the constructor is this:
348
+
349
+ .. class:: MinimalClientInterface
350
+
351
+ .. method:: set(key, value[, timeout])
352
+
353
+ Stores the bytecode in the cache. `value` is a string and
354
+ `timeout` the timeout of the key. If timeout is not provided
355
+ a default timeout or no timeout should be assumed, if it's
356
+ provided it's an integer with the number of seconds the cache
357
+ item should exist.
358
+
359
+ .. method:: get(key)
360
+
361
+ Returns the value for the cache key. If the item does not
362
+ exist in the cache the return value must be `None`.
363
+
364
+ The other arguments to the constructor are the prefix for all keys that
365
+ is added before the actual cache key and the timeout for the bytecode in
366
+ the cache system. We recommend a high (or no) timeout.
367
+
368
+ This bytecode cache does not support clearing of used items in the cache.
369
+ The clear method is a no-operation function.
370
+
371
+ .. versionadded:: 2.7
372
+ Added support for ignoring memcache errors through the
373
+ `ignore_memcache_errors` parameter.
374
+ """
375
+
376
+ def __init__(
377
+ self,
378
+ client: "_MemcachedClient",
379
+ prefix: str = "jinja2/bytecode/",
380
+ timeout: t.Optional[int] = None,
381
+ ignore_memcache_errors: bool = True,
382
+ ):
383
+ self.client = client
384
+ self.prefix = prefix
385
+ self.timeout = timeout
386
+ self.ignore_memcache_errors = ignore_memcache_errors
387
+
388
+ def load_bytecode(self, bucket: Bucket) -> None:
389
+ try:
390
+ code = self.client.get(self.prefix + bucket.key)
391
+ except Exception:
392
+ if not self.ignore_memcache_errors:
393
+ raise
394
+ else:
395
+ bucket.bytecode_from_string(code)
396
+
397
+ def dump_bytecode(self, bucket: Bucket) -> None:
398
+ key = self.prefix + bucket.key
399
+ value = bucket.bytecode_to_string()
400
+
401
+ try:
402
+ if self.timeout is not None:
403
+ self.client.set(key, value, self.timeout)
404
+ else:
405
+ self.client.set(key, value)
406
+ except Exception:
407
+ if not self.ignore_memcache_errors:
408
+ raise
.venv/lib/python3.11/site-packages/jinja2/constants.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #: list of lorem ipsum words used by the lipsum() helper function
2
+ LOREM_IPSUM_WORDS = """\
3
+ a ac accumsan ad adipiscing aenean aliquam aliquet amet ante aptent arcu at
4
+ auctor augue bibendum blandit class commodo condimentum congue consectetuer
5
+ consequat conubia convallis cras cubilia cum curabitur curae cursus dapibus
6
+ diam dictum dictumst dignissim dis dolor donec dui duis egestas eget eleifend
7
+ elementum elit enim erat eros est et etiam eu euismod facilisi facilisis fames
8
+ faucibus felis fermentum feugiat fringilla fusce gravida habitant habitasse hac
9
+ hendrerit hymenaeos iaculis id imperdiet in inceptos integer interdum ipsum
10
+ justo lacinia lacus laoreet lectus leo libero ligula litora lobortis lorem
11
+ luctus maecenas magna magnis malesuada massa mattis mauris metus mi molestie
12
+ mollis montes morbi mus nam nascetur natoque nec neque netus nibh nisi nisl non
13
+ nonummy nostra nulla nullam nunc odio orci ornare parturient pede pellentesque
14
+ penatibus per pharetra phasellus placerat platea porta porttitor posuere
15
+ potenti praesent pretium primis proin pulvinar purus quam quis quisque rhoncus
16
+ ridiculus risus rutrum sagittis sapien scelerisque sed sem semper senectus sit
17
+ sociis sociosqu sodales sollicitudin suscipit suspendisse taciti tellus tempor
18
+ tempus tincidunt torquent tortor tristique turpis ullamcorper ultrices
19
+ ultricies urna ut varius vehicula vel velit venenatis vestibulum vitae vivamus
20
+ viverra volutpat vulputate"""
.venv/lib/python3.11/site-packages/jinja2/environment.py ADDED
@@ -0,0 +1,1672 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Classes for managing templates and their runtime and compile time
2
+ options.
3
+ """
4
+
5
+ import os
6
+ import typing
7
+ import typing as t
8
+ import weakref
9
+ from collections import ChainMap
10
+ from functools import lru_cache
11
+ from functools import partial
12
+ from functools import reduce
13
+ from types import CodeType
14
+
15
+ from markupsafe import Markup
16
+
17
+ from . import nodes
18
+ from .compiler import CodeGenerator
19
+ from .compiler import generate
20
+ from .defaults import BLOCK_END_STRING
21
+ from .defaults import BLOCK_START_STRING
22
+ from .defaults import COMMENT_END_STRING
23
+ from .defaults import COMMENT_START_STRING
24
+ from .defaults import DEFAULT_FILTERS # type: ignore[attr-defined]
25
+ from .defaults import DEFAULT_NAMESPACE
26
+ from .defaults import DEFAULT_POLICIES
27
+ from .defaults import DEFAULT_TESTS # type: ignore[attr-defined]
28
+ from .defaults import KEEP_TRAILING_NEWLINE
29
+ from .defaults import LINE_COMMENT_PREFIX
30
+ from .defaults import LINE_STATEMENT_PREFIX
31
+ from .defaults import LSTRIP_BLOCKS
32
+ from .defaults import NEWLINE_SEQUENCE
33
+ from .defaults import TRIM_BLOCKS
34
+ from .defaults import VARIABLE_END_STRING
35
+ from .defaults import VARIABLE_START_STRING
36
+ from .exceptions import TemplateNotFound
37
+ from .exceptions import TemplateRuntimeError
38
+ from .exceptions import TemplatesNotFound
39
+ from .exceptions import TemplateSyntaxError
40
+ from .exceptions import UndefinedError
41
+ from .lexer import get_lexer
42
+ from .lexer import Lexer
43
+ from .lexer import TokenStream
44
+ from .nodes import EvalContext
45
+ from .parser import Parser
46
+ from .runtime import Context
47
+ from .runtime import new_context
48
+ from .runtime import Undefined
49
+ from .utils import _PassArg
50
+ from .utils import concat
51
+ from .utils import consume
52
+ from .utils import import_string
53
+ from .utils import internalcode
54
+ from .utils import LRUCache
55
+ from .utils import missing
56
+
57
+ if t.TYPE_CHECKING:
58
+ import typing_extensions as te
59
+
60
+ from .bccache import BytecodeCache
61
+ from .ext import Extension
62
+ from .loaders import BaseLoader
63
+
64
+ _env_bound = t.TypeVar("_env_bound", bound="Environment")
65
+
66
+
67
+ # for direct template usage we have up to ten living environments
68
+ @lru_cache(maxsize=10)
69
+ def get_spontaneous_environment(cls: t.Type[_env_bound], *args: t.Any) -> _env_bound:
70
+ """Return a new spontaneous environment. A spontaneous environment
71
+ is used for templates created directly rather than through an
72
+ existing environment.
73
+
74
+ :param cls: Environment class to create.
75
+ :param args: Positional arguments passed to environment.
76
+ """
77
+ env = cls(*args)
78
+ env.shared = True
79
+ return env
80
+
81
+
82
+ def create_cache(
83
+ size: int,
84
+ ) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
85
+ """Return the cache class for the given size."""
86
+ if size == 0:
87
+ return None
88
+
89
+ if size < 0:
90
+ return {}
91
+
92
+ return LRUCache(size) # type: ignore
93
+
94
+
95
+ def copy_cache(
96
+ cache: t.Optional[t.MutableMapping[t.Any, t.Any]],
97
+ ) -> t.Optional[t.MutableMapping[t.Tuple["weakref.ref[t.Any]", str], "Template"]]:
98
+ """Create an empty copy of the given cache."""
99
+ if cache is None:
100
+ return None
101
+
102
+ if type(cache) is dict: # noqa E721
103
+ return {}
104
+
105
+ return LRUCache(cache.capacity) # type: ignore
106
+
107
+
108
+ def load_extensions(
109
+ environment: "Environment",
110
+ extensions: t.Sequence[t.Union[str, t.Type["Extension"]]],
111
+ ) -> t.Dict[str, "Extension"]:
112
+ """Load the extensions from the list and bind it to the environment.
113
+ Returns a dict of instantiated extensions.
114
+ """
115
+ result = {}
116
+
117
+ for extension in extensions:
118
+ if isinstance(extension, str):
119
+ extension = t.cast(t.Type["Extension"], import_string(extension))
120
+
121
+ result[extension.identifier] = extension(environment)
122
+
123
+ return result
124
+
125
+
126
+ def _environment_config_check(environment: _env_bound) -> _env_bound:
127
+ """Perform a sanity check on the environment."""
128
+ assert issubclass(
129
+ environment.undefined, Undefined
130
+ ), "'undefined' must be a subclass of 'jinja2.Undefined'."
131
+ assert (
132
+ environment.block_start_string
133
+ != environment.variable_start_string
134
+ != environment.comment_start_string
135
+ ), "block, variable and comment start strings must be different."
136
+ assert environment.newline_sequence in {
137
+ "\r",
138
+ "\r\n",
139
+ "\n",
140
+ }, "'newline_sequence' must be one of '\\n', '\\r\\n', or '\\r'."
141
+ return environment
142
+
143
+
144
+ class Environment:
145
+ r"""The core component of Jinja is the `Environment`. It contains
146
+ important shared variables like configuration, filters, tests,
147
+ globals and others. Instances of this class may be modified if
148
+ they are not shared and if no template was loaded so far.
149
+ Modifications on environments after the first template was loaded
150
+ will lead to surprising effects and undefined behavior.
151
+
152
+ Here are the possible initialization parameters:
153
+
154
+ `block_start_string`
155
+ The string marking the beginning of a block. Defaults to ``'{%'``.
156
+
157
+ `block_end_string`
158
+ The string marking the end of a block. Defaults to ``'%}'``.
159
+
160
+ `variable_start_string`
161
+ The string marking the beginning of a print statement.
162
+ Defaults to ``'{{'``.
163
+
164
+ `variable_end_string`
165
+ The string marking the end of a print statement. Defaults to
166
+ ``'}}'``.
167
+
168
+ `comment_start_string`
169
+ The string marking the beginning of a comment. Defaults to ``'{#'``.
170
+
171
+ `comment_end_string`
172
+ The string marking the end of a comment. Defaults to ``'#}'``.
173
+
174
+ `line_statement_prefix`
175
+ If given and a string, this will be used as prefix for line based
176
+ statements. See also :ref:`line-statements`.
177
+
178
+ `line_comment_prefix`
179
+ If given and a string, this will be used as prefix for line based
180
+ comments. See also :ref:`line-statements`.
181
+
182
+ .. versionadded:: 2.2
183
+
184
+ `trim_blocks`
185
+ If this is set to ``True`` the first newline after a block is
186
+ removed (block, not variable tag!). Defaults to `False`.
187
+
188
+ `lstrip_blocks`
189
+ If this is set to ``True`` leading spaces and tabs are stripped
190
+ from the start of a line to a block. Defaults to `False`.
191
+
192
+ `newline_sequence`
193
+ The sequence that starts a newline. Must be one of ``'\r'``,
194
+ ``'\n'`` or ``'\r\n'``. The default is ``'\n'`` which is a
195
+ useful default for Linux and OS X systems as well as web
196
+ applications.
197
+
198
+ `keep_trailing_newline`
199
+ Preserve the trailing newline when rendering templates.
200
+ The default is ``False``, which causes a single newline,
201
+ if present, to be stripped from the end of the template.
202
+
203
+ .. versionadded:: 2.7
204
+
205
+ `extensions`
206
+ List of Jinja extensions to use. This can either be import paths
207
+ as strings or extension classes. For more information have a
208
+ look at :ref:`the extensions documentation <jinja-extensions>`.
209
+
210
+ `optimized`
211
+ should the optimizer be enabled? Default is ``True``.
212
+
213
+ `undefined`
214
+ :class:`Undefined` or a subclass of it that is used to represent
215
+ undefined values in the template.
216
+
217
+ `finalize`
218
+ A callable that can be used to process the result of a variable
219
+ expression before it is output. For example one can convert
220
+ ``None`` implicitly into an empty string here.
221
+
222
+ `autoescape`
223
+ If set to ``True`` the XML/HTML autoescaping feature is enabled by
224
+ default. For more details about autoescaping see
225
+ :class:`~markupsafe.Markup`. As of Jinja 2.4 this can also
226
+ be a callable that is passed the template name and has to
227
+ return ``True`` or ``False`` depending on autoescape should be
228
+ enabled by default.
229
+
230
+ .. versionchanged:: 2.4
231
+ `autoescape` can now be a function
232
+
233
+ `loader`
234
+ The template loader for this environment.
235
+
236
+ `cache_size`
237
+ The size of the cache. Per default this is ``400`` which means
238
+ that if more than 400 templates are loaded the loader will clean
239
+ out the least recently used template. If the cache size is set to
240
+ ``0`` templates are recompiled all the time, if the cache size is
241
+ ``-1`` the cache will not be cleaned.
242
+
243
+ .. versionchanged:: 2.8
244
+ The cache size was increased to 400 from a low 50.
245
+
246
+ `auto_reload`
247
+ Some loaders load templates from locations where the template
248
+ sources may change (ie: file system or database). If
249
+ ``auto_reload`` is set to ``True`` (default) every time a template is
250
+ requested the loader checks if the source changed and if yes, it
251
+ will reload the template. For higher performance it's possible to
252
+ disable that.
253
+
254
+ `bytecode_cache`
255
+ If set to a bytecode cache object, this object will provide a
256
+ cache for the internal Jinja bytecode so that templates don't
257
+ have to be parsed if they were not changed.
258
+
259
+ See :ref:`bytecode-cache` for more information.
260
+
261
+ `enable_async`
262
+ If set to true this enables async template execution which
263
+ allows using async functions and generators.
264
+ """
265
+
266
+ #: if this environment is sandboxed. Modifying this variable won't make
267
+ #: the environment sandboxed though. For a real sandboxed environment
268
+ #: have a look at jinja2.sandbox. This flag alone controls the code
269
+ #: generation by the compiler.
270
+ sandboxed = False
271
+
272
+ #: True if the environment is just an overlay
273
+ overlayed = False
274
+
275
+ #: the environment this environment is linked to if it is an overlay
276
+ linked_to: t.Optional["Environment"] = None
277
+
278
+ #: shared environments have this set to `True`. A shared environment
279
+ #: must not be modified
280
+ shared = False
281
+
282
+ #: the class that is used for code generation. See
283
+ #: :class:`~jinja2.compiler.CodeGenerator` for more information.
284
+ code_generator_class: t.Type["CodeGenerator"] = CodeGenerator
285
+
286
+ concat = "".join
287
+
288
+ #: the context class that is used for templates. See
289
+ #: :class:`~jinja2.runtime.Context` for more information.
290
+ context_class: t.Type[Context] = Context
291
+
292
+ template_class: t.Type["Template"]
293
+
294
+ def __init__(
295
+ self,
296
+ block_start_string: str = BLOCK_START_STRING,
297
+ block_end_string: str = BLOCK_END_STRING,
298
+ variable_start_string: str = VARIABLE_START_STRING,
299
+ variable_end_string: str = VARIABLE_END_STRING,
300
+ comment_start_string: str = COMMENT_START_STRING,
301
+ comment_end_string: str = COMMENT_END_STRING,
302
+ line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX,
303
+ line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
304
+ trim_blocks: bool = TRIM_BLOCKS,
305
+ lstrip_blocks: bool = LSTRIP_BLOCKS,
306
+ newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
307
+ keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
308
+ extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
309
+ optimized: bool = True,
310
+ undefined: t.Type[Undefined] = Undefined,
311
+ finalize: t.Optional[t.Callable[..., t.Any]] = None,
312
+ autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
313
+ loader: t.Optional["BaseLoader"] = None,
314
+ cache_size: int = 400,
315
+ auto_reload: bool = True,
316
+ bytecode_cache: t.Optional["BytecodeCache"] = None,
317
+ enable_async: bool = False,
318
+ ):
319
+ # !!Important notice!!
320
+ # The constructor accepts quite a few arguments that should be
321
+ # passed by keyword rather than position. However it's important to
322
+ # not change the order of arguments because it's used at least
323
+ # internally in those cases:
324
+ # - spontaneous environments (i18n extension and Template)
325
+ # - unittests
326
+ # If parameter changes are required only add parameters at the end
327
+ # and don't change the arguments (or the defaults!) of the arguments
328
+ # existing already.
329
+
330
+ # lexer / parser information
331
+ self.block_start_string = block_start_string
332
+ self.block_end_string = block_end_string
333
+ self.variable_start_string = variable_start_string
334
+ self.variable_end_string = variable_end_string
335
+ self.comment_start_string = comment_start_string
336
+ self.comment_end_string = comment_end_string
337
+ self.line_statement_prefix = line_statement_prefix
338
+ self.line_comment_prefix = line_comment_prefix
339
+ self.trim_blocks = trim_blocks
340
+ self.lstrip_blocks = lstrip_blocks
341
+ self.newline_sequence = newline_sequence
342
+ self.keep_trailing_newline = keep_trailing_newline
343
+
344
+ # runtime information
345
+ self.undefined: t.Type[Undefined] = undefined
346
+ self.optimized = optimized
347
+ self.finalize = finalize
348
+ self.autoescape = autoescape
349
+
350
+ # defaults
351
+ self.filters = DEFAULT_FILTERS.copy()
352
+ self.tests = DEFAULT_TESTS.copy()
353
+ self.globals = DEFAULT_NAMESPACE.copy()
354
+
355
+ # set the loader provided
356
+ self.loader = loader
357
+ self.cache = create_cache(cache_size)
358
+ self.bytecode_cache = bytecode_cache
359
+ self.auto_reload = auto_reload
360
+
361
+ # configurable policies
362
+ self.policies = DEFAULT_POLICIES.copy()
363
+
364
+ # load extensions
365
+ self.extensions = load_extensions(self, extensions)
366
+
367
+ self.is_async = enable_async
368
+ _environment_config_check(self)
369
+
370
+ def add_extension(self, extension: t.Union[str, t.Type["Extension"]]) -> None:
371
+ """Adds an extension after the environment was created.
372
+
373
+ .. versionadded:: 2.5
374
+ """
375
+ self.extensions.update(load_extensions(self, [extension]))
376
+
377
+ def extend(self, **attributes: t.Any) -> None:
378
+ """Add the items to the instance of the environment if they do not exist
379
+ yet. This is used by :ref:`extensions <writing-extensions>` to register
380
+ callbacks and configuration values without breaking inheritance.
381
+ """
382
+ for key, value in attributes.items():
383
+ if not hasattr(self, key):
384
+ setattr(self, key, value)
385
+
386
+ def overlay(
387
+ self,
388
+ block_start_string: str = missing,
389
+ block_end_string: str = missing,
390
+ variable_start_string: str = missing,
391
+ variable_end_string: str = missing,
392
+ comment_start_string: str = missing,
393
+ comment_end_string: str = missing,
394
+ line_statement_prefix: t.Optional[str] = missing,
395
+ line_comment_prefix: t.Optional[str] = missing,
396
+ trim_blocks: bool = missing,
397
+ lstrip_blocks: bool = missing,
398
+ newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = missing,
399
+ keep_trailing_newline: bool = missing,
400
+ extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = missing,
401
+ optimized: bool = missing,
402
+ undefined: t.Type[Undefined] = missing,
403
+ finalize: t.Optional[t.Callable[..., t.Any]] = missing,
404
+ autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = missing,
405
+ loader: t.Optional["BaseLoader"] = missing,
406
+ cache_size: int = missing,
407
+ auto_reload: bool = missing,
408
+ bytecode_cache: t.Optional["BytecodeCache"] = missing,
409
+ enable_async: bool = missing,
410
+ ) -> "te.Self":
411
+ """Create a new overlay environment that shares all the data with the
412
+ current environment except for cache and the overridden attributes.
413
+ Extensions cannot be removed for an overlayed environment. An overlayed
414
+ environment automatically gets all the extensions of the environment it
415
+ is linked to plus optional extra extensions.
416
+
417
+ Creating overlays should happen after the initial environment was set
418
+ up completely. Not all attributes are truly linked, some are just
419
+ copied over so modifications on the original environment may not shine
420
+ through.
421
+
422
+ .. versionchanged:: 3.1.5
423
+ ``enable_async`` is applied correctly.
424
+
425
+ .. versionchanged:: 3.1.2
426
+ Added the ``newline_sequence``, ``keep_trailing_newline``,
427
+ and ``enable_async`` parameters to match ``__init__``.
428
+ """
429
+ args = dict(locals())
430
+ del args["self"], args["cache_size"], args["extensions"], args["enable_async"]
431
+
432
+ rv = object.__new__(self.__class__)
433
+ rv.__dict__.update(self.__dict__)
434
+ rv.overlayed = True
435
+ rv.linked_to = self
436
+
437
+ for key, value in args.items():
438
+ if value is not missing:
439
+ setattr(rv, key, value)
440
+
441
+ if cache_size is not missing:
442
+ rv.cache = create_cache(cache_size)
443
+ else:
444
+ rv.cache = copy_cache(self.cache)
445
+
446
+ rv.extensions = {}
447
+ for key, value in self.extensions.items():
448
+ rv.extensions[key] = value.bind(rv)
449
+ if extensions is not missing:
450
+ rv.extensions.update(load_extensions(rv, extensions))
451
+
452
+ if enable_async is not missing:
453
+ rv.is_async = enable_async
454
+
455
+ return _environment_config_check(rv)
456
+
457
+ @property
458
+ def lexer(self) -> Lexer:
459
+ """The lexer for this environment."""
460
+ return get_lexer(self)
461
+
462
+ def iter_extensions(self) -> t.Iterator["Extension"]:
463
+ """Iterates over the extensions by priority."""
464
+ return iter(sorted(self.extensions.values(), key=lambda x: x.priority))
465
+
466
+ def getitem(
467
+ self, obj: t.Any, argument: t.Union[str, t.Any]
468
+ ) -> t.Union[t.Any, Undefined]:
469
+ """Get an item or attribute of an object but prefer the item."""
470
+ try:
471
+ return obj[argument]
472
+ except (AttributeError, TypeError, LookupError):
473
+ if isinstance(argument, str):
474
+ try:
475
+ attr = str(argument)
476
+ except Exception:
477
+ pass
478
+ else:
479
+ try:
480
+ return getattr(obj, attr)
481
+ except AttributeError:
482
+ pass
483
+ return self.undefined(obj=obj, name=argument)
484
+
485
+ def getattr(self, obj: t.Any, attribute: str) -> t.Any:
486
+ """Get an item or attribute of an object but prefer the attribute.
487
+ Unlike :meth:`getitem` the attribute *must* be a string.
488
+ """
489
+ try:
490
+ return getattr(obj, attribute)
491
+ except AttributeError:
492
+ pass
493
+ try:
494
+ return obj[attribute]
495
+ except (TypeError, LookupError, AttributeError):
496
+ return self.undefined(obj=obj, name=attribute)
497
+
498
+ def _filter_test_common(
499
+ self,
500
+ name: t.Union[str, Undefined],
501
+ value: t.Any,
502
+ args: t.Optional[t.Sequence[t.Any]],
503
+ kwargs: t.Optional[t.Mapping[str, t.Any]],
504
+ context: t.Optional[Context],
505
+ eval_ctx: t.Optional[EvalContext],
506
+ is_filter: bool,
507
+ ) -> t.Any:
508
+ if is_filter:
509
+ env_map = self.filters
510
+ type_name = "filter"
511
+ else:
512
+ env_map = self.tests
513
+ type_name = "test"
514
+
515
+ func = env_map.get(name) # type: ignore
516
+
517
+ if func is None:
518
+ msg = f"No {type_name} named {name!r}."
519
+
520
+ if isinstance(name, Undefined):
521
+ try:
522
+ name._fail_with_undefined_error()
523
+ except Exception as e:
524
+ msg = f"{msg} ({e}; did you forget to quote the callable name?)"
525
+
526
+ raise TemplateRuntimeError(msg)
527
+
528
+ args = [value, *(args if args is not None else ())]
529
+ kwargs = kwargs if kwargs is not None else {}
530
+ pass_arg = _PassArg.from_obj(func)
531
+
532
+ if pass_arg is _PassArg.context:
533
+ if context is None:
534
+ raise TemplateRuntimeError(
535
+ f"Attempted to invoke a context {type_name} without context."
536
+ )
537
+
538
+ args.insert(0, context)
539
+ elif pass_arg is _PassArg.eval_context:
540
+ if eval_ctx is None:
541
+ if context is not None:
542
+ eval_ctx = context.eval_ctx
543
+ else:
544
+ eval_ctx = EvalContext(self)
545
+
546
+ args.insert(0, eval_ctx)
547
+ elif pass_arg is _PassArg.environment:
548
+ args.insert(0, self)
549
+
550
+ return func(*args, **kwargs)
551
+
552
+ def call_filter(
553
+ self,
554
+ name: str,
555
+ value: t.Any,
556
+ args: t.Optional[t.Sequence[t.Any]] = None,
557
+ kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
558
+ context: t.Optional[Context] = None,
559
+ eval_ctx: t.Optional[EvalContext] = None,
560
+ ) -> t.Any:
561
+ """Invoke a filter on a value the same way the compiler does.
562
+
563
+ This might return a coroutine if the filter is running from an
564
+ environment in async mode and the filter supports async
565
+ execution. It's your responsibility to await this if needed.
566
+
567
+ .. versionadded:: 2.7
568
+ """
569
+ return self._filter_test_common(
570
+ name, value, args, kwargs, context, eval_ctx, True
571
+ )
572
+
573
+ def call_test(
574
+ self,
575
+ name: str,
576
+ value: t.Any,
577
+ args: t.Optional[t.Sequence[t.Any]] = None,
578
+ kwargs: t.Optional[t.Mapping[str, t.Any]] = None,
579
+ context: t.Optional[Context] = None,
580
+ eval_ctx: t.Optional[EvalContext] = None,
581
+ ) -> t.Any:
582
+ """Invoke a test on a value the same way the compiler does.
583
+
584
+ This might return a coroutine if the test is running from an
585
+ environment in async mode and the test supports async execution.
586
+ It's your responsibility to await this if needed.
587
+
588
+ .. versionchanged:: 3.0
589
+ Tests support ``@pass_context``, etc. decorators. Added
590
+ the ``context`` and ``eval_ctx`` parameters.
591
+
592
+ .. versionadded:: 2.7
593
+ """
594
+ return self._filter_test_common(
595
+ name, value, args, kwargs, context, eval_ctx, False
596
+ )
597
+
598
+ @internalcode
599
+ def parse(
600
+ self,
601
+ source: str,
602
+ name: t.Optional[str] = None,
603
+ filename: t.Optional[str] = None,
604
+ ) -> nodes.Template:
605
+ """Parse the sourcecode and return the abstract syntax tree. This
606
+ tree of nodes is used by the compiler to convert the template into
607
+ executable source- or bytecode. This is useful for debugging or to
608
+ extract information from templates.
609
+
610
+ If you are :ref:`developing Jinja extensions <writing-extensions>`
611
+ this gives you a good overview of the node tree generated.
612
+ """
613
+ try:
614
+ return self._parse(source, name, filename)
615
+ except TemplateSyntaxError:
616
+ self.handle_exception(source=source)
617
+
618
+ def _parse(
619
+ self, source: str, name: t.Optional[str], filename: t.Optional[str]
620
+ ) -> nodes.Template:
621
+ """Internal parsing function used by `parse` and `compile`."""
622
+ return Parser(self, source, name, filename).parse()
623
+
624
+ def lex(
625
+ self,
626
+ source: str,
627
+ name: t.Optional[str] = None,
628
+ filename: t.Optional[str] = None,
629
+ ) -> t.Iterator[t.Tuple[int, str, str]]:
630
+ """Lex the given sourcecode and return a generator that yields
631
+ tokens as tuples in the form ``(lineno, token_type, value)``.
632
+ This can be useful for :ref:`extension development <writing-extensions>`
633
+ and debugging templates.
634
+
635
+ This does not perform preprocessing. If you want the preprocessing
636
+ of the extensions to be applied you have to filter source through
637
+ the :meth:`preprocess` method.
638
+ """
639
+ source = str(source)
640
+ try:
641
+ return self.lexer.tokeniter(source, name, filename)
642
+ except TemplateSyntaxError:
643
+ self.handle_exception(source=source)
644
+
645
+ def preprocess(
646
+ self,
647
+ source: str,
648
+ name: t.Optional[str] = None,
649
+ filename: t.Optional[str] = None,
650
+ ) -> str:
651
+ """Preprocesses the source with all extensions. This is automatically
652
+ called for all parsing and compiling methods but *not* for :meth:`lex`
653
+ because there you usually only want the actual source tokenized.
654
+ """
655
+ return reduce(
656
+ lambda s, e: e.preprocess(s, name, filename),
657
+ self.iter_extensions(),
658
+ str(source),
659
+ )
660
+
661
+ def _tokenize(
662
+ self,
663
+ source: str,
664
+ name: t.Optional[str],
665
+ filename: t.Optional[str] = None,
666
+ state: t.Optional[str] = None,
667
+ ) -> TokenStream:
668
+ """Called by the parser to do the preprocessing and filtering
669
+ for all the extensions. Returns a :class:`~jinja2.lexer.TokenStream`.
670
+ """
671
+ source = self.preprocess(source, name, filename)
672
+ stream = self.lexer.tokenize(source, name, filename, state)
673
+
674
+ for ext in self.iter_extensions():
675
+ stream = ext.filter_stream(stream) # type: ignore
676
+
677
+ if not isinstance(stream, TokenStream):
678
+ stream = TokenStream(stream, name, filename)
679
+
680
+ return stream
681
+
682
+ def _generate(
683
+ self,
684
+ source: nodes.Template,
685
+ name: t.Optional[str],
686
+ filename: t.Optional[str],
687
+ defer_init: bool = False,
688
+ ) -> str:
689
+ """Internal hook that can be overridden to hook a different generate
690
+ method in.
691
+
692
+ .. versionadded:: 2.5
693
+ """
694
+ return generate( # type: ignore
695
+ source,
696
+ self,
697
+ name,
698
+ filename,
699
+ defer_init=defer_init,
700
+ optimized=self.optimized,
701
+ )
702
+
703
+ def _compile(self, source: str, filename: str) -> CodeType:
704
+ """Internal hook that can be overridden to hook a different compile
705
+ method in.
706
+
707
+ .. versionadded:: 2.5
708
+ """
709
+ return compile(source, filename, "exec")
710
+
711
+ @typing.overload
712
+ def compile(
713
+ self,
714
+ source: t.Union[str, nodes.Template],
715
+ name: t.Optional[str] = None,
716
+ filename: t.Optional[str] = None,
717
+ raw: "te.Literal[False]" = False,
718
+ defer_init: bool = False,
719
+ ) -> CodeType: ...
720
+
721
+ @typing.overload
722
+ def compile(
723
+ self,
724
+ source: t.Union[str, nodes.Template],
725
+ name: t.Optional[str] = None,
726
+ filename: t.Optional[str] = None,
727
+ raw: "te.Literal[True]" = ...,
728
+ defer_init: bool = False,
729
+ ) -> str: ...
730
+
731
+ @internalcode
732
+ def compile(
733
+ self,
734
+ source: t.Union[str, nodes.Template],
735
+ name: t.Optional[str] = None,
736
+ filename: t.Optional[str] = None,
737
+ raw: bool = False,
738
+ defer_init: bool = False,
739
+ ) -> t.Union[str, CodeType]:
740
+ """Compile a node or template source code. The `name` parameter is
741
+ the load name of the template after it was joined using
742
+ :meth:`join_path` if necessary, not the filename on the file system.
743
+ the `filename` parameter is the estimated filename of the template on
744
+ the file system. If the template came from a database or memory this
745
+ can be omitted.
746
+
747
+ The return value of this method is a python code object. If the `raw`
748
+ parameter is `True` the return value will be a string with python
749
+ code equivalent to the bytecode returned otherwise. This method is
750
+ mainly used internally.
751
+
752
+ `defer_init` is use internally to aid the module code generator. This
753
+ causes the generated code to be able to import without the global
754
+ environment variable to be set.
755
+
756
+ .. versionadded:: 2.4
757
+ `defer_init` parameter added.
758
+ """
759
+ source_hint = None
760
+ try:
761
+ if isinstance(source, str):
762
+ source_hint = source
763
+ source = self._parse(source, name, filename)
764
+ source = self._generate(source, name, filename, defer_init=defer_init)
765
+ if raw:
766
+ return source
767
+ if filename is None:
768
+ filename = "<template>"
769
+ return self._compile(source, filename)
770
+ except TemplateSyntaxError:
771
+ self.handle_exception(source=source_hint)
772
+
773
+ def compile_expression(
774
+ self, source: str, undefined_to_none: bool = True
775
+ ) -> "TemplateExpression":
776
+ """A handy helper method that returns a callable that accepts keyword
777
+ arguments that appear as variables in the expression. If called it
778
+ returns the result of the expression.
779
+
780
+ This is useful if applications want to use the same rules as Jinja
781
+ in template "configuration files" or similar situations.
782
+
783
+ Example usage:
784
+
785
+ >>> env = Environment()
786
+ >>> expr = env.compile_expression('foo == 42')
787
+ >>> expr(foo=23)
788
+ False
789
+ >>> expr(foo=42)
790
+ True
791
+
792
+ Per default the return value is converted to `None` if the
793
+ expression returns an undefined value. This can be changed
794
+ by setting `undefined_to_none` to `False`.
795
+
796
+ >>> env.compile_expression('var')() is None
797
+ True
798
+ >>> env.compile_expression('var', undefined_to_none=False)()
799
+ Undefined
800
+
801
+ .. versionadded:: 2.1
802
+ """
803
+ parser = Parser(self, source, state="variable")
804
+ try:
805
+ expr = parser.parse_expression()
806
+ if not parser.stream.eos:
807
+ raise TemplateSyntaxError(
808
+ "chunk after expression", parser.stream.current.lineno, None, None
809
+ )
810
+ expr.set_environment(self)
811
+ except TemplateSyntaxError:
812
+ self.handle_exception(source=source)
813
+
814
+ body = [nodes.Assign(nodes.Name("result", "store"), expr, lineno=1)]
815
+ template = self.from_string(nodes.Template(body, lineno=1))
816
+ return TemplateExpression(template, undefined_to_none)
817
+
818
+ def compile_templates(
819
+ self,
820
+ target: t.Union[str, "os.PathLike[str]"],
821
+ extensions: t.Optional[t.Collection[str]] = None,
822
+ filter_func: t.Optional[t.Callable[[str], bool]] = None,
823
+ zip: t.Optional[str] = "deflated",
824
+ log_function: t.Optional[t.Callable[[str], None]] = None,
825
+ ignore_errors: bool = True,
826
+ ) -> None:
827
+ """Finds all the templates the loader can find, compiles them
828
+ and stores them in `target`. If `zip` is `None`, instead of in a
829
+ zipfile, the templates will be stored in a directory.
830
+ By default a deflate zip algorithm is used. To switch to
831
+ the stored algorithm, `zip` can be set to ``'stored'``.
832
+
833
+ `extensions` and `filter_func` are passed to :meth:`list_templates`.
834
+ Each template returned will be compiled to the target folder or
835
+ zipfile.
836
+
837
+ By default template compilation errors are ignored. In case a
838
+ log function is provided, errors are logged. If you want template
839
+ syntax errors to abort the compilation you can set `ignore_errors`
840
+ to `False` and you will get an exception on syntax errors.
841
+
842
+ .. versionadded:: 2.4
843
+ """
844
+ from .loaders import ModuleLoader
845
+
846
+ if log_function is None:
847
+
848
+ def log_function(x: str) -> None:
849
+ pass
850
+
851
+ assert log_function is not None
852
+ assert self.loader is not None, "No loader configured."
853
+
854
+ def write_file(filename: str, data: str) -> None:
855
+ if zip:
856
+ info = ZipInfo(filename)
857
+ info.external_attr = 0o755 << 16
858
+ zip_file.writestr(info, data)
859
+ else:
860
+ with open(os.path.join(target, filename), "wb") as f:
861
+ f.write(data.encode("utf8"))
862
+
863
+ if zip is not None:
864
+ from zipfile import ZIP_DEFLATED
865
+ from zipfile import ZIP_STORED
866
+ from zipfile import ZipFile
867
+ from zipfile import ZipInfo
868
+
869
+ zip_file = ZipFile(
870
+ target, "w", dict(deflated=ZIP_DEFLATED, stored=ZIP_STORED)[zip]
871
+ )
872
+ log_function(f"Compiling into Zip archive {target!r}")
873
+ else:
874
+ if not os.path.isdir(target):
875
+ os.makedirs(target)
876
+ log_function(f"Compiling into folder {target!r}")
877
+
878
+ try:
879
+ for name in self.list_templates(extensions, filter_func):
880
+ source, filename, _ = self.loader.get_source(self, name)
881
+ try:
882
+ code = self.compile(source, name, filename, True, True)
883
+ except TemplateSyntaxError as e:
884
+ if not ignore_errors:
885
+ raise
886
+ log_function(f'Could not compile "{name}": {e}')
887
+ continue
888
+
889
+ filename = ModuleLoader.get_module_filename(name)
890
+
891
+ write_file(filename, code)
892
+ log_function(f'Compiled "{name}" as {filename}')
893
+ finally:
894
+ if zip:
895
+ zip_file.close()
896
+
897
+ log_function("Finished compiling templates")
898
+
899
+ def list_templates(
900
+ self,
901
+ extensions: t.Optional[t.Collection[str]] = None,
902
+ filter_func: t.Optional[t.Callable[[str], bool]] = None,
903
+ ) -> t.List[str]:
904
+ """Returns a list of templates for this environment. This requires
905
+ that the loader supports the loader's
906
+ :meth:`~BaseLoader.list_templates` method.
907
+
908
+ If there are other files in the template folder besides the
909
+ actual templates, the returned list can be filtered. There are two
910
+ ways: either `extensions` is set to a list of file extensions for
911
+ templates, or a `filter_func` can be provided which is a callable that
912
+ is passed a template name and should return `True` if it should end up
913
+ in the result list.
914
+
915
+ If the loader does not support that, a :exc:`TypeError` is raised.
916
+
917
+ .. versionadded:: 2.4
918
+ """
919
+ assert self.loader is not None, "No loader configured."
920
+ names = self.loader.list_templates()
921
+
922
+ if extensions is not None:
923
+ if filter_func is not None:
924
+ raise TypeError(
925
+ "either extensions or filter_func can be passed, but not both"
926
+ )
927
+
928
+ def filter_func(x: str) -> bool:
929
+ return "." in x and x.rsplit(".", 1)[1] in extensions
930
+
931
+ if filter_func is not None:
932
+ names = [name for name in names if filter_func(name)]
933
+
934
+ return names
935
+
936
+ def handle_exception(self, source: t.Optional[str] = None) -> "te.NoReturn":
937
+ """Exception handling helper. This is used internally to either raise
938
+ rewritten exceptions or return a rendered traceback for the template.
939
+ """
940
+ from .debug import rewrite_traceback_stack
941
+
942
+ raise rewrite_traceback_stack(source=source)
943
+
944
+ def join_path(self, template: str, parent: str) -> str:
945
+ """Join a template with the parent. By default all the lookups are
946
+ relative to the loader root so this method returns the `template`
947
+ parameter unchanged, but if the paths should be relative to the
948
+ parent template, this function can be used to calculate the real
949
+ template name.
950
+
951
+ Subclasses may override this method and implement template path
952
+ joining here.
953
+ """
954
+ return template
955
+
956
+ @internalcode
957
+ def _load_template(
958
+ self, name: str, globals: t.Optional[t.MutableMapping[str, t.Any]]
959
+ ) -> "Template":
960
+ if self.loader is None:
961
+ raise TypeError("no loader for this environment specified")
962
+ cache_key = (weakref.ref(self.loader), name)
963
+ if self.cache is not None:
964
+ template = self.cache.get(cache_key)
965
+ if template is not None and (
966
+ not self.auto_reload or template.is_up_to_date
967
+ ):
968
+ # template.globals is a ChainMap, modifying it will only
969
+ # affect the template, not the environment globals.
970
+ if globals:
971
+ template.globals.update(globals)
972
+
973
+ return template
974
+
975
+ template = self.loader.load(self, name, self.make_globals(globals))
976
+
977
+ if self.cache is not None:
978
+ self.cache[cache_key] = template
979
+ return template
980
+
981
+ @internalcode
982
+ def get_template(
983
+ self,
984
+ name: t.Union[str, "Template"],
985
+ parent: t.Optional[str] = None,
986
+ globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
987
+ ) -> "Template":
988
+ """Load a template by name with :attr:`loader` and return a
989
+ :class:`Template`. If the template does not exist a
990
+ :exc:`TemplateNotFound` exception is raised.
991
+
992
+ :param name: Name of the template to load. When loading
993
+ templates from the filesystem, "/" is used as the path
994
+ separator, even on Windows.
995
+ :param parent: The name of the parent template importing this
996
+ template. :meth:`join_path` can be used to implement name
997
+ transformations with this.
998
+ :param globals: Extend the environment :attr:`globals` with
999
+ these extra variables available for all renders of this
1000
+ template. If the template has already been loaded and
1001
+ cached, its globals are updated with any new items.
1002
+
1003
+ .. versionchanged:: 3.0
1004
+ If a template is loaded from cache, ``globals`` will update
1005
+ the template's globals instead of ignoring the new values.
1006
+
1007
+ .. versionchanged:: 2.4
1008
+ If ``name`` is a :class:`Template` object it is returned
1009
+ unchanged.
1010
+ """
1011
+ if isinstance(name, Template):
1012
+ return name
1013
+ if parent is not None:
1014
+ name = self.join_path(name, parent)
1015
+
1016
+ return self._load_template(name, globals)
1017
+
1018
+ @internalcode
1019
+ def select_template(
1020
+ self,
1021
+ names: t.Iterable[t.Union[str, "Template"]],
1022
+ parent: t.Optional[str] = None,
1023
+ globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
1024
+ ) -> "Template":
1025
+ """Like :meth:`get_template`, but tries loading multiple names.
1026
+ If none of the names can be loaded a :exc:`TemplatesNotFound`
1027
+ exception is raised.
1028
+
1029
+ :param names: List of template names to try loading in order.
1030
+ :param parent: The name of the parent template importing this
1031
+ template. :meth:`join_path` can be used to implement name
1032
+ transformations with this.
1033
+ :param globals: Extend the environment :attr:`globals` with
1034
+ these extra variables available for all renders of this
1035
+ template. If the template has already been loaded and
1036
+ cached, its globals are updated with any new items.
1037
+
1038
+ .. versionchanged:: 3.0
1039
+ If a template is loaded from cache, ``globals`` will update
1040
+ the template's globals instead of ignoring the new values.
1041
+
1042
+ .. versionchanged:: 2.11
1043
+ If ``names`` is :class:`Undefined`, an :exc:`UndefinedError`
1044
+ is raised instead. If no templates were found and ``names``
1045
+ contains :class:`Undefined`, the message is more helpful.
1046
+
1047
+ .. versionchanged:: 2.4
1048
+ If ``names`` contains a :class:`Template` object it is
1049
+ returned unchanged.
1050
+
1051
+ .. versionadded:: 2.3
1052
+ """
1053
+ if isinstance(names, Undefined):
1054
+ names._fail_with_undefined_error()
1055
+
1056
+ if not names:
1057
+ raise TemplatesNotFound(
1058
+ message="Tried to select from an empty list of templates."
1059
+ )
1060
+
1061
+ for name in names:
1062
+ if isinstance(name, Template):
1063
+ return name
1064
+ if parent is not None:
1065
+ name = self.join_path(name, parent)
1066
+ try:
1067
+ return self._load_template(name, globals)
1068
+ except (TemplateNotFound, UndefinedError):
1069
+ pass
1070
+ raise TemplatesNotFound(names) # type: ignore
1071
+
1072
+ @internalcode
1073
+ def get_or_select_template(
1074
+ self,
1075
+ template_name_or_list: t.Union[
1076
+ str, "Template", t.List[t.Union[str, "Template"]]
1077
+ ],
1078
+ parent: t.Optional[str] = None,
1079
+ globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
1080
+ ) -> "Template":
1081
+ """Use :meth:`select_template` if an iterable of template names
1082
+ is given, or :meth:`get_template` if one name is given.
1083
+
1084
+ .. versionadded:: 2.3
1085
+ """
1086
+ if isinstance(template_name_or_list, (str, Undefined)):
1087
+ return self.get_template(template_name_or_list, parent, globals)
1088
+ elif isinstance(template_name_or_list, Template):
1089
+ return template_name_or_list
1090
+ return self.select_template(template_name_or_list, parent, globals)
1091
+
1092
+ def from_string(
1093
+ self,
1094
+ source: t.Union[str, nodes.Template],
1095
+ globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
1096
+ template_class: t.Optional[t.Type["Template"]] = None,
1097
+ ) -> "Template":
1098
+ """Load a template from a source string without using
1099
+ :attr:`loader`.
1100
+
1101
+ :param source: Jinja source to compile into a template.
1102
+ :param globals: Extend the environment :attr:`globals` with
1103
+ these extra variables available for all renders of this
1104
+ template. If the template has already been loaded and
1105
+ cached, its globals are updated with any new items.
1106
+ :param template_class: Return an instance of this
1107
+ :class:`Template` class.
1108
+ """
1109
+ gs = self.make_globals(globals)
1110
+ cls = template_class or self.template_class
1111
+ return cls.from_code(self, self.compile(source), gs, None)
1112
+
1113
+ def make_globals(
1114
+ self, d: t.Optional[t.MutableMapping[str, t.Any]]
1115
+ ) -> t.MutableMapping[str, t.Any]:
1116
+ """Make the globals map for a template. Any given template
1117
+ globals overlay the environment :attr:`globals`.
1118
+
1119
+ Returns a :class:`collections.ChainMap`. This allows any changes
1120
+ to a template's globals to only affect that template, while
1121
+ changes to the environment's globals are still reflected.
1122
+ However, avoid modifying any globals after a template is loaded.
1123
+
1124
+ :param d: Dict of template-specific globals.
1125
+
1126
+ .. versionchanged:: 3.0
1127
+ Use :class:`collections.ChainMap` to always prevent mutating
1128
+ environment globals.
1129
+ """
1130
+ if d is None:
1131
+ d = {}
1132
+
1133
+ return ChainMap(d, self.globals)
1134
+
1135
+
1136
+ class Template:
1137
+ """A compiled template that can be rendered.
1138
+
1139
+ Use the methods on :class:`Environment` to create or load templates.
1140
+ The environment is used to configure how templates are compiled and
1141
+ behave.
1142
+
1143
+ It is also possible to create a template object directly. This is
1144
+ not usually recommended. The constructor takes most of the same
1145
+ arguments as :class:`Environment`. All templates created with the
1146
+ same environment arguments share the same ephemeral ``Environment``
1147
+ instance behind the scenes.
1148
+
1149
+ A template object should be considered immutable. Modifications on
1150
+ the object are not supported.
1151
+ """
1152
+
1153
+ #: Type of environment to create when creating a template directly
1154
+ #: rather than through an existing environment.
1155
+ environment_class: t.Type[Environment] = Environment
1156
+
1157
+ environment: Environment
1158
+ globals: t.MutableMapping[str, t.Any]
1159
+ name: t.Optional[str]
1160
+ filename: t.Optional[str]
1161
+ blocks: t.Dict[str, t.Callable[[Context], t.Iterator[str]]]
1162
+ root_render_func: t.Callable[[Context], t.Iterator[str]]
1163
+ _module: t.Optional["TemplateModule"]
1164
+ _debug_info: str
1165
+ _uptodate: t.Optional[t.Callable[[], bool]]
1166
+
1167
+ def __new__(
1168
+ cls,
1169
+ source: t.Union[str, nodes.Template],
1170
+ block_start_string: str = BLOCK_START_STRING,
1171
+ block_end_string: str = BLOCK_END_STRING,
1172
+ variable_start_string: str = VARIABLE_START_STRING,
1173
+ variable_end_string: str = VARIABLE_END_STRING,
1174
+ comment_start_string: str = COMMENT_START_STRING,
1175
+ comment_end_string: str = COMMENT_END_STRING,
1176
+ line_statement_prefix: t.Optional[str] = LINE_STATEMENT_PREFIX,
1177
+ line_comment_prefix: t.Optional[str] = LINE_COMMENT_PREFIX,
1178
+ trim_blocks: bool = TRIM_BLOCKS,
1179
+ lstrip_blocks: bool = LSTRIP_BLOCKS,
1180
+ newline_sequence: "te.Literal['\\n', '\\r\\n', '\\r']" = NEWLINE_SEQUENCE,
1181
+ keep_trailing_newline: bool = KEEP_TRAILING_NEWLINE,
1182
+ extensions: t.Sequence[t.Union[str, t.Type["Extension"]]] = (),
1183
+ optimized: bool = True,
1184
+ undefined: t.Type[Undefined] = Undefined,
1185
+ finalize: t.Optional[t.Callable[..., t.Any]] = None,
1186
+ autoescape: t.Union[bool, t.Callable[[t.Optional[str]], bool]] = False,
1187
+ enable_async: bool = False,
1188
+ ) -> t.Any: # it returns a `Template`, but this breaks the sphinx build...
1189
+ env = get_spontaneous_environment(
1190
+ cls.environment_class, # type: ignore
1191
+ block_start_string,
1192
+ block_end_string,
1193
+ variable_start_string,
1194
+ variable_end_string,
1195
+ comment_start_string,
1196
+ comment_end_string,
1197
+ line_statement_prefix,
1198
+ line_comment_prefix,
1199
+ trim_blocks,
1200
+ lstrip_blocks,
1201
+ newline_sequence,
1202
+ keep_trailing_newline,
1203
+ frozenset(extensions),
1204
+ optimized,
1205
+ undefined, # type: ignore
1206
+ finalize,
1207
+ autoescape,
1208
+ None,
1209
+ 0,
1210
+ False,
1211
+ None,
1212
+ enable_async,
1213
+ )
1214
+ return env.from_string(source, template_class=cls)
1215
+
1216
+ @classmethod
1217
+ def from_code(
1218
+ cls,
1219
+ environment: Environment,
1220
+ code: CodeType,
1221
+ globals: t.MutableMapping[str, t.Any],
1222
+ uptodate: t.Optional[t.Callable[[], bool]] = None,
1223
+ ) -> "Template":
1224
+ """Creates a template object from compiled code and the globals. This
1225
+ is used by the loaders and environment to create a template object.
1226
+ """
1227
+ namespace = {"environment": environment, "__file__": code.co_filename}
1228
+ exec(code, namespace)
1229
+ rv = cls._from_namespace(environment, namespace, globals)
1230
+ rv._uptodate = uptodate
1231
+ return rv
1232
+
1233
+ @classmethod
1234
+ def from_module_dict(
1235
+ cls,
1236
+ environment: Environment,
1237
+ module_dict: t.MutableMapping[str, t.Any],
1238
+ globals: t.MutableMapping[str, t.Any],
1239
+ ) -> "Template":
1240
+ """Creates a template object from a module. This is used by the
1241
+ module loader to create a template object.
1242
+
1243
+ .. versionadded:: 2.4
1244
+ """
1245
+ return cls._from_namespace(environment, module_dict, globals)
1246
+
1247
+ @classmethod
1248
+ def _from_namespace(
1249
+ cls,
1250
+ environment: Environment,
1251
+ namespace: t.MutableMapping[str, t.Any],
1252
+ globals: t.MutableMapping[str, t.Any],
1253
+ ) -> "Template":
1254
+ t: Template = object.__new__(cls)
1255
+ t.environment = environment
1256
+ t.globals = globals
1257
+ t.name = namespace["name"]
1258
+ t.filename = namespace["__file__"]
1259
+ t.blocks = namespace["blocks"]
1260
+
1261
+ # render function and module
1262
+ t.root_render_func = namespace["root"]
1263
+ t._module = None
1264
+
1265
+ # debug and loader helpers
1266
+ t._debug_info = namespace["debug_info"]
1267
+ t._uptodate = None
1268
+
1269
+ # store the reference
1270
+ namespace["environment"] = environment
1271
+ namespace["__jinja_template__"] = t
1272
+
1273
+ return t
1274
+
1275
+ def render(self, *args: t.Any, **kwargs: t.Any) -> str:
1276
+ """This method accepts the same arguments as the `dict` constructor:
1277
+ A dict, a dict subclass or some keyword arguments. If no arguments
1278
+ are given the context will be empty. These two calls do the same::
1279
+
1280
+ template.render(knights='that say nih')
1281
+ template.render({'knights': 'that say nih'})
1282
+
1283
+ This will return the rendered template as a string.
1284
+ """
1285
+ if self.environment.is_async:
1286
+ import asyncio
1287
+
1288
+ return asyncio.run(self.render_async(*args, **kwargs))
1289
+
1290
+ ctx = self.new_context(dict(*args, **kwargs))
1291
+
1292
+ try:
1293
+ return self.environment.concat(self.root_render_func(ctx)) # type: ignore
1294
+ except Exception:
1295
+ self.environment.handle_exception()
1296
+
1297
+ async def render_async(self, *args: t.Any, **kwargs: t.Any) -> str:
1298
+ """This works similar to :meth:`render` but returns a coroutine
1299
+ that when awaited returns the entire rendered template string. This
1300
+ requires the async feature to be enabled.
1301
+
1302
+ Example usage::
1303
+
1304
+ await template.render_async(knights='that say nih; asynchronously')
1305
+ """
1306
+ if not self.environment.is_async:
1307
+ raise RuntimeError(
1308
+ "The environment was not created with async mode enabled."
1309
+ )
1310
+
1311
+ ctx = self.new_context(dict(*args, **kwargs))
1312
+
1313
+ try:
1314
+ return self.environment.concat( # type: ignore
1315
+ [n async for n in self.root_render_func(ctx)] # type: ignore
1316
+ )
1317
+ except Exception:
1318
+ return self.environment.handle_exception()
1319
+
1320
+ def stream(self, *args: t.Any, **kwargs: t.Any) -> "TemplateStream":
1321
+ """Works exactly like :meth:`generate` but returns a
1322
+ :class:`TemplateStream`.
1323
+ """
1324
+ return TemplateStream(self.generate(*args, **kwargs))
1325
+
1326
+ def generate(self, *args: t.Any, **kwargs: t.Any) -> t.Iterator[str]:
1327
+ """For very large templates it can be useful to not render the whole
1328
+ template at once but evaluate each statement after another and yield
1329
+ piece for piece. This method basically does exactly that and returns
1330
+ a generator that yields one item after another as strings.
1331
+
1332
+ It accepts the same arguments as :meth:`render`.
1333
+ """
1334
+ if self.environment.is_async:
1335
+ import asyncio
1336
+
1337
+ async def to_list() -> t.List[str]:
1338
+ return [x async for x in self.generate_async(*args, **kwargs)]
1339
+
1340
+ yield from asyncio.run(to_list())
1341
+ return
1342
+
1343
+ ctx = self.new_context(dict(*args, **kwargs))
1344
+
1345
+ try:
1346
+ yield from self.root_render_func(ctx)
1347
+ except Exception:
1348
+ yield self.environment.handle_exception()
1349
+
1350
+ async def generate_async(
1351
+ self, *args: t.Any, **kwargs: t.Any
1352
+ ) -> t.AsyncGenerator[str, object]:
1353
+ """An async version of :meth:`generate`. Works very similarly but
1354
+ returns an async iterator instead.
1355
+ """
1356
+ if not self.environment.is_async:
1357
+ raise RuntimeError(
1358
+ "The environment was not created with async mode enabled."
1359
+ )
1360
+
1361
+ ctx = self.new_context(dict(*args, **kwargs))
1362
+
1363
+ try:
1364
+ agen = self.root_render_func(ctx)
1365
+ try:
1366
+ async for event in agen: # type: ignore
1367
+ yield event
1368
+ finally:
1369
+ # we can't use async with aclosing(...) because that's only
1370
+ # in 3.10+
1371
+ await agen.aclose() # type: ignore
1372
+ except Exception:
1373
+ yield self.environment.handle_exception()
1374
+
1375
+ def new_context(
1376
+ self,
1377
+ vars: t.Optional[t.Dict[str, t.Any]] = None,
1378
+ shared: bool = False,
1379
+ locals: t.Optional[t.Mapping[str, t.Any]] = None,
1380
+ ) -> Context:
1381
+ """Create a new :class:`Context` for this template. The vars
1382
+ provided will be passed to the template. Per default the globals
1383
+ are added to the context. If shared is set to `True` the data
1384
+ is passed as is to the context without adding the globals.
1385
+
1386
+ `locals` can be a dict of local variables for internal usage.
1387
+ """
1388
+ return new_context(
1389
+ self.environment, self.name, self.blocks, vars, shared, self.globals, locals
1390
+ )
1391
+
1392
+ def make_module(
1393
+ self,
1394
+ vars: t.Optional[t.Dict[str, t.Any]] = None,
1395
+ shared: bool = False,
1396
+ locals: t.Optional[t.Mapping[str, t.Any]] = None,
1397
+ ) -> "TemplateModule":
1398
+ """This method works like the :attr:`module` attribute when called
1399
+ without arguments but it will evaluate the template on every call
1400
+ rather than caching it. It's also possible to provide
1401
+ a dict which is then used as context. The arguments are the same
1402
+ as for the :meth:`new_context` method.
1403
+ """
1404
+ ctx = self.new_context(vars, shared, locals)
1405
+ return TemplateModule(self, ctx)
1406
+
1407
+ async def make_module_async(
1408
+ self,
1409
+ vars: t.Optional[t.Dict[str, t.Any]] = None,
1410
+ shared: bool = False,
1411
+ locals: t.Optional[t.Mapping[str, t.Any]] = None,
1412
+ ) -> "TemplateModule":
1413
+ """As template module creation can invoke template code for
1414
+ asynchronous executions this method must be used instead of the
1415
+ normal :meth:`make_module` one. Likewise the module attribute
1416
+ becomes unavailable in async mode.
1417
+ """
1418
+ ctx = self.new_context(vars, shared, locals)
1419
+ return TemplateModule(
1420
+ self,
1421
+ ctx,
1422
+ [x async for x in self.root_render_func(ctx)], # type: ignore
1423
+ )
1424
+
1425
+ @internalcode
1426
+ def _get_default_module(self, ctx: t.Optional[Context] = None) -> "TemplateModule":
1427
+ """If a context is passed in, this means that the template was
1428
+ imported. Imported templates have access to the current
1429
+ template's globals by default, but they can only be accessed via
1430
+ the context during runtime.
1431
+
1432
+ If there are new globals, we need to create a new module because
1433
+ the cached module is already rendered and will not have access
1434
+ to globals from the current context. This new module is not
1435
+ cached because the template can be imported elsewhere, and it
1436
+ should have access to only the current template's globals.
1437
+ """
1438
+ if self.environment.is_async:
1439
+ raise RuntimeError("Module is not available in async mode.")
1440
+
1441
+ if ctx is not None:
1442
+ keys = ctx.globals_keys - self.globals.keys()
1443
+
1444
+ if keys:
1445
+ return self.make_module({k: ctx.parent[k] for k in keys})
1446
+
1447
+ if self._module is None:
1448
+ self._module = self.make_module()
1449
+
1450
+ return self._module
1451
+
1452
+ async def _get_default_module_async(
1453
+ self, ctx: t.Optional[Context] = None
1454
+ ) -> "TemplateModule":
1455
+ if ctx is not None:
1456
+ keys = ctx.globals_keys - self.globals.keys()
1457
+
1458
+ if keys:
1459
+ return await self.make_module_async({k: ctx.parent[k] for k in keys})
1460
+
1461
+ if self._module is None:
1462
+ self._module = await self.make_module_async()
1463
+
1464
+ return self._module
1465
+
1466
+ @property
1467
+ def module(self) -> "TemplateModule":
1468
+ """The template as module. This is used for imports in the
1469
+ template runtime but is also useful if one wants to access
1470
+ exported template variables from the Python layer:
1471
+
1472
+ >>> t = Template('{% macro foo() %}42{% endmacro %}23')
1473
+ >>> str(t.module)
1474
+ '23'
1475
+ >>> t.module.foo() == u'42'
1476
+ True
1477
+
1478
+ This attribute is not available if async mode is enabled.
1479
+ """
1480
+ return self._get_default_module()
1481
+
1482
+ def get_corresponding_lineno(self, lineno: int) -> int:
1483
+ """Return the source line number of a line number in the
1484
+ generated bytecode as they are not in sync.
1485
+ """
1486
+ for template_line, code_line in reversed(self.debug_info):
1487
+ if code_line <= lineno:
1488
+ return template_line
1489
+ return 1
1490
+
1491
+ @property
1492
+ def is_up_to_date(self) -> bool:
1493
+ """If this variable is `False` there is a newer version available."""
1494
+ if self._uptodate is None:
1495
+ return True
1496
+ return self._uptodate()
1497
+
1498
+ @property
1499
+ def debug_info(self) -> t.List[t.Tuple[int, int]]:
1500
+ """The debug info mapping."""
1501
+ if self._debug_info:
1502
+ return [
1503
+ tuple(map(int, x.split("="))) # type: ignore
1504
+ for x in self._debug_info.split("&")
1505
+ ]
1506
+
1507
+ return []
1508
+
1509
+ def __repr__(self) -> str:
1510
+ if self.name is None:
1511
+ name = f"memory:{id(self):x}"
1512
+ else:
1513
+ name = repr(self.name)
1514
+ return f"<{type(self).__name__} {name}>"
1515
+
1516
+
1517
+ class TemplateModule:
1518
+ """Represents an imported template. All the exported names of the
1519
+ template are available as attributes on this object. Additionally
1520
+ converting it into a string renders the contents.
1521
+ """
1522
+
1523
+ def __init__(
1524
+ self,
1525
+ template: Template,
1526
+ context: Context,
1527
+ body_stream: t.Optional[t.Iterable[str]] = None,
1528
+ ) -> None:
1529
+ if body_stream is None:
1530
+ if context.environment.is_async:
1531
+ raise RuntimeError(
1532
+ "Async mode requires a body stream to be passed to"
1533
+ " a template module. Use the async methods of the"
1534
+ " API you are using."
1535
+ )
1536
+
1537
+ body_stream = list(template.root_render_func(context))
1538
+
1539
+ self._body_stream = body_stream
1540
+ self.__dict__.update(context.get_exported())
1541
+ self.__name__ = template.name
1542
+
1543
+ def __html__(self) -> Markup:
1544
+ return Markup(concat(self._body_stream))
1545
+
1546
+ def __str__(self) -> str:
1547
+ return concat(self._body_stream)
1548
+
1549
+ def __repr__(self) -> str:
1550
+ if self.__name__ is None:
1551
+ name = f"memory:{id(self):x}"
1552
+ else:
1553
+ name = repr(self.__name__)
1554
+ return f"<{type(self).__name__} {name}>"
1555
+
1556
+
1557
+ class TemplateExpression:
1558
+ """The :meth:`jinja2.Environment.compile_expression` method returns an
1559
+ instance of this object. It encapsulates the expression-like access
1560
+ to the template with an expression it wraps.
1561
+ """
1562
+
1563
+ def __init__(self, template: Template, undefined_to_none: bool) -> None:
1564
+ self._template = template
1565
+ self._undefined_to_none = undefined_to_none
1566
+
1567
+ def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Optional[t.Any]:
1568
+ context = self._template.new_context(dict(*args, **kwargs))
1569
+ consume(self._template.root_render_func(context))
1570
+ rv = context.vars["result"]
1571
+ if self._undefined_to_none and isinstance(rv, Undefined):
1572
+ rv = None
1573
+ return rv
1574
+
1575
+
1576
+ class TemplateStream:
1577
+ """A template stream works pretty much like an ordinary python generator
1578
+ but it can buffer multiple items to reduce the number of total iterations.
1579
+ Per default the output is unbuffered which means that for every unbuffered
1580
+ instruction in the template one string is yielded.
1581
+
1582
+ If buffering is enabled with a buffer size of 5, five items are combined
1583
+ into a new string. This is mainly useful if you are streaming
1584
+ big templates to a client via WSGI which flushes after each iteration.
1585
+ """
1586
+
1587
+ def __init__(self, gen: t.Iterator[str]) -> None:
1588
+ self._gen = gen
1589
+ self.disable_buffering()
1590
+
1591
+ def dump(
1592
+ self,
1593
+ fp: t.Union[str, t.IO[bytes]],
1594
+ encoding: t.Optional[str] = None,
1595
+ errors: t.Optional[str] = "strict",
1596
+ ) -> None:
1597
+ """Dump the complete stream into a file or file-like object.
1598
+ Per default strings are written, if you want to encode
1599
+ before writing specify an `encoding`.
1600
+
1601
+ Example usage::
1602
+
1603
+ Template('Hello {{ name }}!').stream(name='foo').dump('hello.html')
1604
+ """
1605
+ close = False
1606
+
1607
+ if isinstance(fp, str):
1608
+ if encoding is None:
1609
+ encoding = "utf-8"
1610
+
1611
+ real_fp: t.IO[bytes] = open(fp, "wb")
1612
+ close = True
1613
+ else:
1614
+ real_fp = fp
1615
+
1616
+ try:
1617
+ if encoding is not None:
1618
+ iterable = (x.encode(encoding, errors) for x in self) # type: ignore
1619
+ else:
1620
+ iterable = self # type: ignore
1621
+
1622
+ if hasattr(real_fp, "writelines"):
1623
+ real_fp.writelines(iterable)
1624
+ else:
1625
+ for item in iterable:
1626
+ real_fp.write(item)
1627
+ finally:
1628
+ if close:
1629
+ real_fp.close()
1630
+
1631
+ def disable_buffering(self) -> None:
1632
+ """Disable the output buffering."""
1633
+ self._next = partial(next, self._gen)
1634
+ self.buffered = False
1635
+
1636
+ def _buffered_generator(self, size: int) -> t.Iterator[str]:
1637
+ buf: t.List[str] = []
1638
+ c_size = 0
1639
+ push = buf.append
1640
+
1641
+ while True:
1642
+ try:
1643
+ while c_size < size:
1644
+ c = next(self._gen)
1645
+ push(c)
1646
+ if c:
1647
+ c_size += 1
1648
+ except StopIteration:
1649
+ if not c_size:
1650
+ return
1651
+ yield concat(buf)
1652
+ del buf[:]
1653
+ c_size = 0
1654
+
1655
+ def enable_buffering(self, size: int = 5) -> None:
1656
+ """Enable buffering. Buffer `size` items before yielding them."""
1657
+ if size <= 1:
1658
+ raise ValueError("buffer size too small")
1659
+
1660
+ self.buffered = True
1661
+ self._next = partial(next, self._buffered_generator(size))
1662
+
1663
+ def __iter__(self) -> "TemplateStream":
1664
+ return self
1665
+
1666
+ def __next__(self) -> str:
1667
+ return self._next() # type: ignore
1668
+
1669
+
1670
+ # hook in default template class. if anyone reads this comment: ignore that
1671
+ # it's possible to use custom templates ;-)
1672
+ Environment.template_class = Template
.venv/lib/python3.11/site-packages/jinja2/ext.py ADDED
@@ -0,0 +1,870 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Extension API for adding custom tags and behavior."""
2
+
3
+ import pprint
4
+ import re
5
+ import typing as t
6
+
7
+ from markupsafe import Markup
8
+
9
+ from . import defaults
10
+ from . import nodes
11
+ from .environment import Environment
12
+ from .exceptions import TemplateAssertionError
13
+ from .exceptions import TemplateSyntaxError
14
+ from .runtime import concat # type: ignore
15
+ from .runtime import Context
16
+ from .runtime import Undefined
17
+ from .utils import import_string
18
+ from .utils import pass_context
19
+
20
+ if t.TYPE_CHECKING:
21
+ import typing_extensions as te
22
+
23
+ from .lexer import Token
24
+ from .lexer import TokenStream
25
+ from .parser import Parser
26
+
27
+ class _TranslationsBasic(te.Protocol):
28
+ def gettext(self, message: str) -> str: ...
29
+
30
+ def ngettext(self, singular: str, plural: str, n: int) -> str:
31
+ pass
32
+
33
+ class _TranslationsContext(_TranslationsBasic):
34
+ def pgettext(self, context: str, message: str) -> str: ...
35
+
36
+ def npgettext(
37
+ self, context: str, singular: str, plural: str, n: int
38
+ ) -> str: ...
39
+
40
+ _SupportedTranslations = t.Union[_TranslationsBasic, _TranslationsContext]
41
+
42
+
43
+ # I18N functions available in Jinja templates. If the I18N library
44
+ # provides ugettext, it will be assigned to gettext.
45
+ GETTEXT_FUNCTIONS: t.Tuple[str, ...] = (
46
+ "_",
47
+ "gettext",
48
+ "ngettext",
49
+ "pgettext",
50
+ "npgettext",
51
+ )
52
+ _ws_re = re.compile(r"\s*\n\s*")
53
+
54
+
55
+ class Extension:
56
+ """Extensions can be used to add extra functionality to the Jinja template
57
+ system at the parser level. Custom extensions are bound to an environment
58
+ but may not store environment specific data on `self`. The reason for
59
+ this is that an extension can be bound to another environment (for
60
+ overlays) by creating a copy and reassigning the `environment` attribute.
61
+
62
+ As extensions are created by the environment they cannot accept any
63
+ arguments for configuration. One may want to work around that by using
64
+ a factory function, but that is not possible as extensions are identified
65
+ by their import name. The correct way to configure the extension is
66
+ storing the configuration values on the environment. Because this way the
67
+ environment ends up acting as central configuration storage the
68
+ attributes may clash which is why extensions have to ensure that the names
69
+ they choose for configuration are not too generic. ``prefix`` for example
70
+ is a terrible name, ``fragment_cache_prefix`` on the other hand is a good
71
+ name as includes the name of the extension (fragment cache).
72
+ """
73
+
74
+ identifier: t.ClassVar[str]
75
+
76
+ def __init_subclass__(cls) -> None:
77
+ cls.identifier = f"{cls.__module__}.{cls.__name__}"
78
+
79
+ #: if this extension parses this is the list of tags it's listening to.
80
+ tags: t.Set[str] = set()
81
+
82
+ #: the priority of that extension. This is especially useful for
83
+ #: extensions that preprocess values. A lower value means higher
84
+ #: priority.
85
+ #:
86
+ #: .. versionadded:: 2.4
87
+ priority = 100
88
+
89
+ def __init__(self, environment: Environment) -> None:
90
+ self.environment = environment
91
+
92
+ def bind(self, environment: Environment) -> "te.Self":
93
+ """Create a copy of this extension bound to another environment."""
94
+ rv = object.__new__(self.__class__)
95
+ rv.__dict__.update(self.__dict__)
96
+ rv.environment = environment
97
+ return rv
98
+
99
+ def preprocess(
100
+ self, source: str, name: t.Optional[str], filename: t.Optional[str] = None
101
+ ) -> str:
102
+ """This method is called before the actual lexing and can be used to
103
+ preprocess the source. The `filename` is optional. The return value
104
+ must be the preprocessed source.
105
+ """
106
+ return source
107
+
108
+ def filter_stream(
109
+ self, stream: "TokenStream"
110
+ ) -> t.Union["TokenStream", t.Iterable["Token"]]:
111
+ """It's passed a :class:`~jinja2.lexer.TokenStream` that can be used
112
+ to filter tokens returned. This method has to return an iterable of
113
+ :class:`~jinja2.lexer.Token`\\s, but it doesn't have to return a
114
+ :class:`~jinja2.lexer.TokenStream`.
115
+ """
116
+ return stream
117
+
118
+ def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
119
+ """If any of the :attr:`tags` matched this method is called with the
120
+ parser as first argument. The token the parser stream is pointing at
121
+ is the name token that matched. This method has to return one or a
122
+ list of multiple nodes.
123
+ """
124
+ raise NotImplementedError()
125
+
126
+ def attr(
127
+ self, name: str, lineno: t.Optional[int] = None
128
+ ) -> nodes.ExtensionAttribute:
129
+ """Return an attribute node for the current extension. This is useful
130
+ to pass constants on extensions to generated template code.
131
+
132
+ ::
133
+
134
+ self.attr('_my_attribute', lineno=lineno)
135
+ """
136
+ return nodes.ExtensionAttribute(self.identifier, name, lineno=lineno)
137
+
138
+ def call_method(
139
+ self,
140
+ name: str,
141
+ args: t.Optional[t.List[nodes.Expr]] = None,
142
+ kwargs: t.Optional[t.List[nodes.Keyword]] = None,
143
+ dyn_args: t.Optional[nodes.Expr] = None,
144
+ dyn_kwargs: t.Optional[nodes.Expr] = None,
145
+ lineno: t.Optional[int] = None,
146
+ ) -> nodes.Call:
147
+ """Call a method of the extension. This is a shortcut for
148
+ :meth:`attr` + :class:`jinja2.nodes.Call`.
149
+ """
150
+ if args is None:
151
+ args = []
152
+ if kwargs is None:
153
+ kwargs = []
154
+ return nodes.Call(
155
+ self.attr(name, lineno=lineno),
156
+ args,
157
+ kwargs,
158
+ dyn_args,
159
+ dyn_kwargs,
160
+ lineno=lineno,
161
+ )
162
+
163
+
164
+ @pass_context
165
+ def _gettext_alias(
166
+ __context: Context, *args: t.Any, **kwargs: t.Any
167
+ ) -> t.Union[t.Any, Undefined]:
168
+ return __context.call(__context.resolve("gettext"), *args, **kwargs)
169
+
170
+
171
+ def _make_new_gettext(func: t.Callable[[str], str]) -> t.Callable[..., str]:
172
+ @pass_context
173
+ def gettext(__context: Context, __string: str, **variables: t.Any) -> str:
174
+ rv = __context.call(func, __string)
175
+ if __context.eval_ctx.autoescape:
176
+ rv = Markup(rv)
177
+ # Always treat as a format string, even if there are no
178
+ # variables. This makes translation strings more consistent
179
+ # and predictable. This requires escaping
180
+ return rv % variables # type: ignore
181
+
182
+ return gettext
183
+
184
+
185
+ def _make_new_ngettext(func: t.Callable[[str, str, int], str]) -> t.Callable[..., str]:
186
+ @pass_context
187
+ def ngettext(
188
+ __context: Context,
189
+ __singular: str,
190
+ __plural: str,
191
+ __num: int,
192
+ **variables: t.Any,
193
+ ) -> str:
194
+ variables.setdefault("num", __num)
195
+ rv = __context.call(func, __singular, __plural, __num)
196
+ if __context.eval_ctx.autoescape:
197
+ rv = Markup(rv)
198
+ # Always treat as a format string, see gettext comment above.
199
+ return rv % variables # type: ignore
200
+
201
+ return ngettext
202
+
203
+
204
+ def _make_new_pgettext(func: t.Callable[[str, str], str]) -> t.Callable[..., str]:
205
+ @pass_context
206
+ def pgettext(
207
+ __context: Context, __string_ctx: str, __string: str, **variables: t.Any
208
+ ) -> str:
209
+ variables.setdefault("context", __string_ctx)
210
+ rv = __context.call(func, __string_ctx, __string)
211
+
212
+ if __context.eval_ctx.autoescape:
213
+ rv = Markup(rv)
214
+
215
+ # Always treat as a format string, see gettext comment above.
216
+ return rv % variables # type: ignore
217
+
218
+ return pgettext
219
+
220
+
221
+ def _make_new_npgettext(
222
+ func: t.Callable[[str, str, str, int], str],
223
+ ) -> t.Callable[..., str]:
224
+ @pass_context
225
+ def npgettext(
226
+ __context: Context,
227
+ __string_ctx: str,
228
+ __singular: str,
229
+ __plural: str,
230
+ __num: int,
231
+ **variables: t.Any,
232
+ ) -> str:
233
+ variables.setdefault("context", __string_ctx)
234
+ variables.setdefault("num", __num)
235
+ rv = __context.call(func, __string_ctx, __singular, __plural, __num)
236
+
237
+ if __context.eval_ctx.autoescape:
238
+ rv = Markup(rv)
239
+
240
+ # Always treat as a format string, see gettext comment above.
241
+ return rv % variables # type: ignore
242
+
243
+ return npgettext
244
+
245
+
246
+ class InternationalizationExtension(Extension):
247
+ """This extension adds gettext support to Jinja."""
248
+
249
+ tags = {"trans"}
250
+
251
+ # TODO: the i18n extension is currently reevaluating values in a few
252
+ # situations. Take this example:
253
+ # {% trans count=something() %}{{ count }} foo{% pluralize
254
+ # %}{{ count }} fooss{% endtrans %}
255
+ # something is called twice here. One time for the gettext value and
256
+ # the other time for the n-parameter of the ngettext function.
257
+
258
+ def __init__(self, environment: Environment) -> None:
259
+ super().__init__(environment)
260
+ environment.globals["_"] = _gettext_alias
261
+ environment.extend(
262
+ install_gettext_translations=self._install,
263
+ install_null_translations=self._install_null,
264
+ install_gettext_callables=self._install_callables,
265
+ uninstall_gettext_translations=self._uninstall,
266
+ extract_translations=self._extract,
267
+ newstyle_gettext=False,
268
+ )
269
+
270
+ def _install(
271
+ self, translations: "_SupportedTranslations", newstyle: t.Optional[bool] = None
272
+ ) -> None:
273
+ # ugettext and ungettext are preferred in case the I18N library
274
+ # is providing compatibility with older Python versions.
275
+ gettext = getattr(translations, "ugettext", None)
276
+ if gettext is None:
277
+ gettext = translations.gettext
278
+ ngettext = getattr(translations, "ungettext", None)
279
+ if ngettext is None:
280
+ ngettext = translations.ngettext
281
+
282
+ pgettext = getattr(translations, "pgettext", None)
283
+ npgettext = getattr(translations, "npgettext", None)
284
+ self._install_callables(
285
+ gettext, ngettext, newstyle=newstyle, pgettext=pgettext, npgettext=npgettext
286
+ )
287
+
288
+ def _install_null(self, newstyle: t.Optional[bool] = None) -> None:
289
+ import gettext
290
+
291
+ translations = gettext.NullTranslations()
292
+
293
+ if hasattr(translations, "pgettext"):
294
+ # Python < 3.8
295
+ pgettext = translations.pgettext
296
+ else:
297
+
298
+ def pgettext(c: str, s: str) -> str: # type: ignore[misc]
299
+ return s
300
+
301
+ if hasattr(translations, "npgettext"):
302
+ npgettext = translations.npgettext
303
+ else:
304
+
305
+ def npgettext(c: str, s: str, p: str, n: int) -> str: # type: ignore[misc]
306
+ return s if n == 1 else p
307
+
308
+ self._install_callables(
309
+ gettext=translations.gettext,
310
+ ngettext=translations.ngettext,
311
+ newstyle=newstyle,
312
+ pgettext=pgettext,
313
+ npgettext=npgettext,
314
+ )
315
+
316
+ def _install_callables(
317
+ self,
318
+ gettext: t.Callable[[str], str],
319
+ ngettext: t.Callable[[str, str, int], str],
320
+ newstyle: t.Optional[bool] = None,
321
+ pgettext: t.Optional[t.Callable[[str, str], str]] = None,
322
+ npgettext: t.Optional[t.Callable[[str, str, str, int], str]] = None,
323
+ ) -> None:
324
+ if newstyle is not None:
325
+ self.environment.newstyle_gettext = newstyle # type: ignore
326
+ if self.environment.newstyle_gettext: # type: ignore
327
+ gettext = _make_new_gettext(gettext)
328
+ ngettext = _make_new_ngettext(ngettext)
329
+
330
+ if pgettext is not None:
331
+ pgettext = _make_new_pgettext(pgettext)
332
+
333
+ if npgettext is not None:
334
+ npgettext = _make_new_npgettext(npgettext)
335
+
336
+ self.environment.globals.update(
337
+ gettext=gettext, ngettext=ngettext, pgettext=pgettext, npgettext=npgettext
338
+ )
339
+
340
+ def _uninstall(self, translations: "_SupportedTranslations") -> None:
341
+ for key in ("gettext", "ngettext", "pgettext", "npgettext"):
342
+ self.environment.globals.pop(key, None)
343
+
344
+ def _extract(
345
+ self,
346
+ source: t.Union[str, nodes.Template],
347
+ gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
348
+ ) -> t.Iterator[
349
+ t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
350
+ ]:
351
+ if isinstance(source, str):
352
+ source = self.environment.parse(source)
353
+ return extract_from_ast(source, gettext_functions)
354
+
355
+ def parse(self, parser: "Parser") -> t.Union[nodes.Node, t.List[nodes.Node]]:
356
+ """Parse a translatable tag."""
357
+ lineno = next(parser.stream).lineno
358
+
359
+ context = None
360
+ context_token = parser.stream.next_if("string")
361
+
362
+ if context_token is not None:
363
+ context = context_token.value
364
+
365
+ # find all the variables referenced. Additionally a variable can be
366
+ # defined in the body of the trans block too, but this is checked at
367
+ # a later state.
368
+ plural_expr: t.Optional[nodes.Expr] = None
369
+ plural_expr_assignment: t.Optional[nodes.Assign] = None
370
+ num_called_num = False
371
+ variables: t.Dict[str, nodes.Expr] = {}
372
+ trimmed = None
373
+ while parser.stream.current.type != "block_end":
374
+ if variables:
375
+ parser.stream.expect("comma")
376
+
377
+ # skip colon for python compatibility
378
+ if parser.stream.skip_if("colon"):
379
+ break
380
+
381
+ token = parser.stream.expect("name")
382
+ if token.value in variables:
383
+ parser.fail(
384
+ f"translatable variable {token.value!r} defined twice.",
385
+ token.lineno,
386
+ exc=TemplateAssertionError,
387
+ )
388
+
389
+ # expressions
390
+ if parser.stream.current.type == "assign":
391
+ next(parser.stream)
392
+ variables[token.value] = var = parser.parse_expression()
393
+ elif trimmed is None and token.value in ("trimmed", "notrimmed"):
394
+ trimmed = token.value == "trimmed"
395
+ continue
396
+ else:
397
+ variables[token.value] = var = nodes.Name(token.value, "load")
398
+
399
+ if plural_expr is None:
400
+ if isinstance(var, nodes.Call):
401
+ plural_expr = nodes.Name("_trans", "load")
402
+ variables[token.value] = plural_expr
403
+ plural_expr_assignment = nodes.Assign(
404
+ nodes.Name("_trans", "store"), var
405
+ )
406
+ else:
407
+ plural_expr = var
408
+ num_called_num = token.value == "num"
409
+
410
+ parser.stream.expect("block_end")
411
+
412
+ plural = None
413
+ have_plural = False
414
+ referenced = set()
415
+
416
+ # now parse until endtrans or pluralize
417
+ singular_names, singular = self._parse_block(parser, True)
418
+ if singular_names:
419
+ referenced.update(singular_names)
420
+ if plural_expr is None:
421
+ plural_expr = nodes.Name(singular_names[0], "load")
422
+ num_called_num = singular_names[0] == "num"
423
+
424
+ # if we have a pluralize block, we parse that too
425
+ if parser.stream.current.test("name:pluralize"):
426
+ have_plural = True
427
+ next(parser.stream)
428
+ if parser.stream.current.type != "block_end":
429
+ token = parser.stream.expect("name")
430
+ if token.value not in variables:
431
+ parser.fail(
432
+ f"unknown variable {token.value!r} for pluralization",
433
+ token.lineno,
434
+ exc=TemplateAssertionError,
435
+ )
436
+ plural_expr = variables[token.value]
437
+ num_called_num = token.value == "num"
438
+ parser.stream.expect("block_end")
439
+ plural_names, plural = self._parse_block(parser, False)
440
+ next(parser.stream)
441
+ referenced.update(plural_names)
442
+ else:
443
+ next(parser.stream)
444
+
445
+ # register free names as simple name expressions
446
+ for name in referenced:
447
+ if name not in variables:
448
+ variables[name] = nodes.Name(name, "load")
449
+
450
+ if not have_plural:
451
+ plural_expr = None
452
+ elif plural_expr is None:
453
+ parser.fail("pluralize without variables", lineno)
454
+
455
+ if trimmed is None:
456
+ trimmed = self.environment.policies["ext.i18n.trimmed"]
457
+ if trimmed:
458
+ singular = self._trim_whitespace(singular)
459
+ if plural:
460
+ plural = self._trim_whitespace(plural)
461
+
462
+ node = self._make_node(
463
+ singular,
464
+ plural,
465
+ context,
466
+ variables,
467
+ plural_expr,
468
+ bool(referenced),
469
+ num_called_num and have_plural,
470
+ )
471
+ node.set_lineno(lineno)
472
+ if plural_expr_assignment is not None:
473
+ return [plural_expr_assignment, node]
474
+ else:
475
+ return node
476
+
477
+ def _trim_whitespace(self, string: str, _ws_re: t.Pattern[str] = _ws_re) -> str:
478
+ return _ws_re.sub(" ", string.strip())
479
+
480
+ def _parse_block(
481
+ self, parser: "Parser", allow_pluralize: bool
482
+ ) -> t.Tuple[t.List[str], str]:
483
+ """Parse until the next block tag with a given name."""
484
+ referenced = []
485
+ buf = []
486
+
487
+ while True:
488
+ if parser.stream.current.type == "data":
489
+ buf.append(parser.stream.current.value.replace("%", "%%"))
490
+ next(parser.stream)
491
+ elif parser.stream.current.type == "variable_begin":
492
+ next(parser.stream)
493
+ name = parser.stream.expect("name").value
494
+ referenced.append(name)
495
+ buf.append(f"%({name})s")
496
+ parser.stream.expect("variable_end")
497
+ elif parser.stream.current.type == "block_begin":
498
+ next(parser.stream)
499
+ block_name = (
500
+ parser.stream.current.value
501
+ if parser.stream.current.type == "name"
502
+ else None
503
+ )
504
+ if block_name == "endtrans":
505
+ break
506
+ elif block_name == "pluralize":
507
+ if allow_pluralize:
508
+ break
509
+ parser.fail(
510
+ "a translatable section can have only one pluralize section"
511
+ )
512
+ elif block_name == "trans":
513
+ parser.fail(
514
+ "trans blocks can't be nested; did you mean `endtrans`?"
515
+ )
516
+ parser.fail(
517
+ f"control structures in translatable sections are not allowed; "
518
+ f"saw `{block_name}`"
519
+ )
520
+ elif parser.stream.eos:
521
+ parser.fail("unclosed translation block")
522
+ else:
523
+ raise RuntimeError("internal parser error")
524
+
525
+ return referenced, concat(buf)
526
+
527
+ def _make_node(
528
+ self,
529
+ singular: str,
530
+ plural: t.Optional[str],
531
+ context: t.Optional[str],
532
+ variables: t.Dict[str, nodes.Expr],
533
+ plural_expr: t.Optional[nodes.Expr],
534
+ vars_referenced: bool,
535
+ num_called_num: bool,
536
+ ) -> nodes.Output:
537
+ """Generates a useful node from the data provided."""
538
+ newstyle = self.environment.newstyle_gettext # type: ignore
539
+ node: nodes.Expr
540
+
541
+ # no variables referenced? no need to escape for old style
542
+ # gettext invocations only if there are vars.
543
+ if not vars_referenced and not newstyle:
544
+ singular = singular.replace("%%", "%")
545
+ if plural:
546
+ plural = plural.replace("%%", "%")
547
+
548
+ func_name = "gettext"
549
+ func_args: t.List[nodes.Expr] = [nodes.Const(singular)]
550
+
551
+ if context is not None:
552
+ func_args.insert(0, nodes.Const(context))
553
+ func_name = f"p{func_name}"
554
+
555
+ if plural_expr is not None:
556
+ func_name = f"n{func_name}"
557
+ func_args.extend((nodes.Const(plural), plural_expr))
558
+
559
+ node = nodes.Call(nodes.Name(func_name, "load"), func_args, [], None, None)
560
+
561
+ # in case newstyle gettext is used, the method is powerful
562
+ # enough to handle the variable expansion and autoescape
563
+ # handling itself
564
+ if newstyle:
565
+ for key, value in variables.items():
566
+ # the function adds that later anyways in case num was
567
+ # called num, so just skip it.
568
+ if num_called_num and key == "num":
569
+ continue
570
+ node.kwargs.append(nodes.Keyword(key, value))
571
+
572
+ # otherwise do that here
573
+ else:
574
+ # mark the return value as safe if we are in an
575
+ # environment with autoescaping turned on
576
+ node = nodes.MarkSafeIfAutoescape(node)
577
+ if variables:
578
+ node = nodes.Mod(
579
+ node,
580
+ nodes.Dict(
581
+ [
582
+ nodes.Pair(nodes.Const(key), value)
583
+ for key, value in variables.items()
584
+ ]
585
+ ),
586
+ )
587
+ return nodes.Output([node])
588
+
589
+
590
+ class ExprStmtExtension(Extension):
591
+ """Adds a `do` tag to Jinja that works like the print statement just
592
+ that it doesn't print the return value.
593
+ """
594
+
595
+ tags = {"do"}
596
+
597
+ def parse(self, parser: "Parser") -> nodes.ExprStmt:
598
+ node = nodes.ExprStmt(lineno=next(parser.stream).lineno)
599
+ node.node = parser.parse_tuple()
600
+ return node
601
+
602
+
603
+ class LoopControlExtension(Extension):
604
+ """Adds break and continue to the template engine."""
605
+
606
+ tags = {"break", "continue"}
607
+
608
+ def parse(self, parser: "Parser") -> t.Union[nodes.Break, nodes.Continue]:
609
+ token = next(parser.stream)
610
+ if token.value == "break":
611
+ return nodes.Break(lineno=token.lineno)
612
+ return nodes.Continue(lineno=token.lineno)
613
+
614
+
615
+ class DebugExtension(Extension):
616
+ """A ``{% debug %}`` tag that dumps the available variables,
617
+ filters, and tests.
618
+
619
+ .. code-block:: html+jinja
620
+
621
+ <pre>{% debug %}</pre>
622
+
623
+ .. code-block:: text
624
+
625
+ {'context': {'cycler': <class 'jinja2.utils.Cycler'>,
626
+ ...,
627
+ 'namespace': <class 'jinja2.utils.Namespace'>},
628
+ 'filters': ['abs', 'attr', 'batch', 'capitalize', 'center', 'count', 'd',
629
+ ..., 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'xmlattr'],
630
+ 'tests': ['!=', '<', '<=', '==', '>', '>=', 'callable', 'defined',
631
+ ..., 'odd', 'sameas', 'sequence', 'string', 'undefined', 'upper']}
632
+
633
+ .. versionadded:: 2.11.0
634
+ """
635
+
636
+ tags = {"debug"}
637
+
638
+ def parse(self, parser: "Parser") -> nodes.Output:
639
+ lineno = parser.stream.expect("name:debug").lineno
640
+ context = nodes.ContextReference()
641
+ result = self.call_method("_render", [context], lineno=lineno)
642
+ return nodes.Output([result], lineno=lineno)
643
+
644
+ def _render(self, context: Context) -> str:
645
+ result = {
646
+ "context": context.get_all(),
647
+ "filters": sorted(self.environment.filters.keys()),
648
+ "tests": sorted(self.environment.tests.keys()),
649
+ }
650
+
651
+ # Set the depth since the intent is to show the top few names.
652
+ return pprint.pformat(result, depth=3, compact=True)
653
+
654
+
655
+ def extract_from_ast(
656
+ ast: nodes.Template,
657
+ gettext_functions: t.Sequence[str] = GETTEXT_FUNCTIONS,
658
+ babel_style: bool = True,
659
+ ) -> t.Iterator[
660
+ t.Tuple[int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]]
661
+ ]:
662
+ """Extract localizable strings from the given template node. Per
663
+ default this function returns matches in babel style that means non string
664
+ parameters as well as keyword arguments are returned as `None`. This
665
+ allows Babel to figure out what you really meant if you are using
666
+ gettext functions that allow keyword arguments for placeholder expansion.
667
+ If you don't want that behavior set the `babel_style` parameter to `False`
668
+ which causes only strings to be returned and parameters are always stored
669
+ in tuples. As a consequence invalid gettext calls (calls without a single
670
+ string parameter or string parameters after non-string parameters) are
671
+ skipped.
672
+
673
+ This example explains the behavior:
674
+
675
+ >>> from jinja2 import Environment
676
+ >>> env = Environment()
677
+ >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}')
678
+ >>> list(extract_from_ast(node))
679
+ [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))]
680
+ >>> list(extract_from_ast(node, babel_style=False))
681
+ [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))]
682
+
683
+ For every string found this function yields a ``(lineno, function,
684
+ message)`` tuple, where:
685
+
686
+ * ``lineno`` is the number of the line on which the string was found,
687
+ * ``function`` is the name of the ``gettext`` function used (if the
688
+ string was extracted from embedded Python code), and
689
+ * ``message`` is the string, or a tuple of strings for functions
690
+ with multiple string arguments.
691
+
692
+ This extraction function operates on the AST and is because of that unable
693
+ to extract any comments. For comment support you have to use the babel
694
+ extraction interface or extract comments yourself.
695
+ """
696
+ out: t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]]
697
+
698
+ for node in ast.find_all(nodes.Call):
699
+ if (
700
+ not isinstance(node.node, nodes.Name)
701
+ or node.node.name not in gettext_functions
702
+ ):
703
+ continue
704
+
705
+ strings: t.List[t.Optional[str]] = []
706
+
707
+ for arg in node.args:
708
+ if isinstance(arg, nodes.Const) and isinstance(arg.value, str):
709
+ strings.append(arg.value)
710
+ else:
711
+ strings.append(None)
712
+
713
+ for _ in node.kwargs:
714
+ strings.append(None)
715
+ if node.dyn_args is not None:
716
+ strings.append(None)
717
+ if node.dyn_kwargs is not None:
718
+ strings.append(None)
719
+
720
+ if not babel_style:
721
+ out = tuple(x for x in strings if x is not None)
722
+
723
+ if not out:
724
+ continue
725
+ else:
726
+ if len(strings) == 1:
727
+ out = strings[0]
728
+ else:
729
+ out = tuple(strings)
730
+
731
+ yield node.lineno, node.node.name, out
732
+
733
+
734
+ class _CommentFinder:
735
+ """Helper class to find comments in a token stream. Can only
736
+ find comments for gettext calls forwards. Once the comment
737
+ from line 4 is found, a comment for line 1 will not return a
738
+ usable value.
739
+ """
740
+
741
+ def __init__(
742
+ self, tokens: t.Sequence[t.Tuple[int, str, str]], comment_tags: t.Sequence[str]
743
+ ) -> None:
744
+ self.tokens = tokens
745
+ self.comment_tags = comment_tags
746
+ self.offset = 0
747
+ self.last_lineno = 0
748
+
749
+ def find_backwards(self, offset: int) -> t.List[str]:
750
+ try:
751
+ for _, token_type, token_value in reversed(
752
+ self.tokens[self.offset : offset]
753
+ ):
754
+ if token_type in ("comment", "linecomment"):
755
+ try:
756
+ prefix, comment = token_value.split(None, 1)
757
+ except ValueError:
758
+ continue
759
+ if prefix in self.comment_tags:
760
+ return [comment.rstrip()]
761
+ return []
762
+ finally:
763
+ self.offset = offset
764
+
765
+ def find_comments(self, lineno: int) -> t.List[str]:
766
+ if not self.comment_tags or self.last_lineno > lineno:
767
+ return []
768
+ for idx, (token_lineno, _, _) in enumerate(self.tokens[self.offset :]):
769
+ if token_lineno > lineno:
770
+ return self.find_backwards(self.offset + idx)
771
+ return self.find_backwards(len(self.tokens))
772
+
773
+
774
+ def babel_extract(
775
+ fileobj: t.BinaryIO,
776
+ keywords: t.Sequence[str],
777
+ comment_tags: t.Sequence[str],
778
+ options: t.Dict[str, t.Any],
779
+ ) -> t.Iterator[
780
+ t.Tuple[
781
+ int, str, t.Union[t.Optional[str], t.Tuple[t.Optional[str], ...]], t.List[str]
782
+ ]
783
+ ]:
784
+ """Babel extraction method for Jinja templates.
785
+
786
+ .. versionchanged:: 2.3
787
+ Basic support for translation comments was added. If `comment_tags`
788
+ is now set to a list of keywords for extraction, the extractor will
789
+ try to find the best preceding comment that begins with one of the
790
+ keywords. For best results, make sure to not have more than one
791
+ gettext call in one line of code and the matching comment in the
792
+ same line or the line before.
793
+
794
+ .. versionchanged:: 2.5.1
795
+ The `newstyle_gettext` flag can be set to `True` to enable newstyle
796
+ gettext calls.
797
+
798
+ .. versionchanged:: 2.7
799
+ A `silent` option can now be provided. If set to `False` template
800
+ syntax errors are propagated instead of being ignored.
801
+
802
+ :param fileobj: the file-like object the messages should be extracted from
803
+ :param keywords: a list of keywords (i.e. function names) that should be
804
+ recognized as translation functions
805
+ :param comment_tags: a list of translator tags to search for and include
806
+ in the results.
807
+ :param options: a dictionary of additional options (optional)
808
+ :return: an iterator over ``(lineno, funcname, message, comments)`` tuples.
809
+ (comments will be empty currently)
810
+ """
811
+ extensions: t.Dict[t.Type[Extension], None] = {}
812
+
813
+ for extension_name in options.get("extensions", "").split(","):
814
+ extension_name = extension_name.strip()
815
+
816
+ if not extension_name:
817
+ continue
818
+
819
+ extensions[import_string(extension_name)] = None
820
+
821
+ if InternationalizationExtension not in extensions:
822
+ extensions[InternationalizationExtension] = None
823
+
824
+ def getbool(options: t.Mapping[str, str], key: str, default: bool = False) -> bool:
825
+ return options.get(key, str(default)).lower() in {"1", "on", "yes", "true"}
826
+
827
+ silent = getbool(options, "silent", True)
828
+ environment = Environment(
829
+ options.get("block_start_string", defaults.BLOCK_START_STRING),
830
+ options.get("block_end_string", defaults.BLOCK_END_STRING),
831
+ options.get("variable_start_string", defaults.VARIABLE_START_STRING),
832
+ options.get("variable_end_string", defaults.VARIABLE_END_STRING),
833
+ options.get("comment_start_string", defaults.COMMENT_START_STRING),
834
+ options.get("comment_end_string", defaults.COMMENT_END_STRING),
835
+ options.get("line_statement_prefix") or defaults.LINE_STATEMENT_PREFIX,
836
+ options.get("line_comment_prefix") or defaults.LINE_COMMENT_PREFIX,
837
+ getbool(options, "trim_blocks", defaults.TRIM_BLOCKS),
838
+ getbool(options, "lstrip_blocks", defaults.LSTRIP_BLOCKS),
839
+ defaults.NEWLINE_SEQUENCE,
840
+ getbool(options, "keep_trailing_newline", defaults.KEEP_TRAILING_NEWLINE),
841
+ tuple(extensions),
842
+ cache_size=0,
843
+ auto_reload=False,
844
+ )
845
+
846
+ if getbool(options, "trimmed"):
847
+ environment.policies["ext.i18n.trimmed"] = True
848
+ if getbool(options, "newstyle_gettext"):
849
+ environment.newstyle_gettext = True # type: ignore
850
+
851
+ source = fileobj.read().decode(options.get("encoding", "utf-8"))
852
+ try:
853
+ node = environment.parse(source)
854
+ tokens = list(environment.lex(environment.preprocess(source)))
855
+ except TemplateSyntaxError:
856
+ if not silent:
857
+ raise
858
+ # skip templates with syntax errors
859
+ return
860
+
861
+ finder = _CommentFinder(tokens, comment_tags)
862
+ for lineno, func, message in extract_from_ast(node, keywords):
863
+ yield lineno, func, message, finder.find_comments(lineno)
864
+
865
+
866
+ #: nicer import names
867
+ i18n = InternationalizationExtension
868
+ do = ExprStmtExtension
869
+ loopcontrols = LoopControlExtension
870
+ debug = DebugExtension
.venv/lib/python3.11/site-packages/jinja2/lexer.py ADDED
@@ -0,0 +1,868 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Implements a Jinja / Python combination lexer. The ``Lexer`` class
2
+ is used to do some preprocessing. It filters out invalid operators like
3
+ the bitshift operators we don't allow in templates. It separates
4
+ template code and python code in expressions.
5
+ """
6
+
7
+ import re
8
+ import typing as t
9
+ from ast import literal_eval
10
+ from collections import deque
11
+ from sys import intern
12
+
13
+ from ._identifier import pattern as name_re
14
+ from .exceptions import TemplateSyntaxError
15
+ from .utils import LRUCache
16
+
17
+ if t.TYPE_CHECKING:
18
+ import typing_extensions as te
19
+
20
+ from .environment import Environment
21
+
22
+ # cache for the lexers. Exists in order to be able to have multiple
23
+ # environments with the same lexer
24
+ _lexer_cache: t.MutableMapping[t.Tuple, "Lexer"] = LRUCache(50) # type: ignore
25
+
26
+ # static regular expressions
27
+ whitespace_re = re.compile(r"\s+")
28
+ newline_re = re.compile(r"(\r\n|\r|\n)")
29
+ string_re = re.compile(
30
+ r"('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S
31
+ )
32
+ integer_re = re.compile(
33
+ r"""
34
+ (
35
+ 0b(_?[0-1])+ # binary
36
+ |
37
+ 0o(_?[0-7])+ # octal
38
+ |
39
+ 0x(_?[\da-f])+ # hex
40
+ |
41
+ [1-9](_?\d)* # decimal
42
+ |
43
+ 0(_?0)* # decimal zero
44
+ )
45
+ """,
46
+ re.IGNORECASE | re.VERBOSE,
47
+ )
48
+ float_re = re.compile(
49
+ r"""
50
+ (?<!\.) # doesn't start with a .
51
+ (\d+_)*\d+ # digits, possibly _ separated
52
+ (
53
+ (\.(\d+_)*\d+)? # optional fractional part
54
+ e[+\-]?(\d+_)*\d+ # exponent part
55
+ |
56
+ \.(\d+_)*\d+ # required fractional part
57
+ )
58
+ """,
59
+ re.IGNORECASE | re.VERBOSE,
60
+ )
61
+
62
+ # internal the tokens and keep references to them
63
+ TOKEN_ADD = intern("add")
64
+ TOKEN_ASSIGN = intern("assign")
65
+ TOKEN_COLON = intern("colon")
66
+ TOKEN_COMMA = intern("comma")
67
+ TOKEN_DIV = intern("div")
68
+ TOKEN_DOT = intern("dot")
69
+ TOKEN_EQ = intern("eq")
70
+ TOKEN_FLOORDIV = intern("floordiv")
71
+ TOKEN_GT = intern("gt")
72
+ TOKEN_GTEQ = intern("gteq")
73
+ TOKEN_LBRACE = intern("lbrace")
74
+ TOKEN_LBRACKET = intern("lbracket")
75
+ TOKEN_LPAREN = intern("lparen")
76
+ TOKEN_LT = intern("lt")
77
+ TOKEN_LTEQ = intern("lteq")
78
+ TOKEN_MOD = intern("mod")
79
+ TOKEN_MUL = intern("mul")
80
+ TOKEN_NE = intern("ne")
81
+ TOKEN_PIPE = intern("pipe")
82
+ TOKEN_POW = intern("pow")
83
+ TOKEN_RBRACE = intern("rbrace")
84
+ TOKEN_RBRACKET = intern("rbracket")
85
+ TOKEN_RPAREN = intern("rparen")
86
+ TOKEN_SEMICOLON = intern("semicolon")
87
+ TOKEN_SUB = intern("sub")
88
+ TOKEN_TILDE = intern("tilde")
89
+ TOKEN_WHITESPACE = intern("whitespace")
90
+ TOKEN_FLOAT = intern("float")
91
+ TOKEN_INTEGER = intern("integer")
92
+ TOKEN_NAME = intern("name")
93
+ TOKEN_STRING = intern("string")
94
+ TOKEN_OPERATOR = intern("operator")
95
+ TOKEN_BLOCK_BEGIN = intern("block_begin")
96
+ TOKEN_BLOCK_END = intern("block_end")
97
+ TOKEN_VARIABLE_BEGIN = intern("variable_begin")
98
+ TOKEN_VARIABLE_END = intern("variable_end")
99
+ TOKEN_RAW_BEGIN = intern("raw_begin")
100
+ TOKEN_RAW_END = intern("raw_end")
101
+ TOKEN_COMMENT_BEGIN = intern("comment_begin")
102
+ TOKEN_COMMENT_END = intern("comment_end")
103
+ TOKEN_COMMENT = intern("comment")
104
+ TOKEN_LINESTATEMENT_BEGIN = intern("linestatement_begin")
105
+ TOKEN_LINESTATEMENT_END = intern("linestatement_end")
106
+ TOKEN_LINECOMMENT_BEGIN = intern("linecomment_begin")
107
+ TOKEN_LINECOMMENT_END = intern("linecomment_end")
108
+ TOKEN_LINECOMMENT = intern("linecomment")
109
+ TOKEN_DATA = intern("data")
110
+ TOKEN_INITIAL = intern("initial")
111
+ TOKEN_EOF = intern("eof")
112
+
113
+ # bind operators to token types
114
+ operators = {
115
+ "+": TOKEN_ADD,
116
+ "-": TOKEN_SUB,
117
+ "/": TOKEN_DIV,
118
+ "//": TOKEN_FLOORDIV,
119
+ "*": TOKEN_MUL,
120
+ "%": TOKEN_MOD,
121
+ "**": TOKEN_POW,
122
+ "~": TOKEN_TILDE,
123
+ "[": TOKEN_LBRACKET,
124
+ "]": TOKEN_RBRACKET,
125
+ "(": TOKEN_LPAREN,
126
+ ")": TOKEN_RPAREN,
127
+ "{": TOKEN_LBRACE,
128
+ "}": TOKEN_RBRACE,
129
+ "==": TOKEN_EQ,
130
+ "!=": TOKEN_NE,
131
+ ">": TOKEN_GT,
132
+ ">=": TOKEN_GTEQ,
133
+ "<": TOKEN_LT,
134
+ "<=": TOKEN_LTEQ,
135
+ "=": TOKEN_ASSIGN,
136
+ ".": TOKEN_DOT,
137
+ ":": TOKEN_COLON,
138
+ "|": TOKEN_PIPE,
139
+ ",": TOKEN_COMMA,
140
+ ";": TOKEN_SEMICOLON,
141
+ }
142
+
143
+ reverse_operators = {v: k for k, v in operators.items()}
144
+ assert len(operators) == len(reverse_operators), "operators dropped"
145
+ operator_re = re.compile(
146
+ f"({'|'.join(re.escape(x) for x in sorted(operators, key=lambda x: -len(x)))})"
147
+ )
148
+
149
+ ignored_tokens = frozenset(
150
+ [
151
+ TOKEN_COMMENT_BEGIN,
152
+ TOKEN_COMMENT,
153
+ TOKEN_COMMENT_END,
154
+ TOKEN_WHITESPACE,
155
+ TOKEN_LINECOMMENT_BEGIN,
156
+ TOKEN_LINECOMMENT_END,
157
+ TOKEN_LINECOMMENT,
158
+ ]
159
+ )
160
+ ignore_if_empty = frozenset(
161
+ [TOKEN_WHITESPACE, TOKEN_DATA, TOKEN_COMMENT, TOKEN_LINECOMMENT]
162
+ )
163
+
164
+
165
+ def _describe_token_type(token_type: str) -> str:
166
+ if token_type in reverse_operators:
167
+ return reverse_operators[token_type]
168
+
169
+ return {
170
+ TOKEN_COMMENT_BEGIN: "begin of comment",
171
+ TOKEN_COMMENT_END: "end of comment",
172
+ TOKEN_COMMENT: "comment",
173
+ TOKEN_LINECOMMENT: "comment",
174
+ TOKEN_BLOCK_BEGIN: "begin of statement block",
175
+ TOKEN_BLOCK_END: "end of statement block",
176
+ TOKEN_VARIABLE_BEGIN: "begin of print statement",
177
+ TOKEN_VARIABLE_END: "end of print statement",
178
+ TOKEN_LINESTATEMENT_BEGIN: "begin of line statement",
179
+ TOKEN_LINESTATEMENT_END: "end of line statement",
180
+ TOKEN_DATA: "template data / text",
181
+ TOKEN_EOF: "end of template",
182
+ }.get(token_type, token_type)
183
+
184
+
185
+ def describe_token(token: "Token") -> str:
186
+ """Returns a description of the token."""
187
+ if token.type == TOKEN_NAME:
188
+ return token.value
189
+
190
+ return _describe_token_type(token.type)
191
+
192
+
193
+ def describe_token_expr(expr: str) -> str:
194
+ """Like `describe_token` but for token expressions."""
195
+ if ":" in expr:
196
+ type, value = expr.split(":", 1)
197
+
198
+ if type == TOKEN_NAME:
199
+ return value
200
+ else:
201
+ type = expr
202
+
203
+ return _describe_token_type(type)
204
+
205
+
206
+ def count_newlines(value: str) -> int:
207
+ """Count the number of newline characters in the string. This is
208
+ useful for extensions that filter a stream.
209
+ """
210
+ return len(newline_re.findall(value))
211
+
212
+
213
+ def compile_rules(environment: "Environment") -> t.List[t.Tuple[str, str]]:
214
+ """Compiles all the rules from the environment into a list of rules."""
215
+ e = re.escape
216
+ rules = [
217
+ (
218
+ len(environment.comment_start_string),
219
+ TOKEN_COMMENT_BEGIN,
220
+ e(environment.comment_start_string),
221
+ ),
222
+ (
223
+ len(environment.block_start_string),
224
+ TOKEN_BLOCK_BEGIN,
225
+ e(environment.block_start_string),
226
+ ),
227
+ (
228
+ len(environment.variable_start_string),
229
+ TOKEN_VARIABLE_BEGIN,
230
+ e(environment.variable_start_string),
231
+ ),
232
+ ]
233
+
234
+ if environment.line_statement_prefix is not None:
235
+ rules.append(
236
+ (
237
+ len(environment.line_statement_prefix),
238
+ TOKEN_LINESTATEMENT_BEGIN,
239
+ r"^[ \t\v]*" + e(environment.line_statement_prefix),
240
+ )
241
+ )
242
+ if environment.line_comment_prefix is not None:
243
+ rules.append(
244
+ (
245
+ len(environment.line_comment_prefix),
246
+ TOKEN_LINECOMMENT_BEGIN,
247
+ r"(?:^|(?<=\S))[^\S\r\n]*" + e(environment.line_comment_prefix),
248
+ )
249
+ )
250
+
251
+ return [x[1:] for x in sorted(rules, reverse=True)]
252
+
253
+
254
+ class Failure:
255
+ """Class that raises a `TemplateSyntaxError` if called.
256
+ Used by the `Lexer` to specify known errors.
257
+ """
258
+
259
+ def __init__(
260
+ self, message: str, cls: t.Type[TemplateSyntaxError] = TemplateSyntaxError
261
+ ) -> None:
262
+ self.message = message
263
+ self.error_class = cls
264
+
265
+ def __call__(self, lineno: int, filename: t.Optional[str]) -> "te.NoReturn":
266
+ raise self.error_class(self.message, lineno, filename)
267
+
268
+
269
+ class Token(t.NamedTuple):
270
+ lineno: int
271
+ type: str
272
+ value: str
273
+
274
+ def __str__(self) -> str:
275
+ return describe_token(self)
276
+
277
+ def test(self, expr: str) -> bool:
278
+ """Test a token against a token expression. This can either be a
279
+ token type or ``'token_type:token_value'``. This can only test
280
+ against string values and types.
281
+ """
282
+ # here we do a regular string equality check as test_any is usually
283
+ # passed an iterable of not interned strings.
284
+ if self.type == expr:
285
+ return True
286
+
287
+ if ":" in expr:
288
+ return expr.split(":", 1) == [self.type, self.value]
289
+
290
+ return False
291
+
292
+ def test_any(self, *iterable: str) -> bool:
293
+ """Test against multiple token expressions."""
294
+ return any(self.test(expr) for expr in iterable)
295
+
296
+
297
+ class TokenStreamIterator:
298
+ """The iterator for tokenstreams. Iterate over the stream
299
+ until the eof token is reached.
300
+ """
301
+
302
+ def __init__(self, stream: "TokenStream") -> None:
303
+ self.stream = stream
304
+
305
+ def __iter__(self) -> "TokenStreamIterator":
306
+ return self
307
+
308
+ def __next__(self) -> Token:
309
+ token = self.stream.current
310
+
311
+ if token.type is TOKEN_EOF:
312
+ self.stream.close()
313
+ raise StopIteration
314
+
315
+ next(self.stream)
316
+ return token
317
+
318
+
319
+ class TokenStream:
320
+ """A token stream is an iterable that yields :class:`Token`\\s. The
321
+ parser however does not iterate over it but calls :meth:`next` to go
322
+ one token ahead. The current active token is stored as :attr:`current`.
323
+ """
324
+
325
+ def __init__(
326
+ self,
327
+ generator: t.Iterable[Token],
328
+ name: t.Optional[str],
329
+ filename: t.Optional[str],
330
+ ):
331
+ self._iter = iter(generator)
332
+ self._pushed: te.Deque[Token] = deque()
333
+ self.name = name
334
+ self.filename = filename
335
+ self.closed = False
336
+ self.current = Token(1, TOKEN_INITIAL, "")
337
+ next(self)
338
+
339
+ def __iter__(self) -> TokenStreamIterator:
340
+ return TokenStreamIterator(self)
341
+
342
+ def __bool__(self) -> bool:
343
+ return bool(self._pushed) or self.current.type is not TOKEN_EOF
344
+
345
+ @property
346
+ def eos(self) -> bool:
347
+ """Are we at the end of the stream?"""
348
+ return not self
349
+
350
+ def push(self, token: Token) -> None:
351
+ """Push a token back to the stream."""
352
+ self._pushed.append(token)
353
+
354
+ def look(self) -> Token:
355
+ """Look at the next token."""
356
+ old_token = next(self)
357
+ result = self.current
358
+ self.push(result)
359
+ self.current = old_token
360
+ return result
361
+
362
+ def skip(self, n: int = 1) -> None:
363
+ """Got n tokens ahead."""
364
+ for _ in range(n):
365
+ next(self)
366
+
367
+ def next_if(self, expr: str) -> t.Optional[Token]:
368
+ """Perform the token test and return the token if it matched.
369
+ Otherwise the return value is `None`.
370
+ """
371
+ if self.current.test(expr):
372
+ return next(self)
373
+
374
+ return None
375
+
376
+ def skip_if(self, expr: str) -> bool:
377
+ """Like :meth:`next_if` but only returns `True` or `False`."""
378
+ return self.next_if(expr) is not None
379
+
380
+ def __next__(self) -> Token:
381
+ """Go one token ahead and return the old one.
382
+
383
+ Use the built-in :func:`next` instead of calling this directly.
384
+ """
385
+ rv = self.current
386
+
387
+ if self._pushed:
388
+ self.current = self._pushed.popleft()
389
+ elif self.current.type is not TOKEN_EOF:
390
+ try:
391
+ self.current = next(self._iter)
392
+ except StopIteration:
393
+ self.close()
394
+
395
+ return rv
396
+
397
+ def close(self) -> None:
398
+ """Close the stream."""
399
+ self.current = Token(self.current.lineno, TOKEN_EOF, "")
400
+ self._iter = iter(())
401
+ self.closed = True
402
+
403
+ def expect(self, expr: str) -> Token:
404
+ """Expect a given token type and return it. This accepts the same
405
+ argument as :meth:`jinja2.lexer.Token.test`.
406
+ """
407
+ if not self.current.test(expr):
408
+ expr = describe_token_expr(expr)
409
+
410
+ if self.current.type is TOKEN_EOF:
411
+ raise TemplateSyntaxError(
412
+ f"unexpected end of template, expected {expr!r}.",
413
+ self.current.lineno,
414
+ self.name,
415
+ self.filename,
416
+ )
417
+
418
+ raise TemplateSyntaxError(
419
+ f"expected token {expr!r}, got {describe_token(self.current)!r}",
420
+ self.current.lineno,
421
+ self.name,
422
+ self.filename,
423
+ )
424
+
425
+ return next(self)
426
+
427
+
428
+ def get_lexer(environment: "Environment") -> "Lexer":
429
+ """Return a lexer which is probably cached."""
430
+ key = (
431
+ environment.block_start_string,
432
+ environment.block_end_string,
433
+ environment.variable_start_string,
434
+ environment.variable_end_string,
435
+ environment.comment_start_string,
436
+ environment.comment_end_string,
437
+ environment.line_statement_prefix,
438
+ environment.line_comment_prefix,
439
+ environment.trim_blocks,
440
+ environment.lstrip_blocks,
441
+ environment.newline_sequence,
442
+ environment.keep_trailing_newline,
443
+ )
444
+ lexer = _lexer_cache.get(key)
445
+
446
+ if lexer is None:
447
+ _lexer_cache[key] = lexer = Lexer(environment)
448
+
449
+ return lexer
450
+
451
+
452
+ class OptionalLStrip(tuple): # type: ignore[type-arg]
453
+ """A special tuple for marking a point in the state that can have
454
+ lstrip applied.
455
+ """
456
+
457
+ __slots__ = ()
458
+
459
+ # Even though it looks like a no-op, creating instances fails
460
+ # without this.
461
+ def __new__(cls, *members, **kwargs): # type: ignore
462
+ return super().__new__(cls, members)
463
+
464
+
465
+ class _Rule(t.NamedTuple):
466
+ pattern: t.Pattern[str]
467
+ tokens: t.Union[str, t.Tuple[str, ...], t.Tuple[Failure]]
468
+ command: t.Optional[str]
469
+
470
+
471
+ class Lexer:
472
+ """Class that implements a lexer for a given environment. Automatically
473
+ created by the environment class, usually you don't have to do that.
474
+
475
+ Note that the lexer is not automatically bound to an environment.
476
+ Multiple environments can share the same lexer.
477
+ """
478
+
479
+ def __init__(self, environment: "Environment") -> None:
480
+ # shortcuts
481
+ e = re.escape
482
+
483
+ def c(x: str) -> t.Pattern[str]:
484
+ return re.compile(x, re.M | re.S)
485
+
486
+ # lexing rules for tags
487
+ tag_rules: t.List[_Rule] = [
488
+ _Rule(whitespace_re, TOKEN_WHITESPACE, None),
489
+ _Rule(float_re, TOKEN_FLOAT, None),
490
+ _Rule(integer_re, TOKEN_INTEGER, None),
491
+ _Rule(name_re, TOKEN_NAME, None),
492
+ _Rule(string_re, TOKEN_STRING, None),
493
+ _Rule(operator_re, TOKEN_OPERATOR, None),
494
+ ]
495
+
496
+ # assemble the root lexing rule. because "|" is ungreedy
497
+ # we have to sort by length so that the lexer continues working
498
+ # as expected when we have parsing rules like <% for block and
499
+ # <%= for variables. (if someone wants asp like syntax)
500
+ # variables are just part of the rules if variable processing
501
+ # is required.
502
+ root_tag_rules = compile_rules(environment)
503
+
504
+ block_start_re = e(environment.block_start_string)
505
+ block_end_re = e(environment.block_end_string)
506
+ comment_end_re = e(environment.comment_end_string)
507
+ variable_end_re = e(environment.variable_end_string)
508
+
509
+ # block suffix if trimming is enabled
510
+ block_suffix_re = "\\n?" if environment.trim_blocks else ""
511
+
512
+ self.lstrip_blocks = environment.lstrip_blocks
513
+
514
+ self.newline_sequence = environment.newline_sequence
515
+ self.keep_trailing_newline = environment.keep_trailing_newline
516
+
517
+ root_raw_re = (
518
+ rf"(?P<raw_begin>{block_start_re}(\-|\+|)\s*raw\s*"
519
+ rf"(?:\-{block_end_re}\s*|{block_end_re}))"
520
+ )
521
+ root_parts_re = "|".join(
522
+ [root_raw_re] + [rf"(?P<{n}>{r}(\-|\+|))" for n, r in root_tag_rules]
523
+ )
524
+
525
+ # global lexing rules
526
+ self.rules: t.Dict[str, t.List[_Rule]] = {
527
+ "root": [
528
+ # directives
529
+ _Rule(
530
+ c(rf"(.*?)(?:{root_parts_re})"),
531
+ OptionalLStrip(TOKEN_DATA, "#bygroup"), # type: ignore
532
+ "#bygroup",
533
+ ),
534
+ # data
535
+ _Rule(c(".+"), TOKEN_DATA, None),
536
+ ],
537
+ # comments
538
+ TOKEN_COMMENT_BEGIN: [
539
+ _Rule(
540
+ c(
541
+ rf"(.*?)((?:\+{comment_end_re}|\-{comment_end_re}\s*"
542
+ rf"|{comment_end_re}{block_suffix_re}))"
543
+ ),
544
+ (TOKEN_COMMENT, TOKEN_COMMENT_END),
545
+ "#pop",
546
+ ),
547
+ _Rule(c(r"(.)"), (Failure("Missing end of comment tag"),), None),
548
+ ],
549
+ # blocks
550
+ TOKEN_BLOCK_BEGIN: [
551
+ _Rule(
552
+ c(
553
+ rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
554
+ rf"|{block_end_re}{block_suffix_re})"
555
+ ),
556
+ TOKEN_BLOCK_END,
557
+ "#pop",
558
+ ),
559
+ ]
560
+ + tag_rules,
561
+ # variables
562
+ TOKEN_VARIABLE_BEGIN: [
563
+ _Rule(
564
+ c(rf"\-{variable_end_re}\s*|{variable_end_re}"),
565
+ TOKEN_VARIABLE_END,
566
+ "#pop",
567
+ )
568
+ ]
569
+ + tag_rules,
570
+ # raw block
571
+ TOKEN_RAW_BEGIN: [
572
+ _Rule(
573
+ c(
574
+ rf"(.*?)((?:{block_start_re}(\-|\+|))\s*endraw\s*"
575
+ rf"(?:\+{block_end_re}|\-{block_end_re}\s*"
576
+ rf"|{block_end_re}{block_suffix_re}))"
577
+ ),
578
+ OptionalLStrip(TOKEN_DATA, TOKEN_RAW_END), # type: ignore
579
+ "#pop",
580
+ ),
581
+ _Rule(c(r"(.)"), (Failure("Missing end of raw directive"),), None),
582
+ ],
583
+ # line statements
584
+ TOKEN_LINESTATEMENT_BEGIN: [
585
+ _Rule(c(r"\s*(\n|$)"), TOKEN_LINESTATEMENT_END, "#pop")
586
+ ]
587
+ + tag_rules,
588
+ # line comments
589
+ TOKEN_LINECOMMENT_BEGIN: [
590
+ _Rule(
591
+ c(r"(.*?)()(?=\n|$)"),
592
+ (TOKEN_LINECOMMENT, TOKEN_LINECOMMENT_END),
593
+ "#pop",
594
+ )
595
+ ],
596
+ }
597
+
598
+ def _normalize_newlines(self, value: str) -> str:
599
+ """Replace all newlines with the configured sequence in strings
600
+ and template data.
601
+ """
602
+ return newline_re.sub(self.newline_sequence, value)
603
+
604
+ def tokenize(
605
+ self,
606
+ source: str,
607
+ name: t.Optional[str] = None,
608
+ filename: t.Optional[str] = None,
609
+ state: t.Optional[str] = None,
610
+ ) -> TokenStream:
611
+ """Calls tokeniter + tokenize and wraps it in a token stream."""
612
+ stream = self.tokeniter(source, name, filename, state)
613
+ return TokenStream(self.wrap(stream, name, filename), name, filename)
614
+
615
+ def wrap(
616
+ self,
617
+ stream: t.Iterable[t.Tuple[int, str, str]],
618
+ name: t.Optional[str] = None,
619
+ filename: t.Optional[str] = None,
620
+ ) -> t.Iterator[Token]:
621
+ """This is called with the stream as returned by `tokenize` and wraps
622
+ every token in a :class:`Token` and converts the value.
623
+ """
624
+ for lineno, token, value_str in stream:
625
+ if token in ignored_tokens:
626
+ continue
627
+
628
+ value: t.Any = value_str
629
+
630
+ if token == TOKEN_LINESTATEMENT_BEGIN:
631
+ token = TOKEN_BLOCK_BEGIN
632
+ elif token == TOKEN_LINESTATEMENT_END:
633
+ token = TOKEN_BLOCK_END
634
+ # we are not interested in those tokens in the parser
635
+ elif token in (TOKEN_RAW_BEGIN, TOKEN_RAW_END):
636
+ continue
637
+ elif token == TOKEN_DATA:
638
+ value = self._normalize_newlines(value_str)
639
+ elif token == "keyword":
640
+ token = value_str
641
+ elif token == TOKEN_NAME:
642
+ value = value_str
643
+
644
+ if not value.isidentifier():
645
+ raise TemplateSyntaxError(
646
+ "Invalid character in identifier", lineno, name, filename
647
+ )
648
+ elif token == TOKEN_STRING:
649
+ # try to unescape string
650
+ try:
651
+ value = (
652
+ self._normalize_newlines(value_str[1:-1])
653
+ .encode("ascii", "backslashreplace")
654
+ .decode("unicode-escape")
655
+ )
656
+ except Exception as e:
657
+ msg = str(e).split(":")[-1].strip()
658
+ raise TemplateSyntaxError(msg, lineno, name, filename) from e
659
+ elif token == TOKEN_INTEGER:
660
+ value = int(value_str.replace("_", ""), 0)
661
+ elif token == TOKEN_FLOAT:
662
+ # remove all "_" first to support more Python versions
663
+ value = literal_eval(value_str.replace("_", ""))
664
+ elif token == TOKEN_OPERATOR:
665
+ token = operators[value_str]
666
+
667
+ yield Token(lineno, token, value)
668
+
669
+ def tokeniter(
670
+ self,
671
+ source: str,
672
+ name: t.Optional[str],
673
+ filename: t.Optional[str] = None,
674
+ state: t.Optional[str] = None,
675
+ ) -> t.Iterator[t.Tuple[int, str, str]]:
676
+ """This method tokenizes the text and returns the tokens in a
677
+ generator. Use this method if you just want to tokenize a template.
678
+
679
+ .. versionchanged:: 3.0
680
+ Only ``\\n``, ``\\r\\n`` and ``\\r`` are treated as line
681
+ breaks.
682
+ """
683
+ lines = newline_re.split(source)[::2]
684
+
685
+ if not self.keep_trailing_newline and lines[-1] == "":
686
+ del lines[-1]
687
+
688
+ source = "\n".join(lines)
689
+ pos = 0
690
+ lineno = 1
691
+ stack = ["root"]
692
+
693
+ if state is not None and state != "root":
694
+ assert state in ("variable", "block"), "invalid state"
695
+ stack.append(state + "_begin")
696
+
697
+ statetokens = self.rules[stack[-1]]
698
+ source_length = len(source)
699
+ balancing_stack: t.List[str] = []
700
+ newlines_stripped = 0
701
+ line_starting = True
702
+
703
+ while True:
704
+ # tokenizer loop
705
+ for regex, tokens, new_state in statetokens:
706
+ m = regex.match(source, pos)
707
+
708
+ # if no match we try again with the next rule
709
+ if m is None:
710
+ continue
711
+
712
+ # we only match blocks and variables if braces / parentheses
713
+ # are balanced. continue parsing with the lower rule which
714
+ # is the operator rule. do this only if the end tags look
715
+ # like operators
716
+ if balancing_stack and tokens in (
717
+ TOKEN_VARIABLE_END,
718
+ TOKEN_BLOCK_END,
719
+ TOKEN_LINESTATEMENT_END,
720
+ ):
721
+ continue
722
+
723
+ # tuples support more options
724
+ if isinstance(tokens, tuple):
725
+ groups: t.Sequence[str] = m.groups()
726
+
727
+ if isinstance(tokens, OptionalLStrip):
728
+ # Rule supports lstrip. Match will look like
729
+ # text, block type, whitespace control, type, control, ...
730
+ text = groups[0]
731
+ # Skipping the text and first type, every other group is the
732
+ # whitespace control for each type. One of the groups will be
733
+ # -, +, or empty string instead of None.
734
+ strip_sign = next(g for g in groups[2::2] if g is not None)
735
+
736
+ if strip_sign == "-":
737
+ # Strip all whitespace between the text and the tag.
738
+ stripped = text.rstrip()
739
+ newlines_stripped = text[len(stripped) :].count("\n")
740
+ groups = [stripped, *groups[1:]]
741
+ elif (
742
+ # Not marked for preserving whitespace.
743
+ strip_sign != "+"
744
+ # lstrip is enabled.
745
+ and self.lstrip_blocks
746
+ # Not a variable expression.
747
+ and not m.groupdict().get(TOKEN_VARIABLE_BEGIN)
748
+ ):
749
+ # The start of text between the last newline and the tag.
750
+ l_pos = text.rfind("\n") + 1
751
+
752
+ if l_pos > 0 or line_starting:
753
+ # If there's only whitespace between the newline and the
754
+ # tag, strip it.
755
+ if whitespace_re.fullmatch(text, l_pos):
756
+ groups = [text[:l_pos], *groups[1:]]
757
+
758
+ for idx, token in enumerate(tokens):
759
+ # failure group
760
+ if isinstance(token, Failure):
761
+ raise token(lineno, filename)
762
+ # bygroup is a bit more complex, in that case we
763
+ # yield for the current token the first named
764
+ # group that matched
765
+ elif token == "#bygroup":
766
+ for key, value in m.groupdict().items():
767
+ if value is not None:
768
+ yield lineno, key, value
769
+ lineno += value.count("\n")
770
+ break
771
+ else:
772
+ raise RuntimeError(
773
+ f"{regex!r} wanted to resolve the token dynamically"
774
+ " but no group matched"
775
+ )
776
+ # normal group
777
+ else:
778
+ data = groups[idx]
779
+
780
+ if data or token not in ignore_if_empty:
781
+ yield lineno, token, data # type: ignore[misc]
782
+
783
+ lineno += data.count("\n") + newlines_stripped
784
+ newlines_stripped = 0
785
+
786
+ # strings as token just are yielded as it.
787
+ else:
788
+ data = m.group()
789
+
790
+ # update brace/parentheses balance
791
+ if tokens == TOKEN_OPERATOR:
792
+ if data == "{":
793
+ balancing_stack.append("}")
794
+ elif data == "(":
795
+ balancing_stack.append(")")
796
+ elif data == "[":
797
+ balancing_stack.append("]")
798
+ elif data in ("}", ")", "]"):
799
+ if not balancing_stack:
800
+ raise TemplateSyntaxError(
801
+ f"unexpected '{data}'", lineno, name, filename
802
+ )
803
+
804
+ expected_op = balancing_stack.pop()
805
+
806
+ if expected_op != data:
807
+ raise TemplateSyntaxError(
808
+ f"unexpected '{data}', expected '{expected_op}'",
809
+ lineno,
810
+ name,
811
+ filename,
812
+ )
813
+
814
+ # yield items
815
+ if data or tokens not in ignore_if_empty:
816
+ yield lineno, tokens, data
817
+
818
+ lineno += data.count("\n")
819
+
820
+ line_starting = m.group()[-1:] == "\n"
821
+ # fetch new position into new variable so that we can check
822
+ # if there is a internal parsing error which would result
823
+ # in an infinite loop
824
+ pos2 = m.end()
825
+
826
+ # handle state changes
827
+ if new_state is not None:
828
+ # remove the uppermost state
829
+ if new_state == "#pop":
830
+ stack.pop()
831
+ # resolve the new state by group checking
832
+ elif new_state == "#bygroup":
833
+ for key, value in m.groupdict().items():
834
+ if value is not None:
835
+ stack.append(key)
836
+ break
837
+ else:
838
+ raise RuntimeError(
839
+ f"{regex!r} wanted to resolve the new state dynamically"
840
+ f" but no group matched"
841
+ )
842
+ # direct state name given
843
+ else:
844
+ stack.append(new_state)
845
+
846
+ statetokens = self.rules[stack[-1]]
847
+ # we are still at the same position and no stack change.
848
+ # this means a loop without break condition, avoid that and
849
+ # raise error
850
+ elif pos2 == pos:
851
+ raise RuntimeError(
852
+ f"{regex!r} yielded empty string without stack change"
853
+ )
854
+
855
+ # publish new function and start again
856
+ pos = pos2
857
+ break
858
+ # if loop terminated without break we haven't found a single match
859
+ # either we are at the end of the file or we have a problem
860
+ else:
861
+ # end of text
862
+ if pos >= source_length:
863
+ return
864
+
865
+ # something went wrong
866
+ raise TemplateSyntaxError(
867
+ f"unexpected char {source[pos]!r} at {pos}", lineno, name, filename
868
+ )
.venv/lib/python3.11/site-packages/jinja2/loaders.py ADDED
@@ -0,0 +1,693 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """API and implementations for loading templates from different data
2
+ sources.
3
+ """
4
+
5
+ import importlib.util
6
+ import os
7
+ import posixpath
8
+ import sys
9
+ import typing as t
10
+ import weakref
11
+ import zipimport
12
+ from collections import abc
13
+ from hashlib import sha1
14
+ from importlib import import_module
15
+ from types import ModuleType
16
+
17
+ from .exceptions import TemplateNotFound
18
+ from .utils import internalcode
19
+
20
+ if t.TYPE_CHECKING:
21
+ from .environment import Environment
22
+ from .environment import Template
23
+
24
+
25
+ def split_template_path(template: str) -> t.List[str]:
26
+ """Split a path into segments and perform a sanity check. If it detects
27
+ '..' in the path it will raise a `TemplateNotFound` error.
28
+ """
29
+ pieces = []
30
+ for piece in template.split("/"):
31
+ if (
32
+ os.path.sep in piece
33
+ or (os.path.altsep and os.path.altsep in piece)
34
+ or piece == os.path.pardir
35
+ ):
36
+ raise TemplateNotFound(template)
37
+ elif piece and piece != ".":
38
+ pieces.append(piece)
39
+ return pieces
40
+
41
+
42
+ class BaseLoader:
43
+ """Baseclass for all loaders. Subclass this and override `get_source` to
44
+ implement a custom loading mechanism. The environment provides a
45
+ `get_template` method that calls the loader's `load` method to get the
46
+ :class:`Template` object.
47
+
48
+ A very basic example for a loader that looks up templates on the file
49
+ system could look like this::
50
+
51
+ from jinja2 import BaseLoader, TemplateNotFound
52
+ from os.path import join, exists, getmtime
53
+
54
+ class MyLoader(BaseLoader):
55
+
56
+ def __init__(self, path):
57
+ self.path = path
58
+
59
+ def get_source(self, environment, template):
60
+ path = join(self.path, template)
61
+ if not exists(path):
62
+ raise TemplateNotFound(template)
63
+ mtime = getmtime(path)
64
+ with open(path) as f:
65
+ source = f.read()
66
+ return source, path, lambda: mtime == getmtime(path)
67
+ """
68
+
69
+ #: if set to `False` it indicates that the loader cannot provide access
70
+ #: to the source of templates.
71
+ #:
72
+ #: .. versionadded:: 2.4
73
+ has_source_access = True
74
+
75
+ def get_source(
76
+ self, environment: "Environment", template: str
77
+ ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
78
+ """Get the template source, filename and reload helper for a template.
79
+ It's passed the environment and template name and has to return a
80
+ tuple in the form ``(source, filename, uptodate)`` or raise a
81
+ `TemplateNotFound` error if it can't locate the template.
82
+
83
+ The source part of the returned tuple must be the source of the
84
+ template as a string. The filename should be the name of the
85
+ file on the filesystem if it was loaded from there, otherwise
86
+ ``None``. The filename is used by Python for the tracebacks
87
+ if no loader extension is used.
88
+
89
+ The last item in the tuple is the `uptodate` function. If auto
90
+ reloading is enabled it's always called to check if the template
91
+ changed. No arguments are passed so the function must store the
92
+ old state somewhere (for example in a closure). If it returns `False`
93
+ the template will be reloaded.
94
+ """
95
+ if not self.has_source_access:
96
+ raise RuntimeError(
97
+ f"{type(self).__name__} cannot provide access to the source"
98
+ )
99
+ raise TemplateNotFound(template)
100
+
101
+ def list_templates(self) -> t.List[str]:
102
+ """Iterates over all templates. If the loader does not support that
103
+ it should raise a :exc:`TypeError` which is the default behavior.
104
+ """
105
+ raise TypeError("this loader cannot iterate over all templates")
106
+
107
+ @internalcode
108
+ def load(
109
+ self,
110
+ environment: "Environment",
111
+ name: str,
112
+ globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
113
+ ) -> "Template":
114
+ """Loads a template. This method looks up the template in the cache
115
+ or loads one by calling :meth:`get_source`. Subclasses should not
116
+ override this method as loaders working on collections of other
117
+ loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
118
+ will not call this method but `get_source` directly.
119
+ """
120
+ code = None
121
+ if globals is None:
122
+ globals = {}
123
+
124
+ # first we try to get the source for this template together
125
+ # with the filename and the uptodate function.
126
+ source, filename, uptodate = self.get_source(environment, name)
127
+
128
+ # try to load the code from the bytecode cache if there is a
129
+ # bytecode cache configured.
130
+ bcc = environment.bytecode_cache
131
+ if bcc is not None:
132
+ bucket = bcc.get_bucket(environment, name, filename, source)
133
+ code = bucket.code
134
+
135
+ # if we don't have code so far (not cached, no longer up to
136
+ # date) etc. we compile the template
137
+ if code is None:
138
+ code = environment.compile(source, name, filename)
139
+
140
+ # if the bytecode cache is available and the bucket doesn't
141
+ # have a code so far, we give the bucket the new code and put
142
+ # it back to the bytecode cache.
143
+ if bcc is not None and bucket.code is None:
144
+ bucket.code = code
145
+ bcc.set_bucket(bucket)
146
+
147
+ return environment.template_class.from_code(
148
+ environment, code, globals, uptodate
149
+ )
150
+
151
+
152
+ class FileSystemLoader(BaseLoader):
153
+ """Load templates from a directory in the file system.
154
+
155
+ The path can be relative or absolute. Relative paths are relative to
156
+ the current working directory.
157
+
158
+ .. code-block:: python
159
+
160
+ loader = FileSystemLoader("templates")
161
+
162
+ A list of paths can be given. The directories will be searched in
163
+ order, stopping at the first matching template.
164
+
165
+ .. code-block:: python
166
+
167
+ loader = FileSystemLoader(["/override/templates", "/default/templates"])
168
+
169
+ :param searchpath: A path, or list of paths, to the directory that
170
+ contains the templates.
171
+ :param encoding: Use this encoding to read the text from template
172
+ files.
173
+ :param followlinks: Follow symbolic links in the path.
174
+
175
+ .. versionchanged:: 2.8
176
+ Added the ``followlinks`` parameter.
177
+ """
178
+
179
+ def __init__(
180
+ self,
181
+ searchpath: t.Union[
182
+ str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
183
+ ],
184
+ encoding: str = "utf-8",
185
+ followlinks: bool = False,
186
+ ) -> None:
187
+ if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str):
188
+ searchpath = [searchpath]
189
+
190
+ self.searchpath = [os.fspath(p) for p in searchpath]
191
+ self.encoding = encoding
192
+ self.followlinks = followlinks
193
+
194
+ def get_source(
195
+ self, environment: "Environment", template: str
196
+ ) -> t.Tuple[str, str, t.Callable[[], bool]]:
197
+ pieces = split_template_path(template)
198
+
199
+ for searchpath in self.searchpath:
200
+ # Use posixpath even on Windows to avoid "drive:" or UNC
201
+ # segments breaking out of the search directory.
202
+ filename = posixpath.join(searchpath, *pieces)
203
+
204
+ if os.path.isfile(filename):
205
+ break
206
+ else:
207
+ plural = "path" if len(self.searchpath) == 1 else "paths"
208
+ paths_str = ", ".join(repr(p) for p in self.searchpath)
209
+ raise TemplateNotFound(
210
+ template,
211
+ f"{template!r} not found in search {plural}: {paths_str}",
212
+ )
213
+
214
+ with open(filename, encoding=self.encoding) as f:
215
+ contents = f.read()
216
+
217
+ mtime = os.path.getmtime(filename)
218
+
219
+ def uptodate() -> bool:
220
+ try:
221
+ return os.path.getmtime(filename) == mtime
222
+ except OSError:
223
+ return False
224
+
225
+ # Use normpath to convert Windows altsep to sep.
226
+ return contents, os.path.normpath(filename), uptodate
227
+
228
+ def list_templates(self) -> t.List[str]:
229
+ found = set()
230
+ for searchpath in self.searchpath:
231
+ walk_dir = os.walk(searchpath, followlinks=self.followlinks)
232
+ for dirpath, _, filenames in walk_dir:
233
+ for filename in filenames:
234
+ template = (
235
+ os.path.join(dirpath, filename)[len(searchpath) :]
236
+ .strip(os.path.sep)
237
+ .replace(os.path.sep, "/")
238
+ )
239
+ if template[:2] == "./":
240
+ template = template[2:]
241
+ if template not in found:
242
+ found.add(template)
243
+ return sorted(found)
244
+
245
+
246
+ if sys.version_info >= (3, 13):
247
+
248
+ def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]:
249
+ try:
250
+ get_files = z._get_files
251
+ except AttributeError as e:
252
+ raise TypeError(
253
+ "This zip import does not have the required"
254
+ " metadata to list templates."
255
+ ) from e
256
+ return get_files()
257
+ else:
258
+
259
+ def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]:
260
+ try:
261
+ files = z._files
262
+ except AttributeError as e:
263
+ raise TypeError(
264
+ "This zip import does not have the required"
265
+ " metadata to list templates."
266
+ ) from e
267
+ return files # type: ignore[no-any-return]
268
+
269
+
270
+ class PackageLoader(BaseLoader):
271
+ """Load templates from a directory in a Python package.
272
+
273
+ :param package_name: Import name of the package that contains the
274
+ template directory.
275
+ :param package_path: Directory within the imported package that
276
+ contains the templates.
277
+ :param encoding: Encoding of template files.
278
+
279
+ The following example looks up templates in the ``pages`` directory
280
+ within the ``project.ui`` package.
281
+
282
+ .. code-block:: python
283
+
284
+ loader = PackageLoader("project.ui", "pages")
285
+
286
+ Only packages installed as directories (standard pip behavior) or
287
+ zip/egg files (less common) are supported. The Python API for
288
+ introspecting data in packages is too limited to support other
289
+ installation methods the way this loader requires.
290
+
291
+ There is limited support for :pep:`420` namespace packages. The
292
+ template directory is assumed to only be in one namespace
293
+ contributor. Zip files contributing to a namespace are not
294
+ supported.
295
+
296
+ .. versionchanged:: 3.0
297
+ No longer uses ``setuptools`` as a dependency.
298
+
299
+ .. versionchanged:: 3.0
300
+ Limited PEP 420 namespace package support.
301
+ """
302
+
303
+ def __init__(
304
+ self,
305
+ package_name: str,
306
+ package_path: "str" = "templates",
307
+ encoding: str = "utf-8",
308
+ ) -> None:
309
+ package_path = os.path.normpath(package_path).rstrip(os.path.sep)
310
+
311
+ # normpath preserves ".", which isn't valid in zip paths.
312
+ if package_path == os.path.curdir:
313
+ package_path = ""
314
+ elif package_path[:2] == os.path.curdir + os.path.sep:
315
+ package_path = package_path[2:]
316
+
317
+ self.package_path = package_path
318
+ self.package_name = package_name
319
+ self.encoding = encoding
320
+
321
+ # Make sure the package exists. This also makes namespace
322
+ # packages work, otherwise get_loader returns None.
323
+ import_module(package_name)
324
+ spec = importlib.util.find_spec(package_name)
325
+ assert spec is not None, "An import spec was not found for the package."
326
+ loader = spec.loader
327
+ assert loader is not None, "A loader was not found for the package."
328
+ self._loader = loader
329
+ self._archive = None
330
+
331
+ if isinstance(loader, zipimport.zipimporter):
332
+ self._archive = loader.archive
333
+ pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore
334
+ template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
335
+ else:
336
+ roots: t.List[str] = []
337
+
338
+ # One element for regular packages, multiple for namespace
339
+ # packages, or None for single module file.
340
+ if spec.submodule_search_locations:
341
+ roots.extend(spec.submodule_search_locations)
342
+ # A single module file, use the parent directory instead.
343
+ elif spec.origin is not None:
344
+ roots.append(os.path.dirname(spec.origin))
345
+
346
+ if not roots:
347
+ raise ValueError(
348
+ f"The {package_name!r} package was not installed in a"
349
+ " way that PackageLoader understands."
350
+ )
351
+
352
+ for root in roots:
353
+ root = os.path.join(root, package_path)
354
+
355
+ if os.path.isdir(root):
356
+ template_root = root
357
+ break
358
+ else:
359
+ raise ValueError(
360
+ f"PackageLoader could not find a {package_path!r} directory"
361
+ f" in the {package_name!r} package."
362
+ )
363
+
364
+ self._template_root = template_root
365
+
366
+ def get_source(
367
+ self, environment: "Environment", template: str
368
+ ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
369
+ # Use posixpath even on Windows to avoid "drive:" or UNC
370
+ # segments breaking out of the search directory. Use normpath to
371
+ # convert Windows altsep to sep.
372
+ p = os.path.normpath(
373
+ posixpath.join(self._template_root, *split_template_path(template))
374
+ )
375
+ up_to_date: t.Optional[t.Callable[[], bool]]
376
+
377
+ if self._archive is None:
378
+ # Package is a directory.
379
+ if not os.path.isfile(p):
380
+ raise TemplateNotFound(template)
381
+
382
+ with open(p, "rb") as f:
383
+ source = f.read()
384
+
385
+ mtime = os.path.getmtime(p)
386
+
387
+ def up_to_date() -> bool:
388
+ return os.path.isfile(p) and os.path.getmtime(p) == mtime
389
+
390
+ else:
391
+ # Package is a zip file.
392
+ try:
393
+ source = self._loader.get_data(p) # type: ignore
394
+ except OSError as e:
395
+ raise TemplateNotFound(template) from e
396
+
397
+ # Could use the zip's mtime for all template mtimes, but
398
+ # would need to safely reload the module if it's out of
399
+ # date, so just report it as always current.
400
+ up_to_date = None
401
+
402
+ return source.decode(self.encoding), p, up_to_date
403
+
404
+ def list_templates(self) -> t.List[str]:
405
+ results: t.List[str] = []
406
+
407
+ if self._archive is None:
408
+ # Package is a directory.
409
+ offset = len(self._template_root)
410
+
411
+ for dirpath, _, filenames in os.walk(self._template_root):
412
+ dirpath = dirpath[offset:].lstrip(os.path.sep)
413
+ results.extend(
414
+ os.path.join(dirpath, name).replace(os.path.sep, "/")
415
+ for name in filenames
416
+ )
417
+ else:
418
+ files = _get_zipimporter_files(self._loader)
419
+
420
+ # Package is a zip file.
421
+ prefix = (
422
+ self._template_root[len(self._archive) :].lstrip(os.path.sep)
423
+ + os.path.sep
424
+ )
425
+ offset = len(prefix)
426
+
427
+ for name in files:
428
+ # Find names under the templates directory that aren't directories.
429
+ if name.startswith(prefix) and name[-1] != os.path.sep:
430
+ results.append(name[offset:].replace(os.path.sep, "/"))
431
+
432
+ results.sort()
433
+ return results
434
+
435
+
436
+ class DictLoader(BaseLoader):
437
+ """Loads a template from a Python dict mapping template names to
438
+ template source. This loader is useful for unittesting:
439
+
440
+ >>> loader = DictLoader({'index.html': 'source here'})
441
+
442
+ Because auto reloading is rarely useful this is disabled by default.
443
+ """
444
+
445
+ def __init__(self, mapping: t.Mapping[str, str]) -> None:
446
+ self.mapping = mapping
447
+
448
+ def get_source(
449
+ self, environment: "Environment", template: str
450
+ ) -> t.Tuple[str, None, t.Callable[[], bool]]:
451
+ if template in self.mapping:
452
+ source = self.mapping[template]
453
+ return source, None, lambda: source == self.mapping.get(template)
454
+ raise TemplateNotFound(template)
455
+
456
+ def list_templates(self) -> t.List[str]:
457
+ return sorted(self.mapping)
458
+
459
+
460
+ class FunctionLoader(BaseLoader):
461
+ """A loader that is passed a function which does the loading. The
462
+ function receives the name of the template and has to return either
463
+ a string with the template source, a tuple in the form ``(source,
464
+ filename, uptodatefunc)`` or `None` if the template does not exist.
465
+
466
+ >>> def load_template(name):
467
+ ... if name == 'index.html':
468
+ ... return '...'
469
+ ...
470
+ >>> loader = FunctionLoader(load_template)
471
+
472
+ The `uptodatefunc` is a function that is called if autoreload is enabled
473
+ and has to return `True` if the template is still up to date. For more
474
+ details have a look at :meth:`BaseLoader.get_source` which has the same
475
+ return value.
476
+ """
477
+
478
+ def __init__(
479
+ self,
480
+ load_func: t.Callable[
481
+ [str],
482
+ t.Optional[
483
+ t.Union[
484
+ str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
485
+ ]
486
+ ],
487
+ ],
488
+ ) -> None:
489
+ self.load_func = load_func
490
+
491
+ def get_source(
492
+ self, environment: "Environment", template: str
493
+ ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
494
+ rv = self.load_func(template)
495
+
496
+ if rv is None:
497
+ raise TemplateNotFound(template)
498
+
499
+ if isinstance(rv, str):
500
+ return rv, None, None
501
+
502
+ return rv
503
+
504
+
505
+ class PrefixLoader(BaseLoader):
506
+ """A loader that is passed a dict of loaders where each loader is bound
507
+ to a prefix. The prefix is delimited from the template by a slash per
508
+ default, which can be changed by setting the `delimiter` argument to
509
+ something else::
510
+
511
+ loader = PrefixLoader({
512
+ 'app1': PackageLoader('mypackage.app1'),
513
+ 'app2': PackageLoader('mypackage.app2')
514
+ })
515
+
516
+ By loading ``'app1/index.html'`` the file from the app1 package is loaded,
517
+ by loading ``'app2/index.html'`` the file from the second.
518
+ """
519
+
520
+ def __init__(
521
+ self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
522
+ ) -> None:
523
+ self.mapping = mapping
524
+ self.delimiter = delimiter
525
+
526
+ def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
527
+ try:
528
+ prefix, name = template.split(self.delimiter, 1)
529
+ loader = self.mapping[prefix]
530
+ except (ValueError, KeyError) as e:
531
+ raise TemplateNotFound(template) from e
532
+ return loader, name
533
+
534
+ def get_source(
535
+ self, environment: "Environment", template: str
536
+ ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
537
+ loader, name = self.get_loader(template)
538
+ try:
539
+ return loader.get_source(environment, name)
540
+ except TemplateNotFound as e:
541
+ # re-raise the exception with the correct filename here.
542
+ # (the one that includes the prefix)
543
+ raise TemplateNotFound(template) from e
544
+
545
+ @internalcode
546
+ def load(
547
+ self,
548
+ environment: "Environment",
549
+ name: str,
550
+ globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
551
+ ) -> "Template":
552
+ loader, local_name = self.get_loader(name)
553
+ try:
554
+ return loader.load(environment, local_name, globals)
555
+ except TemplateNotFound as e:
556
+ # re-raise the exception with the correct filename here.
557
+ # (the one that includes the prefix)
558
+ raise TemplateNotFound(name) from e
559
+
560
+ def list_templates(self) -> t.List[str]:
561
+ result = []
562
+ for prefix, loader in self.mapping.items():
563
+ for template in loader.list_templates():
564
+ result.append(prefix + self.delimiter + template)
565
+ return result
566
+
567
+
568
+ class ChoiceLoader(BaseLoader):
569
+ """This loader works like the `PrefixLoader` just that no prefix is
570
+ specified. If a template could not be found by one loader the next one
571
+ is tried.
572
+
573
+ >>> loader = ChoiceLoader([
574
+ ... FileSystemLoader('/path/to/user/templates'),
575
+ ... FileSystemLoader('/path/to/system/templates')
576
+ ... ])
577
+
578
+ This is useful if you want to allow users to override builtin templates
579
+ from a different location.
580
+ """
581
+
582
+ def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
583
+ self.loaders = loaders
584
+
585
+ def get_source(
586
+ self, environment: "Environment", template: str
587
+ ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
588
+ for loader in self.loaders:
589
+ try:
590
+ return loader.get_source(environment, template)
591
+ except TemplateNotFound:
592
+ pass
593
+ raise TemplateNotFound(template)
594
+
595
+ @internalcode
596
+ def load(
597
+ self,
598
+ environment: "Environment",
599
+ name: str,
600
+ globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
601
+ ) -> "Template":
602
+ for loader in self.loaders:
603
+ try:
604
+ return loader.load(environment, name, globals)
605
+ except TemplateNotFound:
606
+ pass
607
+ raise TemplateNotFound(name)
608
+
609
+ def list_templates(self) -> t.List[str]:
610
+ found = set()
611
+ for loader in self.loaders:
612
+ found.update(loader.list_templates())
613
+ return sorted(found)
614
+
615
+
616
+ class _TemplateModule(ModuleType):
617
+ """Like a normal module but with support for weak references"""
618
+
619
+
620
+ class ModuleLoader(BaseLoader):
621
+ """This loader loads templates from precompiled templates.
622
+
623
+ Example usage:
624
+
625
+ >>> loader = ModuleLoader('/path/to/compiled/templates')
626
+
627
+ Templates can be precompiled with :meth:`Environment.compile_templates`.
628
+ """
629
+
630
+ has_source_access = False
631
+
632
+ def __init__(
633
+ self,
634
+ path: t.Union[
635
+ str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
636
+ ],
637
+ ) -> None:
638
+ package_name = f"_jinja2_module_templates_{id(self):x}"
639
+
640
+ # create a fake module that looks for the templates in the
641
+ # path given.
642
+ mod = _TemplateModule(package_name)
643
+
644
+ if not isinstance(path, abc.Iterable) or isinstance(path, str):
645
+ path = [path]
646
+
647
+ mod.__path__ = [os.fspath(p) for p in path]
648
+
649
+ sys.modules[package_name] = weakref.proxy(
650
+ mod, lambda x: sys.modules.pop(package_name, None)
651
+ )
652
+
653
+ # the only strong reference, the sys.modules entry is weak
654
+ # so that the garbage collector can remove it once the
655
+ # loader that created it goes out of business.
656
+ self.module = mod
657
+ self.package_name = package_name
658
+
659
+ @staticmethod
660
+ def get_template_key(name: str) -> str:
661
+ return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()
662
+
663
+ @staticmethod
664
+ def get_module_filename(name: str) -> str:
665
+ return ModuleLoader.get_template_key(name) + ".py"
666
+
667
+ @internalcode
668
+ def load(
669
+ self,
670
+ environment: "Environment",
671
+ name: str,
672
+ globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
673
+ ) -> "Template":
674
+ key = self.get_template_key(name)
675
+ module = f"{self.package_name}.{key}"
676
+ mod = getattr(self.module, module, None)
677
+
678
+ if mod is None:
679
+ try:
680
+ mod = __import__(module, None, None, ["root"])
681
+ except ImportError as e:
682
+ raise TemplateNotFound(name) from e
683
+
684
+ # remove the entry from sys.modules, we only want the attribute
685
+ # on the module object we have stored on the loader.
686
+ sys.modules.pop(module, None)
687
+
688
+ if globals is None:
689
+ globals = {}
690
+
691
+ return environment.template_class.from_module_dict(
692
+ environment, mod.__dict__, globals
693
+ )
.venv/lib/python3.11/site-packages/jinja2/optimizer.py ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The optimizer tries to constant fold expressions and modify the AST
2
+ in place so that it should be faster to evaluate.
3
+
4
+ Because the AST does not contain all the scoping information and the
5
+ compiler has to find that out, we cannot do all the optimizations we
6
+ want. For example, loop unrolling doesn't work because unrolled loops
7
+ would have a different scope. The solution would be a second syntax tree
8
+ that stored the scoping rules.
9
+ """
10
+
11
+ import typing as t
12
+
13
+ from . import nodes
14
+ from .visitor import NodeTransformer
15
+
16
+ if t.TYPE_CHECKING:
17
+ from .environment import Environment
18
+
19
+
20
+ def optimize(node: nodes.Node, environment: "Environment") -> nodes.Node:
21
+ """The context hint can be used to perform an static optimization
22
+ based on the context given."""
23
+ optimizer = Optimizer(environment)
24
+ return t.cast(nodes.Node, optimizer.visit(node))
25
+
26
+
27
+ class Optimizer(NodeTransformer):
28
+ def __init__(self, environment: "t.Optional[Environment]") -> None:
29
+ self.environment = environment
30
+
31
+ def generic_visit(
32
+ self, node: nodes.Node, *args: t.Any, **kwargs: t.Any
33
+ ) -> nodes.Node:
34
+ node = super().generic_visit(node, *args, **kwargs)
35
+
36
+ # Do constant folding. Some other nodes besides Expr have
37
+ # as_const, but folding them causes errors later on.
38
+ if isinstance(node, nodes.Expr):
39
+ try:
40
+ return nodes.Const.from_untrusted(
41
+ node.as_const(args[0] if args else None),
42
+ lineno=node.lineno,
43
+ environment=self.environment,
44
+ )
45
+ except nodes.Impossible:
46
+ pass
47
+
48
+ return node
.venv/lib/python3.11/site-packages/jinja2/parser.py ADDED
@@ -0,0 +1,1049 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Parse tokens from the lexer into nodes for the compiler."""
2
+
3
+ import typing
4
+ import typing as t
5
+
6
+ from . import nodes
7
+ from .exceptions import TemplateAssertionError
8
+ from .exceptions import TemplateSyntaxError
9
+ from .lexer import describe_token
10
+ from .lexer import describe_token_expr
11
+
12
+ if t.TYPE_CHECKING:
13
+ import typing_extensions as te
14
+
15
+ from .environment import Environment
16
+
17
+ _ImportInclude = t.TypeVar("_ImportInclude", nodes.Import, nodes.Include)
18
+ _MacroCall = t.TypeVar("_MacroCall", nodes.Macro, nodes.CallBlock)
19
+
20
+ _statement_keywords = frozenset(
21
+ [
22
+ "for",
23
+ "if",
24
+ "block",
25
+ "extends",
26
+ "print",
27
+ "macro",
28
+ "include",
29
+ "from",
30
+ "import",
31
+ "set",
32
+ "with",
33
+ "autoescape",
34
+ ]
35
+ )
36
+ _compare_operators = frozenset(["eq", "ne", "lt", "lteq", "gt", "gteq"])
37
+
38
+ _math_nodes: t.Dict[str, t.Type[nodes.Expr]] = {
39
+ "add": nodes.Add,
40
+ "sub": nodes.Sub,
41
+ "mul": nodes.Mul,
42
+ "div": nodes.Div,
43
+ "floordiv": nodes.FloorDiv,
44
+ "mod": nodes.Mod,
45
+ }
46
+
47
+
48
+ class Parser:
49
+ """This is the central parsing class Jinja uses. It's passed to
50
+ extensions and can be used to parse expressions or statements.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ environment: "Environment",
56
+ source: str,
57
+ name: t.Optional[str] = None,
58
+ filename: t.Optional[str] = None,
59
+ state: t.Optional[str] = None,
60
+ ) -> None:
61
+ self.environment = environment
62
+ self.stream = environment._tokenize(source, name, filename, state)
63
+ self.name = name
64
+ self.filename = filename
65
+ self.closed = False
66
+ self.extensions: t.Dict[
67
+ str, t.Callable[[Parser], t.Union[nodes.Node, t.List[nodes.Node]]]
68
+ ] = {}
69
+ for extension in environment.iter_extensions():
70
+ for tag in extension.tags:
71
+ self.extensions[tag] = extension.parse
72
+ self._last_identifier = 0
73
+ self._tag_stack: t.List[str] = []
74
+ self._end_token_stack: t.List[t.Tuple[str, ...]] = []
75
+
76
+ def fail(
77
+ self,
78
+ msg: str,
79
+ lineno: t.Optional[int] = None,
80
+ exc: t.Type[TemplateSyntaxError] = TemplateSyntaxError,
81
+ ) -> "te.NoReturn":
82
+ """Convenience method that raises `exc` with the message, passed
83
+ line number or last line number as well as the current name and
84
+ filename.
85
+ """
86
+ if lineno is None:
87
+ lineno = self.stream.current.lineno
88
+ raise exc(msg, lineno, self.name, self.filename)
89
+
90
+ def _fail_ut_eof(
91
+ self,
92
+ name: t.Optional[str],
93
+ end_token_stack: t.List[t.Tuple[str, ...]],
94
+ lineno: t.Optional[int],
95
+ ) -> "te.NoReturn":
96
+ expected: t.Set[str] = set()
97
+ for exprs in end_token_stack:
98
+ expected.update(map(describe_token_expr, exprs))
99
+ if end_token_stack:
100
+ currently_looking: t.Optional[str] = " or ".join(
101
+ map(repr, map(describe_token_expr, end_token_stack[-1]))
102
+ )
103
+ else:
104
+ currently_looking = None
105
+
106
+ if name is None:
107
+ message = ["Unexpected end of template."]
108
+ else:
109
+ message = [f"Encountered unknown tag {name!r}."]
110
+
111
+ if currently_looking:
112
+ if name is not None and name in expected:
113
+ message.append(
114
+ "You probably made a nesting mistake. Jinja is expecting this tag,"
115
+ f" but currently looking for {currently_looking}."
116
+ )
117
+ else:
118
+ message.append(
119
+ f"Jinja was looking for the following tags: {currently_looking}."
120
+ )
121
+
122
+ if self._tag_stack:
123
+ message.append(
124
+ "The innermost block that needs to be closed is"
125
+ f" {self._tag_stack[-1]!r}."
126
+ )
127
+
128
+ self.fail(" ".join(message), lineno)
129
+
130
+ def fail_unknown_tag(
131
+ self, name: str, lineno: t.Optional[int] = None
132
+ ) -> "te.NoReturn":
133
+ """Called if the parser encounters an unknown tag. Tries to fail
134
+ with a human readable error message that could help to identify
135
+ the problem.
136
+ """
137
+ self._fail_ut_eof(name, self._end_token_stack, lineno)
138
+
139
+ def fail_eof(
140
+ self,
141
+ end_tokens: t.Optional[t.Tuple[str, ...]] = None,
142
+ lineno: t.Optional[int] = None,
143
+ ) -> "te.NoReturn":
144
+ """Like fail_unknown_tag but for end of template situations."""
145
+ stack = list(self._end_token_stack)
146
+ if end_tokens is not None:
147
+ stack.append(end_tokens)
148
+ self._fail_ut_eof(None, stack, lineno)
149
+
150
+ def is_tuple_end(
151
+ self, extra_end_rules: t.Optional[t.Tuple[str, ...]] = None
152
+ ) -> bool:
153
+ """Are we at the end of a tuple?"""
154
+ if self.stream.current.type in ("variable_end", "block_end", "rparen"):
155
+ return True
156
+ elif extra_end_rules is not None:
157
+ return self.stream.current.test_any(extra_end_rules) # type: ignore
158
+ return False
159
+
160
+ def free_identifier(self, lineno: t.Optional[int] = None) -> nodes.InternalName:
161
+ """Return a new free identifier as :class:`~jinja2.nodes.InternalName`."""
162
+ self._last_identifier += 1
163
+ rv = object.__new__(nodes.InternalName)
164
+ nodes.Node.__init__(rv, f"fi{self._last_identifier}", lineno=lineno)
165
+ return rv
166
+
167
+ def parse_statement(self) -> t.Union[nodes.Node, t.List[nodes.Node]]:
168
+ """Parse a single statement."""
169
+ token = self.stream.current
170
+ if token.type != "name":
171
+ self.fail("tag name expected", token.lineno)
172
+ self._tag_stack.append(token.value)
173
+ pop_tag = True
174
+ try:
175
+ if token.value in _statement_keywords:
176
+ f = getattr(self, f"parse_{self.stream.current.value}")
177
+ return f() # type: ignore
178
+ if token.value == "call":
179
+ return self.parse_call_block()
180
+ if token.value == "filter":
181
+ return self.parse_filter_block()
182
+ ext = self.extensions.get(token.value)
183
+ if ext is not None:
184
+ return ext(self)
185
+
186
+ # did not work out, remove the token we pushed by accident
187
+ # from the stack so that the unknown tag fail function can
188
+ # produce a proper error message.
189
+ self._tag_stack.pop()
190
+ pop_tag = False
191
+ self.fail_unknown_tag(token.value, token.lineno)
192
+ finally:
193
+ if pop_tag:
194
+ self._tag_stack.pop()
195
+
196
+ def parse_statements(
197
+ self, end_tokens: t.Tuple[str, ...], drop_needle: bool = False
198
+ ) -> t.List[nodes.Node]:
199
+ """Parse multiple statements into a list until one of the end tokens
200
+ is reached. This is used to parse the body of statements as it also
201
+ parses template data if appropriate. The parser checks first if the
202
+ current token is a colon and skips it if there is one. Then it checks
203
+ for the block end and parses until if one of the `end_tokens` is
204
+ reached. Per default the active token in the stream at the end of
205
+ the call is the matched end token. If this is not wanted `drop_needle`
206
+ can be set to `True` and the end token is removed.
207
+ """
208
+ # the first token may be a colon for python compatibility
209
+ self.stream.skip_if("colon")
210
+
211
+ # in the future it would be possible to add whole code sections
212
+ # by adding some sort of end of statement token and parsing those here.
213
+ self.stream.expect("block_end")
214
+ result = self.subparse(end_tokens)
215
+
216
+ # we reached the end of the template too early, the subparser
217
+ # does not check for this, so we do that now
218
+ if self.stream.current.type == "eof":
219
+ self.fail_eof(end_tokens)
220
+
221
+ if drop_needle:
222
+ next(self.stream)
223
+ return result
224
+
225
+ def parse_set(self) -> t.Union[nodes.Assign, nodes.AssignBlock]:
226
+ """Parse an assign statement."""
227
+ lineno = next(self.stream).lineno
228
+ target = self.parse_assign_target(with_namespace=True)
229
+ if self.stream.skip_if("assign"):
230
+ expr = self.parse_tuple()
231
+ return nodes.Assign(target, expr, lineno=lineno)
232
+ filter_node = self.parse_filter(None)
233
+ body = self.parse_statements(("name:endset",), drop_needle=True)
234
+ return nodes.AssignBlock(target, filter_node, body, lineno=lineno)
235
+
236
+ def parse_for(self) -> nodes.For:
237
+ """Parse a for loop."""
238
+ lineno = self.stream.expect("name:for").lineno
239
+ target = self.parse_assign_target(extra_end_rules=("name:in",))
240
+ self.stream.expect("name:in")
241
+ iter = self.parse_tuple(
242
+ with_condexpr=False, extra_end_rules=("name:recursive",)
243
+ )
244
+ test = None
245
+ if self.stream.skip_if("name:if"):
246
+ test = self.parse_expression()
247
+ recursive = self.stream.skip_if("name:recursive")
248
+ body = self.parse_statements(("name:endfor", "name:else"))
249
+ if next(self.stream).value == "endfor":
250
+ else_ = []
251
+ else:
252
+ else_ = self.parse_statements(("name:endfor",), drop_needle=True)
253
+ return nodes.For(target, iter, body, else_, test, recursive, lineno=lineno)
254
+
255
+ def parse_if(self) -> nodes.If:
256
+ """Parse an if construct."""
257
+ node = result = nodes.If(lineno=self.stream.expect("name:if").lineno)
258
+ while True:
259
+ node.test = self.parse_tuple(with_condexpr=False)
260
+ node.body = self.parse_statements(("name:elif", "name:else", "name:endif"))
261
+ node.elif_ = []
262
+ node.else_ = []
263
+ token = next(self.stream)
264
+ if token.test("name:elif"):
265
+ node = nodes.If(lineno=self.stream.current.lineno)
266
+ result.elif_.append(node)
267
+ continue
268
+ elif token.test("name:else"):
269
+ result.else_ = self.parse_statements(("name:endif",), drop_needle=True)
270
+ break
271
+ return result
272
+
273
+ def parse_with(self) -> nodes.With:
274
+ node = nodes.With(lineno=next(self.stream).lineno)
275
+ targets: t.List[nodes.Expr] = []
276
+ values: t.List[nodes.Expr] = []
277
+ while self.stream.current.type != "block_end":
278
+ if targets:
279
+ self.stream.expect("comma")
280
+ target = self.parse_assign_target()
281
+ target.set_ctx("param")
282
+ targets.append(target)
283
+ self.stream.expect("assign")
284
+ values.append(self.parse_expression())
285
+ node.targets = targets
286
+ node.values = values
287
+ node.body = self.parse_statements(("name:endwith",), drop_needle=True)
288
+ return node
289
+
290
+ def parse_autoescape(self) -> nodes.Scope:
291
+ node = nodes.ScopedEvalContextModifier(lineno=next(self.stream).lineno)
292
+ node.options = [nodes.Keyword("autoescape", self.parse_expression())]
293
+ node.body = self.parse_statements(("name:endautoescape",), drop_needle=True)
294
+ return nodes.Scope([node])
295
+
296
+ def parse_block(self) -> nodes.Block:
297
+ node = nodes.Block(lineno=next(self.stream).lineno)
298
+ node.name = self.stream.expect("name").value
299
+ node.scoped = self.stream.skip_if("name:scoped")
300
+ node.required = self.stream.skip_if("name:required")
301
+
302
+ # common problem people encounter when switching from django
303
+ # to jinja. we do not support hyphens in block names, so let's
304
+ # raise a nicer error message in that case.
305
+ if self.stream.current.type == "sub":
306
+ self.fail(
307
+ "Block names in Jinja have to be valid Python identifiers and may not"
308
+ " contain hyphens, use an underscore instead."
309
+ )
310
+
311
+ node.body = self.parse_statements(("name:endblock",), drop_needle=True)
312
+
313
+ # enforce that required blocks only contain whitespace or comments
314
+ # by asserting that the body, if not empty, is just TemplateData nodes
315
+ # with whitespace data
316
+ if node.required:
317
+ for body_node in node.body:
318
+ if not isinstance(body_node, nodes.Output) or any(
319
+ not isinstance(output_node, nodes.TemplateData)
320
+ or not output_node.data.isspace()
321
+ for output_node in body_node.nodes
322
+ ):
323
+ self.fail("Required blocks can only contain comments or whitespace")
324
+
325
+ self.stream.skip_if("name:" + node.name)
326
+ return node
327
+
328
+ def parse_extends(self) -> nodes.Extends:
329
+ node = nodes.Extends(lineno=next(self.stream).lineno)
330
+ node.template = self.parse_expression()
331
+ return node
332
+
333
+ def parse_import_context(
334
+ self, node: _ImportInclude, default: bool
335
+ ) -> _ImportInclude:
336
+ if self.stream.current.test_any(
337
+ "name:with", "name:without"
338
+ ) and self.stream.look().test("name:context"):
339
+ node.with_context = next(self.stream).value == "with"
340
+ self.stream.skip()
341
+ else:
342
+ node.with_context = default
343
+ return node
344
+
345
+ def parse_include(self) -> nodes.Include:
346
+ node = nodes.Include(lineno=next(self.stream).lineno)
347
+ node.template = self.parse_expression()
348
+ if self.stream.current.test("name:ignore") and self.stream.look().test(
349
+ "name:missing"
350
+ ):
351
+ node.ignore_missing = True
352
+ self.stream.skip(2)
353
+ else:
354
+ node.ignore_missing = False
355
+ return self.parse_import_context(node, True)
356
+
357
+ def parse_import(self) -> nodes.Import:
358
+ node = nodes.Import(lineno=next(self.stream).lineno)
359
+ node.template = self.parse_expression()
360
+ self.stream.expect("name:as")
361
+ node.target = self.parse_assign_target(name_only=True).name
362
+ return self.parse_import_context(node, False)
363
+
364
+ def parse_from(self) -> nodes.FromImport:
365
+ node = nodes.FromImport(lineno=next(self.stream).lineno)
366
+ node.template = self.parse_expression()
367
+ self.stream.expect("name:import")
368
+ node.names = []
369
+
370
+ def parse_context() -> bool:
371
+ if self.stream.current.value in {
372
+ "with",
373
+ "without",
374
+ } and self.stream.look().test("name:context"):
375
+ node.with_context = next(self.stream).value == "with"
376
+ self.stream.skip()
377
+ return True
378
+ return False
379
+
380
+ while True:
381
+ if node.names:
382
+ self.stream.expect("comma")
383
+ if self.stream.current.type == "name":
384
+ if parse_context():
385
+ break
386
+ target = self.parse_assign_target(name_only=True)
387
+ if target.name.startswith("_"):
388
+ self.fail(
389
+ "names starting with an underline can not be imported",
390
+ target.lineno,
391
+ exc=TemplateAssertionError,
392
+ )
393
+ if self.stream.skip_if("name:as"):
394
+ alias = self.parse_assign_target(name_only=True)
395
+ node.names.append((target.name, alias.name))
396
+ else:
397
+ node.names.append(target.name)
398
+ if parse_context() or self.stream.current.type != "comma":
399
+ break
400
+ else:
401
+ self.stream.expect("name")
402
+ if not hasattr(node, "with_context"):
403
+ node.with_context = False
404
+ return node
405
+
406
+ def parse_signature(self, node: _MacroCall) -> None:
407
+ args = node.args = []
408
+ defaults = node.defaults = []
409
+ self.stream.expect("lparen")
410
+ while self.stream.current.type != "rparen":
411
+ if args:
412
+ self.stream.expect("comma")
413
+ arg = self.parse_assign_target(name_only=True)
414
+ arg.set_ctx("param")
415
+ if self.stream.skip_if("assign"):
416
+ defaults.append(self.parse_expression())
417
+ elif defaults:
418
+ self.fail("non-default argument follows default argument")
419
+ args.append(arg)
420
+ self.stream.expect("rparen")
421
+
422
+ def parse_call_block(self) -> nodes.CallBlock:
423
+ node = nodes.CallBlock(lineno=next(self.stream).lineno)
424
+ if self.stream.current.type == "lparen":
425
+ self.parse_signature(node)
426
+ else:
427
+ node.args = []
428
+ node.defaults = []
429
+
430
+ call_node = self.parse_expression()
431
+ if not isinstance(call_node, nodes.Call):
432
+ self.fail("expected call", node.lineno)
433
+ node.call = call_node
434
+ node.body = self.parse_statements(("name:endcall",), drop_needle=True)
435
+ return node
436
+
437
+ def parse_filter_block(self) -> nodes.FilterBlock:
438
+ node = nodes.FilterBlock(lineno=next(self.stream).lineno)
439
+ node.filter = self.parse_filter(None, start_inline=True) # type: ignore
440
+ node.body = self.parse_statements(("name:endfilter",), drop_needle=True)
441
+ return node
442
+
443
+ def parse_macro(self) -> nodes.Macro:
444
+ node = nodes.Macro(lineno=next(self.stream).lineno)
445
+ node.name = self.parse_assign_target(name_only=True).name
446
+ self.parse_signature(node)
447
+ node.body = self.parse_statements(("name:endmacro",), drop_needle=True)
448
+ return node
449
+
450
+ def parse_print(self) -> nodes.Output:
451
+ node = nodes.Output(lineno=next(self.stream).lineno)
452
+ node.nodes = []
453
+ while self.stream.current.type != "block_end":
454
+ if node.nodes:
455
+ self.stream.expect("comma")
456
+ node.nodes.append(self.parse_expression())
457
+ return node
458
+
459
+ @typing.overload
460
+ def parse_assign_target(
461
+ self, with_tuple: bool = ..., name_only: "te.Literal[True]" = ...
462
+ ) -> nodes.Name: ...
463
+
464
+ @typing.overload
465
+ def parse_assign_target(
466
+ self,
467
+ with_tuple: bool = True,
468
+ name_only: bool = False,
469
+ extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
470
+ with_namespace: bool = False,
471
+ ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]: ...
472
+
473
+ def parse_assign_target(
474
+ self,
475
+ with_tuple: bool = True,
476
+ name_only: bool = False,
477
+ extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
478
+ with_namespace: bool = False,
479
+ ) -> t.Union[nodes.NSRef, nodes.Name, nodes.Tuple]:
480
+ """Parse an assignment target. As Jinja allows assignments to
481
+ tuples, this function can parse all allowed assignment targets. Per
482
+ default assignments to tuples are parsed, that can be disable however
483
+ by setting `with_tuple` to `False`. If only assignments to names are
484
+ wanted `name_only` can be set to `True`. The `extra_end_rules`
485
+ parameter is forwarded to the tuple parsing function. If
486
+ `with_namespace` is enabled, a namespace assignment may be parsed.
487
+ """
488
+ target: nodes.Expr
489
+
490
+ if name_only:
491
+ token = self.stream.expect("name")
492
+ target = nodes.Name(token.value, "store", lineno=token.lineno)
493
+ else:
494
+ if with_tuple:
495
+ target = self.parse_tuple(
496
+ simplified=True,
497
+ extra_end_rules=extra_end_rules,
498
+ with_namespace=with_namespace,
499
+ )
500
+ else:
501
+ target = self.parse_primary(with_namespace=with_namespace)
502
+
503
+ target.set_ctx("store")
504
+
505
+ if not target.can_assign():
506
+ self.fail(
507
+ f"can't assign to {type(target).__name__.lower()!r}", target.lineno
508
+ )
509
+
510
+ return target # type: ignore
511
+
512
+ def parse_expression(self, with_condexpr: bool = True) -> nodes.Expr:
513
+ """Parse an expression. Per default all expressions are parsed, if
514
+ the optional `with_condexpr` parameter is set to `False` conditional
515
+ expressions are not parsed.
516
+ """
517
+ if with_condexpr:
518
+ return self.parse_condexpr()
519
+ return self.parse_or()
520
+
521
+ def parse_condexpr(self) -> nodes.Expr:
522
+ lineno = self.stream.current.lineno
523
+ expr1 = self.parse_or()
524
+ expr3: t.Optional[nodes.Expr]
525
+
526
+ while self.stream.skip_if("name:if"):
527
+ expr2 = self.parse_or()
528
+ if self.stream.skip_if("name:else"):
529
+ expr3 = self.parse_condexpr()
530
+ else:
531
+ expr3 = None
532
+ expr1 = nodes.CondExpr(expr2, expr1, expr3, lineno=lineno)
533
+ lineno = self.stream.current.lineno
534
+ return expr1
535
+
536
+ def parse_or(self) -> nodes.Expr:
537
+ lineno = self.stream.current.lineno
538
+ left = self.parse_and()
539
+ while self.stream.skip_if("name:or"):
540
+ right = self.parse_and()
541
+ left = nodes.Or(left, right, lineno=lineno)
542
+ lineno = self.stream.current.lineno
543
+ return left
544
+
545
+ def parse_and(self) -> nodes.Expr:
546
+ lineno = self.stream.current.lineno
547
+ left = self.parse_not()
548
+ while self.stream.skip_if("name:and"):
549
+ right = self.parse_not()
550
+ left = nodes.And(left, right, lineno=lineno)
551
+ lineno = self.stream.current.lineno
552
+ return left
553
+
554
+ def parse_not(self) -> nodes.Expr:
555
+ if self.stream.current.test("name:not"):
556
+ lineno = next(self.stream).lineno
557
+ return nodes.Not(self.parse_not(), lineno=lineno)
558
+ return self.parse_compare()
559
+
560
+ def parse_compare(self) -> nodes.Expr:
561
+ lineno = self.stream.current.lineno
562
+ expr = self.parse_math1()
563
+ ops = []
564
+ while True:
565
+ token_type = self.stream.current.type
566
+ if token_type in _compare_operators:
567
+ next(self.stream)
568
+ ops.append(nodes.Operand(token_type, self.parse_math1()))
569
+ elif self.stream.skip_if("name:in"):
570
+ ops.append(nodes.Operand("in", self.parse_math1()))
571
+ elif self.stream.current.test("name:not") and self.stream.look().test(
572
+ "name:in"
573
+ ):
574
+ self.stream.skip(2)
575
+ ops.append(nodes.Operand("notin", self.parse_math1()))
576
+ else:
577
+ break
578
+ lineno = self.stream.current.lineno
579
+ if not ops:
580
+ return expr
581
+ return nodes.Compare(expr, ops, lineno=lineno)
582
+
583
+ def parse_math1(self) -> nodes.Expr:
584
+ lineno = self.stream.current.lineno
585
+ left = self.parse_concat()
586
+ while self.stream.current.type in ("add", "sub"):
587
+ cls = _math_nodes[self.stream.current.type]
588
+ next(self.stream)
589
+ right = self.parse_concat()
590
+ left = cls(left, right, lineno=lineno)
591
+ lineno = self.stream.current.lineno
592
+ return left
593
+
594
+ def parse_concat(self) -> nodes.Expr:
595
+ lineno = self.stream.current.lineno
596
+ args = [self.parse_math2()]
597
+ while self.stream.current.type == "tilde":
598
+ next(self.stream)
599
+ args.append(self.parse_math2())
600
+ if len(args) == 1:
601
+ return args[0]
602
+ return nodes.Concat(args, lineno=lineno)
603
+
604
+ def parse_math2(self) -> nodes.Expr:
605
+ lineno = self.stream.current.lineno
606
+ left = self.parse_pow()
607
+ while self.stream.current.type in ("mul", "div", "floordiv", "mod"):
608
+ cls = _math_nodes[self.stream.current.type]
609
+ next(self.stream)
610
+ right = self.parse_pow()
611
+ left = cls(left, right, lineno=lineno)
612
+ lineno = self.stream.current.lineno
613
+ return left
614
+
615
+ def parse_pow(self) -> nodes.Expr:
616
+ lineno = self.stream.current.lineno
617
+ left = self.parse_unary()
618
+ while self.stream.current.type == "pow":
619
+ next(self.stream)
620
+ right = self.parse_unary()
621
+ left = nodes.Pow(left, right, lineno=lineno)
622
+ lineno = self.stream.current.lineno
623
+ return left
624
+
625
+ def parse_unary(self, with_filter: bool = True) -> nodes.Expr:
626
+ token_type = self.stream.current.type
627
+ lineno = self.stream.current.lineno
628
+ node: nodes.Expr
629
+
630
+ if token_type == "sub":
631
+ next(self.stream)
632
+ node = nodes.Neg(self.parse_unary(False), lineno=lineno)
633
+ elif token_type == "add":
634
+ next(self.stream)
635
+ node = nodes.Pos(self.parse_unary(False), lineno=lineno)
636
+ else:
637
+ node = self.parse_primary()
638
+ node = self.parse_postfix(node)
639
+ if with_filter:
640
+ node = self.parse_filter_expr(node)
641
+ return node
642
+
643
+ def parse_primary(self, with_namespace: bool = False) -> nodes.Expr:
644
+ """Parse a name or literal value. If ``with_namespace`` is enabled, also
645
+ parse namespace attr refs, for use in assignments."""
646
+ token = self.stream.current
647
+ node: nodes.Expr
648
+ if token.type == "name":
649
+ next(self.stream)
650
+ if token.value in ("true", "false", "True", "False"):
651
+ node = nodes.Const(token.value in ("true", "True"), lineno=token.lineno)
652
+ elif token.value in ("none", "None"):
653
+ node = nodes.Const(None, lineno=token.lineno)
654
+ elif with_namespace and self.stream.current.type == "dot":
655
+ # If namespace attributes are allowed at this point, and the next
656
+ # token is a dot, produce a namespace reference.
657
+ next(self.stream)
658
+ attr = self.stream.expect("name")
659
+ node = nodes.NSRef(token.value, attr.value, lineno=token.lineno)
660
+ else:
661
+ node = nodes.Name(token.value, "load", lineno=token.lineno)
662
+ elif token.type == "string":
663
+ next(self.stream)
664
+ buf = [token.value]
665
+ lineno = token.lineno
666
+ while self.stream.current.type == "string":
667
+ buf.append(self.stream.current.value)
668
+ next(self.stream)
669
+ node = nodes.Const("".join(buf), lineno=lineno)
670
+ elif token.type in ("integer", "float"):
671
+ next(self.stream)
672
+ node = nodes.Const(token.value, lineno=token.lineno)
673
+ elif token.type == "lparen":
674
+ next(self.stream)
675
+ node = self.parse_tuple(explicit_parentheses=True)
676
+ self.stream.expect("rparen")
677
+ elif token.type == "lbracket":
678
+ node = self.parse_list()
679
+ elif token.type == "lbrace":
680
+ node = self.parse_dict()
681
+ else:
682
+ self.fail(f"unexpected {describe_token(token)!r}", token.lineno)
683
+ return node
684
+
685
+ def parse_tuple(
686
+ self,
687
+ simplified: bool = False,
688
+ with_condexpr: bool = True,
689
+ extra_end_rules: t.Optional[t.Tuple[str, ...]] = None,
690
+ explicit_parentheses: bool = False,
691
+ with_namespace: bool = False,
692
+ ) -> t.Union[nodes.Tuple, nodes.Expr]:
693
+ """Works like `parse_expression` but if multiple expressions are
694
+ delimited by a comma a :class:`~jinja2.nodes.Tuple` node is created.
695
+ This method could also return a regular expression instead of a tuple
696
+ if no commas where found.
697
+
698
+ The default parsing mode is a full tuple. If `simplified` is `True`
699
+ only names and literals are parsed; ``with_namespace`` allows namespace
700
+ attr refs as well. The `no_condexpr` parameter is forwarded to
701
+ :meth:`parse_expression`.
702
+
703
+ Because tuples do not require delimiters and may end in a bogus comma
704
+ an extra hint is needed that marks the end of a tuple. For example
705
+ for loops support tuples between `for` and `in`. In that case the
706
+ `extra_end_rules` is set to ``['name:in']``.
707
+
708
+ `explicit_parentheses` is true if the parsing was triggered by an
709
+ expression in parentheses. This is used to figure out if an empty
710
+ tuple is a valid expression or not.
711
+ """
712
+ lineno = self.stream.current.lineno
713
+ if simplified:
714
+
715
+ def parse() -> nodes.Expr:
716
+ return self.parse_primary(with_namespace=with_namespace)
717
+
718
+ else:
719
+
720
+ def parse() -> nodes.Expr:
721
+ return self.parse_expression(with_condexpr=with_condexpr)
722
+
723
+ args: t.List[nodes.Expr] = []
724
+ is_tuple = False
725
+
726
+ while True:
727
+ if args:
728
+ self.stream.expect("comma")
729
+ if self.is_tuple_end(extra_end_rules):
730
+ break
731
+ args.append(parse())
732
+ if self.stream.current.type == "comma":
733
+ is_tuple = True
734
+ else:
735
+ break
736
+ lineno = self.stream.current.lineno
737
+
738
+ if not is_tuple:
739
+ if args:
740
+ return args[0]
741
+
742
+ # if we don't have explicit parentheses, an empty tuple is
743
+ # not a valid expression. This would mean nothing (literally
744
+ # nothing) in the spot of an expression would be an empty
745
+ # tuple.
746
+ if not explicit_parentheses:
747
+ self.fail(
748
+ "Expected an expression,"
749
+ f" got {describe_token(self.stream.current)!r}"
750
+ )
751
+
752
+ return nodes.Tuple(args, "load", lineno=lineno)
753
+
754
+ def parse_list(self) -> nodes.List:
755
+ token = self.stream.expect("lbracket")
756
+ items: t.List[nodes.Expr] = []
757
+ while self.stream.current.type != "rbracket":
758
+ if items:
759
+ self.stream.expect("comma")
760
+ if self.stream.current.type == "rbracket":
761
+ break
762
+ items.append(self.parse_expression())
763
+ self.stream.expect("rbracket")
764
+ return nodes.List(items, lineno=token.lineno)
765
+
766
+ def parse_dict(self) -> nodes.Dict:
767
+ token = self.stream.expect("lbrace")
768
+ items: t.List[nodes.Pair] = []
769
+ while self.stream.current.type != "rbrace":
770
+ if items:
771
+ self.stream.expect("comma")
772
+ if self.stream.current.type == "rbrace":
773
+ break
774
+ key = self.parse_expression()
775
+ self.stream.expect("colon")
776
+ value = self.parse_expression()
777
+ items.append(nodes.Pair(key, value, lineno=key.lineno))
778
+ self.stream.expect("rbrace")
779
+ return nodes.Dict(items, lineno=token.lineno)
780
+
781
+ def parse_postfix(self, node: nodes.Expr) -> nodes.Expr:
782
+ while True:
783
+ token_type = self.stream.current.type
784
+ if token_type == "dot" or token_type == "lbracket":
785
+ node = self.parse_subscript(node)
786
+ # calls are valid both after postfix expressions (getattr
787
+ # and getitem) as well as filters and tests
788
+ elif token_type == "lparen":
789
+ node = self.parse_call(node)
790
+ else:
791
+ break
792
+ return node
793
+
794
+ def parse_filter_expr(self, node: nodes.Expr) -> nodes.Expr:
795
+ while True:
796
+ token_type = self.stream.current.type
797
+ if token_type == "pipe":
798
+ node = self.parse_filter(node) # type: ignore
799
+ elif token_type == "name" and self.stream.current.value == "is":
800
+ node = self.parse_test(node)
801
+ # calls are valid both after postfix expressions (getattr
802
+ # and getitem) as well as filters and tests
803
+ elif token_type == "lparen":
804
+ node = self.parse_call(node)
805
+ else:
806
+ break
807
+ return node
808
+
809
+ def parse_subscript(
810
+ self, node: nodes.Expr
811
+ ) -> t.Union[nodes.Getattr, nodes.Getitem]:
812
+ token = next(self.stream)
813
+ arg: nodes.Expr
814
+
815
+ if token.type == "dot":
816
+ attr_token = self.stream.current
817
+ next(self.stream)
818
+ if attr_token.type == "name":
819
+ return nodes.Getattr(
820
+ node, attr_token.value, "load", lineno=token.lineno
821
+ )
822
+ elif attr_token.type != "integer":
823
+ self.fail("expected name or number", attr_token.lineno)
824
+ arg = nodes.Const(attr_token.value, lineno=attr_token.lineno)
825
+ return nodes.Getitem(node, arg, "load", lineno=token.lineno)
826
+ if token.type == "lbracket":
827
+ args: t.List[nodes.Expr] = []
828
+ while self.stream.current.type != "rbracket":
829
+ if args:
830
+ self.stream.expect("comma")
831
+ args.append(self.parse_subscribed())
832
+ self.stream.expect("rbracket")
833
+ if len(args) == 1:
834
+ arg = args[0]
835
+ else:
836
+ arg = nodes.Tuple(args, "load", lineno=token.lineno)
837
+ return nodes.Getitem(node, arg, "load", lineno=token.lineno)
838
+ self.fail("expected subscript expression", token.lineno)
839
+
840
+ def parse_subscribed(self) -> nodes.Expr:
841
+ lineno = self.stream.current.lineno
842
+ args: t.List[t.Optional[nodes.Expr]]
843
+
844
+ if self.stream.current.type == "colon":
845
+ next(self.stream)
846
+ args = [None]
847
+ else:
848
+ node = self.parse_expression()
849
+ if self.stream.current.type != "colon":
850
+ return node
851
+ next(self.stream)
852
+ args = [node]
853
+
854
+ if self.stream.current.type == "colon":
855
+ args.append(None)
856
+ elif self.stream.current.type not in ("rbracket", "comma"):
857
+ args.append(self.parse_expression())
858
+ else:
859
+ args.append(None)
860
+
861
+ if self.stream.current.type == "colon":
862
+ next(self.stream)
863
+ if self.stream.current.type not in ("rbracket", "comma"):
864
+ args.append(self.parse_expression())
865
+ else:
866
+ args.append(None)
867
+ else:
868
+ args.append(None)
869
+
870
+ return nodes.Slice(lineno=lineno, *args) # noqa: B026
871
+
872
+ def parse_call_args(
873
+ self,
874
+ ) -> t.Tuple[
875
+ t.List[nodes.Expr],
876
+ t.List[nodes.Keyword],
877
+ t.Optional[nodes.Expr],
878
+ t.Optional[nodes.Expr],
879
+ ]:
880
+ token = self.stream.expect("lparen")
881
+ args = []
882
+ kwargs = []
883
+ dyn_args = None
884
+ dyn_kwargs = None
885
+ require_comma = False
886
+
887
+ def ensure(expr: bool) -> None:
888
+ if not expr:
889
+ self.fail("invalid syntax for function call expression", token.lineno)
890
+
891
+ while self.stream.current.type != "rparen":
892
+ if require_comma:
893
+ self.stream.expect("comma")
894
+
895
+ # support for trailing comma
896
+ if self.stream.current.type == "rparen":
897
+ break
898
+
899
+ if self.stream.current.type == "mul":
900
+ ensure(dyn_args is None and dyn_kwargs is None)
901
+ next(self.stream)
902
+ dyn_args = self.parse_expression()
903
+ elif self.stream.current.type == "pow":
904
+ ensure(dyn_kwargs is None)
905
+ next(self.stream)
906
+ dyn_kwargs = self.parse_expression()
907
+ else:
908
+ if (
909
+ self.stream.current.type == "name"
910
+ and self.stream.look().type == "assign"
911
+ ):
912
+ # Parsing a kwarg
913
+ ensure(dyn_kwargs is None)
914
+ key = self.stream.current.value
915
+ self.stream.skip(2)
916
+ value = self.parse_expression()
917
+ kwargs.append(nodes.Keyword(key, value, lineno=value.lineno))
918
+ else:
919
+ # Parsing an arg
920
+ ensure(dyn_args is None and dyn_kwargs is None and not kwargs)
921
+ args.append(self.parse_expression())
922
+
923
+ require_comma = True
924
+
925
+ self.stream.expect("rparen")
926
+ return args, kwargs, dyn_args, dyn_kwargs
927
+
928
+ def parse_call(self, node: nodes.Expr) -> nodes.Call:
929
+ # The lparen will be expected in parse_call_args, but the lineno
930
+ # needs to be recorded before the stream is advanced.
931
+ token = self.stream.current
932
+ args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
933
+ return nodes.Call(node, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno)
934
+
935
+ def parse_filter(
936
+ self, node: t.Optional[nodes.Expr], start_inline: bool = False
937
+ ) -> t.Optional[nodes.Expr]:
938
+ while self.stream.current.type == "pipe" or start_inline:
939
+ if not start_inline:
940
+ next(self.stream)
941
+ token = self.stream.expect("name")
942
+ name = token.value
943
+ while self.stream.current.type == "dot":
944
+ next(self.stream)
945
+ name += "." + self.stream.expect("name").value
946
+ if self.stream.current.type == "lparen":
947
+ args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
948
+ else:
949
+ args = []
950
+ kwargs = []
951
+ dyn_args = dyn_kwargs = None
952
+ node = nodes.Filter(
953
+ node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno
954
+ )
955
+ start_inline = False
956
+ return node
957
+
958
+ def parse_test(self, node: nodes.Expr) -> nodes.Expr:
959
+ token = next(self.stream)
960
+ if self.stream.current.test("name:not"):
961
+ next(self.stream)
962
+ negated = True
963
+ else:
964
+ negated = False
965
+ name = self.stream.expect("name").value
966
+ while self.stream.current.type == "dot":
967
+ next(self.stream)
968
+ name += "." + self.stream.expect("name").value
969
+ dyn_args = dyn_kwargs = None
970
+ kwargs: t.List[nodes.Keyword] = []
971
+ if self.stream.current.type == "lparen":
972
+ args, kwargs, dyn_args, dyn_kwargs = self.parse_call_args()
973
+ elif self.stream.current.type in {
974
+ "name",
975
+ "string",
976
+ "integer",
977
+ "float",
978
+ "lparen",
979
+ "lbracket",
980
+ "lbrace",
981
+ } and not self.stream.current.test_any("name:else", "name:or", "name:and"):
982
+ if self.stream.current.test("name:is"):
983
+ self.fail("You cannot chain multiple tests with is")
984
+ arg_node = self.parse_primary()
985
+ arg_node = self.parse_postfix(arg_node)
986
+ args = [arg_node]
987
+ else:
988
+ args = []
989
+ node = nodes.Test(
990
+ node, name, args, kwargs, dyn_args, dyn_kwargs, lineno=token.lineno
991
+ )
992
+ if negated:
993
+ node = nodes.Not(node, lineno=token.lineno)
994
+ return node
995
+
996
+ def subparse(
997
+ self, end_tokens: t.Optional[t.Tuple[str, ...]] = None
998
+ ) -> t.List[nodes.Node]:
999
+ body: t.List[nodes.Node] = []
1000
+ data_buffer: t.List[nodes.Node] = []
1001
+ add_data = data_buffer.append
1002
+
1003
+ if end_tokens is not None:
1004
+ self._end_token_stack.append(end_tokens)
1005
+
1006
+ def flush_data() -> None:
1007
+ if data_buffer:
1008
+ lineno = data_buffer[0].lineno
1009
+ body.append(nodes.Output(data_buffer[:], lineno=lineno))
1010
+ del data_buffer[:]
1011
+
1012
+ try:
1013
+ while self.stream:
1014
+ token = self.stream.current
1015
+ if token.type == "data":
1016
+ if token.value:
1017
+ add_data(nodes.TemplateData(token.value, lineno=token.lineno))
1018
+ next(self.stream)
1019
+ elif token.type == "variable_begin":
1020
+ next(self.stream)
1021
+ add_data(self.parse_tuple(with_condexpr=True))
1022
+ self.stream.expect("variable_end")
1023
+ elif token.type == "block_begin":
1024
+ flush_data()
1025
+ next(self.stream)
1026
+ if end_tokens is not None and self.stream.current.test_any(
1027
+ *end_tokens
1028
+ ):
1029
+ return body
1030
+ rv = self.parse_statement()
1031
+ if isinstance(rv, list):
1032
+ body.extend(rv)
1033
+ else:
1034
+ body.append(rv)
1035
+ self.stream.expect("block_end")
1036
+ else:
1037
+ raise AssertionError("internal parsing error")
1038
+
1039
+ flush_data()
1040
+ finally:
1041
+ if end_tokens is not None:
1042
+ self._end_token_stack.pop()
1043
+ return body
1044
+
1045
+ def parse(self) -> nodes.Template:
1046
+ """Parse the whole template into a `Template` node."""
1047
+ result = nodes.Template(self.subparse(), lineno=1)
1048
+ result.set_environment(self.environment)
1049
+ return result
.venv/lib/python3.11/site-packages/rsa/__init__.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """RSA module
15
+
16
+ Module for calculating large primes, and RSA encryption, decryption, signing
17
+ and verification. Includes generating public and private keys.
18
+
19
+ WARNING: this implementation does not use compression of the cleartext input to
20
+ prevent repetitions, or other common security improvements. Use with care.
21
+
22
+ """
23
+
24
+ from rsa.key import newkeys, PrivateKey, PublicKey
25
+ from rsa.pkcs1 import (
26
+ encrypt,
27
+ decrypt,
28
+ sign,
29
+ verify,
30
+ DecryptionError,
31
+ VerificationError,
32
+ find_signature_hash,
33
+ sign_hash,
34
+ compute_hash,
35
+ )
36
+
37
+ __author__ = "Sybren Stuvel, Barry Mead and Yesudeep Mangalapilly"
38
+ __date__ = "2022-07-20"
39
+ __version__ = "4.9"
40
+
41
+ # Do doctest if we're run directly
42
+ if __name__ == "__main__":
43
+ import doctest
44
+
45
+ doctest.testmod()
46
+
47
+ __all__ = [
48
+ "newkeys",
49
+ "encrypt",
50
+ "decrypt",
51
+ "sign",
52
+ "verify",
53
+ "PublicKey",
54
+ "PrivateKey",
55
+ "DecryptionError",
56
+ "VerificationError",
57
+ "find_signature_hash",
58
+ "compute_hash",
59
+ "sign_hash",
60
+ ]
.venv/lib/python3.11/site-packages/rsa/__pycache__/__init__.cpython-311.pyc ADDED
Binary file (1.3 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/asn1.cpython-311.pyc ADDED
Binary file (2.33 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/cli.cpython-311.pyc ADDED
Binary file (15.9 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/common.cpython-311.pyc ADDED
Binary file (5.73 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/core.cpython-311.pyc ADDED
Binary file (1.97 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/key.cpython-311.pyc ADDED
Binary file (37 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/parallel.cpython-311.pyc ADDED
Binary file (3.1 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/pem.cpython-311.pyc ADDED
Binary file (4.42 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/pkcs1.cpython-311.pyc ADDED
Binary file (17.4 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/pkcs1_v2.cpython-311.pyc ADDED
Binary file (3.85 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/prime.cpython-311.pyc ADDED
Binary file (5.24 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/randnum.cpython-311.pyc ADDED
Binary file (2.51 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/transform.cpython-311.pyc ADDED
Binary file (2.38 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/__pycache__/util.cpython-311.pyc ADDED
Binary file (3.85 kB). View file
 
.venv/lib/python3.11/site-packages/rsa/asn1.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """ASN.1 definitions.
16
+
17
+ Not all ASN.1-handling code use these definitions, but when it does, they should be here.
18
+ """
19
+
20
+ from pyasn1.type import univ, namedtype, tag
21
+
22
+
23
+ class PubKeyHeader(univ.Sequence):
24
+ componentType = namedtype.NamedTypes(
25
+ namedtype.NamedType("oid", univ.ObjectIdentifier()),
26
+ namedtype.NamedType("parameters", univ.Null()),
27
+ )
28
+
29
+
30
+ class OpenSSLPubKey(univ.Sequence):
31
+ componentType = namedtype.NamedTypes(
32
+ namedtype.NamedType("header", PubKeyHeader()),
33
+ # This little hack (the implicit tag) allows us to get a Bit String as Octet String
34
+ namedtype.NamedType(
35
+ "key",
36
+ univ.OctetString().subtype(implicitTag=tag.Tag(tagClass=0, tagFormat=0, tagId=3)),
37
+ ),
38
+ )
39
+
40
+
41
+ class AsnPubKey(univ.Sequence):
42
+ """ASN.1 contents of DER encoded public key:
43
+
44
+ RSAPublicKey ::= SEQUENCE {
45
+ modulus INTEGER, -- n
46
+ publicExponent INTEGER, -- e
47
+ """
48
+
49
+ componentType = namedtype.NamedTypes(
50
+ namedtype.NamedType("modulus", univ.Integer()),
51
+ namedtype.NamedType("publicExponent", univ.Integer()),
52
+ )
.venv/lib/python3.11/site-packages/rsa/cli.py ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Commandline scripts.
16
+
17
+ These scripts are called by the executables defined in setup.py.
18
+ """
19
+
20
+ import abc
21
+ import sys
22
+ import typing
23
+ import optparse
24
+
25
+ import rsa
26
+ import rsa.key
27
+ import rsa.pkcs1
28
+
29
+ HASH_METHODS = sorted(rsa.pkcs1.HASH_METHODS.keys())
30
+ Indexable = typing.Union[typing.Tuple, typing.List[str]]
31
+
32
+
33
+ def keygen() -> None:
34
+ """Key generator."""
35
+
36
+ # Parse the CLI options
37
+ parser = optparse.OptionParser(
38
+ usage="usage: %prog [options] keysize",
39
+ description='Generates a new RSA key pair of "keysize" bits.',
40
+ )
41
+
42
+ parser.add_option(
43
+ "--pubout",
44
+ type="string",
45
+ help="Output filename for the public key. The public key is "
46
+ "not saved if this option is not present. You can use "
47
+ "pyrsa-priv2pub to create the public key file later.",
48
+ )
49
+
50
+ parser.add_option(
51
+ "-o",
52
+ "--out",
53
+ type="string",
54
+ help="Output filename for the private key. The key is "
55
+ "written to stdout if this option is not present.",
56
+ )
57
+
58
+ parser.add_option(
59
+ "--form",
60
+ help="key format of the private and public keys - default PEM",
61
+ choices=("PEM", "DER"),
62
+ default="PEM",
63
+ )
64
+
65
+ (cli, cli_args) = parser.parse_args(sys.argv[1:])
66
+
67
+ if len(cli_args) != 1:
68
+ parser.print_help()
69
+ raise SystemExit(1)
70
+
71
+ try:
72
+ keysize = int(cli_args[0])
73
+ except ValueError as ex:
74
+ parser.print_help()
75
+ print("Not a valid number: %s" % cli_args[0], file=sys.stderr)
76
+ raise SystemExit(1) from ex
77
+
78
+ print("Generating %i-bit key" % keysize, file=sys.stderr)
79
+ (pub_key, priv_key) = rsa.newkeys(keysize)
80
+
81
+ # Save public key
82
+ if cli.pubout:
83
+ print("Writing public key to %s" % cli.pubout, file=sys.stderr)
84
+ data = pub_key.save_pkcs1(format=cli.form)
85
+ with open(cli.pubout, "wb") as outfile:
86
+ outfile.write(data)
87
+
88
+ # Save private key
89
+ data = priv_key.save_pkcs1(format=cli.form)
90
+
91
+ if cli.out:
92
+ print("Writing private key to %s" % cli.out, file=sys.stderr)
93
+ with open(cli.out, "wb") as outfile:
94
+ outfile.write(data)
95
+ else:
96
+ print("Writing private key to stdout", file=sys.stderr)
97
+ sys.stdout.buffer.write(data)
98
+
99
+
100
+ class CryptoOperation(metaclass=abc.ABCMeta):
101
+ """CLI callable that operates with input, output, and a key."""
102
+
103
+ keyname = "public" # or 'private'
104
+ usage = "usage: %%prog [options] %(keyname)s_key"
105
+ description = ""
106
+ operation = "decrypt"
107
+ operation_past = "decrypted"
108
+ operation_progressive = "decrypting"
109
+ input_help = "Name of the file to %(operation)s. Reads from stdin if " "not specified."
110
+ output_help = (
111
+ "Name of the file to write the %(operation_past)s file "
112
+ "to. Written to stdout if this option is not present."
113
+ )
114
+ expected_cli_args = 1
115
+ has_output = True
116
+
117
+ key_class = rsa.PublicKey # type: typing.Type[rsa.key.AbstractKey]
118
+
119
+ def __init__(self) -> None:
120
+ self.usage = self.usage % self.__class__.__dict__
121
+ self.input_help = self.input_help % self.__class__.__dict__
122
+ self.output_help = self.output_help % self.__class__.__dict__
123
+
124
+ @abc.abstractmethod
125
+ def perform_operation(
126
+ self, indata: bytes, key: rsa.key.AbstractKey, cli_args: Indexable
127
+ ) -> typing.Any:
128
+ """Performs the program's operation.
129
+
130
+ Implement in a subclass.
131
+
132
+ :returns: the data to write to the output.
133
+ """
134
+
135
+ def __call__(self) -> None:
136
+ """Runs the program."""
137
+
138
+ (cli, cli_args) = self.parse_cli()
139
+
140
+ key = self.read_key(cli_args[0], cli.keyform)
141
+
142
+ indata = self.read_infile(cli.input)
143
+
144
+ print(self.operation_progressive.title(), file=sys.stderr)
145
+ outdata = self.perform_operation(indata, key, cli_args)
146
+
147
+ if self.has_output:
148
+ self.write_outfile(outdata, cli.output)
149
+
150
+ def parse_cli(self) -> typing.Tuple[optparse.Values, typing.List[str]]:
151
+ """Parse the CLI options
152
+
153
+ :returns: (cli_opts, cli_args)
154
+ """
155
+
156
+ parser = optparse.OptionParser(usage=self.usage, description=self.description)
157
+
158
+ parser.add_option("-i", "--input", type="string", help=self.input_help)
159
+
160
+ if self.has_output:
161
+ parser.add_option("-o", "--output", type="string", help=self.output_help)
162
+
163
+ parser.add_option(
164
+ "--keyform",
165
+ help="Key format of the %s key - default PEM" % self.keyname,
166
+ choices=("PEM", "DER"),
167
+ default="PEM",
168
+ )
169
+
170
+ (cli, cli_args) = parser.parse_args(sys.argv[1:])
171
+
172
+ if len(cli_args) != self.expected_cli_args:
173
+ parser.print_help()
174
+ raise SystemExit(1)
175
+
176
+ return cli, cli_args
177
+
178
+ def read_key(self, filename: str, keyform: str) -> rsa.key.AbstractKey:
179
+ """Reads a public or private key."""
180
+
181
+ print("Reading %s key from %s" % (self.keyname, filename), file=sys.stderr)
182
+ with open(filename, "rb") as keyfile:
183
+ keydata = keyfile.read()
184
+
185
+ return self.key_class.load_pkcs1(keydata, keyform)
186
+
187
+ def read_infile(self, inname: str) -> bytes:
188
+ """Read the input file"""
189
+
190
+ if inname:
191
+ print("Reading input from %s" % inname, file=sys.stderr)
192
+ with open(inname, "rb") as infile:
193
+ return infile.read()
194
+
195
+ print("Reading input from stdin", file=sys.stderr)
196
+ return sys.stdin.buffer.read()
197
+
198
+ def write_outfile(self, outdata: bytes, outname: str) -> None:
199
+ """Write the output file"""
200
+
201
+ if outname:
202
+ print("Writing output to %s" % outname, file=sys.stderr)
203
+ with open(outname, "wb") as outfile:
204
+ outfile.write(outdata)
205
+ else:
206
+ print("Writing output to stdout", file=sys.stderr)
207
+ sys.stdout.buffer.write(outdata)
208
+
209
+
210
+ class EncryptOperation(CryptoOperation):
211
+ """Encrypts a file."""
212
+
213
+ keyname = "public"
214
+ description = (
215
+ "Encrypts a file. The file must be shorter than the key " "length in order to be encrypted."
216
+ )
217
+ operation = "encrypt"
218
+ operation_past = "encrypted"
219
+ operation_progressive = "encrypting"
220
+
221
+ def perform_operation(
222
+ self, indata: bytes, pub_key: rsa.key.AbstractKey, cli_args: Indexable = ()
223
+ ) -> bytes:
224
+ """Encrypts files."""
225
+ assert isinstance(pub_key, rsa.key.PublicKey)
226
+ return rsa.encrypt(indata, pub_key)
227
+
228
+
229
+ class DecryptOperation(CryptoOperation):
230
+ """Decrypts a file."""
231
+
232
+ keyname = "private"
233
+ description = (
234
+ "Decrypts a file. The original file must be shorter than "
235
+ "the key length in order to have been encrypted."
236
+ )
237
+ operation = "decrypt"
238
+ operation_past = "decrypted"
239
+ operation_progressive = "decrypting"
240
+ key_class = rsa.PrivateKey
241
+
242
+ def perform_operation(
243
+ self, indata: bytes, priv_key: rsa.key.AbstractKey, cli_args: Indexable = ()
244
+ ) -> bytes:
245
+ """Decrypts files."""
246
+ assert isinstance(priv_key, rsa.key.PrivateKey)
247
+ return rsa.decrypt(indata, priv_key)
248
+
249
+
250
+ class SignOperation(CryptoOperation):
251
+ """Signs a file."""
252
+
253
+ keyname = "private"
254
+ usage = "usage: %%prog [options] private_key hash_method"
255
+ description = (
256
+ "Signs a file, outputs the signature. Choose the hash "
257
+ "method from %s" % ", ".join(HASH_METHODS)
258
+ )
259
+ operation = "sign"
260
+ operation_past = "signature"
261
+ operation_progressive = "Signing"
262
+ key_class = rsa.PrivateKey
263
+ expected_cli_args = 2
264
+
265
+ output_help = (
266
+ "Name of the file to write the signature to. Written "
267
+ "to stdout if this option is not present."
268
+ )
269
+
270
+ def perform_operation(
271
+ self, indata: bytes, priv_key: rsa.key.AbstractKey, cli_args: Indexable
272
+ ) -> bytes:
273
+ """Signs files."""
274
+ assert isinstance(priv_key, rsa.key.PrivateKey)
275
+
276
+ hash_method = cli_args[1]
277
+ if hash_method not in HASH_METHODS:
278
+ raise SystemExit("Invalid hash method, choose one of %s" % ", ".join(HASH_METHODS))
279
+
280
+ return rsa.sign(indata, priv_key, hash_method)
281
+
282
+
283
+ class VerifyOperation(CryptoOperation):
284
+ """Verify a signature."""
285
+
286
+ keyname = "public"
287
+ usage = "usage: %%prog [options] public_key signature_file"
288
+ description = (
289
+ "Verifies a signature, exits with status 0 upon success, "
290
+ "prints an error message and exits with status 1 upon error."
291
+ )
292
+ operation = "verify"
293
+ operation_past = "verified"
294
+ operation_progressive = "Verifying"
295
+ key_class = rsa.PublicKey
296
+ expected_cli_args = 2
297
+ has_output = False
298
+
299
+ def perform_operation(
300
+ self, indata: bytes, pub_key: rsa.key.AbstractKey, cli_args: Indexable
301
+ ) -> None:
302
+ """Verifies files."""
303
+ assert isinstance(pub_key, rsa.key.PublicKey)
304
+
305
+ signature_file = cli_args[1]
306
+
307
+ with open(signature_file, "rb") as sigfile:
308
+ signature = sigfile.read()
309
+
310
+ try:
311
+ rsa.verify(indata, signature, pub_key)
312
+ except rsa.VerificationError as ex:
313
+ raise SystemExit("Verification failed.") from ex
314
+
315
+ print("Verification OK", file=sys.stderr)
316
+
317
+
318
+ encrypt = EncryptOperation()
319
+ decrypt = DecryptOperation()
320
+ sign = SignOperation()
321
+ verify = VerifyOperation()
.venv/lib/python3.11/site-packages/rsa/common.py ADDED
@@ -0,0 +1,184 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Common functionality shared by several modules."""
16
+
17
+ import typing
18
+
19
+
20
+ class NotRelativePrimeError(ValueError):
21
+ def __init__(self, a: int, b: int, d: int, msg: str = "") -> None:
22
+ super().__init__(msg or "%d and %d are not relatively prime, divider=%i" % (a, b, d))
23
+ self.a = a
24
+ self.b = b
25
+ self.d = d
26
+
27
+
28
+ def bit_size(num: int) -> int:
29
+ """
30
+ Number of bits needed to represent a integer excluding any prefix
31
+ 0 bits.
32
+
33
+ Usage::
34
+
35
+ >>> bit_size(1023)
36
+ 10
37
+ >>> bit_size(1024)
38
+ 11
39
+ >>> bit_size(1025)
40
+ 11
41
+
42
+ :param num:
43
+ Integer value. If num is 0, returns 0. Only the absolute value of the
44
+ number is considered. Therefore, signed integers will be abs(num)
45
+ before the number's bit length is determined.
46
+ :returns:
47
+ Returns the number of bits in the integer.
48
+ """
49
+
50
+ try:
51
+ return num.bit_length()
52
+ except AttributeError as ex:
53
+ raise TypeError("bit_size(num) only supports integers, not %r" % type(num)) from ex
54
+
55
+
56
+ def byte_size(number: int) -> int:
57
+ """
58
+ Returns the number of bytes required to hold a specific long number.
59
+
60
+ The number of bytes is rounded up.
61
+
62
+ Usage::
63
+
64
+ >>> byte_size(1 << 1023)
65
+ 128
66
+ >>> byte_size((1 << 1024) - 1)
67
+ 128
68
+ >>> byte_size(1 << 1024)
69
+ 129
70
+
71
+ :param number:
72
+ An unsigned integer
73
+ :returns:
74
+ The number of bytes required to hold a specific long number.
75
+ """
76
+ if number == 0:
77
+ return 1
78
+ return ceil_div(bit_size(number), 8)
79
+
80
+
81
+ def ceil_div(num: int, div: int) -> int:
82
+ """
83
+ Returns the ceiling function of a division between `num` and `div`.
84
+
85
+ Usage::
86
+
87
+ >>> ceil_div(100, 7)
88
+ 15
89
+ >>> ceil_div(100, 10)
90
+ 10
91
+ >>> ceil_div(1, 4)
92
+ 1
93
+
94
+ :param num: Division's numerator, a number
95
+ :param div: Division's divisor, a number
96
+
97
+ :return: Rounded up result of the division between the parameters.
98
+ """
99
+ quanta, mod = divmod(num, div)
100
+ if mod:
101
+ quanta += 1
102
+ return quanta
103
+
104
+
105
+ def extended_gcd(a: int, b: int) -> typing.Tuple[int, int, int]:
106
+ """Returns a tuple (r, i, j) such that r = gcd(a, b) = ia + jb"""
107
+ # r = gcd(a,b) i = multiplicitive inverse of a mod b
108
+ # or j = multiplicitive inverse of b mod a
109
+ # Neg return values for i or j are made positive mod b or a respectively
110
+ # Iterateive Version is faster and uses much less stack space
111
+ x = 0
112
+ y = 1
113
+ lx = 1
114
+ ly = 0
115
+ oa = a # Remember original a/b to remove
116
+ ob = b # negative values from return results
117
+ while b != 0:
118
+ q = a // b
119
+ (a, b) = (b, a % b)
120
+ (x, lx) = ((lx - (q * x)), x)
121
+ (y, ly) = ((ly - (q * y)), y)
122
+ if lx < 0:
123
+ lx += ob # If neg wrap modulo original b
124
+ if ly < 0:
125
+ ly += oa # If neg wrap modulo original a
126
+ return a, lx, ly # Return only positive values
127
+
128
+
129
+ def inverse(x: int, n: int) -> int:
130
+ """Returns the inverse of x % n under multiplication, a.k.a x^-1 (mod n)
131
+
132
+ >>> inverse(7, 4)
133
+ 3
134
+ >>> (inverse(143, 4) * 143) % 4
135
+ 1
136
+ """
137
+
138
+ (divider, inv, _) = extended_gcd(x, n)
139
+
140
+ if divider != 1:
141
+ raise NotRelativePrimeError(x, n, divider)
142
+
143
+ return inv
144
+
145
+
146
+ def crt(a_values: typing.Iterable[int], modulo_values: typing.Iterable[int]) -> int:
147
+ """Chinese Remainder Theorem.
148
+
149
+ Calculates x such that x = a[i] (mod m[i]) for each i.
150
+
151
+ :param a_values: the a-values of the above equation
152
+ :param modulo_values: the m-values of the above equation
153
+ :returns: x such that x = a[i] (mod m[i]) for each i
154
+
155
+
156
+ >>> crt([2, 3], [3, 5])
157
+ 8
158
+
159
+ >>> crt([2, 3, 2], [3, 5, 7])
160
+ 23
161
+
162
+ >>> crt([2, 3, 0], [7, 11, 15])
163
+ 135
164
+ """
165
+
166
+ m = 1
167
+ x = 0
168
+
169
+ for modulo in modulo_values:
170
+ m *= modulo
171
+
172
+ for (m_i, a_i) in zip(modulo_values, a_values):
173
+ M_i = m // m_i
174
+ inv = inverse(M_i, m_i)
175
+
176
+ x = (x + a_i * M_i * inv) % m
177
+
178
+ return x
179
+
180
+
181
+ if __name__ == "__main__":
182
+ import doctest
183
+
184
+ doctest.testmod()
.venv/lib/python3.11/site-packages/rsa/core.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Core mathematical operations.
16
+
17
+ This is the actual core RSA implementation, which is only defined
18
+ mathematically on integers.
19
+ """
20
+
21
+
22
+ def assert_int(var: int, name: str) -> None:
23
+ if isinstance(var, int):
24
+ return
25
+
26
+ raise TypeError("%s should be an integer, not %s" % (name, var.__class__))
27
+
28
+
29
+ def encrypt_int(message: int, ekey: int, n: int) -> int:
30
+ """Encrypts a message using encryption key 'ekey', working modulo n"""
31
+
32
+ assert_int(message, "message")
33
+ assert_int(ekey, "ekey")
34
+ assert_int(n, "n")
35
+
36
+ if message < 0:
37
+ raise ValueError("Only non-negative numbers are supported")
38
+
39
+ if message > n:
40
+ raise OverflowError("The message %i is too long for n=%i" % (message, n))
41
+
42
+ return pow(message, ekey, n)
43
+
44
+
45
+ def decrypt_int(cyphertext: int, dkey: int, n: int) -> int:
46
+ """Decrypts a cypher text using the decryption key 'dkey', working modulo n"""
47
+
48
+ assert_int(cyphertext, "cyphertext")
49
+ assert_int(dkey, "dkey")
50
+ assert_int(n, "n")
51
+
52
+ message = pow(cyphertext, dkey, n)
53
+ return message
.venv/lib/python3.11/site-packages/rsa/key.py ADDED
@@ -0,0 +1,858 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """RSA key generation code.
16
+
17
+ Create new keys with the newkeys() function. It will give you a PublicKey and a
18
+ PrivateKey object.
19
+
20
+ Loading and saving keys requires the pyasn1 module. This module is imported as
21
+ late as possible, such that other functionality will remain working in absence
22
+ of pyasn1.
23
+
24
+ .. note::
25
+
26
+ Storing public and private keys via the `pickle` module is possible.
27
+ However, it is insecure to load a key from an untrusted source.
28
+ The pickle module is not secure against erroneous or maliciously
29
+ constructed data. Never unpickle data received from an untrusted
30
+ or unauthenticated source.
31
+
32
+ """
33
+
34
+ import threading
35
+ import typing
36
+ import warnings
37
+
38
+ import rsa.prime
39
+ import rsa.pem
40
+ import rsa.common
41
+ import rsa.randnum
42
+ import rsa.core
43
+
44
+
45
+ DEFAULT_EXPONENT = 65537
46
+
47
+
48
+ T = typing.TypeVar("T", bound="AbstractKey")
49
+
50
+
51
+ class AbstractKey:
52
+ """Abstract superclass for private and public keys."""
53
+
54
+ __slots__ = ("n", "e", "blindfac", "blindfac_inverse", "mutex")
55
+
56
+ def __init__(self, n: int, e: int) -> None:
57
+ self.n = n
58
+ self.e = e
59
+
60
+ # These will be computed properly on the first call to blind().
61
+ self.blindfac = self.blindfac_inverse = -1
62
+
63
+ # Used to protect updates to the blinding factor in multi-threaded
64
+ # environments.
65
+ self.mutex = threading.Lock()
66
+
67
+ @classmethod
68
+ def _load_pkcs1_pem(cls: typing.Type[T], keyfile: bytes) -> T:
69
+ """Loads a key in PKCS#1 PEM format, implement in a subclass.
70
+
71
+ :param keyfile: contents of a PEM-encoded file that contains
72
+ the public key.
73
+ :type keyfile: bytes
74
+
75
+ :return: the loaded key
76
+ :rtype: AbstractKey
77
+ """
78
+
79
+ @classmethod
80
+ def _load_pkcs1_der(cls: typing.Type[T], keyfile: bytes) -> T:
81
+ """Loads a key in PKCS#1 PEM format, implement in a subclass.
82
+
83
+ :param keyfile: contents of a DER-encoded file that contains
84
+ the public key.
85
+ :type keyfile: bytes
86
+
87
+ :return: the loaded key
88
+ :rtype: AbstractKey
89
+ """
90
+
91
+ def _save_pkcs1_pem(self) -> bytes:
92
+ """Saves the key in PKCS#1 PEM format, implement in a subclass.
93
+
94
+ :returns: the PEM-encoded key.
95
+ :rtype: bytes
96
+ """
97
+
98
+ def _save_pkcs1_der(self) -> bytes:
99
+ """Saves the key in PKCS#1 DER format, implement in a subclass.
100
+
101
+ :returns: the DER-encoded key.
102
+ :rtype: bytes
103
+ """
104
+
105
+ @classmethod
106
+ def load_pkcs1(cls: typing.Type[T], keyfile: bytes, format: str = "PEM") -> T:
107
+ """Loads a key in PKCS#1 DER or PEM format.
108
+
109
+ :param keyfile: contents of a DER- or PEM-encoded file that contains
110
+ the key.
111
+ :type keyfile: bytes
112
+ :param format: the format of the file to load; 'PEM' or 'DER'
113
+ :type format: str
114
+
115
+ :return: the loaded key
116
+ :rtype: AbstractKey
117
+ """
118
+
119
+ methods = {
120
+ "PEM": cls._load_pkcs1_pem,
121
+ "DER": cls._load_pkcs1_der,
122
+ }
123
+
124
+ method = cls._assert_format_exists(format, methods)
125
+ return method(keyfile)
126
+
127
+ @staticmethod
128
+ def _assert_format_exists(
129
+ file_format: str, methods: typing.Mapping[str, typing.Callable]
130
+ ) -> typing.Callable:
131
+ """Checks whether the given file format exists in 'methods'."""
132
+
133
+ try:
134
+ return methods[file_format]
135
+ except KeyError as ex:
136
+ formats = ", ".join(sorted(methods.keys()))
137
+ raise ValueError(
138
+ "Unsupported format: %r, try one of %s" % (file_format, formats)
139
+ ) from ex
140
+
141
+ def save_pkcs1(self, format: str = "PEM") -> bytes:
142
+ """Saves the key in PKCS#1 DER or PEM format.
143
+
144
+ :param format: the format to save; 'PEM' or 'DER'
145
+ :type format: str
146
+ :returns: the DER- or PEM-encoded key.
147
+ :rtype: bytes
148
+ """
149
+
150
+ methods = {
151
+ "PEM": self._save_pkcs1_pem,
152
+ "DER": self._save_pkcs1_der,
153
+ }
154
+
155
+ method = self._assert_format_exists(format, methods)
156
+ return method()
157
+
158
+ def blind(self, message: int) -> typing.Tuple[int, int]:
159
+ """Performs blinding on the message.
160
+
161
+ :param message: the message, as integer, to blind.
162
+ :param r: the random number to blind with.
163
+ :return: tuple (the blinded message, the inverse of the used blinding factor)
164
+
165
+ The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
166
+
167
+ See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
168
+ """
169
+ blindfac, blindfac_inverse = self._update_blinding_factor()
170
+ blinded = (message * pow(blindfac, self.e, self.n)) % self.n
171
+ return blinded, blindfac_inverse
172
+
173
+ def unblind(self, blinded: int, blindfac_inverse: int) -> int:
174
+ """Performs blinding on the message using random number 'blindfac_inverse'.
175
+
176
+ :param blinded: the blinded message, as integer, to unblind.
177
+ :param blindfac: the factor to unblind with.
178
+ :return: the original message.
179
+
180
+ The blinding is such that message = unblind(decrypt(blind(encrypt(message))).
181
+
182
+ See https://en.wikipedia.org/wiki/Blinding_%28cryptography%29
183
+ """
184
+ return (blindfac_inverse * blinded) % self.n
185
+
186
+ def _initial_blinding_factor(self) -> int:
187
+ for _ in range(1000):
188
+ blind_r = rsa.randnum.randint(self.n - 1)
189
+ if rsa.prime.are_relatively_prime(self.n, blind_r):
190
+ return blind_r
191
+ raise RuntimeError("unable to find blinding factor")
192
+
193
+ def _update_blinding_factor(self) -> typing.Tuple[int, int]:
194
+ """Update blinding factors.
195
+
196
+ Computing a blinding factor is expensive, so instead this function
197
+ does this once, then updates the blinding factor as per section 9
198
+ of 'A Timing Attack against RSA with the Chinese Remainder Theorem'
199
+ by Werner Schindler.
200
+ See https://tls.mbed.org/public/WSchindler-RSA_Timing_Attack.pdf
201
+
202
+ :return: the new blinding factor and its inverse.
203
+ """
204
+
205
+ with self.mutex:
206
+ if self.blindfac < 0:
207
+ # Compute initial blinding factor, which is rather slow to do.
208
+ self.blindfac = self._initial_blinding_factor()
209
+ self.blindfac_inverse = rsa.common.inverse(self.blindfac, self.n)
210
+ else:
211
+ # Reuse previous blinding factor.
212
+ self.blindfac = pow(self.blindfac, 2, self.n)
213
+ self.blindfac_inverse = pow(self.blindfac_inverse, 2, self.n)
214
+
215
+ return self.blindfac, self.blindfac_inverse
216
+
217
+
218
+ class PublicKey(AbstractKey):
219
+ """Represents a public RSA key.
220
+
221
+ This key is also known as the 'encryption key'. It contains the 'n' and 'e'
222
+ values.
223
+
224
+ Supports attributes as well as dictionary-like access. Attribute access is
225
+ faster, though.
226
+
227
+ >>> PublicKey(5, 3)
228
+ PublicKey(5, 3)
229
+
230
+ >>> key = PublicKey(5, 3)
231
+ >>> key.n
232
+ 5
233
+ >>> key['n']
234
+ 5
235
+ >>> key.e
236
+ 3
237
+ >>> key['e']
238
+ 3
239
+
240
+ """
241
+
242
+ __slots__ = ()
243
+
244
+ def __getitem__(self, key: str) -> int:
245
+ return getattr(self, key)
246
+
247
+ def __repr__(self) -> str:
248
+ return "PublicKey(%i, %i)" % (self.n, self.e)
249
+
250
+ def __getstate__(self) -> typing.Tuple[int, int]:
251
+ """Returns the key as tuple for pickling."""
252
+ return self.n, self.e
253
+
254
+ def __setstate__(self, state: typing.Tuple[int, int]) -> None:
255
+ """Sets the key from tuple."""
256
+ self.n, self.e = state
257
+ AbstractKey.__init__(self, self.n, self.e)
258
+
259
+ def __eq__(self, other: typing.Any) -> bool:
260
+ if other is None:
261
+ return False
262
+
263
+ if not isinstance(other, PublicKey):
264
+ return False
265
+
266
+ return self.n == other.n and self.e == other.e
267
+
268
+ def __ne__(self, other: typing.Any) -> bool:
269
+ return not (self == other)
270
+
271
+ def __hash__(self) -> int:
272
+ return hash((self.n, self.e))
273
+
274
+ @classmethod
275
+ def _load_pkcs1_der(cls, keyfile: bytes) -> "PublicKey":
276
+ """Loads a key in PKCS#1 DER format.
277
+
278
+ :param keyfile: contents of a DER-encoded file that contains the public
279
+ key.
280
+ :return: a PublicKey object
281
+
282
+ First let's construct a DER encoded key:
283
+
284
+ >>> import base64
285
+ >>> b64der = 'MAwCBQCNGmYtAgMBAAE='
286
+ >>> der = base64.standard_b64decode(b64der)
287
+
288
+ This loads the file:
289
+
290
+ >>> PublicKey._load_pkcs1_der(der)
291
+ PublicKey(2367317549, 65537)
292
+
293
+ """
294
+
295
+ from pyasn1.codec.der import decoder
296
+ from rsa.asn1 import AsnPubKey
297
+
298
+ (priv, _) = decoder.decode(keyfile, asn1Spec=AsnPubKey())
299
+ return cls(n=int(priv["modulus"]), e=int(priv["publicExponent"]))
300
+
301
+ def _save_pkcs1_der(self) -> bytes:
302
+ """Saves the public key in PKCS#1 DER format.
303
+
304
+ :returns: the DER-encoded public key.
305
+ :rtype: bytes
306
+ """
307
+
308
+ from pyasn1.codec.der import encoder
309
+ from rsa.asn1 import AsnPubKey
310
+
311
+ # Create the ASN object
312
+ asn_key = AsnPubKey()
313
+ asn_key.setComponentByName("modulus", self.n)
314
+ asn_key.setComponentByName("publicExponent", self.e)
315
+
316
+ return encoder.encode(asn_key)
317
+
318
+ @classmethod
319
+ def _load_pkcs1_pem(cls, keyfile: bytes) -> "PublicKey":
320
+ """Loads a PKCS#1 PEM-encoded public key file.
321
+
322
+ The contents of the file before the "-----BEGIN RSA PUBLIC KEY-----" and
323
+ after the "-----END RSA PUBLIC KEY-----" lines is ignored.
324
+
325
+ :param keyfile: contents of a PEM-encoded file that contains the public
326
+ key.
327
+ :return: a PublicKey object
328
+ """
329
+
330
+ der = rsa.pem.load_pem(keyfile, "RSA PUBLIC KEY")
331
+ return cls._load_pkcs1_der(der)
332
+
333
+ def _save_pkcs1_pem(self) -> bytes:
334
+ """Saves a PKCS#1 PEM-encoded public key file.
335
+
336
+ :return: contents of a PEM-encoded file that contains the public key.
337
+ :rtype: bytes
338
+ """
339
+
340
+ der = self._save_pkcs1_der()
341
+ return rsa.pem.save_pem(der, "RSA PUBLIC KEY")
342
+
343
+ @classmethod
344
+ def load_pkcs1_openssl_pem(cls, keyfile: bytes) -> "PublicKey":
345
+ """Loads a PKCS#1.5 PEM-encoded public key file from OpenSSL.
346
+
347
+ These files can be recognised in that they start with BEGIN PUBLIC KEY
348
+ rather than BEGIN RSA PUBLIC KEY.
349
+
350
+ The contents of the file before the "-----BEGIN PUBLIC KEY-----" and
351
+ after the "-----END PUBLIC KEY-----" lines is ignored.
352
+
353
+ :param keyfile: contents of a PEM-encoded file that contains the public
354
+ key, from OpenSSL.
355
+ :type keyfile: bytes
356
+ :return: a PublicKey object
357
+ """
358
+
359
+ der = rsa.pem.load_pem(keyfile, "PUBLIC KEY")
360
+ return cls.load_pkcs1_openssl_der(der)
361
+
362
+ @classmethod
363
+ def load_pkcs1_openssl_der(cls, keyfile: bytes) -> "PublicKey":
364
+ """Loads a PKCS#1 DER-encoded public key file from OpenSSL.
365
+
366
+ :param keyfile: contents of a DER-encoded file that contains the public
367
+ key, from OpenSSL.
368
+ :return: a PublicKey object
369
+ """
370
+
371
+ from rsa.asn1 import OpenSSLPubKey
372
+ from pyasn1.codec.der import decoder
373
+ from pyasn1.type import univ
374
+
375
+ (keyinfo, _) = decoder.decode(keyfile, asn1Spec=OpenSSLPubKey())
376
+
377
+ if keyinfo["header"]["oid"] != univ.ObjectIdentifier("1.2.840.113549.1.1.1"):
378
+ raise TypeError("This is not a DER-encoded OpenSSL-compatible public key")
379
+
380
+ return cls._load_pkcs1_der(keyinfo["key"][1:])
381
+
382
+
383
+ class PrivateKey(AbstractKey):
384
+ """Represents a private RSA key.
385
+
386
+ This key is also known as the 'decryption key'. It contains the 'n', 'e',
387
+ 'd', 'p', 'q' and other values.
388
+
389
+ Supports attributes as well as dictionary-like access. Attribute access is
390
+ faster, though.
391
+
392
+ >>> PrivateKey(3247, 65537, 833, 191, 17)
393
+ PrivateKey(3247, 65537, 833, 191, 17)
394
+
395
+ exp1, exp2 and coef will be calculated:
396
+
397
+ >>> pk = PrivateKey(3727264081, 65537, 3349121513, 65063, 57287)
398
+ >>> pk.exp1
399
+ 55063
400
+ >>> pk.exp2
401
+ 10095
402
+ >>> pk.coef
403
+ 50797
404
+
405
+ """
406
+
407
+ __slots__ = ("d", "p", "q", "exp1", "exp2", "coef")
408
+
409
+ def __init__(self, n: int, e: int, d: int, p: int, q: int) -> None:
410
+ AbstractKey.__init__(self, n, e)
411
+ self.d = d
412
+ self.p = p
413
+ self.q = q
414
+
415
+ # Calculate exponents and coefficient.
416
+ self.exp1 = int(d % (p - 1))
417
+ self.exp2 = int(d % (q - 1))
418
+ self.coef = rsa.common.inverse(q, p)
419
+
420
+ def __getitem__(self, key: str) -> int:
421
+ return getattr(self, key)
422
+
423
+ def __repr__(self) -> str:
424
+ return "PrivateKey(%i, %i, %i, %i, %i)" % (
425
+ self.n,
426
+ self.e,
427
+ self.d,
428
+ self.p,
429
+ self.q,
430
+ )
431
+
432
+ def __getstate__(self) -> typing.Tuple[int, int, int, int, int, int, int, int]:
433
+ """Returns the key as tuple for pickling."""
434
+ return self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef
435
+
436
+ def __setstate__(self, state: typing.Tuple[int, int, int, int, int, int, int, int]) -> None:
437
+ """Sets the key from tuple."""
438
+ self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef = state
439
+ AbstractKey.__init__(self, self.n, self.e)
440
+
441
+ def __eq__(self, other: typing.Any) -> bool:
442
+ if other is None:
443
+ return False
444
+
445
+ if not isinstance(other, PrivateKey):
446
+ return False
447
+
448
+ return (
449
+ self.n == other.n
450
+ and self.e == other.e
451
+ and self.d == other.d
452
+ and self.p == other.p
453
+ and self.q == other.q
454
+ and self.exp1 == other.exp1
455
+ and self.exp2 == other.exp2
456
+ and self.coef == other.coef
457
+ )
458
+
459
+ def __ne__(self, other: typing.Any) -> bool:
460
+ return not (self == other)
461
+
462
+ def __hash__(self) -> int:
463
+ return hash((self.n, self.e, self.d, self.p, self.q, self.exp1, self.exp2, self.coef))
464
+
465
+ def blinded_decrypt(self, encrypted: int) -> int:
466
+ """Decrypts the message using blinding to prevent side-channel attacks.
467
+
468
+ :param encrypted: the encrypted message
469
+ :type encrypted: int
470
+
471
+ :returns: the decrypted message
472
+ :rtype: int
473
+ """
474
+
475
+ # Blinding and un-blinding should be using the same factor
476
+ blinded, blindfac_inverse = self.blind(encrypted)
477
+
478
+ # Instead of using the core functionality, use the Chinese Remainder
479
+ # Theorem and be 2-4x faster. This the same as:
480
+ #
481
+ # decrypted = rsa.core.decrypt_int(blinded, self.d, self.n)
482
+ s1 = pow(blinded, self.exp1, self.p)
483
+ s2 = pow(blinded, self.exp2, self.q)
484
+ h = ((s1 - s2) * self.coef) % self.p
485
+ decrypted = s2 + self.q * h
486
+
487
+ return self.unblind(decrypted, blindfac_inverse)
488
+
489
+ def blinded_encrypt(self, message: int) -> int:
490
+ """Encrypts the message using blinding to prevent side-channel attacks.
491
+
492
+ :param message: the message to encrypt
493
+ :type message: int
494
+
495
+ :returns: the encrypted message
496
+ :rtype: int
497
+ """
498
+
499
+ blinded, blindfac_inverse = self.blind(message)
500
+ encrypted = rsa.core.encrypt_int(blinded, self.d, self.n)
501
+ return self.unblind(encrypted, blindfac_inverse)
502
+
503
+ @classmethod
504
+ def _load_pkcs1_der(cls, keyfile: bytes) -> "PrivateKey":
505
+ """Loads a key in PKCS#1 DER format.
506
+
507
+ :param keyfile: contents of a DER-encoded file that contains the private
508
+ key.
509
+ :type keyfile: bytes
510
+ :return: a PrivateKey object
511
+
512
+ First let's construct a DER encoded key:
513
+
514
+ >>> import base64
515
+ >>> b64der = 'MC4CAQACBQDeKYlRAgMBAAECBQDHn4npAgMA/icCAwDfxwIDANcXAgInbwIDAMZt'
516
+ >>> der = base64.standard_b64decode(b64der)
517
+
518
+ This loads the file:
519
+
520
+ >>> PrivateKey._load_pkcs1_der(der)
521
+ PrivateKey(3727264081, 65537, 3349121513, 65063, 57287)
522
+
523
+ """
524
+
525
+ from pyasn1.codec.der import decoder
526
+
527
+ (priv, _) = decoder.decode(keyfile)
528
+
529
+ # ASN.1 contents of DER encoded private key:
530
+ #
531
+ # RSAPrivateKey ::= SEQUENCE {
532
+ # version Version,
533
+ # modulus INTEGER, -- n
534
+ # publicExponent INTEGER, -- e
535
+ # privateExponent INTEGER, -- d
536
+ # prime1 INTEGER, -- p
537
+ # prime2 INTEGER, -- q
538
+ # exponent1 INTEGER, -- d mod (p-1)
539
+ # exponent2 INTEGER, -- d mod (q-1)
540
+ # coefficient INTEGER, -- (inverse of q) mod p
541
+ # otherPrimeInfos OtherPrimeInfos OPTIONAL
542
+ # }
543
+
544
+ if priv[0] != 0:
545
+ raise ValueError("Unable to read this file, version %s != 0" % priv[0])
546
+
547
+ as_ints = map(int, priv[1:6])
548
+ key = cls(*as_ints)
549
+
550
+ exp1, exp2, coef = map(int, priv[6:9])
551
+
552
+ if (key.exp1, key.exp2, key.coef) != (exp1, exp2, coef):
553
+ warnings.warn(
554
+ "You have provided a malformed keyfile. Either the exponents "
555
+ "or the coefficient are incorrect. Using the correct values "
556
+ "instead.",
557
+ UserWarning,
558
+ )
559
+
560
+ return key
561
+
562
+ def _save_pkcs1_der(self) -> bytes:
563
+ """Saves the private key in PKCS#1 DER format.
564
+
565
+ :returns: the DER-encoded private key.
566
+ :rtype: bytes
567
+ """
568
+
569
+ from pyasn1.type import univ, namedtype
570
+ from pyasn1.codec.der import encoder
571
+
572
+ class AsnPrivKey(univ.Sequence):
573
+ componentType = namedtype.NamedTypes(
574
+ namedtype.NamedType("version", univ.Integer()),
575
+ namedtype.NamedType("modulus", univ.Integer()),
576
+ namedtype.NamedType("publicExponent", univ.Integer()),
577
+ namedtype.NamedType("privateExponent", univ.Integer()),
578
+ namedtype.NamedType("prime1", univ.Integer()),
579
+ namedtype.NamedType("prime2", univ.Integer()),
580
+ namedtype.NamedType("exponent1", univ.Integer()),
581
+ namedtype.NamedType("exponent2", univ.Integer()),
582
+ namedtype.NamedType("coefficient", univ.Integer()),
583
+ )
584
+
585
+ # Create the ASN object
586
+ asn_key = AsnPrivKey()
587
+ asn_key.setComponentByName("version", 0)
588
+ asn_key.setComponentByName("modulus", self.n)
589
+ asn_key.setComponentByName("publicExponent", self.e)
590
+ asn_key.setComponentByName("privateExponent", self.d)
591
+ asn_key.setComponentByName("prime1", self.p)
592
+ asn_key.setComponentByName("prime2", self.q)
593
+ asn_key.setComponentByName("exponent1", self.exp1)
594
+ asn_key.setComponentByName("exponent2", self.exp2)
595
+ asn_key.setComponentByName("coefficient", self.coef)
596
+
597
+ return encoder.encode(asn_key)
598
+
599
+ @classmethod
600
+ def _load_pkcs1_pem(cls, keyfile: bytes) -> "PrivateKey":
601
+ """Loads a PKCS#1 PEM-encoded private key file.
602
+
603
+ The contents of the file before the "-----BEGIN RSA PRIVATE KEY-----" and
604
+ after the "-----END RSA PRIVATE KEY-----" lines is ignored.
605
+
606
+ :param keyfile: contents of a PEM-encoded file that contains the private
607
+ key.
608
+ :type keyfile: bytes
609
+ :return: a PrivateKey object
610
+ """
611
+
612
+ der = rsa.pem.load_pem(keyfile, b"RSA PRIVATE KEY")
613
+ return cls._load_pkcs1_der(der)
614
+
615
+ def _save_pkcs1_pem(self) -> bytes:
616
+ """Saves a PKCS#1 PEM-encoded private key file.
617
+
618
+ :return: contents of a PEM-encoded file that contains the private key.
619
+ :rtype: bytes
620
+ """
621
+
622
+ der = self._save_pkcs1_der()
623
+ return rsa.pem.save_pem(der, b"RSA PRIVATE KEY")
624
+
625
+
626
+ def find_p_q(
627
+ nbits: int,
628
+ getprime_func: typing.Callable[[int], int] = rsa.prime.getprime,
629
+ accurate: bool = True,
630
+ ) -> typing.Tuple[int, int]:
631
+ """Returns a tuple of two different primes of nbits bits each.
632
+
633
+ The resulting p * q has exactly 2 * nbits bits, and the returned p and q
634
+ will not be equal.
635
+
636
+ :param nbits: the number of bits in each of p and q.
637
+ :param getprime_func: the getprime function, defaults to
638
+ :py:func:`rsa.prime.getprime`.
639
+
640
+ *Introduced in Python-RSA 3.1*
641
+
642
+ :param accurate: whether to enable accurate mode or not.
643
+ :returns: (p, q), where p > q
644
+
645
+ >>> (p, q) = find_p_q(128)
646
+ >>> from rsa import common
647
+ >>> common.bit_size(p * q)
648
+ 256
649
+
650
+ When not in accurate mode, the number of bits can be slightly less
651
+
652
+ >>> (p, q) = find_p_q(128, accurate=False)
653
+ >>> from rsa import common
654
+ >>> common.bit_size(p * q) <= 256
655
+ True
656
+ >>> common.bit_size(p * q) > 240
657
+ True
658
+
659
+ """
660
+
661
+ total_bits = nbits * 2
662
+
663
+ # Make sure that p and q aren't too close or the factoring programs can
664
+ # factor n.
665
+ shift = nbits // 16
666
+ pbits = nbits + shift
667
+ qbits = nbits - shift
668
+
669
+ # Choose the two initial primes
670
+ p = getprime_func(pbits)
671
+ q = getprime_func(qbits)
672
+
673
+ def is_acceptable(p: int, q: int) -> bool:
674
+ """Returns True iff p and q are acceptable:
675
+
676
+ - p and q differ
677
+ - (p * q) has the right nr of bits (when accurate=True)
678
+ """
679
+
680
+ if p == q:
681
+ return False
682
+
683
+ if not accurate:
684
+ return True
685
+
686
+ # Make sure we have just the right amount of bits
687
+ found_size = rsa.common.bit_size(p * q)
688
+ return total_bits == found_size
689
+
690
+ # Keep choosing other primes until they match our requirements.
691
+ change_p = False
692
+ while not is_acceptable(p, q):
693
+ # Change p on one iteration and q on the other
694
+ if change_p:
695
+ p = getprime_func(pbits)
696
+ else:
697
+ q = getprime_func(qbits)
698
+
699
+ change_p = not change_p
700
+
701
+ # We want p > q as described on
702
+ # http://www.di-mgt.com.au/rsa_alg.html#crt
703
+ return max(p, q), min(p, q)
704
+
705
+
706
+ def calculate_keys_custom_exponent(p: int, q: int, exponent: int) -> typing.Tuple[int, int]:
707
+ """Calculates an encryption and a decryption key given p, q and an exponent,
708
+ and returns them as a tuple (e, d)
709
+
710
+ :param p: the first large prime
711
+ :param q: the second large prime
712
+ :param exponent: the exponent for the key; only change this if you know
713
+ what you're doing, as the exponent influences how difficult your
714
+ private key can be cracked. A very common choice for e is 65537.
715
+ :type exponent: int
716
+
717
+ """
718
+
719
+ phi_n = (p - 1) * (q - 1)
720
+
721
+ try:
722
+ d = rsa.common.inverse(exponent, phi_n)
723
+ except rsa.common.NotRelativePrimeError as ex:
724
+ raise rsa.common.NotRelativePrimeError(
725
+ exponent,
726
+ phi_n,
727
+ ex.d,
728
+ msg="e (%d) and phi_n (%d) are not relatively prime (divider=%i)"
729
+ % (exponent, phi_n, ex.d),
730
+ ) from ex
731
+
732
+ if (exponent * d) % phi_n != 1:
733
+ raise ValueError(
734
+ "e (%d) and d (%d) are not mult. inv. modulo " "phi_n (%d)" % (exponent, d, phi_n)
735
+ )
736
+
737
+ return exponent, d
738
+
739
+
740
+ def calculate_keys(p: int, q: int) -> typing.Tuple[int, int]:
741
+ """Calculates an encryption and a decryption key given p and q, and
742
+ returns them as a tuple (e, d)
743
+
744
+ :param p: the first large prime
745
+ :param q: the second large prime
746
+
747
+ :return: tuple (e, d) with the encryption and decryption exponents.
748
+ """
749
+
750
+ return calculate_keys_custom_exponent(p, q, DEFAULT_EXPONENT)
751
+
752
+
753
+ def gen_keys(
754
+ nbits: int,
755
+ getprime_func: typing.Callable[[int], int],
756
+ accurate: bool = True,
757
+ exponent: int = DEFAULT_EXPONENT,
758
+ ) -> typing.Tuple[int, int, int, int]:
759
+ """Generate RSA keys of nbits bits. Returns (p, q, e, d).
760
+
761
+ Note: this can take a long time, depending on the key size.
762
+
763
+ :param nbits: the total number of bits in ``p`` and ``q``. Both ``p`` and
764
+ ``q`` will use ``nbits/2`` bits.
765
+ :param getprime_func: either :py:func:`rsa.prime.getprime` or a function
766
+ with similar signature.
767
+ :param exponent: the exponent for the key; only change this if you know
768
+ what you're doing, as the exponent influences how difficult your
769
+ private key can be cracked. A very common choice for e is 65537.
770
+ :type exponent: int
771
+ """
772
+
773
+ # Regenerate p and q values, until calculate_keys doesn't raise a
774
+ # ValueError.
775
+ while True:
776
+ (p, q) = find_p_q(nbits // 2, getprime_func, accurate)
777
+ try:
778
+ (e, d) = calculate_keys_custom_exponent(p, q, exponent=exponent)
779
+ break
780
+ except ValueError:
781
+ pass
782
+
783
+ return p, q, e, d
784
+
785
+
786
+ def newkeys(
787
+ nbits: int,
788
+ accurate: bool = True,
789
+ poolsize: int = 1,
790
+ exponent: int = DEFAULT_EXPONENT,
791
+ ) -> typing.Tuple[PublicKey, PrivateKey]:
792
+ """Generates public and private keys, and returns them as (pub, priv).
793
+
794
+ The public key is also known as the 'encryption key', and is a
795
+ :py:class:`rsa.PublicKey` object. The private key is also known as the
796
+ 'decryption key' and is a :py:class:`rsa.PrivateKey` object.
797
+
798
+ :param nbits: the number of bits required to store ``n = p*q``.
799
+ :param accurate: when True, ``n`` will have exactly the number of bits you
800
+ asked for. However, this makes key generation much slower. When False,
801
+ `n`` may have slightly less bits.
802
+ :param poolsize: the number of processes to use to generate the prime
803
+ numbers. If set to a number > 1, a parallel algorithm will be used.
804
+ This requires Python 2.6 or newer.
805
+ :param exponent: the exponent for the key; only change this if you know
806
+ what you're doing, as the exponent influences how difficult your
807
+ private key can be cracked. A very common choice for e is 65537.
808
+ :type exponent: int
809
+
810
+ :returns: a tuple (:py:class:`rsa.PublicKey`, :py:class:`rsa.PrivateKey`)
811
+
812
+ The ``poolsize`` parameter was added in *Python-RSA 3.1* and requires
813
+ Python 2.6 or newer.
814
+
815
+ """
816
+
817
+ if nbits < 16:
818
+ raise ValueError("Key too small")
819
+
820
+ if poolsize < 1:
821
+ raise ValueError("Pool size (%i) should be >= 1" % poolsize)
822
+
823
+ # Determine which getprime function to use
824
+ if poolsize > 1:
825
+ from rsa import parallel
826
+
827
+ def getprime_func(nbits: int) -> int:
828
+ return parallel.getprime(nbits, poolsize=poolsize)
829
+
830
+ else:
831
+ getprime_func = rsa.prime.getprime
832
+
833
+ # Generate the key components
834
+ (p, q, e, d) = gen_keys(nbits, getprime_func, accurate=accurate, exponent=exponent)
835
+
836
+ # Create the key objects
837
+ n = p * q
838
+
839
+ return (PublicKey(n, e), PrivateKey(n, e, d, p, q))
840
+
841
+
842
+ __all__ = ["PublicKey", "PrivateKey", "newkeys"]
843
+
844
+ if __name__ == "__main__":
845
+ import doctest
846
+
847
+ try:
848
+ for count in range(100):
849
+ (failures, tests) = doctest.testmod()
850
+ if failures:
851
+ break
852
+
853
+ if (count % 10 == 0 and count) or count == 1:
854
+ print("%i times" % count)
855
+ except KeyboardInterrupt:
856
+ print("Aborted")
857
+ else:
858
+ print("Doctests done")
.venv/lib/python3.11/site-packages/rsa/parallel.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Functions for parallel computation on multiple cores.
16
+
17
+ Introduced in Python-RSA 3.1.
18
+
19
+ .. note::
20
+
21
+ Requires Python 2.6 or newer.
22
+
23
+ """
24
+
25
+ import multiprocessing as mp
26
+ from multiprocessing.connection import Connection
27
+
28
+ import rsa.prime
29
+ import rsa.randnum
30
+
31
+
32
+ def _find_prime(nbits: int, pipe: Connection) -> None:
33
+ while True:
34
+ integer = rsa.randnum.read_random_odd_int(nbits)
35
+
36
+ # Test for primeness
37
+ if rsa.prime.is_prime(integer):
38
+ pipe.send(integer)
39
+ return
40
+
41
+
42
+ def getprime(nbits: int, poolsize: int) -> int:
43
+ """Returns a prime number that can be stored in 'nbits' bits.
44
+
45
+ Works in multiple threads at the same time.
46
+
47
+ >>> p = getprime(128, 3)
48
+ >>> rsa.prime.is_prime(p-1)
49
+ False
50
+ >>> rsa.prime.is_prime(p)
51
+ True
52
+ >>> rsa.prime.is_prime(p+1)
53
+ False
54
+
55
+ >>> from rsa import common
56
+ >>> common.bit_size(p) == 128
57
+ True
58
+
59
+ """
60
+
61
+ (pipe_recv, pipe_send) = mp.Pipe(duplex=False)
62
+
63
+ # Create processes
64
+ try:
65
+ procs = [mp.Process(target=_find_prime, args=(nbits, pipe_send)) for _ in range(poolsize)]
66
+ # Start processes
67
+ for p in procs:
68
+ p.start()
69
+
70
+ result = pipe_recv.recv()
71
+ finally:
72
+ pipe_recv.close()
73
+ pipe_send.close()
74
+
75
+ # Terminate processes
76
+ for p in procs:
77
+ p.terminate()
78
+
79
+ return result
80
+
81
+
82
+ __all__ = ["getprime"]
83
+
84
+ if __name__ == "__main__":
85
+ print("Running doctests 1000x or until failure")
86
+ import doctest
87
+
88
+ for count in range(100):
89
+ (failures, tests) = doctest.testmod()
90
+ if failures:
91
+ break
92
+
93
+ if count % 10 == 0 and count:
94
+ print("%i times" % count)
95
+
96
+ print("Doctests done")
.venv/lib/python3.11/site-packages/rsa/pem.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Functions that load and write PEM-encoded files."""
16
+
17
+ import base64
18
+ import typing
19
+
20
+ # Should either be ASCII strings or bytes.
21
+ FlexiText = typing.Union[str, bytes]
22
+
23
+
24
+ def _markers(pem_marker: FlexiText) -> typing.Tuple[bytes, bytes]:
25
+ """
26
+ Returns the start and end PEM markers, as bytes.
27
+ """
28
+
29
+ if not isinstance(pem_marker, bytes):
30
+ pem_marker = pem_marker.encode("ascii")
31
+
32
+ return (
33
+ b"-----BEGIN " + pem_marker + b"-----",
34
+ b"-----END " + pem_marker + b"-----",
35
+ )
36
+
37
+
38
+ def _pem_lines(contents: bytes, pem_start: bytes, pem_end: bytes) -> typing.Iterator[bytes]:
39
+ """Generator over PEM lines between pem_start and pem_end."""
40
+
41
+ in_pem_part = False
42
+ seen_pem_start = False
43
+
44
+ for line in contents.splitlines():
45
+ line = line.strip()
46
+
47
+ # Skip empty lines
48
+ if not line:
49
+ continue
50
+
51
+ # Handle start marker
52
+ if line == pem_start:
53
+ if in_pem_part:
54
+ raise ValueError('Seen start marker "%r" twice' % pem_start)
55
+
56
+ in_pem_part = True
57
+ seen_pem_start = True
58
+ continue
59
+
60
+ # Skip stuff before first marker
61
+ if not in_pem_part:
62
+ continue
63
+
64
+ # Handle end marker
65
+ if in_pem_part and line == pem_end:
66
+ in_pem_part = False
67
+ break
68
+
69
+ # Load fields
70
+ if b":" in line:
71
+ continue
72
+
73
+ yield line
74
+
75
+ # Do some sanity checks
76
+ if not seen_pem_start:
77
+ raise ValueError('No PEM start marker "%r" found' % pem_start)
78
+
79
+ if in_pem_part:
80
+ raise ValueError('No PEM end marker "%r" found' % pem_end)
81
+
82
+
83
+ def load_pem(contents: FlexiText, pem_marker: FlexiText) -> bytes:
84
+ """Loads a PEM file.
85
+
86
+ :param contents: the contents of the file to interpret
87
+ :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
88
+ when your file has '-----BEGIN RSA PRIVATE KEY-----' and
89
+ '-----END RSA PRIVATE KEY-----' markers.
90
+
91
+ :return: the base64-decoded content between the start and end markers.
92
+
93
+ @raise ValueError: when the content is invalid, for example when the start
94
+ marker cannot be found.
95
+
96
+ """
97
+
98
+ # We want bytes, not text. If it's text, it can be converted to ASCII bytes.
99
+ if not isinstance(contents, bytes):
100
+ contents = contents.encode("ascii")
101
+
102
+ (pem_start, pem_end) = _markers(pem_marker)
103
+ pem_lines = [line for line in _pem_lines(contents, pem_start, pem_end)]
104
+
105
+ # Base64-decode the contents
106
+ pem = b"".join(pem_lines)
107
+ return base64.standard_b64decode(pem)
108
+
109
+
110
+ def save_pem(contents: bytes, pem_marker: FlexiText) -> bytes:
111
+ """Saves a PEM file.
112
+
113
+ :param contents: the contents to encode in PEM format
114
+ :param pem_marker: the marker of the PEM content, such as 'RSA PRIVATE KEY'
115
+ when your file has '-----BEGIN RSA PRIVATE KEY-----' and
116
+ '-----END RSA PRIVATE KEY-----' markers.
117
+
118
+ :return: the base64-encoded content between the start and end markers, as bytes.
119
+
120
+ """
121
+
122
+ (pem_start, pem_end) = _markers(pem_marker)
123
+
124
+ b64 = base64.standard_b64encode(contents).replace(b"\n", b"")
125
+ pem_lines = [pem_start]
126
+
127
+ for block_start in range(0, len(b64), 64):
128
+ block = b64[block_start : block_start + 64]
129
+ pem_lines.append(block)
130
+
131
+ pem_lines.append(pem_end)
132
+ pem_lines.append(b"")
133
+
134
+ return b"\n".join(pem_lines)
.venv/lib/python3.11/site-packages/rsa/pkcs1.py ADDED
@@ -0,0 +1,485 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Functions for PKCS#1 version 1.5 encryption and signing
16
+
17
+ This module implements certain functionality from PKCS#1 version 1.5. For a
18
+ very clear example, read http://www.di-mgt.com.au/rsa_alg.html#pkcs1schemes
19
+
20
+ At least 8 bytes of random padding is used when encrypting a message. This makes
21
+ these methods much more secure than the ones in the ``rsa`` module.
22
+
23
+ WARNING: this module leaks information when decryption fails. The exceptions
24
+ that are raised contain the Python traceback information, which can be used to
25
+ deduce where in the process the failure occurred. DO NOT PASS SUCH INFORMATION
26
+ to your users.
27
+ """
28
+
29
+ import hashlib
30
+ import os
31
+ import sys
32
+ import typing
33
+ from hmac import compare_digest
34
+
35
+ from . import common, transform, core, key
36
+
37
+ if typing.TYPE_CHECKING:
38
+ HashType = hashlib._Hash
39
+ else:
40
+ HashType = typing.Any
41
+
42
+ # ASN.1 codes that describe the hash algorithm used.
43
+ HASH_ASN1 = {
44
+ "MD5": b"\x30\x20\x30\x0c\x06\x08\x2a\x86\x48\x86\xf7\x0d\x02\x05\x05\x00\x04\x10",
45
+ "SHA-1": b"\x30\x21\x30\x09\x06\x05\x2b\x0e\x03\x02\x1a\x05\x00\x04\x14",
46
+ "SHA-224": b"\x30\x2d\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x04\x05\x00\x04\x1c",
47
+ "SHA-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x01\x05\x00\x04\x20",
48
+ "SHA-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x02\x05\x00\x04\x30",
49
+ "SHA-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x03\x05\x00\x04\x40",
50
+ }
51
+
52
+ HASH_METHODS: typing.Dict[str, typing.Callable[[], HashType]] = {
53
+ "MD5": hashlib.md5,
54
+ "SHA-1": hashlib.sha1,
55
+ "SHA-224": hashlib.sha224,
56
+ "SHA-256": hashlib.sha256,
57
+ "SHA-384": hashlib.sha384,
58
+ "SHA-512": hashlib.sha512,
59
+ }
60
+ """Hash methods supported by this library."""
61
+
62
+
63
+ if sys.version_info >= (3, 6):
64
+ # Python 3.6 introduced SHA3 support.
65
+ HASH_ASN1.update(
66
+ {
67
+ "SHA3-256": b"\x30\x31\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x08\x05\x00\x04\x20",
68
+ "SHA3-384": b"\x30\x41\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x09\x05\x00\x04\x30",
69
+ "SHA3-512": b"\x30\x51\x30\x0d\x06\x09\x60\x86\x48\x01\x65\x03\x04\x02\x0a\x05\x00\x04\x40",
70
+ }
71
+ )
72
+
73
+ HASH_METHODS.update(
74
+ {
75
+ "SHA3-256": hashlib.sha3_256,
76
+ "SHA3-384": hashlib.sha3_384,
77
+ "SHA3-512": hashlib.sha3_512,
78
+ }
79
+ )
80
+
81
+
82
+ class CryptoError(Exception):
83
+ """Base class for all exceptions in this module."""
84
+
85
+
86
+ class DecryptionError(CryptoError):
87
+ """Raised when decryption fails."""
88
+
89
+
90
+ class VerificationError(CryptoError):
91
+ """Raised when verification fails."""
92
+
93
+
94
+ def _pad_for_encryption(message: bytes, target_length: int) -> bytes:
95
+ r"""Pads the message for encryption, returning the padded message.
96
+
97
+ :return: 00 02 RANDOM_DATA 00 MESSAGE
98
+
99
+ >>> block = _pad_for_encryption(b'hello', 16)
100
+ >>> len(block)
101
+ 16
102
+ >>> block[0:2]
103
+ b'\x00\x02'
104
+ >>> block[-6:]
105
+ b'\x00hello'
106
+
107
+ """
108
+
109
+ max_msglength = target_length - 11
110
+ msglength = len(message)
111
+
112
+ if msglength > max_msglength:
113
+ raise OverflowError(
114
+ "%i bytes needed for message, but there is only"
115
+ " space for %i" % (msglength, max_msglength)
116
+ )
117
+
118
+ # Get random padding
119
+ padding = b""
120
+ padding_length = target_length - msglength - 3
121
+
122
+ # We remove 0-bytes, so we'll end up with less padding than we've asked for,
123
+ # so keep adding data until we're at the correct length.
124
+ while len(padding) < padding_length:
125
+ needed_bytes = padding_length - len(padding)
126
+
127
+ # Always read at least 8 bytes more than we need, and trim off the rest
128
+ # after removing the 0-bytes. This increases the chance of getting
129
+ # enough bytes, especially when needed_bytes is small
130
+ new_padding = os.urandom(needed_bytes + 5)
131
+ new_padding = new_padding.replace(b"\x00", b"")
132
+ padding = padding + new_padding[:needed_bytes]
133
+
134
+ assert len(padding) == padding_length
135
+
136
+ return b"".join([b"\x00\x02", padding, b"\x00", message])
137
+
138
+
139
+ def _pad_for_signing(message: bytes, target_length: int) -> bytes:
140
+ r"""Pads the message for signing, returning the padded message.
141
+
142
+ The padding is always a repetition of FF bytes.
143
+
144
+ :return: 00 01 PADDING 00 MESSAGE
145
+
146
+ >>> block = _pad_for_signing(b'hello', 16)
147
+ >>> len(block)
148
+ 16
149
+ >>> block[0:2]
150
+ b'\x00\x01'
151
+ >>> block[-6:]
152
+ b'\x00hello'
153
+ >>> block[2:-6]
154
+ b'\xff\xff\xff\xff\xff\xff\xff\xff'
155
+
156
+ """
157
+
158
+ max_msglength = target_length - 11
159
+ msglength = len(message)
160
+
161
+ if msglength > max_msglength:
162
+ raise OverflowError(
163
+ "%i bytes needed for message, but there is only"
164
+ " space for %i" % (msglength, max_msglength)
165
+ )
166
+
167
+ padding_length = target_length - msglength - 3
168
+
169
+ return b"".join([b"\x00\x01", padding_length * b"\xff", b"\x00", message])
170
+
171
+
172
+ def encrypt(message: bytes, pub_key: key.PublicKey) -> bytes:
173
+ """Encrypts the given message using PKCS#1 v1.5
174
+
175
+ :param message: the message to encrypt. Must be a byte string no longer than
176
+ ``k-11`` bytes, where ``k`` is the number of bytes needed to encode
177
+ the ``n`` component of the public key.
178
+ :param pub_key: the :py:class:`rsa.PublicKey` to encrypt with.
179
+ :raise OverflowError: when the message is too large to fit in the padded
180
+ block.
181
+
182
+ >>> from rsa import key, common
183
+ >>> (pub_key, priv_key) = key.newkeys(256)
184
+ >>> message = b'hello'
185
+ >>> crypto = encrypt(message, pub_key)
186
+
187
+ The crypto text should be just as long as the public key 'n' component:
188
+
189
+ >>> len(crypto) == common.byte_size(pub_key.n)
190
+ True
191
+
192
+ """
193
+
194
+ keylength = common.byte_size(pub_key.n)
195
+ padded = _pad_for_encryption(message, keylength)
196
+
197
+ payload = transform.bytes2int(padded)
198
+ encrypted = core.encrypt_int(payload, pub_key.e, pub_key.n)
199
+ block = transform.int2bytes(encrypted, keylength)
200
+
201
+ return block
202
+
203
+
204
+ def decrypt(crypto: bytes, priv_key: key.PrivateKey) -> bytes:
205
+ r"""Decrypts the given message using PKCS#1 v1.5
206
+
207
+ The decryption is considered 'failed' when the resulting cleartext doesn't
208
+ start with the bytes 00 02, or when the 00 byte between the padding and
209
+ the message cannot be found.
210
+
211
+ :param crypto: the crypto text as returned by :py:func:`rsa.encrypt`
212
+ :param priv_key: the :py:class:`rsa.PrivateKey` to decrypt with.
213
+ :raise DecryptionError: when the decryption fails. No details are given as
214
+ to why the code thinks the decryption fails, as this would leak
215
+ information about the private key.
216
+
217
+
218
+ >>> import rsa
219
+ >>> (pub_key, priv_key) = rsa.newkeys(256)
220
+
221
+ It works with strings:
222
+
223
+ >>> crypto = encrypt(b'hello', pub_key)
224
+ >>> decrypt(crypto, priv_key)
225
+ b'hello'
226
+
227
+ And with binary data:
228
+
229
+ >>> crypto = encrypt(b'\x00\x00\x00\x00\x01', pub_key)
230
+ >>> decrypt(crypto, priv_key)
231
+ b'\x00\x00\x00\x00\x01'
232
+
233
+ Altering the encrypted information will *likely* cause a
234
+ :py:class:`rsa.pkcs1.DecryptionError`. If you want to be *sure*, use
235
+ :py:func:`rsa.sign`.
236
+
237
+
238
+ .. warning::
239
+
240
+ Never display the stack trace of a
241
+ :py:class:`rsa.pkcs1.DecryptionError` exception. It shows where in the
242
+ code the exception occurred, and thus leaks information about the key.
243
+ It's only a tiny bit of information, but every bit makes cracking the
244
+ keys easier.
245
+
246
+ >>> crypto = encrypt(b'hello', pub_key)
247
+ >>> crypto = crypto[0:5] + b'X' + crypto[6:] # change a byte
248
+ >>> decrypt(crypto, priv_key)
249
+ Traceback (most recent call last):
250
+ ...
251
+ rsa.pkcs1.DecryptionError: Decryption failed
252
+
253
+ """
254
+
255
+ blocksize = common.byte_size(priv_key.n)
256
+ encrypted = transform.bytes2int(crypto)
257
+ decrypted = priv_key.blinded_decrypt(encrypted)
258
+ cleartext = transform.int2bytes(decrypted, blocksize)
259
+
260
+ # Detect leading zeroes in the crypto. These are not reflected in the
261
+ # encrypted value (as leading zeroes do not influence the value of an
262
+ # integer). This fixes CVE-2020-13757.
263
+ if len(crypto) > blocksize:
264
+ # This is operating on public information, so doesn't need to be constant-time.
265
+ raise DecryptionError("Decryption failed")
266
+
267
+ # If we can't find the cleartext marker, decryption failed.
268
+ cleartext_marker_bad = not compare_digest(cleartext[:2], b"\x00\x02")
269
+
270
+ # Find the 00 separator between the padding and the message
271
+ sep_idx = cleartext.find(b"\x00", 2)
272
+
273
+ # sep_idx indicates the position of the `\x00` separator that separates the
274
+ # padding from the actual message. The padding should be at least 8 bytes
275
+ # long (see https://tools.ietf.org/html/rfc8017#section-7.2.2 step 3), which
276
+ # means the separator should be at least at index 10 (because of the
277
+ # `\x00\x02` marker that precedes it).
278
+ sep_idx_bad = sep_idx < 10
279
+
280
+ anything_bad = cleartext_marker_bad | sep_idx_bad
281
+ if anything_bad:
282
+ raise DecryptionError("Decryption failed")
283
+
284
+ return cleartext[sep_idx + 1 :]
285
+
286
+
287
+ def sign_hash(hash_value: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
288
+ """Signs a precomputed hash with the private key.
289
+
290
+ Hashes the message, then signs the hash with the given key. This is known
291
+ as a "detached signature", because the message itself isn't altered.
292
+
293
+ :param hash_value: A precomputed hash to sign (ignores message).
294
+ :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
295
+ :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
296
+ 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
297
+ :return: a message signature block.
298
+ :raise OverflowError: if the private key is too small to contain the
299
+ requested hash.
300
+
301
+ """
302
+
303
+ # Get the ASN1 code for this hash method
304
+ if hash_method not in HASH_ASN1:
305
+ raise ValueError("Invalid hash method: %s" % hash_method)
306
+ asn1code = HASH_ASN1[hash_method]
307
+
308
+ # Encrypt the hash with the private key
309
+ cleartext = asn1code + hash_value
310
+ keylength = common.byte_size(priv_key.n)
311
+ padded = _pad_for_signing(cleartext, keylength)
312
+
313
+ payload = transform.bytes2int(padded)
314
+ encrypted = priv_key.blinded_encrypt(payload)
315
+ block = transform.int2bytes(encrypted, keylength)
316
+
317
+ return block
318
+
319
+
320
+ def sign(message: bytes, priv_key: key.PrivateKey, hash_method: str) -> bytes:
321
+ """Signs the message with the private key.
322
+
323
+ Hashes the message, then signs the hash with the given key. This is known
324
+ as a "detached signature", because the message itself isn't altered.
325
+
326
+ :param message: the message to sign. Can be an 8-bit string or a file-like
327
+ object. If ``message`` has a ``read()`` method, it is assumed to be a
328
+ file-like object.
329
+ :param priv_key: the :py:class:`rsa.PrivateKey` to sign with
330
+ :param hash_method: the hash method used on the message. Use 'MD5', 'SHA-1',
331
+ 'SHA-224', SHA-256', 'SHA-384' or 'SHA-512'.
332
+ :return: a message signature block.
333
+ :raise OverflowError: if the private key is too small to contain the
334
+ requested hash.
335
+
336
+ """
337
+
338
+ msg_hash = compute_hash(message, hash_method)
339
+ return sign_hash(msg_hash, priv_key, hash_method)
340
+
341
+
342
+ def verify(message: bytes, signature: bytes, pub_key: key.PublicKey) -> str:
343
+ """Verifies that the signature matches the message.
344
+
345
+ The hash method is detected automatically from the signature.
346
+
347
+ :param message: the signed message. Can be an 8-bit string or a file-like
348
+ object. If ``message`` has a ``read()`` method, it is assumed to be a
349
+ file-like object.
350
+ :param signature: the signature block, as created with :py:func:`rsa.sign`.
351
+ :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
352
+ :raise VerificationError: when the signature doesn't match the message.
353
+ :returns: the name of the used hash.
354
+
355
+ """
356
+
357
+ keylength = common.byte_size(pub_key.n)
358
+ encrypted = transform.bytes2int(signature)
359
+ decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
360
+ clearsig = transform.int2bytes(decrypted, keylength)
361
+
362
+ # Get the hash method
363
+ method_name = _find_method_hash(clearsig)
364
+ message_hash = compute_hash(message, method_name)
365
+
366
+ # Reconstruct the expected padded hash
367
+ cleartext = HASH_ASN1[method_name] + message_hash
368
+ expected = _pad_for_signing(cleartext, keylength)
369
+
370
+ if len(signature) != keylength:
371
+ raise VerificationError("Verification failed")
372
+
373
+ # Compare with the signed one
374
+ if expected != clearsig:
375
+ raise VerificationError("Verification failed")
376
+
377
+ return method_name
378
+
379
+
380
+ def find_signature_hash(signature: bytes, pub_key: key.PublicKey) -> str:
381
+ """Returns the hash name detected from the signature.
382
+
383
+ If you also want to verify the message, use :py:func:`rsa.verify()` instead.
384
+ It also returns the name of the used hash.
385
+
386
+ :param signature: the signature block, as created with :py:func:`rsa.sign`.
387
+ :param pub_key: the :py:class:`rsa.PublicKey` of the person signing the message.
388
+ :returns: the name of the used hash.
389
+ """
390
+
391
+ keylength = common.byte_size(pub_key.n)
392
+ encrypted = transform.bytes2int(signature)
393
+ decrypted = core.decrypt_int(encrypted, pub_key.e, pub_key.n)
394
+ clearsig = transform.int2bytes(decrypted, keylength)
395
+
396
+ return _find_method_hash(clearsig)
397
+
398
+
399
+ def yield_fixedblocks(infile: typing.BinaryIO, blocksize: int) -> typing.Iterator[bytes]:
400
+ """Generator, yields each block of ``blocksize`` bytes in the input file.
401
+
402
+ :param infile: file to read and separate in blocks.
403
+ :param blocksize: block size in bytes.
404
+ :returns: a generator that yields the contents of each block
405
+ """
406
+
407
+ while True:
408
+ block = infile.read(blocksize)
409
+
410
+ read_bytes = len(block)
411
+ if read_bytes == 0:
412
+ break
413
+
414
+ yield block
415
+
416
+ if read_bytes < blocksize:
417
+ break
418
+
419
+
420
+ def compute_hash(message: typing.Union[bytes, typing.BinaryIO], method_name: str) -> bytes:
421
+ """Returns the message digest.
422
+
423
+ :param message: the signed message. Can be an 8-bit string or a file-like
424
+ object. If ``message`` has a ``read()`` method, it is assumed to be a
425
+ file-like object.
426
+ :param method_name: the hash method, must be a key of
427
+ :py:const:`rsa.pkcs1.HASH_METHODS`.
428
+
429
+ """
430
+
431
+ if method_name not in HASH_METHODS:
432
+ raise ValueError("Invalid hash method: %s" % method_name)
433
+
434
+ method = HASH_METHODS[method_name]
435
+ hasher = method()
436
+
437
+ if isinstance(message, bytes):
438
+ hasher.update(message)
439
+ else:
440
+ assert hasattr(message, "read") and hasattr(message.read, "__call__")
441
+ # read as 1K blocks
442
+ for block in yield_fixedblocks(message, 1024):
443
+ hasher.update(block)
444
+
445
+ return hasher.digest()
446
+
447
+
448
+ def _find_method_hash(clearsig: bytes) -> str:
449
+ """Finds the hash method.
450
+
451
+ :param clearsig: full padded ASN1 and hash.
452
+ :return: the used hash method.
453
+ :raise VerificationFailed: when the hash method cannot be found
454
+ """
455
+
456
+ for (hashname, asn1code) in HASH_ASN1.items():
457
+ if asn1code in clearsig:
458
+ return hashname
459
+
460
+ raise VerificationError("Verification failed")
461
+
462
+
463
+ __all__ = [
464
+ "encrypt",
465
+ "decrypt",
466
+ "sign",
467
+ "verify",
468
+ "DecryptionError",
469
+ "VerificationError",
470
+ "CryptoError",
471
+ ]
472
+
473
+ if __name__ == "__main__":
474
+ print("Running doctests 1000x or until failure")
475
+ import doctest
476
+
477
+ for count in range(1000):
478
+ (failures, tests) = doctest.testmod()
479
+ if failures:
480
+ break
481
+
482
+ if count % 100 == 0 and count:
483
+ print("%i times" % count)
484
+
485
+ print("Doctests done")
.venv/lib/python3.11/site-packages/rsa/pkcs1_v2.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Functions for PKCS#1 version 2 encryption and signing
16
+
17
+ This module implements certain functionality from PKCS#1 version 2. Main
18
+ documentation is RFC 2437: https://tools.ietf.org/html/rfc2437
19
+ """
20
+
21
+ from rsa import (
22
+ common,
23
+ pkcs1,
24
+ transform,
25
+ )
26
+
27
+
28
+ def mgf1(seed: bytes, length: int, hasher: str = "SHA-1") -> bytes:
29
+ """
30
+ MGF1 is a Mask Generation Function based on a hash function.
31
+
32
+ A mask generation function takes an octet string of variable length and a
33
+ desired output length as input, and outputs an octet string of the desired
34
+ length. The plaintext-awareness of RSAES-OAEP relies on the random nature of
35
+ the output of the mask generation function, which in turn relies on the
36
+ random nature of the underlying hash.
37
+
38
+ :param bytes seed: seed from which mask is generated, an octet string
39
+ :param int length: intended length in octets of the mask, at most 2^32(hLen)
40
+ :param str hasher: hash function (hLen denotes the length in octets of the hash
41
+ function output)
42
+
43
+ :return: mask, an octet string of length `length`
44
+ :rtype: bytes
45
+
46
+ :raise OverflowError: when `length` is too large for the specified `hasher`
47
+ :raise ValueError: when specified `hasher` is invalid
48
+ """
49
+
50
+ try:
51
+ hash_length = pkcs1.HASH_METHODS[hasher]().digest_size
52
+ except KeyError as ex:
53
+ raise ValueError(
54
+ "Invalid `hasher` specified. Please select one of: {hash_list}".format(
55
+ hash_list=", ".join(sorted(pkcs1.HASH_METHODS.keys()))
56
+ )
57
+ ) from ex
58
+
59
+ # If l > 2^32(hLen), output "mask too long" and stop.
60
+ if length > (2 ** 32 * hash_length):
61
+ raise OverflowError(
62
+ "Desired length should be at most 2**32 times the hasher's output "
63
+ "length ({hash_length} for {hasher} function)".format(
64
+ hash_length=hash_length,
65
+ hasher=hasher,
66
+ )
67
+ )
68
+
69
+ # Looping `counter` from 0 to ceil(l / hLen)-1, build `output` based on the
70
+ # hashes formed by (`seed` + C), being `C` an octet string of length 4
71
+ # generated by converting `counter` with the primitive I2OSP
72
+ output = b"".join(
73
+ pkcs1.compute_hash(
74
+ seed + transform.int2bytes(counter, fill_size=4),
75
+ method_name=hasher,
76
+ )
77
+ for counter in range(common.ceil_div(length, hash_length) + 1)
78
+ )
79
+
80
+ # Output the leading `length` octets of `output` as the octet string mask.
81
+ return output[:length]
82
+
83
+
84
+ __all__ = [
85
+ "mgf1",
86
+ ]
87
+
88
+ if __name__ == "__main__":
89
+ print("Running doctests 1000x or until failure")
90
+ import doctest
91
+
92
+ for count in range(1000):
93
+ (failures, tests) = doctest.testmod()
94
+ if failures:
95
+ break
96
+
97
+ if count % 100 == 0 and count:
98
+ print("%i times" % count)
99
+
100
+ print("Doctests done")
.venv/lib/python3.11/site-packages/rsa/prime.py ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Numerical functions related to primes.
16
+
17
+ Implementation based on the book Algorithm Design by Michael T. Goodrich and
18
+ Roberto Tamassia, 2002.
19
+ """
20
+
21
+ import rsa.common
22
+ import rsa.randnum
23
+
24
+ __all__ = ["getprime", "are_relatively_prime"]
25
+
26
+
27
+ def gcd(p: int, q: int) -> int:
28
+ """Returns the greatest common divisor of p and q
29
+
30
+ >>> gcd(48, 180)
31
+ 12
32
+ """
33
+
34
+ while q != 0:
35
+ (p, q) = (q, p % q)
36
+ return p
37
+
38
+
39
+ def get_primality_testing_rounds(number: int) -> int:
40
+ """Returns minimum number of rounds for Miller-Rabing primality testing,
41
+ based on number bitsize.
42
+
43
+ According to NIST FIPS 186-4, Appendix C, Table C.3, minimum number of
44
+ rounds of M-R testing, using an error probability of 2 ** (-100), for
45
+ different p, q bitsizes are:
46
+ * p, q bitsize: 512; rounds: 7
47
+ * p, q bitsize: 1024; rounds: 4
48
+ * p, q bitsize: 1536; rounds: 3
49
+ See: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf
50
+ """
51
+
52
+ # Calculate number bitsize.
53
+ bitsize = rsa.common.bit_size(number)
54
+ # Set number of rounds.
55
+ if bitsize >= 1536:
56
+ return 3
57
+ if bitsize >= 1024:
58
+ return 4
59
+ if bitsize >= 512:
60
+ return 7
61
+ # For smaller bitsizes, set arbitrary number of rounds.
62
+ return 10
63
+
64
+
65
+ def miller_rabin_primality_testing(n: int, k: int) -> bool:
66
+ """Calculates whether n is composite (which is always correct) or prime
67
+ (which theoretically is incorrect with error probability 4**-k), by
68
+ applying Miller-Rabin primality testing.
69
+
70
+ For reference and implementation example, see:
71
+ https://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test
72
+
73
+ :param n: Integer to be tested for primality.
74
+ :type n: int
75
+ :param k: Number of rounds (witnesses) of Miller-Rabin testing.
76
+ :type k: int
77
+ :return: False if the number is composite, True if it's probably prime.
78
+ :rtype: bool
79
+ """
80
+
81
+ # prevent potential infinite loop when d = 0
82
+ if n < 2:
83
+ return False
84
+
85
+ # Decompose (n - 1) to write it as (2 ** r) * d
86
+ # While d is even, divide it by 2 and increase the exponent.
87
+ d = n - 1
88
+ r = 0
89
+
90
+ while not (d & 1):
91
+ r += 1
92
+ d >>= 1
93
+
94
+ # Test k witnesses.
95
+ for _ in range(k):
96
+ # Generate random integer a, where 2 <= a <= (n - 2)
97
+ a = rsa.randnum.randint(n - 3) + 1
98
+
99
+ x = pow(a, d, n)
100
+ if x == 1 or x == n - 1:
101
+ continue
102
+
103
+ for _ in range(r - 1):
104
+ x = pow(x, 2, n)
105
+ if x == 1:
106
+ # n is composite.
107
+ return False
108
+ if x == n - 1:
109
+ # Exit inner loop and continue with next witness.
110
+ break
111
+ else:
112
+ # If loop doesn't break, n is composite.
113
+ return False
114
+
115
+ return True
116
+
117
+
118
+ def is_prime(number: int) -> bool:
119
+ """Returns True if the number is prime, and False otherwise.
120
+
121
+ >>> is_prime(2)
122
+ True
123
+ >>> is_prime(42)
124
+ False
125
+ >>> is_prime(41)
126
+ True
127
+ """
128
+
129
+ # Check for small numbers.
130
+ if number < 10:
131
+ return number in {2, 3, 5, 7}
132
+
133
+ # Check for even numbers.
134
+ if not (number & 1):
135
+ return False
136
+
137
+ # Calculate minimum number of rounds.
138
+ k = get_primality_testing_rounds(number)
139
+
140
+ # Run primality testing with (minimum + 1) rounds.
141
+ return miller_rabin_primality_testing(number, k + 1)
142
+
143
+
144
+ def getprime(nbits: int) -> int:
145
+ """Returns a prime number that can be stored in 'nbits' bits.
146
+
147
+ >>> p = getprime(128)
148
+ >>> is_prime(p-1)
149
+ False
150
+ >>> is_prime(p)
151
+ True
152
+ >>> is_prime(p+1)
153
+ False
154
+
155
+ >>> from rsa import common
156
+ >>> common.bit_size(p) == 128
157
+ True
158
+ """
159
+
160
+ assert nbits > 3 # the loop will hang on too small numbers
161
+
162
+ while True:
163
+ integer = rsa.randnum.read_random_odd_int(nbits)
164
+
165
+ # Test for primeness
166
+ if is_prime(integer):
167
+ return integer
168
+
169
+ # Retry if not prime
170
+
171
+
172
+ def are_relatively_prime(a: int, b: int) -> bool:
173
+ """Returns True if a and b are relatively prime, and False if they
174
+ are not.
175
+
176
+ >>> are_relatively_prime(2, 3)
177
+ True
178
+ >>> are_relatively_prime(2, 4)
179
+ False
180
+ """
181
+
182
+ d = gcd(a, b)
183
+ return d == 1
184
+
185
+
186
+ if __name__ == "__main__":
187
+ print("Running doctests 1000x or until failure")
188
+ import doctest
189
+
190
+ for count in range(1000):
191
+ (failures, tests) = doctest.testmod()
192
+ if failures:
193
+ break
194
+
195
+ if count % 100 == 0 and count:
196
+ print("%i times" % count)
197
+
198
+ print("Doctests done")
.venv/lib/python3.11/site-packages/rsa/py.typed ADDED
@@ -0,0 +1 @@
 
 
1
+ # Marker file for PEP 561. The rsa package uses inline types.
.venv/lib/python3.11/site-packages/rsa/transform.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Data transformation functions.
16
+
17
+ From bytes to a number, number to bytes, etc.
18
+ """
19
+
20
+ import math
21
+
22
+
23
+ def bytes2int(raw_bytes: bytes) -> int:
24
+ r"""Converts a list of bytes or an 8-bit string to an integer.
25
+
26
+ When using unicode strings, encode it to some encoding like UTF8 first.
27
+
28
+ >>> (((128 * 256) + 64) * 256) + 15
29
+ 8405007
30
+ >>> bytes2int(b'\x80@\x0f')
31
+ 8405007
32
+
33
+ """
34
+ return int.from_bytes(raw_bytes, "big", signed=False)
35
+
36
+
37
+ def int2bytes(number: int, fill_size: int = 0) -> bytes:
38
+ """
39
+ Convert an unsigned integer to bytes (big-endian)::
40
+
41
+ Does not preserve leading zeros if you don't specify a fill size.
42
+
43
+ :param number:
44
+ Integer value
45
+ :param fill_size:
46
+ If the optional fill size is given the length of the resulting
47
+ byte string is expected to be the fill size and will be padded
48
+ with prefix zero bytes to satisfy that length.
49
+ :returns:
50
+ Raw bytes (base-256 representation).
51
+ :raises:
52
+ ``OverflowError`` when fill_size is given and the number takes up more
53
+ bytes than fit into the block. This requires the ``overflow``
54
+ argument to this function to be set to ``False`` otherwise, no
55
+ error will be raised.
56
+ """
57
+
58
+ if number < 0:
59
+ raise ValueError("Number must be an unsigned integer: %d" % number)
60
+
61
+ bytes_required = max(1, math.ceil(number.bit_length() / 8))
62
+
63
+ if fill_size > 0:
64
+ return number.to_bytes(fill_size, "big")
65
+
66
+ return number.to_bytes(bytes_required, "big")
67
+
68
+
69
+ if __name__ == "__main__":
70
+ import doctest
71
+
72
+ doctest.testmod()
.venv/lib/python3.11/site-packages/rsa/util.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2011 Sybren A. Stüvel <sybren@stuvel.eu>
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Utility functions."""
16
+
17
+ import sys
18
+ from optparse import OptionParser
19
+
20
+ import rsa.key
21
+
22
+
23
+ def private_to_public() -> None:
24
+ """Reads a private key and outputs the corresponding public key."""
25
+
26
+ # Parse the CLI options
27
+ parser = OptionParser(
28
+ usage="usage: %prog [options]",
29
+ description="Reads a private key and outputs the "
30
+ "corresponding public key. Both private and public keys use "
31
+ "the format described in PKCS#1 v1.5",
32
+ )
33
+
34
+ parser.add_option(
35
+ "-i",
36
+ "--input",
37
+ dest="infilename",
38
+ type="string",
39
+ help="Input filename. Reads from stdin if not specified",
40
+ )
41
+ parser.add_option(
42
+ "-o",
43
+ "--output",
44
+ dest="outfilename",
45
+ type="string",
46
+ help="Output filename. Writes to stdout of not specified",
47
+ )
48
+
49
+ parser.add_option(
50
+ "--inform",
51
+ dest="inform",
52
+ help="key format of input - default PEM",
53
+ choices=("PEM", "DER"),
54
+ default="PEM",
55
+ )
56
+
57
+ parser.add_option(
58
+ "--outform",
59
+ dest="outform",
60
+ help="key format of output - default PEM",
61
+ choices=("PEM", "DER"),
62
+ default="PEM",
63
+ )
64
+
65
+ (cli, cli_args) = parser.parse_args(sys.argv)
66
+
67
+ # Read the input data
68
+ if cli.infilename:
69
+ print(
70
+ "Reading private key from %s in %s format" % (cli.infilename, cli.inform),
71
+ file=sys.stderr,
72
+ )
73
+ with open(cli.infilename, "rb") as infile:
74
+ in_data = infile.read()
75
+ else:
76
+ print("Reading private key from stdin in %s format" % cli.inform, file=sys.stderr)
77
+ in_data = sys.stdin.read().encode("ascii")
78
+
79
+ assert type(in_data) == bytes, type(in_data)
80
+
81
+ # Take the public fields and create a public key
82
+ priv_key = rsa.key.PrivateKey.load_pkcs1(in_data, cli.inform)
83
+ pub_key = rsa.key.PublicKey(priv_key.n, priv_key.e)
84
+
85
+ # Save to the output file
86
+ out_data = pub_key.save_pkcs1(cli.outform)
87
+
88
+ if cli.outfilename:
89
+ print(
90
+ "Writing public key to %s in %s format" % (cli.outfilename, cli.outform),
91
+ file=sys.stderr,
92
+ )
93
+ with open(cli.outfilename, "wb") as outfile:
94
+ outfile.write(out_data)
95
+ else:
96
+ print("Writing public key to stdout in %s format" % cli.outform, file=sys.stderr)
97
+ sys.stdout.write(out_data.decode("ascii"))
.venv/lib/python3.11/site-packages/uvicorn/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from uvicorn.config import Config
2
+ from uvicorn.main import Server, main, run
3
+
4
+ __version__ = "0.34.0"
5
+ __all__ = ["main", "run", "Config", "Server"]
.venv/lib/python3.11/site-packages/uvicorn/__main__.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import uvicorn
2
+
3
+ if __name__ == "__main__":
4
+ uvicorn.main()
.venv/lib/python3.11/site-packages/uvicorn/_subprocess.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Some light wrappers around Python's multiprocessing, to deal with cleanly
3
+ starting child processes.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import multiprocessing
9
+ import os
10
+ import sys
11
+ from multiprocessing.context import SpawnProcess
12
+ from socket import socket
13
+ from typing import Callable
14
+
15
+ from uvicorn.config import Config
16
+
17
+ multiprocessing.allow_connection_pickling()
18
+ spawn = multiprocessing.get_context("spawn")
19
+
20
+
21
+ def get_subprocess(
22
+ config: Config,
23
+ target: Callable[..., None],
24
+ sockets: list[socket],
25
+ ) -> SpawnProcess:
26
+ """
27
+ Called in the parent process, to instantiate a new child process instance.
28
+ The child is not yet started at this point.
29
+
30
+ * config - The Uvicorn configuration instance.
31
+ * target - A callable that accepts a list of sockets. In practice this will
32
+ be the `Server.run()` method.
33
+ * sockets - A list of sockets to pass to the server. Sockets are bound once
34
+ by the parent process, and then passed to the child processes.
35
+ """
36
+ # We pass across the stdin fileno, and reopen it in the child process.
37
+ # This is required for some debugging environments.
38
+ try:
39
+ stdin_fileno = sys.stdin.fileno()
40
+ # The `sys.stdin` can be `None`, see https://docs.python.org/3/library/sys.html#sys.__stdin__.
41
+ except (AttributeError, OSError):
42
+ stdin_fileno = None
43
+
44
+ kwargs = {
45
+ "config": config,
46
+ "target": target,
47
+ "sockets": sockets,
48
+ "stdin_fileno": stdin_fileno,
49
+ }
50
+
51
+ return spawn.Process(target=subprocess_started, kwargs=kwargs)
52
+
53
+
54
+ def subprocess_started(
55
+ config: Config,
56
+ target: Callable[..., None],
57
+ sockets: list[socket],
58
+ stdin_fileno: int | None,
59
+ ) -> None:
60
+ """
61
+ Called when the child process starts.
62
+
63
+ * config - The Uvicorn configuration instance.
64
+ * target - A callable that accepts a list of sockets. In practice this will
65
+ be the `Server.run()` method.
66
+ * sockets - A list of sockets to pass to the server. Sockets are bound once
67
+ by the parent process, and then passed to the child processes.
68
+ * stdin_fileno - The file number of sys.stdin, so that it can be reattached
69
+ to the child process.
70
+ """
71
+ # Re-open stdin.
72
+ if stdin_fileno is not None:
73
+ sys.stdin = os.fdopen(stdin_fileno) # pragma: full coverage
74
+
75
+ # Logging needs to be setup again for each child.
76
+ config.configure_logging()
77
+
78
+ try:
79
+ # Now we can call into `Server.run(sockets=sockets)`
80
+ target(sockets=sockets)
81
+ except KeyboardInterrupt: # pragma: no cover
82
+ # supress the exception to avoid a traceback from subprocess.Popen
83
+ # the parent already expects us to end, so no vital information is lost
84
+ pass