使用Python验证XMPP jid?

8
什么是验证XMPP JID的正确方法?语法在这里描述,但我并不真正理解它。此外,它似乎非常复杂,因此使用库来执行此操作似乎是个好主意。
我目前正在使用xmpppy,但我似乎找不到如何使用它来验证JID。任何帮助都将不胜感激!
1个回答

20

首先,目前最好的JID参考资料是RFC 6122

我本来只想在这里给您提供正则表达式,但有点走火入魔,实现了所有规范:

import re
import sys
import socket
import encodings.idna
import stringprep

# These characters aren't allowed in domain names that are used
# in XMPP
BAD_DOMAIN_ASCII = "".join([chr(c) for c in range(0,0x2d) +
                    [0x2e, 0x2f] +
                    range(0x3a,0x41) +
                    range(0x5b,0x61) +
                    range(0x7b, 0x80)])

# check bi-directional character validity
def bidi(chars):
    RandAL = map(stringprep.in_table_d1, chars)
    for c in RandAL:
        if c:
            # There is a RandAL char in the string. Must perform further
            # tests:
            # 1) The characters in section 5.8 MUST be prohibited.
            # This is table C.8, which was already checked
            # 2) If a string contains any RandALCat character, the string
            # MUST NOT contain any LCat character.
            if filter(stringprep.in_table_d2, chars):
                raise UnicodeError("Violation of BIDI requirement 2")

            # 3) If a string contains any RandALCat character, a
            # RandALCat character MUST be the first character of the
            # string, and a RandALCat character MUST be the last
            # character of the string.
            if not RandAL[0] or not RandAL[-1]:
                raise UnicodeError("Violation of BIDI requirement 3")

def nodeprep(u):
    chars = list(unicode(u))
    i = 0
    while i < len(chars):
        c = chars[i]
        # map to nothing
        if stringprep.in_table_b1(c):
            del chars[i]
        else:
            # case fold
            chars[i] = stringprep.map_table_b2(c)
            i += 1
    # NFKC
    chars = stringprep.unicodedata.normalize("NFKC", "".join(chars))
    for c in chars:
        if (stringprep.in_table_c11(c) or
            stringprep.in_table_c12(c) or
            stringprep.in_table_c21(c) or
            stringprep.in_table_c22(c) or
            stringprep.in_table_c3(c) or
            stringprep.in_table_c4(c) or
            stringprep.in_table_c5(c) or
            stringprep.in_table_c6(c) or
            stringprep.in_table_c7(c) or
            stringprep.in_table_c8(c) or
            stringprep.in_table_c9(c) or
            c in "\"&'/:<>@"):
            raise UnicodeError("Invalid node character")

    bidi(chars)

    return chars

def resourceprep(res):
    chars = list(unicode(res))
    i = 0
    while i < len(chars):
        c = chars[i]
        # map to nothing
        if stringprep.in_table_b1(c):
            del chars[i]
        else:
            i += 1
    # NFKC
    chars = stringprep.unicodedata.normalize("NFKC", "".join(chars))
    for c in chars:
        if (stringprep.in_table_c12(c) or
            stringprep.in_table_c21(c) or
            stringprep.in_table_c22(c) or
            stringprep.in_table_c3(c) or
            stringprep.in_table_c4(c) or
            stringprep.in_table_c5(c) or
            stringprep.in_table_c6(c) or
            stringprep.in_table_c7(c) or
            stringprep.in_table_c8(c) or
            stringprep.in_table_c9(c)):
            raise UnicodeError("Invalid node character")

    bidi(chars)
    
    return chars

def parse_jid(jid):
    # first pass
    m = re.match("^(?:([^\"&'/:<>@]{1,1023})@)?([^/@]{1,1023})(?:/(.{1,1023}))?$", jid)
    if not m:
        return False
    
    (node, domain, resource) = m.groups()
    try:
        # ipv4 address?
        socket.inet_pton(socket.AF_INET, domain)
    except socket.error:
        # ipv6 address?
        try:
            socket.inet_pton(socket.AF_INET6, domain)
        except socket.error:
            # domain name
            dom = []
            for label in domain.split("."):
                try:
                    label = encodings.idna.nameprep(unicode(label))
                    encodings.idna.ToASCII(label)
                except UnicodeError:
                    return False
                
                # UseSTD3ASCIIRules is set, but Python's nameprep doesn't enforce it.
                # a) Verify the absence of non-LDH ASCII code points; that is, the
                for c in label:
                    if c in BAD_DOMAIN_ASCII:
                        return False
                # Verify the absence of leading and trailing hyphen-minus
                if label[0] == '-' or label[-1] == "-":
                    return False
                dom.append(label)
            domain = ".".join(dom)
    try:
        if node is not None:
            node = nodeprep(node)
        if resource is not None:
            resource = resourceprep(resource)
    except UnicodeError:
        return False
    
    return node, domain, resource

if __name__ == "__main__":
    results = parse_jid(sys.argv[1])
    if not results:
        print "FAIL"
    else:   
        print results

是的,这需要做很多工作。所有这些都有很好的原因,但我们希望在未来能够通过 précis 工作组简化它。


抱歉晚了请求;我本来打算按照您的方式实现,但我想知道在stringprep中是否迭代代码点实际上是正确的。在stringprep RFC中,他们谈论字符,这不一定等同于代码点(考虑组合变音符号)。或者我错过了有关Unicode术语的某些内容吗? - Jonas Schäfer
字符串预处理RFC是在IETF对Unicode的理解不够细致的情况下编写的,而这种细致的理解是解决问题所必需的。当RFC中提到“字符”时,在大多数情况下它的意思是“代码点”。我们正在précis工作组中努力解决这个问题。 - Joe Hildebrand
为了帮助其他像我一样尝试在Python 3中使用此代码的人,需要进行两个更改:range()需要移交给itertools.chain(),而不是与+连接(并且一个列表也需要成为range()),并且需要删除unicode()调用。 - Kromey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接