Пример #1
0
    def test_get_auth_string(self):
        # Typical Header with Authorization
        headers_with_auth = Headers([
            ('X-Amz-Date', '20210313T160953Z'),
            ('Authorization', ('AWS4-HMAC-SHA256 Credential='
                               'test/20210313/us-east-1/sqs/aws4_request, '
                               'SignedHeaders=content-type;host;x-amz-date, '
                               'Signature='
                               '3cba88ae6cbb8036126d2ba18ba8ded5'
                               'eea9e5484d70822affce9dad03be5993')),
        ])

        body_with_auth = (
            b'X-Amz-Algorithm=AWS4-HMAC-SHA256&' + b'X-Amz-Credential=' +
            b'test%2F20210313%2Fus-east-1%2Fsqs%2Faws4_request&' +
            b'X-Amz-Date=20210313T011059Z&' + b'X-Amz-Expires=86400000&' +
            b'X-Amz-SignedHeaders=content-type%3Bhost%3Bx-amz-date&' +
            b'X-Amz-Signature=' +
            b'3cba88ae6cbb8036126d2ba18ba8ded5eea9e5484d70822affce9dad03be5993'
        )

        # check getting auth string from header with Authorization header
        self.assertEqual(headers_with_auth.get('authorization'),
                         get_auth_string('POST', '/', headers_with_auth, b''))

        # check getting auth string from body with authorization params
        self.assertEqual(
            headers_with_auth.get('authorization'),
            get_auth_string('POST', '/', Headers(), body_with_auth))
Пример #2
0
    def _create_request_from_scope(self, send: Callable) -> Request:
        headers = Headers()
        headers["Remote-Addr"] = (self.scope.get("client") or ["<local>"])[0]
        for name, value in self.scope["headers"]:
            headers.add(name.decode("latin1").title(), value.decode("latin1"))
        if self.scope["http_version"] < "1.1":
            headers.setdefault("Host", self.app.config["SERVER_NAME"] or "")

        path = self.scope["path"]
        path = path if path[0] == "/" else urlparse(path).path

        x_proto = self._get_real_value(1, headers.get("X-Forwarded-Proto"))
        if x_proto:
            self.scope["scheme"] = x_proto

        x_host = self._get_real_value(1, headers.get("X-Forwarded-Host"))
        if x_host:
            headers["host"] = x_host.lower()

        return self.app.request_class(
            self.scope["method"],
            self.scope["scheme"],
            path,
            self.scope["query_string"],
            headers,
            self.scope.get("root_path", ""),
            self.scope["http_version"],
            max_content_length=self.app.config["MAX_CONTENT_LENGTH"],
            body_timeout=self.app.config["BODY_TIMEOUT"],
            send_push_promise=partial(self._send_push_promise, send),
            scope=self.scope,
        )
def handle_payload_v2(app, event, context):
    headers = Headers(event[u"headers"])

    script_name = get_script_name(headers, event.get("requestContext", {}))

    path_info = event[u"rawPath"]

    body = event.get("body", "")
    body = get_body_bytes(event, body)

    headers["Cookie"] = "; ".join(event.get("cookies", []))

    environ = {
        "CONTENT_LENGTH": str(len(body)),
        "CONTENT_TYPE": headers.get(u"Content-Type", ""),
        "PATH_INFO": url_unquote(path_info),
        "QUERY_STRING": url_encode(event.get(u"queryStringParameters", {})),
        "REMOTE_ADDR": event.get("requestContext", {})
        .get(u"http", {})
        .get(u"sourceIp", ""),
        "REMOTE_USER": event.get("requestContext", {})
        .get(u"authorizer", {})
        .get(u"principalId", ""),
        "REQUEST_METHOD": event.get("requestContext", {})
        .get("http", {})
        .get("method", ""),
        "SCRIPT_NAME": script_name,
        "SERVER_NAME": headers.get(u"Host", "lambda"),
        "SERVER_PORT": headers.get(u"X-Forwarded-Port", "80"),
        "SERVER_PROTOCOL": "HTTP/1.1",
        "wsgi.errors": sys.stderr,
        "wsgi.input": BytesIO(body),
        "wsgi.multiprocess": False,
        "wsgi.multithread": False,
        "wsgi.run_once": False,
        "wsgi.url_scheme": headers.get(u"X-Forwarded-Proto", "http"),
        "wsgi.version": (1, 0),
        "serverless.authorizer": event.get("requestContext", {}).get(u"authorizer"),
        "serverless.event": event,
        "serverless.context": context,
        # TODO: Deprecate the following entries, as they do not comply with the WSGI
        # spec. For custom variables, the spec says:
        #
        #   Finally, the environ dictionary may also contain server-defined variables.
        #   These variables should be named using only lower-case letters, numbers, dots,
        #   and underscores, and should be prefixed with a name that is unique to the
        #   defining server or gateway.
        "API_GATEWAY_AUTHORIZER": event.get("requestContext", {}).get(u"authorizer"),
        "event": event,
        "context": context,
    }

    environ = setup_environ_items(environ, headers)

    response = Response.from_app(app, environ)

    returndict = generate_response(response, event)
	
	returndict["multiValueHeaders"]["Access-Control-Allow-Headers"]=["Content-Type"]
Пример #4
0
def handle_lambda_integration(app, event, context):
    headers = Headers(event[u"headers"])

    script_name = get_script_name(headers, event)

    path_info = event[u"requestPath"]

    for key, value in event.get(u"path", {}).items():
        path_info = path_info.replace("{%s}" % key, value)
        path_info = path_info.replace("{%s+}" % key, value)

    body = event.get("body", {})
    body = json.dumps(body) if body else ""
    body = get_body_bytes(event, body)

    environ = {
        "CONTENT_LENGTH": str(len(body)),
        "CONTENT_TYPE": headers.get(u"Content-Type", ""),
        "PATH_INFO": url_unquote(path_info),
        "QUERY_STRING": url_encode(event.get(u"query", {})),
        "REMOTE_ADDR": event.get("identity", {}).get(u"sourceIp", ""),
        "REMOTE_USER": event.get("principalId", ""),
        "REQUEST_METHOD": event.get("method", ""),
        "SCRIPT_NAME": script_name,
        "SERVER_NAME": headers.get(u"Host", "lambda"),
        "SERVER_PORT": headers.get(u"X-Forwarded-Port", "80"),
        "SERVER_PROTOCOL": "HTTP/1.1",
        "wsgi.errors": sys.stderr,
        "wsgi.input": BytesIO(body),
        "wsgi.multiprocess": False,
        "wsgi.multithread": False,
        "wsgi.run_once": False,
        "wsgi.url_scheme": headers.get(u"X-Forwarded-Proto", "http"),
        "wsgi.version": (1, 0),
        "serverless.authorizer": event.get("enhancedAuthContext"),
        "serverless.event": event,
        "serverless.context": context,
        # TODO: Deprecate the following entries, as they do not comply with the WSGI
        # spec. For custom variables, the spec says:
        #
        #   Finally, the environ dictionary may also contain server-defined variables.
        #   These variables should be named using only lower-case letters, numbers, dots,
        #   and underscores, and should be prefixed with a name that is unique to the
        #   defining server or gateway.
        "API_GATEWAY_AUTHORIZER": event.get("enhancedAuthContext"),
        "event": event,
        "context": context,
    }

    environ = setup_environ_items(environ, headers)

    response = Response.from_app(app, environ)

    returndict = generate_response(response, event)

    if response.status_code >= 300:
        raise RuntimeError(json.dumps(returndict))

    return returndict
