web/request.py
changeset 8316 d5b1b75805dd
parent 8314 cfd6ab461849
child 8480 086cff6a306a
child 8496 e4d71fc0b701
equal deleted inserted replaced
8315:166e6d5d8e17 8316:d5b1b75805dd
    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