# -*- 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