25 from hashlib import sha1 # pylint: disable=E0611 |
25 from hashlib import sha1 # pylint: disable=E0611 |
26 from Cookie import SimpleCookie |
26 from Cookie import SimpleCookie |
27 from calendar import timegm |
27 from calendar import timegm |
28 from datetime import date, datetime |
28 from datetime import date, datetime |
29 from urlparse import urlsplit |
29 from urlparse import urlsplit |
|
30 import httplib |
30 from itertools import count |
31 from itertools import count |
31 from warnings import warn |
32 from warnings import warn |
32 |
33 |
33 from rql.utils import rqlvar_maker |
34 from rql.utils import rqlvar_maker |
34 |
35 |
41 from cubicweb.uilib import remove_html_tags, js |
42 from cubicweb.uilib import remove_html_tags, js |
42 from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid |
43 from cubicweb.utils import SizeConstrainedList, HTMLHead, make_uid |
43 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT |
44 from cubicweb.view import STRICT_DOCTYPE, TRANSITIONAL_DOCTYPE_NOEXT |
44 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit, |
45 from cubicweb.web import (INTERNAL_FIELD_VALUE, LOGGER, NothingToEdit, |
45 RequestError, StatusResponse) |
46 RequestError, StatusResponse) |
46 from cubicweb.web.httpcache import GMTOFFSET |
47 from cubicweb.web.httpcache import GMTOFFSET, get_validators |
47 from cubicweb.web.http_headers import Headers, Cookie |
48 from cubicweb.web.http_headers import Headers, Cookie, parseDateTime |
48 |
49 |
49 _MARKER = object() |
50 _MARKER = object() |
50 |
51 |
51 def build_cb_uid(seed): |
52 def build_cb_uid(seed): |
52 sha = sha1('%s%s%s' % (time.time(), seed, random.random())) |
53 sha = sha1('%s%s%s' % (time.time(), seed, random.random())) |
748 if controller in registered_controllers: |
749 if controller in registered_controllers: |
749 return controller |
750 return controller |
750 return 'view' |
751 return 'view' |
751 |
752 |
752 def validate_cache(self): |
753 def validate_cache(self): |
753 """raise a `DirectResponse` exception if a cached page along the way |
754 """raise a `StatusResponse` exception if a cached page along the way |
754 exists and is still usable. |
755 exists and is still usable. |
755 |
756 |
756 calls the client-dependant implementation of `_validate_cache` |
757 calls the client-dependant implementation of `_validate_cache` |
757 """ |
758 """ |
758 self._validate_cache() |
759 modified = True |
759 if self.http_method() == 'HEAD': |
760 if self.get_header('Cache-Control') not in ('max-age=0', 'no-cache'): |
760 raise StatusResponse(200, '') |
761 # Here, we search for any invalid 'not modified' condition |
|
762 # see http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3 |
|
763 validators = get_validators(self._headers_in) |
|
764 if validators: # if we have no |
|
765 modified = any(func(val, self.headers_out) for func, val in validators) |
|
766 # Forge expected response |
|
767 if modified: |
|
768 if 'Expires' not in self.headers_out: |
|
769 # Expires header seems to be required by IE7 -- Are you sure ? |
|
770 self.add_header('Expires', 'Sat, 01 Jan 2000 00:00:00 GMT') |
|
771 if self.http_method() == 'HEAD': |
|
772 raise StatusResponse(200, '') |
|
773 # /!\ no raise, the function returns and we keep processing the request) |
|
774 else: |
|
775 # overwrite headers_out to forge a brand new not-modified response |
|
776 self.headers_out = self._forge_cached_headers() |
|
777 if self.http_method() in ('HEAD', 'GET'): |
|
778 raise StatusResponse(httplib.NOT_MODIFIED) |
|
779 else: |
|
780 raise StatusResponse(httplib.PRECONDITION_FAILED) |
761 |
781 |
762 # abstract methods to override according to the web front-end ############# |
782 # abstract methods to override according to the web front-end ############# |
763 |
783 |
764 def http_method(self): |
784 def http_method(self): |
765 """returns 'POST', 'GET', 'HEAD', etc.""" |
785 """returns 'POST', 'GET', 'HEAD', etc.""" |
766 raise NotImplementedError() |
786 raise NotImplementedError() |
767 |
787 |
768 def _validate_cache(self): |
788 def _forge_cached_headers(self): |
769 """raise a `DirectResponse` exception if a cached page along the way |
789 # overwrite headers_out to forge a brand new not-modified response |
770 exists and is still usable |
790 headers = Headers() |
771 """ |
791 for header in ( |
772 raise NotImplementedError() |
792 # Required from sec 10.3.5: |
|
793 'date', 'etag', 'content-location', 'expires', |
|
794 'cache-control', 'vary', |
|
795 # Others: |
|
796 'server', 'proxy-authenticate', 'www-authenticate', 'warning'): |
|
797 value = self._headers_in.getRawHeaders(header) |
|
798 if value is not None: |
|
799 headers.setRawHeaders(header, value) |
|
800 return headers |
773 |
801 |
774 def relative_path(self, includeparams=True): |
802 def relative_path(self, includeparams=True): |
775 """return the normalized path of the request (ie at least relative |
803 """return the normalized path of the request (ie at least relative |
776 to the instance's root, but some other normalization may be needed |
804 to the instance's root, but some other normalization may be needed |
777 so that the returned path may be used to compare to generated urls |
805 so that the returned path may be used to compare to generated urls |