def parse(subject, value, red): try: date = rh.parse_date(value) except ValueError: red.set_message(subject, rs.BAD_DATE_SYNTAX) return None if date > red.res_ts: red.set_message(subject, rs.LM_FUTURE) return date else: red.set_message(subject, rs.LM_PRESENT, last_modified_string=rh.relative_time(date, red.res_ts)) return date
def parse(subject, value, red): try: date = rh.parse_date(value) except ValueError: red.set_message(subject, rs.BAD_DATE_SYNTAX) return None if date > red.res_ts: red.set_message(subject, rs.LM_FUTURE) return date else: red.set_message(subject, rs.LM_PRESENT, last_modified_string=rh.relative_time( date, red.res_ts)) return date
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(state): "Examine HTTP caching characteristics." # TODO: check URI for query string, message about HTTP/1.0 if so # get header values lm = state.parsed_hdrs.get('last-modified', None) date = state.parsed_hdrs.get('date', None) cc_set = state.parsed_hdrs.get('cache-control', []) cc_list = [k for (k, v) in cc_set] cc_dict = dict(cc_set) cc_keys = cc_dict.keys() # Last-Modified if lm: serv_date = date or state.res_ts if lm > (date or serv_date): state.set_message('header-last-modified', rs.LM_FUTURE) else: state.set_message('header-last-modified', rs.LM_PRESENT, last_modified_string=rh.relative_time( lm, serv_date)) # 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", ] # 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(): state.set_message('header-cache-control', rs.CC_MISCAP, cc_lower=cc.lower(), cc=cc) if cc in known_cc and cc_list.count(cc) > 1: state.set_message('header-cache-control', rs.CC_DUP, cc=cc) # Who can store this? if state.method not in cacheable_methods: state.store_shared = state.store_private = False state.set_message('method', rs.METHOD_UNCACHEABLE, method=state.method) return # bail; nothing else to see here elif 'no-store' in cc_keys: state.store_shared = state.store_private = False state.set_message('header-cache-control', rs.NO_STORE) return # bail; nothing else to see here elif 'private' in cc_keys: state.store_shared = False state.store_private = True state.set_message('header-cache-control', rs.PRIVATE_CC) elif 'authorization' in [k.lower() for k, v in state.req_hdrs] and \ not 'public' in cc_keys: state.store_shared = False state.store_private = True state.set_message('header-cache-control', rs.PRIVATE_AUTH) else: state.store_shared = state.store_private = True state.set_message('header-cache-control', rs.STOREABLE) # no-cache? if 'no-cache' in cc_keys: if "last-modified" not in state.parsed_hdrs.keys() and \ "etag" not in state.parsed_hdrs.keys(): state.set_message('header-cache-control', rs.NO_CACHE_NO_VALIDATOR) else: state.set_message('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: state.set_message('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: state.set_message('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: state.set_message('header-cache-control', rs.CHECK_ALL_ZERO) elif post_check > pre_check: state.set_message('header-cache-control', rs.CHECK_POST_BIGGER) post_check = pre_check elif post_check == 0: state.set_message('header-cache-control', rs.CHECK_POST_ZERO) else: state.set_message('header-cache-control', rs.CHECK_POST_PRE, pre_check=pre_check, post_check=post_check) # vary? vary = state.parsed_hdrs.get('vary', set()) if "*" in vary: state.set_message('header-vary', rs.VARY_ASTERISK) return # bail; nothing else to see here elif len(vary) > 3: state.set_message('header-vary', rs.VARY_COMPLEX, vary_count=f_num(len(vary))) else: if "user-agent" in vary: state.set_message('header-vary', rs.VARY_USER_AGENT) if "host" in vary: state.set_message('header-vary', rs.VARY_HOST) # TODO: enumerate the axes in a message # calculate age age_hdr = state.parsed_hdrs.get('age', 0) date_hdr = state.parsed_hdrs.get('date', 0) if date_hdr > 0: apparent_age = max(0, int(state.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) state.age = age_hdr if age_hdr >= 1: state.set_message('header-age header-date', rs.CURRENT_AGE, age=age_str) # Check for clock skew and dateless origin server. skew = date_hdr - state.res_ts + age_hdr if not date_hdr: state.set_message('', rs.DATE_CLOCKLESS) if state.parsed_hdrs.has_key('expires') or \ state.parsed_hdrs.has_key('last-modified'): state.set_message('header-expires header-last-modified', rs.DATE_CLOCKLESS_BAD_HDR) elif age_hdr > max_clock_skew and current_age - skew < max_clock_skew: state.set_message('header-date header-age', rs.AGE_PENALTY) elif abs(skew) > max_clock_skew: state.set_message('header-date', rs.DATE_INCORRECT, clock_skew_string=relative_time(skew, 0, 2)) else: state.set_message('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 state.parsed_hdrs.has_key('expires'): has_explicit_freshness = True freshness_hdrs.append('header-expires') if state.parsed_hdrs.has_key('date'): freshness_lifetime = state.parsed_hdrs['expires'] - \ state.parsed_hdrs['date'] else: freshness_lifetime = state.parsed_hdrs['expires'] - \ state.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) state.freshness_lifetime = freshness_lifetime fresh = freshness_left > 0 if has_explicit_freshness: if fresh: state.set_message(" ".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 state.age > freshness_lifetime: state.set_message(" ".join(freshness_hdrs), rs.FRESHNESS_STALE_CACHE, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age=current_age_str) else: state.set_message(" ".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 state.res_status in heuristic_cacheable_status: state.set_message('header-last-modified', rs.FRESHNESS_HEURISTIC) else: state.set_message('', rs.FRESHNESS_NONE) # can stale responses be served? if 'must-revalidate' in cc_keys: if fresh: state.set_message('header-cache-control', rs.FRESH_MUST_REVALIDATE) elif has_explicit_freshness: state.set_message('header-cache-control', rs.STALE_MUST_REVALIDATE) elif 'proxy-revalidate' in cc_keys or 's-maxage' in cc_keys: if fresh: state.set_message('header-cache-control', rs.FRESH_PROXY_REVALIDATE) elif has_explicit_freshness: state.set_message('header-cache-control', rs.STALE_PROXY_REVALIDATE) else: if fresh: state.set_message('header-cache-control', rs.FRESH_SERVABLE) elif has_explicit_freshness: state.set_message('header-cache-control', rs.STALE_SERVABLE) # public? if 'public' in cc_keys: # TODO: check for authentication in request state.set_message('header-cache-control', rs.PUBLIC)
def checkCaching(state): "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 = state.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(): state.set_message("header-cache-control", rs.CC_MISCAP, cc_lower=cc.lower(), cc=cc) if cc in known_cc and cc_list.count(cc) > 1: state.set_message("header-cache-control", rs.CC_DUP, cc=cc) # Who can store this? if state.method not in cacheable_methods: state.store_shared = state.store_private = False state.set_message("method", rs.METHOD_UNCACHEABLE, method=state.method) return # bail; nothing else to see here elif "no-store" in cc_keys: state.store_shared = state.store_private = False state.set_message("header-cache-control", rs.NO_STORE) return # bail; nothing else to see here elif "private" in cc_keys: state.store_shared = False state.store_private = True state.set_message("header-cache-control", rs.PRIVATE_CC) elif "authorization" in [k.lower() for k, v in state.req_hdrs] and not "public" in cc_keys: state.store_shared = False state.store_private = True state.set_message("header-cache-control", rs.PRIVATE_AUTH) else: state.store_shared = state.store_private = True state.set_message("header-cache-control", rs.STOREABLE) # no-cache? if "no-cache" in cc_keys: if "last-modified" not in state.parsed_hdrs.keys() and "etag" not in state.parsed_hdrs.keys(): state.set_message("header-cache-control", rs.NO_CACHE_NO_VALIDATOR) else: state.set_message("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: state.set_message("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: state.set_message("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: state.set_message("header-cache-control", rs.CHECK_ALL_ZERO) elif post_check > pre_check: state.set_message("header-cache-control", rs.CHECK_POST_BIGGER) post_check = pre_check elif post_check == 0: state.set_message("header-cache-control", rs.CHECK_POST_ZERO) else: state.set_message( "header-cache-control", rs.CHECK_POST_PRE, pre_check=pre_check, post_check=post_check ) # vary? vary = state.parsed_hdrs.get("vary", set()) if "*" in vary: state.set_message("header-vary", rs.VARY_ASTERISK) return # bail; nothing else to see here elif len(vary) > 3: state.set_message("header-vary", rs.VARY_COMPLEX, vary_count=f_num(len(vary))) else: if "user-agent" in vary: state.set_message("header-vary", rs.VARY_USER_AGENT) if "host" in vary: state.set_message("header-vary", rs.VARY_HOST) # TODO: enumerate the axes in a message # calculate age age_hdr = state.parsed_hdrs.get("age", 0) date_hdr = state.parsed_hdrs.get("date", 0) if date_hdr > 0: apparent_age = max(0, int(state.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) state.age = age_hdr if age_hdr >= 1: state.set_message("header-age header-date", rs.CURRENT_AGE, age=age_str) # Check for clock skew and dateless origin server. skew = date_hdr - state.res_ts + age_hdr if not date_hdr: state.set_message("", rs.DATE_CLOCKLESS) if state.parsed_hdrs.has_key("expires") or state.parsed_hdrs.has_key("last-modified"): state.set_message("header-expires header-last-modified", rs.DATE_CLOCKLESS_BAD_HDR) elif age_hdr > max_clock_skew and current_age - skew < max_clock_skew: state.set_message("header-date header-age", rs.AGE_PENALTY) elif abs(skew) > max_clock_skew: state.set_message("header-date", rs.DATE_INCORRECT, clock_skew_string=relative_time(skew, 0, 2)) else: state.set_message("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 state.parsed_hdrs.has_key("expires"): has_explicit_freshness = True freshness_hdrs.append("header-expires") if state.parsed_hdrs.has_key("date"): freshness_lifetime = state.parsed_hdrs["expires"] - state.parsed_hdrs["date"] else: freshness_lifetime = state.parsed_hdrs["expires"] - state.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) state.freshness_lifetime = freshness_lifetime fresh = freshness_left > 0 if has_explicit_freshness: if fresh: state.set_message( " ".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 state.age > freshness_lifetime: state.set_message( " ".join(freshness_hdrs), rs.FRESHNESS_STALE_CACHE, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age=current_age_str, ) else: state.set_message( " ".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 state.res_status in heuristic_cacheable_status: state.set_message("header-last-modified", rs.FRESHNESS_HEURISTIC) else: state.set_message("", rs.FRESHNESS_NONE) # can stale responses be served? if "must-revalidate" in cc_keys: if fresh: state.set_message("header-cache-control", rs.FRESH_MUST_REVALIDATE) elif has_explicit_freshness: state.set_message("header-cache-control", rs.STALE_MUST_REVALIDATE) elif "proxy-revalidate" in cc_keys or "s-maxage" in cc_keys: if fresh: state.set_message("header-cache-control", rs.FRESH_PROXY_REVALIDATE) elif has_explicit_freshness: state.set_message("header-cache-control", rs.STALE_PROXY_REVALIDATE) else: if fresh: state.set_message("header-cache-control", rs.FRESH_SERVABLE) elif has_explicit_freshness: state.set_message("header-cache-control", rs.STALE_SERVABLE) # public? if "public" in cc_keys: # TODO: check for authentication in request state.set_message("header-cache-control", rs.PUBLIC)