예제 #1
0
def _parse_request_heading(stream, scheme=None):
    beginning = stream.point
    try:
        with stream:
            method_ = Method(stream.consume_regex(rfc7230.method))
            stream.consume_regex(SP)
            target = stream.consume_regex(b'[^\\s]+', u'request target')
            stream.consume_regex(SP)
            version_ = HTTPVersion(stream.consume_regex(rfc7230.HTTP_version))
            _parse_line_ending(stream)
            entries = parse_header_fields(stream)
    except ParseError as e:
        stream.sane = False
        stream.complain(1006, error=e)
        return Unavailable
    else:
        req = Request(scheme,
                      method_,
                      target,
                      version_,
                      entries,
                      body=None,
                      remark=u'from %s, offset %d' % (stream.name, beginning))
        stream.dump_complaints(req.complain, place=u'request heading')
        return req
예제 #2
0
파일: iana.py 프로젝트: jayvdb/httpolice
 def _from_record(self, record):
     return {
         'key': Method(record.find('iana:value', self.xmlns).text),
         'citation': self.extract_citation(record),
         'safe': yes_no(record.find('iana:safe', self.xmlns).text),
         'idempotent':
         yes_no(record.find('iana:idempotent', self.xmlns).text),
     }
예제 #3
0
 def _from_record(self, record):
     return {
         '_': Method(record.find('iana:value', self.xmlns).text),
         '_citations': list(self.extract_citations(record)),
         'safe': yes_no(record.find('iana:safe', self.xmlns).text),
         'idempotent':
             yes_no(record.find('iana:idempotent', self.xmlns).text),
     }
예제 #4
0
def _parse_request_heading(stream, scheme=None):
    beginning = stream.tell()
    with stream.parsing(request_line):
        line = stream.readline()
        pieces = line.split(u' ')
        if len(pieces) != 3 or not HTTP_VERSION.match(pieces[2]):
            raise stream.error(beginning)
    method = Method(pieces[0])
    target = pieces[1]
    version_ = HTTPVersion(pieces[2])
    entries = parse_header_fields(stream)
    with stream.parsing(HTTP_message):
        stream.readlineend()
    req = Request(scheme, method, target, version_, entries, body=None,
                  remark=u'from %s, offset %d' % (stream.name, beginning))
    stream.dump_complaints(req.complain, place=u'request heading')
    return req
예제 #5
0
    def __init__(self,
                 scheme,
                 method,
                 target,
                 version,
                 header_entries,
                 body,
                 trailer_entries=None,
                 remark=None):
        # pylint: disable=redefined-outer-name
        """
        :param scheme:
            The scheme of the request URI, as a Unicode string
            (usually ``u'http'`` or ``u'https'``),
            or `None` if unknown (this disables some checks).

        :param method:
            The request method, as a Unicode string.

        :param target:
            The request target, as a Unicode string.
            It must be in one of the four forms `defined by RFC 7230`__.
            (For HTTP/2, it can be `reconstructed from pseudo-headers`__.)

            __ https://tools.ietf.org/html/rfc7230#section-5.3
            __ https://tools.ietf.org/html/rfc7540#section-8.1.2.3

        :param version:
            The request's protocol version, as a Unicode string,
            or `None` if unknown (this disables some checks).

            For requests sent over HTTP/1.x connections,
            this should be the HTTP version sent in the `request line`__,
            such as ``u'HTTP/1.0'`` or ``u'HTTP/1.1'``.

            __ https://tools.ietf.org/html/rfc7230#section-3.1.1

            For requests sent over HTTP/2 connections,
            this should be ``u'HTTP/2'``.

        :param header_entries:
            A list of the request's headers (may be empty).
            It must **not** include HTTP/2 `pseudo-headers`__.

            __ https://tools.ietf.org/html/rfc7540#section-8.1.2.1

            Every item of the list must be a ``(name, value)`` pair.

            `name` must be a Unicode string.

            `value` may be a byte string or a Unicode string.
            If it is Unicode, HTTPolice will assume that it has been decoded
            from ISO-8859-1 (the historic encoding of HTTP),
            and will encode it back into ISO-8859-1 before any processing.

        :param body:
            The request's payload body, as a **byte string**,
            or `None` if unknown (this disables some checks).

            If the request has no payload (like a GET request),
            this should be the empty string ``b''``.

            This must be the payload body as `defined by RFC 7230`__:
            **after** removing any ``Transfer-Encoding`` (like ``chunked``),
            but **before** removing any ``Content-Encoding`` (like ``gzip``).

            __ https://tools.ietf.org/html/rfc7230#section-3.3

        :param trailer_entries:
            A list of headers from the request's trailer part
            (as found in `chunked coding`__ or `HTTP/2`__),
            or `None` if there is no trailer part.

            __ https://tools.ietf.org/html/rfc7230#section-4.1.2
            __ https://tools.ietf.org/html/rfc7540#section-8.1

            The format is the same as for `header_entries`.

        :param remark:
            If not `None`, this Unicode string will be shown
            above the request in HTML reports
            (when the appropriate option is enabled).
            For example, it can be used to identify the source of the data:
            ``u'from somefile.dat, offset 1337'``.

        """
        super(Request, self).__init__(version, header_entries, body,
                                      trailer_entries, remark)
        self.scheme = force_unicode(scheme) if scheme is not None else None
        self.method = Method(force_unicode(method))
        self.target = force_unicode(target)
예제 #6
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)
예제 #7
0
#
#   ``_``, ``_citations``, ``safe``, ``idempotent``
#     Obvious, and usually filled by ``tools/iana.py``.
#
#   ``defines_body``
#     Whether a meaning is defined for a payload body with this method.
#     (For example, RFC 7231 Section 4.3.1 says
#     "a payload within a GET request message has no defined semantics",
#     so ``defines_body`` is ``False``.)
#
#   ``cacheable``
#     Whether responses to this method can be cached (RFC 7234).

known = KnownMethods(
    [{
        '_': Method(u'ACL'),
        '_citations': [RFC(3744, section=(8, 1))],
        'idempotent': True,
        'safe': False
    }, {
        '_': Method(u'BASELINE-CONTROL'),
        '_citations': [RFC(3253, section=(12, 6))],
        'idempotent': True,
        'safe': False
    }, {
        '_': Method(u'BIND'),
        '_citations': [RFC(5842, section=(4, ))],
        'idempotent': True,
        'safe': False
    }, {
        '_': Method(u'CHECKIN'),