Beispiel #1
0
def check_request(req):
    """Apply all checks to the request `req`."""

    req.silence(notice_id
                for (notice_id, in_resp) in req.headers.httpolice_silence.okay
                if not in_resp)

    message.check_message(req)

    _ = req.target_form                 # Force check.

    if req.body and req.headers.content_type.is_absent:
        req.complain(1041)

    if (method.defines_body(req.method) and
            req.headers.content_length.is_absent and
            req.headers.transfer_encoding.is_absent):
        req.complain(1021)

    if (method.defines_body(req.method) == False) and (not req.body) and \
            req.headers.content_length.is_present:
        req.complain(1022)

    if tc.chunked in req.headers.te:
        req.complain(1028)

    if req.version == http2:
        if req.headers.te and req.headers.te.value != [u'trailers']:
            req.complain(1244, header=req.headers.te)
    else:
        if req.headers.te and u'TE' not in req.headers.connection:
            req.complain(1029)

    if req.version == http11 and req.headers.host.is_absent:
        req.complain(1031)
    if req.headers.host.is_present and req.header_entries[0].name != h.host:
        req.complain(1032)

    for hdr in req.headers:
        if header.is_for_request(hdr.name) == False:
            req.complain(1063, header=hdr)
        elif header.is_representation_metadata(hdr.name) and req.body == b'':
            req.complain(1053, header=hdr)

    if req.body:
        if req.method == m.GET:
            req.complain(1056)
        elif req.method == m.HEAD:
            req.complain(1057)
        elif req.method == m.DELETE:
            req.complain(1059)
        elif req.method == m.CONNECT:
            req.complain(1061)

    if req.method == m.OPTIONS and req.body and \
            req.headers.content_type.is_absent:
        req.complain(1062)

    if req.headers.expect == u'100-continue' and req.body == b'':
        req.complain(1066)

    if req.headers.max_forwards.is_present and \
            req.method not in [m.OPTIONS, m.TRACE]:
        req.complain(1067)

    if req.headers.referer.is_okay:
        if req.is_tls == False:
            parsed = urlparse(req.headers.referer.value)
            if parsed.scheme == u'https':
                req.complain(1068)

    if req.headers.user_agent.is_absent:
        req.complain(1070)
    elif req.headers.user_agent.is_okay:
        products = [p for p in req.headers.user_agent.value
                    if isinstance(p, Versioned)]
        if products and all(product.is_library(p.item) for p in products):
            req.complain(1093, library=products[0])

    for x in req.headers.accept_encoding.okay:
        if x.item in [cc.x_gzip, cc.x_compress] and x.param is not None:
            req.complain(1116, coding=x.item)

    if req.headers.if_match.is_okay and req.headers.if_match != u'*':
        if any(tag.weak for tag in req.headers.if_match.value):
            req.complain(1120)

    if req.method == m.HEAD:
        for hdr in req.headers:
            if header.is_precondition(hdr.name):
                req.complain(1131, header=hdr)

    if req.method in [m.CONNECT, m.OPTIONS, m.TRACE]:
        for hdr in req.headers:
            if hdr.name in [h.if_modified_since, h.if_unmodified_since,
                            h.if_match, h.if_none_match, h.if_range]:
                req.complain(1130, header=hdr)
    elif req.method not in [m.GET, m.HEAD]:
        if req.headers.if_modified_since.is_present:
            req.complain(1122)

    if req.headers.range.is_present and req.method != m.GET:
        req.complain(1132)

    if req.headers.if_range.is_present and req.headers.range.is_absent:
        req.complain(1134)

    if isinstance(req.headers.if_range.value, EntityTag) and \
            req.headers.if_range.value.weak:
        req.complain(1135)

    for d in req.headers.cache_control.okay:
        if cache_directive.is_for_request(d.item) == False:
            req.complain(1152, directive=d.item)
        if d == cache.no_cache and d.param is not None:
            req.complain(1159, directive=d.item)

    if req.headers.cache_control.no_cache and \
            u'no-cache' not in req.headers.pragma:
        req.complain(1161)

    for warning in req.headers.warning.okay:
        if 100 <= warning.code < 200:
            req.complain(1165, code=warning.code)

    if method.is_cacheable(req.method) == False:
        for direct in req.headers.cache_control.okay:
            if direct.item in [cache.max_age, cache.max_stale, cache.min_fresh,
                               cache.no_cache, cache.no_store,
                               cache.only_if_cached]:
                req.complain(1171, directive=direct)

    for direct1, direct2 in [(cache.max_stale, cache.min_fresh),
                             (cache.stale_if_error, cache.min_fresh),
                             (cache.max_stale, cache.no_cache),
                             (cache.max_age, cache.no_cache)]:
        if req.headers.cache_control[direct1] and \
                req.headers.cache_control[direct2]:
            req.complain(1193, directive1=direct1, directive2=direct2)

    for hdr in [req.headers.authorization, req.headers.proxy_authorization]:
        if hdr.is_okay:
            scheme, credentials = hdr.value
            if scheme == auth.basic:
                _check_basic_auth(req, hdr, credentials)
            elif scheme == auth.bearer:
                _check_bearer_auth(req, hdr, credentials)
            elif not credentials:
                req.complain(1274, header=hdr)

    if req.method == m.PATCH and req.headers.content_type.is_okay:
        if media_type.is_patch(req.headers.content_type.value.item) == False:
            req.complain(1213)

    for proto in req.headers.upgrade.okay:
        if proto.item == upgrade.h2c:
            if req.is_tls:
                req.complain(1233)
            if req.headers.http2_settings.is_absent:
                req.complain(1231)

    if req.headers.http2_settings and \
            u'HTTP2-Settings' not in req.headers.connection:
        req.complain(1230)

    if req.headers.http2_settings.is_okay:
        for c in req.headers.http2_settings.value:
            if c not in string.ascii_letters + string.digits + '-_':
                req.complain(1234, char=c)

    if u'access_token' in req.query_params:
        req.complain(1270)
        if req.is_tls == False:
            req.complain(1271, where=req.target)
        if not req.headers.cache_control.no_store:
            req.complain(1272)

    if okay(req.url_encoded_data) and u'access_token' in req.url_encoded_data:
        if req.is_tls == False:
            req.complain(1271, where=req.body)
