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

« back to all changes in this revision

Viewing changes to openid/yadis/accept.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
"""Functions for generating and parsing HTTP Accept: headers for
 
2
supporting server-directed content negotiation.
 
3
"""
 
4
 
 
5
def generateAcceptHeader(*elements):
 
6
    """Generate an accept header value
 
7
 
 
8
    [str or (str, float)] -> str
 
9
    """
 
10
    parts = []
 
11
    for element in elements:
 
12
        if type(element) is str:
 
13
            qs = "1.0"
 
14
            mtype = element
 
15
        else:
 
16
            mtype, q = element
 
17
            q = float(q)
 
18
            if q > 1 or q <= 0:
 
19
                raise ValueError('Invalid preference factor: %r' % q)
 
20
 
 
21
            qs = '%0.1f' % (q,)
 
22
 
 
23
        parts.append((qs, mtype))
 
24
 
 
25
    parts.sort()
 
26
    chunks = []
 
27
    for q, mtype in parts:
 
28
        if q == '1.0':
 
29
            chunks.append(mtype)
 
30
        else:
 
31
            chunks.append('%s; q=%s' % (mtype, q))
 
32
 
 
33
    return ', '.join(chunks)
 
34
 
 
35
def parseAcceptHeader(value):
 
36
    """Parse an accept header, ignoring any accept-extensions
 
37
 
 
38
    returns a list of tuples containing main MIME type, MIME subtype,
 
39
    and quality markdown.
 
40
 
 
41
    str -> [(str, str, float)]
 
42
    """
 
43
    chunks = [chunk.strip() for chunk in value.split(',')]
 
44
    accept = []
 
45
    for chunk in chunks:
 
46
        parts = [s.strip() for s in chunk.split(';')]
 
47
 
 
48
        mtype = parts.pop(0)
 
49
        if '/' not in mtype:
 
50
            # This is not a MIME type, so ignore the bad data
 
51
            continue
 
52
 
 
53
        main, sub = mtype.split('/', 1)
 
54
 
 
55
        for ext in parts:
 
56
            if '=' in ext:
 
57
                k, v = ext.split('=', 1)
 
58
                if k == 'q':
 
59
                    try:
 
60
                        q = float(v)
 
61
                        break
 
62
                    except ValueError:
 
63
                        # Ignore poorly formed q-values
 
64
                        pass
 
65
        else:
 
66
            q = 1.0
 
67
 
 
68
        accept.append((q, main, sub))
 
69
 
 
70
    accept.sort()
 
71
    accept.reverse()
 
72
    return [(main, sub, q) for (q, main, sub) in accept]
 
73
 
 
74
def matchTypes(accept_types, have_types):
 
75
    """Given the result of parsing an Accept: header, and the
 
76
    available MIME types, return the acceptable types with their
 
77
    quality markdowns.
 
78
 
 
79
    For example:
 
80
 
 
81
    >>> acceptable = parseAcceptHeader('text/html, text/plain; q=0.5')
 
82
    >>> matchTypes(acceptable, ['text/plain', 'text/html', 'image/jpeg'])
 
83
    [('text/html', 1.0), ('text/plain', 0.5)]
 
84
 
 
85
 
 
86
    Type signature: ([(str, str, float)], [str]) -> [(str, float)]
 
87
    """
 
88
    if not accept_types:
 
89
        # Accept all of them
 
90
        default = 1
 
91
    else:
 
92
        default = 0
 
93
 
 
94
    match_main = {}
 
95
    match_sub = {}
 
96
    for (main, sub, q) in accept_types:
 
97
        if main == '*':
 
98
            default = max(default, q)
 
99
            continue
 
100
        elif sub == '*':
 
101
            match_main[main] = max(match_main.get(main, 0), q)
 
102
        else:
 
103
            match_sub[(main, sub)] = max(match_sub.get((main, sub), 0), q)
 
104
 
 
105
    accepted_list = []
 
106
    order_maintainer = 0
 
107
    for mtype in have_types:
 
108
        main, sub = mtype.split('/')
 
109
        if (main, sub) in match_sub:
 
110
            q = match_sub[(main, sub)]
 
111
        else:
 
112
            q = match_main.get(main, default)
 
113
 
 
114
        if q:
 
115
            accepted_list.append((1 - q, order_maintainer, q, mtype))
 
116
            order_maintainer += 1
 
117
 
 
118
    accepted_list.sort()
 
119
    return [(mtype, q) for (_, _, q, mtype) in accepted_list]
 
120
 
 
121
def getAcceptable(accept_header, have_types):
 
122
    """Parse the accept header and return a list of available types in
 
123
    preferred order. If a type is unacceptable, it will not be in the
 
124
    resulting list.
 
125
 
 
126
    This is a convenience wrapper around matchTypes and
 
127
    parseAcceptHeader.
 
128
 
 
129
    (str, [str]) -> [str]
 
130
    """
 
131
    accepted = parseAcceptHeader(accept_header)
 
132
    preferred = matchTypes(accepted, have_types)
 
133
    return [mtype for (mtype, _) in preferred]