~gary/python-openid/python-openid-2.2.1-patched

« back to all changes in this revision

Viewing changes to openid/yadis/xri.py

  • Committer: Launchpad Patch Queue Manager
  • Date: 2007-11-30 02:46:28 UTC
  • mfrom: (1.1.1 pyopenid-2.0)
  • Revision ID: launchpad@pqm.canonical.com-20071130024628-qktwsew3383iawmq
[rs=SteveA] upgrade to python-openid-2.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- test-case-name: openid.test.test_xri -*-
 
2
"""Utility functions for handling XRIs.
 
3
 
 
4
@see: XRI Syntax v2.0 at the U{OASIS XRI Technical Committee<http://www.oasis-open.org/committees/tc_home.php?wg_abbrev=xri>}
 
5
"""
 
6
 
 
7
import re
 
8
 
 
9
XRI_AUTHORITIES = ['!', '=', '@', '+', '$', '(']
 
10
 
 
11
try:
 
12
    unichr(0x10000)
 
13
except ValueError:
 
14
    # narrow python build
 
15
    UCSCHAR = [
 
16
        (0xA0, 0xD7FF),
 
17
        (0xF900, 0xFDCF),
 
18
        (0xFDF0, 0xFFEF),
 
19
        ]
 
20
 
 
21
    IPRIVATE = [
 
22
        (0xE000, 0xF8FF),
 
23
        ]
 
24
else:
 
25
    UCSCHAR = [
 
26
        (0xA0, 0xD7FF),
 
27
        (0xF900, 0xFDCF),
 
28
        (0xFDF0, 0xFFEF),
 
29
        (0x10000, 0x1FFFD),
 
30
        (0x20000, 0x2FFFD),
 
31
        (0x30000, 0x3FFFD),
 
32
        (0x40000, 0x4FFFD),
 
33
        (0x50000, 0x5FFFD),
 
34
        (0x60000, 0x6FFFD),
 
35
        (0x70000, 0x7FFFD),
 
36
        (0x80000, 0x8FFFD),
 
37
        (0x90000, 0x9FFFD),
 
38
        (0xA0000, 0xAFFFD),
 
39
        (0xB0000, 0xBFFFD),
 
40
        (0xC0000, 0xCFFFD),
 
41
        (0xD0000, 0xDFFFD),
 
42
        (0xE1000, 0xEFFFD),
 
43
        ]
 
44
 
 
45
    IPRIVATE = [
 
46
        (0xE000, 0xF8FF),
 
47
        (0xF0000, 0xFFFFD),
 
48
        (0x100000, 0x10FFFD),
 
49
        ]
 
50
 
 
51
 
 
52
_escapeme_re = re.compile('[%s]' % (''.join(
 
53
    map(lambda (m, n): u'%s-%s' % (unichr(m), unichr(n)),
 
54
        UCSCHAR + IPRIVATE)),))
 
55
 
 
56
 
 
57
def identifierScheme(identifier):
 
58
    """Determine if this identifier is an XRI or URI.
 
59
 
 
60
    @returns: C{"XRI"} or C{"URI"}
 
61
    """
 
62
    if identifier.startswith('xri://') or identifier[0] in XRI_AUTHORITIES:
 
63
        return "XRI"
 
64
    else:
 
65
        return "URI"
 
66
 
 
67
 
 
68
def toIRINormal(xri):
 
69
    """Transform an XRI to IRI-normal form."""
 
70
    if not xri.startswith('xri://'):
 
71
        xri = 'xri://' + xri
 
72
    return escapeForIRI(xri)
 
73
 
 
74
 
 
75
_xref_re = re.compile('\((.*?)\)')
 
76
 
 
77
 
 
78
def _escape_xref(xref_match):
 
79
    """Escape things that need to be escaped if they're in a cross-reference.
 
80
    """
 
81
    xref = xref_match.group()
 
82
    xref = xref.replace('/', '%2F')
 
83
    xref = xref.replace('?', '%3F')
 
84
    xref = xref.replace('#', '%23')
 
85
    return xref
 
86
 
 
87
 
 
88
def escapeForIRI(xri):
 
89
    """Escape things that need to be escaped when transforming to an IRI."""
 
90
    xri = xri.replace('%', '%25')
 
91
    xri = _xref_re.sub(_escape_xref, xri)
 
92
    return xri
 
93
 
 
94
 
 
95
def toURINormal(xri):
 
96
    """Transform an XRI to URI normal form."""
 
97
    return iriToURI(toIRINormal(xri))
 
98
 
 
99
 
 
100
def _percentEscapeUnicode(char_match):
 
101
    c = char_match.group()
 
102
    return ''.join(['%%%X' % (ord(octet),) for octet in c.encode('utf-8')])
 
103
 
 
104
 
 
105
def iriToURI(iri):
 
106
    """Transform an IRI to a URI by escaping unicode."""
 
107
    # According to RFC 3987, section 3.1, "Mapping of IRIs to URIs"
 
108
    return _escapeme_re.sub(_percentEscapeUnicode, iri)
 
109
 
 
110
 
 
111
def providerIsAuthoritative(providerID, canonicalID):
 
112
    """Is this provider ID authoritative for this XRI?
 
113
 
 
114
    @returntype: bool
 
115
    """
 
116
    # XXX: can't use rsplit until we require python >= 2.4.
 
117
    lastbang = canonicalID.rindex('!')
 
118
    parent = canonicalID[:lastbang]
 
119
    return parent == providerID
 
120
 
 
121
 
 
122
def rootAuthority(xri):
 
123
    """Return the root authority for an XRI.
 
124
 
 
125
    Example::
 
126
 
 
127
        rootAuthority("xri://@example") == "xri://@"
 
128
 
 
129
    @type xri: unicode
 
130
    @returntype: unicode
 
131
    """
 
132
    if xri.startswith('xri://'):
 
133
        xri = xri[6:]
 
134
    authority = xri.split('/', 1)[0]
 
135
    if authority[0] == '(':
 
136
        # Cross-reference.
 
137
        # XXX: This is incorrect if someone nests cross-references so there
 
138
        #   is another close-paren in there.  Hopefully nobody does that
 
139
        #   before we have a real xriparse function.  Hopefully nobody does
 
140
        #   that *ever*.
 
141
        root = authority[:authority.index(')') + 1]
 
142
    elif authority[0] in XRI_AUTHORITIES:
 
143
        # Other XRI reference.
 
144
        root = authority[0]
 
145
    else:
 
146
        # IRI reference.  XXX: Can IRI authorities have segments?
 
147
        segments = authority.split('!')
 
148
        segments = reduce(list.__add__,
 
149
            map(lambda s: s.split('*'), segments))
 
150
        root = segments[0]
 
151
 
 
152
    return XRI(root)
 
153
 
 
154
 
 
155
def XRI(xri):
 
156
    """An XRI object allowing comparison of XRI.
 
157
 
 
158
    Ideally, this would do full normalization and provide comparsion
 
159
    operators as per XRI Syntax.  Right now, it just does a bit of
 
160
    canonicalization by ensuring the xri scheme is present.
 
161
 
 
162
    @param xri: an xri string
 
163
    @type xri: unicode
 
164
    """
 
165
    if not xri.startswith('xri://'):
 
166
        xri = 'xri://' + xri
 
167
    return xri