flask-cors 0.0.0.dev3__tar.gz → 0.0.0.dev4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: flask-cors
3
- Version: 0.0.0.dev3
3
+ Version: 0.0.0.dev4
4
4
  Summary: A Flask extension simplifying CORS support
5
5
  Author-email: Cory Dolphin <corydolphin@gmail.com>
6
6
  Project-URL: Homepage, https://corydolphin.github.io/flask-cors/
@@ -0,0 +1,17 @@
1
+ from .decorator import cross_origin
2
+ from .extension import CORS
3
+ from .version import __version__
4
+
5
+ __all__ = ["CORS", "__version__", "cross_origin"]
6
+
7
+ # Set default logging handler to avoid "No handler found" warnings.
8
+ import logging
9
+ from logging import NullHandler
10
+
11
+ # Set initial level to WARN. Users must manually enable logging for
12
+ # flask_cors to see our logging.
13
+ rootlogger = logging.getLogger(__name__)
14
+ rootlogger.addHandler(NullHandler())
15
+
16
+ if rootlogger.level == logging.NOTSET:
17
+ rootlogger.setLevel(logging.WARN)
@@ -0,0 +1,386 @@
1
+ import logging
2
+ import re
3
+ from collections.abc import Iterable
4
+ from datetime import timedelta
5
+
6
+ from flask import current_app, request
7
+ from werkzeug.datastructures import Headers, MultiDict
8
+
9
+ LOG = logging.getLogger(__name__)
10
+
11
+ # Response Headers
12
+ ACL_ORIGIN = "Access-Control-Allow-Origin"
13
+ ACL_METHODS = "Access-Control-Allow-Methods"
14
+ ACL_ALLOW_HEADERS = "Access-Control-Allow-Headers"
15
+ ACL_EXPOSE_HEADERS = "Access-Control-Expose-Headers"
16
+ ACL_CREDENTIALS = "Access-Control-Allow-Credentials"
17
+ ACL_MAX_AGE = "Access-Control-Max-Age"
18
+ ACL_RESPONSE_PRIVATE_NETWORK = "Access-Control-Allow-Private-Network"
19
+
20
+ # Request Header
21
+ ACL_REQUEST_METHOD = "Access-Control-Request-Method"
22
+ ACL_REQUEST_HEADERS = "Access-Control-Request-Headers"
23
+ ACL_REQUEST_HEADER_PRIVATE_NETWORK = "Access-Control-Request-Private-Network"
24
+
25
+ ALL_METHODS = ["GET", "HEAD", "POST", "OPTIONS", "PUT", "PATCH", "DELETE"]
26
+ CONFIG_OPTIONS = [
27
+ "CORS_ORIGINS",
28
+ "CORS_METHODS",
29
+ "CORS_ALLOW_HEADERS",
30
+ "CORS_EXPOSE_HEADERS",
31
+ "CORS_SUPPORTS_CREDENTIALS",
32
+ "CORS_MAX_AGE",
33
+ "CORS_SEND_WILDCARD",
34
+ "CORS_AUTOMATIC_OPTIONS",
35
+ "CORS_VARY_HEADER",
36
+ "CORS_RESOURCES",
37
+ "CORS_INTERCEPT_EXCEPTIONS",
38
+ "CORS_ALWAYS_SEND",
39
+ "CORS_ALLOW_PRIVATE_NETWORK",
40
+ ]
41
+ # Attribute added to request object by decorator to indicate that CORS
42
+ # was evaluated, in case the decorator and extension are both applied
43
+ # to a view.
44
+ FLASK_CORS_EVALUATED = "_FLASK_CORS_EVALUATED"
45
+
46
+ # Strange, but this gets the type of a compiled regex, which is otherwise not
47
+ # exposed in a public API.
48
+ RegexObject = type(re.compile(""))
49
+ DEFAULT_OPTIONS = dict(
50
+ origins="*",
51
+ methods=ALL_METHODS,
52
+ allow_headers="*",
53
+ expose_headers=None,
54
+ supports_credentials=False,
55
+ max_age=None,
56
+ send_wildcard=False,
57
+ automatic_options=True,
58
+ vary_header=True,
59
+ resources=r"/*",
60
+ intercept_exceptions=True,
61
+ always_send=True,
62
+ allow_private_network=False,
63
+ )
64
+
65
+
66
+ def parse_resources(resources):
67
+ if isinstance(resources, dict):
68
+ # To make the API more consistent with the decorator, allow a
69
+ # resource of '*', which is not actually a valid regexp.
70
+ resources = [(re_fix(k), v) for k, v in resources.items()]
71
+
72
+ # Sort by regex length to provide consistency of matching and
73
+ # to provide a proxy for specificity of match. E.G. longer
74
+ # regular expressions are tried first.
75
+ def pattern_length(pair):
76
+ maybe_regex, _ = pair
77
+ return len(get_regexp_pattern(maybe_regex))
78
+
79
+ return sorted(resources, key=pattern_length, reverse=True)
80
+
81
+ elif isinstance(resources, str):
82
+ return [(re_fix(resources), {})]
83
+
84
+ elif isinstance(resources, Iterable):
85
+ return [(re_fix(r), {}) for r in resources]
86
+
87
+ # Type of compiled regex is not part of the public API. Test for this
88
+ # at runtime.
89
+ elif isinstance(resources, RegexObject):
90
+ return [(re_fix(resources), {})]
91
+
92
+ else:
93
+ raise ValueError("Unexpected value for resources argument.")
94
+
95
+
96
+ def get_regexp_pattern(regexp):
97
+ """
98
+ Helper that returns regexp pattern from given value.
99
+
100
+ :param regexp: regular expression to stringify
101
+ :type regexp: _sre.SRE_Pattern or str
102
+ :returns: string representation of given regexp pattern
103
+ :rtype: str
104
+ """
105
+ try:
106
+ return regexp.pattern
107
+ except AttributeError:
108
+ return str(regexp)
109
+
110
+
111
+ def get_cors_origins(options, request_origin):
112
+ origins = options.get("origins")
113
+ wildcard = r".*" in origins
114
+
115
+ # If the Origin header is not present terminate this set of steps.
116
+ # The request is outside the scope of this specification.-- W3Spec
117
+ if request_origin:
118
+ LOG.debug("CORS request received with 'Origin' %s", request_origin)
119
+
120
+ # If the allowed origins is an asterisk or 'wildcard', always match
121
+ if wildcard and options.get("send_wildcard"):
122
+ LOG.debug("Allowed origins are set to '*'. Sending wildcard CORS header.")
123
+ return ["*"]
124
+ # If the value of the Origin header is a case-sensitive match
125
+ # for any of the values in list of origins
126
+ elif try_match_any(request_origin, origins):
127
+ LOG.debug(
128
+ "The request's Origin header matches. Sending CORS headers.",
129
+ )
130
+ # Add a single Access-Control-Allow-Origin header, with either
131
+ # the value of the Origin header or the string "*" as value.
132
+ # -- W3Spec
133
+ return [request_origin]
134
+ else:
135
+ LOG.debug("The request's Origin header does not match any of allowed origins.")
136
+ return None
137
+
138
+ elif options.get("always_send"):
139
+ if wildcard:
140
+ # If wildcard is in the origins, even if 'send_wildcard' is False,
141
+ # simply send the wildcard. Unless supports_credentials is True,
142
+ # since that is forbidden by the spec..
143
+ # It is the most-likely to be correct thing to do (the only other
144
+ # option is to return nothing, which almost certainly not what
145
+ # the developer wants if the '*' origin was specified.
146
+ if options.get("supports_credentials"):
147
+ return None
148
+ else:
149
+ return ["*"]
150
+ else:
151
+ # Return all origins that are not regexes.
152
+ return sorted([o for o in origins if not probably_regex(o)])
153
+
154
+ # Terminate these steps, return the original request untouched.
155
+ else:
156
+ LOG.debug(
157
+ "The request did not contain an 'Origin' header. This means the browser or client did not request CORS, ensure the Origin Header is set."
158
+ )
159
+ return None
160
+
161
+
162
+ def get_allow_headers(options, acl_request_headers):
163
+ if acl_request_headers:
164
+ request_headers = [h.strip() for h in acl_request_headers.split(",")]
165
+
166
+ # any header that matches in the allow_headers
167
+ matching_headers = filter(lambda h: try_match_any(h, options.get("allow_headers")), request_headers)
168
+
169
+ return ", ".join(sorted(matching_headers))
170
+
171
+ return None
172
+
173
+
174
+ def get_cors_headers(options, request_headers, request_method):
175
+ origins_to_set = get_cors_origins(options, request_headers.get("Origin"))
176
+ headers = MultiDict()
177
+
178
+ if not origins_to_set: # CORS is not enabled for this route
179
+ return headers
180
+
181
+ for origin in origins_to_set:
182
+ headers.add(ACL_ORIGIN, origin)
183
+
184
+ headers[ACL_EXPOSE_HEADERS] = options.get("expose_headers")
185
+
186
+ if options.get("supports_credentials"):
187
+ headers[ACL_CREDENTIALS] = "true" # case sensitive
188
+
189
+ if (
190
+ ACL_REQUEST_HEADER_PRIVATE_NETWORK in request_headers
191
+ and request_headers.get(ACL_REQUEST_HEADER_PRIVATE_NETWORK) == "true"
192
+ ):
193
+ allow_private_network = "true" if options.get("allow_private_network") else "false"
194
+ headers[ACL_RESPONSE_PRIVATE_NETWORK] = allow_private_network
195
+
196
+ # This is a preflight request
197
+ # http://www.w3.org/TR/cors/#resource-preflight-requests
198
+ if request_method == "OPTIONS":
199
+ acl_request_method = request_headers.get(ACL_REQUEST_METHOD, "").upper()
200
+
201
+ # If there is no Access-Control-Request-Method header or if parsing
202
+ # failed, do not set any additional headers
203
+ if acl_request_method and acl_request_method in options.get("methods"):
204
+ # If method is not a case-sensitive match for any of the values in
205
+ # list of methods do not set any additional headers and terminate
206
+ # this set of steps.
207
+ headers[ACL_ALLOW_HEADERS] = get_allow_headers(options, request_headers.get(ACL_REQUEST_HEADERS))
208
+ headers[ACL_MAX_AGE] = options.get("max_age")
209
+ headers[ACL_METHODS] = options.get("methods")
210
+ else:
211
+ LOG.info(
212
+ "The request's Access-Control-Request-Method header does not match allowed methods. CORS headers will not be applied."
213
+ )
214
+
215
+ # http://www.w3.org/TR/cors/#resource-implementation
216
+ if options.get("vary_header"):
217
+ # Only set header if the origin returned will vary dynamically,
218
+ # i.e. if we are not returning an asterisk, and there are multiple
219
+ # origins that can be matched.
220
+ if headers[ACL_ORIGIN] == "*":
221
+ pass
222
+ elif (
223
+ len(options.get("origins")) > 1
224
+ or len(origins_to_set) > 1
225
+ or any(map(probably_regex, options.get("origins")))
226
+ ):
227
+ headers.add("Vary", "Origin")
228
+
229
+ return MultiDict((k, v) for k, v in headers.items() if v)
230
+
231
+
232
+ def set_cors_headers(resp, options):
233
+ """
234
+ Performs the actual evaluation of Flask-CORS options and actually
235
+ modifies the response object.
236
+
237
+ This function is used both in the decorator and the after_request
238
+ callback
239
+ """
240
+
241
+ # If CORS has already been evaluated via the decorator, skip
242
+ if hasattr(resp, FLASK_CORS_EVALUATED):
243
+ LOG.debug("CORS have been already evaluated, skipping")
244
+ return resp
245
+
246
+ # Some libraries, like OAuthlib, set resp.headers to non Multidict
247
+ # objects (Werkzeug Headers work as well). This is a problem because
248
+ # headers allow repeated values.
249
+ if not isinstance(resp.headers, Headers) and not isinstance(resp.headers, MultiDict):
250
+ resp.headers = MultiDict(resp.headers)
251
+
252
+ headers_to_set = get_cors_headers(options, request.headers, request.method)
253
+
254
+ LOG.debug("Settings CORS headers: %s", str(headers_to_set))
255
+
256
+ for k, v in headers_to_set.items():
257
+ resp.headers.add(k, v)
258
+
259
+ return resp
260
+
261
+
262
+ def probably_regex(maybe_regex):
263
+ if isinstance(maybe_regex, RegexObject):
264
+ return True
265
+ else:
266
+ common_regex_chars = ["*", "\\", "]", "?", "$", "^", "[", "]", "(", ")"]
267
+ # Use common characters used in regular expressions as a proxy
268
+ # for if this string is in fact a regex.
269
+ return any(c in maybe_regex for c in common_regex_chars)
270
+
271
+
272
+ def re_fix(reg):
273
+ """
274
+ Replace the invalid regex r'*' with the valid, wildcard regex r'/.*' to
275
+ enable the CORS app extension to have a more user friendly api.
276
+ """
277
+ return r".*" if reg == r"*" else reg
278
+
279
+
280
+ def try_match_any(inst, patterns):
281
+ return any(try_match(inst, pattern) for pattern in patterns)
282
+
283
+
284
+ def try_match(request_origin, maybe_regex):
285
+ """Safely attempts to match a pattern or string to a request origin."""
286
+ if isinstance(maybe_regex, RegexObject):
287
+ return re.match(maybe_regex, request_origin)
288
+ elif probably_regex(maybe_regex):
289
+ return re.match(maybe_regex, request_origin, flags=re.IGNORECASE)
290
+ else:
291
+ try:
292
+ return request_origin.lower() == maybe_regex.lower()
293
+ except AttributeError:
294
+ return request_origin == maybe_regex
295
+
296
+
297
+ def get_cors_options(appInstance, *dicts):
298
+ """
299
+ Compute CORS options for an application by combining the DEFAULT_OPTIONS,
300
+ the app's configuration-specified options and any dictionaries passed. The
301
+ last specified option wins.
302
+ """
303
+ options = DEFAULT_OPTIONS.copy()
304
+ options.update(get_app_kwarg_dict(appInstance))
305
+ if dicts:
306
+ for d in dicts:
307
+ options.update(d)
308
+
309
+ return serialize_options(options)
310
+
311
+
312
+ def get_app_kwarg_dict(appInstance=None):
313
+ """Returns the dictionary of CORS specific app configurations."""
314
+ app = appInstance or current_app
315
+
316
+ # In order to support blueprints which do not have a config attribute
317
+ app_config = getattr(app, "config", {})
318
+
319
+ return {k.lower().replace("cors_", ""): app_config.get(k) for k in CONFIG_OPTIONS if app_config.get(k) is not None}
320
+
321
+
322
+ def flexible_str(obj):
323
+ """
324
+ A more flexible str function which intelligently handles stringifying
325
+ strings, lists and other iterables. The results are lexographically sorted
326
+ to ensure generated responses are consistent when iterables such as Set
327
+ are used.
328
+ """
329
+ if obj is None:
330
+ return None
331
+ elif not isinstance(obj, str) and isinstance(obj, Iterable):
332
+ return ", ".join(str(item) for item in sorted(obj))
333
+ else:
334
+ return str(obj)
335
+
336
+
337
+ def serialize_option(options_dict, key, upper=False):
338
+ if key in options_dict:
339
+ value = flexible_str(options_dict[key])
340
+ options_dict[key] = value.upper() if upper else value
341
+
342
+
343
+ def ensure_iterable(inst):
344
+ """
345
+ Wraps scalars or string types as a list, or returns the iterable instance.
346
+ """
347
+ if isinstance(inst, str) or not isinstance(inst, Iterable):
348
+ return [inst]
349
+ else:
350
+ return inst
351
+
352
+
353
+ def sanitize_regex_param(param):
354
+ return [re_fix(x) for x in ensure_iterable(param)]
355
+
356
+
357
+ def serialize_options(opts):
358
+ """
359
+ A helper method to serialize and processes the options dictionary.
360
+ """
361
+ options = (opts or {}).copy()
362
+
363
+ for key in opts.keys():
364
+ if key not in DEFAULT_OPTIONS:
365
+ LOG.warning("Unknown option passed to Flask-CORS: %s", key)
366
+
367
+ # Ensure origins is a list of allowed origins with at least one entry.
368
+ options["origins"] = sanitize_regex_param(options.get("origins"))
369
+ options["allow_headers"] = sanitize_regex_param(options.get("allow_headers"))
370
+
371
+ # This is expressly forbidden by the spec. Raise a value error so people
372
+ # don't get burned in production.
373
+ if r".*" in options["origins"] and options["supports_credentials"] and options["send_wildcard"]:
374
+ raise ValueError(
375
+ "Cannot use supports_credentials in conjunction with"
376
+ "an origin string of '*'. See: "
377
+ "http://www.w3.org/TR/cors/#resource-requests"
378
+ )
379
+
380
+ serialize_option(options, "expose_headers")
381
+ serialize_option(options, "methods", upper=True)
382
+
383
+ if isinstance(options.get("max_age"), timedelta):
384
+ options["max_age"] = str(int(options["max_age"].total_seconds()))
385
+
386
+ return options
@@ -0,0 +1,129 @@
1
+ import logging
2
+ from functools import update_wrapper
3
+
4
+ from flask import current_app, make_response, request
5
+
6
+ from .core import FLASK_CORS_EVALUATED, get_cors_options, set_cors_headers
7
+
8
+ LOG = logging.getLogger(__name__)
9
+
10
+
11
+ def cross_origin(*args, **kwargs):
12
+ """
13
+ This function is the decorator which is used to wrap a Flask route with.
14
+ In the simplest case, simply use the default parameters to allow all
15
+ origins in what is the most permissive configuration. If this method
16
+ modifies state or performs authentication which may be brute-forced, you
17
+ should add some degree of protection, such as Cross Site Request Forgery
18
+ protection.
19
+
20
+ :param origins:
21
+ The origin, or list of origins to allow requests from.
22
+ The origin(s) may be regular expressions, case-sensitive strings,
23
+ or else an asterisk
24
+
25
+ Default : '*'
26
+ :type origins: list, string or regex
27
+
28
+ :param methods:
29
+ The method or list of methods which the allowed origins are allowed to
30
+ access for non-simple requests.
31
+
32
+ Default : [GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]
33
+ :type methods: list or string
34
+
35
+ :param expose_headers:
36
+ The header or list which are safe to expose to the API of a CORS API
37
+ specification.
38
+
39
+ Default : None
40
+ :type expose_headers: list or string
41
+
42
+ :param allow_headers:
43
+ The header or list of header field names which can be used when this
44
+ resource is accessed by allowed origins. The header(s) may be regular
45
+ expressions, case-sensitive strings, or else an asterisk.
46
+
47
+ Default : '*', allow all headers
48
+ :type allow_headers: list, string or regex
49
+
50
+ :param supports_credentials:
51
+ Allows users to make authenticated requests. If true, injects the
52
+ `Access-Control-Allow-Credentials` header in responses. This allows
53
+ cookies and credentials to be submitted across domains.
54
+
55
+ :note: This option cannot be used in conjunction with a '*' origin
56
+
57
+ Default : False
58
+ :type supports_credentials: bool
59
+
60
+ :param max_age:
61
+ The maximum time for which this CORS request maybe cached. This value
62
+ is set as the `Access-Control-Max-Age` header.
63
+
64
+ Default : None
65
+ :type max_age: timedelta, integer, string or None
66
+
67
+ :param send_wildcard: If True, and the origins parameter is `*`, a wildcard
68
+ `Access-Control-Allow-Origin` header is sent, rather than the
69
+ request's `Origin` header.
70
+
71
+ Default : False
72
+ :type send_wildcard: bool
73
+
74
+ :param vary_header:
75
+ If True, the header Vary: Origin will be returned as per the W3
76
+ implementation guidelines.
77
+
78
+ Setting this header when the `Access-Control-Allow-Origin` is
79
+ dynamically generated (e.g. when there is more than one allowed
80
+ origin, and an Origin than '*' is returned) informs CDNs and other
81
+ caches that the CORS headers are dynamic, and cannot be cached.
82
+
83
+ If False, the Vary header will never be injected or altered.
84
+
85
+ Default : True
86
+ :type vary_header: bool
87
+
88
+ :param automatic_options:
89
+ Only applies to the `cross_origin` decorator. If True, Flask-CORS will
90
+ override Flask's default OPTIONS handling to return CORS headers for
91
+ OPTIONS requests.
92
+
93
+ Default : True
94
+ :type automatic_options: bool
95
+
96
+ """
97
+ _options = kwargs
98
+
99
+ def decorator(f):
100
+ LOG.debug("Enabling %s for cross_origin using options:%s", f, _options)
101
+
102
+ # If True, intercept OPTIONS requests by modifying the view function,
103
+ # replicating Flask's default behavior, and wrapping the response with
104
+ # CORS headers.
105
+ #
106
+ # If f.provide_automatic_options is unset or True, Flask's route
107
+ # decorator (which is actually wraps the function object we return)
108
+ # intercepts OPTIONS handling, and requests will not have CORS headers
109
+ if _options.get("automatic_options", True):
110
+ f.required_methods = getattr(f, "required_methods", set())
111
+ f.required_methods.add("OPTIONS")
112
+ f.provide_automatic_options = False
113
+
114
+ def wrapped_function(*args, **kwargs):
115
+ # Handle setting of Flask-Cors parameters
116
+ options = get_cors_options(current_app, _options)
117
+
118
+ if options.get("automatic_options") and request.method == "OPTIONS":
119
+ resp = current_app.make_default_options_response()
120
+ else:
121
+ resp = make_response(f(*args, **kwargs))
122
+
123
+ set_cors_headers(resp, options)
124
+ setattr(resp, FLASK_CORS_EVALUATED, True)
125
+ return resp
126
+
127
+ return update_wrapper(wrapped_function, f)
128
+
129
+ return decorator
@@ -0,0 +1,206 @@
1
+ import logging
2
+ from urllib.parse import unquote_plus
3
+
4
+ from flask import request
5
+
6
+ from .core import ACL_ORIGIN, get_cors_options, get_regexp_pattern, parse_resources, set_cors_headers, try_match
7
+
8
+ LOG = logging.getLogger(__name__)
9
+
10
+
11
+ class CORS:
12
+ """
13
+ Initializes Cross Origin Resource sharing for the application. The
14
+ arguments are identical to `cross_origin`, with the addition of a
15
+ `resources` parameter. The resources parameter defines a series of regular
16
+ expressions for resource paths to match and optionally, the associated
17
+ options to be applied to the particular resource. These options are
18
+ identical to the arguments to `cross_origin`.
19
+
20
+ The settings for CORS are determined in the following order
21
+
22
+ 1. Resource level settings (e.g when passed as a dictionary)
23
+ 2. Keyword argument settings
24
+ 3. App level configuration settings (e.g. CORS_*)
25
+ 4. Default settings
26
+
27
+ Note: as it is possible for multiple regular expressions to match a
28
+ resource path, the regular expressions are first sorted by length,
29
+ from longest to shortest, in order to attempt to match the most
30
+ specific regular expression. This allows the definition of a
31
+ number of specific resource options, with a wildcard fallback
32
+ for all other resources.
33
+
34
+ :param resources:
35
+ The series of regular expression and (optionally) associated CORS
36
+ options to be applied to the given resource path.
37
+
38
+ If the argument is a dictionary, it's keys must be regular expressions,
39
+ and the values must be a dictionary of kwargs, identical to the kwargs
40
+ of this function.
41
+
42
+ If the argument is a list, it is expected to be a list of regular
43
+ expressions, for which the app-wide configured options are applied.
44
+
45
+ If the argument is a string, it is expected to be a regular expression
46
+ for which the app-wide configured options are applied.
47
+
48
+ Default : Match all and apply app-level configuration
49
+
50
+ :type resources: dict, iterable or string
51
+
52
+ :param origins:
53
+ The origin, or list of origins to allow requests from.
54
+ The origin(s) may be regular expressions, case-sensitive strings,
55
+ or else an asterisk.
56
+
57
+ .. note::
58
+
59
+ origins must include the schema and the port (if not port 80),
60
+ e.g.,
61
+ `CORS(app, origins=["http://localhost:8000", "https://example.com"])`.
62
+
63
+ Default : '*'
64
+ :type origins: list, string or regex
65
+
66
+ :param methods:
67
+ The method or list of methods which the allowed origins are allowed to
68
+ access for non-simple requests.
69
+
70
+ Default : [GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]
71
+ :type methods: list or string
72
+
73
+ :param expose_headers:
74
+ The header or list which are safe to expose to the API of a CORS API
75
+ specification.
76
+
77
+ Default : None
78
+ :type expose_headers: list or string
79
+
80
+ :param allow_headers:
81
+ The header or list of header field names which can be used when this
82
+ resource is accessed by allowed origins. The header(s) may be regular
83
+ expressions, case-sensitive strings, or else an asterisk.
84
+
85
+ Default : '*', allow all headers
86
+ :type allow_headers: list, string or regex
87
+
88
+ :param supports_credentials:
89
+ Allows users to make authenticated requests. If true, injects the
90
+ `Access-Control-Allow-Credentials` header in responses. This allows
91
+ cookies and credentials to be submitted across domains.
92
+
93
+ :note: This option cannot be used in conjunction with a '*' origin
94
+
95
+ Default : False
96
+ :type supports_credentials: bool
97
+
98
+ :param max_age:
99
+ The maximum time for which this CORS request maybe cached. This value
100
+ is set as the `Access-Control-Max-Age` header.
101
+
102
+ Default : None
103
+ :type max_age: timedelta, integer, string or None
104
+
105
+ :param send_wildcard: If True, and the origins parameter is `*`, a wildcard
106
+ `Access-Control-Allow-Origin` header is sent, rather than the
107
+ request's `Origin` header.
108
+
109
+ Default : False
110
+ :type send_wildcard: bool
111
+
112
+ :param vary_header:
113
+ If True, the header Vary: Origin will be returned as per the W3
114
+ implementation guidelines.
115
+
116
+ Setting this header when the `Access-Control-Allow-Origin` is
117
+ dynamically generated (e.g. when there is more than one allowed
118
+ origin, and an Origin than '*' is returned) informs CDNs and other
119
+ caches that the CORS headers are dynamic, and cannot be cached.
120
+
121
+ If False, the Vary header will never be injected or altered.
122
+
123
+ Default : True
124
+ :type vary_header: bool
125
+
126
+ :param allow_private_network:
127
+ If True, the response header `Access-Control-Allow-Private-Network`
128
+ will be set with the value 'true' whenever the request header
129
+ `Access-Control-Request-Private-Network` has a value 'true'.
130
+
131
+ If False, the response header `Access-Control-Allow-Private-Network`
132
+ will be set with the value 'false' whenever the request header
133
+ `Access-Control-Request-Private-Network` has a value of 'true'.
134
+
135
+ If the request header `Access-Control-Request-Private-Network` is
136
+ not present or has a value other than 'true', the response header
137
+ `Access-Control-Allow-Private-Network` will not be set.
138
+
139
+ Default : True
140
+ :type allow_private_network: bool
141
+ """
142
+
143
+ def __init__(self, app=None, **kwargs):
144
+ self._options = kwargs
145
+ if app is not None:
146
+ self.init_app(app, **kwargs)
147
+
148
+ def init_app(self, app, **kwargs):
149
+ # The resources and options may be specified in the App Config, the CORS constructor
150
+ # or the kwargs to the call to init_app.
151
+ options = get_cors_options(app, self._options, kwargs)
152
+
153
+ # Flatten our resources into a list of the form
154
+ # (pattern_or_regexp, dictionary_of_options)
155
+ resources = parse_resources(options.get("resources"))
156
+
157
+ # Compute the options for each resource by combining the options from
158
+ # the app's configuration, the constructor, the kwargs to init_app, and
159
+ # finally the options specified in the resources dictionary.
160
+ resources = [(pattern, get_cors_options(app, options, opts)) for (pattern, opts) in resources]
161
+
162
+ # Create a human-readable form of these resources by converting the compiled
163
+ # regular expressions into strings.
164
+ resources_human = {get_regexp_pattern(pattern): opts for (pattern, opts) in resources}
165
+ LOG.debug("Configuring CORS with resources: %s", resources_human)
166
+
167
+ cors_after_request = make_after_request_function(resources)
168
+ app.after_request(cors_after_request)
169
+
170
+ # Wrap exception handlers with cross_origin
171
+ # These error handlers will still respect the behavior of the route
172
+ if options.get("intercept_exceptions", True):
173
+
174
+ def _after_request_decorator(f):
175
+ def wrapped_function(*args, **kwargs):
176
+ return cors_after_request(app.make_response(f(*args, **kwargs)))
177
+
178
+ return wrapped_function
179
+
180
+ if hasattr(app, "handle_exception"):
181
+ app.handle_exception = _after_request_decorator(app.handle_exception)
182
+ app.handle_user_exception = _after_request_decorator(app.handle_user_exception)
183
+
184
+
185
+ def make_after_request_function(resources):
186
+ def cors_after_request(resp):
187
+ # If CORS headers are set in a view decorator, pass
188
+ if resp.headers is not None and resp.headers.get(ACL_ORIGIN):
189
+ LOG.debug("CORS have been already evaluated, skipping")
190
+ return resp
191
+ normalized_path = unquote_plus(request.path)
192
+ for res_regex, res_options in resources:
193
+ if try_match(normalized_path, res_regex):
194
+ LOG.debug(
195
+ "Request to '%r' matches CORS resource '%s'. Using options: %s",
196
+ request.path,
197
+ get_regexp_pattern(res_regex),
198
+ res_options,
199
+ )
200
+ set_cors_headers(resp, res_options)
201
+ break
202
+ else:
203
+ LOG.debug("No CORS rule matches")
204
+ return resp
205
+
206
+ return cors_after_request
@@ -0,0 +1 @@
1
+ __version__ = "0.0.0dev4"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: flask-cors
3
- Version: 0.0.0.dev3
3
+ Version: 0.0.0.dev4
4
4
  Summary: A Flask extension simplifying CORS support
