Ejemplo n.º 1
0
    def _process_parsed(self, entry, parsed):
        # Work at the second level of nesting (forwarded-pairs,
        # not forwarded-elements).
        elements = [super(ForwardedView, self)._process_parsed(entry, elem)
                    for elem in parsed]

        # ``Forwarded`` is probably more likely than other headers to appear
        # multiple times in a message (as appended by intermediaries), so
        # it's more important to report these notices on a specific entry
        # rather than on the entire `ForwardedView` in `check_request`.
        for elem in elements:
            for duped in duplicates(param for (param, _value) in elem):
                self.message.complain(1296, entry=entry, param=duped)
        if len(elements) > 1 and all(len(elem) == 1 for elem in elements):
            if not duplicates(param for [(param, _value)] in elements):
                self.message.complain(1297, entry=entry,
                                      n_elements=len(elements))

        return elements
Ejemplo n.º 2
0
    def _process_parsed(self, entry, parsed):
        # Work at the second level of nesting (forwarded-pairs,
        # not forwarded-elements).
        elements = [super(ForwardedView, self)._process_parsed(entry, elem)
                    for elem in parsed]

        # ``Forwarded`` is probably more likely than other headers to appear
        # multiple times in a message (as appended by intermediaries), so
        # it's more important to report these notices on a specific entry
        # rather than on the entire `ForwardedView` in `check_request`.
        for elem in elements:
            for duped in duplicates(param for (param, _value) in elem):
                self.message.complain(1296, entry=entry, param=duped)
        if len(elements) > 1 and all(len(elem) == 1 for elem in elements):
            if not duplicates(param for [(param, _value)] in elements):
                self.message.complain(1297, entry=entry,
                                      n_elements=len(elements))

        return elements
Ejemplo n.º 3
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)
Ejemplo n.º 4
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)
Ejemplo n.º 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)