123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- """Functions for generating and parsing HTTP Accept: headers for
- supporting server-directed content negotiation.
- """
- def generateAcceptHeader(*elements):
- """Generate an accept header value
- [str or (str, float)] -> str
- """
- parts = []
- for element in elements:
- if type(element) is str:
- qs = "1.0"
- mtype = element
- else:
- mtype, q = element
- q = float(q)
- if q > 1 or q <= 0:
- raise ValueError('Invalid preference factor: %r' % q)
- qs = '%0.1f' % (q,)
- parts.append((qs, mtype))
- parts.sort()
- chunks = []
- for q, mtype in parts:
- if q == '1.0':
- chunks.append(mtype)
- else:
- chunks.append('%s; q=%s' % (mtype, q))
- return ', '.join(chunks)
- def parseAcceptHeader(value):
- """Parse an accept header, ignoring any accept-extensions
- returns a list of tuples containing main MIME type, MIME subtype,
- and quality markdown.
- str -> [(str, str, float)]
- """
- chunks = [chunk.strip() for chunk in value.split(',')]
- accept = []
- for chunk in chunks:
- parts = [s.strip() for s in chunk.split(';')]
- mtype = parts.pop(0)
- if '/' not in mtype:
- # This is not a MIME type, so ignore the bad data
- continue
- main, sub = mtype.split('/', 1)
- for ext in parts:
- if '=' in ext:
- k, v = ext.split('=', 1)
- if k == 'q':
- try:
- q = float(v)
- break
- except ValueError:
- # Ignore poorly formed q-values
- pass
- else:
- q = 1.0
- accept.append((q, main, sub))
- accept.sort()
- accept.reverse()
- return [(main, sub, q) for (q, main, sub) in accept]
- def matchTypes(accept_types, have_types):
- """Given the result of parsing an Accept: header, and the
- available MIME types, return the acceptable types with their
- quality markdowns.
- For example:
- >>> acceptable = parseAcceptHeader('text/html, text/plain; q=0.5')
- >>> matchTypes(acceptable, ['text/plain', 'text/html', 'image/jpeg'])
- [('text/html', 1.0), ('text/plain', 0.5)]
- Type signature: ([(str, str, float)], [str]) -> [(str, float)]
- """
- if not accept_types:
- # Accept all of them
- default = 1
- else:
- default = 0
- match_main = {}
- match_sub = {}
- for (main, sub, q) in accept_types:
- if main == '*':
- default = max(default, q)
- continue
- elif sub == '*':
- match_main[main] = max(match_main.get(main, 0), q)
- else:
- match_sub[(main, sub)] = max(match_sub.get((main, sub), 0), q)
- accepted_list = []
- order_maintainer = 0
- for mtype in have_types:
- main, sub = mtype.split('/')
- if (main, sub) in match_sub:
- q = match_sub[(main, sub)]
- else:
- q = match_main.get(main, default)
- if q:
- accepted_list.append((1 - q, order_maintainer, q, mtype))
- order_maintainer += 1
- accepted_list.sort()
- return [(mtype, q) for (_, _, q, mtype) in accepted_list]
- def getAcceptable(accept_header, have_types):
- """Parse the accept header and return a list of available types in
- preferred order. If a type is unacceptable, it will not be in the
- resulting list.
- This is a convenience wrapper around matchTypes and
- parseAcceptHeader.
- (str, [str]) -> [str]
- """
- accepted = parseAcceptHeader(accept_header)
- preferred = matchTypes(accepted, have_types)
- return [mtype for (mtype, _) in preferred]
|