Beispiel #2
0
def check_request(req):
    """Apply all checks to the request `req`."""
    complain = req.complain
    method = req.method
    version = req.version
    headers = req.headers
    body = req.body

    req.silence(notice_id for (notice_id, in_resp) in headers.httpolice_silence
                if not in_resp)

    message.check_message(req)

    # Check the syntax of request method and target.
    simple_parse(method,
                 rfc7230.method,
                 complain,
                 1292,
                 place=u'request method')
    _ = req.target_form

    if method != method.upper() and method.upper() in m:
        complain(1295, uppercase=Method(method.upper()))

    if body and headers.content_type.is_absent:
        complain(1041)

    if (version in [http10, http11] and method_info.defines_body(method)
            and headers.content_length.is_absent
            and headers.transfer_encoding.is_absent):
        complain(1021)

    if (method_info.defines_body(method) == False) and (body == b'') and \
            headers.content_length.is_present:
        complain(1022)

    if tc.chunked in headers.te:
        complain(1028)

    if version == http2:
        if headers.te and headers.te != [u'trailers']:
            complain(1244, header=headers.te)
    else:
        if headers.te and u'TE' not in headers.connection:
            complain(1029)

    if version == http11 and headers.host.is_absent:
        complain(1031)
    if headers.host.is_present and req.header_entries[0].name != h.host:
        complain(1032)

    for hdr in headers:
        if header.is_for_request(hdr.name) == False:
            complain(1063, header=hdr)
        elif header.is_representation_metadata(hdr.name) and \
                req.has_body == False:
            complain(1053, header=hdr)

    if body:
        if method == m.GET:
            complain(1056)
        elif method == m.HEAD:
            complain(1057)
        elif method == m.DELETE:
            complain(1059)
        elif method == m.CONNECT:
            complain(1061)

    if method == m.OPTIONS and body and headers.content_type.is_absent:
        complain(1062)

    if headers.expect == u'100-continue' and req.has_body == False:
        complain(1066)

    if headers.max_forwards.is_present and method not in [m.OPTIONS, m.TRACE]:
        complain(1067)

    if headers.referer.is_okay:
        if req.is_tls == False:
            parsed = urlparse(headers.referer.value)
            if parsed.scheme == u'https':
                complain(1068)

    if headers.user_agent.is_absent:
        complain(1070)
    elif headers.user_agent.is_okay:
        products = [p for p in headers.user_agent if isinstance(p, Versioned)]
        if products and all(product.is_library(p.item) for p in products):
            complain(1093, library=products[0])

    for x in headers.accept_encoding:
        if x.item in [cc.x_gzip, cc.x_compress] and x.param is not None:
            complain(1116, coding=x.item)

    if headers.if_match.is_okay and headers.if_match != u'*':
        if any(tag.weak for tag in headers.if_match):
            complain(1120)

    if method == m.HEAD:
        for hdr in headers:
            if header.is_precondition(hdr.name):
                complain(1131, header=hdr)

    if method in [m.CONNECT, m.OPTIONS, m.TRACE]:
        for hdr in headers:
            if hdr.name in [
                    h.if_modified_since, h.if_unmodified_since, h.if_match,
                    h.if_none_match, h.if_range
            ]:
                complain(1130, header=hdr)
    elif method not in [m.GET, m.HEAD]:
        if headers.if_modified_since.is_present:
            complain(1122)

    if headers.range.is_present and method != m.GET:
        complain(1132)

    if headers.if_range.is_present and headers.range.is_absent:
        complain(1134)

    if isinstance(headers.if_range.value, EntityTag) and headers.if_range.weak:
        complain(1135)

    for direct in headers.cache_control:
        if cache_directive.is_for_request(direct.item) == False:
            complain(1152, directive=direct.item)
        if direct == cache.no_cache and direct.param is not None:
            complain(1159, directive=direct.item)

    if headers.cache_control.no_cache and u'no-cache' not in headers.pragma:
        complain(1161)

    for warning in headers.warning:
        if 100 <= warning.code <= 199:
            complain(1165, code=warning.code)

    if method_info.is_cacheable(method) == False:
        for direct in headers.cache_control:
            if direct.item in [
                    cache.max_age, cache.max_stale, cache.min_fresh,
                    cache.no_cache, cache.no_store, cache.only_if_cached
            ]:
                complain(1171, directive=direct)

    for direct1, direct2 in [(cache.max_stale, cache.min_fresh),
                             (cache.stale_if_error, cache.min_fresh),
                             (cache.max_stale, cache.no_cache),
                             (cache.max_age, cache.no_cache)]:
        if headers.cache_control[direct1] and headers.cache_control[direct2]:
            complain(1193, directive1=direct1, directive2=direct2)

    for hdr in [headers.authorization, headers.proxy_authorization]:
        if hdr.is_okay:
            scheme, credentials = hdr.value
            if scheme == auth.basic:
                _check_basic_auth(req, hdr, credentials)
            elif scheme == auth.bearer:
                _check_bearer_auth(req, hdr, credentials)
            elif not credentials:
                complain(1274, header=hdr)

    if method == m.PATCH and headers.content_type.is_okay:
        if media_type.is_patch(headers.content_type.item) == False:
            complain(1213)

    for protocol in headers.upgrade:
        if protocol.item == upgrade.h2c:
            if req.is_tls:
                complain(1233)
            if headers.http2_settings.is_absent:
                complain(1231)

    if headers.http2_settings and u'HTTP2-Settings' not in headers.connection:
        complain(1230)

    if headers.http2_settings.is_okay:
        if not _is_urlsafe_base64(headers.http2_settings.value):
            complain(1234)

    if u'access_token' in req.query_params:
        complain(1270)
        if req.is_tls == False:
            complain(1271, where=req.target)
        if not headers.cache_control.no_store:
            complain(1272)

    if okay(req.url_encoded_data) and u'access_token' in req.url_encoded_data:
        if req.is_tls == False:
            complain(1271, where=req.displayable_body)

    for hdr in [
            headers.accept, headers.accept_charset, headers.accept_encoding,
            headers.accept_language
    ]:
        for (wildcard, value) in _accept_subsumptions(hdr):
            complain(1276, header=hdr, wildcard=wildcard, value=value)
            # No need to report more than one subsumption per header.
            break

    for dup_pref in duplicates(name for ((name, _), _) in headers.prefer):
        complain(1285, name=dup_pref)

    if headers.prefer.respond_async and method_info.is_safe(method):
        complain(1287)

    if headers.prefer.return_ == u'minimal' and method == m.GET:
        complain(1288)

    if (pref.return_, u'minimal') in headers.prefer.without_params and \
       (pref.return_, u'representation') in headers.prefer.without_params:
        complain(1289)

    if (pref.handling, u'strict') in headers.prefer.without_params and \
       (pref.handling, u'lenient') in headers.prefer.without_params:
        complain(1290)
