1"""
2Does parsing of ETag-related headers: If-None-Matches, If-Matches
3
4Also If-Range parsing
5"""
6
7from webob.datetime_utils import (
8    parse_date,
9    serialize_date,
10    )
11from webob.descriptors import _rx_etag
12
13from webob.util import (
14    header_docstring,
15    warn_deprecation,
16    )
17
18__all__ = ['AnyETag', 'NoETag', 'ETagMatcher', 'IfRange', 'etag_property']
19
20def 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
38def _warn_weak_match_deprecated():
39    warn_deprecation("weak_match is deprecated", '1.2', 3)
40
41def _warn_if_range_match_deprecated(*args, **kw): # pragma: no cover
42    raise DeprecationWarning("IfRange.match[_response] API is deprecated")
43
44
45class _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
67AnyETag = _AnyETag()
68
69class _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
91NoETag = _NoETag()
92
93
94# TODO: convert into a simple tuple
95
96class 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
130class 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
169class 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