Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .venv/lib/python3.11/site-packages/jinja2/__init__.py +38 -0
- .venv/lib/python3.11/site-packages/jinja2/__pycache__/async_utils.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/jinja2/__pycache__/bccache.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/jinja2/__pycache__/defaults.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/jinja2/__pycache__/environment.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/jinja2/__pycache__/exceptions.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/jinja2/__pycache__/lexer.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/jinja2/__pycache__/meta.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/jinja2/__pycache__/nativetypes.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/jinja2/__pycache__/parser.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/jinja2/_identifier.py +6 -0
- .venv/lib/python3.11/site-packages/jinja2/bccache.py +408 -0
- .venv/lib/python3.11/site-packages/jinja2/constants.py +20 -0
- .venv/lib/python3.11/site-packages/jinja2/environment.py +1672 -0
- .venv/lib/python3.11/site-packages/jinja2/ext.py +870 -0
- .venv/lib/python3.11/site-packages/jinja2/lexer.py +868 -0
- .venv/lib/python3.11/site-packages/jinja2/loaders.py +693 -0
- .venv/lib/python3.11/site-packages/jinja2/optimizer.py +48 -0
- .venv/lib/python3.11/site-packages/jinja2/parser.py +1049 -0
- .venv/lib/python3.11/site-packages/rsa/__init__.py +60 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/__init__.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/asn1.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/cli.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/common.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/core.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/key.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/parallel.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/pem.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/pkcs1.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/pkcs1_v2.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/prime.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/randnum.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/transform.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/__pycache__/util.cpython-311.pyc +0 -0
- .venv/lib/python3.11/site-packages/rsa/asn1.py +52 -0
- .venv/lib/python3.11/site-packages/rsa/cli.py +321 -0
- .venv/lib/python3.11/site-packages/rsa/common.py +184 -0
- .venv/lib/python3.11/site-packages/rsa/core.py +53 -0
- .venv/lib/python3.11/site-packages/rsa/key.py +858 -0
- .venv/lib/python3.11/site-packages/rsa/parallel.py +96 -0
- .venv/lib/python3.11/site-packages/rsa/pem.py +134 -0
- .venv/lib/python3.11/site-packages/rsa/pkcs1.py +485 -0
- .venv/lib/python3.11/site-packages/rsa/pkcs1_v2.py +100 -0
- .venv/lib/python3.11/site-packages/rsa/prime.py +198 -0
- .venv/lib/python3.11/site-packages/rsa/py.typed +1 -0
- .venv/lib/python3.11/site-packages/rsa/transform.py +72 -0
- .venv/lib/python3.11/site-packages/rsa/util.py +97 -0
- .venv/lib/python3.11/site-packages/uvicorn/__init__.py +5 -0
- .venv/lib/python3.11/site-packages/uvicorn/__main__.py +4 -0
- .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
|