summaryrefslogtreecommitdiff
path: root/aiohttp_cors
diff options
context:
space:
mode:
Diffstat (limited to 'aiohttp_cors')
-rw-r--r--aiohttp_cors/__about__.py6
-rw-r--r--aiohttp_cors/__init__.py3
-rw-r--r--aiohttp_cors/_log.py22
-rw-r--r--aiohttp_cors/abc.py9
-rw-r--r--aiohttp_cors/cors_config.py200
-rw-r--r--aiohttp_cors/mixin.py47
-rw-r--r--aiohttp_cors/preflight_handler.py130
-rw-r--r--aiohttp_cors/urldispatcher_router_adapter.py209
8 files changed, 263 insertions, 363 deletions
diff --git a/aiohttp_cors/__about__.py b/aiohttp_cors/__about__.py
index eb70c30..51c4841 100644
--- a/aiohttp_cors/__about__.py
+++ b/aiohttp_cors/__about__.py
@@ -19,10 +19,10 @@ This module must be stand-alone executable.
"""
__title__ = "aiohttp-cors"
-__version__ = "0.5.3"
-__author__ = "Vladimir Rutsky"
+__version__ = "0.7.0"
+__author__ = "Vladimir Rutsky and aio-libs team"
__email__ = "vladimir@rutsky.org"
__summary__ = "CORS support for aiohttp"
__uri__ = "https://github.com/aio-libs/aiohttp-cors"
__license__ = "Apache License, Version 2.0"
-__copyright__ = "2015, 2016, 2017 {}".format(__author__)
+__copyright__ = "2015-2018 {}".format(__author__)
diff --git a/aiohttp_cors/__init__.py b/aiohttp_cors/__init__.py
index 49474c8..cbcc5ef 100644
--- a/aiohttp_cors/__init__.py
+++ b/aiohttp_cors/__init__.py
@@ -25,11 +25,12 @@ from .__about__ import (
)
from .resource_options import ResourceOptions
from .cors_config import CorsConfig
+from .mixin import CorsViewMixin, custom_cors
__all__ = (
"__title__", "__version__", "__author__", "__email__", "__summary__",
"__uri__", "__license__", "__copyright__",
- "setup", "CorsConfig", "ResourceOptions",
+ "setup", "CorsConfig", "ResourceOptions", "CorsViewMixin", "custom_cors"
)
diff --git a/aiohttp_cors/_log.py b/aiohttp_cors/_log.py
deleted file mode 100644
index 5b216cb..0000000
--- a/aiohttp_cors/_log.py
+++ /dev/null
@@ -1,22 +0,0 @@
-# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""aiohttp_cors logger"""
-
-import logging
-
-__all__ = ("logger",)
-
-# pylint: disable=invalid-name
-logger = logging.getLogger("aiohttp_cors")
diff --git a/aiohttp_cors/abc.py b/aiohttp_cors/abc.py
index cdddd36..5204a83 100644
--- a/aiohttp_cors/abc.py
+++ b/aiohttp_cors/abc.py
@@ -15,7 +15,6 @@
"""Abstract base classes.
"""
-import asyncio
from abc import ABCMeta, abstractmethod
from aiohttp import web
@@ -51,7 +50,10 @@ class AbstractRouterAdapter(metaclass=ABCMeta):
"""
@abstractmethod
- def add_preflight_handler(self, routing_entity, handler):
+ def add_preflight_handler(self,
+ routing_entity,
+ handler,
+ webview: bool=False):
"""Add OPTIONS handler for all routes defined by `routing_entity`.
Does nothing if CORS handler already handles routing entity.
@@ -79,9 +81,8 @@ class AbstractRouterAdapter(metaclass=ABCMeta):
entity.
"""
- @asyncio.coroutine
@abstractmethod
- def get_preflight_request_config(
+ async def get_preflight_request_config(
self,
preflight_request: web.Request,
origin: str,
diff --git a/aiohttp_cors/cors_config.py b/aiohttp_cors/cors_config.py
index d8017c9..2e1aeea 100644
--- a/aiohttp_cors/cors_config.py
+++ b/aiohttp_cors/cors_config.py
@@ -15,22 +15,21 @@
"""CORS configuration container class definition.
"""
-import asyncio
import collections
+import warnings
from typing import Mapping, Union, Any
from aiohttp import hdrs, web
-from .urldispatcher_router_adapter import OldRoutesUrlDispatcherRouterAdapter
from .urldispatcher_router_adapter import ResourcesUrlDispatcherRouterAdapter
from .abc import AbstractRouterAdapter
from .resource_options import ResourceOptions
+from .preflight_handler import _PreflightHandler
__all__ = (
"CorsConfig",
)
-
# Positive response to Access-Control-Allow-Credentials
_TRUE = "true"
# CORS simple response headers:
@@ -103,7 +102,7 @@ def _parse_config_options(
_ConfigType = Mapping[str, Union[ResourceOptions, Mapping[str, Any]]]
-class _CorsConfigImpl:
+class _CorsConfigImpl(_PreflightHandler):
def __init__(self,
app: web.Application,
@@ -139,10 +138,9 @@ class _CorsConfigImpl:
return routing_entity
- @asyncio.coroutine
- def _on_response_prepare(self,
- request: web.Request,
- response: web.StreamResponse):
+ async def _on_response_prepare(self,
+ request: web.Request,
+ response: web.StreamResponse):
"""Non-preflight CORS request response processor.
If request is done on CORS-enabled route, process request parameters
@@ -196,127 +194,11 @@ class _CorsConfigImpl:
# Set allowed credentials.
response.headers[hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS] = _TRUE
- @staticmethod
- def _parse_request_method(request: web.Request):
- """Parse Access-Control-Request-Method header of the preflight request
- """
- method = request.headers.get(hdrs.ACCESS_CONTROL_REQUEST_METHOD)
- if method is None:
- raise web.HTTPForbidden(
- text="CORS preflight request failed: "
- "'Access-Control-Request-Method' header is not specified")
-
- # FIXME: validate method string (ABNF: method = token), if parsing
- # fails, raise HTTPForbidden.
-
- return method
-
- @staticmethod
- def _parse_request_headers(request: web.Request):
- """Parse Access-Control-Request-Headers header or the preflight request
-
- Returns set of headers in upper case.
- """
- headers = request.headers.get(hdrs.ACCESS_CONTROL_REQUEST_HEADERS)
- if headers is None:
- return frozenset()
-
- # FIXME: validate each header string, if parsing fails, raise
- # HTTPForbidden.
- # FIXME: check, that headers split and stripped correctly (according
- # to ABNF).
- headers = (h.strip(" \t").upper() for h in headers.split(","))
- # pylint: disable=bad-builtin
- return frozenset(filter(None, headers))
-
- @asyncio.coroutine
- def _preflight_handler(self, request: web.Request):
- """CORS preflight request handler"""
-
- # Handle according to part 6.2 of the CORS specification.
-
- origin = request.headers.get(hdrs.ORIGIN)
- if origin is None:
- # Terminate CORS according to CORS 6.2.1.
- raise web.HTTPForbidden(
- text="CORS preflight request failed: "
- "origin header is not specified in the request")
-
- # CORS 6.2.3. Doing it out of order is not an error.
- request_method = self._parse_request_method(request)
-
- # CORS 6.2.5. Doing it out of order is not an error.
-
- try:
- config = \
- yield from self._router_adapter.get_preflight_request_config(
- request, origin, request_method)
- except KeyError:
- raise web.HTTPForbidden(
- text="CORS preflight request failed: "
- "request method {!r} is not allowed "
- "for {!r} origin".format(request_method, origin))
-
- if not config:
- # No allowed origins for the route.
- # Terminate CORS according to CORS 6.2.1.
- raise web.HTTPForbidden(
- text="CORS preflight request failed: "
- "no origins are allowed")
-
- options = config.get(origin, config.get("*"))
- if options is None:
- # No configuration for the origin - deny.
- # Terminate CORS according to CORS 6.2.2.
- raise web.HTTPForbidden(
- text="CORS preflight request failed: "
- "origin '{}' is not allowed".format(origin))
-
- # CORS 6.2.4
- request_headers = self._parse_request_headers(request)
-
- # CORS 6.2.6
- if options.allow_headers == "*":
- pass
- else:
- disallowed_headers = request_headers - options.allow_headers
- if disallowed_headers:
- raise web.HTTPForbidden(
- text="CORS preflight request failed: "
- "headers are not allowed: {}".format(
- ", ".join(disallowed_headers)))
-
- # Ok, CORS actual request with specified in the preflight request
- # parameters is allowed.
- # Set appropriate headers and return 200 response.
-
- response = web.Response()
-
- # CORS 6.2.7
- response.headers[hdrs.ACCESS_CONTROL_ALLOW_ORIGIN] = origin
- if options.allow_credentials:
- # Set allowed credentials.
- response.headers[hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS] = _TRUE
-
- # CORS 6.2.8
- if options.max_age is not None:
- response.headers[hdrs.ACCESS_CONTROL_MAX_AGE] = \
- str(options.max_age)
-
- # CORS 6.2.9
- # TODO: more optimal for client preflight request cache would be to
- # respond with ALL allowed methods.
- response.headers[hdrs.ACCESS_CONTROL_ALLOW_METHODS] = request_method
-
- # CORS 6.2.10
- if request_headers:
- # Note: case of the headers in the request is changed, but this
- # shouldn't be a problem, since the headers should be compared in
- # the case-insensitive way.
- response.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS] = \
- ",".join(request_headers)
-
- return response
+ async def _get_config(self, request, origin, request_method):
+ config = \
+ await self._router_adapter.get_preflight_request_config(
+ request, origin, request_method)
+ return config
class CorsConfig:
@@ -341,7 +223,7 @@ class CorsConfig:
Router adapter. Required if application uses non-default router.
"""
- defaults = _parse_config_options(defaults)
+ self.defaults = _parse_config_options(defaults)
self._cors_impl = None
@@ -350,27 +232,16 @@ class CorsConfig:
self._old_routes_cors_impl = None
- if router_adapter is not None:
- self._cors_impl = _CorsConfigImpl(app, router_adapter)
-
- elif isinstance(app.router, web.UrlDispatcher):
- self._resources_router_adapter = \
- ResourcesUrlDispatcherRouterAdapter(app.router, defaults)
- self._resources_cors_impl = _CorsConfigImpl(
- app,
- self._resources_router_adapter)
- self._old_routes_cors_impl = _CorsConfigImpl(
- app,
- OldRoutesUrlDispatcherRouterAdapter(app.router, defaults))
- else:
- raise RuntimeError(
- "Router adapter is not specified. "
- "Routers other than aiohttp.web.UrlDispatcher requires"
- "custom router adapter.")
+ if router_adapter is None:
+ router_adapter = \
+ ResourcesUrlDispatcherRouterAdapter(app.router, self.defaults)
+
+ self._cors_impl = _CorsConfigImpl(app, router_adapter)
def add(self,
routing_entity,
- config: _ConfigType = None):
+ config: _ConfigType = None,
+ webview: bool=False):
"""Enable CORS for specific route or resource.
If route is passed CORS is enabled for route's resource.
@@ -382,30 +253,11 @@ class CorsConfig:
:return: `routing_entity`.
"""
- if self._cors_impl is not None:
- # Custom router adapter.
- return self._cors_impl.add(routing_entity, config)
+ if webview:
+ warnings.warn('webview argument is deprecated, '
+ 'views are handled authomatically without '
+ 'extra settings',
+ DeprecationWarning,
+ stacklevel=2)
- else:
- # UrlDispatcher.
-
- if isinstance(routing_entity, (web.Resource, web.StaticResource)):
- # New Resource - use new router adapter.
- return self._resources_cors_impl.add(routing_entity, config)
-
- elif isinstance(routing_entity, web.AbstractRoute):
- if self._resources_router_adapter.is_cors_for_resource(
- routing_entity.resource):
- # Route which resource has CORS configuration in
- # new-style router adapter.
- return self._resources_cors_impl.add(
- routing_entity, config)
- else:
- # Route which resource has no CORS configuration, i.e.
- # old-style route.
- return self._old_routes_cors_impl.add(
- routing_entity, config)
-
- else:
- raise ValueError(
- "Unknown resource/route type: {!r}".format(routing_entity))
+ return self._cors_impl.add(routing_entity, config)
diff --git a/aiohttp_cors/mixin.py b/aiohttp_cors/mixin.py
new file mode 100644
index 0000000..f5b4506
--- /dev/null
+++ b/aiohttp_cors/mixin.py
@@ -0,0 +1,47 @@
+import collections
+
+from .preflight_handler import _PreflightHandler
+
+
+def custom_cors(config):
+ def wrapper(function):
+ name = "{}_cors_config".format(function.__name__)
+ setattr(function, name, config)
+ return function
+ return wrapper
+
+
+class CorsViewMixin(_PreflightHandler):
+ cors_config = None
+
+ @classmethod
+ def get_request_config(cls, request, request_method):
+ try:
+ from . import APP_CONFIG_KEY
+ cors = request.app[APP_CONFIG_KEY]
+ except KeyError:
+ raise ValueError("aiohttp-cors is not configured.")
+
+ method = getattr(cls, request_method.lower(), None)
+
+ if not method:
+ raise KeyError()
+
+ config_property_key = "{}_cors_config".format(request_method.lower())
+
+ custom_config = getattr(method, config_property_key, None)
+ if not custom_config:
+ custom_config = {}
+
+ class_config = cls.cors_config
+ if not class_config:
+ class_config = {}
+
+ return collections.ChainMap(custom_config, class_config, cors.defaults)
+
+ async def _get_config(self, request, origin, request_method):
+ return self.get_request_config(request, request_method)
+
+ async def options(self):
+ response = await self._preflight_handler(self.request)
+ return response
diff --git a/aiohttp_cors/preflight_handler.py b/aiohttp_cors/preflight_handler.py
new file mode 100644
index 0000000..35e15e1
--- /dev/null
+++ b/aiohttp_cors/preflight_handler.py
@@ -0,0 +1,130 @@
+from aiohttp import hdrs, web
+
+# Positive response to Access-Control-Allow-Credentials
+_TRUE = "true"
+
+
+class _PreflightHandler:
+
+ @staticmethod
+ def _parse_request_method(request: web.Request):
+ """Parse Access-Control-Request-Method header of the preflight request
+ """
+ method = request.headers.get(hdrs.ACCESS_CONTROL_REQUEST_METHOD)
+ if method is None:
+ raise web.HTTPForbidden(
+ text="CORS preflight request failed: "
+ "'Access-Control-Request-Method' header is not specified")
+
+ # FIXME: validate method string (ABNF: method = token), if parsing
+ # fails, raise HTTPForbidden.
+
+ return method
+
+ @staticmethod
+ def _parse_request_headers(request: web.Request):
+ """Parse Access-Control-Request-Headers header or the preflight request
+
+ Returns set of headers in upper case.
+ """
+ headers = request.headers.get(hdrs.ACCESS_CONTROL_REQUEST_HEADERS)
+ if headers is None:
+ return frozenset()
+
+ # FIXME: validate each header string, if parsing fails, raise
+ # HTTPForbidden.
+ # FIXME: check, that headers split and stripped correctly (according
+ # to ABNF).
+ headers = (h.strip(" \t").upper() for h in headers.split(","))
+ # pylint: disable=bad-builtin
+ return frozenset(filter(None, headers))
+
+ async def _get_config(self, request, origin, request_method):
+ raise NotImplementedError()
+
+ async def _preflight_handler(self, request: web.Request):
+ """CORS preflight request handler"""
+
+ # Handle according to part 6.2 of the CORS specification.
+
+ origin = request.headers.get(hdrs.ORIGIN)
+ if origin is None:
+ # Terminate CORS according to CORS 6.2.1.
+ raise web.HTTPForbidden(
+ text="CORS preflight request failed: "
+ "origin header is not specified in the request")
+
+ # CORS 6.2.3. Doing it out of order is not an error.
+ request_method = self._parse_request_method(request)
+
+ # CORS 6.2.5. Doing it out of order is not an error.
+
+ try:
+ config = \
+ await self._get_config(request, origin, request_method)
+ except KeyError:
+ raise web.HTTPForbidden(
+ text="CORS preflight request failed: "
+ "request method {!r} is not allowed "
+ "for {!r} origin".format(request_method, origin))
+
+ if not config:
+ # No allowed origins for the route.
+ # Terminate CORS according to CORS 6.2.1.
+ raise web.HTTPForbidden(
+ text="CORS preflight request failed: "
+ "no origins are allowed")
+
+ options = config.get(origin, config.get("*"))
+ if options is None:
+ # No configuration for the origin - deny.
+ # Terminate CORS according to CORS 6.2.2.
+ raise web.HTTPForbidden(
+ text="CORS preflight request failed: "
+ "origin '{}' is not allowed".format(origin))
+
+ # CORS 6.2.4
+ request_headers = self._parse_request_headers(request)
+
+ # CORS 6.2.6
+ if options.allow_headers == "*":
+ pass
+ else:
+ disallowed_headers = request_headers - options.allow_headers
+ if disallowed_headers:
+ raise web.HTTPForbidden(
+ text="CORS preflight request failed: "
+ "headers are not allowed: {}".format(
+ ", ".join(disallowed_headers)))
+
+ # Ok, CORS actual request with specified in the preflight request
+ # parameters is allowed.
+ # Set appropriate headers and return 200 response.
+
+ response = web.Response()
+
+ # CORS 6.2.7
+ response.headers[hdrs.ACCESS_CONTROL_ALLOW_ORIGIN] = origin
+ if options.allow_credentials:
+ # Set allowed credentials.
+ response.headers[hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS] = _TRUE
+
+ # CORS 6.2.8
+ if options.max_age is not None:
+ response.headers[hdrs.ACCESS_CONTROL_MAX_AGE] = \
+ str(options.max_age)
+
+ # CORS 6.2.9
+ # TODO: more optimal for client preflight request cache would be to
+ # respond with ALL allowed methods.
+ response.headers[hdrs.ACCESS_CONTROL_ALLOW_METHODS] = request_method
+
+ # CORS 6.2.10
+ if request_headers:
+ # Note: case of the headers in the request is changed, but this
+ # shouldn't be a problem, since the headers should be compared in
+ # the case-insensitive way.
+ response.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS] = \
+ ",".join(request_headers)
+
+ return response
diff --git a/aiohttp_cors/urldispatcher_router_adapter.py b/aiohttp_cors/urldispatcher_router_adapter.py
index 92bfbb3..1a65e99 100644
--- a/aiohttp_cors/urldispatcher_router_adapter.py
+++ b/aiohttp_cors/urldispatcher_router_adapter.py
@@ -14,9 +14,7 @@
"""AbstractRouterAdapter for aiohttp.web.UrlDispatcher.
"""
-import asyncio
import collections
-import re
from typing import Union
@@ -24,6 +22,7 @@ from aiohttp import web
from aiohttp import hdrs
from .abc import AbstractRouterAdapter
+from .mixin import CorsViewMixin
# There several usage patterns of routes which should be handled
@@ -89,6 +88,22 @@ class _ResourceConfig:
self.method_config = {}
+def _is_web_view(entity, strict=True):
+ webview = False
+ if isinstance(entity, web.AbstractRoute):
+ handler = entity.handler
+ if isinstance(handler, type) and issubclass(handler, web.View):
+ webview = True
+ if not issubclass(handler, CorsViewMixin):
+ if strict:
+ raise ValueError("web view should be derived from "
+ "aiohttp_cors.WebViewMixig for working "
+ "with the library")
+ else:
+ return False
+ return webview
+
+
class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
"""Adapter for `UrlDispatcher` for Resources-based routing only.
@@ -138,6 +153,22 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
if resource in self._resources_with_preflight_handlers:
# Preflight handler already added for this resource.
return
+ for route_obj in resource:
+ if route_obj.method == hdrs.METH_OPTIONS:
+ if route_obj.handler is handler:
+ return # already added
+ else:
+ raise ValueError(
+ "{!r} already has OPTIONS handler {!r}"
+ .format(resource, route_obj.handler))
+ elif route_obj.method == hdrs.METH_ANY:
+ if _is_web_view(route_obj):
+ self._preflight_routes.add(route_obj)
+ self._resources_with_preflight_handlers.add(resource)
+ return
+ else:
+ raise ValueError("{!r} already has a '*' handler "
+ "for all methods".format(resource))
preflight_route = resource.add_route(hdrs.METH_OPTIONS, handler)
self._preflight_routes.add(preflight_route)
@@ -160,13 +191,8 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
elif isinstance(routing_entity, web.ResourceRoute):
route = routing_entity
- # Preflight handler for Route's Resource already must be
- # configured.
if not self.is_cors_for_resource(route.resource):
- raise ValueError(
- "Can't setup CORS for {!r} request, "
- "CORS must be enabled for route's resource first.".format(
- route))
+ self.add_preflight_handler(route.resource, handler)
else:
raise ValueError(
@@ -187,8 +213,10 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
def is_preflight_request(self, request: web.Request) -> bool:
"""Is `request` is a CORS preflight request."""
-
- return self._request_route(request) in self._preflight_routes
+ route = self._request_route(request)
+ if _is_web_view(route, strict=False):
+ return request.method == 'OPTIONS'
+ return route in self._preflight_routes
def is_cors_enabled_on_request(self, request: web.Request) -> bool:
"""Is `request` is a request for CORS-enabled resource."""
@@ -219,6 +247,9 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
# Add resource's route configuration or fail if it's already added.
if route.resource not in self._resource_config:
+ self.set_config_for_routing_entity(route.resource, config)
+
+ if route.resource not in self._resource_config:
raise ValueError(
"Can't setup CORS for {!r} request, "
"CORS must be enabled for route's resource first.".format(
@@ -239,8 +270,7 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
"Resource or ResourceRoute expected, got {!r}".format(
routing_entity))
- @asyncio.coroutine
- def get_preflight_request_config(
+ async def get_preflight_request_config(
self,
preflight_request: web.Request,
origin: str,
@@ -279,155 +309,16 @@ class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
resource_config = self._resource_config[resource]
# Take Route config (if any) with defaults from Resource CORS
# configuration and global defaults.
+ route = request.match_info.route
+ if _is_web_view(route, strict=False):
+ method_config = request.match_info.handler.get_request_config(
+ request, request.method)
+ else:
+ method_config = resource_config.method_config.get(request.method,
+ {})
defaulted_config = collections.ChainMap(
- resource_config.method_config.get(request.method, {}),
+ method_config,
resource_config.default_config,
self._default_config)
return defaulted_config
-
-
-class OldRoutesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
- """Adapter for `UrlDispatcher` for old-style routing only.
-
- In all use cases when Resource is not explicitly used,
- Resource will automatically allocated for old route.
- In this case all routes will have it's own resource, and to find
- related routes (routes that shares same path) we need to iterate over
- all routes with enabled CORS and check is they handle specific path.
-
- This whole class should go away when user will migrate to proper
- Resource/Route usage scheme.
- """
-
- def __init__(self,
- router: web.UrlDispatcher,
- defaults):
- """
- :param defaults:
- Default CORS configuration.
- """
- self._router = router
-
- # Default configuration for all routes.
- self._default_config = defaults
-
- # Mapping from route to config.
- self._route_config = collections.OrderedDict()
-
- self._preflight_routes = set()
-
- def add_preflight_handler(
- self,
- route: web.AbstractRoute,
- handler):
- """Add OPTIONS handler for same paths that `route` handles."""
-
- assert isinstance(route, web.AbstractRoute)
-
- if isinstance(route, web.ResourceRoute):
- # New-style route (which Resource is not used explicitly,
- # otherwise it would be handled by other adapter).
- preflight_route = route.resource.add_route(
- hdrs.METH_OPTIONS, handler)
-
- elif isinstance(route, web.Route):
- # Old-style route.
-
- if isinstance(route, web.StaticRoute):
- # TODO: Use custom matches that uses `str.startswith()`
- # if regexp performance is not enough.
- pattern = re.compile("^" + re.escape(route._prefix))
- preflight_route = web.DynamicRoute(
- hdrs.METH_OPTIONS, handler, None, pattern, "")
- self._router.register_route(preflight_route)
-
- elif isinstance(route, web.PlainRoute):
- # May occur only if user manually creates PlainRoute.
- preflight_route = self._router.add_route(
- hdrs.METH_OPTIONS, route._path, handler)
-
- elif isinstance(route, web.DynamicRoute):
- # May occur only if user manually creates DynamicRoute.
- preflight_route = web.DynamicRoute(
- hdrs.METH_OPTIONS, handler, None,
- route._pattern, route._formatter)
- self._router.register_route(preflight_route)
-
- else:
- raise RuntimeError(
- "Unhandled deprecated route type {!r}".format(route))
-
- else:
- raise RuntimeError("Unhandled route type {!r}".format(route))
-
- self._preflight_routes.add(preflight_route)
-
- def _request_route(self, request: web.Request) -> web.ResourceRoute:
- match_info = request.match_info
- assert isinstance(match_info, web.UrlMappingMatchInfo)
- return match_info.route
-
- def is_preflight_request(self, request: web.Request) -> bool:
- """Is `request` is a CORS preflight request."""
-
- return self._request_route(request) in self._preflight_routes
-
- def is_cors_enabled_on_request(self, request: web.Request) -> bool:
- """Is `request` is a request for CORS-enabled resource."""
-
- return self._request_route(request) in self._route_config
-
- def set_config_for_routing_entity(
- self,
- route: web.AbstractRoute,
- config):
- """Record CORS configuration for route."""
-
- assert isinstance(route, web.AbstractRoute)
-
- if any(options.allow_methods is not None
- for options in config.values()):
- raise ValueError(
- "'allow_methods' parameter is not supported on old-style "
- "routes. You specified {!r} for {!r}. "
- "Use Resources to configure CORS.".format(
- config, route))
-
- if route in self._route_config:
- raise ValueError(
- "CORS is already configured for {!r} route.".format(
- route))
-
- self._route_config[route] = config
-
- @asyncio.coroutine
- def get_preflight_request_config(
- self,
- preflight_request: web.Request,
- origin: str,
- requested_method: str):
- assert self.is_preflight_request(preflight_request)
-
- request = preflight_request.clone(method=requested_method)
- for route, config in self._route_config.items():
- match_info, allowed_methods = yield from route.resource.resolve(
- request)
- if match_info is not None:
- return collections.ChainMap(config, self._default_config)
- else:
- raise KeyError
-
- def get_non_preflight_request_config(self, request: web.Request):
- """Get stored CORS configuration for routing entity that handles
- specified request."""
-
- assert self.is_cors_enabled_on_request(request)
-
- route = self._request_route(request)
- route_config = self._route_config[route]
-
- defaulted_config = collections.ChainMap(
- route_config, self._default_config)
-
- return defaulted_config