1import json
2import os
3import re
4from collections import abc
5from collections import deque
6from random import choice
7from random import randrange
8from threading import Lock
9from urllib.parse import quote_from_bytes
10
11from markupsafe import escape
12from markupsafe import Markup
13
14_word_split_re = re.compile(r"(\s+)")
15_lead_pattern = "|".join(map(re.escape, ("(", "<", "&lt;")))
16_trail_pattern = "|".join(map(re.escape, (".", ",", ")", ">", "\n", "&gt;")))
17_punctuation_re = re.compile(
18    fr"^(?P<lead>(?:{_lead_pattern})*)(?P<middle>.*?)(?P<trail>(?:{_trail_pattern})*)$"
19)
20_simple_email_re = re.compile(r"^\S+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9._-]+$")
21_striptags_re = re.compile(r"(<!--.*?-->|<[^>]*>)")
22_entity_re = re.compile(r"&([^;]+);")
23_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
24_digits = "0123456789"
25
26# special singleton representing missing values for the runtime
27missing = type("MissingType", (), {"__repr__": lambda x: "missing"})()
28
29# internal code
30internal_code = set()
31
32concat = "".join
33
34_slash_escape = "\\/" not in json.dumps("/")
35
36
37def contextfunction(f):
38    """This decorator can be used to mark a function or method context callable.
39    A context callable is passed the active :class:`Context` as first argument when
40    called from the template.  This is useful if a function wants to get access
41    to the context or functions provided on the context object.  For example
42    a function that returns a sorted list of template variables the current
43    template exports could look like this::
44
45        @contextfunction
46        def get_exported_names(context):
47            return sorted(context.exported_vars)
48    """
49    f.contextfunction = True
50    return f
51
52
53def evalcontextfunction(f):
54    """This decorator can be used to mark a function or method as an eval
55    context callable.  This is similar to the :func:`contextfunction`
56    but instead of passing the context, an evaluation context object is
57    passed.  For more information about the eval context, see
58    :ref:`eval-context`.
59
60    .. versionadded:: 2.4
61    """
62    f.evalcontextfunction = True
63    return f
64
65
66def environmentfunction(f):
67    """This decorator can be used to mark a function or method as environment
68    callable.  This decorator works exactly like the :func:`contextfunction`
69    decorator just that the first argument is the active :class:`Environment`
70    and not context.
71    """
72    f.environmentfunction = True
73    return f
74
75
76def internalcode(f):
77    """Marks the function as internally used"""
78    internal_code.add(f.__code__)
79    return f
80
81
82def is_undefined(obj):
83    """Check if the object passed is undefined.  This does nothing more than
84    performing an instance check against :class:`Undefined` but looks nicer.
85    This can be used for custom filters or tests that want to react to
86    undefined variables.  For example a custom default filter can look like
87    this::
88
89        def default(var, default=''):
90            if is_undefined(var):
91                return default
92            return var
93    """
94    from .runtime import Undefined
95
96    return isinstance(obj, Undefined)
97
98
99def consume(iterable):
100    """Consumes an iterable without doing anything with it."""
101    for _ in iterable:
102        pass
103
104
105def clear_caches():
106    """Jinja keeps internal caches for environments and lexers.  These are
107    used so that Jinja doesn't have to recreate environments and lexers all
108    the time.  Normally you don't have to care about that but if you are
109    measuring memory consumption you may want to clean the caches.
110    """
111    from .environment import _spontaneous_environments
112    from .lexer import _lexer_cache
113
114    _spontaneous_environments.clear()
115    _lexer_cache.clear()
116
117
118def import_string(import_name, silent=False):
119    """Imports an object based on a string.  This is useful if you want to
120    use import paths as endpoints or something similar.  An import path can
121    be specified either in dotted notation (``xml.sax.saxutils.escape``)
122    or with a colon as object delimiter (``xml.sax.saxutils:escape``).
123
124    If the `silent` is True the return value will be `None` if the import
125    fails.
126
127    :return: imported object
128    """
129    try:
130        if ":" in import_name:
131            module, obj = import_name.split(":", 1)
132        elif "." in import_name:
133            module, _, obj = import_name.rpartition(".")
134        else:
135            return __import__(import_name)
136        return getattr(__import__(module, None, None, [obj]), obj)
137    except (ImportError, AttributeError):
138        if not silent:
139            raise
140
141
142def open_if_exists(filename, mode="rb"):
143    """Returns a file descriptor for the filename if that file exists,
144    otherwise ``None``.
145    """
146    if not os.path.isfile(filename):
147        return None
148
149    return open(filename, mode)
150
151
152def object_type_repr(obj):
153    """Returns the name of the object's type.  For some recognized
154    singletons the name of the object is returned instead. (For
155    example for `None` and `Ellipsis`).
156    """
157    if obj is None:
158        return "None"
159    elif obj is Ellipsis:
160        return "Ellipsis"
161
162    cls = type(obj)
163
164    if cls.__module__ == "builtins":
165        return f"{cls.__name__} object"
166
167    return f"{cls.__module__}.{cls.__name__} object"
168
169
170def pformat(obj):
171    """Format an object using :func:`pprint.pformat`.
172    """
173    from pprint import pformat
174
175    return pformat(obj)
176
177
178def urlize(text, trim_url_limit=None, rel=None, target=None):
179    """Converts any URLs in text into clickable links. Works on http://,
180    https:// and www. links. Links can have trailing punctuation (periods,
181    commas, close-parens) and leading punctuation (opening parens) and
182    it'll still do the right thing.
183
184    If trim_url_limit is not None, the URLs in link text will be limited
185    to trim_url_limit characters.
186
187    If nofollow is True, the URLs in link text will get a rel="nofollow"
188    attribute.
189
190    If target is not None, a target attribute will be added to the link.
191    """
192
193    def trim_url(x, limit=trim_url_limit):
194        if limit is not None:
195            return x[:limit] + ("..." if len(x) >= limit else "")
196
197        return x
198
199    words = _word_split_re.split(str(escape(text)))
200    rel_attr = f' rel="{escape(rel)}"' if rel else ""
201    target_attr = f' target="{escape(target)}"' if target else ""
202
203    for i, word in enumerate(words):
204        match = _punctuation_re.match(word)
205        if match:
206            lead, middle, trail = match.groups()
207            if middle.startswith("www.") or (
208                "@" not in middle
209                and not middle.startswith("http://")
210                and not middle.startswith("https://")
211                and len(middle) > 0
212                and middle[0] in _letters + _digits
213                and (
214                    middle.endswith(".org")
215                    or middle.endswith(".net")
216                    or middle.endswith(".com")
217                )
218            ):
219                middle = (
220                    f'<a href="http://{middle}"{rel_attr}{target_attr}>'
221                    f"{trim_url(middle)}</a>"
222                )
223            if middle.startswith("http://") or middle.startswith("https://"):
224                middle = (
225                    f'<a href="{middle}"{rel_attr}{target_attr}>{trim_url(middle)}</a>'
226                )
227            if (
228                "@" in middle
229                and not middle.startswith("www.")
230                and ":" not in middle
231                and _simple_email_re.match(middle)
232            ):
233                middle = f'<a href="mailto:{middle}">{middle}</a>'
234            if lead + middle + trail != word:
235                words[i] = lead + middle + trail
236    return "".join(words)
237
238
239def generate_lorem_ipsum(n=5, html=True, min=20, max=100):
240    """Generate some lorem ipsum for the template."""
241    from .constants import LOREM_IPSUM_WORDS
242
243    words = LOREM_IPSUM_WORDS.split()
244    result = []
245
246    for _ in range(n):
247        next_capitalized = True
248        last_comma = last_fullstop = 0
249        word = None
250        last = None
251        p = []
252
253        # each paragraph contains out of 20 to 100 words.
254        for idx, _ in enumerate(range(randrange(min, max))):
255            while True:
256                word = choice(words)
257                if word != last:
258                    last = word
259                    break
260            if next_capitalized:
261                word = word.capitalize()
262                next_capitalized = False
263            # add commas
264            if idx - randrange(3, 8) > last_comma:
265                last_comma = idx
266                last_fullstop += 2
267                word += ","
268            # add end of sentences
269            if idx - randrange(10, 20) > last_fullstop:
270                last_comma = last_fullstop = idx
271                word += "."
272                next_capitalized = True
273            p.append(word)
274
275        # ensure that the paragraph ends with a dot.
276        p = " ".join(p)
277        if p.endswith(","):
278            p = p[:-1] + "."
279        elif not p.endswith("."):
280            p += "."
281        result.append(p)
282
283    if not html:
284        return "\n\n".join(result)
285    return Markup("\n".join(f"<p>{escape(x)}</p>" for x in result))
286
287
288def url_quote(obj, charset="utf-8", for_qs=False):
289    """Quote a string for use in a URL using the given charset.
290
291    This function is misnamed, it is a wrapper around
292    :func:`urllib.parse.quote`.
293
294    :param obj: String or bytes to quote. Other types are converted to
295        string then encoded to bytes using the given charset.
296    :param charset: Encode text to bytes using this charset.
297    :param for_qs: Quote "/" and use "+" for spaces.
298    """
299    if not isinstance(obj, bytes):
300        if not isinstance(obj, str):
301            obj = str(obj)
302
303        obj = obj.encode(charset)
304
305    safe = b"" if for_qs else b"/"
306    rv = quote_from_bytes(obj, safe)
307
308    if for_qs:
309        rv = rv.replace("%20", "+")
310
311    return rv
312
313
314def unicode_urlencode(obj, charset="utf-8", for_qs=False):
315    import warnings
316
317    warnings.warn(
318        "'unicode_urlencode' has been renamed to 'url_quote'. The old"
319        " name will be removed in version 3.1.",
320        DeprecationWarning,
321        stacklevel=2,
322    )
323    return url_quote(obj, charset=charset, for_qs=for_qs)
324
325
326@abc.MutableMapping.register
327class LRUCache:
328    """A simple LRU Cache implementation."""
329
330    # this is fast for small capacities (something below 1000) but doesn't
331    # scale.  But as long as it's only used as storage for templates this
332    # won't do any harm.
333
334    def __init__(self, capacity):
335        self.capacity = capacity
336        self._mapping = {}
337        self._queue = deque()
338        self._postinit()
339
340    def _postinit(self):
341        # alias all queue methods for faster lookup
342        self._popleft = self._queue.popleft
343        self._pop = self._queue.pop
344        self._remove = self._queue.remove
345        self._wlock = Lock()
346        self._append = self._queue.append
347
348    def __getstate__(self):
349        return {
350            "capacity": self.capacity,
351            "_mapping": self._mapping,
352            "_queue": self._queue,
353        }
354
355    def __setstate__(self, d):
356        self.__dict__.update(d)
357        self._postinit()
358
359    def __getnewargs__(self):
360        return (self.capacity,)
361
362    def copy(self):
363        """Return a shallow copy of the instance."""
364        rv = self.__class__(self.capacity)
365        rv._mapping.update(self._mapping)
366        rv._queue.extend(self._queue)
367        return rv
368
369    def get(self, key, default=None):
370        """Return an item from the cache dict or `default`"""
371        try:
372            return self[key]
373        except KeyError:
374            return default
375
376    def setdefault(self, key, default=None):
377        """Set `default` if the key is not in the cache otherwise
378        leave unchanged. Return the value of this key.
379        """
380        try:
381            return self[key]
382        except KeyError:
383            self[key] = default
384            return default
385
386    def clear(self):
387        """Clear the cache."""
388        self._wlock.acquire()
389        try:
390            self._mapping.clear()
391            self._queue.clear()
392        finally:
393            self._wlock.release()
394
395    def __contains__(self, key):
396        """Check if a key exists in this cache."""
397        return key in self._mapping
398
399    def __len__(self):
400        """Return the current size of the cache."""
401        return len(self._mapping)
402
403    def __repr__(self):
404        return f"<{self.__class__.__name__} {self._mapping!r}>"
405
406    def __getitem__(self, key):
407        """Get an item from the cache. Moves the item up so that it has the
408        highest priority then.
409
410        Raise a `KeyError` if it does not exist.
411        """
412        self._wlock.acquire()
413        try:
414            rv = self._mapping[key]
415            if self._queue[-1] != key:
416                try:
417                    self._remove(key)
418                except ValueError:
419                    # if something removed the key from the container
420                    # when we read, ignore the ValueError that we would
421                    # get otherwise.
422                    pass
423                self._append(key)
424            return rv
425        finally:
426            self._wlock.release()
427
428    def __setitem__(self, key, value):
429        """Sets the value for an item. Moves the item up so that it
430        has the highest priority then.
431        """
432        self._wlock.acquire()
433        try:
434            if key in self._mapping:
435                self._remove(key)
436            elif len(self._mapping) == self.capacity:
437                del self._mapping[self._popleft()]
438            self._append(key)
439            self._mapping[key] = value
440        finally:
441            self._wlock.release()
442
443    def __delitem__(self, key):
444        """Remove an item from the cache dict.
445        Raise a `KeyError` if it does not exist.
446        """
447        self._wlock.acquire()
448        try:
449            del self._mapping[key]
450            try:
451                self._remove(key)
452            except ValueError:
453                pass
454        finally:
455            self._wlock.release()
456
457    def items(self):
458        """Return a list of items."""
459        result = [(key, self._mapping[key]) for key in list(self._queue)]
460        result.reverse()
461        return result
462
463    def values(self):
464        """Return a list of all values."""
465        return [x[1] for x in self.items()]
466
467    def keys(self):
468        """Return a list of all keys ordered by most recent usage."""
469        return list(self)
470
471    def __iter__(self):
472        return reversed(tuple(self._queue))
473
474    def __reversed__(self):
475        """Iterate over the keys in the cache dict, oldest items
476        coming first.
477        """
478        return iter(tuple(self._queue))
479
480    __copy__ = copy
481
482
483def select_autoescape(
484    enabled_extensions=("html", "htm", "xml"),
485    disabled_extensions=(),
486    default_for_string=True,
487    default=False,
488):
489    """Intelligently sets the initial value of autoescaping based on the
490    filename of the template.  This is the recommended way to configure
491    autoescaping if you do not want to write a custom function yourself.
492
493    If you want to enable it for all templates created from strings or
494    for all templates with `.html` and `.xml` extensions::
495
496        from jinja2 import Environment, select_autoescape
497        env = Environment(autoescape=select_autoescape(
498            enabled_extensions=('html', 'xml'),
499            default_for_string=True,
500        ))
501
502    Example configuration to turn it on at all times except if the template
503    ends with `.txt`::
504
505        from jinja2 import Environment, select_autoescape
506        env = Environment(autoescape=select_autoescape(
507            disabled_extensions=('txt',),
508            default_for_string=True,
509            default=True,
510        ))
511
512    The `enabled_extensions` is an iterable of all the extensions that
513    autoescaping should be enabled for.  Likewise `disabled_extensions` is
514    a list of all templates it should be disabled for.  If a template is
515    loaded from a string then the default from `default_for_string` is used.
516    If nothing matches then the initial value of autoescaping is set to the
517    value of `default`.
518
519    For security reasons this function operates case insensitive.
520
521    .. versionadded:: 2.9
522    """
523    enabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in enabled_extensions)
524    disabled_patterns = tuple(f".{x.lstrip('.').lower()}" for x in disabled_extensions)
525
526    def autoescape(template_name):
527        if template_name is None:
528            return default_for_string
529        template_name = template_name.lower()
530        if template_name.endswith(enabled_patterns):
531            return True
532        if template_name.endswith(disabled_patterns):
533            return False
534        return default
535
536    return autoescape
537
538
539def htmlsafe_json_dumps(obj, dumper=None, **kwargs):
540    """Works exactly like :func:`dumps` but is safe for use in ``<script>``
541    tags.  It accepts the same arguments and returns a JSON string.  Note that
542    this is available in templates through the ``|tojson`` filter which will
543    also mark the result as safe.  Due to how this function escapes certain
544    characters this is safe even if used outside of ``<script>`` tags.
545
546    The following characters are escaped in strings:
547
548    -   ``<``
549    -   ``>``
550    -   ``&``
551    -   ``'``
552
553    This makes it safe to embed such strings in any place in HTML with the
554    notable exception of double quoted attributes.  In that case single
555    quote your attributes or HTML escape it in addition.
556    """
557    if dumper is None:
558        dumper = json.dumps
559    rv = (
560        dumper(obj, **kwargs)
561        .replace("<", "\\u003c")
562        .replace(">", "\\u003e")
563        .replace("&", "\\u0026")
564        .replace("'", "\\u0027")
565    )
566    return Markup(rv)
567
568
569class Cycler:
570    """Cycle through values by yield them one at a time, then restarting
571    once the end is reached. Available as ``cycler`` in templates.
572
573    Similar to ``loop.cycle``, but can be used outside loops or across
574    multiple loops. For example, render a list of folders and files in a
575    list, alternating giving them "odd" and "even" classes.
576
577    .. code-block:: html+jinja
578
579        {% set row_class = cycler("odd", "even") %}
580        <ul class="browser">
581        {% for folder in folders %}
582          <li class="folder {{ row_class.next() }}">{{ folder }}
583        {% endfor %}
584        {% for file in files %}
585          <li class="file {{ row_class.next() }}">{{ file }}
586        {% endfor %}
587        </ul>
588
589    :param items: Each positional argument will be yielded in the order
590        given for each cycle.
591
592    .. versionadded:: 2.1
593    """
594
595    def __init__(self, *items):
596        if not items:
597            raise RuntimeError("at least one item has to be provided")
598        self.items = items
599        self.pos = 0
600
601    def reset(self):
602        """Resets the current item to the first item."""
603        self.pos = 0
604
605    @property
606    def current(self):
607        """Return the current item. Equivalent to the item that will be
608        returned next time :meth:`next` is called.
609        """
610        return self.items[self.pos]
611
612    def next(self):
613        """Return the current item, then advance :attr:`current` to the
614        next item.
615        """
616        rv = self.current
617        self.pos = (self.pos + 1) % len(self.items)
618        return rv
619
620    __next__ = next
621
622
623class Joiner:
624    """A joining helper for templates."""
625
626    def __init__(self, sep=", "):
627        self.sep = sep
628        self.used = False
629
630    def __call__(self):
631        if not self.used:
632            self.used = True
633            return ""
634        return self.sep
635
636
637class Namespace:
638    """A namespace object that can hold arbitrary attributes.  It may be
639    initialized from a dictionary or with keyword arguments."""
640
641    def __init__(*args, **kwargs):  # noqa: B902
642        self, args = args[0], args[1:]
643        self.__attrs = dict(*args, **kwargs)
644
645    def __getattribute__(self, name):
646        # __class__ is needed for the awaitable check in async mode
647        if name in {"_Namespace__attrs", "__class__"}:
648            return object.__getattribute__(self, name)
649        try:
650            return self.__attrs[name]
651        except KeyError:
652            raise AttributeError(name)
653
654    def __setitem__(self, name, value):
655        self.__attrs[name] = value
656
657    def __repr__(self):
658        return f"<Namespace {self.__attrs!r}>"
659
660
661# does this python version support async for in and async generators?
662try:
663    exec("async def _():\n async for _ in ():\n  yield _")
664    have_async_gen = True
665except SyntaxError:
666    have_async_gen = False
667