def format_time(value: float) -> str: if value is None: return '<td>-</td>' return '<td>%s</td>' % relative_time(value, 0, 0)
def checkCaching(response: HttpResponse, request: HttpRequest = None) -> None: "Examine HTTP caching characteristics." # get header values lm_hdr = response.parsed_headers.get("last-modified", None) date_hdr = response.parsed_headers.get("date", None) expires_hdr = response.parsed_headers.get("expires", None) etag_hdr = response.parsed_headers.get("etag", None) age_hdr = response.parsed_headers.get("age", None) cc_set = response.parsed_headers.get("cache-control", []) cc_list = [k for (k, v) in cc_set] cc_dict = dict(cc_set) cc_keys = list(cc_dict.keys()) # Last-Modified if lm_hdr: serv_date = date_hdr or response.start_time if lm_hdr > serv_date: response.add_note("header-last-modified", LM_FUTURE) else: response.add_note( "header-last-modified", LM_PRESENT, last_modified_string=relative_time(lm_hdr, 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(): response.add_note("header-cache-control", CC_MISCAP, cc_lower=cc.lower(), cc=cc) if cc in known_cc and cc_list.count(cc) > 1: response.add_note("header-cache-control", CC_DUP, cc=cc) # Who can store this? if request and request.method not in cacheable_methods: response.store_shared = response.store_private = False request.add_note("method", METHOD_UNCACHEABLE, method=request.method) return # bail; nothing else to see here if "no-store" in cc_keys: response.store_shared = response.store_private = False response.add_note("header-cache-control", NO_STORE) return # bail; nothing else to see here if "private" in cc_keys: response.store_shared = False response.store_private = True response.add_note("header-cache-control", PRIVATE_CC) elif (request and "authorization" in [k.lower() for k, v in request.headers] and "public" not in cc_keys): response.store_shared = False response.store_private = True response.add_note("header-cache-control", PRIVATE_AUTH) else: response.store_shared = response.store_private = True response.add_note("header-cache-control", STOREABLE) # no-cache? if "no-cache" in cc_keys: if lm_hdr is None and etag_hdr is None: response.add_note("header-cache-control", NO_CACHE_NO_VALIDATOR) else: response.add_note("header-cache-control", 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: response.add_note("header-cache-control", 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: response.add_note("header-cache-control", CHECK_NOT_INTEGER) if pre_check is not None and post_check is not None: if pre_check == 0 and post_check == 0: response.add_note("header-cache-control", CHECK_ALL_ZERO) elif post_check > pre_check: response.add_note("header-cache-control", CHECK_POST_BIGGER) post_check = pre_check elif post_check == 0: response.add_note("header-cache-control", CHECK_POST_ZERO) else: response.add_note( "header-cache-control", CHECK_POST_PRE, pre_check=pre_check, post_check=post_check, ) # vary? vary = response.parsed_headers.get("vary", set()) if "*" in vary: response.add_note("header-vary", VARY_ASTERISK) return # bail; nothing else to see here if len(vary) > 3: response.add_note("header-vary", VARY_COMPLEX, vary_count=f_num(len(vary))) else: if "user-agent" in vary: response.add_note("header-vary", VARY_USER_AGENT) if "host" in vary: response.add_note("header-vary", VARY_HOST) # calculate age response.age = age_hdr or 0 age_str = relative_time(response.age, 0, 0) if date_hdr and date_hdr > 0: apparent_age = max(0, int(response.start_time - date_hdr)) else: apparent_age = 0 current_age = max(apparent_age, response.age) current_age_str = relative_time(current_age, 0, 0) if response.age >= 1: response.add_note("header-age header-date", CURRENT_AGE, age=age_str) # Check for clock skew and dateless origin server. if not date_hdr: response.add_note("", DATE_CLOCKLESS) if expires_hdr or lm_hdr: response.add_note("header-expires header-last-modified", DATE_CLOCKLESS_BAD_HDR) else: skew = date_hdr - response.start_time + (response.age) if response.age > max_clock_skew and (current_age - skew) < max_clock_skew: response.add_note("header-date header-age", AGE_PENALTY) elif abs(skew) > max_clock_skew: response.add_note( "header-date", DATE_INCORRECT, clock_skew_string=relative_time(skew, 0, 2), ) else: response.add_note("header-date", 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: 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 "expires" in response.parsed_headers: # An invalid Expires header means it's automatically stale has_explicit_freshness = True freshness_hdrs.append("header-expires") freshness_lifetime = (expires_hdr or 0) - (date_hdr or int(response.start_time)) 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) response.freshness_lifetime = freshness_lifetime fresh = freshness_left > 0 if has_explicit_freshness: if fresh: response.add_note( " ".join(freshness_hdrs), FRESHNESS_FRESH, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age=current_age_str, ) elif has_cc_freshness and response.age > freshness_lifetime: response.add_note( " ".join(freshness_hdrs), FRESHNESS_STALE_CACHE, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age=current_age_str, ) else: response.add_note( " ".join(freshness_hdrs), FRESHNESS_STALE_ALREADY, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age=current_age_str, ) # can heuristic freshness be used? elif response.status_code in heuristic_cacheable_status: response.add_note("header-last-modified", FRESHNESS_HEURISTIC) else: response.add_note("", FRESHNESS_NONE) # can stale responses be served? if "must-revalidate" in cc_keys: if fresh: response.add_note("header-cache-control", FRESH_MUST_REVALIDATE) elif has_explicit_freshness: response.add_note("header-cache-control", STALE_MUST_REVALIDATE) elif "proxy-revalidate" in cc_keys or "s-maxage" in cc_keys: if fresh: response.add_note("header-cache-control", FRESH_PROXY_REVALIDATE) elif has_explicit_freshness: response.add_note("header-cache-control", STALE_PROXY_REVALIDATE) else: if fresh: response.add_note("header-cache-control", FRESH_SERVABLE) elif has_explicit_freshness: response.add_note("header-cache-control", STALE_SERVABLE) # public? if "public" in cc_keys: # TODO: check for authentication in request response.add_note("header-cache-control", PUBLIC)
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(response: HttpResponse, request: HttpRequest=None) -> None: "Examine HTTP caching characteristics." # get header values lm_hdr = response.parsed_headers.get('last-modified', None) date_hdr = response.parsed_headers.get('date', None) expires_hdr = response.parsed_headers.get('expires', None) etag_hdr = response.parsed_headers.get('etag', None) age_hdr = response.parsed_headers.get('age', None) cc_set = response.parsed_headers.get('cache-control', []) cc_list = [k for (k, v) in cc_set] cc_dict = dict(cc_set) cc_keys = list(cc_dict.keys()) # Last-Modified if lm_hdr: serv_date = date_hdr or response.start_time if lm_hdr > serv_date: response.add_note('header-last-modified', LM_FUTURE) else: response.add_note('header-last-modified', LM_PRESENT, last_modified_string=relative_time(lm_hdr, 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(): response.add_note('header-cache-control', CC_MISCAP, cc_lower=cc.lower(), cc=cc) if cc in known_cc and cc_list.count(cc) > 1: response.add_note('header-cache-control', CC_DUP, cc=cc) # Who can store this? if request and request.method not in cacheable_methods: response.store_shared = response.store_private = False request.add_note('method', METHOD_UNCACHEABLE, method=request.method) return # bail; nothing else to see here elif 'no-store' in cc_keys: response.store_shared = response.store_private = False response.add_note('header-cache-control', NO_STORE) return # bail; nothing else to see here elif 'private' in cc_keys: response.store_shared = False response.store_private = True response.add_note('header-cache-control', PRIVATE_CC) elif request and 'authorization' in [k.lower() for k, v in request.headers] \ and 'public' not in cc_keys: response.store_shared = False response.store_private = True response.add_note('header-cache-control', PRIVATE_AUTH) else: response.store_shared = response.store_private = True response.add_note('header-cache-control', STOREABLE) # no-cache? if 'no-cache' in cc_keys: if lm_hdr is None and etag_hdr is None: response.add_note('header-cache-control', NO_CACHE_NO_VALIDATOR) else: response.add_note('header-cache-control', 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: response.add_note('header-cache-control', 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: response.add_note('header-cache-control', CHECK_NOT_INTEGER) if pre_check is not None and post_check is not None: if pre_check == 0 and post_check == 0: response.add_note('header-cache-control', CHECK_ALL_ZERO) elif post_check > pre_check: response.add_note('header-cache-control', CHECK_POST_BIGGER) post_check = pre_check elif post_check == 0: response.add_note('header-cache-control', CHECK_POST_ZERO) else: response.add_note('header-cache-control', CHECK_POST_PRE, pre_check=pre_check, post_check=post_check) # vary? vary = response.parsed_headers.get('vary', set()) if "*" in vary: response.add_note('header-vary', VARY_ASTERISK) return # bail; nothing else to see here elif len(vary) > 3: response.add_note('header-vary', VARY_COMPLEX, vary_count=f_num(len(vary))) else: if "user-agent" in vary: response.add_note('header-vary', VARY_USER_AGENT) if "host" in vary: response.add_note('header-vary', VARY_HOST) # TODO: enumerate the axes in a message # calculate age response.age = age_hdr or 0 age_str = relative_time(response.age, 0, 0) if date_hdr and date_hdr > 0: apparent_age = max(0, int(response.start_time - date_hdr)) else: apparent_age = 0 current_age = max(apparent_age, response.age) current_age_str = relative_time(current_age, 0, 0) if response.age >= 1: response.add_note('header-age header-date', CURRENT_AGE, age=age_str) # Check for clock skew and dateless origin server. if not date_hdr: response.add_note('', DATE_CLOCKLESS) if expires_hdr or lm_hdr: response.add_note('header-expires header-last-modified', DATE_CLOCKLESS_BAD_HDR) else: skew = date_hdr - response.start_time + (response.age) if response.age > max_clock_skew and (current_age - skew) < max_clock_skew: response.add_note('header-date header-age', AGE_PENALTY) elif abs(skew) > max_clock_skew: response.add_note('header-date', DATE_INCORRECT, clock_skew_string=relative_time(skew, 0, 2)) else: response.add_note('header-date', 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: 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 'expires' in response.parsed_headers: # An invalid Expires header means it's automatically stale has_explicit_freshness = True freshness_hdrs.append('header-expires') freshness_lifetime = (expires_hdr or 0) - (date_hdr or response.start_time) 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) response.freshness_lifetime = freshness_lifetime fresh = freshness_left > 0 if has_explicit_freshness: if fresh: response.add_note(" ".join(freshness_hdrs), FRESHNESS_FRESH, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age=current_age_str) # FIXME: response.age = None elif has_cc_freshness and response.age > freshness_lifetime: response.add_note(" ".join(freshness_hdrs), FRESHNESS_STALE_CACHE, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age=current_age_str) else: response.add_note(" ".join(freshness_hdrs), FRESHNESS_STALE_ALREADY, freshness_lifetime=freshness_lifetime_str, freshness_left=freshness_left_str, current_age=current_age_str) # can heuristic freshness be used? elif response.status_code in heuristic_cacheable_status: response.add_note('header-last-modified', FRESHNESS_HEURISTIC) else: response.add_note('', FRESHNESS_NONE) # can stale responses be served? if 'must-revalidate' in cc_keys: if fresh: response.add_note('header-cache-control', FRESH_MUST_REVALIDATE) elif has_explicit_freshness: response.add_note('header-cache-control', STALE_MUST_REVALIDATE) elif 'proxy-revalidate' in cc_keys or 's-maxage' in cc_keys: if fresh: response.add_note('header-cache-control', FRESH_PROXY_REVALIDATE) elif has_explicit_freshness: response.add_note('header-cache-control', STALE_PROXY_REVALIDATE) else: if fresh: response.add_note('header-cache-control', FRESH_SERVABLE) elif has_explicit_freshness: response.add_note('header-cache-control', STALE_SERVABLE) # public? if 'public' in cc_keys: # TODO: check for authentication in request response.add_note('header-cache-control', PUBLIC)
def format_time(value: float) -> str: if value is None: return '<td>-</td>' else: return '<td>%s</td>' % relative_time(value, 0, 0)
def format_time(value: float) -> str: if value is None: return "<td>-</td>" return "<td>%s</td>" % relative_time(value, 0, 0)