Пример #5
0
 def is_cors_origin_allowed(headers: Headers) -> bool:
     """Returns true if origin is allowed to perform cors requests, false otherwise."""
     origin = headers.get("origin")
     referer = headers.get("referer")
     if origin:
         return CorsEnforcer._is_in_allowed_origins(ALLOWED_CORS_ORIGINS,
                                                    origin)
     elif referer:
         referer_uri = "{uri.scheme}://{uri.netloc}".format(
             uri=urlparse(referer))
         return CorsEnforcer._is_in_allowed_origins(ALLOWED_CORS_ORIGINS,
                                                    referer_uri)
     # If both headers are not set, let it through (awscli etc. do not send these headers)
     return True
Пример #6
0
def handle_lambda_integration(app, event, context):
    headers = Headers(event["headers"])

    script_name = get_script_name(headers, event)

    path_info = event["requestPath"]

    for key, value in event.get("path", {}).items():
        path_info = path_info.replace("{%s}" % key, value)
        path_info = path_info.replace("{%s+}" % key, value)

    body = event.get("body", {})
    body = json.dumps(body) if body else ""
    body = get_body_bytes(event, body)

    environ = {
        "CONTENT_LENGTH": str(len(body)),
        "CONTENT_TYPE": headers.get("Content-Type", ""),
        "PATH_INFO": url_unquote(path_info),
        "QUERY_STRING": url_encode(event.get("query", {})),
        "REMOTE_ADDR": event.get("identity", {}).get("sourceIp", ""),
        "REMOTE_USER": event.get("principalId", ""),
        "REQUEST_METHOD": event.get("method", ""),
        "SCRIPT_NAME": script_name,
        "SERVER_NAME": headers.get("Host", "lambda"),
        "SERVER_PORT": headers.get("X-Forwarded-Port", "80"),
        "SERVER_PROTOCOL": "HTTP/1.1",
        "wsgi.errors": sys.stderr,
        "wsgi.input": io.BytesIO(body),
        "wsgi.multiprocess": False,
        "wsgi.multithread": False,
        "wsgi.run_once": False,
        "wsgi.url_scheme": headers.get("X-Forwarded-Proto", "http"),
        "wsgi.version": (1, 0),
        "serverless.authorizer": event.get("enhancedAuthContext"),
        "serverless.event": event,
        "serverless.context": context,
    }

    environ = setup_environ_items(environ, headers)

    response = Response.from_app(app, environ)

    returndict = generate_response(response, event)

    if response.status_code >= 300:
        raise RuntimeError(json.dumps(returndict))

    return returndict
Пример #7
0
def check_mimetype(
    url_path,
    headers,
    default='application/octet-stream',
    *,
    get_mimetype=default_mimetype,
):
    """Ensure mimetype sent from headers with file mimetype guessed
    from its suffix.
    Raise WrongMimetypeError if they don't match.
    """
    if url_path.endswith('/'):
        # Directories get saved as index.html
        url_path = 'index.html'
    file_mimetypes = get_mimetype(url_path)
    if file_mimetypes is None:
        file_mimetypes = [default]

    headers = Headers(headers)
    headers_mimetype, encoding = parse_options_header(
        headers.get('Content-Type'))

    if isinstance(file_mimetypes, str):
        raise TypeError("get_mimetype result must not be a string")

    if headers_mimetype.lower() not in (m.lower() for m in file_mimetypes):
        raise WrongMimetypeError(file_mimetypes, headers_mimetype, url_path)
Пример #8
0
    def send_file(self, file_id, content_type, filename, inline=True):
        if self.proxy_downloads == ProxyDownloadsMode.local:
            return send_file(filename,
                             self.open(file_id),
                             content_type,
                             inline=inline)

        try:
            bucket, id_ = self._parse_file_id(file_id)
            content_disp = 'inline' if inline else 'attachment'
            h = Headers()
            h.add('Content-Disposition', content_disp,
                  **make_content_disposition_args(filename))
            url = self.client.generate_presigned_url(
                'get_object',
                Params={
                    'Bucket': bucket,
                    'Key': id_,
                    'ResponseContentDisposition': h.get('Content-Disposition'),
                    'ResponseContentType': content_type
                },
                ExpiresIn=120)
            response = redirect(url)
            if self.proxy_downloads == ProxyDownloadsMode.nginx:
                # nginx can proxy the request to S3 to avoid exposing the redirect and
                # bucket URL to the end user (since it is quite ugly and temporary)
                response.headers[
                    'X-Accel-Redirect'] = '/.xsf/s3/' + url.replace(
                        '://', '/', 1)
            return response
        except Exception as exc:
            raise StorageError(
                f'Could not send file "{file_id}": {exc}') from exc
Пример #9
0
    def send_file(self, file_id, content_type, filename, inline=True):
        if self.proxy_downloads:
            return send_file(filename,
                             self.open(file_id),
                             content_type,
                             inline=inline)

        try:
            bucket, id_ = self._parse_file_id(file_id)
            content_disp = 'inline' if inline else 'attachment'
            h = Headers()
            h.add('Content-Disposition', content_disp, filename=filename)
            url = self.client.generate_presigned_url(
                'get_object',
                Params={
                    'Bucket': bucket,
                    'Key': id_,
                    'ResponseContentDisposition': h.get('Content-Disposition'),
                    'ResponseContentType': content_type
                },
                ExpiresIn=120)
            return redirect(url)
        except Exception as e:
            raise StorageError('Could not send file "{}": {}'.format(
                file_id, e)), None, sys.exc_info()[2]
Пример #10
0
def calendar_api(api_request: Request):
    """Cloud function entry point

    :param api_request: http request
    """
    with app.app_context():

        # construct headers
        headers = Headers()
        for key, value in api_request.headers.items():
            headers.add(key, value)

        # prepare content
        content = {}
        if headers.get('content-type') == 'application/json':
            content['json'] = api_request.get_json(silent=True)
        else:
            content['data'] = api_request.form

        with app.test_request_context(method=api_request.method,
                                      base_url=api_request.base_url,
                                      path=api_request.path,
                                      query_string=api_request.query_string,
                                      headers=headers,
                                      **content):
            try:
                rv = app.preprocess_request()
                if rv is None:
                    rv = app.dispatch_request()
            except Exception as e:
                rv = app.handle_user_exception(e)
            response = app.make_response(rv)
            return app.process_response(response)
