コード例 #1
0
    def process_connection(self, quick_reply_with=None):
        """ Continually serve an http connection.

        :param quick_reply_with: If given will be sent to the client immediately
        as a response without parsing his request.
        :return: This method doesn't return until the connection is closed.
        """
        if quick_reply_with:
            assert isinstance(quick_reply_with, ws.http.structs.HTTPResponse)

            self.push_exchange()
            self.respond(quick_reply_with)

        while True:
            error_log.debug(
                'HTTP Connection is open. Pushing new http exchange '
                'context and parsing request...')
            self.push_exchange()
            with record_rusage(self.exchange):
                request, body_start = ws.http.parser.parse(self.sock)
                self.exchange['request'] = request
                self.exchange['body_start'] = body_start
                response = self.handle_request(request)
                error_log.debug3('Request handler succeeded.')
                response = self.respond(response)
                client_persists = request and request_is_persistent(request)
                server_persists = response and response_is_persistent(response)

                if not (client_persists and server_persists):
                    break
コード例 #2
0
    def resolve_route(self, route):
        if self.reindex_is_scheduled:
            self.reindex_files()

        route = ws.http.utils.decode_uri_component(route)

        if not route.startswith(self.route_prefix):
            error_log.debug('Route %s does not start with prefix %s',
                            route, self.route_prefix)
            return None
        elif route == self.route_prefix:
            return config.get('resources', 'index_page', fallback=None)

        rel_path = route[len(self.route_prefix):]
        file_path = os.path.abspath(os.path.join(self.document_root, rel_path))

        try:
            stat = os.stat(file_path)
            file_key = (stat.st_ino, stat.st_dev)
        except FileNotFoundError:
            return None

        if file_key not in self.file_keys:
            error_log.debug('File key %s of route %s is not inside indexed '
                            'file keys.')
            return None

        return file_path
コード例 #3
0
    def accept(self):
        """
        :rtype: ws.sockets.ClientSocket
        """
        s, a = super().accept()
        error_log.debug('Accepted connection from %s on socket %s', a,
                        s.fileno())

        return Socket(fileno=s.detach()), a
コード例 #4
0
def resolve_route_depreciated(route, route_prefix, dir_prefix):
    if not route.startswith(route_prefix):
        error_log.debug('Route %s does not start with prefix %s',
                        route, route_prefix)
        return None

    rel_path = route[len(route_prefix):]
    file_path = os.path.join(dir_prefix, rel_path)
    resolved = os.path.abspath(os.path.realpath(file_path))

    # if a symlink get's created after this if statement an exploit is possible
    if not resolved.startswith(dir_prefix):
        error_log.debug('Resolved route %s is not inside dir %s',
                        resolved, dir_prefix)
        return None

    return resolved
コード例 #5
0
def worker_status(request_stats):
    error_log.debug('Serving worker stats.')
    body = []
    for stat_name, stat_entry in request_stats.items():
        total, count = stat_entry['total'], stat_entry['count']
        if count:
            line = '{}:{}'.format(stat_name, total / count).encode('ascii')
            body.append(line)
    body = b'\n'.join(body)
    ib = io.BytesIO()
    ib.write(body)
    ib.seek(0)
    return ws.http.utils.build_response(
        status_code=200,
        headers={'Content-Length': len(body),
                 'Content-Type': 'text/html'},
        body=ib
    )
コード例 #6
0
    def respond(self, response=None, *, closing=False):
        """

        :param response: Response object to send to client
        :param closing: Boolean switch whether the http connection should be
            closed after this response.
        """
        assert isinstance(closing, bool)

        self.exchange['response'] = response

        if response:
            assert isinstance(response, ws.http.structs.HTTPResponse)

            request = self.exchange['request']
            if closing:
                error_log.debug('Closing connection. (explicitly)')
                conn = 'close'
            elif request:
                try:
                    conn = str(request.headers['Connection'], encoding='ascii')
                except (KeyError, UnicodeDecodeError):
                    error_log.debug('Getting Connection header from request '
                                    'failed. Closing connection.')
                    conn = 'close'
            else:
                error_log.debug('No request parsed. Closing connection.')
                conn = 'close'
            response.headers['Connection'] = conn
            self.exchange['written'] = True
            for chunk in response.iter_chunks():
                self.sock.sendall(chunk)

        return response
