| | 66 | |
|---|
| | 67 | |
|---|
| | 68 | # Conditional HTTP request support # |
|---|
| | 69 | |
|---|
| | 70 | def validate_etags(autotags=False): |
|---|
| | 71 | """Validate the current ETag against If-Match, If-None-Match headers. |
|---|
| | 72 | |
|---|
| | 73 | If autotags is True, an ETag response-header value will be provided |
|---|
| | 74 | from an MD5 hash of the response body (unless some other code has |
|---|
| | 75 | already provided an ETag header). If False (the default), the ETag |
|---|
| | 76 | will not be automatic. |
|---|
| | 77 | |
|---|
| | 78 | WARNING: the autotags feature is not designed for URL's which allow |
|---|
| | 79 | methods other than GET. For example, if a POST to the same URL returns |
|---|
| | 80 | no content, the automatic ETag will be incorrect, breaking a fundamental |
|---|
| | 81 | use for entity tags in a possibly destructive fashion. Likewise, if you |
|---|
| | 82 | raise 304 Not Modified, the response body will be empty, the ETag hash |
|---|
| | 83 | will be incorrect, and your application will break. |
|---|
| | 84 | See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 |
|---|
| | 85 | """ |
|---|
| | 86 | response = cherrypy.response |
|---|
| | 87 | |
|---|
| | 88 | # Guard against being run twice. |
|---|
| | 89 | if hasattr(response, "ETag"): |
|---|
| | 90 | return |
|---|
| | 91 | |
|---|
| | 92 | status, reason, msg = httptools.validStatus(response.status) |
|---|
| | 93 | |
|---|
| | 94 | etag = response.headers.get('ETag') |
|---|
| | 95 | |
|---|
| | 96 | # Automatic ETag generation. See warning in docstring. |
|---|
| | 97 | if (not etag) and autotags: |
|---|
| | 98 | if status == 200: |
|---|
| | 99 | etag = response.collapse_body() |
|---|
| | 100 | etag = '"%s"' % md5.new(etag).hexdigest() |
|---|
| | 101 | response.headers['ETag'] = etag |
|---|
| | 102 | |
|---|
| | 103 | response.ETag = etag |
|---|
| | 104 | |
|---|
| | 105 | # "If the request would, without the If-Match header field, result in |
|---|
| | 106 | # anything other than a 2xx or 412 status, then the If-Match header |
|---|
| | 107 | # MUST be ignored." |
|---|
| | 108 | if status >= 200 and status <= 299: |
|---|
| | 109 | request = cherrypy.request |
|---|
| | 110 | |
|---|
| | 111 | conditions = request.headers.elements('If-Match') or [] |
|---|
| | 112 | conditions = [str(x) for x in conditions] |
|---|
| | 113 | if conditions and not (conditions == ["*"] or etag in conditions): |
|---|
| | 114 | raise cherrypy.HTTPError(412, "If-Match failed: ETag %r did " |
|---|
| | 115 | "not match %r" % (etag, conditions)) |
|---|
| | 116 | |
|---|
| | 117 | conditions = request.headers.elements('If-None-Match') or [] |
|---|
| | 118 | conditions = [str(x) for x in conditions] |
|---|
| | 119 | if conditions == ["*"] or etag in conditions: |
|---|
| | 120 | if request.method in ("GET", "HEAD"): |
|---|
| | 121 | raise cherrypy.HTTPRedirect([], 304) |
|---|
| | 122 | else: |
|---|
| | 123 | raise cherrypy.HTTPError(412, "If-None-Match failed: ETag %r " |
|---|
| | 124 | "matched %r" % (etag, conditions)) |
|---|
| | 125 | |
|---|
| | 126 | def validate_since(): |
|---|
| | 127 | """Validate the current Last-Modified against If-Modified-Since headers. |
|---|
| | 128 | |
|---|
| | 129 | If no code has set the Last-Modified response header, then no validation |
|---|
| | 130 | will be performed. |
|---|
| | 131 | """ |
|---|
| | 132 | response = cherrypy.response |
|---|
| | 133 | lastmod = response.headers.get('Last-Modified') |
|---|
| | 134 | if lastmod: |
|---|
| | 135 | status, reason, msg = httptools.validStatus(response.status) |
|---|
| | 136 | |
|---|
| | 137 | request = cherrypy.request |
|---|
| | 138 | |
|---|
| | 139 | since = request.headers.get('If-Unmodified-Since') |
|---|
| | 140 | if since and since != lastmod: |
|---|
| | 141 | if (status >= 200 and status <= 299) or status == 412: |
|---|
| | 142 | raise cherrypy.HTTPError(412) |
|---|
| | 143 | |
|---|
| | 144 | since = request.headers.get('If-Modified-Since') |
|---|
| | 145 | if since and since == lastmod: |
|---|
| | 146 | if (status >= 200 and status <= 299) or status == 304: |
|---|
| | 147 | if request.method in ("GET", "HEAD"): |
|---|
| | 148 | raise cherrypy.HTTPRedirect([], 304) |
|---|
| | 149 | else: |
|---|
| | 150 | raise cherrypy.HTTPError(412) |
|---|
| | 151 | |
|---|