Пример #11
0
    def get_part_charset(self, headers: Headers) -> str:
        content_type = headers.get("content-type")

        if content_type:
            mimetype, ct_params = parse_options_header(content_type)
            return ct_params.get("charset", self.charset)

        return self.charset
Пример #12
0
def _get_api_token(headers: Headers) -> Optional[str]:
    authorization_value = headers.get('Authorization')
    if not authorization_value:
        return None

    prefix = 'Token '
    if not authorization_value.startswith(prefix):
        return None

    return authorization_value[len(prefix) :]
Пример #13
0
def check_mimetype(url_path, headers, default='application/octet-stream'):
    if url_path.endswith('/'):
        # Directories get saved as index.html
        url_path = 'index.html'
    file_type, file_encoding = guess_type(url_path)
    if not file_type:
        file_type = default
    headers = Headers(headers)
    mime_type, encoding = parse_options_header(headers.get('Content-Type'))
    if file_type.lower() != mime_type.lower():
        raise WrongMimetypeError(file_type, mime_type, url_path)
Пример #14
0
    def test_get_auth_string(self):
        # Typical Header with Authorization
        headers_with_auth = Headers(
            [
                ("X-Amz-Date", "20210313T160953Z"),
                (
                    "Authorization",
                    (
                        "AWS4-HMAC-SHA256 Credential="
                        "test/20210313/us-east-1/sqs/aws4_request, "
                        "SignedHeaders=content-type;host;x-amz-date, "
                        "Signature="
                        "3cba88ae6cbb8036126d2ba18ba8ded5"
                        "eea9e5484d70822affce9dad03be5993"
                    ),
                ),
            ]
        )

        body_with_auth = (
            b"X-Amz-Algorithm=AWS4-HMAC-SHA256&"
            + b"X-Amz-Credential="
            + b"test%2F20210313%2Fus-east-1%2Fsqs%2Faws4_request&"
            + b"X-Amz-Date=20210313T011059Z&"
            + b"X-Amz-Expires=86400000&"
            + b"X-Amz-SignedHeaders=content-type%3Bhost%3Bx-amz-date&"
            + b"X-Amz-Signature="
            + b"3cba88ae6cbb8036126d2ba18ba8ded5eea9e5484d70822affce9dad03be5993"
        )

        # check getting auth string from header with Authorization header
        self.assertEqual(
            headers_with_auth.get("authorization"),
            get_auth_string("POST", "/", headers_with_auth, b""),
        )

        # check getting auth string from body with authorization params
        self.assertEqual(
            headers_with_auth.get("authorization"),
            get_auth_string("POST", "/", Headers(), body_with_auth),
        )
Пример #15
0
    def now_handler(event, context):
        payload = json.loads(event['body'])

        headers = Headers(payload.get('headers', {}))

        body = payload.get('body', '')
        if body != '':
            if payload.get('encoding') == 'base64':
                body = base64.b64decode(body)
        if isinstance(body, string_types):
            body = to_bytes(body, charset='utf-8')

        path = unquote(payload['path'])
        query = urlparse(path).query

        environ = {
            'CONTENT_LENGTH': str(len(body)),
            'CONTENT_TYPE': headers.get('content-type', ''),
            'PATH_INFO': path,
            'QUERY_STRING': query,
            'REMOTE_ADDR': headers.get(
                'x-forwarded-for', headers.get(
                    'x-real-ip', payload.get(
                        'true-client-ip', ''))),
            'REQUEST_METHOD': payload['method'],
            'SERVER_NAME': headers.get('host', 'lambda'),
            'SERVER_PORT': headers.get('x-forwarded-port', '80'),
            'SERVER_PROTOCOL': 'HTTP/1.1',
            'event': event,
            'context': context,
            'wsgi.errors': sys.stderr,
            'wsgi.input': BytesIO(body),
            'wsgi.multiprocess': False,
            'wsgi.multithread': False,
            'wsgi.run_once': False,
            'wsgi.url_scheme': headers.get('x-forwarded-proto', 'http'),
            'wsgi.version': (1, 0),
        }

        for key, value in environ.items():
            if isinstance(value, string_types) and key != 'QUERY_STRING':
                environ[key] = wsgi_encoding_dance(value)

        for key, value in headers.items():
            key = 'HTTP_' + key.upper().replace('-', '_')
            if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
                environ[key] = value

        response = Response.from_app(__NOW_HANDLER_FILENAME.app, environ)

        return_dict = {
            'statusCode': response.status_code,
            'headers': dict(response.headers)
        }

        if response.data:
            return_dict['body'] = base64.b64encode(response.data).decode('utf-8')
            return_dict['encoding'] = 'base64'

        return return_dict
Пример #16
0
class _BaseRequestResponse:
    """This is the base class for Request or Response.

    It implements a number of properties for header handling.

    Attributes:
        charset: The default charset for encoding/decoding.
    """

    charset = url_charset = "utf-8"

    def __init__(self, headers: Optional[Union[dict, Headers]]) -> None:
        self.headers: Headers
        if headers is None:
            self.headers = Headers()
        else:
            self.headers = Headers(headers)

    @property
    def mimetype(self) -> str:
        """Returns the mimetype parsed from the Content-Type header."""
        return parse_options_header(self.headers.get("Content-Type"))[0]

    @mimetype.setter
    def mimetype(self, value: str) -> None:
        """Set the mimetype to the value."""
        self.headers["Content-Type"] = get_content_type(value, self.charset)

    @property
    def mimetype_params(self) -> Dict[str, str]:
        """Returns the params parsed from the Content-Type header."""
        def _on_update(value: Dict[str, Any]) -> None:
            self.headers["Content-Type"] = dump_options_header(
                self.mimetype, value)

        value = parse_options_header(self.headers.get("Content-Type"))[1]
        return CallbackDict(value, _on_update)

    async def get_data(self, raw: bool = True) -> AnyStr:
        raise NotImplementedError()
Пример #17
0
    def get_wsgi_headers(self, environ):
        headers = Headers(self.headers)
        location = headers.get('location')
        if location is not None:
            if isinstance(location, unicode):
                location = iri_to_uri(location)
            headers['Location'] = urlparse.urljoin(get_current_url(environ, root_only=True), location)
        content_location = headers.get('content-location')
        if content_location is not None and isinstance(content_location, unicode):
            headers['Content-Location'] = iri_to_uri(content_location)
        if 100 <= self.status_code < 200 or self.status_code == 204:
            headers['Content-Length'] = '0'
        elif self.status_code == 304:
            remove_entity_headers(headers)
        if self.is_sequence and 'content-length' not in self.headers:
            try:
                content_length = sum((len(str(x)) for x in self.response))
            except UnicodeError:
                pass
            else:
                headers['Content-Length'] = str(content_length)

        return headers
