Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 1445

Show
Ignore:
Timestamp:
11/22/06 13:44:12
Author:
fumanchu
Message:

Solution for #602 (ETag autotags are incorrect on 304):

  1. Added WARNING to docstring.
  2. Only generate ETag if status == 200.
  3. Now performs If-Match, If-None-Match tests even if no ETag provided.
  4. Corrected "< 299" to "<= 299".
Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/lib/cptools.py

    r1431 r1445  
    11"""Functions for builtin CherryPy tools.""" 
    22 
     3import md5 
    34import re 
    45 
     
    1415    If autotags is True, an ETag response-header value will be provided 
    1516    from an MD5 hash of the response body (unless some other code has 
    16     already provided an ETag header). If False, the ETag will not be 
    17     automatic, and if no other code has provided an ETag value, then no 
    18     checks will be performed against If-Match or If-None-Match headers. 
     17    already provided an ETag header). If False (the default), the ETag 
     18    will not be automatic. 
     19     
     20    WARNING: the autotags feature is not designed for URL's which allow 
     21    methods other than GET. For example, if a POST to the same URL returns 
     22    no content, the automatic ETag will be incorrect, breaking a fundamental 
     23    use for entity tags in a possibly destructive fashion. Likewise, if you 
     24    raise 304 Not Modified, the response body will be empty, the ETag hash 
     25    will be incorrect, and your application will break. 
     26    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 
    1927    """ 
    2028    response = cherrypy.response 
     
    2432        return 
    2533     
     34    status, reason, msg = _http.valid_status(response.status) 
     35     
    2636    etag = response.headers.get('ETag') 
    2737     
     38    # Automatic ETag generation. See warning in docstring. 
    2839    if (not etag) and autotags: 
    29         import md5 
    30         etag = '"%s"' % md5.new(response.collapse_body()).hexdigest() 
    31         response.headers['ETag'] = etag 
    32      
    33     if etag: 
    34         response.ETag = etag 
    35          
    36         status, reason, msg = _http.valid_status(response.status) 
    37          
     40        if status == 200: 
     41            etag = response.collapse_body() 
     42            etag = '"%s"' % md5.new(etag).hexdigest() 
     43            response.headers['ETag'] = etag 
     44     
     45    response.ETag = etag 
     46     
     47    # "If the request would, without the If-Match header field, result in 
     48    # anything other than a 2xx or 412 status, then the If-Match header 
     49    # MUST be ignored." 
     50    if status >= 200 and status <= 299: 
    3851        request = cherrypy.request 
    3952         
     
    4154        conditions = [str(x) for x in conditions] 
    4255        if conditions and not (conditions == ["*"] or etag in conditions): 
    43             if status >= 200 and status < 299: 
    44                 raise cherrypy.HTTPError(412
     56            raise cherrypy.HTTPError(412, "If-Match failed: ETag %r did " 
     57                                     "not match %r" % (etag, conditions)
    4558         
    4659        conditions = request.headers.elements('If-None-Match') or [] 
    4760        conditions = [str(x) for x in conditions] 
    4861        if conditions == ["*"] or etag in conditions: 
    49             if status >= 200 and status < 299
    50                 if request.method in ("GET", "HEAD"): 
    51                     raise cherrypy.HTTPRedirect([], 304) 
    52                 else: 
    53                     raise cherrypy.HTTPError(412
     62            if request.method in ("GET", "HEAD")
     63                raise cherrypy.HTTPRedirect([], 304) 
     64            else: 
     65                raise cherrypy.HTTPError(412, "If-None-Match failed: ETag %r " 
     66                                         "matched %r" % (etag, conditions)
    5467 
    5568def validate_since(): 
     
    6881        since = request.headers.get('If-Unmodified-Since') 
    6982        if since and since != lastmod: 
    70             if (status >= 200 and status < 299) or status == 412: 
     83            if (status >= 200 and status <= 299) or status == 412: 
    7184                raise cherrypy.HTTPError(412) 
    7285         
    7386        since = request.headers.get('If-Modified-Since') 
    7487        if since and since == lastmod: 
    75             if (status >= 200 and status < 299) or status == 304: 
     88            if (status >= 200 and status <= 299) or status == 304: 
    7689                if request.method in ("GET", "HEAD"): 
    7790                    raise cherrypy.HTTPRedirect([], 304) 
  • trunk/cherrypy/test/test_etags.py

    r1311 r1445  
    1111        resource.exposed = True 
    1212         
    13         def fail(self): 
    14             raise cherrypy.HTTPError(412) 
     13        def fail(self, code): 
     14            code = int(code) 
     15            if 300 <= code <= 399: 
     16                raise cherrypy.HTTPRedirect([], code) 
     17            else: 
     18                raise cherrypy.HTTPError(code) 
    1519        fail.exposed = True 
    1620     
     
    2933        self.assertHeader('Content-Type', 'text/html') 
    3034        self.assertBody('Oh wah ta goo Siam.') 
    31         self.assertHeader('ETag') 
    32         for k, v in self.headers: 
    33             if k.lower() == 'etag': 
    34                 etag = v 
    35                 break 
     35        etag = self.assertHeader('ETag') 
    3636         
    3737        # Test If-Match (both valid and invalid) 
     
    3939        self.assertStatus("200 OK") 
    4040        self.getPage("/resource", headers=[('If-Match', "*")]) 
     41        self.assertStatus("200 OK") 
     42        self.getPage("/resource", headers=[('If-Match', "*")], method="POST") 
    4143        self.assertStatus("200 OK") 
    4244        self.getPage("/resource", headers=[('If-Match', "a bogus tag")]) 
     
    5355        self.assertStatus("200 OK") 
    5456         
    55         # Test raising 412 in page handler 
    56         self.getPage("/fail", headers=[('If-Match', etag)]) 
     57        # Test raising errors in page handler 
     58        self.getPage("/fail/412", headers=[('If-Match', etag)]) 
    5759        self.assertStatus(412) 
     60        self.getPage("/fail/304", headers=[('If-Match', etag)]) 
     61        self.assertStatus(304) 
     62        self.getPage("/fail/412", headers=[('If-None-Match', "*")]) 
     63        self.assertStatus(412) 
     64        self.getPage("/fail/304", headers=[('If-None-Match', "*")]) 
     65        self.assertStatus(304) 
    5866 
    5967 

Hosted by WebFaction

Log in as guest/cpguest to create tickets