[3296] | 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 |
---|