web/request.py
changeset 8316 d5b1b75805dd
parent 8314 cfd6ab461849
child 8480 086cff6a306a
child 8496 e4d71fc0b701
--- a/web/request.py	Thu Mar 15 17:57:40 2012 +0100
+++ b/web/request.py	Thu Mar 15 17:59:27 2012 +0100
@@ -27,6 +27,7 @@
 from calendar import timegm
 from datetime import date, datetime
 from urlparse import urlsplit
+import httplib
 from itertools import count
 from warnings import warn
 
@@ -43,8 +44,8 @@
 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT
 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit,
                           RequestError, StatusResponse)
-from cubicweb.web.httpcache import GMTOFFSET
-from cubicweb.web.http_headers import Headers, Cookie
+from cubicweb.web.httpcache import GMTOFFSET, get_validators
+from cubicweb.web.http_headers import Headers, Cookie, parseDateTime
 
 _MARKER = object()
 
@@ -750,14 +751,33 @@
         return 'view'
 
     def validate_cache(self):
-        """raise a `DirectResponse` exception if a cached page along the way
+        """raise a `StatusResponse` exception if a cached page along the way
         exists and is still usable.
 
         calls the client-dependant implementation of `_validate_cache`
         """
-        self._validate_cache()
-        if self.http_method() == 'HEAD':
-            raise StatusResponse(200, '')
+        modified = True
+        if self.get_header('Cache-Control') not in ('max-age=0', 'no-cache'):
+            # Here, we search for any invalid 'not modified' condition
+            # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3
+            validators = get_validators(self._headers_in)
+            if validators: # if we have no
+                modified = any(func(val, self.headers_out) for func, val in validators)
+        # Forge expected response
+        if modified:
+            if 'Expires' not in self.headers_out:
+                # Expires header seems to be required by IE7 -- Are you sure ?
+                self.add_header('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT')
+            if self.http_method() == 'HEAD':
+                raise StatusResponse(200, '')
+            # /!\ no raise, the function returns and we keep processing the request)
+        else:
+            # overwrite headers_out to forge a brand new not-modified response
+            self.headers_out = self._forge_cached_headers()
+            if self.http_method() in ('HEAD', 'GET'):
+                raise StatusResponse(httplib.NOT_MODIFIED)
+            else:
+                raise StatusResponse(httplib.PRECONDITION_FAILED)
 
     # abstract methods to override according to the web front-end #############
 
@@ -765,11 +785,19 @@
         """returns 'POST', 'GET', 'HEAD', etc."""
         raise NotImplementedError()
 
-    def _validate_cache(self):
-        """raise a `DirectResponse` exception if a cached page along the way
-        exists and is still usable
-        """
-        raise NotImplementedError()
+    def _forge_cached_headers(self):
+        # overwrite headers_out to forge a brand new not-modified response
+        headers = Headers()
+        for header in (
+            # Required from sec 10.3.5:
+            'date', 'etag', 'content-location', 'expires',
+            'cache-control', 'vary',
+            # Others:
+            'server', 'proxy-authenticate', 'www-authenticate', 'warning'):
+            value = self._headers_in.getRawHeaders(header)
+            if value is not None:
+                headers.setRawHeaders(header, value)
+        return headers
 
     def relative_path(self, includeparams=True):
         """return the normalized path of the request (ie at least relative