Beispiel #3
0
def check_response_itself(resp):
    resp.silence(notice_id
                 for (notice_id, _) in resp.headers.httpolice_silence)

    message.check_message(resp)

    complain = resp.complain
    version = resp.version
    status = resp.status
    headers = resp.headers
    body = resp.body

    # Check syntax of reason phrase.
    if okay(resp.reason):
        simple_parse(resp.reason,
                     rfc7230.reason_phrase,
                     complain,
                     1294,
                     place=u'reason phrase')

    if not (100 <= status <= 599):
        complain(1167)

    if status.informational and u'close' in headers.connection:
        complain(1198)

    if status.informational or status == st.no_content:
        if headers.transfer_encoding.is_present:
            complain(1018)
        if headers.content_length.is_present:
            complain(1023)

    for hdr in headers:
        if header.is_for_response(hdr.name) == False:
            complain(1064, header=hdr)
        elif header.is_representation_metadata(hdr.name) and \
                status.informational:
            complain(1052, header=hdr)

    if status == st.switching_protocols:
        if headers.upgrade.is_absent:
            complain(1048)
        if version == http2:
            complain(1246)

    if status == st.no_content and body:
        complain(1240)

    if status == st.reset_content and body:
        complain(1076)

    if headers.location.is_absent:
        if status == st.moved_permanently:
            complain(1078)
        if status == st.found:
            complain(1079)
        if status == st.see_other:
            complain(1080)
        if status == st.temporary_redirect:
            complain(1084)
        if status == st.permanent_redirect:
            complain(1205)

    if status == st.use_proxy:
        complain(1082)
    if status == 306:
        complain(1083)
    if status == st.payment_required:
        complain(1088)

    if status == st.method_not_allowed and headers.allow.is_absent:
        complain(1089)

    if status == st.request_timeout and u'close' not in headers.connection:
        complain(1094)

    if headers.date.is_absent and (status.successful or status.redirection
                                   or status.client_error):
        complain(1110)

    if status == st.created and headers.location.is_okay and \
            urlparse(headers.location.value).fragment:
        complain(1111)

    if headers.location.is_present and \
            not status.redirection and status != st.created:
        complain(1112)

    if headers.retry_after.is_present and \
            not status.redirection and \
            status not in [st.payload_too_large, st.service_unavailable,
                           st.too_many_requests]:
        complain(1113)

    if headers.date < headers.last_modified.value:
        complain(1118)

    if status == st.not_modified:
        for hdr in headers:
            # RFC 7232 says "Last-Modified might be useful
            # if the response does not have an ETag field",
            # but really it doesn't hurt even if there is an ETag,
            # and this is widely seen in practice.
            if hdr.name in [h.etag, h.last_modified]:
                continue
            elif header.is_representation_metadata(hdr.name):
                complain(1127, header=hdr)

    if headers.content_range.is_present and \
            status not in [st.partial_content, st.range_not_satisfiable]:
        complain(1147)

    if status == st.partial_content:
        if headers.content_type == media.multipart_byteranges:
            _check_multipart_byteranges(resp)
            if headers.content_range.is_present:
                complain(1143)
        elif headers.content_range.is_absent:
            complain(1138)

    for direct in headers.cache_control:
        if cache_directive.is_for_response(direct.item) == False:
            complain(1153, directive=direct.item)

    if u'no-cache' in headers.pragma:
        complain(1162)

    if resp.from_cache:
        if headers.age.is_absent:
            complain(1166)
        if headers.cache_control.no_cache in [True, []]:
            complain(1175)
        if headers.cache_control.no_store:
            complain(1176)

        if status_code.is_cacheable(status) == NOT_AT_ALL:
            complain(1202)
        elif status_code.is_cacheable(status) == NOT_BY_DEFAULT:
            if headers.expires.is_absent and headers.cache_control.is_absent:
                complain(1177)

    if resp.heuristic_expiration:
        if headers.age > (24 * 60 * 60) and \
                warn.heuristic_expiration not in headers.warning:
            complain(1180)
        if headers.expires.is_present:
            complain(1181)
        elif headers.cache_control.max_age is not None:
            complain(1182)

    if resp.stale:
        if warn.response_is_stale not in headers.warning:
            complain(1186)
        if headers.cache_control.must_revalidate:
            complain(1187)

    for direct1, direct2 in [(cache.public, cache.no_store),
                             (cache.private, cache.public),
                             (cache.private, cache.no_store),
                             (cache.must_revalidate,
                              cache.stale_while_revalidate),
                             (cache.must_revalidate, cache.stale_if_error)]:
        if headers.cache_control[direct1] and headers.cache_control[direct2]:
            complain(1193, directive1=direct1, directive2=direct2)

    for direct1, direct2 in [(cache.max_age, cache.no_cache),
                             (cache.max_age, cache.no_store),
                             (cache.s_maxage, cache.private),
                             (cache.s_maxage, cache.no_cache),
                             (cache.s_maxage, cache.no_store)]:
        if headers.cache_control[direct1] and \
                headers.cache_control[direct2] in [True, []]:
            complain(1238, directive1=direct1, directive2=direct2)

    if headers.vary != u'*' and h.host in headers.vary:
        complain(1235)

    if status == st.unauthorized and headers.www_authenticate.is_absent:
        complain(1194)

    if status == st.proxy_authentication_required and \
            headers.proxy_authenticate.is_absent:
        complain(1195)

    for hdr in [headers.www_authenticate, headers.proxy_authenticate]:
        for challenge in hdr:
            if challenge.item == auth.basic:
                _check_basic_challenge(resp, hdr, challenge)
            if challenge.item == auth.bearer:
                _check_bearer_challenge(resp, hdr, challenge)

    if headers.allow.is_present and headers.accept_patch.is_present and \
            m.PATCH not in headers.allow:
        complain(1217)

    if headers.strict_transport_security.is_okay:
        if hsts.max_age not in headers.strict_transport_security:
            complain(1218)
        if headers.strict_transport_security.max_age == 0 and \
                headers.strict_transport_security.includesubdomains:
            complain(1219)
        for dupe in duplicates(d.item
                               for d in headers.strict_transport_security):
            complain(1220, directive=dupe)

    for patch_type in headers.accept_patch:
        if media_type.is_patch(patch_type.item) == False:
            complain(1227, patch_type=patch_type.item)

    if resp.transformed_by_proxy and headers.via.is_absent:
        complain(1046)

    if status == st.unavailable_for_legal_reasons:
        if not any(rel.blocked_by in link.param.get(u'rel', [])
                   for link in headers.link):
            complain(1243)

    if headers.content_disposition.is_okay:
        params = headers.content_disposition.param
        for name in params.duplicates():
            complain(1247, param=name)

        filename = params.get(u'filename')
        if filename is not None:
            if contains_percent_encodes(filename):
                complain(1248)
            if u'"' in filename or u'\\' in filename:
                # These must have been backslash-escaped.
                complain(1249)
            if not is_ascii(filename):
                complain(1250)

        filename_ext = params.get(u'filename*')
        if filename_ext is not None:
            if filename is None:
                complain(1251)
            elif params.index(u'filename*') < params.index(u'filename'):
                complain(1252)
            if filename_ext.charset != u'UTF-8':
                complain(1255)

    if headers.alt_svc.is_present:
        if version == http2:
            complain(1258)
        if status == st.misdirected_request:
            complain(1260)
