bazarr/libs/flask_cors/extension.py

191 lines
7.4 KiB
Python

# -*- coding: utf-8 -*-
"""
extension
~~~~
Flask-CORS is a simple extension to Flask allowing you to support cross
origin resource sharing (CORS) using a simple decorator.
:copyright: (c) 2016 by Cory Dolphin.
:license: MIT, see LICENSE for more details.
"""
from flask import request
from .core import *
try:
from urllib.parse import unquote_plus
except ImportError:
from urllib import unquote_plus
LOG = logging.getLogger(__name__)
class CORS(object):
"""
Initializes Cross Origin Resource sharing for the application. The
arguments are identical to :py:func:`cross_origin`, with the addition of a
`resources` parameter. The resources parameter defines a series of regular
expressions for resource paths to match and optionally, the associated
options to be applied to the particular resource. These options are
identical to the arguments to :py:func:`cross_origin`.
The settings for CORS are determined in the following order
1. Resource level settings (e.g when passed as a dictionary)
2. Keyword argument settings
3. App level configuration settings (e.g. CORS_*)
4. Default settings
Note: as it is possible for multiple regular expressions to match a
resource path, the regular expressions are first sorted by length,
from longest to shortest, in order to attempt to match the most
specific regular expression. This allows the definition of a
number of specific resource options, with a wildcard fallback
for all other resources.
:param resources:
The series of regular expression and (optionally) associated CORS
options to be applied to the given resource path.
If the argument is a dictionary, it's keys must be regular expressions,
and the values must be a dictionary of kwargs, identical to the kwargs
of this function.
If the argument is a list, it is expected to be a list of regular
expressions, for which the app-wide configured options are applied.
If the argument is a string, it is expected to be a regular expression
for which the app-wide configured options are applied.
Default : Match all and apply app-level configuration
:type resources: dict, iterable or string
:param origins:
The origin, or list of origins to allow requests from.
The origin(s) may be regular expressions, case-sensitive strings,
or else an asterisk
Default : '*'
:type origins: list, string or regex
:param methods:
The method or list of methods which the allowed origins are allowed to
access for non-simple requests.
Default : [GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]
:type methods: list or string
:param expose_headers:
The header or list which are safe to expose to the API of a CORS API
specification.
Default : None
:type expose_headers: list or string
:param allow_headers:
The header or list of header field names which can be used when this
resource is accessed by allowed origins. The header(s) may be regular
expressions, case-sensitive strings, or else an asterisk.
Default : '*', allow all headers
:type allow_headers: list, string or regex
:param supports_credentials:
Allows users to make authenticated requests. If true, injects the
`Access-Control-Allow-Credentials` header in responses. This allows
cookies and credentials to be submitted across domains.
:note: This option cannot be used in conjunction with a '*' origin
Default : False
:type supports_credentials: bool
:param max_age:
The maximum time for which this CORS request maybe cached. This value
is set as the `Access-Control-Max-Age` header.
Default : None
:type max_age: timedelta, integer, string or None
:param send_wildcard: If True, and the origins parameter is `*`, a wildcard
`Access-Control-Allow-Origin` header is sent, rather than the
request's `Origin` header.
Default : False
:type send_wildcard: bool
:param vary_header:
If True, the header Vary: Origin will be returned as per the W3
implementation guidelines.
Setting this header when the `Access-Control-Allow-Origin` is
dynamically generated (e.g. when there is more than one allowed
origin, and an Origin than '*' is returned) informs CDNs and other
caches that the CORS headers are dynamic, and cannot be cached.
If False, the Vary header will never be injected or altered.
Default : True
:type vary_header: bool
"""
def __init__(self, app=None, **kwargs):
self._options = kwargs
if app is not None:
self.init_app(app, **kwargs)
def init_app(self, app, **kwargs):
# The resources and options may be specified in the App Config, the CORS constructor
# or the kwargs to the call to init_app.
options = get_cors_options(app, self._options, kwargs)
# Flatten our resources into a list of the form
# (pattern_or_regexp, dictionary_of_options)
resources = parse_resources(options.get('resources'))
# Compute the options for each resource by combining the options from
# the app's configuration, the constructor, the kwargs to init_app, and
# finally the options specified in the resources dictionary.
resources = [
(pattern, get_cors_options(app, options, opts))
for (pattern, opts) in resources
]
# Create a human readable form of these resources by converting the compiled
# regular expressions into strings.
resources_human = {get_regexp_pattern(pattern): opts for (pattern,opts) in resources}
LOG.debug("Configuring CORS with resources: %s", resources_human)
cors_after_request = make_after_request_function(resources)
app.after_request(cors_after_request)
# Wrap exception handlers with cross_origin
# These error handlers will still respect the behavior of the route
if options.get('intercept_exceptions', True):
def _after_request_decorator(f):
def wrapped_function(*args, **kwargs):
return cors_after_request(app.make_response(f(*args, **kwargs)))
return wrapped_function
if hasattr(app, 'handle_exception'):
app.handle_exception = _after_request_decorator(
app.handle_exception)
app.handle_user_exception = _after_request_decorator(
app.handle_user_exception)
def make_after_request_function(resources):
def cors_after_request(resp):
# If CORS headers are set in a view decorator, pass
if resp.headers is not None and resp.headers.get(ACL_ORIGIN):
LOG.debug('CORS have been already evaluated, skipping')
return resp
normalized_path = unquote_plus(request.path)
for res_regex, res_options in resources:
if try_match(normalized_path, res_regex):
LOG.debug("Request to '%s' matches CORS resource '%s'. Using options: %s",
request.path, get_regexp_pattern(res_regex), res_options)
set_cors_headers(resp, res_options)
break
else:
LOG.debug('No CORS rule matches')
return resp
return cors_after_request