Esempio n. 1
0
 def format_time(value: float) -> str:
     if value is None:
         return '<td>-</td>'
     return '<td>%s</td>' % relative_time(value, 0, 0)
Esempio n. 2
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)
Esempio n. 3
0
 def format_time(self, value):
     if value is None:
         return u'<td>-</td>'
     else:
         return u'<td>%s</td>' % relative_time(value, 0, 0)
Esempio n. 4
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)
Esempio n. 5
0
 def format_time(value: float) -> str:
     if value is None:
         return '<td>-</td>'
     else:
         return '<td>%s</td>' % relative_time(value, 0, 0)
Esempio n. 6
0
 def format_time(value: float) -> str:
     if value is None:
         return "<td>-</td>"
     return "<td>%s</td>" % relative_time(value, 0, 0)