Home | History | Annotate | Download | only in webob
      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