0001"""
0002Does parsing of ETag-related headers: If-None-Matches, If-Matches
0003
0004Also If-Range parsing
0005"""
0006
0007import webob
0008
0009__all__ = ['AnyETag', 'NoETag', 'ETagMatcher', 'IfRange', 'NoIfRange']
0010
0011class _AnyETag(object):
0012 """
0013 Represents an ETag of *, or a missing ETag when matching is 'safe'
0014 """
0015
0016 def __repr__(self):
0017 return '<ETag *>'
0018
0019 def __nonzero__(self):
0020 return False
0021
0022 def __contains__(self, other):
0023 return True
0024
0025 def weak_match(self, other):
0026 return True
0027
0028 def __str__(self):
0029 return '*'
0030
0031AnyETag = _AnyETag()
0032
0033class _NoETag(object):
0034 """
0035 Represents a missing ETag when matching is unsafe
0036 """
0037
0038 def __repr__(self):
0039 return '<No ETag>'
0040
0041 def __nonzero__(self):
0042 return False
0043
0044 def __contains__(self, other):
0045 return False
0046
0047 def weak_match(self, other):
0048 return False
0049
0050 def __str__(self):
0051 return ''
0052
0053NoETag = _NoETag()
0054
0055class ETagMatcher(object):
0056
0057 """
0058 Represents an ETag request. Supports containment to see if an
0059 ETag matches. You can also use
0060 ``etag_matcher.weak_contains(etag)`` to allow weak ETags to match
0061 (allowable for conditional GET requests, but not ranges or other
0062 methods).
0063 """
0064
0065 def __init__(self, etags, weak_etags=()):
0066 self.etags = etags
0067 self.weak_etags = weak_etags
0068
0069 def __contains__(self, other):
0070 return other in self.etags
0071
0072 def weak_match(self, other):
0073 if other.lower().startswith('w/'):
0074 other = other[2:]
0075 return other in self.etags or other in self.weak_etags
0076
0077 def __repr__(self):
0078 return '<ETag %s>' % (
0079 ' or '.join(self.etags))
0080
0081 def parse(cls, value):
0082 """
0083 Parse this from a header value
0084 """
0085 results = []
0086 weak_results = []
0087 while value:
0088 if value.lower().startswith('w/'):
0089
0090 weak = True
0091 value = value[2:]
0092 else:
0093 weak = False
0094 if value.startswith('"'):
0095 try:
0096 etag, rest = value[1:].split('"', 1)
0097 except ValueError:
0098 etag = value.strip(' ",')
0099 rest = ''
0100 else:
0101 rest = rest.strip(', ')
0102 else:
0103 if ',' in value:
0104 etag, rest = value.split(',', 1)
0105 rest = rest.strip()
0106 else:
0107 etag = value
0108 rest = ''
0109 if etag == '*':
0110 return AnyETag
0111 if etag:
0112 if weak:
0113 weak_results.append(etag)
0114 else:
0115 results.append(etag)
0116 value = rest
0117 return cls(results, weak_results)
0118 parse = classmethod(parse)
0119
0120 def __str__(self):
0121
0122 items = list(self.etags)
0123 for weak in self.weak_etags:
0124 items.append('W/%s' % weak)
0125 return ', '.join(items)
0126
0127class IfRange(object):
0128 """
0129 Parses and represents the If-Range header, which can be
0130 an ETag *or* a date
0131 """
0132 def __init__(self, etag=None, date=None):
0133 self.etag = etag
0134 self.date = date
0135
0136 def __repr__(self):
0137 if self.etag is None:
0138 etag = '*'
0139 else:
0140 etag = str(self.etag)
0141 if self.date is None:
0142 date = '*'
0143 else:
0144 date = webob._serialize_date(self.date)
0145 return '<%s etag=%s, date=%s>' % (
0146 self.__class__.__name__,
0147 etag, date)
0148
0149 def __str__(self):
0150 if self.etag is not None:
0151 return str(self.etag)
0152 elif self.date:
0153 return webob._serialize_date(self.date)
0154 else:
0155 return ''
0156
0157 def match(self, etag=None, last_modified=None):
0158 """
0159 Return True if the If-Range header matches the given etag or last_modified
0160 """
0161 if self.date is not None:
0162 if last_modified is None:
0163
0164 return False
0165 return last_modified <= self.date
0166 elif self.etag is not None:
0167 if not etag:
0168 return False
0169 return etag in self.etag
0170 return True
0171
0172 def match_response(self, response):
0173 """
0174 Return True if this matches the given ``webob.Response`` instance.
0175 """
0176 return self.match(etag=response.etag, last_modified=response.last_modified)
0177
0178
0179 def parse(cls, value):
0180 """
0181 Parse this from a header value.
0182 """
0183 date = etag = None
0184 if not value:
0185 etag = NoETag()
0186 elif value and value.endswith(' GMT'):
0187
0188 date = webob._parse_date(value)
0189 else:
0190 etag = ETagMatcher.parse(value)
0191 return cls(etag=etag, date=date)
0192 parse = classmethod(parse)
0193
0194class _NoIfRange(object):
0195 """
0196 Represents a missing If-Range header
0197 """
0198
0199 def __repr__(self):
0200 return '<Empty If-Range>'
0201
0202 def __str__(self):
0203 return ''
0204
0205 def __nonzero__(self):
0206 return False
0207
0208 def match(self, etag=None, last_modified=None):
0209 return True
0210
0211 def match_response(self, response):
0212 return True
0213
0214NoIfRange = _NoIfRange()