Esempio n. 1
0
    def prepare_response(self, inheaders, request_body_file):
        if self.method == 'TRACE':
            msg = force_unicode(self.request_line, 'utf-8') + '\n' + inheaders.pretty()
            return self.simple_response(httplib.OK, msg, close_after_response=False)
        request_body_file.seek(0)
        outheaders = MultiDict()
        data = RequestData(
            self.method, self.path, self.query, inheaders, request_body_file,
            outheaders, self.response_protocol, self.static_cache, self.opts,
            self.remote_addr, self.remote_port
        )
        try:
            output = self.request_handler(data)
        except HTTP404 as e:
            return self.simple_response(httplib.NOT_FOUND, msg=e.message or '', close_after_response=False)

        output = self.finalize_output(output, data, self.method is HTTP1)
        if output is None:
            return

        outheaders.set('Date', http_date(), replace_all=True)
        outheaders.set('Server', 'calibre %s' % __version__, replace_all=True)
        keep_alive = not self.close_after_response and self.opts.timeout > 0
        if keep_alive:
            outheaders.set('Keep-Alive', 'timeout=%d' % int(self.opts.timeout))
        if 'Connection' not in outheaders:
            if self.response_protocol is HTTP11:
                if self.close_after_response:
                    outheaders.set('Connection', 'close')
            else:
                if not self.close_after_response:
                    outheaders.set('Connection', 'Keep-Alive')

        ct = outheaders.get('Content-Type', '')
        if ct.startswith('text/') and 'charset=' not in ct:
            outheaders.set('Content-Type', ct + '; charset=UTF-8')

        buf = [HTTP11 + (' %d ' % data.status_code) + httplib.responses[data.status_code]]
        for header, value in sorted(outheaders.iteritems(), key=itemgetter(0)):
            buf.append('%s: %s' % (header, value))
        buf.append('')
        self.response_ready(BytesIO(b''.join((x + '\r\n').encode('ascii') for x in buf)), output=output)
