Beispiel #1
0
    def do_request(self, req, status, expect_errors):
        """
        Override webtest.TestApp's method so that we do real HTTP requests
        instead of WSGI calls.
        """
        headers = {}
        if self.cookies:
            c = BaseCookie()
            for name, value in self.cookies.items():
                c[name] = value
            hc = '; '.join(['='.join([m.key, m.value]) for m in c.values()])
            req.headers['Cookie'] = hc

        res = self._do_httplib_request(req)
        # Set these attributes for consistency with webtest.
        res.request = req
        res.test_app = self

        if not expect_errors:
            self._check_status(res.status_int, res)
            self._check_errors(res)
        res.cookies_set = {}

        for header in res.headers.getall('set-cookie'):
            try:
                c = BaseCookie(header)
            except CookieError as e:
                raise CookieError(
                    "Could not parse cookie header %r: %s" % (header, e))
            for key, morsel in c.items():
                self.cookies[key] = morsel.value
                res.cookies_set[key] = morsel.value
        return res
    def do_request(self, req, status, expect_errors):
        """
        Override webtest.TestApp's method so that we do real HTTP requests
        instead of WSGI calls.
        """
        headers = {}
        if self.cookies:
            c = BaseCookie()
            for name, value in list(self.cookies.items()):
                c[name] = value
            hc = '; '.join(
                ['='.join([m.key, m.value]) for m in list(c.values())])
            req.headers['Cookie'] = hc
        res = self._do_httplib_request(req)
        # Set these attributes for consistency with webtest.
        res.request = req
        res.test_app = self

        if not expect_errors:
            self._check_status(res.status_int, res)
            self._check_errors(res)
        res.cookies_set = {}

        # merge cookies back in
        self.cookiejar.extract_cookies(ResponseCookieAdapter(res),
                                       RequestCookieAdapter(req))
        return res
Beispiel #3
0
    def unset_cookie(self, name):
        """Remove a cookie from those that are being sent with the response"""
        name = isomorphic_decode(name)
        cookies = self.headers.get("Set-Cookie")
        parser = BaseCookie()
        for cookie in cookies:
            parser.load(isomorphic_decode(cookie))

        if name in parser.keys():
            del self.headers["Set-Cookie"]
            for m in parser.values():
                if m.key != name:
                    self.headers.append(("Set-Cookie", m.OutputString()))