Пример #18
0
    def get_wsgi_headers(self, environ):
        headers = Headers(self.headers)
        location = headers.get('location')
        if location is not None:
            if isinstance(location, unicode):
                location = iri_to_uri(location)
            headers['Location'] = urlparse.urljoin(get_current_url(environ, root_only=True), location)
        content_location = headers.get('content-location')
        if content_location is not None and isinstance(content_location, unicode):
            headers['Content-Location'] = iri_to_uri(content_location)
        if 100 <= self.status_code < 200 or self.status_code == 204:
            headers['Content-Length'] = '0'
        elif self.status_code == 304:
            remove_entity_headers(headers)
        if self.is_sequence and 'content-length' not in self.headers:
            try:
                content_length = sum((len(str(x)) for x in self.response))
            except UnicodeError:
                pass
            else:
                headers['Content-Length'] = str(content_length)

        return headers
Пример #19
0
def get_all_links(
    page_content: bytes, base_url, headers: Headers = None
) -> list:
    """Get all links from "page_content".

    Return an iterable of strings.

    base_url is the URL of the page.
    """
    if headers == None:
        cont_charset = None
    else:
        content_type_header = headers.get('Content-Type')
        cont_type, cont_options = parse_options_header(content_type_header)
        cont_charset = cont_options.get('charset')
    document = html5lib.parse(page_content, transport_encoding=cont_charset)
    return get_links_from_node(document, base_url)
Пример #20
0
class OAuthResponse(object):
    """Contains the response sent back from an OAuth protected remote
    application.
    """
    def __init__(self, resp, content):
        #: a :class:`~werkzeug.Headers` object with the response headers
        #: the application sent.
        self.headers = Headers(resp)
        #: the raw, unencoded content from the server
        self.raw_data = content
        #: the parsed content from the server
        self.data = parse_response(resp, content, strict=True)

    @property
    def status(self):
        """The status code of the response."""
        return self.headers.get('status', type=int)
Пример #21
0
    def get_url(self, request_headers: Headers) -> str:
        """
        Args:
            request_headers: The request headers

        Returns:
            The service URL according to the protocol.
        """
        if self.service_address:
            return self.service_address
        if request_headers and '/instance/execute' in request_headers.get('X-Request-URI', ''):
            # if the server rerouting is used, then the X-Request-URI header is added to the request by the server
            # and we should use the /instance/execute endpoint in the address
            self.url_scheme = 'https'
            calling_context = get_calling_context()
            instance_name = calling_context.get('IntegrationInstance', '')
            endpoint = requote_uri(os.path.join('/instance', 'execute', instance_name))
        else:
            endpoint = f':{self.port}'
        return f'{self.url_scheme}://{self.host}{endpoint}'
Пример #22
0
    def __init__(
        self,
        method: str,
        scheme: str,
        path: str,
        query_string: bytes,
        headers: Headers,
        root_path: str,
        http_version: str,
        scope: WWWScope,
    ) -> None:
        """Create a request or websocket base object.

        Arguments:
            method: The HTTP verb.
            scheme: The scheme used for the request.
            path: The full unquoted path of the request.
            query_string: The raw bytes for the query string part.
            headers: The request headers.
            root_path: The root path that should be prepended to all
                routes.
            http_version: The HTTP version of the request.
            scope: Underlying ASGI scope dictionary.

        Attributes:
            args: The query string arguments.
            scheme: The URL scheme, http or https.
        """
        super().__init__(
            method,
            scheme,
            scope.get("server"),
            root_path,
            path,
            query_string,
            headers,
            headers.get("Remote-Addr"),
        )
        self.http_version = http_version
        self.scope = scope
Пример #23
0
    def get_token_from_header(headers: Headers = None) -> str:
        """
        Extracts the token given on the request from the Authorization headers.

        :param headers: the request headers
        :type headers: `Headers`
        :return: the extracted token
        :rtype: str
        """
        if headers is None:
            raise InvalidUsage

        if "Authorization" not in headers:
            raise InvalidCredentials("Auth token is not available")
        auth_header = headers.get("Authorization")
        if not auth_header:
            return ""
        try:
            return auth_header.split(" ")[1]
        except Exception as e:
            raise InvalidCredentials(
                f"The authorization header has a bad syntax: {e}")
def handle_request(app, event, context):
    if event.get("source") in ["aws.events", "serverless-plugin-warmup"]:
        return {}

    if u"multiValueHeaders" in event:
        headers = Headers(event[u"multiValueHeaders"])
    else:
        headers = Headers(event[u"headers"])

    if u"amazonaws.com" in headers.get(u"Host", u""):
        script_name = "/{}".format(event[u"requestContext"].get(u"stage", ""))
    else:
        script_name = ""

    # If a user is using a custom domain on API Gateway, they may have a base
    # path in their URL. This allows us to strip it out via an optional
    # environment variable.
    path_info = event[u"path"]
    base_path = os.environ.get("API_GATEWAY_BASE_PATH", "")
    if base_path:
        script_name = "/" + base_path

        if path_info.startswith(script_name):
            path_info = path_info[len(script_name):]

    body = event[u"body"] or ""
    if event.get("isBase64Encoded", False):
        body = base64.b64decode(body)
    if isinstance(body, string_types):
        body = to_bytes(body, charset="utf-8")

    environ = {
        "CONTENT_LENGTH":
        str(len(body)),
        "CONTENT_TYPE":
        headers.get(u"Content-Type", ""),
        "PATH_INFO":
        path_info,
        "QUERY_STRING":
        encode_query_string(event),
        "REMOTE_ADDR":
        event[u"requestContext"].get(u"identity", {}).get(u"sourceIp", ""),
        "REMOTE_USER":
        event[u"requestContext"].get(u"authorizer",
                                     {}).get(u"principalId", ""),
        "REQUEST_METHOD":
        event[u"httpMethod"],
        "SCRIPT_NAME":
        script_name,
        "SERVER_NAME":
        headers.get(u"Host", "lambda"),
        "SERVER_PORT":
        headers.get(u"X-Forwarded-Port", "80"),
        "SERVER_PROTOCOL":
        "HTTP/1.1",
        "wsgi.errors":
        sys.stderr,
        "wsgi.input":
        BytesIO(body),
        "wsgi.multiprocess":
        False,
        "wsgi.multithread":
        False,
        "wsgi.run_once":
        False,
        "wsgi.url_scheme":
        headers.get(u"X-Forwarded-Proto", "http"),
        "wsgi.version": (1, 0),
        "API_GATEWAY_AUTHORIZER":
        event[u"requestContext"].get(u"authorizer"),
        "event":
        event,
        "context":
        context,
    }

    for key, value in environ.items():
        if isinstance(value, string_types):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = "HTTP_" + key.upper().replace("-", "_")
        if key not in ("HTTP_CONTENT_TYPE", "HTTP_CONTENT_LENGTH"):
            environ[key] = value

    response = Response.from_app(app, environ)

    returndict = {u"statusCode": response.status_code}

    if u"multiValueHeaders" in event:
        returndict[u"multiValueHeaders"] = group_headers(response.headers)
    else:
        returndict[u"headers"] = split_headers(response.headers)

    if event.get("requestContext").get("elb"):
        # If the request comes from ALB we need to add a status description
        returndict["statusDescription"] = u"%d %s" % (
            response.status_code,
            HTTP_STATUS_CODES[response.status_code],
        )

    if response.data:
        mimetype = response.mimetype or "text/plain"
        if (mimetype.startswith("text/")
                or mimetype in TEXT_MIME_TYPES) and not response.headers.get(
                    "Content-Encoding", ""):
            returndict["body"] = response.get_data(as_text=True)
            returndict["isBase64Encoded"] = False
        else:
            returndict["body"] = base64.b64encode(
                response.data).decode("utf-8")
            returndict["isBase64Encoded"] = True

    return returndict
