1 | from DateTime import DateTime |
---|
2 | from Products.CMFCore.utils import getToolByName |
---|
3 | from Products.CMFCore.utils import parse_etags |
---|
4 | # ^^^ There was a whole bunch of code here for plone versions that |
---|
5 | # didn't have a recent enough version of CMF to include above |
---|
6 | # parse_etags method. I removed it. If needed it can be |
---|
7 | # resurrected. [Reinout] |
---|
8 | from Products.CacheSetup.config import CACHE_TOOL_ID |
---|
9 | |
---|
10 | #from AccessControl import ModuleSecurityInfo |
---|
11 | #security = ModuleSecurityInfo( 'Products.CMFCore.utils' ) |
---|
12 | #security.declarePrivate('_setCacheHeaders') |
---|
13 | |
---|
14 | def _setCacheHeaders(obj, extra_context, rule=None, header_set=None, |
---|
15 | expr_context=None): |
---|
16 | """Set cache headers according to cache policy manager for the obj.""" |
---|
17 | REQUEST = getattr(obj, 'REQUEST', None) |
---|
18 | if REQUEST is None: |
---|
19 | return |
---|
20 | if rule is None: |
---|
21 | pcs = getToolByName(obj, CACHE_TOOL_ID, None) |
---|
22 | object = obj.getParentNode() |
---|
23 | view = obj.getId() |
---|
24 | member = pcs.getMember() |
---|
25 | (rule, header_set) = pcs.getRuleAndHeaderSet(REQUEST, object, |
---|
26 | view, member) |
---|
27 | if header_set is None: |
---|
28 | return |
---|
29 | expr_context = rule._getExpressionContext(REQUEST, object, |
---|
30 | view, member, |
---|
31 | keywords=extra_context) |
---|
32 | elif header_set is None: |
---|
33 | return |
---|
34 | RESPONSE = REQUEST.RESPONSE |
---|
35 | (headers_to_add, |
---|
36 | headers_to_remove) = header_set.getHeaders(expr_context) |
---|
37 | for h in headers_to_remove: |
---|
38 | if RESPONSE.headers.has_key(h.lower()): |
---|
39 | del RESPONSE.headers[h.lower()] |
---|
40 | elif RESPONSE.headers.has_key(h): |
---|
41 | del RESPONSE.headers[h] |
---|
42 | for key, value in headers_to_add: |
---|
43 | if key == 'ETag': |
---|
44 | RESPONSE.setHeader(key, value, literal=1) |
---|
45 | else: |
---|
46 | RESPONSE.setHeader(key, value) |
---|
47 | |
---|
48 | #security.declarePrivate('_checkConditionalGET') |
---|
49 | def _checkConditionalGET(obj, extra_context, rule, header_set, |
---|
50 | expr_context): |
---|
51 | """A conditional GET is done using one or both of the request |
---|
52 | headers: |
---|
53 | |
---|
54 | If-Modified-Since: Date |
---|
55 | If-None-Match: list ETags (comma delimited, sometimes quoted) |
---|
56 | |
---|
57 | If both conditions are present, both must be satisfied. |
---|
58 | |
---|
59 | This method checks the caching policy manager to see if |
---|
60 | a content object's Last-modified date and ETag satisfy |
---|
61 | the conditional GET headers. |
---|
62 | |
---|
63 | Returns the tuple (last_modified, etag) if the conditional |
---|
64 | GET requirements are met and None if not. |
---|
65 | |
---|
66 | It is possible for one of the tuple elements to be None. |
---|
67 | For example, if there is no If-None-Match header and |
---|
68 | the caching policy does not specify an ETag, we will |
---|
69 | just return (last_modified, None). |
---|
70 | """ |
---|
71 | |
---|
72 | if header_set is None: |
---|
73 | return False |
---|
74 | |
---|
75 | # 304s not enabled |
---|
76 | if not header_set.getEnable304s(): |
---|
77 | return False |
---|
78 | |
---|
79 | REQUEST = getattr(obj, 'REQUEST', None) |
---|
80 | if REQUEST is None: |
---|
81 | return False |
---|
82 | |
---|
83 | if_modified_since = REQUEST.get_header('If-Modified-Since', None) |
---|
84 | if_none_match = REQUEST.get_header('If-None-Match', None) |
---|
85 | |
---|
86 | if if_modified_since is None and if_none_match is None: |
---|
87 | # not a conditional GET |
---|
88 | return False |
---|
89 | |
---|
90 | etag_matched = False |
---|
91 | |
---|
92 | # handle if-none-match |
---|
93 | if if_none_match: |
---|
94 | if not header_set.getEtag(): |
---|
95 | # no etag available |
---|
96 | return False |
---|
97 | |
---|
98 | content_etag = header_set.getEtagValue(expr_context) |
---|
99 | # ETag not available |
---|
100 | if content_etag is None: |
---|
101 | return False |
---|
102 | |
---|
103 | client_etags = parse_etags(if_none_match) |
---|
104 | if not client_etags: |
---|
105 | # bad if-none-match |
---|
106 | return False |
---|
107 | |
---|
108 | # is the current etag in the list of client-side etags? |
---|
109 | if (content_etag not in client_etags and '*' not in client_etags): |
---|
110 | return False |
---|
111 | |
---|
112 | etag_matched = True |
---|
113 | |
---|
114 | # handle if-modified-since |
---|
115 | if if_modified_since: |
---|
116 | if header_set.getLastModified() != 'yes': |
---|
117 | # no modification time available for content object |
---|
118 | return False |
---|
119 | |
---|
120 | # from CMFCore/FSFile.py: |
---|
121 | if_modified_since = if_modified_since.split(';')[0] |
---|
122 | # Some proxies seem to send invalid date strings for this |
---|
123 | # header. If the date string is not valid, we ignore it |
---|
124 | # rather than raise an error to be generally consistent |
---|
125 | # with common servers such as Apache (which can usually |
---|
126 | # understand the screwy date string as a lucky side effect |
---|
127 | # of the way they parse it). |
---|
128 | try: |
---|
129 | if_modified_since=long(DateTime(if_modified_since).timeTime()) |
---|
130 | except: |
---|
131 | # bad if-modified-since header - bail |
---|
132 | return False |
---|
133 | |
---|
134 | if if_modified_since < 0: # bad header |
---|
135 | return False |
---|
136 | |
---|
137 | content_mod_time = header_set.getLastModifiedValue(expr_context) |
---|
138 | if not content_mod_time: |
---|
139 | # if-modified-since but no content modification time available - bail |
---|
140 | return False |
---|
141 | content_mod_time = long(content_mod_time.timeTime()) |
---|
142 | if content_mod_time < 0: # bogus modification time |
---|
143 | return False |
---|
144 | |
---|
145 | # has content been modified since the if-modified-since time? |
---|
146 | if content_mod_time > if_modified_since: |
---|
147 | return False |
---|
148 | |
---|
149 | # If we generate an ETag, don't validate the conditional GET unless |
---|
150 | # the client supplies an ETag. This may be more conservative than the |
---|
151 | # spec requires. |
---|
152 | if header_set.getEtag(): |
---|
153 | if not etag_matched: |
---|
154 | return False |
---|
155 | |
---|
156 | _setCacheHeaders(obj, extra_context, rule, header_set, expr_context) |
---|
157 | REQUEST.RESPONSE.setStatus(304) |
---|
158 | return True |
---|