Welcome to mirror list, hosted at ThFree Co, Russian Federation.

xmppiri.py « nbxmpp - dev.gajim.org/gajim/python-nbxmpp.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: bc0035ea80ec16c4c2a3a3a3da0078c9ec54d84b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
from __future__ import annotations

from typing import Callable

import functools
import re


# https://www.rfc-editor.org/rfc/rfc3987
ucschar = (
    '\xA0-\uD7FF'
    '\uF900-\uFDCF'
    '\uFDF0-\uFFEF'
    '\U00010000-\U0001FFFD'
    '\U00020000-\U0002FFFD'
    '\U00030000-\U0003FFFD'
    '\U00040000-\U0004FFFD'
    '\U00050000-\U0005FFFD'
    '\U00060000-\U0006FFFD'
    '\U00070000-\U0007FFFD'
    '\U00080000-\U0008FFFD'
    '\U00090000-\U0009FFFD'
    '\U000A0000-\U000AFFFD'
    '\U000B0000-\U000BFFFD'
    '\U000C0000-\U000CFFFD'
    '\U000D0000-\U000DFFFD'
    '\U000E1000-\U000EFFFD'
)

ALPHA           = 'A-Za-z'
DIGIT           = '0-9'
unreserved      = fr'{ALPHA}{DIGIT}\-\._\~'
subdelims       = "!$&'()*+,;="
iunreserved     = f'{unreserved}{ucschar}'
ipchar          = f'{iunreserved}{re.escape(subdelims)}:@'
ifragment       = fr'{ipchar}/\?'

# https://www.rfc-editor.org/rfc/rfc5122.html#section-2.2
nodeallow       = r"!$()*+,;="
resallow        = r"!$&'()*+,:;="
inode           = f'{iunreserved}{re.escape(nodeallow)}'
ires            = f'{iunreserved}{re.escape(resallow)}'
ivalue          = f'{iunreserved}'

rx_iunreserved  = re.compile(f'[{iunreserved}]*')
rx_inode        = re.compile(f'[{inode}]')
rx_ires         = re.compile(f'[{ires}]')
rx_ikey         = rx_iunreserved
rx_iquerytype   = rx_iunreserved
rx_ivalue       = rx_iunreserved
rx_ifragment     = re.compile(f'[{ifragment}]')


class _Quoter(dict[str, str]):
    """A mapping from a string to its percent encoded form.

    Mapping is only done if string is not in safe range.

    Keeps a cache internally, via __missing__, for efficiency (lookups
    of cached keys don't call Python code at all).
    """
    def __init__(self, safe: re.Pattern[str]) -> None:
        self._safe = safe

    def __repr__(self):
        return f"<Quoter {dict(self)!r}>"

    def __missing__(self, b: str):
        if len(b) != 1:
            raise ValueError("String must be exactly one character long")

        if self._safe.fullmatch(b) is None:
            res = "".join(['%{:02X}'.format(i) for i in b.encode()])
        else:
            res = b
        self[b] = res
        return res


@functools.lru_cache
def _quoter_factory(safe: re.Pattern[str]) -> Callable[[str], str]:
    return _Quoter(safe).__getitem__


def validate_ikey(ikey: str) -> str:
    res = rx_ikey.fullmatch(ikey)
    if res is None:
        raise ValueError('Not allowed characters in key')
    return ikey


def validate_querytype(querytype: str) -> str:
    res = rx_iquerytype.fullmatch(querytype)
    if res is None:
        raise ValueError('Not allowed characters in querytype')
    return querytype


def _escape(string: str, pattern: re.Pattern[str]) -> str:
    quoter = _quoter_factory(safe=pattern)
    return ''.join([quoter(c) for c in string])


def escape_ifragment(fragment: str) -> str:
    return _escape(fragment, rx_ifragment)


def escape_ivalue(value: str) -> str:
    return _escape(value, rx_ivalue)


def escape_inode(node: str) -> str:
    return _escape(node, rx_inode)


def escape_ires(res: str) -> str:
    return _escape(res, rx_ires)