Пример #25
0
def redirect_stream(s3_url_builder,
                    filename,
                    mimetype=None,
                    restricted=True,
                    as_attachment=False,
                    trusted=False):
    """Redirect to URL to serve the file directly from there.

    :param url: redirection URL

    :return: Flaks response.
    """
    # Guess mimetype from filename if not provided.
    if mimetype is None and filename:
        mimetype = mimetypes.guess_type(filename)[0]
    if mimetype is None:
        mimetype = 'application/octet-stream'

    # Construct headers
    headers = Headers()

    if not trusted:
        # Sanitize MIME type
        mimetype = sanitize_mimetype(mimetype, filename=filename)
        # See https://www.owasp.org/index.php/OWASP_Secure_Headers_Project
        # Prevent JavaScript execution
        headers['Content-Security-Policy'] = "default-src 'none';"
        # Prevent MIME type sniffing for browser.
        headers['X-Content-Type-Options'] = 'nosniff'
        # Prevent opening of downloaded file by IE
        headers['X-Download-Options'] = 'noopen'
        # Prevent cross domain requests from Flash/Acrobat.
        headers['X-Permitted-Cross-Domain-Policies'] = 'none'
        # Prevent files from being embedded in frame, iframe and object tags.
        headers['X-Frame-Options'] = 'deny'
        # Enable XSS protection (IE, Chrome, Safari)
        headers['X-XSS-Protection'] = '1; mode=block'

    # Force Content-Disposition for application/octet-stream to prevent
    # Content-Type sniffing.
    if as_attachment or mimetype == 'application/octet-stream':
        # See https://github.com/pallets/flask/commit/0049922f2e690a6d
        try:
            filenames = {'filename': filename.encode('latin-1')}
        except UnicodeEncodeError:
            filenames = {'filename*': "UTF-8''%s" % url_quote(filename)}
            encoded_filename = (unicodedata.normalize('NFKD', filename).encode(
                'latin-1', 'ignore'))
            if encoded_filename:
                filenames['filename'] = encoded_filename
        headers.add('Content-Disposition', 'attachment', **filenames)
    else:
        headers.add('Content-Disposition', 'inline')

    url = s3_url_builder(
        ResponseContentType=mimetype,
        ResponseContentDisposition=headers.get('Content-Disposition'))
    headers['Location'] = url

    # TODO: Set cache-control
    # if not restricted:
    #     rv.cache_control.public = True
    #     cache_timeout = current_app.get_send_file_max_age(filename)
    #     if cache_timeout is not None:
    #         rv.cache_control.max_age = cache_timeout
    #         rv.expires = int(time() + cache_timeout)
    # Construct response object.

    rv = current_app.response_class(
        url,
        status=302,
        headers=headers,
        mimetype=mimetype,
        direct_passthrough=True,
    )

    return rv
def get_bearer_token(headers: Headers) -> str:
    auth = headers.get("Authorization", "")
    # count= 1 as in the rare case that the bearer token itself has the word Bearer in it we want it intact
    return auth.replace("Bearer", "", 1).strip()
Пример #27
0
class FileResponse(object):
    def __init__(self,
                 f,
                 disposition=None,
                 filename=None,
                 block_size=16384,
                 mimetype=None,
                 headers={},
                 auto_etag=False):
        self.f = f
        self.disposition = disposition
        self.filename = filename
        self.block_size = block_size
        self.mimetype = mimetype
        self.headers = Headers(headers)
        self.auto_etag = auto_etag

        self._stat = None
        self._last_modified = None

    def _get_filename(self):
        if self.filename is not None:
            return self.filename
        return self.f.name  # TODO: Check if this is correct

    def _get_mimetype(self):
        if self.mimetype is not None:
            return self.mimetype
        return mimetypes.guess_type(self.f.name)[0]

    def _get_disposition(self):
        if self.disposition is not None:
            return self.disposition
        return "attachment"

    def _get_stat(self):
        if self._stat is None:
            self._stat = os.fstat(self.f.fileno())
        return self._stat

    def _get_filesize(self):
        return self._get_stat().st_size

    def _get_last_modified(self):
        if self._last_modified is not None:
            return self._last_modified
        if "Last-Modified" in self.headers:
            return self.headers["Last-Modified"]
        epoch = self._get_stat().st_mtime
        dt = datetime.utcfromtimestamp(epoch)
        self._last_modified = dt.strftime("%a, %d %b %Y %H:%M:%S GMT")
        return self._last_modified

    def _get_etag(self):
        return '"{}"'.format(self._get_stat().st_mtime)

    def _check_if_range(self, environ):
        if "HTTP_IF_RANGE" not in environ:
            return True

        ifrange = environ["HTTP_IF_RANGE"]

        if not self.auto_etag and self.headers.get("Etag") == ifrange:
            return True

        if self.auto_etag and ifrange == self._get_etag():
            return True

        if ifrange == self._get_last_modified():
            return True

        return False

    def _build_content_disposition(self):
        if self.disposition is not None and (self.filename is None
                                             or self.filename is False):
            return self._get_disposition()

        if self.filename is not None and self.filename is not False:
            return "{}; filename=\"{}\"".format(self._get_disposition(),
                                                self._get_filename())

        return None

    def _get_range_data(self, environ, size):
        start = 0
        end = size - 1

        # Not a range request, sending 200
        if "HTTP_RANGE" not in environ:
            return 200, start, end, size

        # If-Range "failed"
        if not self._check_if_range(environ):
            return 200, start, end, size

        unit_ranges = environ["HTTP_RANGE"].split("=")
        # Missing ranges ("bytes="), or not "bytes". Sending 200
        if len(unit_ranges) <= 1 or unit_ranges[0] != "bytes":
            return 200, start, end, size

        ranges = unit_ranges[1].split(",")
        ran = ranges[0].split("-")
        # Too few params in range (for example "10" not "10-20")
        if len(ran) <= 1:
            return 200, start, end, size

        try:
            # Example: -10
            if len(ran[0]) == 0:
                start = end - int(ran[1]) + 1
            # Example: 10-
            elif len(ran[1]) == 0:
                start = int(ran[0])
            # Start byte is after end byte (20-10). Sending 200
            elif int(ran[0]) > int(ran[1]):
                return 200, start, end, size
            # Example: 10-20
            else:
                start = int(ran[0])
                end = int(ran[1])
        except Exception:
            # Most likely happens if the range did not contain a number
            # Example: 10-abc
            return 200, start, end, size

        return (206, start, end, (end - start) + 1)

    def _build_headers(self, status, start, end, length, size):
        headers = self.headers.copy()

        if status == 206:
            headers["Content-Range"] = "bytes {}-{}/{}".format(
                start, end, size)

        content_disposition = self._build_content_disposition()
        if content_disposition is not None:
            headers["Content-Disposition"] = content_disposition

        if self.auto_etag:
            headers["Etag"] = self._get_etag()

        headers["Accept-Ranges"] = "bytes"
        headers["Content-Type"] = self._get_mimetype()
        headers["Content-Length"] = length
        headers["Last-Modified"] = self._get_last_modified()

        return headers

    def __call__(self, environ, start_response):
        size = self._get_filesize()
        status, start, end, length = self._get_range_data(environ, size)

        headers = self._build_headers(status, start, end, length, size)

        start_response('{} {}'.format(status, HTTP_STATUS_CODES[status]),
                       headers.to_wsgi_list())

        if status == 200 and "wsgi.file_wrapper" in environ:
            print("wsgi.file_wrapper")
            return environ["wsgi.file_wrapper"](self.f, self.block_size)

        self.f.seek(start)

        return self._generator(end)

    def _generator(self, end):
        with self.f:
            block = self.block_size
            while self.f.tell() <= end:
                pos = self.f.tell()
                if pos + block > end:
                    block = end - pos + 1
                yield self.f.read(block)