Beispiel #4
0
    async def handleRequest(self, reader, writer):
        # connection id, for debugging
        connid = self.nextConnId
        self.nextConnId += 1

        logger.debug(f'{connid}: new incoming connection')
        # simple HTTP parsing, yes this is a terrible idea.
        l = await reader.readline()
        if l == bytes():
            raise BadRequest(f'{connid}: unexpected eof')
        l = l.rstrip(b'\r\n')
        try:
            method, rawPath, proto = l.split(b' ')
            logger.debug(f'{connid}: got {method} {rawPath} {proto}')
        except ValueError:
            logger.error(f'{connid}: cannot split line {l}')
            raise
        reqUrl = furl(rawPath.decode('utf-8'))

        headers = CIMultiDict()
        while True:
            if len(headers) > 100:
                raise BadRequest('too many headers')

            l = await reader.readline()
            if l == bytes():
                raise BadRequest(f'{connid}: unexpected eof in headers')
            l = l.rstrip(b'\r\n')
            logger.debug(f'{connid}: got header line {l!r}')
            # end of headers?
            if l == bytes():
                break
            try:
                key, value = l.decode('utf-8').split(':', 1)
                headers.add(key.strip(), value.strip())
            except ValueError:
                logger.error(f'cannot parse {l}')

        logger.debug(f'{connid}: {rawPath} {method} got headers {headers}')

        route = None
        try:
            netloc = headers['host']
            reqUrl = reqUrl.set(scheme='http', netloc=netloc)
            logger.debug(f'got request url {reqUrl}')
            routeKey = None
            for d in self.domain:
                m = parse(d, reqUrl.netloc)
                if m is not None:
                    routeKey = RouteKey(key=m['key'], user=m['user'])
                    logger.debug(f'{connid}: got route key {routeKey}')
                    break
            route = self.routes[routeKey]
        except (KeyError, ValueError):
            logger.info(f'{connid}: cannot find route for {reqUrl}')
            self.status['noroute'] += 1
            # error is written to client later

        # is this a non-forwarded request?
        segments = reqUrl.path.segments
        if len(segments) > 0 and segments[0] == '_conductor':
            if segments[1] == 'auth':
                logger.info(f'authorization request for {reqUrl.netloc}')
                try:
                    nextLoc = reqUrl.query.params['next'].encode('utf-8')
                except KeyError:
                    nextLoc = b'/'
                writer.write(b'\r\n'.join([
                    b'HTTP/1.0 302 Found', b'Location: ' + nextLoc,
                    b'Set-Cookie: authorization=' +
                    segments[2].encode('utf-8') + b'; HttpOnly; Path=/',
                    b'Cache-Control: no-store', b'',
                    b'Follow the white rabbit.'
                ]))
            elif segments[1] == 'status':
                writer.write(
                    b'HTTP/1.0 200 OK\r\nContent-Type: application/json\r\n\r\n'
                )
                self.status['routesTotal'] = len(self.routes)
                writer.write(
                    json.dumps(self.status, ensure_ascii=True).encode('ascii'))
            else:
                writer.write(
                    b'HTTP/1.0 404 Not Found\r\nContent-Type: plain/text\r\n\r\nNot found'
                )
            writer.close()
            return

        if not route:
            writer.write(
                b'HTTP/1.0 404 Not Found\r\nConnection: close\r\n\r\n')
            writer.close()
            return

        # check authorization
        cookies = BaseCookie()
        try:
            cookies.load(headers['Cookie'])
        except KeyError:
            # will be rejected later
            pass
        authorized = False
        for c in cookies.values():
            # Only hashed authorization is available to server.
            if c.key == 'authorization' and self.hashKey(
                    c.value) == route.auth:
                authorized = True
                break
        try:
            # do not forward auth cookie to the application, so it can’t leak it.
            del cookies['authorization']
            headers['Cookie'] = cookies.output(header='', sep='')
        except KeyError:
            # nonexistent cookie is fine
            pass

        if not authorized:
            logger.info(
                f'{connid}-{reqUrl}: not authorized, cookies sent {cookies.values()}'
            )
            writer.write(
                b'HTTP/1.0 403 Unauthorized\r\nContent-Type: plain/text\r\nConnection: close\r\n\r\nUnauthorized'
            )
            writer.close()
            self.status['unauthorized'] += 1
            return

        # try opening the socket
        try:
            start = time.time()
            sockreader, sockwriter = await asyncio.open_unix_connection(
                path=route.socket)
            end = time.time()
            logger.debug(f'opening socket took {end-start}s')
        except (ConnectionRefusedError, FileNotFoundError, PermissionError):
            logger.info(f'{connid}-{reqUrl}: route {routeKey} is broken')
            writer.write(
                b'HTTP/1.0 502 Bad Gateway\r\nConnection: close\r\n\r\n')
            writer.close()
            self.status['broken'] += 1
            return

        # some headers are fixed
        # not parsing body, so we cannot handle more than one request per connection
        # XXX: this is super-inefficient
        if 'Upgrade' not in headers or headers['Upgrade'].lower(
        ) != 'websocket':
            headers['Connection'] = 'close'

        # write http banner plus headers
        sockwriter.write(method + b' ' + rawPath + b' ' + proto + b'\r\n')
        for k, v in headers.items():
            sockwriter.write(f'{k}: {v}\r\n'.encode('utf-8'))
        sockwriter.write(b'\r\n')

        async def beforeABClose(result):
            if result == 0:
                # no response received from client
                logger.info(
                    f'{connid}-{reqUrl}: route {routeKey} got no result from server'
                )
                writer.write(
                    b'HTTP/1.0 502 Bad Gateway\r\nConnection: close\r\n\r\n')

        await proxy((sockreader, sockwriter, 'sock'), (reader, writer, 'web'),
                    logger=logger,
                    logPrefix=connid,
                    beforeABClose=beforeABClose)