173 lines
3.7 KiB
Python
173 lines
3.7 KiB
Python
|
"""
|
||
|
Does parsing of ETag-related headers: If-None-Matches, If-Matches
|
||
|
|
||
|
Also If-Range parsing
|
||
|
"""
|
||
|
|
||
|
from webob.datetime_utils import (
|
||
|
parse_date,
|
||
|
serialize_date,
|
||
|
)
|
||
|
from webob.descriptors import _rx_etag
|
||
|
from webob.util import header_docstring
|
||
|
|
||
|
__all__ = ['AnyETag', 'NoETag', 'ETagMatcher', 'IfRange', 'etag_property']
|
||
|
|
||
|
|
||
|
def etag_property(key, default, rfc_section, strong=True):
|
||
|
doc = header_docstring(key, rfc_section)
|
||
|
doc += " Converts it as a Etag."
|
||
|
|
||
|
def fget(req):
|
||
|
value = req.environ.get(key)
|
||
|
if not value:
|
||
|
return default
|
||
|
else:
|
||
|
return ETagMatcher.parse(value, strong=strong)
|
||
|
|
||
|
def fset(req, val):
|
||
|
if val is None:
|
||
|
req.environ[key] = None
|
||
|
else:
|
||
|
req.environ[key] = str(val)
|
||
|
|
||
|
def fdel(req):
|
||
|
del req.environ[key]
|
||
|
|
||
|
return property(fget, fset, fdel, doc=doc)
|
||
|
|
||
|
|
||
|
class _AnyETag(object):
|
||
|
"""
|
||
|
Represents an ETag of *, or a missing ETag when matching is 'safe'
|
||
|
"""
|
||
|
|
||
|
def __repr__(self):
|
||
|
return '<ETag *>'
|
||
|
|
||
|
def __nonzero__(self):
|
||
|
return False
|
||
|
|
||
|
__bool__ = __nonzero__ # python 3
|
||
|
|
||
|
def __contains__(self, other):
|
||
|
return True
|
||
|
|
||
|
def __str__(self):
|
||
|
return '*'
|
||
|
|
||
|
|
||
|
AnyETag = _AnyETag()
|
||
|
|
||
|
|
||
|
class _NoETag(object):
|
||
|
"""
|
||
|
Represents a missing ETag when matching is unsafe
|
||
|
"""
|
||
|
|
||
|
def __repr__(self):
|
||
|
return '<No ETag>'
|
||
|
|
||
|
def __nonzero__(self):
|
||
|
return False
|
||
|
|
||
|
__bool__ = __nonzero__ # python 3
|
||
|
|
||
|
def __contains__(self, other):
|
||
|
return False
|
||
|
|
||
|
def __str__(self):
|
||
|
return ''
|
||
|
|
||
|
|
||
|
NoETag = _NoETag()
|
||
|
|
||
|
|
||
|
# TODO: convert into a simple tuple
|
||
|
|
||
|
class ETagMatcher(object):
|
||
|
def __init__(self, etags):
|
||
|
self.etags = etags
|
||
|
|
||
|
def __contains__(self, other):
|
||
|
return other in self.etags
|
||
|
|
||
|
def __repr__(self):
|
||
|
return '<ETag %s>' % (' or '.join(self.etags))
|
||
|
|
||
|
@classmethod
|
||
|
def parse(cls, value, strong=True):
|
||
|
"""
|
||
|
Parse this from a header value
|
||
|
"""
|
||
|
if value == '*':
|
||
|
return AnyETag
|
||
|
if not value:
|
||
|
return cls([])
|
||
|
matches = _rx_etag.findall(value)
|
||
|
if not matches:
|
||
|
return cls([value])
|
||
|
elif strong:
|
||
|
return cls([t for w, t in matches if not w])
|
||
|
else:
|
||
|
return cls([t for w, t in matches])
|
||
|
|
||
|
def __str__(self):
|
||
|
return ', '.join(map('"%s"'.__mod__, self.etags))
|
||
|
|
||
|
|
||
|
class IfRange(object):
|
||
|
def __init__(self, etag):
|
||
|
self.etag = etag
|
||
|
|
||
|
@classmethod
|
||
|
def parse(cls, value):
|
||
|
"""
|
||
|
Parse this from a header value.
|
||
|
"""
|
||
|
if not value:
|
||
|
return cls(AnyETag)
|
||
|
elif value.endswith(' GMT'):
|
||
|
# Must be a date
|
||
|
return IfRangeDate(parse_date(value))
|
||
|
else:
|
||
|
return cls(ETagMatcher.parse(value))
|
||
|
|
||
|
def __contains__(self, resp):
|
||
|
"""
|
||
|
Return True if the If-Range header matches the given etag or last_modified
|
||
|
"""
|
||
|
return resp.etag_strong in self.etag
|
||
|
|
||
|
def __nonzero__(self):
|
||
|
return bool(self.etag)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return '%s(%r)' % (
|
||
|
self.__class__.__name__,
|
||
|
self.etag
|
||
|
)
|
||
|
|
||
|
def __str__(self):
|
||
|
return str(self.etag) if self.etag else ''
|
||
|
|
||
|
__bool__ = __nonzero__ # python 3
|
||
|
|
||
|
|
||
|
class IfRangeDate(object):
|
||
|
def __init__(self, date):
|
||
|
self.date = date
|
||
|
|
||
|
def __contains__(self, resp):
|
||
|
last_modified = resp.last_modified
|
||
|
return last_modified and (last_modified <= self.date)
|
||
|
|
||
|
def __repr__(self):
|
||
|
return '%s(%r)' % (
|
||
|
self.__class__.__name__,
|
||
|
self.date
|
||
|
)
|
||
|
|
||
|
def __str__(self):
|
||
|
return serialize_date(self.date)
|