Пример #28
0
def handler(app, lambda_event, context):
    event = json.loads(lambda_event['body'])
    headers = Headers(event.get('headers', None))
    parsed_url = urlparse(event['path'])

    body = event.get('body', '')
    if event.get('isBase64Encoded', False):
        body = base64.b64decode(body)
    if isinstance(body, string_types):
        body = to_bytes(body, charset='utf-8')

    environ = {
        'CONTENT_LENGTH': str(len(body)),
        'CONTENT_TYPE': headers.get('Content-Type', ''),
        'PATH_INFO': parsed_url.path,
        'QUERY_STRING': parsed_url.query,
        'REMOTE_ADDR': event.get('x-real-ip', ''),
        'REQUEST_METHOD': event['method'],
        'SCRIPT_NAME': '',
        'SERVER_NAME': headers.get('Host', 'lambda'),
        'SERVER_PORT': headers.get('X-Forwarded-Port', '80'),
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'event': lambda_event['body'],
        'wsgi.errors': sys.stderr,
        'wsgi.input': BytesIO(body),
        'wsgi.multiprocess': False,
        'wsgi.multithread': False,
        'wsgi.run_once': False,
        'wsgi.url_scheme': headers.get('X-Forwarded-Proto', 'http'),
        'wsgi.version': (1, 0),
    }

    for key, value in environ.items():
        if isinstance(value, string_types):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = 'HTTP_' + key.upper().replace('-', '_')
        if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
            environ[key] = value

    response = Response.from_app(app, environ)

    # If there are multiple Set-Cookie headers, create case-mutated variations
    # in order to pass them through APIGW. This is a hack that's currently
    # needed. See: https://github.com/logandk/serverless-wsgi/issues/11
    # Source: https://github.com/Miserlou/Zappa/blob/master/zappa/middleware.py
    new_headers = [x for x in response.headers if x[0] != 'Set-Cookie']
    cookie_headers = [x for x in response.headers if x[0] == 'Set-Cookie']
    if len(cookie_headers) > 1:
        for header, new_name in zip(cookie_headers, all_casings('Set-Cookie')):
            new_headers.append((new_name, header[1]))
    elif len(cookie_headers) == 1:
        new_headers.extend(cookie_headers)

    returndict = {
        'statusCode': response.status_code,
        'headers': dict(new_headers),
        'body': '',
    }

    if response.data:
        mimetype = response.mimetype or 'text/plain'
        if (mimetype.startswith('text/')
                or mimetype in TEXT_MIME_TYPES) and not response.headers.get(
                    'Content-Encoding', ''):
            returndict['body'] = response.get_data(as_text=True)
            returndict['isBase64Encoded'] = False
        else:
            returndict['body'] = base64.b64encode(response.data)\
                                       .decode('utf-8')
            returndict['isBase64Encoded'] = True

    return returndict
Пример #29
0
def test_headers():
    # simple header tests
    headers = Headers()
    headers.add("Content-Type", "text/plain")
    headers.add("X-Foo", "bar")
    assert "x-Foo" in headers
    assert "Content-type" in headers

    headers["Content-Type"] = "foo/bar"
    assert headers["Content-Type"] == "foo/bar"
    assert len(headers.getlist("Content-Type")) == 1

    # list conversion
    assert headers.to_list() == [("Content-Type", "foo/bar"), ("X-Foo", "bar")]
    assert str(headers) == ("Content-Type: foo/bar\r\n" "X-Foo: bar\r\n" "\r\n")
    assert str(Headers()) == "\r\n"

    # extended add
    headers.add("Content-Disposition", "attachment", filename="foo")
    assert headers["Content-Disposition"] == "attachment; filename=foo"

    headers.add("x", "y", z='"')
    assert headers["x"] == r'y; z="\""'

    # defaults
    headers = Headers([("Content-Type", "text/plain"), ("X-Foo", "bar"), ("X-Bar", "1"), ("X-Bar", "2")])
    assert headers.getlist("x-bar") == ["1", "2"]
    assert headers.get("x-Bar") == "1"
    assert headers.get("Content-Type") == "text/plain"

    assert headers.setdefault("X-Foo", "nope") == "bar"
    assert headers.setdefault("X-Bar", "nope") == "1"
    assert headers.setdefault("X-Baz", "quux") == "quux"
    assert headers.setdefault("X-Baz", "nope") == "quux"
    headers.pop("X-Baz")

    # type conversion
    assert headers.get("x-bar", type=int) == 1
    assert headers.getlist("x-bar", type=int) == [1, 2]

    # list like operations
    assert headers[0] == ("Content-Type", "text/plain")
    assert headers[:1] == Headers([("Content-Type", "text/plain")])
    del headers[:2]
    del headers[-1]
    assert headers == Headers([("X-Bar", "1")])

    # copying
    a = Headers([("foo", "bar")])
    b = a.copy()
    a.add("foo", "baz")
    assert a.getlist("foo") == ["bar", "baz"]
    assert b.getlist("foo") == ["bar"]

    headers = Headers([("a", 1)])
    assert headers.pop("a") == 1
    assert headers.pop("b", 2) == 2
    assert_raises(KeyError, headers.pop, "c")

    # set replaces and accepts same arguments as add
    a = Headers()
    a.set("Content-Disposition", "useless")
    a.set("Content-Disposition", "attachment", filename="foo")
    assert a["Content-Disposition"] == "attachment; filename=foo"