5
5
  Author-email: Cory Dolphin <corydolphin@gmail.com>
6
6
  Project-URL: Homepage, https://corydolphin.github.io/flask-cors/
@@ -1,5 +1,10 @@
1
1
  README.rst
2
2
  pyproject.toml
3
+ flask_cors/__init__.py
4
+ flask_cors/core.py
5
+ flask_cors/decorator.py
6
+ flask_cors/extension.py
7
+ flask_cors/version.py
3
8
  flask_cors.egg-info/PKG-INFO
4
9
  flask_cors.egg-info/SOURCES.txt
5
10
  flask_cors.egg-info/dependency_links.txt
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "flask-cors"
3
- version = "0.0.0dev3"
3
+ version = "0.0.0dev4"
4
4
  description = "A Flask extension simplifying CORS support"
5
5
  authors = [{ name = "Cory Dolphin", email = "corydolphin@gmail.com" }]
6
6
  readme = "README.md"
@@ -46,7 +46,7 @@ requires = ["setuptools"]
46
46
  build-backend = "setuptools.build_meta"
47
47
 
48
48
  [tool.setuptools]
49
- py-modules = ["flask_cors"]
49
+ packages = ["flask_cors"]
50
50
  # This is a workaround for https://github.com/astral-sh/uv/issues/9513
51
51
  license-files = []
52
52