v1.3.5
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
########################################################################
|
||||
# File name: stringprep.py
|
||||
# This file is part of: aioxmpp
|
||||
#
|
||||
# LICENSE
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of the
|
||||
# License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful, but
|
||||
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public
|
||||
# License along with this program. If not, see
|
||||
# <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
########################################################################
|
||||
"""
|
||||
Stringprep support
|
||||
##################
|
||||
|
||||
This module implements the Nodeprep (`RFC 6122`_) and Resourceprep
|
||||
(`RFC 6122`_) stringprep profiles.
|
||||
|
||||
.. autofunction:: nodeprep
|
||||
|
||||
.. autofunction:: resourceprep
|
||||
|
||||
.. autofunction:: nameprep
|
||||
|
||||
.. _RFC 3454: https://tools.ietf.org/html/rfc3454
|
||||
.. _RFC 6122: https://tools.ietf.org/html/rfc6122
|
||||
|
||||
"""
|
||||
|
||||
import stringprep
|
||||
|
||||
from unicodedata import ucd_3_2_0 as unicodedata
|
||||
|
||||
_nodeprep_prohibited = frozenset("\"&'/:<>@")
|
||||
|
||||
|
||||
def is_RandALCat(c):
|
||||
return unicodedata.bidirectional(c) in ("R", "AL")
|
||||
|
||||
|
||||
def is_LCat(c):
|
||||
return unicodedata.bidirectional(c) == "L"
|
||||
|
||||
|
||||
def check_against_tables(chars, tables):
|
||||
"""
|
||||
Perform a check against the table predicates in `tables`. `tables` must be
|
||||
a reusable iterable containing characteristic functions of character sets,
|
||||
that is, functions which return :data:`True` if the character is in the
|
||||
table.
|
||||
|
||||
The function returns the first character occurring in any of the tables or
|
||||
:data:`None` if no character matches.
|
||||
"""
|
||||
|
||||
for c in chars:
|
||||
if any(in_table(c) for in_table in tables):
|
||||
return c
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def do_normalization(chars):
|
||||
"""
|
||||
Perform the stringprep normalization. Operates in-place on a list of
|
||||
unicode characters provided in `chars`.
|
||||
"""
|
||||
chars[:] = list(unicodedata.normalize("NFKC", "".join(chars)))
|
||||
|
||||
|
||||
def check_bidi(chars):
|
||||
"""
|
||||
Check proper bidirectionality as per stringprep. Operates on a list of
|
||||
unicode characters provided in `chars`.
|
||||
"""
|
||||
|
||||
# the empty string is valid, as it cannot violate the RandALCat constraints
|
||||
if not chars:
|
||||
return
|
||||
|
||||
# first_is_RorAL = unicodedata.bidirectional(chars[0]) in {"R", "AL"}
|
||||
# if first_is_RorAL:
|
||||
|
||||
has_RandALCat = any(is_RandALCat(c) for c in chars)
|
||||
if not has_RandALCat:
|
||||
return
|
||||
|
||||
has_LCat = any(is_LCat(c) for c in chars)
|
||||
if has_LCat:
|
||||
raise ValueError("L and R/AL characters must not occur in the same"
|
||||
" string")
|
||||
|
||||
if not is_RandALCat(chars[0]) or not is_RandALCat(chars[-1]):
|
||||
raise ValueError("R/AL string must start and end with R/AL character.")
|
||||
|
||||
|
||||
def check_prohibited_output(chars, bad_tables):
|
||||
"""
|
||||
Check against prohibited output, by checking whether any of the characters
|
||||
from `chars` are in any of the `bad_tables`.
|
||||
|
||||
Operates in-place on a list of code points from `chars`.
|
||||
"""
|
||||
violator = check_against_tables(chars, bad_tables)
|
||||
if violator is not None:
|
||||
raise ValueError("Input contains invalid unicode codepoint: "
|
||||
"U+{:04x}".format(ord(violator)))
|
||||
|
||||
|
||||
def check_unassigned(chars, bad_tables):
|
||||
"""
|
||||
Check that `chars` does not contain any unassigned code points as per
|
||||
the given list of `bad_tables`.
|
||||
|
||||
Operates on a list of unicode code points provided in `chars`.
|
||||
"""
|
||||
bad_tables = (
|
||||
stringprep.in_table_a1,)
|
||||
|
||||
violator = check_against_tables(chars, bad_tables)
|
||||
if violator is not None:
|
||||
raise ValueError("Input contains unassigned code point: "
|
||||
"U+{:04x}".format(ord(violator)))
|
||||
|
||||
|
||||
def _nodeprep_do_mapping(chars):
|
||||
i = 0
|
||||
while i < len(chars):
|
||||
c = chars[i]
|
||||
if stringprep.in_table_b1(c):
|
||||
del chars[i]
|
||||
else:
|
||||
replacement = stringprep.map_table_b2(c)
|
||||
if replacement != c:
|
||||
chars[i:(i + 1)] = list(replacement)
|
||||
i += len(replacement)
|
||||
|
||||
|
||||
def nodeprep(string, allow_unassigned=False):
|
||||
"""
|
||||
Process the given `string` using the Nodeprep (`RFC 6122`_) profile. In the
|
||||
error cases defined in `RFC 3454`_ (stringprep), a :class:`ValueError` is
|
||||
raised.
|
||||
"""
|
||||
|
||||
chars = list(string)
|
||||
_nodeprep_do_mapping(chars)
|
||||
do_normalization(chars)
|
||||
check_prohibited_output(
|
||||
chars,
|
||||
(
|
||||
stringprep.in_table_c11,
|
||||
stringprep.in_table_c12,
|
||||
stringprep.in_table_c21,
|
||||
stringprep.in_table_c22,
|
||||
stringprep.in_table_c3,
|
||||
stringprep.in_table_c4,
|
||||
stringprep.in_table_c5,
|
||||
stringprep.in_table_c6,
|
||||
stringprep.in_table_c7,
|
||||
stringprep.in_table_c8,
|
||||
stringprep.in_table_c9,
|
||||
lambda x: x in _nodeprep_prohibited
|
||||
))
|
||||
check_bidi(chars)
|
||||
|
||||
if not allow_unassigned:
|
||||
check_unassigned(
|
||||
chars,
|
||||
(
|
||||
stringprep.in_table_a1,
|
||||
)
|
||||
)
|
||||
|
||||
return "".join(chars)
|
||||
|
||||
|
||||
def _resourceprep_do_mapping(chars):
|
||||
i = 0
|
||||
while i < len(chars):
|
||||
c = chars[i]
|
||||
if stringprep.in_table_b1(c):
|
||||
del chars[i]
|
||||
continue
|
||||
i += 1
|
||||
|
||||
|
||||
def resourceprep(string, allow_unassigned=False):
|
||||
"""
|
||||
Process the given `string` using the Resourceprep (`RFC 6122`_) profile. In
|
||||
the error cases defined in `RFC 3454`_ (stringprep), a :class:`ValueError`
|
||||
is raised.
|
||||
"""
|
||||
|
||||
chars = list(string)
|
||||
_resourceprep_do_mapping(chars)
|
||||
do_normalization(chars)
|
||||
check_prohibited_output(
|
||||
chars,
|
||||
(
|
||||
stringprep.in_table_c12,
|
||||
stringprep.in_table_c21,
|
||||
stringprep.in_table_c22,
|
||||
stringprep.in_table_c3,
|
||||
stringprep.in_table_c4,
|
||||
stringprep.in_table_c5,
|
||||
stringprep.in_table_c6,
|
||||
stringprep.in_table_c7,
|
||||
stringprep.in_table_c8,
|
||||
stringprep.in_table_c9,
|
||||
))
|
||||
check_bidi(chars)
|
||||
|
||||
if not allow_unassigned:
|
||||
check_unassigned(
|
||||
chars,
|
||||
(
|
||||
stringprep.in_table_a1,
|
||||
)
|
||||
)
|
||||
|
||||
return "".join(chars)
|
||||
|
||||
|
||||
def nameprep(string, allow_unassigned=False):
|
||||
"""
|
||||
Process the given `string` using the Nameprep (`RFC 3491`_) profile. In the
|
||||
error cases defined in `RFC 3454`_ (stringprep), a :class:`ValueError` is
|
||||
raised.
|
||||
"""
|
||||
|
||||
chars = list(string)
|
||||
_nodeprep_do_mapping(chars)
|
||||
do_normalization(chars)
|
||||
check_prohibited_output(
|
||||
chars,
|
||||
(
|
||||
stringprep.in_table_c12,
|
||||
stringprep.in_table_c22,
|
||||
stringprep.in_table_c3,
|
||||
stringprep.in_table_c4,
|
||||
stringprep.in_table_c5,
|
||||
stringprep.in_table_c6,
|
||||
stringprep.in_table_c7,
|
||||
stringprep.in_table_c8,
|
||||
stringprep.in_table_c9,
|
||||
))
|
||||
check_bidi(chars)
|
||||
|
||||
if not allow_unassigned:
|
||||
check_unassigned(
|
||||
chars,
|
||||
(
|
||||
stringprep.in_table_a1,
|
||||
)
|
||||
)
|
||||
|
||||
return "".join(chars)
|
||||
Reference in New Issue
Block a user