def handle_payload_v1(app, event, context):
    if u"multiValueHeaders" in event:
        headers = Headers(event[u"multiValueHeaders"])
    else:
        headers = Headers(event[u"headers"])

    script_name = get_script_name(headers, event.get("requestContext", {}))

    # If a user is using a custom domain on API Gateway, they may have a base
    # path in their URL. This allows us to strip it out via an optional
    # environment variable.
    path_info = event[u"path"]
    base_path = os.environ.get("API_GATEWAY_BASE_PATH")
    if base_path:
        script_name = "/" + base_path

        if path_info.startswith(script_name):
            path_info = path_info[len(script_name):]  # noqa: E203

    body = event[u"body"] or ""
    body = get_body_bytes(event, body)

    environ = {
        "CONTENT_LENGTH":
        str(len(body)),
        "CONTENT_TYPE":
        headers.get(u"Content-Type", ""),
        "PATH_INFO":
        url_unquote(path_info),
        "QUERY_STRING":
        encode_query_string(event),
        "REMOTE_ADDR":
        event.get(u"requestContext", {}).get(u"identity",
                                             {}).get(u"sourceIp", ""),
        "REMOTE_USER":
        event.get(u"requestContext", {}).get(u"authorizer",
                                             {}).get(u"principalId", ""),
        "REQUEST_METHOD":
        event.get(u"httpMethod", {}),
        "SCRIPT_NAME":
        script_name,
        "SERVER_NAME":
        headers.get(u"Host", "lambda"),
        "SERVER_PORT":
        headers.get(u"X-Forwarded-Port", "80"),
        "SERVER_PROTOCOL":
        "HTTP/1.1",
        "wsgi.errors":
        sys.stderr,
        "wsgi.input":
        BytesIO(body),
        "wsgi.multiprocess":
        False,
        "wsgi.multithread":
        False,
        "wsgi.run_once":
        False,
        "wsgi.url_scheme":
        headers.get(u"X-Forwarded-Proto", "http"),
        "wsgi.version": (1, 0),
        "serverless.authorizer":
        event.get(u"requestContext", {}).get(u"authorizer"),
        "serverless.event":
        event,
        "serverless.context":
        context,
        # TODO: Deprecate the following entries, as they do not comply with the WSGI
        # spec. For custom variables, the spec says:
        #
        #  Finally, the environ dictionary may also contain server-defined variables.
        #  These variables should be named using only lower-case letters, numbers, dots,
        #  and underscores, and should be prefixed with a name that is unique to the
        #  defining server or gateway.
        "API_GATEWAY_AUTHORIZER":
        event.get(u"requestContext", {}).get(u"authorizer"),
        "event":
        event,
        "context":
        context,
    }

    environ = setup_environ_items(environ, headers)

    response = Response.from_app(app, environ)
    returndict = generate_response(response, event)

    return returndict
Пример #31
0
def test_headers():
    # simple header tests
    headers = Headers()
    headers.add('Content-Type', 'text/plain')
    headers.add('X-Foo', 'bar')
    assert 'x-Foo' in headers
    assert 'Content-type' in headers

    headers['Content-Type'] = 'foo/bar'
    assert headers['Content-Type'] == 'foo/bar'
    assert len(headers.getlist('Content-Type')) == 1

    # list conversion
    assert headers.to_list() == [
        ('Content-Type', 'foo/bar'),
        ('X-Foo', 'bar')
    ]
    assert str(headers) == (
        "Content-Type: foo/bar\r\n"
        "X-Foo: bar\r\n"
        "\r\n")
    assert str(Headers()) == "\r\n"

    # extended add
    headers.add('Content-Disposition', 'attachment', filename='foo')
    assert headers['Content-Disposition'] == 'attachment; filename=foo'

    headers.add('x', 'y', z='"')
    assert headers['x'] == r'y; z="\""'

    # defaults
    headers = Headers([
        ('Content-Type', 'text/plain'),
        ('X-Foo',        'bar'),
        ('X-Bar',        '1'),
        ('X-Bar',        '2')
    ])
    assert headers.getlist('x-bar') == ['1', '2']
    assert headers.get('x-Bar') == '1'
    assert headers.get('Content-Type') == 'text/plain'

    assert headers.setdefault('X-Foo', 'nope') == 'bar'
    assert headers.setdefault('X-Bar', 'nope') == '1'
    assert headers.setdefault('X-Baz', 'quux') == 'quux'
    assert headers.setdefault('X-Baz', 'nope') == 'quux'
    headers.pop('X-Baz')

    # type conversion
    assert headers.get('x-bar', type=int) == 1
    assert headers.getlist('x-bar', type=int) == [1, 2]

    # list like operations
    assert headers[0] == ('Content-Type', 'text/plain')
    assert headers[:1] == Headers([('Content-Type', 'text/plain')])
    del headers[:2]
    del headers[-1]
    assert headers == Headers([('X-Bar', '1')])

    # copying
    a = Headers([('foo', 'bar')])
    b = a.copy()
    a.add('foo', 'baz')
    assert a.getlist('foo') == ['bar', 'baz']
    assert b.getlist('foo') == ['bar']

    headers = Headers([('a', 1)])
    assert headers.pop('a') == 1
    assert headers.pop('b', 2) == 2
    assert_raises(KeyError, headers.pop, 'c')

    # set replaces and accepts same arguments as add
    a = Headers()
    a.set('Content-Disposition', 'useless')
    a.set('Content-Disposition', 'attachment', filename='foo')
    assert a['Content-Disposition'] == 'attachment; filename=foo'
