web/request.py
changeset 7164 93a19c1831aa
parent 7070 5f8e52d722c5
child 7187 496f51b92154
equal deleted inserted replaced
7162:62561ea082d2 7164:93a19c1831aa
   732                 self.debug('bad authorization %s (%s: %s)',
   732                 self.debug('bad authorization %s (%s: %s)',
   733                            auth, ex.__class__.__name__, ex)
   733                            auth, ex.__class__.__name__, ex)
   734         return None, None
   734         return None, None
   735 
   735 
   736     def parse_accept_header(self, header):
   736     def parse_accept_header(self, header):
   737         """returns an ordered list of preferred languages"""
   737         """returns an ordered list of accepted values"""
       
   738         try:
       
   739             value_parser, value_sort_key = ACCEPT_HEADER_PARSER[header.lower()]
       
   740         except KeyError:
       
   741             value_parser = value_sort_key = None
   738         accepteds = self.get_header(header, '')
   742         accepteds = self.get_header(header, '')
   739         values = []
   743         values = _parse_accept_header(accepteds, value_parser, value_sort_key)
   740         for info in accepteds.split(','):
   744         return (raw_value for (raw_value, parsed_value, score) in values)
   741             try:
       
   742                 value, scores = info.split(';', 1)
       
   743             except ValueError:
       
   744                 value = info
       
   745                 score = 1.0
       
   746             else:
       
   747                 for score in scores.split(';'):
       
   748                     try:
       
   749                         scorekey, scoreval = score.split('=')
       
   750                         if scorekey == 'q': # XXX 'level'
       
   751                             score = float(scoreval)
       
   752                     except ValueError:
       
   753                         continue
       
   754             values.append((score, value))
       
   755         values.sort(reverse=True)
       
   756         return (value for (score, value) in values)
       
   757 
   745 
   758     def header_if_modified_since(self):
   746     def header_if_modified_since(self):
   759         """If the HTTP header If-modified-since is set, return the equivalent
   747         """If the HTTP header If-modified-since is set, return the equivalent
   760         mx date time value (GMT), else return None
   748         mx date time value (GMT), else return None
   761         """
   749         """
   856         """returns an ordered list of preferred languages"""
   844         """returns an ordered list of preferred languages"""
   857         return [value.split('-')[0] for value in
   845         return [value.split('-')[0] for value in
   858                 self.parse_accept_header('Accept-Language')]
   846                 self.parse_accept_header('Accept-Language')]
   859 
   847 
   860 
   848 
       
   849 
       
   850 ## HTTP-accept parsers / utilies ##############################################
       
   851 def _mimetype_sort_key(accept_info):
       
   852     """accepted mimetypes must be sorted by :
       
   853 
       
   854     1/ highest score first
       
   855     2/ most specific mimetype first, e.g. :
       
   856        - 'text/html level=1' is more specific 'text/html'
       
   857        - 'text/html' is more specific than 'text/*'
       
   858        - 'text/*' itself more specific than '*/*'
       
   859 
       
   860     """
       
   861     raw_value, (media_type, media_subtype, media_type_params), score = accept_info
       
   862     # FIXME: handle '+' in media_subtype ? (should xhtml+xml have a
       
   863     # higher precedence than xml ?)
       
   864     if media_subtype == '*':
       
   865         score -= 0.0001
       
   866     if media_type == '*':
       
   867         score -= 0.0001
       
   868     return 1./score, media_type, media_subtype, 1./(1+len(media_type_params))
       
   869 
       
   870 def _charset_sort_key(accept_info):
       
   871     """accepted mimetypes must be sorted by :
       
   872 
       
   873     1/ highest score first
       
   874     2/ most specific charset first, e.g. :
       
   875        - 'utf-8' is more specific than '*'
       
   876     """
       
   877     raw_value, value, score = accept_info
       
   878     if value == '*':
       
   879         score -= 0.0001
       
   880     return 1./score, value
       
   881 
       
   882 def _parse_accept_header(raw_header, value_parser=None, value_sort_key=None):
       
   883     """returns an ordered list accepted types
       
   884 
       
   885     returned value is a list of 2-tuple (value, score), ordered
       
   886     by score. Exact type of `value` will depend on what `value_parser`
       
   887     will reutrn. if `value_parser` is None, then the raw value, as found
       
   888     in the http header, is used.
       
   889     """
       
   890     if value_sort_key is None:
       
   891         value_sort_key = lambda infos: 1./infos[-1]
       
   892     values = []
       
   893     for info in raw_header.split(','):
       
   894         score = 1.0
       
   895         other_params = {}
       
   896         try:
       
   897             value, infodef = info.split(';', 1)
       
   898         except ValueError:
       
   899             value = info
       
   900         else:
       
   901             for info in infodef.split(';'):
       
   902                 try:
       
   903                     infokey, infoval = info.split('=')
       
   904                     if infokey == 'q': # XXX 'level'
       
   905                         score = float(infoval)
       
   906                         continue
       
   907                 except ValueError:
       
   908                     continue
       
   909                 other_params[infokey] = infoval
       
   910         parsed_value = value_parser(value, other_params) if value_parser else value
       
   911         values.append( (value.strip(), parsed_value, score) )
       
   912     values.sort(key=value_sort_key)
       
   913     return values
       
   914 
       
   915 
       
   916 def _mimetype_parser(value, other_params):
       
   917     """return a 3-tuple
       
   918     (type, subtype, type_params) corresponding to the mimetype definition
       
   919     e.g. : for 'text/*', `mimetypeinfo` will be ('text', '*', {}), for
       
   920     'text/html;level=1', `mimetypeinfo` will be ('text', '*', {'level': '1'})
       
   921     """
       
   922     try:
       
   923         media_type, media_subtype = value.strip().split('/')
       
   924     except ValueError: # safety belt : '/' should always be present
       
   925         media_type = value.strip()
       
   926         media_subtype = '*'
       
   927     return (media_type, media_subtype, other_params)
       
   928 
       
   929 
       
   930 ACCEPT_HEADER_PARSER = {
       
   931     'accept': (_mimetype_parser, _mimetype_sort_key),
       
   932     'accept-charset': (None, _charset_sort_key),
       
   933     }
       
   934 
   861 from cubicweb import set_log_methods
   935 from cubicweb import set_log_methods
   862 set_log_methods(CubicWebRequestBase, LOGGER)
   936 set_log_methods(CubicWebRequestBase, LOGGER)