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) |