Пример #32
0
def handle_request(application, event, context):

    if u"multiValueHeaders" in event:
        headers = Headers(event["multiValueHeaders"])
    else:
        headers = Headers(event["headers"])

    strip_stage_path = os.environ.get("STRIP_STAGE_PATH",
                                      "").lower().strip() in [
                                          "yes",
                                          "y",
                                          "true",
                                          "t",
                                          "1",
                                      ]
    if u"apigw.tencentcs.com" in headers.get(u"Host",
                                             u"") and not strip_stage_path:
        script_name = "/{}".format(event["requestContext"].get(u"stage", ""))
    else:
        script_name = ""

    path_info = event["path"]
    base_path = os.environ.get("API_GATEWAY_BASE_PATH")
    if base_path:
        script_name = "/" + base_path

        if path_info.startswith(script_name):
            path_info = path_info[len(script_name):] or "/"

    if u"body" in event:
        body = event[u"body"] or ""
    else:
        body = ""

    if event.get("isBase64Encoded", False):
        body = base64.b64decode(body)
    if isinstance(body, string_types):
        body = to_bytes(body, charset="utf-8")

    environ = {
        "CONTENT_LENGTH":
        str(len(body)),
        "CONTENT_TYPE":
        headers.get(u"Content-Type", ""),
        "PATH_INFO":
        url_unquote(path_info),
        "QUERY_STRING":
        encode_query_string(event),
        "REMOTE_ADDR":
        event["requestContext"].get(u"identity", {}).get(u"sourceIp", ""),
        "REMOTE_USER":
        event["requestContext"].get(u"authorizer", {}).get(u"principalId", ""),
        "REQUEST_METHOD":
        event["httpMethod"],
        "SCRIPT_NAME":
        script_name,
        "SERVER_NAME":
        headers.get(u"Host", "lambda"),
        "SERVER_PORT":
        headers.get(u"X-Forwarded-Port", "80"),
        "SERVER_PROTOCOL":
        "HTTP/1.1",
        "wsgi.errors":
        sys.stderr,
        "wsgi.input":
        BytesIO(body),
        "wsgi.multiprocess":
        False,
        "wsgi.multithread":
        False,
        "wsgi.run_once":
        False,
        "wsgi.url_scheme":
        headers.get(u"X-Forwarded-Proto", "http"),
        "wsgi.version": (1, 0),
        "serverless.authorizer":
        event["requestContext"].get(u"authorizer"),
        "serverless.event":
        event,
        "serverless.context":
        context,
        # TODO: Deprecate the following entries, as they do not comply with the WSGI
        # spec. For custom variables, the spec says:
        #
        #   Finally, the environ dictionary may also contain server-defined variables.
        #   These variables should be named using only lower-case letters, numbers, dots,
        #   and underscores, and should be prefixed with a name that is unique to the
        #   defining server or gateway.
        "API_GATEWAY_AUTHORIZER":
        event["requestContext"].get(u"authorizer"),
        "event":
        event,
        "context":
        context,
    }

    for key, value in environ.items():
        if isinstance(value, string_types):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = "HTTP_" + key.upper().replace("-", "_")
        if key not in ("HTTP_CONTENT_TYPE", "HTTP_CONTENT_LENGTH"):
            environ[key] = value

    response = Response.from_app(application, environ)

    returndict = {u"statusCode": response.status_code}

    if u"multiValueHeaders" in event:
        returndict["multiValueHeaders"] = group_headers(response.headers)
    else:
        returndict["headers"] = split_headers(response.headers)

    if event.get("requestContext").get("elb"):
        # If the request comes from ALB we need to add a status description
        returndict["statusDescription"] = u"%d %s" % (
            response.status_code,
            HTTP_STATUS_CODES[response.status_code],
        )

    if response.data:
        mimetype = response.mimetype or "text/plain"
        if (mimetype.startswith("text/")
                or mimetype in TEXT_MIME_TYPES) and not response.headers.get(
                    "Content-Encoding", ""):
            returndict["body"] = response.get_data(as_text=True)
            returndict["isBase64Encoded"] = False
        else:
            returndict["body"] = base64.b64encode(
                response.data).decode("utf-8")
            returndict["isBase64Encoded"] = True

    return returndict
Пример #33
0
def handler(event, context):
    headers = Headers(event[u'headers'])

    if headers.get(u'Host', u'').endswith(u'.amazonaws.com'):
        script_name = '/{}'.format(event[u'requestContext'].get(u'stage', ''))
    else:
        script_name = ''

    # If a user is using a custom domain on API Gateway, they may have a base
    # path in their URL. This allows us to strip it out via an optional
    # environment variable.
    path_info = event[u'path']
    base_path = os.environ.get('API_GATEWAY_BASE_PATH', '')
    if base_path:
        script_name = '/' + base_path

        if path_info.startswith(script_name):
            path_info = path_info[len(script_name):]

    body = event[u'body'] or ''
    if event.get('isBase64Encoded', False):
        body = base64.b64decode(body)
    if isinstance(body, string_types):
        body = to_bytes(wsgi_encoding_dance(body))

    environ = {
        'API_GATEWAY_AUTHORIZER':
        event[u'requestContext'].get(u'authorizer', None),
        'CONTENT_LENGTH':
        str(len(body)),
        'CONTENT_TYPE':
        headers.get(u'Content-Type', ''),
        'PATH_INFO':
        path_info,
        'QUERY_STRING':
        url_encode(event.get(u'queryStringParameters', None) or {}),
        'REMOTE_ADDR':
        event[u'requestContext'].get(u'identity', {}).get(u'sourceIp', ''),
        'REMOTE_USER':
        event[u'requestContext'].get(u'authorizer',
                                     {}).get(u'principalId', ''),
        'REQUEST_METHOD':
        event[u'httpMethod'],
        'SCRIPT_NAME':
        script_name,
        'SERVER_NAME':
        headers.get(u'Host', 'lambda'),
        'SERVER_PORT':
        headers.get(u'X-Forwarded-Port', '80'),
        'SERVER_PROTOCOL':
        'HTTP/1.1',
        'event':
        event,
        'context':
        context,
        'wsgi.errors':
        sys.stderr,
        'wsgi.input':
        BytesIO(body),
        'wsgi.multiprocess':
        False,
        'wsgi.multithread':
        False,
        'wsgi.run_once':
        False,
        'wsgi.url_scheme':
        headers.get(u'X-Forwarded-Proto', 'http'),
        'wsgi.version': (1, 0),
    }

    for key, value in environ.items():
        if isinstance(value, string_types):
            environ[key] = wsgi_encoding_dance(value)

    for key, value in headers.items():
        key = 'HTTP_' + key.upper().replace('-', '_')
        if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
            environ[key] = value

    response = Response.from_app(wsgi_app, environ)

    # If there are multiple Set-Cookie headers, create case-mutated variations
    # in order to pass them through APIGW. This is a hack that's currently
    # needed. See: https://github.com/logandk/serverless-wsgi/issues/11
    # Source: https://github.com/Miserlou/Zappa/blob/master/zappa/middleware.py
    new_headers = [x for x in response.headers if x[0] != 'Set-Cookie']
    cookie_headers = [x for x in response.headers if x[0] == 'Set-Cookie']
    if len(cookie_headers) > 1:
        for header, new_name in zip(cookie_headers, all_casings('Set-Cookie')):
            new_headers.append((new_name, header[1]))
    elif len(cookie_headers) == 1:
        new_headers.extend(cookie_headers)

    returndict = {
        u'statusCode': response.status_code,
        u'headers': dict(new_headers)
    }

    if response.data:
        mimetype = response.mimetype or 'text/plain'
        if ((mimetype.startswith('text/') or mimetype in TEXT_MIME_TYPES)
                and not response.headers.get('Content-Encoding', '')):
            returndict['body'] = response.get_data(as_text=True)
        else:
            returndict['body'] = base64.b64encode(
                response.data).decode('utf-8')
            returndict['isBase64Encoded'] = 'true'

    return returndict