def format_time(self, value): if value is None: return u'<td>-</td>' else: return u'<td>%s</td>' % relative_time(value, 0, 0)
def checkCaching(self): "Examine HTTP caching characteristics." # TODO: check URI for query string, message about HTTP/1.0 if so # known Cache-Control directives that don't allow duplicates known_cc = ["max-age", "no-store", "s-maxage", "public", "private", "pre-check", "post-check", "stale-while-revalidate", "stale-if-error", ] cc_set = self.parsed_hdrs.get('cache-control', []) cc_list = [k for (k,v) in cc_set] cc_dict = dict(cc_set) cc_keys = cc_dict.keys() # check for mis-capitalised directives / # assure there aren't any dup directives with different values for cc in cc_keys: if cc.lower() in known_cc and cc != cc.lower(): self.setMessage('header-cache-control', rs.CC_MISCAP, cc_lower = cc.lower(), cc=cc ) if cc in known_cc and cc_list.count(cc) > 1: self.setMessage('header-cache-control', rs.CC_DUP, cc=cc ) # Who can store this? if self.method not in cacheable_methods: self.store_shared = self.store_private = False self.setMessage('method', rs.METHOD_UNCACHEABLE, method=self.method ) return # bail; nothing else to see here elif 'no-store' in cc_keys: self.store_shared = self.store_private = False self.setMessage('header-cache-control', rs.NO_STORE) return # bail; nothing else to see here elif 'private' in cc_keys: self.store_shared = False self.store_private = True self.setMessage('header-cache-control', rs.PRIVATE_CC) elif 'authorization' in [k.lower() for k, v in self.req_hdrs] and \ not 'public' in cc_keys: self.store_shared = False self.store_private = True self.setMessage('header-cache-control', rs.PRIVATE_AUTH) else: self.store_shared = self.store_private = True self.setMessage('header-cache-control', rs.STOREABLE) # no-cache? if 'no-cache' in cc_keys: if "last-modified" not in self.parsed_hdrs.keys() and \ "etag" not in self.parsed_hdrs.keys(): self.setMessage('header-cache-control', rs.NO_CACHE_NO_VALIDATOR ) else: self.setMessage('header-cache-control', rs.NO_CACHE) return # pre-check / post-check if 'pre-check' in cc_keys or 'post-check' in cc_keys: if 'pre-check' not in cc_keys or 'post_check' not in cc_keys: self.setMessage('header-cache-control', rs.CHECK_SINGLE) else: pre_check = post_check = None try: pre_check = int(cc_dict['pre-check']) post_check = int(cc_dict['post-check']) except ValueError: self.setMessage('header-cache-control', rs.CHECK_NOT_INTEGER ) if pre_check is not None and post_check is not None: if pre_check == 0 and post_check == 0: self.setMessage('header-cache-control', rs.CHECK_ALL_ZERO ) elif post_check > pre_check: self.setMessage('header-cache-control', rs.CHECK_POST_BIGGER ) post_check = pre_check elif post_check == 0: self.setMessage('header-cache-control', rs.CHECK_POST_ZERO ) else: self.setMessage('header-cache-control', rs.CHECK_POST_PRE, pre_check=pre_check, post_check=post_check ) # vary? vary = self.parsed_hdrs.get('vary', set()) if "*" in vary: self.setMessage('header-vary', rs.VARY_ASTERISK) return # bail; nothing else to see here elif len(vary) > 3: self.setMessage('header-vary', rs.VARY_COMPLEX, vary_count=f_num(len(vary)) ) else: if "user-agent" in vary: self.setMessage('header-vary', rs.VARY_USER_AGENT) if "host" in vary: self.setMessage('header-vary', rs.VARY_HOST) # TODO: enumerate the axes in a message # calculate age age_hdr = self.parsed_hdrs.get('age', 0) date_hdr = self.parsed_hdrs.get('date', 0) if date_hdr > 0: apparent_age = max(0, int(self.res_ts - date_hdr)) else: apparent_age = 0 current_age = max(apparent_age, age_hdr) current_age_str = relative_time(current_age, 0, 0) age_str = relative_time(age_hdr, 0, 0) self.age = age_hdr if age_hdr >= 1: self.setMessage('header-age header-date', rs.CURRENT_AGE, age=age_str) # Check for clock skew and dateless origin server. skew = date_hdr - self.res_ts + age_hdr if not date_hdr: self.setMessage('', rs.DATE_CLOCKLESS) if self.parsed_hdrs.has_key('expires') or \ self.parsed_hdrs.has_key('last-modified'): self.setMessage('header-expires header-last-modified', rs.DATE_CLOCKLESS_BAD_HDR) elif age_hdr > max_clock_skew and current_age - skew < max_clock_skew: self.setMessage('header-date header-age', rs.AGE_PENALTY) elif abs(skew) > max_clock_skew: self.setMessage('header-date', rs.DATE_INCORRECT, clock_skew_string=relative_time(skew, 0, 2) ) else: self.setMessage('header-date', rs.DATE_CORRECT) # calculate freshness freshness_lifetime = 0 has_explicit_freshness = False has_cc_freshness = False freshness_hdrs = ['header-date'] if 's-maxage' in cc_keys: # TODO: differentiate message for s-maxage freshness_lifetime = cc_dict['s-maxage'] freshness_hdrs.append('header-cache-control') has_explicit_freshness = True has_cc_freshness = True elif 'max-age' in cc_keys: freshness_lifetime = cc_dict['max-age'] freshness_hdrs.append('header-cache-control') has_explicit_freshness = True has_cc_freshness = True elif self.parsed_hdrs.has_key('expires'): has_explicit_freshness = True freshness_hdrs.append('header-expires') if self.parsed_hdrs.has_key('date'): freshness_lifetime = self.parsed_hdrs['expires'] - \ self.parsed_hdrs['date'] else: freshness_lifetime = self.parsed_hdrs['expires'] - \ self.res_ts # ? freshness_left = freshness_lifetime - current_age freshness_left_str = relative_time(abs(int(freshness_left)), 0, 0) freshness_lifetime_str = relative_time(int(freshness_lifetime), 0, 0) self.freshness_lifetime = freshness_lifetime fresh = freshness_left > 0 if has_explicit_freshness: if fresh: self.setMessage(" ".join(freshness_hdrs), rs.FRESHNESS_FRESH, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age = current_age_str ) elif has_cc_freshness and self.age > freshness_lifetime: self.setMessage(" ".join(freshness_hdrs), rs.FRESHNESS_STALE_CACHE, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age = current_age_str ) else: self.setMessage(" ".join(freshness_hdrs), rs.FRESHNESS_STALE_ALREADY, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age = current_age_str ) # can heuristic freshness be used? elif self.res_status in heuristic_cacheable_status: self.setMessage('header-last-modified', rs.FRESHNESS_HEURISTIC) else: self.setMessage('', rs.FRESHNESS_NONE) # can stale responses be served? if 'must-revalidate' in cc_keys: if fresh: self.setMessage('header-cache-control', rs.FRESH_MUST_REVALIDATE ) elif has_explicit_freshness: self.setMessage('header-cache-control', rs.STALE_MUST_REVALIDATE ) elif 'proxy-revalidate' in cc_keys or 's-maxage' in cc_keys: if fresh: self.setMessage('header-cache-control', rs.FRESH_PROXY_REVALIDATE ) elif has_explicit_freshness: self.setMessage('header-cache-control', rs.STALE_PROXY_REVALIDATE ) else: if fresh: self.setMessage('header-cache-control', rs.FRESH_SERVABLE) elif has_explicit_freshness: self.setMessage('header-cache-control', rs.STALE_SERVABLE) # public? if 'public' in cc_keys: # TODO: check for authentication in request self.setMessage('header-cache-control', rs.PUBLIC)