Esempio n. 2
0
class HTTPPair(object):

    ''' Represents a HTTP request/response pair '''

    def __init__(self, conn, handle_request):
        self.conn = conn
        self.server_loop = conn.server_loop
        self.max_header_line_size = self.server_loop.opts.max_header_line_size
        self.scheme = 'http' if self.server_loop.ssl_context is None else 'https'
        self.inheaders = MultiDict()
        self.outheaders = MultiDict()
        self.handle_request = handle_request
        self.request_line = None
        self.path = ()
        self.qs = MultiDict()

        """When True, the request has been parsed and is ready to begin generating
        the response. When False, signals the calling Connection that the response
        should not be generated and the connection should close, immediately after
        parsing the request."""
        self.ready = False

        """Signals the calling Connection that the request should close. This does
        not imply an error! The client and/or server may each request that the
        connection be closed, after the response."""
        self.close_connection = False

        self.started_request = False
        self.reponse_protocol = HTTP1

        self.status_code = None
        self.sent_headers = False

        self.request_content_length = 0
        self.chunked_read = False

    def parse_request(self):
        """Parse the next HTTP request start-line and message-headers."""
        try:
            if not self.read_request_line():
                return
        except MaxSizeExceeded:
            self.simple_response(
                httplib.REQUEST_URI_TOO_LONG,
                "The Request-URI sent with the request exceeds the maximum allowed bytes.")
            return

        try:
            if not self.read_request_headers():
                return
        except MaxSizeExceeded:
            self.simple_response(
                httplib.REQUEST_ENTITY_TOO_LARGE,
                "The headers sent with the request exceed the maximum allowed bytes.")
            return

        self.ready = True

    def read_request_line(self):
        request_line = self.conn.socket_file.readline(maxsize=self.max_header_line_size)

        # Set started_request to True so http_communicate() knows to send 408
        # from here on out.
        self.started_request = True
        if not request_line:
            return False

        if request_line == b'\r\n':
            # RFC 2616 sec 4.1: "...if the server is reading the protocol
            # stream at the beginning of a message and receives a CRLF
            # first, it should ignore the CRLF."
            # But only ignore one leading line! else we enable a DoS.
            request_line = self.conn.socket_file.readline(maxsize=self.max_header_line_size)
            if not request_line:
                return False

        if not request_line.endswith(b'\r\n'):
            self.simple_response(
                httplib.BAD_REQUEST, "HTTP requires CRLF terminators")
            return False

        self.request_line = request_line
        try:
            method, uri, req_protocol = request_line.strip().split(b' ', 2)
            rp = int(req_protocol[5]), int(req_protocol[7])
            self.method = method.decode('ascii')
        except (ValueError, IndexError):
            self.simple_response(httplib.BAD_REQUEST, "Malformed Request-Line")
            return False

        try:
            self.request_protocol = protocol_map[rp]
        except KeyError:
            self.simple_response(httplib.HTTP_VERSION_NOT_SUPPORTED)
            return False

        scheme, authority, path = parse_request_uri(uri)
        if b'#' in path:
            self.simple_response(httplib.BAD_REQUEST, "Illegal #fragment in Request-URI.")
            return False

        if scheme:
            try:
                self.scheme = scheme.decode('ascii')
            except ValueError:
                self.simple_response(httplib.BAD_REQUEST, 'Un-decodeable scheme')
                return False

        qs = b''
        if b'?' in path:
            path, qs = path.split(b'?', 1)
            try:
                self.qs = MultiDict.create_from_query_string(qs)
            except Exception:
                self.simple_response(httplib.BAD_REQUEST, "Malformed Request-Line",
                                     'Unparseable query string')
                return False

        try:
            path = '%2F'.join(unquote(x).decode('utf-8') for x in quoted_slash.split(path))
        except ValueError as e:
            self.simple_response(httplib.BAD_REQUEST, as_unicode(e))
            return False
        self.path = tuple(x.replace('%2F', '/') for x in path.split('/'))

        self.response_protocol = protocol_map[min((1, 1), rp)]

        return True

    def read_request_headers(self):
        # then all the http headers
        try:
            self.inheaders = read_headers(partial(self.conn.socket_file.readline, maxsize=self.max_header_line_size))
            self.request_content_length = int(self.inheaders.get('Content-Length', 0))
        except ValueError as e:
            self.simple_response(httplib.BAD_REQUEST, as_unicode(e))
            return False

        if self.request_content_length > self.server_loop.opts.max_request_body_size:
            self.simple_response(
                httplib.REQUEST_ENTITY_TOO_LARGE,
                "The entity sent with the request exceeds the maximum "
                "allowed bytes (%d)." % self.server_loop.opts.max_request_body_size)
            return False

        # Persistent connection support
        if self.response_protocol is HTTP11:
            # Both server and client are HTTP/1.1
            if self.inheaders.get("Connection", "") == "close":
                self.close_connection = True
        else:
            # Either the server or client (or both) are HTTP/1.0
            if self.inheaders.get("Connection", "") != "Keep-Alive":
                self.close_connection = True

        # Transfer-Encoding support
        te = ()
        if self.response_protocol is HTTP11:
            rte = self.inheaders.get("Transfer-Encoding")
            if rte:
                te = [x.strip().lower() for x in rte.split(",") if x.strip()]
        self.chunked_read = False
        if te:
            for enc in te:
                if enc == "chunked":
                    self.chunked_read = True
                else:
                    # Note that, even if we see "chunked", we must reject
                    # if there is an extension we don't recognize.
                    self.simple_response(httplib.NOT_IMPLEMENTED, "Unknown transfer encoding: %r" % enc)
                    self.close_connection = True
                    return False

        if self.inheaders.get("Expect", '').lower() == "100-continue":
            # Don't use simple_response here, because it emits headers
            # we don't want.
            msg = HTTP11 + " 100 Continue\r\n\r\n"
            self.flushed_write(msg.encode('ascii'))
        return True

    def simple_response(self, status_code, msg=""):
        abort = status_code in (httplib.REQUEST_ENTITY_TOO_LARGE, httplib.REQUEST_URI_TOO_LONG)
        if abort:
            self.close_connection = True
            if self.reponse_protocol is HTTP1:
                # HTTP/1.0 has no 413/414 codes
                status_code = httplib.BAD_REQUEST

        msg = msg.encode('utf-8')
        buf = [
            '%s %d %s' % (self.reponse_protocol, status_code, httplib.responses[status_code]),
            "Content-Length: %s" % len(msg),
            "Content-Type: text/plain; charset=UTF-8"
        ]
        if abort and self.reponse_protocol is HTTP11:
            buf.append("Connection: close")
        buf.append('')
        buf = [(x + '\r\n').encode('ascii') for x in buf]
        buf.append(msg)
        self.flushed_write(b''.join(buf))

    def flushed_write(self, data):
        self.conn.socket_file.write(data)
        self.conn.socket_file.flush()

    def repr_for_log(self):
        return 'HTTPPair: %r\nPath:%r\nQuery:\n%s\nIn Headers:\n%s\nOut Headers:\n%s' % (
            self.request_line, self.path, self.qs.pretty('\t'), self.inheaders.pretty('\t'), self.outheaders.pretty('\t')
        )

    def generate_static_output(self, name, generator):
        return generate_static_output(self.server_loop.gso_cache, self.server_loop.gso_lock, name, generator)

    def response(self):
        if self.chunked_read:
            self.input_reader = ChunkedReader(self.conn.socket_file, self.server_loop.opts.max_request_body_size)
        else:
            self.input_reader = FixedSizeReader(self.conn.socket_file, self.request_content_length)

        output = self.handle_request(self)
        if self.status_code is None:
            raise Exception('Request handler did not set status_code')
        # Read and discard any remaining body from the HTTP request
        self.input_reader.read()

        self.status_code, output = finalize_output(output, self.inheaders, self.outheaders, self.status_code)

        self.send_headers()

        if self.method != 'HEAD':
            output.commit(self.conn.socket_file)
        self.conn.socket_file.flush()

    def send_headers(self):
        self.sent_headers = True