1 """ 2 Does parsing of ETag-related headers: If-None-Matches, If-Matches 3 4 Also If-Range parsing 5 """ 6 7 from webob.datetime_utils import ( 8 parse_date, 9 serialize_date, 10 ) 11 from webob.descriptors import _rx_etag 12 13 from webob.util import ( 14 header_docstring, 15 warn_deprecation, 16 ) 17 18 __all__ = ['AnyETag', 'NoETag', 'ETagMatcher', 'IfRange', 'etag_property'] 19 20 def etag_property(key, default, rfc_section, strong=True): 21 doc = header_docstring(key, rfc_section) 22 doc += " Converts it as a Etag." 23 def fget(req): 24 value = req.environ.get(key) 25 if not value: 26 return default 27 else: 28 return ETagMatcher.parse(value, strong=strong) 29 def fset(req, val): 30 if val is None: 31 req.environ[key] = None 32 else: 33 req.environ[key] = str(val) 34 def fdel(req): 35 del req.environ[key] 36 return property(fget, fset, fdel, doc=doc) 37 38 def _warn_weak_match_deprecated(): 39 warn_deprecation("weak_match is deprecated", '1.2', 3) 40 41 def _warn_if_range_match_deprecated(*args, **kw): # pragma: no cover 42 raise DeprecationWarning("IfRange.match[_response] API is deprecated") 43 44 45 class _AnyETag(object): 46 """ 47 Represents an ETag of *, or a missing ETag when matching is 'safe' 48 """ 49 50 def __repr__(self): 51 return '<ETag *>' 52 53 def __nonzero__(self): 54 return False 55 56 __bool__ = __nonzero__ # python 3 57 58 def __contains__(self, other): 59 return True 60 61 def weak_match(self, other): 62 _warn_weak_match_deprecated() 63 64 def __str__(self): 65 return '*' 66 67 AnyETag = _AnyETag() 68 69 class _NoETag(object): 70 """ 71 Represents a missing ETag when matching is unsafe 72 """ 73 74 def __repr__(self): 75 return '<No ETag>' 76 77 def __nonzero__(self): 78 return False 79 80 __bool__ = __nonzero__ # python 3 81 82 def __contains__(self, other): 83 return False 84 85 def weak_match(self, other): # pragma: no cover 86 _warn_weak_match_deprecated() 87 88 def __str__(self): 89 return '' 90 91 NoETag = _NoETag() 92 93 94 # TODO: convert into a simple tuple 95 96 class ETagMatcher(object): 97 def __init__(self, etags): 98 self.etags = etags 99 100 def __contains__(self, other): 101 return other in self.etags 102 103 def weak_match(self, other): # pragma: no cover 104 _warn_weak_match_deprecated() 105 106 def __repr__(self): 107 return '<ETag %s>' % (' or '.join(self.etags)) 108 109 @classmethod 110 def parse(cls, value, strong=True): 111 """ 112 Parse this from a header value 113 """ 114 if value == '*': 115 return AnyETag 116 if not value: 117 return cls([]) 118 matches = _rx_etag.findall(value) 119 if not matches: 120 return cls([value]) 121 elif strong: 122 return cls([t for w,t in matches if not w]) 123 else: 124 return cls([t for w,t in matches]) 125 126 def __str__(self): 127 return ', '.join(map('"%s"'.__mod__, self.etags)) 128 129 130 class IfRange(object): 131 def __init__(self, etag): 132 self.etag = etag 133 134 @classmethod 135 def parse(cls, value): 136 """ 137 Parse this from a header value. 138 """ 139 if not value: 140 return cls(AnyETag) 141 elif value.endswith(' GMT'): 142 # Must be a date 143 return IfRangeDate(parse_date(value)) 144 else: 145 return cls(ETagMatcher.parse(value)) 146 147 def __contains__(self, resp): 148 """ 149 Return True if the If-Range header matches the given etag or last_modified 150 """ 151 return resp.etag_strong in self.etag 152 153 def __nonzero__(self): 154 return bool(self.etag) 155 156 def __repr__(self): 157 return '%s(%r)' % ( 158 self.__class__.__name__, 159 self.etag 160 ) 161 162 def __str__(self): 163 return str(self.etag) if self.etag else '' 164 165 match = match_response = _warn_if_range_match_deprecated 166 167 __bool__ = __nonzero__ # python 3 168 169 class IfRangeDate(object): 170 def __init__(self, date): 171 self.date = date 172 173 def __contains__(self, resp): 174 last_modified = resp.last_modified 175 #if isinstance(last_modified, str): 176 # last_modified = parse_date(last_modified) 177 return last_modified and (last_modified <= self.date) 178 179 def __repr__(self): 180 return '%s(%r)' % ( 181 self.__class__.__name__, 182 self.date 183 #serialize_date(self.date) 184 ) 185 186 def __str__(self): 187 return serialize_date(self.date) 188 189 match = match_response = _warn_if_range_match_deprecated 190