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))
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"]
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
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
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
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)
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
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]
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)
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
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) :]
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)
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), )
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
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()
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
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)
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)
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}'
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
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
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()
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)
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
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
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_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
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