コード例 #7
0
    def fork_workers(self, count=1):
        error_log.info('Forking %s workers', self.process_count_limit)
        assert isinstance(count, int)

        for _ in range(count):
            fd_transport = ws.sockets.FDTransport()
            pid = os.fork()
            if pid:
                self.execution_context = self.ExecutionContext.main
                error_log.debug('Forked worker with pid=%s', pid)
                ws.sockets.randomize_ssl_after_fork()
                fd_transport.mode = 'sender'
                wp = WorkerProcess(pid=pid, fd_transport=fd_transport)
                self.workers[wp.pid] = wp
            else:
                self.execution_context = self.ExecutionContext.worker
                ws.signals.reset_handlers(excluding={ws.signals.SIGTERM})
                ws.signals.signal(ws.signals.SIGCHLD, ws.signals.SIG_IGN)
                # noinspection PyBroadException
                try:
                    ws.logs.setup_worker_handlers()
                    fd_transport.mode = 'receiver'
                    for other_worker in self.workers.values():
                        other_worker.close_ipc()
                    self.sock.close()
                    os.close(0)
                    os.close(1)
                    os.close(2)
                    with profile(WORKER_PROFILING_ON):
                        worker = ws.worker.Worker(fd_transport=fd_transport)
                        exit_code = worker.work()
                except BaseException:
                    error_log.exception('Worker failed.')
                    exit_code = 1

                # noinspection PyProtectedMember
                os._exit(exit_code)
コード例 #8
0
    def reap_children(self, signum, stack_frame):
        # TODO use a lock
        error_log.debug3('reap_children() called.')
        if self.reaping:
            return

        self.reaping = True

        try:
            while True:
                pid, exit_indicator = os.waitpid(-1, os.WNOHANG)
                if pid:
                    error_log.debug('Reaped pid %s', pid)
                    assert pid not in self.reaped_pids
                    self.reaped_pids.add(pid)
                else:
                    break
        except OSError as err:
            error_log.debug(
                'During reaping of zombie child: wait() sys call '
                'failed with ERRNO=%s and MSG=%s. This is mostly '
                'not a problem.', err.errno, err.strerror)
        finally:
            self.reaping = False
コード例 #9
0
    def record_handled_connection(self, ip_address, status_codes):
        assert isinstance(ip_address, (str, bytes))
        assert all(isinstance(sc, int) for sc in status_codes)

        err_count = sum(sc in CONSIDERED_CLIENT_ERRORS for sc in status_codes)

        if not err_count:
            return

        error_log.debug('Error count of address %s increased by %s.',
                        ip_address, err_count)

        if len(self.bad_connection_records) == self.cleanup_threshold:
            error_log.warning(
                "Reached max recorded addresses for rate limiting "
                "purposes. Dumping/freeing recorded bad connections.")
            self.bad_connection_records = {}
        if ip_address not in self.bad_connection_records:
            self.bad_connection_records[ip_address] = dict(
                ip_address=ip_address,
                first_recorded_on=time.time(),
                err_count=err_count)
        else:
            self.bad_connection_records[ip_address]['err_count'] += err_count
コード例 #10
0
def execute_script(request, socket, body_start=b''):
    assert isinstance(socket, (ws.sockets.SSLSocket, ws.sockets.Socket))
    assert isinstance(body_start, (bytes, bytearray))
    assert can_handle_request(request)

    uri = request.request_line.request_target
    cgi_script = find_cgi_script(uri)
    error_log.info('Executing CGI script %s', cgi_script.name)
    script_env = prepare_cgi_script_env(request, socket)
    error_log.debug('CGIScript environment will be: %s', script_env)

    has_body = 'Content-Length' in request.headers

    try:
        proc = subprocess.Popen(
            args=os.path.abspath(cgi_script.script_path),
            env=script_env,
            stdin=subprocess.PIPE if has_body else socket.fileno(),
            stdout=socket.fileno())
    except (OSError, ValueError):
        error_log.exception(
            'Failed to open subprocess for cgi script {}'.format(
                cgi_script.name))
        return ws.http.utils.build_response(500)

    # noinspection PyBroadException
    try:
        if not has_body:
            return

        error_log.debug('Request to CGI has body. Writing to stdin...')
        start = time.time()

        length = int(request.headers['Content-Length'])
        read_bytes = 0
        chunk_size = 4096
        timeout_reached = False
        while read_bytes < length and not timeout_reached:
            if body_start:
                chunk = body_start
                body_start = b''
            else:
                chunk = socket.recv(chunk_size)
                read_bytes += len(chunk)
            if not chunk:
                break

            while chunk:
                avail = select.select([], [proc.stdin], [], cgi_script.timeout)
                _, wlist, _ = avail
                if wlist:
                    written = wlist[0].write(chunk)
                    chunk = chunk[written:]
                else:
                    timeout_reached = True
                    break
            if time.time() - start > cgi_script.timeout:
                timeout_reached = True

        if timeout_reached:
            error_log.warning(
                'CGI script %s took too long to read body. '
                'Leaving process alive but no more data will '
                'be piped to it.', cgi_script)
    except ws.sockets.TimeoutException:
        error_log.warning("Client sent data too slowly - socket timed out.")
    except Exception:
        error_log.exception('Failed to write body to CGI script.')
    finally:
        try:
            proc.stdin.close()
        except OSError as err:
            error_log.warning(
                'Closing CGI stdin pipe failed. ERRNO=%s MSG=%s.', err.errno,
                err.strerror)