Beispiel #4
0
def check_response_itself(resp):
    resp.silence(notice_id
                 for (notice_id, _) in resp.headers.httpolice_silence.okay)

    message.check_message(resp)

    version = resp.version
    status = resp.status
    headers = resp.headers
    body = resp.body

    if not (100 <= status < 600):
        resp.complain(1167)

    if status.informational and u'close' in headers.connection:
        resp.complain(1198)

    if status.informational or status == st.no_content:
        if headers.transfer_encoding.is_present:
            resp.complain(1018)
        if headers.content_length.is_present:
            resp.complain(1023)

    for hdr in headers:
        if header.is_for_response(hdr.name) == False:
            resp.complain(1064, header=hdr)
        elif header.is_representation_metadata(hdr.name) and \
                status.informational:
            resp.complain(1052, header=hdr)

    if status == st.switching_protocols:
        if headers.upgrade.is_absent:
            resp.complain(1048)
        if version == http2:
            resp.complain(1246)

    if status == st.no_content and body:
        resp.complain(1240)

    if status == st.reset_content and body:
        resp.complain(1076)

    if headers.location.is_absent:
        if status == st.moved_permanently:
            resp.complain(1078)
        elif status == st.found:
            resp.complain(1079)
        elif status == st.see_other:
            resp.complain(1080)
        elif status == st.temporary_redirect:
            resp.complain(1084)
        elif status == st.permanent_redirect:
            resp.complain(1205)

    if status == st.use_proxy:
        resp.complain(1082)
    elif status == 306:
        resp.complain(1083)
    elif status == st.payment_required:
        resp.complain(1088)

    if status == st.method_not_allowed:
        if headers.allow.is_absent:
            resp.complain(1089)

    if status == st.request_timeout and u'close' not in headers.connection:
        resp.complain(1094)

    if headers.date.is_absent and (status.successful or status.redirection or
                                   status.client_error):
        resp.complain(1110)

    if headers.location.is_present:
        if status == st.created:
            if headers.location.is_okay and \
                    urlparse(headers.location.value).fragment:
                resp.complain(1111)
        elif not status.redirection:
            resp.complain(1112)

    if headers.retry_after.is_present and \
            not status.redirection and \
            status not in [st.payload_too_large, st.service_unavailable,
                           st.too_many_requests]:
        resp.complain(1113)

    if headers.date < headers.last_modified:
        resp.complain(1118)

    if status == st.not_modified:
        for hdr in headers:
            # RFC 7232 says "Last-Modified might be useful
            # if the response does not have an ETag field",
            # but really it doesn't hurt even if there is an ETag,
            # and this is widely seen in practice.
            if hdr.name in [h.etag, h.last_modified]:
                continue
            elif header.is_representation_metadata(hdr.name):
                resp.complain(1127, header=hdr)

    if headers.content_range.is_present and \
            status not in [st.partial_content, st.range_not_satisfiable]:
        resp.complain(1147)

    if status == st.partial_content:
        if headers.content_type == media.multipart_byteranges:
            if okay(resp.multipart_data):
                for i, part in enumerate(resp.multipart_data.get_payload()):
                    if u'Content-Range' not in part:
                        resp.complain(1141, part_num=(i + 1))
                    if u'Content-Type' not in part:
                        resp.complain(1142, part_num=(i + 1))
            if headers.content_range.is_present:
                resp.complain(1143)
        elif headers.content_range.is_absent:
            resp.complain(1138)

    for d in headers.cache_control.okay:
        if cache_directive.is_for_response(d.item) == False:
            resp.complain(1153, directive=d.item)

    if u'no-cache' in headers.pragma:
        resp.complain(1162)

    if resp.from_cache:
        if headers.age.is_absent:
            resp.complain(1166)
        if headers.cache_control.no_cache in [True, []]:
            resp.complain(1175)
        if headers.cache_control.no_store:
            resp.complain(1176)
        if status_code.is_cacheable(status) == NOT_AT_ALL:
            resp.complain(1202)
        elif status_code.is_cacheable(status) == NOT_BY_DEFAULT:
            if headers.expires.is_absent and headers.cache_control.is_absent:
                resp.complain(1177)

    if resp.heuristic_expiration:
        if headers.age > (24 * 60 * 60) and \
                warn.heuristic_expiration not in headers.warning:
            resp.complain(1180)
        if headers.expires.is_present:
            resp.complain(1181)
        elif headers.cache_control.max_age is not None:
            resp.complain(1182)

    if resp.stale:
        if warn.response_is_stale not in headers.warning:
            resp.complain(1186)
        if headers.cache_control.must_revalidate:
            resp.complain(1187)

    for direct1, direct2 in [(cache.public, cache.no_store),
                             (cache.private, cache.public),
                             (cache.private, cache.no_store),
                             (cache.must_revalidate,
                              cache.stale_while_revalidate),
                             (cache.must_revalidate, cache.stale_if_error)]:
        if headers.cache_control[direct1] and headers.cache_control[direct2]:
            resp.complain(1193, directive1=direct1, directive2=direct2)

    for direct1, direct2 in [(cache.max_age, cache.no_cache),
                             (cache.max_age, cache.no_store),
                             (cache.s_maxage, cache.private),
                             (cache.s_maxage, cache.no_cache),
                             (cache.s_maxage, cache.no_store)]:
        if headers.cache_control[direct1] and \
                headers.cache_control[direct2] in [True, []]:
            resp.complain(1238, directive1=direct1, directive2=direct2)

    if headers.vary != u'*' and h.host in headers.vary.value:
        resp.complain(1235)

    if status == st.unauthorized and headers.www_authenticate.is_absent:
        resp.complain(1194)

    if status == st.proxy_authentication_required and \
            headers.proxy_authenticate.is_absent:
        resp.complain(1195)

    for hdr in [headers.www_authenticate, headers.proxy_authenticate]:
        for challenge in hdr.okay:
            if challenge.item == auth.basic:
                _check_basic_challenge(resp, hdr, challenge)
            if challenge.item == auth.bearer:
                _check_bearer_challenge(resp, hdr, challenge)

    if headers.allow.is_present and headers.accept_patch.is_present and \
            m.PATCH not in headers.allow:
        resp.complain(1217)

    if headers.strict_transport_security.is_okay:
        if hsts.max_age not in headers.strict_transport_security:
            resp.complain(1218)
        if headers.strict_transport_security.max_age == 0 and \
                headers.strict_transport_security.includesubdomains:
            resp.complain(1219)
        seen = set()
        for direct, _ in headers.strict_transport_security:
            if direct in seen:
                resp.complain(1220, directive=direct)
            seen.add(direct)

    for patch_type in headers.accept_patch.okay:
        if media_type.is_patch(patch_type.item) == False:
            resp.complain(1227, patch_type=patch_type.item)

    if resp.transformed_by_proxy and headers.via.is_absent:
        resp.complain(1046)

    if status == st.unavailable_for_legal_reasons:
        if not any(rel.blocked_by in link.param.get(u'rel', [])
                   for link in headers.link.okay):
            resp.complain(1243)

    if headers.content_disposition.is_okay:
        params = headers.content_disposition.value.param
        for name in params.duplicates():
            resp.complain(1247, param=name)
        filename = params.get(u'filename')
        if filename is not None:
            if re.search(u'%[0-9A-Fa-f]{2}', filename):
                resp.complain(1248)
            if u'"' in filename or u'\\' in filename:
                # These must have been backslash-escaped.
                resp.complain(1249)
            if not is_ascii(filename):
                resp.complain(1250)
        filename_ext = params.get(u'filename*')
        if filename_ext is not None:
            if filename is None:
                resp.complain(1251)
            elif params.index(u'filename*') < params.index(u'filename'):
                resp.complain(1252)
            if filename_ext.charset != u'UTF-8':
                resp.complain(1255)

    if headers.alt_svc.is_present:
        if version == http2:
            resp.complain(1258)
        if status == st.misdirected_request:
            resp.complain(1260)
