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
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()))
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)