Beispiel #5
0
def check_response_itself(resp):
    resp.silence(notice_id
                 for (notice_id, _) in resp.headers.httpolice_silence)

    message.check_message(resp)

    complain = resp.complain
    version = resp.version
    status = resp.status
    headers = resp.headers
    body = resp.body

    # Check syntax of reason phrase.
    if okay(resp.reason):
        parse(resp.reason, rfc7230.reason_phrase, complain, 1294,
              place=u'reason phrase')

    if not (100 <= status <= 599):
        complain(1167)

    if resp.delimited_by_close:
        if resp.version == http11 and u'close' not in resp.headers.connection:
            complain(1047)

    for hdr in headers:
        if known.header.is_for_response(hdr.name) is False:
            complain(1064, header=hdr)

    if status == st.switching_protocols:
        if headers.upgrade.is_absent:
            complain(1048)
        if version == http2:
            complain(1246)

    if status == st.no_content and body:
        complain(1240)

    if status == st.reset_content and body:
        complain(1076)

    if headers.location.is_absent:
        if status == st.moved_permanently:
            complain(1078)
        elif status == st.found:
            complain(1079)
        elif status == st.see_other:
            complain(1080)
        elif status == st.temporary_redirect:
            complain(1084)
        elif status == st.permanent_redirect:
            complain(1205)

    if status == st.use_proxy:
        complain(1082)
    elif status == 306:
        complain(1083)
    elif status == st.payment_required:
        complain(1088)

    if status == st.method_not_allowed and headers.allow.is_absent:
        complain(1089)

    if status == st.request_timeout and u'close' not in headers.connection:
        complain(1094)

    if headers.date.is_absent and (status.successful or status.redirection or
                                   status.client_error):
        complain(1110)

    if status == st.created and headers.location.is_okay and \
            urlparse(headers.location.value).fragment:
        complain(1111)

    if headers.date < headers.last_modified.value:
        complain(1118)

    if status == st.not_modified:
        for hdr in headers:
            # RFC 7232 says "Last-Modified might be useful
            # if the response does not have an ETag field",
            # but really it doesn't hurt even if there is an ETag,
            # and this is widely seen in practice.
            if hdr.name in [h.etag, h.last_modified]:
                continue
            elif known.header.is_representation_metadata(hdr.name):
                complain(1127, header=hdr)

    if status == st.partial_content:
        if headers.content_type == media.multipart_byteranges:
            _check_multipart_byteranges(resp)
            if headers.content_range.is_present:
                complain(1143)
        elif headers.content_range.is_absent:
            complain(1138)

    for direct in headers.cache_control:
        if known.cache_directive.is_for_response(direct.item) is False:
            complain(1153, directive=direct.item)

    if u'no-cache' in headers.pragma:
        complain(1162)

    if status == st.unavailable_for_legal_reasons:
        if not any(u'blocked-by' in link.param.get(u'rel', [])
                   for link in headers.link):
            complain(1243)

    if headers.content_disposition.is_okay:
        params = headers.content_disposition.param
        for name in params.duplicates():
            complain(1247, param=name)

        filename = params.get(u'filename')
        if filename is not None:
            if contains_percent_encodes(filename):
                complain(1248)
            if u'"' in filename or u'\\' in filename:
                # These must have been backslash-escaped.
                complain(1249)
            if not is_ascii(filename):
                complain(1250)

        filename_ext = params.get(u'filename*')
        if filename_ext is not None:
            if filename is None:
                complain(1251)
            elif params.index(u'filename*') < params.index(u'filename'):
                complain(1252)

    if headers.alt_svc.is_present:
        if version == http2:
            complain(1258)
        if status == st.misdirected_request:
            complain(1260)

    if headers.strict_transport_security.is_okay:
        if hsts.max_age not in headers.strict_transport_security:
            complain(1218)
        if headers.strict_transport_security.max_age == 0 and \
                headers.strict_transport_security.includesubdomains:
            complain(1219)
        for dupe in duplicates(d.item
                               for d in headers.strict_transport_security):
            complain(1220, directive=dupe)

    for patch_type in headers.accept_patch:
        if known.media_type.is_patch(patch_type.item) is False:
            complain(1227, patch_type=patch_type.item)

    for direct1, direct2 in [(cache.public, cache.no_store),
                             (cache.private, cache.public),
                             (cache.private, cache.no_store),
                             (cache.must_revalidate,
                              cache.stale_while_revalidate),
                             (cache.must_revalidate, cache.stale_if_error)]:
        if headers.cache_control[direct1] and headers.cache_control[direct2]:
            complain(1193, directive1=direct1, directive2=direct2)

    for direct1, direct2 in [(cache.max_age, cache.no_cache),
                             (cache.max_age, cache.no_store),
                             (cache.s_maxage, cache.private),
                             (cache.s_maxage, cache.no_cache),
                             (cache.s_maxage, cache.no_store)]:
        if headers.cache_control[direct1] and \
                headers.cache_control[direct2] in [True, []]:
            complain(1238, directive1=direct1, directive2=direct2)

    if headers.vary != u'*' and h.host in headers.vary:
        complain(1235)

    if status == st.unauthorized and headers.www_authenticate.is_absent:
        complain(1194)

    if status == st.proxy_authentication_required and \
            headers.proxy_authenticate.is_absent:
        complain(1195)

    for hdr in [headers.www_authenticate, headers.proxy_authenticate]:
        for challenge in hdr:
            if challenge.item == auth.basic:
                _check_basic_challenge(resp, hdr, challenge)
            if challenge.item == auth.bearer:
                _check_bearer_challenge(resp, hdr, challenge)

    if status == st.early_hints:
        # 103 (Early Hints) responses are weird in that the headers they carry
        # do not apply to themselves (RFC 8297 Section 2) but only to the final
        # response (and then only speculatively). For such responses, we limit
        # ourselves to checks that do not rely on having a complete and
        # self-consistent message header block.
        return

    if status.informational or status == st.no_content:
        if headers.transfer_encoding.is_present:
            complain(1018)
        if headers.content_length.is_present:
            complain(1023)

    if status.informational and u'close' in headers.connection:
        complain(1198)

    for hdr in headers:
        if known.header.is_representation_metadata(hdr.name) and \
                status.informational:
            complain(1052, header=hdr)

    if headers.location.is_present and \
            not status.redirection and status != st.created:
        complain(1112)

    if headers.retry_after.is_present and \
            not status.redirection and \
            status not in [st.payload_too_large, st.service_unavailable,
                           st.too_many_requests]:
        complain(1113)

    if headers.content_range.is_present and \
            status not in [st.partial_content, st.range_not_satisfiable]:
        complain(1147)

    if resp.from_cache:
        if headers.age.is_absent:
            complain(1166)
        if headers.cache_control.no_cache in [True, []]:
            complain(1175)
        if headers.cache_control.no_store:
            complain(1176)

        if known.status_code.is_cacheable(status) is Cacheable.not_at_all:
            complain(1202)
        if known.status_code.is_cacheable(status) is Cacheable.not_by_default:
            if headers.expires.is_absent and headers.cache_control.is_absent:
                complain(1177)

    if resp.heuristic_expiration:
        if headers.age > (24 * 60 * 60) and \
                warn.heuristic_expiration not in headers.warning:
            complain(1180)
        if headers.expires.is_present:
            complain(1181)
        elif headers.cache_control.max_age is not None:
            complain(1182)

    if resp.stale:
        if warn.response_is_stale not in headers.warning:
            complain(1186)
        if headers.cache_control.must_revalidate:
            complain(1187)

    if headers.cache_control.immutable:
        if headers.expires.is_absent or headers.expires.value <= headers.date:
            if not headers.cache_control.max_age:
                complain(1301)
        if resp.is_tls is False:
            complain(1302)
        if resp.delimited_by_close:
            complain(1303)

    if headers.allow.is_present:
        if headers.accept_patch.is_present and m.PATCH not in headers.allow:
            complain(1217)
        if headers.accept_post.is_present and m.POST not in headers.allow:
            complain(1310)

    if resp.transformed_by_proxy and headers.via.is_absent:
        complain(1046)