Full Code of aio-libs/aiohttp-cors for AI

master d36e400cd23e cached
44 files
163.0 KB
39.0k tokens
138 symbols
1 requests
Download .txt
Repository: aio-libs/aiohttp-cors
Branch: master
Commit: d36e400cd23e
Files: 44
Total size: 163.0 KB

Directory structure:
gitextract_8o3pvrw5/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── auto-merge.yml
│       ├── ci-cd.yml
│       └── codeql.yml
├── .gitignore
├── .jscs.json
├── .jshintrc
├── .pep8rc
├── .pre-commit-config.yaml
├── .pylintrc
├── .pyup.yml
├── CHANGES.rst
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── aiohttp_cors/
│   ├── __about__.py
│   ├── __init__.py
│   ├── abc.py
│   ├── cors_config.py
│   ├── mixin.py
│   ├── preflight_handler.py
│   ├── py.typed
│   ├── resource_options.py
│   └── urldispatcher_router_adapter.py
├── install_python_and_pip.ps1
├── pytest.ini
├── requirements-dev.txt
├── setup.cfg
├── setup.py
└── tests/
    ├── __init__.py
    ├── doc/
    │   ├── __init__.py
    │   └── test_basic_usage.py
    ├── integration/
    │   ├── __init__.py
    │   ├── test_main.py
    │   ├── test_page.html
    │   └── test_real_browser.py
    └── unit/
        ├── __init__.py
        ├── test___about__.py
        ├── test_cors_config.py
        ├── test_mixin.py
        ├── test_preflight_handler.py
        ├── test_resource_options.py
        └── test_urldispatcher_router_adapter.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: pip
  directory: "/"
  schedule:
    interval: daily
  open-pull-requests-limit: 10
- package-ecosystem: github-actions
  directory: /
  schedule:
    interval: daily
  open-pull-requests-limit: 10


================================================
FILE: .github/workflows/auto-merge.yml
================================================
name: Dependabot auto-merge
on: pull_request_target

permissions:
  pull-requests: write
  contents: write

jobs:
  dependabot:
    runs-on: ubuntu-latest
    if: ${{ github.actor == 'dependabot[bot]' }}
    steps:
      - name: Dependabot metadata
        id: metadata
        uses: dependabot/fetch-metadata@v3.1.0
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
      - name: Enable auto-merge for Dependabot PRs
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}


================================================
FILE: .github/workflows/ci-cd.yml
================================================
name: CI

on:
  push:
    branches:
      - master
      - '[0-9].[0-9]+'  # matches to backport branches, e.g. 3.6
    tags: [ 'v*' ]
  pull_request:
    branches:
      - master
      - '[0-9].[0-9]+'


jobs:
  lint:
    name: Linter
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
    - name: Checkout
      uses: actions/checkout@v6
    - name: Setup Python
      uses: actions/setup-python@v6
      with:
        python-version: 3.11
        cache: 'pip'
        cache-dependency-path: '**/requirements*.txt'
    - name: Pre-Commit hooks
      uses: pre-commit/action@v3.0.1
    - name: Install dependencies
      uses: py-actions/py-dependency-install@v4
      with:
        path: requirements-dev.txt
    - name: Install itself
      run: |
        pip install .
    - name: Run linter
      run: |
        make lint
    - name: Prepare twine checker
      run: |
        pip install -U build twine wheel
        python -m build
    - name: Run twine checker
      run: |
        twine check dist/*

  test:
    name: Test
    strategy:
      matrix:
        pyver: ['3.9', '3.10', '3.11', '3.12', '3.13']
    runs-on: ubuntu-latest
    timeout-minutes: 15
    steps:
    - name: Checkout
      uses: actions/checkout@v6
    - name: Setup Python ${{ matrix.pyver }}
      uses: actions/setup-python@v6
      with:
        python-version: ${{ matrix.pyver }}
        cache: 'pip'
        cache-dependency-path: '**/requirements*.txt'
    - name: Install dependencies
      uses: py-actions/py-dependency-install@v4
      with:
        path: requirements-dev.txt
    - name: Run unittests
      run: pytest tests
      env:
        COLOR: 'yes'
    # - run: python -m coverage xml
    # - name: Upload coverage
    #   uses: codecov/codecov-action@v5
    #   with:
    #     fail_ci_if_error: true
    #     token: ${{ secrets.CODECOV_TOKEN }}

  check:  # This job does nothing and is only used for the branch protection
    if: always()

    needs: [lint, test]

    runs-on: ubuntu-latest

    steps:
    - name: Decide whether the needed jobs succeeded or failed
      uses: re-actors/alls-green@release/v1
      with:
        jobs: ${{ toJSON(needs) }}

  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    needs: [check]
    if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
    permissions:
      contents: write  # IMPORTANT: mandatory for making GitHub Releases
      id-token: write  # IMPORTANT: mandatory for trusted publishing & sigstore
    environment:
      name: pypi
      url: >-
        https://pypi.org/project/${{ env.PROJECT_NAME }}/${{ github.ref_name }}
    steps:
    - name: Checkout
      uses: actions/checkout@v6
    - name: Setup Python
      uses: actions/setup-python@v6
      with:
        python-version: 3.9
    - name: Install dependencies
      run:
        python -m pip install -U pip wheel setuptools build twine
    - name: Build dists
      run: |
        python -m build
    - name: >-
        Publish 🐍📦 to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1
    - name: Sign the dists with Sigstore
      uses: sigstore/gh-action-sigstore-python@v3.3.0
      with:
        inputs: >-
          ./dist/${{ env.PROJECT_NAME }}*.tar.gz
          ./dist/${{ env.PROJECT_NAME }}*.whl
    - name: Upload artifact signatures to GitHub Release
      # Confusingly, this action also supports updating releases, not
      # just creating them. This is what we want here, since we've manually
      # created the release above.
      uses: softprops/action-gh-release@v3
      with:
        # dist/ contains the built packages, which smoketest-artifacts/
        # contains the signatures and certificates.
        files: dist/**
    # - name: Make Release
    #   uses: aio-libs/create-release@v1.6.6
    #   with:
    #     changes_file: CHANGES.rst
    #     name: aiohttp-jinja2
    #     version_file: aiohttp_jinja2/__init__.py
    #     github_token: ${{ secrets.GITHUB_TOKEN }}
    #     pypi_token: ${{ secrets.PYPI_API_TOKEN }}
    #     dist_dir: dist
    #     fix_issue_regex: "`#(\\d+) <https://github.com/aio-libs/aiohttp-jinja2/issues/\\1>`"
    #     fix_issue_repl: "(#\\1)"


================================================
FILE: .github/workflows/codeql.yml
================================================
name: "CodeQL"

on:
  push:
    branches: [ "master" ]
  pull_request:
    branches: [ "master" ]
  schedule:
    - cron: "43 0 * * 4"

jobs:
  analyze:
    name: Analyze
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write

    strategy:
      fail-fast: false
      matrix:
        language: [ javascript, python ]

    steps:
      - name: Checkout
        uses: actions/checkout@v6

      - name: Initialize CodeQL
        uses: github/codeql-action/init@v4
        with:
          languages: ${{ matrix.language }}
          queries: +security-and-quality

      - name: Autobuild
        uses: github/codeql-action/autobuild@v4
        if: ${{ matrix.language == 'javascript' || matrix.language == 'python' }}

      - name: Perform CodeQL Analysis
        uses: github/codeql-action/analyze@v4
        with:
          category: "/language:${{ matrix.language }}"


================================================
FILE: .gitignore
================================================
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
# Byte-compiled / optimized / DLL files
# C extensions
# Distribution / packaging
# Installer logs
# PyBuilder
# PyInstaller
# Sphinx documentation
# Unit test / coverage reports
*,cover
*.egg-info/
*.manifest
*.py[cod]
*.so
*.spec
.Python
.coverage
.coverage.*
.eggs/
.installed.cfg
.pytest_cache
/*.egg
/.cache
/.tox/
/build/
/dist/
/env/
__pycache__/
coverage.xml
develop-eggs/
docs/_build/
downloads/
eggs/
geckodriver.log
htmlcov/
lib/
lib64/
nosetests.xml
parts/
pip-delete-this-directory.txt
pip-log.txt
sdist/
target/
var/
.python-version


================================================
FILE: .jscs.json
================================================
{
    "requireCurlyBraces": [
        "if",
        "else",
        "for",
        "while",
        "do",
        "try",
        "catch"
    ],
    "requireOperatorBeforeLineBreak": true,
    "requireCamelCaseOrUpperCaseIdentifiers": true,
    "maximumLineLength": {
      "value": 80,
      "allowComments": true,
      "allowRegex": true
    },
    "validateIndentation": 2,
    "validateQuoteMarks": "'",

    "disallowMultipleLineStrings": true,
    "disallowMixedSpacesAndTabs": true,
    "disallowTrailingWhitespace": true,
    "disallowSpaceAfterPrefixUnaryOperators": true,
    "disallowMultipleVarDecl": true,

    "requireSpaceAfterKeywords": [
      "if",
      "else",
      "for",
      "while",
      "do",
      "switch",
      "return",
      "try",
      "catch"
    ],
    "requireSpaceBeforeBinaryOperators": [
        "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
        "&=", "|=", "^=", "+=",

        "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
        "|", "^", "&&", "||", "===", "==", ">=",
        "<=", "<", ">", "!=", "!=="
    ],
    "requireSpaceAfterBinaryOperators": true,
    "requireSpacesInConditionalExpression": true,
    "requireSpaceBeforeBlockStatements": true,
    "requireSpacesInForStatement": true,
    "requireLineFeedAtFileEnd": true,
    "requireSpacesInFunctionExpression": {
        "beforeOpeningCurlyBrace": true
    },
    "disallowSpacesInsideObjectBrackets": "all",
    "disallowSpacesInsideArrayBrackets": "all",
    "disallowSpacesInsideParentheses": true,

    "disallowMultipleLineBreaks": true,
    "disallowNewlineBeforeBlockStatements": [
      "if", "else", "try", "catch", "finally", "do", "while", "for", "function"
    ]
}


================================================
FILE: .jshintrc
================================================
{
	"asi": false,
	"bitwise": false,
	"boss": false,
	"browser": true,
	"camelcase": true,
	"couch": false,
	"curly": true,
	"debug": false,
	"devel": true,
	"dojo": false,
	"eqeqeq": true,
	"eqnull": true,
	"es3": true,
	"evil": false,
	"expr": true,
	"forin": false,
	"funcscope": true,
	"globalstrict": false,
	"immed": true,
	"iterator": false,
	"jquery": false,
	"lastsemic": false,
	"latedef": false,
	"laxbreak": true,
	"laxcomma": false,
	"loopfunc": true,
	"mootools": false,
	"multistr": false,
	"newcap": true,
	"noarg": true,
	"node": false,
	"noempty": false,
	"nonew": true,
	"nonstandard": false,
	"nomen": false,
	"onecase": false,
	"onevar": false,
	"passfail": false,
	"plusplus": false,
	"proto": false,
	"prototypejs": false,
	"regexdash": true,
	"regexp": false,
	"rhino": false,
	"undef": true,
	"unused": "strict",
	"scripturl": true,
	"shadow": false,
	"smarttabs": true,
	"strict": false,
	"sub": false,
	"supernew": false,
	"trailing": true,
	"validthis": true,
	"withstmt": false,
	"white": true,
	"worker": false,
	"wsh": false,
	"yui": false,
	"indent": 4,
	"predef": ["require", "define", "JSON"],
	"quotmark": "single",
	"maxcomplexity": 10,
	"esnext": true
}


================================================
FILE: .pep8rc
================================================
; TODO: This configuration currently not used in pytest-pep8, see
; <https://bitbucket.org/pytest-dev/pytest-pep8/issues/11/add-option-to-use-pep8rc-configuration>

[pep8]
show-source = yes
statistics = yes
count = yes


================================================
FILE: .pre-commit-config.yaml
================================================
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: 'v5.0.0'
  hooks:
  - id: check-merge-conflict
    exclude: "rst$"
- repo: https://github.com/asottile/yesqa
  rev: v1.5.0
  hooks:
  - id: yesqa
- repo: https://github.com/PyCQA/isort
  rev: '6.0.1'
  hooks:
  - id: isort
- repo: https://github.com/psf/black
  rev: '25.1.0'
  hooks:
  - id: black
    language_version: python3
- repo: https://github.com/pre-commit/pre-commit-hooks
  rev: 'v5.0.0'
  hooks:
  - id: check-case-conflict
  - id: check-json
  - id: check-xml
  - id: check-yaml
  - id: debug-statements
  - id: check-added-large-files
  - id: end-of-file-fixer
    exclude: "[.]md$"
  - id: requirements-txt-fixer
  - id: trailing-whitespace
    exclude: "[.]ref$"
  - id: check-symlinks
  - id: debug-statements
- repo: https://github.com/asottile/pyupgrade
  rev: 'v3.19.1'
  hooks:
  - id: pyupgrade
    args: ['--py39-plus']
- repo: https://github.com/PyCQA/flake8
  rev: '7.1.2'
  hooks:
  - id: flake8
- repo: https://github.com/rhysd/actionlint
  rev: v1.7.7
  hooks:
  - id: actionlint-docker
    args:
    - -ignore
    - 'SC2155:'
    - -ignore
    - 'SC2086:'
    - -ignore
    - 'SC1004:'
ci:
  skip:
  - actionlint-docker


================================================
FILE: .pylintrc
================================================
[MASTER]

# Specify a configuration file.
#rcfile=

# Python code to execute, usually for sys.path manipulation such as
# pygtk.require().
#init-hook=

# Profiled execution.
profile=no

# Add files or directories to the blacklist. They should be base names, not
# paths.
ignore=

# Pickle collected data for later comparisons.
persistent=yes

# List of plugins (as comma separated values of python modules names) to load,
# usually to register additional checkers.
load-plugins=

# Deprecated. It was used to include message's id in output. Use --msg-template
# instead.
#include-ids=no

# Deprecated. It was used to include symbolic ids of messages in output. Use
# --msg-template instead.
#symbols=no

# Use multiple processes to speed up Pylint.
jobs=1

# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no

# A comma-separated list of package or module names from where C extensions may
# be loaded. Extensions are loading into the active Python interpreter and may
# run arbitrary code
extension-pkg-whitelist=

# Allow optimization of some AST trees. This will activate a peephole AST
# optimizer, which will apply various small optimizations. For instance, it can
# be used to obtain the result of joining multiple strings with the addition
# operator. Joining a lot of strings can lead to a maximum recursion error in
# Pylint and this flag can prevent that. It has one side effect, the resulting
# AST will be different than the one from reality.
optimize-ast=no


[MESSAGES CONTROL]

# Only show warnings with the listed confidence levels. Leave empty to show
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
confidence=

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
# multiple time. See also the "--disable" option for examples.
#enable=

# Disable the message, report, category or checker with the given id(s). You
# can either give multiple identifiers separated by comma (,) or put this
# option multiple times (only on the command line, not in the configuration
# file where it should appear only once).You can also use "--disable=all" to
# disable everything first and then reenable specific checks. For example, if
# you want to run only the similarities checker, you can use "--disable=all
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=E1604,W1629,W1605,I0020,W1609,W1615,W1610,W1618,W1608,W1622,W1640,E1603,W1635,W1636,W1634,W1628,W1614,E1601,W1601,I0021,E1605,W1611,W1612,W1619,W1616,W1638,W1626,W1630,W1607,E1602,W1623,W1613,W1606,W1625,W0704,W1639,W1603,W1632,E1606,W1602,W1637,W1624,W1620,E1608,W1627,E1607,W1633,W1604,W1617,W1621


[REPORTS]

# Set the output format. Available formats are text, parseable, colorized, msvs
# (visual studio) and html. You can also give a reporter class, eg
# mypackage.mymodule.MyReporterClass.
output-format=text

# Put messages in a separate file for each module / package specified on the
# command line instead of printing them on stdout. Reports (if any) will be
# written in a file name "pylint_global.[txt|html]".
files-output=no

# Tells whether to display a full report or only the messages
reports=yes

# Python expression which should return a note less than 10 (10 is the highest
# note). You have access to the variables errors warning, statement which
# respectively contain the number of errors / warnings messages and the total
# number of statements analyzed. This is used by the global evaluation report
# (RP0004).
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)

# Add a comment according to your evaluation note. This is used by the global
# evaluation report (RP0004).
comment=no

# Template used to display messages. This is a python new-style format string
# used to format the message information. See doc for all details.
# Path/line on separate added to allow IDE to parse error messages and
# provide interactive link on the place of the error.
msg-template={path}:{line}:
    {C}:{msg_id}:{line:3d},{column:2d}: {msg} ({symbol})


[LOGGING]

# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging


[FORMAT]

# Maximum number of characters on a single line.
max-line-length=100

# Regexp for a line that is allowed to be longer than the limit.
ignore-long-lines=^\s*(# )?<?https?://\S+>?$

# Allow the body of an if to be on the same line as the test if there is no
# else.
single-line-if-stmt=no

# List of optional constructs for which whitespace checking is disabled
no-space-check=trailing-comma,dict-separator

# Maximum number of lines in a module
max-module-lines=1000

# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
# tab).
indent-string='    '

# Number of spaces of indent required inside a hanging or continued line.
indent-after-paren=4

# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
expected-line-ending-format=


[MISCELLANEOUS]

# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO


[VARIABLES]

# Tells whether we should check for unused import in __init__ files.
init-import=no

# A regular expression matching the name of dummy variables (i.e. expectedly
# not used).
dummy-variables-rgx=_$|dummy

# List of additional names supposed to be defined in builtins. Remember that
# you should avoid to define new builtins when possible.
additional-builtins=

# List of strings which can identify a callback function by name. A callback
# name must start or end with one of those strings.
callbacks=cb_,_cb


[BASIC]

# Required attributes for module, separated by a comma
required-attributes=

# List of builtins function names that should not be used, separated by a comma
bad-functions=map,filter

# Good variable names which should always be accepted, separated by a comma
good-names=i,j,k,ex,Run,_

# Bad variable names which should always be refused, separated by a comma
bad-names=foo,bar,baz,toto,tutu,tata

# Colon-delimited sets of names that determine each other's naming style when
# the name regexes allow several styles.
name-group=

# Include a hint for the correct naming format with invalid-name
include-naming-hint=no

# Regular expression matching correct module names
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$

# Naming hint for module names
module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$

# Regular expression matching correct method names
method-rgx=[a-z_][a-z0-9_]{2,30}$

# Naming hint for method names
method-name-hint=[a-z_][a-z0-9_]{2,30}$

# Regular expression matching correct class attribute names
class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$

# Naming hint for class attribute names
class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$

# Regular expression matching correct class names
class-rgx=[A-Z_][a-zA-Z0-9]+$

# Naming hint for class names
class-name-hint=[A-Z_][a-zA-Z0-9]+$

# Regular expression matching correct function names
function-rgx=[a-z_][a-z0-9_]{2,30}$

# Naming hint for function names
function-name-hint=[a-z_][a-z0-9_]{2,30}$

# Regular expression matching correct inline iteration names
inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$

# Naming hint for inline iteration names
inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$

# Regular expression matching correct constant names
const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$

# Naming hint for constant names
const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$

# Regular expression matching correct argument names
argument-rgx=[a-z_][a-z0-9_]{2,30}$

# Naming hint for argument names
argument-name-hint=[a-z_][a-z0-9_]{2,30}$

# Regular expression matching correct attribute names
attr-rgx=[a-z_][a-z0-9_]{2,30}$

# Naming hint for attribute names
attr-name-hint=[a-z_][a-z0-9_]{2,30}$

# Regular expression matching correct variable names
variable-rgx=[a-z_][a-z0-9_]{2,30}$

# Naming hint for variable names
variable-name-hint=[a-z_][a-z0-9_]{2,30}$

# Regular expression which should only match function or class names that do
# not require a docstring.
no-docstring-rgx=__.*__

# Minimum line length for functions/classes that require docstrings, shorter
# ones are exempt.
docstring-min-length=-1


[TYPECHECK]

# Tells whether missing members accessed in mixin class should be ignored. A
# mixin class is detected if its name ends with "mixin" (case insensitive).
ignore-mixin-members=yes

# List of module names for which member attributes should not be checked
# (useful for modules/projects where namespaces are manipulated during runtime
# and thus existing member attributes cannot be deduced by static analysis
ignored-modules=

# List of classes names for which member attributes should not be checked
# (useful for classes with attributes dynamically set).
ignored-classes=SQLObject

# When zope mode is activated, add a predefined set of Zope acquired attributes
# to generated-members.
zope=no

# List of members which are set dynamically and missed by pylint inference
# system, and so shouldn't trigger E0201 when accessed. Python regular
# expressions are accepted.
generated-members=REQUEST,acl_users,aq_parent


[SPELLING]

# Spelling dictionary name. Available dictionaries: none. To make it working
# install python-enchant package.
spelling-dict=

# List of comma separated words that should not be checked.
spelling-ignore-words=

# A path to a file that contains private dictionary; one word per line.
spelling-private-dict-file=

# Tells whether to store unknown words to indicated private dictionary in
# --spelling-private-dict-file option instead of raising a message.
spelling-store-unknown-words=no


[SIMILARITIES]

# Minimum lines number of a similarity.
min-similarity-lines=4

# Ignore comments when computing similarities.
ignore-comments=yes

# Ignore docstrings when computing similarities.
ignore-docstrings=yes

# Ignore imports when computing similarities.
ignore-imports=no


[IMPORTS]

# Deprecated modules which should not be used, separated by a comma
deprecated-modules=stringprep,optparse

# Create a graph of every (i.e. internal and external) dependencies in the
# given file (report RP0402 must not be disabled)
import-graph=

# Create a graph of external dependencies in the given file (report RP0402 must
# not be disabled)
ext-import-graph=

# Create a graph of internal dependencies in the given file (report RP0402 must
# not be disabled)
int-import-graph=


[CLASSES]

# List of interface methods to ignore, separated by a comma. This is used for
# instance to not check methods defines in Zope's Interface base class.
ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by

# List of method names used to declare (i.e. assign) instance attributes.
defining-attr-methods=__init__,__new__,setUp

# List of valid names for the first argument in a class method.
valid-classmethod-first-arg=cls

# List of valid names for the first argument in a metaclass class method.
valid-metaclass-classmethod-first-arg=mcs

# List of member names, which should be excluded from the protected access
# warning.
exclude-protected=_asdict,_fields,_replace,_source,_make


[DESIGN]

# Maximum number of arguments for function / method
max-args=5

# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*

# Maximum number of locals for function / method body
max-locals=15

# Maximum number of return / yield for function / method body
max-returns=6

# Maximum number of branch for function / method body
max-branches=12

# Maximum number of statements in function / method body
max-statements=50

# Maximum number of parents for a class (see R0901).
max-parents=7

# Maximum number of attributes for a class (see R0902).
max-attributes=7

# Minimum number of public methods for a class (see R0903).
min-public-methods=2

# Maximum number of public methods for a class (see R0904).
max-public-methods=20


[EXCEPTIONS]

# Exceptions that will emit a warning when being caught. Defaults to
# "Exception"
overgeneral-exceptions=Exception


================================================
FILE: .pyup.yml
================================================
# Label PRs with `deps-update` label
label_prs: deps-update

schedule: every week


================================================
FILE: CHANGES.rst
================================================
=========
 CHANGES
=========

0.8.1 (2025-03-31)
==================

- Fix packaging to not install on Python 3.8.

0.8.0 (2025-03-11)
==================

- Make the library compatible with aiohttp 3.9+ and Python 3.9+

0.7.0 (2018-03-05)
==================

- Make web view check implicit and type based (#159)

- Disable Python 3.4 support (#156)

- Support aiohttp 3.0+ (#155)

0.6.0 (2017-12-21)
==================

- Support aiohttp views by ``CorsViewMixin`` (#145)

0.5.3 (2017-04-21)
==================

- Fix ``typing`` being installed on Python 3.6.

0.5.2 (2017-03-28)
==================

- Fix tests compatibility with ``aiohttp`` 2.0.
  This release and release v0.5.0 should work on ``aiohttp`` 2.0.


0.5.1 (2017-03-23)
==================

- Enforce ``aiohttp`` version to be less than 2.0.
  Newer ``aiohttp`` releases will be supported in the next release.

0.5.0 (2016-11-18)
==================

- Fix compatibility with ``aiohttp`` 1.1


0.4.0 (2016-04-04)
==================

- Fixed support with new Resources objects introduced in ``aiohttp`` 0.21.0.
  Minimum supported version of ``aiohttp`` is 0.21.4 now.

- New Resources objects are supported.
  You can specify default configuration for a Resource and use
  ``allow_methods`` to explicitly list allowed methods (or ``*`` for all
  HTTP methods):

  .. code-block:: python

        # Allow POST and PUT requests from "http://client.example.org" origin.
        hello_resource = cors.add(app.router.add_resource("/hello"), {
                "http://client.example.org":
                    aiohttp_cors.ResourceOptions(
                        allow_methods=["POST", "PUT"]),
            })
        # No need to add POST and PUT routes into CORS configuration object.
        hello_resource.add_route("POST", handler_post)
        hello_resource.add_route("PUT", handler_put)
        # Still you can add additional methods to CORS configuration object:
        cors.add(hello_resource.add_route("DELETE", handler_delete))

- ``AbstractRouterAdapter`` was completely rewritten to be more Router
  agnostic.

0.3.0 (2016-02-06)
==================

- Rename ``UrlDistatcherRouterAdapter`` to ``UrlDispatcherRouterAdapter``.

- Set maximum supported ``aiohttp`` version to ``0.20.2``, see bug #30 for
  details.

0.2.0 (2015-11-30)
==================

- Move ABCs from ``aiohttp_cors.router_adapter`` to ``aiohttp_cors.abc``.

- Rename ``RouterAdapter`` to ``AbstractRouterAdapter``.

- Fix bug with configuring CORS for named routes.

0.1.0 (2015-11-05)
==================

* Initial release.


================================================
FILE: LICENSE
================================================
                                 Apache License
                           Version 2.0, January 2004
                        http://www.apache.org/licenses/

   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

   1. Definitions.

      "License" shall mean the terms and conditions for use, reproduction,
      and distribution as defined by Sections 1 through 9 of this document.

      "Licensor" shall mean the copyright owner or entity authorized by
      the copyright owner that is granting the License.

      "Legal Entity" shall mean the union of the acting entity and all
      other entities that control, are controlled by, or are under common
      control with that entity. For the purposes of this definition,
      "control" means (i) the power, direct or indirect, to cause the
      direction or management of such entity, whether by contract or
      otherwise, or (ii) ownership of fifty percent (50%) or more of the
      outstanding shares, or (iii) beneficial ownership of such entity.

      "You" (or "Your") shall mean an individual or Legal Entity
      exercising permissions granted by this License.

      "Source" form shall mean the preferred form for making modifications,
      including but not limited to software source code, documentation
      source, and configuration files.

      "Object" form shall mean any form resulting from mechanical
      transformation or translation of a Source form, including but
      not limited to compiled object code, generated documentation,
      and conversions to other media types.

      "Work" shall mean the work of authorship, whether in Source or
      Object form, made available under the License, as indicated by a
      copyright notice that is included in or attached to the work
      (an example is provided in the Appendix below).

      "Derivative Works" shall mean any work, whether in Source or Object
      form, that is based on (or derived from) the Work and for which the
      editorial revisions, annotations, elaborations, or other modifications
      represent, as a whole, an original work of authorship. For the purposes
      of this License, Derivative Works shall not include works that remain
      separable from, or merely link (or bind by name) to the interfaces of,
      the Work and Derivative Works thereof.

      "Contribution" shall mean any work of authorship, including
      the original version of the Work and any modifications or additions
      to that Work or Derivative Works thereof, that is intentionally
      submitted to Licensor for inclusion in the Work by the copyright owner
      or by an individual or Legal Entity authorized to submit on behalf of
      the copyright owner. For the purposes of this definition, "submitted"
      means any form of electronic, verbal, or written communication sent
      to the Licensor or its representatives, including but not limited to
      communication on electronic mailing lists, source code control systems,
      and issue tracking systems that are managed by, or on behalf of, the
      Licensor for the purpose of discussing and improving the Work, but
      excluding communication that is conspicuously marked or otherwise
      designated in writing by the copyright owner as "Not a Contribution."

      "Contributor" shall mean Licensor and any individual or Legal Entity
      on behalf of whom a Contribution has been received by Licensor and
      subsequently incorporated within the Work.

   2. Grant of Copyright License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      copyright license to reproduce, prepare Derivative Works of,
      publicly display, publicly perform, sublicense, and distribute the
      Work and such Derivative Works in Source or Object form.

   3. Grant of Patent License. Subject to the terms and conditions of
      this License, each Contributor hereby grants to You a perpetual,
      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
      (except as stated in this section) patent license to make, have made,
      use, offer to sell, sell, import, and otherwise transfer the Work,
      where such license applies only to those patent claims licensable
      by such Contributor that are necessarily infringed by their
      Contribution(s) alone or by combination of their Contribution(s)
      with the Work to which such Contribution(s) was submitted. If You
      institute patent litigation against any entity (including a
      cross-claim or counterclaim in a lawsuit) alleging that the Work
      or a Contribution incorporated within the Work constitutes direct
      or contributory patent infringement, then any patent licenses
      granted to You under this License for that Work shall terminate
      as of the date such litigation is filed.

   4. Redistribution. You may reproduce and distribute copies of the
      Work or Derivative Works thereof in any medium, with or without
      modifications, and in Source or Object form, provided that You
      meet the following conditions:

      (a) You must give any other recipients of the Work or
          Derivative Works a copy of this License; and

      (b) You must cause any modified files to carry prominent notices
          stating that You changed the files; and

      (c) You must retain, in the Source form of any Derivative Works
          that You distribute, all copyright, patent, trademark, and
          attribution notices from the Source form of the Work,
          excluding those notices that do not pertain to any part of
          the Derivative Works; and

      (d) If the Work includes a "NOTICE" text file as part of its
          distribution, then any Derivative Works that You distribute must
          include a readable copy of the attribution notices contained
          within such NOTICE file, excluding those notices that do not
          pertain to any part of the Derivative Works, in at least one
          of the following places: within a NOTICE text file distributed
          as part of the Derivative Works; within the Source form or
          documentation, if provided along with the Derivative Works; or,
          within a display generated by the Derivative Works, if and
          wherever such third-party notices normally appear. The contents
          of the NOTICE file are for informational purposes only and
          do not modify the License. You may add Your own attribution
          notices within Derivative Works that You distribute, alongside
          or as an addendum to the NOTICE text from the Work, provided
          that such additional attribution notices cannot be construed
          as modifying the License.

      You may add Your own copyright statement to Your modifications and
      may provide additional or different license terms and conditions
      for use, reproduction, or distribution of Your modifications, or
      for any such Derivative Works as a whole, provided Your use,
      reproduction, and distribution of the Work otherwise complies with
      the conditions stated in this License.

   5. Submission of Contributions. Unless You explicitly state otherwise,
      any Contribution intentionally submitted for inclusion in the Work
      by You to the Licensor shall be under the terms and conditions of
      this License, without any additional terms or conditions.
      Notwithstanding the above, nothing herein shall supersede or modify
      the terms of any separate license agreement you may have executed
      with Licensor regarding such Contributions.

   6. Trademarks. This License does not grant permission to use the trade
      names, trademarks, service marks, or product names of the Licensor,
      except as required for reasonable and customary use in describing the
      origin of the Work and reproducing the content of the NOTICE file.

   7. Disclaimer of Warranty. Unless required by applicable law or
      agreed to in writing, Licensor provides the Work (and each
      Contributor provides its Contributions) on an "AS IS" BASIS,
      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
      implied, including, without limitation, any warranties or conditions
      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
      PARTICULAR PURPOSE. You are solely responsible for determining the
      appropriateness of using or redistributing the Work and assume any
      risks associated with Your exercise of permissions under this License.

   8. Limitation of Liability. In no event and under no legal theory,
      whether in tort (including negligence), contract, or otherwise,
      unless required by applicable law (such as deliberate and grossly
      negligent acts) or agreed to in writing, shall any Contributor be
      liable to You for damages, including any direct, indirect, special,
      incidental, or consequential damages of any character arising as a
      result of this License or out of the use or inability to use the
      Work (including but not limited to damages for loss of goodwill,
      work stoppage, computer failure or malfunction, or any and all
      other commercial damages or losses), even if such Contributor
      has been advised of the possibility of such damages.

   9. Accepting Warranty or Additional Liability. While redistributing
      the Work or Derivative Works thereof, You may choose to offer,
      and charge a fee for, acceptance of support, warranty, indemnity,
      or other liability obligations and/or rights consistent with this
      License. However, in accepting such obligations, You may act only
      on Your own behalf and on Your sole responsibility, not on behalf
      of any other Contributor, and only if You agree to indemnify,
      defend, and hold each Contributor harmless for any liability
      incurred by, or claims asserted against, such Contributor by reason
      of your accepting any such warranty or additional liability.

   END OF TERMS AND CONDITIONS

   APPENDIX: How to apply the Apache License to your work.

      To apply the Apache License to your work, attach the following
      boilerplate notice, with the fields enclosed by brackets "{}"
      replaced with your own identifying information. (Don't include
      the brackets!)  The text should be enclosed in the appropriate
      comment syntax for the file format. We also recommend that a
      file or class name and description of purpose be included on the
      same "printed page" as the copyright notice for easier
      identification within third-party archives.

   Copyright 2015-2018 Vladimir Rutsky and aio-libs team

   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.


================================================
FILE: MANIFEST.in
================================================
include LICENSE
include CHANGES.rst
include README.rst
recursive-include tests *.py
include tests/integration/test_page.html


================================================
FILE: Makefile
================================================
all: test


lint:
	pre-commit run --all-files

test: lint
	pytest tests


================================================
FILE: README.rst
================================================
========================
CORS support for aiohttp
========================

``aiohttp_cors`` library implements
`Cross Origin Resource Sharing (CORS) <cors_>`__
support for `aiohttp <aiohttp_>`__
asyncio-powered asynchronous HTTP server.

Jump directly to `Usage`_ part to see how to use ``aiohttp_cors``.

Same-origin policy
==================

Web security model is tightly connected to
`Same-origin policy (SOP) <sop_>`__.
In short: web pages cannot *Read* resources which origin
doesn't match origin of requested page, but can *Embed* (or *Execute*)
resources and have limited ability to *Write* resources.

Origin of a page is defined in the `Standard <cors_>`__ as tuple
``(schema, host, port)``
(there is a notable exception with Internet Explorer: it doesn't use port to
define origin, but uses it's own
`Security Zones <https://msdn.microsoft.com/en-us/library/ms537183.aspx>`__).

Can *Embed* means that resource from other origin can be embedded into
the page,
e.g. by using ``<script src="...">``, ``<img src="...">``,
``<iframe src="...">``.

Cannot *Read* means that resource from other origin *source* cannot be
obtained by page
(*source* — any information that would allow to reconstruct resource).
E.g. the page can *Embed* image with ``<img src="...">``,
but it can't get information about specific pixels, so page can't reconstruct
original image
(though some information from the other resource may still be leaked:
e.g. the page can read embedded image dimensions).

Limited ability to *Write* means, that the page can send POST requests to
other origin with limited set of ``Content-Type`` values and headers.

Restriction to *Read* resource from other origin is related to authentication
mechanism that is used by browsers:
when browser reads (downloads) resource he automatically sends all security
credentials that user previously authorized for that resource
(e.g. cookies, HTTP Basic Authentication).

For example, if *Read* would be allowed and user is authenticated
in some internet banking,
malicious page would be able to embed internet banking page with ``iframe``
(since authentication is done by the browser it may be embedded as if
user is directly navigated to internet banking page),
then read user private information by reading *source* of the embedded page
(which may be not only source code, but, for example,
screenshot of the embedded internet banking page).

Cross-origin resource sharing
=============================

`Cross-origin Resource Sharing (CORS) <cors_>`__ allows to override
SOP for specific resources.

In short, CORS works in the following way.

When page ``https://client.example.com`` request (*Read*) resource
``https://server.example.com/resource`` that have other origin,
browser implicitly appends ``Origin: https://client.example.com`` header
to the HTTP request,
effectively requesting server to give read permission for
the resource to the ``https://client.example.com`` page::

    GET /resource HTTP/1.1
    Origin: https://client.example.com
    Host: server.example.com

If server allows access from the page to the resource, it responds with
resource with ``Access-Control-Allow-Origin: https://client.example.com``
HTTP header
(optionally allowing exposing custom server headers to the page and
enabling use of the user credentials on the server resource)::

    Access-Control-Allow-Origin: https://client.example.com
    Access-Control-Allow-Credentials: true
    Access-Control-Expose-Headers: X-Server-Header

Browser checks, if server responded with proper
``Access-Control-Allow-Origin`` header and accordingly allows or denies
access for the obtained resource to the page.

CORS specification designed in a way that servers that are not aware
of CORS will not expose any additional information, except allowed by the
SOP.

To request resources with custom headers or using custom HTTP methods
(e.g. ``PUT``, ``DELETE``) that are not allowed by SOP,
CORS-enabled browser first send *preflight request* to the
resource using ``OPTIONS`` method, in which he queries access to the resource
with specific method and headers::

    OPTIONS / HTTP/1.1
    Origin: https://client.example.com
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: X-Client-Header

CORS-enabled server responds is requested method is allowed and which of
the specified headers are allowed::

    Access-Control-Allow-Origin: https://client.example.com
    Access-Control-Allow-Credentials: true
    Access-Control-Allow-Methods: PUT
    Access-Control-Allow-Headers: X-Client-Header
    Access-Control-Max-Age: 3600

Browser checks response to preflight request, and, if actual request allowed,
does actual request.

Installation
============

You can install ``aiohttp_cors`` as a typical Python library from PyPI or
from git:

.. code-block:: bash

    $ pip install aiohttp_cors

Usage
=====

To use ``aiohttp_cors`` you need to configure the application and
enable CORS on
`resources and routes <https://aiohttp.readthedocs.org/en/stable/web.html#resources-and-routes>`__
that you want to expose:

.. code-block:: python

    import asyncio
    from aiohttp import web
    import aiohttp_cors

    @asyncio.coroutine
    def handler(request):
        return web.Response(
            text="Hello!",
            headers={
                "X-Custom-Server-Header": "Custom data",
            })

    app = web.Application()

    # `aiohttp_cors.setup` returns `aiohttp_cors.CorsConfig` instance.
    # The `cors` instance will store CORS configuration for the
    # application.
    cors = aiohttp_cors.setup(app)

    # To enable CORS processing for specific route you need to add
    # that route to the CORS configuration object and specify its
    # CORS options.
    resource = cors.add(app.router.add_resource("/hello"))
    route = cors.add(
        resource.add_route("GET", handler), {
            "http://client.example.org": aiohttp_cors.ResourceOptions(
                allow_credentials=True,
                expose_headers=("X-Custom-Server-Header",),
                allow_headers=("X-Requested-With", "Content-Type"),
                max_age=3600,
            )
        })

Each route has it's own CORS configuration passed in ``CorsConfig.add()``
method.

CORS configuration is a mapping from origins to options for that origins.

In the example above CORS is configured for the resource under path ``/hello``
and HTTP method ``GET``, and in the context of CORS:

* This resource will be available using CORS only to
  ``http://client.example.org`` origin.

* Passing of credentials to this resource will be allowed.

* The resource will expose to the client ``X-Custom-Server-Header``
  server header.

* The client will be allowed to pass ``X-Requested-With`` and
  ``Content-Type`` headers to the server.

* Preflight requests will be allowed to be cached by client for ``3600``
  seconds.

Resource will be available only to the explicitly specified origins.
You can specify "all other origins" using special ``*`` origin:

.. code-block:: python

    cors.add(route, {
            "*":
                aiohttp_cors.ResourceOptions(allow_credentials=False),
            "http://client.example.org":
                aiohttp_cors.ResourceOptions(allow_credentials=True),
        })

Here the resource specified by ``route`` will be available to all origins with
disallowed credentials passing, and with allowed credentials passing only to
``http://client.example.org``.

By default ``ResourceOptions`` will be constructed without any allowed CORS
options.
This means, that resource will be available using CORS to specified origin,
but client will not be allowed to send either credentials,
or send non-simple headers, or read from server non-simple headers.

To enable sending or receiving all headers you can specify special value
``*`` instead of sequence of headers:

.. code-block:: python

    cors.add(route, {
            "http://client.example.org":
                aiohttp_cors.ResourceOptions(
                    expose_headers="*",
                    allow_headers="*"),
        })

You can specify default CORS-enabled resource options using
``aiohttp_cors.setup()``'s ``defaults`` argument:

.. code-block:: python

    cors = aiohttp_cors.setup(app, defaults={
            # Allow all to read all CORS-enabled resources from
            # http://client.example.org.
            "http://client.example.org": aiohttp_cors.ResourceOptions(),
        })

    # Enable CORS on routes.

    # According to defaults POST and PUT will be available only to
    # "http://client.example.org".
    hello_resource = cors.add(app.router.add_resource("/hello"))
    cors.add(hello_resource.add_route("POST", handler_post))
    cors.add(hello_resource.add_route("PUT", handler_put))

    # In addition to "http://client.example.org", GET request will be
    # allowed from "http://other-client.example.org" origin.
    cors.add(hello_resource.add_route("GET", handler), {
            "http://other-client.example.org":
                aiohttp_cors.ResourceOptions(),
        })

    # CORS will be enabled only on the resources added to `CorsConfig`,
    # so following resource will be NOT CORS-enabled.
    app.router.add_route("GET", "/private", handler)

Also you can specify default options for resources:

.. code-block:: python

    # Allow POST and PUT requests from "http://client.example.org" origin.
    hello_resource = cors.add(app.router.add_resource("/hello"), {
            "http://client.example.org": aiohttp_cors.ResourceOptions(),
        })
    cors.add(hello_resource.add_route("POST", handler_post))
    cors.add(hello_resource.add_route("PUT", handler_put))

Resource CORS configuration allows to use ``allow_methods`` option that
explicitly specifies list of allowed HTTP methods for origin
(or ``*`` for all HTTP methods).
By using this option it is not required to add all resource routes to
CORS configuration object:

.. code-block:: python

    # Allow POST and PUT requests from "http://client.example.org" origin.
    hello_resource = cors.add(app.router.add_resource("/hello"), {
            "http://client.example.org":
                aiohttp_cors.ResourceOptions(allow_methods=["POST", "PUT"]),
        })
    # No need to add POST and PUT routes into CORS configuration object.
    hello_resource.add_route("POST", handler_post)
    hello_resource.add_route("PUT", handler_put)
    # Still you can add additional methods to CORS configuration object:
    cors.add(hello_resource.add_route("DELETE", handler_delete))

Here is an example of how to enable CORS for all origins with all CORS
features:

.. code-block:: python

    cors = aiohttp_cors.setup(app, defaults={
        "*": aiohttp_cors.ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers="*",
            )
    })

    # Add all resources to `CorsConfig`.
    resource = cors.add(app.router.add_resource("/hello"))
    cors.add(resource.add_route("GET", handler_get))
    cors.add(resource.add_route("PUT", handler_put))
    cors.add(resource.add_route("POST", handler_put))
    cors.add(resource.add_route("DELETE", handler_delete))

Old routes API is supported — you can use ``router.add_router`` and
``router.register_route`` as before, though this usage is discouraged:

.. code-block:: python

    cors.add(
        app.router.add_route("GET", "/hello", handler), {
            "http://client.example.org": aiohttp_cors.ResourceOptions(
                allow_credentials=True,
                expose_headers=("X-Custom-Server-Header",),
                allow_headers=("X-Requested-With", "Content-Type"),
                max_age=3600,
            )
        })

You can enable CORS for all added routes by accessing routes list
in the router:

.. code-block:: python

    # Setup application routes.
    app.router.add_route("GET", "/hello", handler_get)
    app.router.add_route("PUT", "/hello", handler_put)
    app.router.add_route("POST", "/hello", handler_put)
    app.router.add_route("DELETE", "/hello", handler_delete)

    # Configure default CORS settings.
    cors = aiohttp_cors.setup(app, defaults={
        "*": aiohttp_cors.ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers="*",
            )
    })

    # Configure CORS on all routes.
    for route in list(app.router.routes()):
        cors.add(route)

You can also use ``CorsViewMixin`` on ``web.View``:

.. code-block:: python

    class CorsView(web.View, CorsViewMixin):

        cors_config = {
            "*": ResourceOption(
                allow_credentials=True,
                allow_headers="X-Request-ID",
            )
        }

        @asyncio.coroutine
        def get(self):
            return web.Response(text="Done")

        @custom_cors({
            "*": ResourceOption(
                allow_credentials=True,
                allow_headers="*",
            )
        })
        @asyncio.coroutine
        def post(self):
            return web.Response(text="Done")

    cors = aiohttp_cors.setup(app, defaults={
        "*": aiohttp_cors.ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers="*",
            )
    })

    cors.add(
        app.router.add_route("*", "/resource", CorsView),
        webview=True)


Security
========

TODO: fill this

Development
===========

To setup development environment:

.. code-block:: bash

   # Clone sources repository:
   git clone https://github.com/aio-libs/aiohttp_cors.git .
   # Create and activate virtual Python environment:
   python3 -m venv env
   source env/bin/activate
   # Install requirements and aiohttp_cors into virtual environment
   pip install -r requirements-dev.txt

To run tests:

.. code-block:: bash

   tox

To run only runtime tests in current environment:

.. code-block:: bash

   py.test

To run only static code analysis checks:

.. code-block:: bash

   tox -e check

Running Selenium tests
----------------------

To run Selenium tests with Firefox web driver you need to install Firefox.

To run Selenium tests with Chromium web driver you need to:

1. Install Chrome driver. On Ubuntu 14.04 it's in ``chromium-chromedriver``
   package.

2. Either add ``chromedriver`` to PATH or set ``WEBDRIVER_CHROMEDRIVER_PATH``
   environment variable to ``chromedriver``, e.g. on Ubuntu 14.04
   ``WEBDRIVER_CHROMEDRIVER_PATH=/usr/lib/chromium-browser/chromedriver``.

Release process
---------------

To release version ``vA.B.C`` from the current version of ``master`` branch
you need to:

1. Create local branch ``vA.B.C``.
2. In ``CHANGES.rst`` set release date to today.
3. In ``aiohttp_cors/__about__.py`` change version from ``A.B.Ca0`` to
   ``A.B.C``.
4. Create pull request with ``vA.B.C`` branch, wait for all checks to
   successfully finish (Travis and Appveyor).
5. Merge pull request to master.
6. Update and checkout ``master`` branch.

7. Create and push tag for release version to GitHub:

   .. code-block:: bash

      git tag vA.B.C
      git push --tags

   Now Travis should ran tests again, and build and deploy wheel on PyPI.

   If Travis release doesn't work for some reason, use following steps
   for manual release upload.

   1. Install fresh versions of setuptools and pip.
      Install ``wheel`` for building wheels.
      Install ``twine`` for uploading to PyPI.

      .. code-block:: bash

         pip install -U pip setuptools twine wheel

   2. Configure PyPI credentials in ``~/.pypirc``.

   3. Build distribution:

      .. code-block:: bash

         rm -rf build dist; python setup.py sdist bdist_wheel

   4. Upload new release to PyPI:

      .. code-block:: bash

         twine upload dist/*

8. Edit release description on GitHub if needed.
9. Announce new release on the *aio-libs* mailing list:
   https://groups.google.com/forum/#!forum/aio-libs.

Post release steps:

1. In ``CHANGES.rst`` add template for the next release.
2. In ``aiohttp_cors/__about__.py`` change version from ``A.B.C`` to
   ``A.(B + 1).0a0``.

Bugs
====

Please report bugs, issues, feature requests, etc. on
`GitHub <https://github.com/aio-libs/aiohttp_cors/issues>`__.


License
=======

Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>.

Licensed under the
`Apache License, Version 2.0 <https://www.apache.org/licenses/LICENSE-2.0>`__,
see ``LICENSE`` file for details.

.. _cors: http://www.w3.org/TR/cors/
.. _aiohttp: https://github.com/KeepSafe/aiohttp/
.. _sop: https://en.wikipedia.org/wiki/Same-origin_policy


================================================
FILE: aiohttp_cors/__about__.py
================================================
# 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.

"""
Library meta information.

This module must be stand-alone executable.
"""

__title__ = "aiohttp-cors"
__version__ = "0.8.1"
__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 aio-libs team"


================================================
FILE: aiohttp_cors/__init__.py
================================================
# 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.

"""CORS support for aiohttp."""

from collections.abc import Mapping
from typing import Any, Union

from aiohttp import web

from .__about__ import (
    __author__,
    __copyright__,
    __email__,
    __license__,
    __summary__,
    __title__,
    __uri__,
    __version__,
)
from .cors_config import CorsConfig
from .mixin import CorsViewMixin, custom_cors
from .resource_options import ResourceOptions

__all__ = (
    "CorsConfig",
    "CorsViewMixin",
    "ResourceOptions",
    "__author__",
    "__copyright__",
    "__email__",
    "__license__",
    "__summary__",
    "__title__",
    "__uri__",
    "__version__",
    "custom_cors",
    "setup",
)


APP_CONFIG_KEY: web.AppKey[CorsConfig] = web.AppKey("aiohttp_cors", CorsConfig)


def setup(
    app: web.Application,
    *,
    defaults: Mapping[str, Union[ResourceOptions, Mapping[str, Any]]] = None
) -> CorsConfig:
    """Setup CORS processing for the application.

    To enable CORS for a resource you need to explicitly add route for
    that resource using `CorsConfig.add()` method::

        app = aiohttp.web.Application()
        cors = aiohttp_cors.setup(app)
        cors.add(
            app.router.add_route("GET", "/resource", handler),
            {
                "*": aiohttp_cors.ResourceOptions(
                    allow_credentials=True,
                    expose_headers="*",
                    allow_headers="*"),
            })

    :param app:
        The application for which CORS will be configured.
    :param defaults:
        Default settings for origins.
    )
    """
    cors = CorsConfig(app, defaults=defaults)
    app[APP_CONFIG_KEY] = cors
    return cors


================================================
FILE: aiohttp_cors/abc.py
================================================
# 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.

"""Abstract base classes."""

from abc import ABCMeta, abstractmethod

from aiohttp import web

__all__ = ("AbstractRouterAdapter",)


class AbstractRouterAdapter(metaclass=ABCMeta):
    """Router adapter for handling CORS configuration interface.

    `AbstractRouter` doesn't specify how HTTP requests are delivered
    to handlers, and aiohttp_cors doesn't rely on specific implementation
    details.

    In general Router can be seen as a substance that allows to setup handlers
    for specific HTTP methods and requests paths, lets call these Router's
    items routing entities.
    Generic Router is configured with set of routing entities and their
    handlers.

    This adapter assumes that its reasonable to configure CORS for same
    routing entities as used in `AbstractRouter`.
    Routing entities will be added to CorsConfig to enable CORS for them.

    For example, for aiohttp < 0.21.0 routing entity would be
    `aiohttp.web.Route` — tuple of (HTTP method, URI path).
    And CORS can be configured for each `aiohttp.web.Route`.

    In aiohttp >= 0.21.0 there are two routing entities: Resource and Route.
    You can configure CORS for Resource (which will be interpreted as default
    for all Routes on Resources), and configure CORS for specific Route.
    """

    @abstractmethod
    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.
        Should fail if there are conflicting user-defined OPTIONS handlers.
        """

    @abstractmethod
    def is_preflight_request(self, request: web.Request) -> bool:
        """Is `request` is a CORS preflight request."""

    @abstractmethod
    def is_cors_enabled_on_request(self, request: web.Request) -> bool:
        """Is `request` is a request for CORS-enabled resource."""

    @abstractmethod
    def set_config_for_routing_entity(self, routing_entity, config):
        """Record configuration for routing entity.

        If router implements hierarchical routing entities, stored config
        can be used in hierarchical manner too.

        Should raise if there is conflicting configuration for the routing
        entity.
        """

    @abstractmethod
    async def get_preflight_request_config(
        self, preflight_request: web.Request, origin: str, requested_method: str
    ):
        """Get stored CORS configuration for specified HTTP method and origin
        that corresponds to preflight request.

        Should raise KeyError if CORS is not configured or not enabled
        for specified HTTP method.
        """

    @abstractmethod
    def get_non_preflight_request_config(self, request: web.Request):
        """Get stored CORS configuration for routing entity that handles
        specified request."""


================================================
FILE: aiohttp_cors/cors_config.py
================================================
# 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.

"""CORS configuration container class definition."""

import collections
import warnings
from collections.abc import Mapping
from typing import Any, Union

from aiohttp import hdrs, web

from .abc import AbstractRouterAdapter
from .preflight_handler import _PreflightHandler
from .resource_options import ResourceOptions
from .urldispatcher_router_adapter import ResourcesUrlDispatcherRouterAdapter

__all__ = ("CorsConfig",)

# Positive response to Access-Control-Allow-Credentials
_TRUE = "true"
# CORS simple response headers:
# <http://www.w3.org/TR/cors/#simple-response-header>
_SIMPLE_RESPONSE_HEADERS = frozenset(
    [
        hdrs.CACHE_CONTROL,
        hdrs.CONTENT_LANGUAGE,
        hdrs.CONTENT_TYPE,
        hdrs.EXPIRES,
        hdrs.LAST_MODIFIED,
        hdrs.PRAGMA,
    ]
)


def _parse_config_options(
    config: Mapping[str, Union[ResourceOptions, Mapping[str, Any]]] = None,
):
    """Parse CORS configuration (default or per-route)

    :param config:
        Mapping from Origin to Resource configuration (allowed headers etc)
        defined either as mapping or `ResourceOptions` instance.

    Raises `ValueError` if configuration is not correct.
    """

    if config is None:
        return {}

    if not isinstance(config, collections.abc.Mapping):
        raise ValueError(f"Config must be mapping, got '{config}'")

    parsed = {}

    options_keys = {"allow_credentials", "expose_headers", "allow_headers", "max_age"}

    for origin, options in config.items():
        # TODO: check that all origins are properly formatted.
        # This is not a security issue, since origin is compared as strings.
        if not isinstance(origin, str):
            raise ValueError(f"Origin must be string, got '{origin}'")

        if isinstance(options, ResourceOptions):
            resource_options = options

        else:
            if not isinstance(options, collections.abc.Mapping):
                raise ValueError(
                    "Origin options must be either "
                    "aiohttp_cors.ResourceOptions instance or mapping, "
                    "got '{}'".format(options)
                )

            unexpected_args = frozenset(options.keys()) - options_keys
            if unexpected_args:
                raise ValueError(
                    "Unexpected keywords in resource options: {}".format(
                        # pylint: disable=bad-builtin
                        ",".join(map(str, unexpected_args))
                    )
                )

            resource_options = ResourceOptions(**options)

        parsed[origin] = resource_options

    return parsed


_ConfigType = Mapping[str, Union[ResourceOptions, Mapping[str, Any]]]


class _CorsConfigImpl(_PreflightHandler):

    def __init__(self, app: web.Application, router_adapter: AbstractRouterAdapter):
        self._app = app

        self._router_adapter = router_adapter

        # Register hook for all responses.  This hook handles CORS-related
        # headers on non-preflight requests.
        self._app.on_response_prepare.append(self._on_response_prepare)

    def add(self, routing_entity, config: _ConfigType = None):
        """Enable CORS for specific route or resource.

        If route is passed CORS is enabled for route's resource.

        :param routing_entity:
            Route or Resource for which CORS should be enabled.
        :param config:
            CORS options for the route.
        :return: `routing_entity`.
        """

        parsed_config = _parse_config_options(config)

        self._router_adapter.add_preflight_handler(
            routing_entity, self._preflight_handler
        )
        self._router_adapter.set_config_for_routing_entity(
            routing_entity, parsed_config
        )

        return routing_entity

    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
        and set appropriate CORS response headers.
        """
        if not self._router_adapter.is_cors_enabled_on_request(
            request
        ) or self._router_adapter.is_preflight_request(request):
            # Either not CORS enabled route, or preflight request which is
            # handled in its own handler.
            return

        # Processing response of non-preflight CORS-enabled request.

        config = self._router_adapter.get_non_preflight_request_config(request)

        # Handle according to part 6.1 of the CORS specification.

        origin = request.headers.get(hdrs.ORIGIN)
        if origin is None:
            # Terminate CORS according to CORS 6.1.1.
            return

        options = config.get(origin, config.get("*"))
        if options is None:
            # Terminate CORS according to CORS 6.1.2.
            return

        assert hdrs.ACCESS_CONTROL_ALLOW_ORIGIN not in response.headers
        assert hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS not in response.headers
        assert hdrs.ACCESS_CONTROL_EXPOSE_HEADERS not in response.headers

        # Process according to CORS 6.1.4.
        # Set exposed headers (server headers exposed to client) before
        # setting any other headers.
        if options.expose_headers == "*":
            # Expose all headers that are set in response.
            exposed_headers = (
                frozenset(response.headers.keys()) - _SIMPLE_RESPONSE_HEADERS
            )
            response.headers[hdrs.ACCESS_CONTROL_EXPOSE_HEADERS] = ",".join(
                exposed_headers
            )

        elif options.expose_headers:
            # Expose predefined list of headers.
            response.headers[hdrs.ACCESS_CONTROL_EXPOSE_HEADERS] = ",".join(
                options.expose_headers
            )

        # Process according to CORS 6.1.3.
        # Set allowed origin.
        response.headers[hdrs.ACCESS_CONTROL_ALLOW_ORIGIN] = origin
        if options.allow_credentials:
            # Set allowed credentials.
            response.headers[hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS] = _TRUE

    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:
    """CORS configuration instance.

    The instance holds default CORS parameters and per-route options specified
    in `add()` method.

    Each `aiohttp.web.Application` can have exactly one instance of this class.
    """

    def __init__(
        self,
        app: web.Application,
        *,
        defaults: _ConfigType = None,
        router_adapter: AbstractRouterAdapter = None,
    ):
        """Construct CORS configuration.

        :param app:
            Application for which CORS configuration is built.
        :param defaults:
            Default CORS settings for origins.
        :param router_adapter:
            Router adapter. Required if application uses non-default router.
        """

        self.defaults = _parse_config_options(defaults)

        self._cors_impl = None

        self._resources_router_adapter = None
        self._resources_cors_impl = None

        self._old_routes_cors_impl = None

        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, webview: bool = False):
        """Enable CORS for specific route or resource.

        If route is passed CORS is enabled for route's resource.

        :param routing_entity:
            Route or Resource for which CORS should be enabled.
        :param config:
            CORS options for the route.
        :return: `routing_entity`.
        """

        if webview:
            warnings.warn(
                "webview argument is deprecated, "
                "views are handled authomatically without "
                "extra settings",
                DeprecationWarning,
                stacklevel=2,
            )

        return self._cors_impl.add(routing_entity, config)


================================================
FILE: aiohttp_cors/mixin.py
================================================
import collections

from .preflight_handler import _PreflightHandler


def custom_cors(config):
    def wrapper(function):
        name = f"{function.__name__}_cors_config"
        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 = f"{request_method.lower()}_cors_config"

        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


================================================
FILE: aiohttp_cors/preflight_handler.py
================================================
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


================================================
FILE: aiohttp_cors/py.typed
================================================


================================================
FILE: aiohttp_cors/resource_options.py
================================================
# 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.

"""Resource CORS options class definition."""

import collections
import collections.abc
import numbers

__all__ = ("ResourceOptions",)


def _is_proper_sequence(seq):
    """Returns is seq is sequence and not string."""
    return isinstance(seq, collections.abc.Sequence) and not isinstance(seq, str)


class ResourceOptions(
    collections.namedtuple(
        "Base",
        (
            "allow_credentials",
            "expose_headers",
            "allow_headers",
            "max_age",
            "allow_methods",
        ),
    )
):
    """Resource CORS options."""

    __slots__ = ()

    def __init__(
        self,
        *,
        allow_credentials=False,
        expose_headers=(),
        allow_headers=(),
        max_age=None,
        allow_methods=None
    ):
        """Construct resource CORS options.

        Options will be normalized.

        :param allow_credentials:
            Is passing client credentials to the resource from other origin
            is allowed.
            See <http://www.w3.org/TR/cors/#user-credentials> for
            the definition.
        :type allow_credentials: bool
            Is passing client credentials to the resource from other origin
            is allowed.

        :param expose_headers:
            Server headers that are allowed to be exposed to the client.
            Simple response headers are excluded from this set, see
            <http://www.w3.org/TR/cors/#list-of-exposed-headers>.
        :type expose_headers: sequence of strings or ``*`` string.

        :param allow_headers:
            Client headers that are allowed to be passed to the resource.
            See <http://www.w3.org/TR/cors/#list-of-headers>.
        :type allow_headers: sequence of strings or ``*`` string.

        :param max_age:
            How long the results of a preflight request can be cached in a
            preflight result cache (in seconds).
            See <http://www.w3.org/TR/cors/#http-access-control-max-age>.

        :param allow_methods:
            List of allowed methods or ``*``string. Can be used in resource or
            global defaults, but not in specific route.

            It's not required to specify all allowed methods for specific
            resource, routes that have explicit CORS configuration will be
            treated as if their methods are allowed.
        """
        super().__init__()

    def __new__(
        cls,
        *,
        allow_credentials=False,
        expose_headers=(),
        allow_headers=(),
        max_age=None,
        allow_methods=None
    ):
        """Normalize source parameters and store them in namedtuple."""

        if not isinstance(allow_credentials, bool):
            raise ValueError(
                "'allow_credentials' must be boolean, "
                "got '{!r}'".format(allow_credentials)
            )
        _allow_credentials = allow_credentials

        # `expose_headers` is either "*", or sequence of strings.
        if expose_headers == "*":
            _expose_headers = expose_headers
        elif not _is_proper_sequence(expose_headers):
            raise ValueError(
                "'expose_headers' must be either '*', or sequence of strings, "
                "got '{!r}'".format(expose_headers)
            )
        elif expose_headers:
            # "Access-Control-Expose-Headers" ":" #field-name
            # TODO: Check that headers are valid.
            # TODO: Remove headers that in the _SIMPLE_RESPONSE_HEADERS set
            # according to
            # <http://www.w3.org/TR/cors/#list-of-exposed-headers>.
            _expose_headers = frozenset(expose_headers)
        else:
            # No headers exposed.
            _expose_headers = frozenset()

        # `allow_headers` is either "*", or set of headers in upper case.
        if allow_headers == "*":
            _allow_headers = allow_headers
        elif not _is_proper_sequence(allow_headers):
            raise ValueError(
                "'allow_headers' must be either '*', or sequence of strings, "
                "got '{!r}'".format(allow_headers)
            )
        else:
            # TODO: Check that headers are valid.
            _allow_headers = frozenset(h.upper() for h in allow_headers)

        if max_age is None:
            _max_age = None
        else:
            if not isinstance(max_age, numbers.Integral) or max_age < 0:
                raise ValueError(
                    "'max_age' must be non-negative integer, "
                    "got '{!r}'".format(max_age)
                )
            _max_age = max_age

        if allow_methods is None or allow_methods == "*":
            _allow_methods = allow_methods
        elif not _is_proper_sequence(allow_methods):
            raise ValueError(
                "'allow_methods' must be either '*', or sequence of strings, "
                "got '{!r}'".format(allow_methods)
            )
        else:
            # TODO: Check that methods are valid.
            _allow_methods = frozenset(m.upper() for m in allow_methods)

        return super().__new__(
            cls,
            allow_credentials=_allow_credentials,
            expose_headers=_expose_headers,
            allow_headers=_allow_headers,
            max_age=_max_age,
            allow_methods=_allow_methods,
        )

    def is_method_allowed(self, method):
        if self.allow_methods is None:
            return False

        if self.allow_methods == "*":
            return True

        return method.upper() in self.allow_methods


================================================
FILE: aiohttp_cors/urldispatcher_router_adapter.py
================================================
# 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.

"""AbstractRouterAdapter for aiohttp.web.UrlDispatcher."""
import collections
from typing import Union

from aiohttp import hdrs, web

from .abc import AbstractRouterAdapter
from .mixin import CorsViewMixin

# There several usage patterns of routes which should be handled
# differently.
#
# 1. Using new Resources:
#
#     resource = app.router.add_resource(path)
#     cors.add(resource, resource_defaults=...)
#     cors.add(resource.add_route(method1, handler1), config=...)
#     cors.add(resource.add_route(method2, handler2), config=...)
#     cors.add(resource.add_route(method3, handler3), config=...)
#
# Here all related Routes (i.e. routes with the same path) are in
# a single Resource.
#
# 2. Using `router.add_static()`:
#
#     route1 = app.router.add_static(
#         "/images", "/usr/share/app/images/")
#     cors.add(route1, config=...)
#
# Here old-style `web.StaticRoute` is created and wrapped with
# `web.ResourceAdapter`.
#
# 3. Using old `router.add_route()`:
#
#     cors.add(app.router.add_route(method1, path, hand1), config=...)
#     cors.add(app.router.add_route(method2, path, hand2), config=...)
#     cors.add(app.router.add_route(method3, path, hand3), config=...)
#
# This creates three Resources with single Route in each.
#
# 4. Using deprecated `register_route` with manually created
#    `web.Route`:
#
#     route1 = RouteSubclass(...)
#     app.router.register_route(route1)
#     cors.add(route1, config=...)
#
# Here old-style route is wrapped with `web.ResourceAdapter`.
#
# Preflight requests is roughly an OPTIONS request with query
# "is specific HTTP method is allowed".
# In order to properly handle preflight request we need to know which
# routes have enabled CORS on the request path and CORS configuration
# for requested HTTP method.
#
# In case of new usage pattern it's simple: we need to take a look at
# self._resource_config[resource][method] for the processing resource.
#
# In case of old usage pattern we need to iterate over routes with
# enabled CORS and check is requested path and HTTP method is accepted
# by a route.


class _ResourceConfig:
    def __init__(self, default_config):
        # Resource default config.
        self.default_config = default_config

        # HTTP method to route configuration.
        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.CorsViewMixin for working "
                        "with the library"
                    )
                else:
                    return False
    return webview


class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
    """Adapter for `UrlDispatcher` for Resources-based routing only.

    Should be used with routes added in the following way:

        resource = app.router.add_resource(path)
        cors.add(resource, resource_defaults=...)
        cors.add(resource.add_route(method1, handler1), config=...)
        cors.add(resource.add_route(method2, handler2), config=...)
        cors.add(resource.add_route(method3, handler3), config=...)
    """

    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 Resource to _ResourceConfig.
        self._resource_config = {}

        self._resources_with_preflight_handlers = set()
        self._preflight_routes = set()

    def add_preflight_handler(
        self,
        routing_entity: Union[web.Resource, web.StaticResource, web.ResourceRoute],
        handler,
    ):
        """Add OPTIONS handler for all routes defined by `routing_entity`.

        Does nothing if CORS handler already handles routing entity.
        Should fail if there are conflicting user-defined OPTIONS handlers.
        """

        if isinstance(routing_entity, web.Resource):
            resource = routing_entity

            # Add preflight handler for Resource, if not yet added.

            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)
            self._resources_with_preflight_handlers.add(resource)

        elif isinstance(routing_entity, web.StaticResource):
            resource = routing_entity

            # Add preflight handler for Resource, if not yet added.

            if resource in self._resources_with_preflight_handlers:
                # Preflight handler already added for this resource.
                return

            resource.set_options_route(handler)
            preflight_route = resource._routes[hdrs.METH_OPTIONS]
            self._preflight_routes.add(preflight_route)
            self._resources_with_preflight_handlers.add(resource)

        elif isinstance(routing_entity, web.ResourceRoute):
            route = routing_entity

            if not self.is_cors_for_resource(route.resource):
                self.add_preflight_handler(route.resource, handler)

        else:
            raise ValueError(
                f"Resource or ResourceRoute expected, got {routing_entity!r}"
            )

    def is_cors_for_resource(self, resource: web.Resource) -> bool:
        """Is CORS is configured for the resource"""
        return resource in self._resources_with_preflight_handlers

    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 _request_resource(self, request: web.Request) -> web.Resource:
        return self._request_route(request).resource

    def is_preflight_request(self, request: web.Request) -> bool:
        """Is `request` is a CORS preflight request."""
        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."""

        return self._request_resource(request) in self._resource_config

    def set_config_for_routing_entity(
        self,
        routing_entity: Union[web.Resource, web.StaticResource, web.ResourceRoute],
        config,
    ):
        """Record configuration for resource or it's route."""

        if isinstance(routing_entity, (web.Resource, web.StaticResource)):
            resource = routing_entity

            # Add resource configuration or fail if it's already added.
            if resource in self._resource_config:
                raise ValueError(
                    f"CORS is already configured for {resource!r} resource."
                )

            self._resource_config[resource] = _ResourceConfig(default_config=config)

        elif isinstance(routing_entity, web.ResourceRoute):
            route = routing_entity

            # 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(route)
                )

            resource_config = self._resource_config[route.resource]

            if route.method in resource_config.method_config:
                raise ValueError(
                    "Can't setup CORS for {!r} route: CORS already "
                    "configured on resource {!r} for {} method".format(
                        route, route.resource, route.method
                    )
                )

            resource_config.method_config[route.method] = config

        else:
            raise ValueError(
                f"Resource or ResourceRoute expected, got {routing_entity!r}"
            )

    async def get_preflight_request_config(
        self, preflight_request: web.Request, origin: str, requested_method: str
    ):
        assert self.is_preflight_request(preflight_request)

        resource = self._request_resource(preflight_request)
        resource_config = self._resource_config[resource]
        defaulted_config = collections.ChainMap(
            resource_config.default_config, self._default_config
        )

        options = defaulted_config.get(origin, defaulted_config.get("*"))
        if options is not None and options.is_method_allowed(requested_method):
            # Requested method enabled for CORS in defaults, override it with
            # explicit route configuration (if any).
            route_config = resource_config.method_config.get(requested_method, {})

        else:
            # Requested method is not enabled in defaults.
            # Enable CORS for it only if explicit configuration exists.
            route_config = resource_config.method_config[requested_method]

        defaulted_config = collections.ChainMap(route_config, defaulted_config)

        return defaulted_config

    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)

        resource = self._request_resource(request)
        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(
            method_config, resource_config.default_config, self._default_config
        )

        return defaulted_config


================================================
FILE: install_python_and_pip.ps1
================================================
# Sample script to install Python and pip under Windows
# Authors: Olivier Grisel and Kyle Kastner
# License: BSD 3 clause

# The script is borrowed from Scikit-learn project

$BASE_URL = "https://www.python.org/ftp/python/"
$GET_PIP_URL = "https://bootstrap.pypa.io/get-pip.py"
$GET_PIP_PATH = "C:\get-pip.py"


function DownloadPython ($python_version, $platform_suffix) {
    $webclient = New-Object System.Net.WebClient
    $filename = "python-" + $python_version + $platform_suffix + ".msi"
    $url = $BASE_URL + $python_version + "/" + $filename

    $basedir = $pwd.Path + "\"
    $filepath = $basedir + $filename
    if (Test-Path $filename) {
        Write-Host "Reusing" $filepath
        return $filepath
    }

    # Download and retry up to 3 times in case of network transient errors.
    Write-Host "Downloading" $filename "from" $url
    $retry_attempts = 2
    for($i=0; $i -lt $retry_attempts; $i++){
        try {
            $webclient.DownloadFile($url, $filepath)
            break
        }
        Catch [Exception]{
            Start-Sleep 1
        }
   }
   if (Test-Path $filepath) {
       Write-Host "File saved at" $filepath
   } else {
       # Retry once to get the error message if any at the last try
       $webclient.DownloadFile($url, $filepath)
   }
   return $filepath
}


function InstallPython ($python_version, $architecture, $python_home) {
    Write-Host "Installing Python" $python_version "for" $architecture "bit architecture to" $python_home
    if (Test-Path $python_home) {
        Write-Host $python_home "already exists, skipping."
        return $false
    }
    if ($architecture -eq "32") {
        $platform_suffix = ""
    } else {
        $platform_suffix = ".amd64"
    }
    $msipath = DownloadPython $python_version $platform_suffix
    Write-Host "Installing" $msipath "to" $python_home
    $install_log = $python_home + ".log"
    $install_args = "/qn /log $install_log /i $msipath TARGETDIR=$python_home"
    $uninstall_args = "/qn /x $msipath"
    RunCommand "msiexec.exe" $install_args
    if (-not(Test-Path $python_home)) {
        Write-Host "Python seems to be installed else-where, reinstalling."
        RunCommand "msiexec.exe" $uninstall_args
        RunCommand "msiexec.exe" $install_args
    }
    if (Test-Path $python_home) {
        Write-Host "Python $python_version ($architecture) installation complete"
    } else {
        Write-Host "Failed to install Python in $python_home"
        Get-Content -Path $install_log
        Exit 1
    }
}

function RunCommand ($command, $command_args) {
    Write-Host $command $command_args
    Start-Process -FilePath $command -ArgumentList $command_args -Wait -Passthru
}


function InstallPip ($python_home) {
    $pip_path = $python_home + "\Scripts\pip.exe"
    $python_path = $python_home + "\python.exe"
    if (-not(Test-Path $pip_path)) {
        Write-Host "Installing pip..."
        $webclient = New-Object System.Net.WebClient
        $webclient.DownloadFile($GET_PIP_URL, $GET_PIP_PATH)
        Write-Host "Executing:" $python_path $GET_PIP_PATH
        Start-Process -FilePath "$python_path" -ArgumentList "$GET_PIP_PATH" -Wait -Passthru
    } else {
        Write-Host "pip already installed."
    }
}


function main () {
    InstallPython $env:PYTHON_VERSION $env:PYTHON_ARCH $env:PYTHON
    InstallPip $env:PYTHON
}

main


================================================
FILE: pytest.ini
================================================
[pytest]
filterwarnings=
    error


================================================
FILE: requirements-dev.txt
================================================
-e .
aiohttp==3.13.5
packaging==26.2
pytest==8.4.2
pytest-aiohttp==0.3.0
pytest-cov==7.1.0
selenium==4.34.0
tox==4.30.3
urllib3==2.4.0  # for selenium


================================================
FILE: setup.cfg
================================================
[tool:pytest]
addopts= --cov=aiohttp_cors --cov-report=term --cov-report=html --cov-branch --no-cov-on-fail

[flake8]
extend-select =
  B950,
  # NIC001 -- "Implicitly concatenated str literals on one line"
  NIC001,
  # NIC101 -- "Implicitly concatenated bytes literals on one line"
  NIC101,
# TODO: don't disable D*, fix up issues instead
ignore = N801,N802,N803,NIC002,NIC102,E203,E226,E305,W504,E252,E301,E302,E501,E704,W503,W504,D1,D4
max-line-length = 88
# flake8-requirements
requirements-file = requirements-dev.txt
requirements-max-depth = 4

[isort]
line_length=88
include_trailing_comma=True
multi_line_output=3
force_grid_wrap=0
combine_as_imports=True

known_third_party=pytest,multidict,yarl
known_first_party=aiohttp


================================================
FILE: setup.py
================================================
# 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.

import os
import sys

from setuptools import setup


def read_file(filename):
    abs_path = os.path.join(os.path.dirname(__file__), filename)
    with open(abs_path, encoding="utf-8") as f:
        return f.read()


about = {}
exec(read_file(os.path.join("aiohttp_cors", "__about__.py")), about)

needs_pytest = {"pytest", "test"}.intersection(sys.argv)
pytest_runner = ["pytest_runner"] if needs_pytest else []


setup(
    name=about["__title__"],
    version=about["__version__"],
    author=about["__author__"],
    author_email=about["__email__"],
    description=about["__summary__"],
    url=about["__uri__"],
    long_description="\n\n".join(
        (
            read_file("README.rst"),
            read_file("CHANGES.rst"),
        )
    ),
    long_description_content_type="text/x-rst",
    packages=["aiohttp_cors"],
    setup_requires=[
        # Environment markers were implemented and stabilized in setuptools
        # v20.8.1 (see <http://stackoverflow.com/a/32643122/391865>).
        "setuptools>=20.8.1",
        # If line above doesn't work, check that you have at least
        # setuptools v19.4 (released 2016-01-16):
        # <https://github.com/pypa/setuptools/issues/141>
    ]
    + pytest_runner,
    tests_require=[
        "pytest",
        "pytest-cov",
        "pytest-pylint",
        "selenium",
    ],
    test_suite="tests",
    install_requires=[
        "aiohttp>=3.9",
    ],
    python_requires=">=3.9",
    license=about["__license__"],
    classifiers=[
        "License :: OSI Approved :: Apache Software License",
        "Intended Audience :: Developers",
        "Programming Language :: Python",
        "Programming Language :: Python :: 3",
        "Topic :: Software Development :: Libraries",
        "Topic :: Internet :: WWW/HTTP",
        "Framework :: AsyncIO",
        "Operating System :: MacOS :: MacOS X",
        "Operating System :: Microsoft :: Windows",
        "Operating System :: POSIX",
        "Development Status :: 3 - Alpha",
        "Framework :: aiohttp",
    ],
)


================================================
FILE: tests/__init__.py
================================================


================================================
FILE: tests/doc/__init__.py
================================================


================================================
FILE: tests/doc/test_basic_usage.py
================================================
# 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.

"""Test basic usage."""


async def test_main():
    # This tests corresponds to example from documentation.
    # If you updating it, don't forget to update documentation.

    import aiohttp_cors
    from aiohttp import web

    async def handler(request):
        return web.Response(
            text="Hello!",
            headers={
                "X-Custom-Server-Header": "Custom data",
            },
        )

    app = web.Application()

    # `aiohttp_cors.setup` returns `aiohttp_cors.CorsConfig` instance.
    # The `cors` instance will store CORS configuration for the
    # application.
    cors = aiohttp_cors.setup(app)

    # To enable CORS processing for specific route you need to add
    # that route to the CORS configuration object and specify its
    # CORS options.
    resource = cors.add(app.router.add_resource("/hello"))
    route = cors.add(
        resource.add_route("GET", handler),
        {
            "http://client.example.org": aiohttp_cors.ResourceOptions(
                allow_credentials=True,
                expose_headers=("X-Custom-Server-Header",),
                allow_headers=("X-Requested-With", "Content-Type"),
                max_age=3600,
            )
        },
    )

    assert route is not None


async def test_defaults():
    # This tests corresponds to example from documentation.
    # If you updating it, don't forget to update documentation.

    import aiohttp_cors
    from aiohttp import web

    async def handler(request):
        return web.Response(
            text="Hello!",
            headers={
                "X-Custom-Server-Header": "Custom data",
            },
        )

    handler_post = handler
    handler_put = handler

    app = web.Application()

    # Example:

    cors = aiohttp_cors.setup(
        app,
        defaults={
            # Allow all to read all CORS-enabled resources from
            # http://client.example.org.
            "http://client.example.org": aiohttp_cors.ResourceOptions(),
        },
    )

    # Enable CORS on routes.

    # According to defaults POST and PUT will be available only to
    # "http://client.example.org".
    hello_resource = cors.add(app.router.add_resource("/hello"))
    cors.add(hello_resource.add_route("POST", handler_post))
    cors.add(hello_resource.add_route("PUT", handler_put))

    # In addition to "http://client.example.org", GET request will be
    # allowed from "http://other-client.example.org" origin.
    cors.add(
        hello_resource.add_route("GET", handler),
        {
            "http://other-client.example.org": aiohttp_cors.ResourceOptions(),
        },
    )

    # CORS will be enabled only on the resources added to `CorsConfig`,
    # so following resource will be NOT CORS-enabled.
    app.router.add_route("GET", "/private", handler)


================================================
FILE: tests/integration/__init__.py
================================================


================================================
FILE: tests/integration/test_main.py
================================================
# 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.

"""Test generic usage"""

import pathlib

import pytest

from aiohttp import hdrs, web
from aiohttp_cors import CorsViewMixin, ResourceOptions, setup as _setup

TEST_BODY = "Hello, world"
SERVER_CUSTOM_HEADER_NAME = "X-Server-Custom-Header"
SERVER_CUSTOM_HEADER_VALUE = "some value"


# pylint: disable=unused-argument
async def handler(request: web.Request) -> web.StreamResponse:
    """Dummy request handler, returning `TEST_BODY`."""
    response = web.Response(text=TEST_BODY)

    response.headers[SERVER_CUSTOM_HEADER_NAME] = SERVER_CUSTOM_HEADER_VALUE

    return response


class WebViewHandler(web.View, CorsViewMixin):

    async def get(self) -> web.StreamResponse:
        """Dummy request handler, returning `TEST_BODY`."""
        response = web.Response(text=TEST_BODY)

        response.headers[SERVER_CUSTOM_HEADER_NAME] = SERVER_CUSTOM_HEADER_VALUE

        return response


@pytest.fixture(params=["resource", "view", "route"])
def make_app(request):
    def inner(defaults, route_config):
        app = web.Application()
        cors = _setup(app, defaults=defaults)

        if request.param == "resource":
            resource = cors.add(app.router.add_resource("/resource"))
            cors.add(resource.add_route("GET", handler), route_config)
        elif request.param == "view":
            WebViewHandler.cors_config = route_config
            cors.add(app.router.add_route("*", "/resource", WebViewHandler))
        elif request.param == "route":
            cors.add(app.router.add_route("GET", "/resource", handler), route_config)
        else:
            raise RuntimeError(f"unknown parameter {request.param}")

        return app

    return inner


async def test_message_roundtrip(aiohttp_client):
    """Test that aiohttp server is correctly setup in the base class."""

    app = web.Application()
    app.router.add_route("GET", "/", handler)

    client = await aiohttp_client(app)

    resp = await client.get("/")
    assert resp.status == 200
    data = await resp.text()

    assert data == TEST_BODY


async def test_dummy_setup(aiohttp_server):
    """Test a dummy configuration."""
    app = web.Application()
    _setup(app)

    await aiohttp_server(app)


async def test_dummy_setup_roundtrip(aiohttp_client):
    """Test a dummy configuration with a message round-trip."""
    app = web.Application()
    _setup(app)

    app.router.add_route("GET", "/", handler)

    client = await aiohttp_client(app)

    resp = await client.get("/")
    assert resp.status == 200
    data = await resp.text()

    assert data == TEST_BODY


async def test_dummy_setup_roundtrip_resource(aiohttp_client):
    """Test a dummy configuration with a message round-trip."""
    app = web.Application()
    _setup(app)

    app.router.add_resource("/").add_route("GET", handler)

    client = await aiohttp_client(app)

    resp = await client.get("/")
    assert resp.status == 200
    data = await resp.text()

    assert data == TEST_BODY


async def test_simple_no_origin(aiohttp_client, make_app):
    app = make_app(None, {"http://client1.example.org": ResourceOptions()})

    client = await aiohttp_client(app)

    resp = await client.get("/resource")
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
    }:
        assert header_name not in resp.headers


async def test_simple_allowed_origin(aiohttp_client, make_app):
    app = make_app(None, {"http://client1.example.org": ResourceOptions()})

    client = await aiohttp_client(app)

    resp = await client.get(
        "/resource", headers={hdrs.ORIGIN: "http://client1.example.org"}
    )
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for hdr, val in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN: "http://client1.example.org",
    }.items():
        assert resp.headers.get(hdr) == val

    for header_name in {
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
    }:
        assert header_name not in resp.headers


async def test_simple_not_allowed_origin(aiohttp_client, make_app):
    app = make_app(None, {"http://client1.example.org": ResourceOptions()})

    client = await aiohttp_client(app)

    resp = await client.get(
        "/resource", headers={hdrs.ORIGIN: "http://client2.example.org"}
    )
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
    }:
        assert header_name not in resp.headers


async def test_simple_explicit_port(aiohttp_client, make_app):
    app = make_app(None, {"http://client1.example.org": ResourceOptions()})

    client = await aiohttp_client(app)

    resp = await client.get(
        "/resource", headers={hdrs.ORIGIN: "http://client1.example.org:80"}
    )
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
    }:
        assert header_name not in resp.headers


async def test_simple_different_scheme(aiohttp_client, make_app):
    app = make_app(None, {"http://client1.example.org": ResourceOptions()})

    client = await aiohttp_client(app)

    resp = await client.get(
        "/resource", headers={hdrs.ORIGIN: "https://client1.example.org"}
    )
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
    }:
        assert header_name not in resp.headers


@pytest.fixture(
    params=[
        (None, {"http://client1.example.org": ResourceOptions(allow_credentials=True)}),
        ({"http://client1.example.org": ResourceOptions(allow_credentials=True)}, None),
    ]
)
def app_for_credentials(make_app, request):
    return make_app(*request.param)


async def test_cred_no_origin(aiohttp_client, app_for_credentials):
    app = app_for_credentials

    client = await aiohttp_client(app)

    resp = await client.get("/resource")
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
    }:
        assert header_name not in resp.headers


async def test_cred_allowed_origin(aiohttp_client, app_for_credentials):
    app = app_for_credentials

    client = await aiohttp_client(app)

    resp = await client.get(
        "/resource", headers={hdrs.ORIGIN: "http://client1.example.org"}
    )
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for hdr, val in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN: "http://client1.example.org",
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS: "true",
    }.items():
        assert resp.headers.get(hdr) == val

    for header_name in {
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
    }:
        assert header_name not in resp.headers


async def test_cred_disallowed_origin(aiohttp_client, app_for_credentials):
    app = app_for_credentials

    client = await aiohttp_client(app)

    resp = await client.get(
        "/resource", headers={hdrs.ORIGIN: "http://client2.example.org"}
    )
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
    }:
        assert header_name not in resp.headers


async def test_simple_expose_headers_no_origin(aiohttp_client, make_app):
    app = make_app(
        None,
        {
            "http://client1.example.org": ResourceOptions(
                expose_headers=(SERVER_CUSTOM_HEADER_NAME,)
            )
        },
    )

    client = await aiohttp_client(app)

    resp = await client.get("/resource")
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
    }:
        assert header_name not in resp.headers


async def test_simple_expose_headers_allowed_origin(aiohttp_client, make_app):
    app = make_app(
        None,
        {
            "http://client1.example.org": ResourceOptions(
                expose_headers=(SERVER_CUSTOM_HEADER_NAME,)
            )
        },
    )

    client = await aiohttp_client(app)

    resp = await client.get(
        "/resource", headers={hdrs.ORIGIN: "http://client1.example.org"}
    )
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for hdr, val in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN: "http://client1.example.org",
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS: SERVER_CUSTOM_HEADER_NAME,
    }.items():
        assert resp.headers.get(hdr) == val

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
    }:
        assert header_name not in resp.headers


async def test_simple_expose_headers_not_allowed_origin(aiohttp_client, make_app):
    app = make_app(
        None,
        {
            "http://client1.example.org": ResourceOptions(
                expose_headers=(SERVER_CUSTOM_HEADER_NAME,)
            )
        },
    )

    client = await aiohttp_client(app)

    resp = await client.get(
        "/resource", headers={hdrs.ORIGIN: "http://client2.example.org"}
    )
    assert resp.status == 200
    resp_text = await resp.text()
    assert resp_text == TEST_BODY

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
    }:
        assert header_name not in resp.headers


async def test_preflight_default_no_origin(aiohttp_client, make_app):
    app = make_app(None, {"http://client1.example.org": ResourceOptions()})

    client = await aiohttp_client(app)

    resp = await client.options("/resource")
    assert resp.status == 403
    resp_text = await resp.text()
    assert "origin header is not specified" in resp_text

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
        hdrs.ACCESS_CONTROL_MAX_AGE,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_METHODS,
        hdrs.ACCESS_CONTROL_ALLOW_HEADERS,
    }:
        assert header_name not in resp.headers


async def test_preflight_default_no_method(aiohttp_client, make_app):

    app = make_app(None, {"http://client1.example.org": ResourceOptions()})

    client = await aiohttp_client(app)

    resp = await client.options(
        "/resource",
        headers={
            hdrs.ORIGIN: "http://client1.example.org",
        },
    )
    assert resp.status == 403
    resp_text = await resp.text()
    assert "'Access-Control-Request-Method' header is not specified" in resp_text

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
        hdrs.ACCESS_CONTROL_MAX_AGE,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_METHODS,
        hdrs.ACCESS_CONTROL_ALLOW_HEADERS,
    }:
        assert header_name not in resp.headers


async def test_preflight_default_origin_and_method(aiohttp_client, make_app):

    app = make_app(None, {"http://client1.example.org": ResourceOptions()})

    client = await aiohttp_client(app)

    resp = await client.options(
        "/resource",
        headers={
            hdrs.ORIGIN: "http://client1.example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "GET",
        },
    )
    assert resp.status == 200
    resp_text = await resp.text()
    assert "" == resp_text

    for hdr, val in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN: "http://client1.example.org",
        hdrs.ACCESS_CONTROL_ALLOW_METHODS: "GET",
    }.items():
        assert resp.headers.get(hdr) == val

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
        hdrs.ACCESS_CONTROL_MAX_AGE,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_HEADERS,
    }:
        assert header_name not in resp.headers


async def test_preflight_default_disallowed_origin(aiohttp_client, make_app):

    app = make_app(None, {"http://client1.example.org": ResourceOptions()})

    client = await aiohttp_client(app)

    resp = await client.options(
        "/resource",
        headers={
            hdrs.ORIGIN: "http://client2.example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "GET",
        },
    )
    assert resp.status == 403
    resp_text = await resp.text()
    assert "origin 'http://client2.example.org' is not allowed" in resp_text

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
        hdrs.ACCESS_CONTROL_MAX_AGE,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_METHODS,
        hdrs.ACCESS_CONTROL_ALLOW_HEADERS,
    }:
        assert header_name not in resp.headers


async def test_preflight_default_disallowed_method(aiohttp_client, make_app):

    app = make_app(None, {"http://client1.example.org": ResourceOptions()})

    client = await aiohttp_client(app)

    resp = await client.options(
        "/resource",
        headers={
            hdrs.ORIGIN: "http://client1.example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "POST",
        },
    )
    assert resp.status == 403
    resp_text = await resp.text()
    assert (
        "request method 'POST' is not allowed for "
        "'http://client1.example.org' origin" in resp_text
    )

    for header_name in {
        hdrs.ACCESS_CONTROL_ALLOW_ORIGIN,
        hdrs.ACCESS_CONTROL_ALLOW_CREDENTIALS,
        hdrs.ACCESS_CONTROL_MAX_AGE,
        hdrs.ACCESS_CONTROL_EXPOSE_HEADERS,
        hdrs.ACCESS_CONTROL_ALLOW_METHODS,
        hdrs.ACCESS_CONTROL_ALLOW_HEADERS,
    }:
        assert header_name not in resp.headers


async def test_preflight_req_multiple_routes_with_one_options(aiohttp_client):
    """Test CORS preflight handling on resource that is available through
    several routes.
    """
    app = web.Application()
    cors = _setup(
        app,
        defaults={
            "*": ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers="*",
            )
        },
    )

    cors.add(app.router.add_route("GET", "/{name}", handler))
    cors.add(app.router.add_route("PUT", "/{name}", handler))

    client = await aiohttp_client(app)

    resp = await client.options(
        "/user",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
        },
    )
    assert resp.status == 200

    data = await resp.text()
    assert data == ""


async def test_preflight_request_mult_routes_with_one_options_resource(aiohttp_client):
    """Test CORS preflight handling on resource that is available through
    several routes.
    """
    app = web.Application()
    cors = _setup(
        app,
        defaults={
            "*": ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers="*",
            )
        },
    )

    resource = cors.add(app.router.add_resource("/{name}"))
    cors.add(resource.add_route("GET", handler))
    cors.add(resource.add_route("PUT", handler))

    client = await aiohttp_client(app)

    resp = await client.options(
        "/user",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
        },
    )
    assert resp.status == 200

    data = await resp.text()
    assert data == ""


async def test_preflight_request_max_age_resource(aiohttp_client):
    """Test CORS preflight handling on resource that is available through
    several routes.
    """
    app = web.Application()
    cors = _setup(
        app,
        defaults={
            "*": ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers="*",
                max_age=1200,
            )
        },
    )

    resource = cors.add(app.router.add_resource("/{name}"))
    cors.add(resource.add_route("GET", handler))

    client = await aiohttp_client(app)

    resp = await client.options(
        "/user",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "GET",
        },
    )
    assert resp.status == 200
    assert resp.headers[hdrs.ACCESS_CONTROL_MAX_AGE].upper() == "1200"

    data = await resp.text()
    assert data == ""


async def test_preflight_request_max_age_webview(aiohttp_client):
    """Test CORS preflight handling on resource that is available through
    several routes.
    """
    app = web.Application()
    cors = _setup(
        app,
        defaults={
            "*": ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers="*",
                max_age=1200,
            )
        },
    )

    class TestView(web.View, CorsViewMixin):
        async def get(self):
            resp = web.Response(text=TEST_BODY)

            resp.headers[SERVER_CUSTOM_HEADER_NAME] = SERVER_CUSTOM_HEADER_VALUE

            return resp

    cors.add(app.router.add_route("*", "/{name}", TestView))

    client = await aiohttp_client(app)

    resp = await client.options(
        "/user",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "GET",
        },
    )
    assert resp.status == 200
    assert resp.headers[hdrs.ACCESS_CONTROL_MAX_AGE].upper() == "1200"

    data = await resp.text()
    assert data == ""


async def test_preflight_request_mult_routes_with_one_options_webview(aiohttp_client):
    """Test CORS preflight handling on resource that is available through
    several routes.
    """
    app = web.Application()
    cors = _setup(
        app,
        defaults={
            "*": ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers="*",
            )
        },
    )

    class TestView(web.View, CorsViewMixin):
        async def get(self):
            resp = web.Response(text=TEST_BODY)

            resp.headers[SERVER_CUSTOM_HEADER_NAME] = SERVER_CUSTOM_HEADER_VALUE

            return resp

        put = get

    cors.add(app.router.add_route("*", "/{name}", TestView))

    client = await aiohttp_client(app)

    resp = await client.options(
        "/user",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
        },
    )
    assert resp.status == 200

    data = await resp.text()
    assert data == ""


async def test_preflight_request_headers_webview(aiohttp_client):
    """Test CORS preflight request handlers handling."""
    app = web.Application()
    cors = _setup(
        app,
        defaults={
            "*": ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers=("Content-Type", "X-Header"),
            )
        },
    )

    class TestView(web.View, CorsViewMixin):
        async def put(self):
            response = web.Response(text=TEST_BODY)

            response.headers[SERVER_CUSTOM_HEADER_NAME] = SERVER_CUSTOM_HEADER_VALUE

            return response

    cors.add(app.router.add_route("*", "/", TestView))

    client = await aiohttp_client(app)

    resp = await client.options(
        "/",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
            hdrs.ACCESS_CONTROL_REQUEST_HEADERS: "content-type",
        },
    )
    assert (await resp.text()) == ""
    assert resp.status == 200
    # Access-Control-Allow-Headers must be compared in case-insensitive
    # way.
    assert (
        resp.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS].upper()
        == "content-type".upper()
    )

    resp = await client.options(
        "/",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
            hdrs.ACCESS_CONTROL_REQUEST_HEADERS: "X-Header,content-type",
        },
    )
    assert resp.status == 200
    # Access-Control-Allow-Headers must be compared in case-insensitive
    # way.
    assert frozenset(
        resp.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS].upper().split(",")
    ) == {"X-Header".upper(), "content-type".upper()}
    assert (await resp.text()) == ""

    resp = await client.options(
        "/",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
            hdrs.ACCESS_CONTROL_REQUEST_HEADERS: "content-type,Test",
        },
    )
    assert resp.status == 403
    assert hdrs.ACCESS_CONTROL_ALLOW_HEADERS not in resp.headers
    assert "headers are not allowed: TEST" in (await resp.text())


async def test_preflight_request_headers_resource(aiohttp_client):
    """Test CORS preflight request handlers handling."""
    app = web.Application()
    cors = _setup(
        app,
        defaults={
            "*": ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers=("Content-Type", "X-Header"),
            )
        },
    )

    cors.add(app.router.add_route("PUT", "/", handler))

    client = await aiohttp_client(app)

    resp = await client.options(
        "/",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
            hdrs.ACCESS_CONTROL_REQUEST_HEADERS: "content-type",
        },
    )
    assert (await resp.text()) == ""
    assert resp.status == 200
    # Access-Control-Allow-Headers must be compared in case-insensitive
    # way.
    assert (
        resp.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS].upper()
        == "content-type".upper()
    )

    resp = await client.options(
        "/",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
            hdrs.ACCESS_CONTROL_REQUEST_HEADERS: "X-Header,content-type",
        },
    )
    assert resp.status == 200
    # Access-Control-Allow-Headers must be compared in case-insensitive
    # way.
    assert frozenset(
        resp.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS].upper().split(",")
    ) == {"X-Header".upper(), "content-type".upper()}
    assert (await resp.text()) == ""

    resp = await client.options(
        "/",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
            hdrs.ACCESS_CONTROL_REQUEST_HEADERS: "content-type,Test",
        },
    )
    assert resp.status == 403
    assert hdrs.ACCESS_CONTROL_ALLOW_HEADERS not in resp.headers
    assert "headers are not allowed: TEST" in (await resp.text())


async def test_preflight_request_headers(aiohttp_client):
    """Test CORS preflight request handlers handling."""
    app = web.Application()
    cors = _setup(
        app,
        defaults={
            "*": ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_headers=("Content-Type", "X-Header"),
            )
        },
    )

    resource = cors.add(app.router.add_resource("/"))
    cors.add(resource.add_route("PUT", handler))

    client = await aiohttp_client(app)

    resp = await client.options(
        "/",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
            hdrs.ACCESS_CONTROL_REQUEST_HEADERS: "content-type",
        },
    )
    assert (await resp.text()) == ""
    assert resp.status == 200
    # Access-Control-Allow-Headers must be compared in case-insensitive
    # way.
    assert (
        resp.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS].upper()
        == "content-type".upper()
    )

    resp = await client.options(
        "/",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
            hdrs.ACCESS_CONTROL_REQUEST_HEADERS: "X-Header,content-type",
        },
    )
    assert resp.status == 200
    # Access-Control-Allow-Headers must be compared in case-insensitive
    # way.
    assert frozenset(
        resp.headers[hdrs.ACCESS_CONTROL_ALLOW_HEADERS].upper().split(",")
    ) == {"X-Header".upper(), "content-type".upper()}
    assert (await resp.text()) == ""

    resp = await client.options(
        "/",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "PUT",
            hdrs.ACCESS_CONTROL_REQUEST_HEADERS: "content-type,Test",
        },
    )
    assert resp.status == 403
    assert hdrs.ACCESS_CONTROL_ALLOW_HEADERS not in resp.headers
    assert "headers are not allowed: TEST" in (await resp.text())


async def test_static_route(aiohttp_client):
    """Test a static route with CORS."""
    app = web.Application()
    cors = _setup(
        app,
        defaults={
            "*": ResourceOptions(
                allow_credentials=True,
                expose_headers="*",
                allow_methods="*",
                allow_headers=("Content-Type", "X-Header"),
            )
        },
    )

    test_static_path = pathlib.Path(__file__).parent
    cors.add(app.router.add_static("/static", test_static_path, name="static"))

    client = await aiohttp_client(app)

    resp = await client.options(
        "/static/test_page.html",
        headers={
            hdrs.ORIGIN: "http://example.org",
            hdrs.ACCESS_CONTROL_REQUEST_METHOD: "OPTIONS",
            hdrs.ACCESS_CONTROL_REQUEST_HEADERS: "content-type",
        },
    )
    data = await resp.text()
    assert resp.status == 200
    assert data == ""


# TODO: test requesting resources with not configured CORS.
# TODO: test wildcard origin in default config.
# TODO: test different combinations of ResourceOptions options.
# TODO: remove deplication of resource/not resource configuration using
# pytest's fixtures.


================================================
FILE: tests/integration/test_page.html
================================================
<!DOCTYPE html>
<html lang="en-US">
<head>
  <meta charset="utf-8">
  <title>aiohttp_cors testing</title>
  <style>
    body {
      width: 900px;
      margin: 0 auto;
    }

    textarea.results {
      width: 100%;
      height: 400px;
    }

    .serverAddressLabel {
      display: block;
      float: left;
      width: 400px;
    }

    .templates {
      display: none;
    }
  </style>
</head>
<body>
  <div class="templates">
    <div id="serverAddressTemplate">
      <label>
        <span id="label" class="serverAddressLabel"></span>
        <input id="input" type="text">
      </label>
    </div>
  </div>

  <form name="test" id="testForm">
    <div>
      <input id="fillOutButton" value="Fill out addresses" type="button">
      <input id="runTestsButton" value="Run tests" type="submit" disabled>
      <input id="clearResultsButton" value="Clear results" type="button" disabled>
    </div>
  </form>
  <div id="log" class="log">
  </div>
  <textarea id="results" class="results">
  </textarea>

<script>
(window.cors = (function() {
  'strict';

  // Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>
  // Licensed under the Apache License, Version 2.0.

  var serversNames = [
    'origin', 'allowing', 'denying', 'free_for_all', 'no_cors'
  ];

  var tests = {
    'GET no_cors.json': {
      // Resource without CORS configuration can be obtained only from origin.
      method: 'GET',
      path: 'no_cors.json',
      allowed: {
        'origin': true,
        'allowing': false,
        'denying': false,
        'free_for_all': false,
        'no_cors': false
      }
    },
    'GET cors_resource default': {
      // Resource with CORS configuration can be obtained only according to
      // CORS configuration.
      method: 'GET',
      path: 'cors_resource',
      allowed: {
        'origin': true,
        'allowing': true,
        'denying': false,
        'free_for_all': true,
        'no_cors': false
      }
    },
    'GET cors_resource with credentials': {
      // Should be same as without credentials on this route.
      method: 'GET',
      path: 'cors_resource',
      withCredentials: true,
      allowed: {
        'origin': true,
        'allowing': true,
        'denying': false,
        'free_for_all': true,
        'no_cors': false
      }
    }
  };

  function logMessage(level, args) {
    console[level].apply(console, args);

    var logContainer = document.getElementById('log');

    var logEntry = document.createElement('div');
    logEntry.classList.add(level);

    var i;
    for (i = 0; i !== args.length; ++i) {
      var partData = args[i];
      var part = document.createElement('span');
      var txt = document.createTextNode(partData + '');
      part.appendChild(txt);
      logEntry.appendChild(part);
    }

    logContainer.appendChild(logEntry);
  }

  function log() {
    logMessage('log', arguments);
  }

  function error() {
    logMessage('error', arguments);
  }

  // Based on an example from <http://www.html5rocks.com/en/tutorials/cors/>.
  function createCORSRequest(method, url) {
    var xhr = new XMLHttpRequest();
    if ('withCredentials' in xhr) {
      // Check if the XMLHttpRequest object has a 'withCredentials' property.
      // 'withCredentials' only exists on XMLHTTPRequest2 objects.
      xhr.open(method, url, true);

    } else if (typeof XDomainRequest !== 'undefined') {
      // Otherwise, check if XDomainRequest.
      // XDomainRequest only exists in IE, and is IE's way of making CORS
      // requests.
      xhr = new XDomainRequest();
      xhr.open(method, url);

    } else {
      // Otherwise, CORS is not supported by the browser.
      xhr = null;
    }
    return xhr;
  }

  function runTest(testName, resourceUrl, testData, expected, onTestDone) {
    var xhr = createCORSRequest(testData.method, resourceUrl);

    function done(isResourceObtained) {
      var result = {};

      var testSucceed = (isResourceObtained === expected);
      result.status = (testSucceed ? 'success' : 'fail');

      result.data = {
        responseType: xhr.responseType,
        response: xhr.response,
        status: xhr.status,
        responseHeaders: xhr.getAllResponseHeaders()
      };

      if (!testSucceed) {
        error('Test ' + testName + ' failed', result.data);
      }

      onTestDone(result);
    }

    if (testData.withCredentials) {
      xhr.withCredentials = true;
    }

    xhr.onload = function() {
      if (xhr.status === 200) {
        done(true);
      } else {
        done(false);
      }
    };
    xhr.onerror = function() {
      done(false);
    };

    xhr.send();
  }

  function onTestDone(state, testName, allDoneCallback, result) {
    state.allResults[testName] = result;
    --state.remainingTests;

    if (state.remainingTests === 0) {
      allDoneCallback(state.allResults);
    }
  }

  function countTestsNumber(tests) {
    var numTests = 0;

    var testName;
    for (testName in tests) {
      var test = tests[testName];

      var serverName;
      for (serverName in test.allowed) {
        ++numTests;
      }
    }

    return numTests;
  }

  function runAllTests(serversURLs, tests, doneCallback) {
    var state = {
      allResults: {},
      remainingTests: 0
    };

    state.remainingTests = countTestsNumber(tests);

    var testName;
    for (testName in tests) {
      var testData = tests[testName];

      var serverName;
      for (serverName in testData.allowed) {
        var expected = testData.allowed[serverName];
        var serverUrl = serversURLs[serverName];
        console.assert(serverUrl);
        var resourceUrl = serverUrl + testData.path;

        var subTestName = testName + '[' + serverName + ']';

        runTest(subTestName, resourceUrl, testData, expected,
          onTestDone.bind(null, state, subTestName, doneCallback));
      }
    }
  }

  function setResults(object) {
    var results = document.getElementById('results');
    results.value = JSON.stringify(object, null, ' ');
  }

  function setExceptionAsResult(ex) {
    error(ex);

    setResults({
      'status': 'error',
      'error': ex.toString(),
      'data': {
        message: ex.message,
        // Firefox-specific
        fileName: ex.fileName,
        lineNumber: ex.lineNumber,
        columnNumber: ex.columnNumber,
        stack: ex.stack,
        // IE-specific
        description: ex.description
      }
    });
  }

  function serverInputId(serverName) {
    return 'server_' + serverName;
  }

  function formatTemplate(node, serverName) {
    if (node.id === 'label') {
      node.innerHTML = 'address of "' + serverName + '" server:';
      node.removeAttribute('id');
    }

    if (node.id === 'input') {
      node.id = serverInputId(serverName);
    }

    var i;
    for (i = 0; i !== node.children.length; ++i) {
      formatTemplate(node.children[i], serverName);
    }
  }

  function createAddressInputFields() {
    var template = document.getElementById('serverAddressTemplate');
    var testForm = document.getElementById('testForm');

    var i;
    for (i = 0; i !== serversNames.length; ++i) {
      var serverName = serversNames[i];

      var node = template.cloneNode(true);
      node.removeAttribute('id');

      formatTemplate(node, serverName);

      testForm.insertBefore(node, testForm.firstChild);
    }
  }

  function setServerAddr(serverName, value) {
    var input = document.getElementById(serverInputId(serverName));
    input.value = value;
  }

  function getServerAddrOptional(serverName) {
    var input = document.getElementById(serverInputId(serverName));
    var addr = input.value;
    if (!addr) {
      return null;
    }

    if (addr[addr.length - 1] !== '/') {
      addr += '/';
    }

    return addr;
  }

  function getServerAddr(serverName) {
    var addr = getServerAddrOptional(serverName);

    if (!addr) {
      throw new Error(
        'Server address for "' + serverName + '" is not specified');
    }

    return addr;
  }

  function getServersUrls() {
    var serverUrls = {};

    var i;
    for (i = 0; i !== serversNames.length; ++i) {
      var serverName = serversNames[i];
      serverUrls[serverName] = getServerAddr(serverName);
    }

    return serverUrls;
  }

  function setServersUrls(serverUrls) {
    var serverName;
    for (serverName in serverUrls) {
      setServerAddr(serverName, serverUrls[serverName]);
    }
  }

  function runTests() {
    setTestingStarted();

    var results = document.getElementById('results');

    try {
      var serversURLs = getServersUrls();
      results = runAllTests(serversURLs, tests, function(results) {
        setResults({
          'status': 'success',
          'data': results
        });

        var numFailed = 0;
        var numTests = 0;
        var failedTests = {};
        var testName;
        for (testName in results) {
          ++numTests;
          if (results[testName].status !== 'success') {
            ++numFailed;
            failedTests[testName] = results[testName];
          }
        }

        if (numFailed > 0) {
          error(
            'Failed ' + numFailed + ' tests of ' + numTests, failedTests);
        } else {
          log('All ' + numTests + ' tests passed');
        }

        setTestingFinished();
      });
    } catch(ex) {
      setExceptionAsResult(ex);
    }
  }

  function fillOutAddresses(callback) {
    var originAddr = getServerAddrOptional('origin');
    if (!originAddr) {
      originAddr = '/';
    }
    var addressesUrl = originAddr + 'servers_addresses';
    var xhr = createCORSRequest('GET', addressesUrl);

    xhr.onload = function() {
      if (xhr.status !== 200) {
        error(
          '/servers_addresses request failed with status ' + xhr.status,
          xhr.responseText);

      } else {
        log('Received server addresses:', xhr.response);
        setServersUrls(JSON.parse(xhr.responseText));
      }

      if (typeof callback !== 'undefined') {
        callback();
      }
    };

    xhr.onerror = function() {
      error('/servers_addresses request failed');
    };

    xhr.send();
  }

  function setReadyToStartTesting() {
    var runButton = document.getElementById('runTestsButton');
    runButton.disabled = false;
  }

  function setTestingStarted() {
    var runButton = document.getElementById('runTestsButton');
    var clearButton = document.getElementById('clearResultsButton');
    runButton.disabled = true;
    clearButton.disabled = true;
  }

  function setTestingFinished() {
    var runButton = document.getElementById('runTestsButton');
    var clearButton = document.getElementById('clearResultsButton');
    runButton.disabled = false;
    clearButton.disabled = false;
  }

  return {
    runTests: runTests,
    fillOutAddresses: fillOutAddresses,
    createAddressInputFields: createAddressInputFields,
    setReadyToStartTesting: setReadyToStartTesting
  };
}()));

(function() {
  var cors = window.cors;

  cors.createAddressInputFields();

  var testForm = document.getElementById('testForm');
  testForm.onsubmit = function() {
    cors.runTests();
    return false;
  };

  var fillOutButton = document.getElementById('fillOutButton');
  fillOutButton.onclick = function() {
    cors.fillOutAddresses();
    return false;
  };

  cors.fillOutAddresses(cors.setReadyToStartTesting);
}());
</script>
</body>
</html>


================================================
FILE: tests/integration/test_real_browser.py
================================================
# 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.

"""System test using real browser."""

import asyncio
import json
import logging
import os
import pathlib
import socket
import webbrowser

import pytest
import selenium.common.exceptions
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from aiohttp import hdrs, web
from aiohttp_cors import CorsViewMixin, ResourceOptions, setup as _setup


class _ServerDescr:
    """Auxiliary class for storing server info"""

    def __init__(self):
        self.app = None
        self.cors = None
        self.handler = None
        self.server = None
        self.url = None


class IntegrationServers:
    """Integration servers starting/stopping manager"""

    def __init__(self, use_resources, use_webview, *, loop=None):
        self.servers = {}

        self.loop = loop
        if self.loop is None:
            self.loop = asyncio.get_event_loop()

        self.use_resources = use_resources
        self.use_webview = use_webview

        self._logger = logging.getLogger("IntegrationServers")

    @property
    def origin_server_url(self):
        return self.servers["origin"].url

    async def start_servers(self):
        test_page_path = pathlib.Path(__file__).with_name("test_page.html")

        async def handle_test_page(request: web.Request) -> web.StreamResponse:
            with test_page_path.open("r", encoding="utf-8") as f:
                return web.Response(
                    text=f.read(), headers={hdrs.CONTENT_TYPE: "text/html"}
                )

        async def handle_no_cors(request: web.Request) -> web.StreamResponse:
            return web.Response(
                text="""{"type": "no_cors.json"}""",
                headers={hdrs.CONTENT_TYPE: "application/json"},
            )

        async def handle_resource(request: web.Request) -> web.StreamResponse:
            return web.Response(
                text="""{"type": "resource"}""",
                headers={hdrs.CONTENT_TYPE: "application/json"},
            )

        async def handle_servers_addresses(request: web.Request) -> web.StreamResponse:
            servers_addresses = {
                name: descr.url for name, descr in self.servers.items()
            }
            return web.Response(text=json.dumps(servers_addresses))

        class ResourceView(web.View, CorsViewMixin):

            async def get(self) -> web.StreamResponse:
                return await handle_resource(self.request)

        # For most resources:
        # "origin" server has no CORS configuration.
        # "allowing" server explicitly allows CORS requests to "origin" server.
        # "denying" server explicitly disallows CORS requests to "origin"
        # server.
        # "free_for_all" server allows CORS requests for all origins server.
        # "no_cors" server has no CORS configuration.
        cors_server_names = ["allowing", "denying", "free_for_all"]
        server_names = cors_server_names + ["origin", "no_cors"]

        for server_name in server_names:
            assert server_name not in self.servers
            self.servers[server_name] = _ServerDescr()

        server_sockets = {}

        # Create applications and sockets.
        for server_name, server_descr in self.servers.items():
            server_descr.app = web.Application()

            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.bind(("127.0.0.1", 0))
            sock.listen(10)
            server_sockets[server_name] = sock

            hostaddr, port = sock.getsockname()
            server_descr.url = f"http://{hostaddr}:{port}"

        # Server test page from origin server.
        self.servers["origin"].app.router.add_route("GET", "/", handle_test_page)
        self.servers["origin"].app.router.add_route(
            "GET", "/servers_addresses", handle_servers_addresses
        )

        # Add routes to all servers.
        for server_name in server_names:
            app = self.servers[server_name].app
            app.router.add_route("GET", "/no_cors.json", handle_no_cors)
            if self.use_webview:
                app.router.add_route(
                    "*", "/cors_resource", ResourceView, name="cors_resource"
                )
            else:
                app.router.add_route(
                    "GET", "/cors_resource", handle_resource, name="cors_resource"
                )

        cors_default_configs = {
            "allowing": {
                self.servers["origin"].url: ResourceOptions(
                    allow_credentials=True, expose_headers="*", allow_headers="*"
                )
            },
            "denying": {
                # Allow requests to other than "origin" server.
                self.servers["allowing"].url: ResourceOptions(
                    allow_credentials=True, expose_headers="*", allow_headers="*"
                )
            },
            "free_for_all": {
                "*": ResourceOptions(
                    allow_credentials=True, expose_headers="*", allow_headers="*"
                )
            },
        }

        # Configure CORS.
        for server_name, server_descr in self.servers.items():
            default_config = cors_default_configs.get(server_name)
            if default_config is None:
                continue
            server_descr.cors = _setup(server_descr.app, defaults=default_config)

        # Add CORS routes.
        for server_name in cors_server_names:
            server_descr = self.servers[server_name]
            # TODO: Starting from aiohttp 0.21.0 name-based access returns
            # Resource, not Route. Manually get route while aiohttp_cors
            # doesn't support configuring for Resources.
            resource = server_descr.app.router["cors_resource"]
            route = next(iter(resource))
            if self.use_resources:
                server_descr.cors.add(resource)
                server_descr.cors.add(route)

            elif self.use_webview:
                server_descr.cors.add(route)

            else:
                server_descr.cors.add(route)

        # Start servers.
        for server_name, server_descr in self.servers.items():
            runner = web.AppRunner(server_descr.app)
            await runner.setup()
            site = web.SockSite(runner, server_sockets[server_name])
            await site.start()
            server_descr.runner = runner

            self._logger.info(
                "Started server '%s' at '%s'", server_name, server_descr.url
            )

    async def stop_servers(self):
        for server_descr in self.servers.values():
            runner = server_descr.runner
            await runner.shutdown()
            await runner.cleanup()

        self.servers = {}


def _get_chrome_driver():
    driver_path_env = "WEBDRIVER_CHROMEDRIVER_PATH"

    if driver_path_env in os.environ:
        driver = webdriver.Chrome(executable_path=os.environ[driver_path_env])
    else:
        driver = webdriver.Chrome()

    return driver


@pytest.fixture(params=[(False, False), (True, False), (False, True)])
def server(request, loop):
    async def inner():
        # to grab implicit loop
        return IntegrationServers(*request.param)

    return loop.run_until_complete(inner())


@pytest.fixture(params=[webdriver.Firefox, _get_chrome_driver])
def driver(request):
    try:
        driver = request.param()
    except selenium.common.exceptions.WebDriverException:
        pytest.skip("Driver is not supported")

    yield driver
    driver.close()


async def test_in_webdriver(driver, server):
    loop = asyncio.get_event_loop()
    await server.start_servers()

    def selenium_thread():
        driver.get(server.origin_server_url)
        assert "aiohttp_cors" in driver.title

        wait = WebDriverWait(driver, 10)

        run_button = wait.until(EC.element_to_be_clickable((By.ID, "runTestsButton")))

        # Start tests.
        run_button.send_keys(Keys.RETURN)

        # Wait while test will finish (until clear button is not
        # activated).
        wait.until(EC.element_to_be_clickable((By.ID, "clearResultsButton")))

        # Get results json
        results_area = driver.find_element(By.ID, "results")

        return json.loads(results_area.get_attribute("value"))

    try:
        results = await loop.run_in_executor(None, selenium_thread)

        assert results["status"] == "success"
        for test_name, test_data in results["data"].items():
            assert test_data["status"] == "success"

    finally:
        await server.stop_servers()


def _run_integration_server():
    """Runs integration server for interactive debugging."""

    logging.basicConfig(level=logging.INFO)

    logger = logging.getLogger("run_integration_server")

    loop = asyncio.get_event_loop()

    servers = IntegrationServers(False, True)
    logger.info("Starting integration servers...")
    loop.run_until_complete(servers.start_servers())

    try:
        webbrowser.open(servers.origin_server_url)
    except webbrowser.Error:
        pass

    try:
        loop.run_forever()
    except KeyboardInterrupt:
        pass
    finally:
        logger.info("Stopping integration servers...")
        loop.run_until_complete(servers.stop_servers())


if __name__ == "__main__":
    # This module can be run in the following way:
    #     $ python -m tests.integration.test_real_browser
    # from aiohttp_cors root directory.
    _run_integration_server()


================================================
FILE: tests/unit/__init__.py
================================================


================================================
FILE: tests/unit/test___about__.py
================================================
# 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.

"""Test aiohttp_cors package metainformation."""

from packaging.version import parse

import aiohttp_cors


def test_version():
    """Test package version string"""
    # not raised
    parse(aiohttp_cors.__version__)


================================================
FILE: tests/unit/test_cors_config.py
================================================
# 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.cors_config unit tests."""

import pytest

from aiohttp import web
from aiohttp_cors import CorsConfig, CorsViewMixin, ResourceOptions


async def _handler(request):
    return web.Response(text="Done")


class _View(web.View, CorsViewMixin):

    async def get(self):
        return web.Response(text="Done")


@pytest.fixture
def app():
    return web.Application()


@pytest.fixture
def cors(app):
    return CorsConfig(app, defaults={"*": ResourceOptions()})


@pytest.fixture
def get_route(app):
    return app.router.add_route("GET", "/get_path", _handler)


@pytest.fixture
def options_route(app):
    return app.router.add_route("OPTIONS", "/options_path", _handler)


def test_add_options_route(app, cors, options_route):
    """Test configuring OPTIONS route"""
    with pytest.raises(ValueError, match="already has OPTIONS handler"):
        cors.add(options_route.resource)


def test_plain_named_route(app, cors):
    """Test adding plain named route."""
    # Adding CORS routes should not introduce new named routes.
    assert len(app.router.keys()) == 0
    route = app.router.add_route("GET", "/{name}", _handler, name="dynamic_named_route")
    assert len(app.router.keys()) == 1
    cors.add(route)
    assert len(app.router.keys()) == 1


def test_dynamic_named_route(app, cors):
    """Test adding dynamic named route."""
    assert len(app.router.keys()) == 0
    route = app.router.add_route("GET", "/{name}", _handler, name="dynamic_named_route")
    assert len(app.router.keys()) == 1
    cors.add(route)
    assert len(app.router.keys()) == 1


def test_static_named_route(app, cors):
    """Test adding dynamic named route."""
    assert len(app.router.keys()) == 0
    route = app.router.add_static("/file", "/", name="dynamic_named_route")
    assert len(app.router.keys()) == 1
    cors.add(route)
    assert len(app.router.keys()) == 1


def test_static_resource(app, cors):
    """Test adding static resource."""
    assert len(app.router.keys()) == 0
    app.router.add_static("/file", "/", name="dynamic_named_route")
    assert len(app.router.keys()) == 1
    for resource in list(app.router.resources()):
        if isinstance(resource, web.StaticResource):
            cors.add(resource)
    assert len(app.router.keys()) == 1


def test_web_view_resource(app, cors):
    """Test adding resource with web.View as handler"""
    assert len(app.router.keys()) == 0
    route = app.router.add_route("GET", "/{name}", _View, name="dynamic_named_route")
    assert len(app.router.keys()) == 1
    cors.add(route)
    assert len(app.router.keys()) == 1


def test_web_view_warning(app, cors):
    """Test adding resource with web.View as handler"""
    route = app.router.add_route("*", "/", _View)
    with pytest.warns(DeprecationWarning):
        cors.add(route, webview=True)


def test_disable_bare_view(app, cors):
    class View(web.View):
        pass

    route = app.router.add_route("*", "/", View)
    with pytest.raises(ValueError):
        cors.add(route)


================================================
FILE: tests/unit/test_mixin.py
================================================
import asyncio
from unittest import mock

import pytest

from aiohttp import web
from aiohttp_cors import (
    APP_CONFIG_KEY,
    CorsConfig,
    CorsViewMixin,
    ResourceOptions,
    custom_cors,
)

DEFAULT_CONFIG = {"*": ResourceOptions()}

CLASS_CONFIG = {"*": ResourceOptions()}

CUSTOM_CONFIG = {"www.client1.com": ResourceOptions(allow_headers=["X-Host"])}


class SimpleView(web.View, CorsViewMixin):
    async def get(self):
        return web.Response(text="Done")


class SimpleViewWithConfig(web.View, CorsViewMixin):

    cors_config = CLASS_CONFIG

    async def get(self):
        return web.Response(text="Done")


class CustomMethodView(web.View, CorsViewMixin):

    cors_config = CLASS_CONFIG

    async def get(self):
        return web.Response(text="Done")

    @custom_cors(CUSTOM_CONFIG)
    async def post(self):
        return web.Response(text="Done")


@pytest.fixture
def _app():
    return web.Application()


@pytest.fixture
def cors(_app):
    ret = CorsConfig(_app, defaults=DEFAULT_CONFIG)
    _app[APP_CONFIG_KEY] = ret
    return ret


@pytest.fixture
def app(_app, cors):
    # a trick to install a cors into app
    return _app


def test_raise_exception_when_cors_not_configure():
    request = mock.Mock()
    request.app = {}
    view = CustomMethodView(request)

    with pytest.raises(ValueError):
        view.get_request_config(request, "post")


async def test_raises_forbidden_when_config_not_found(app):
    app[APP_CONFIG_KEY].defaults = {}
    request = mock.Mock()
    request.app = app
    request.headers = {"Origin": "*", "Access-Control-Request-Method": "GET"}
    view = SimpleView(request)

    with pytest.raises(web.HTTPForbidden):
        await view.options()


def test_method_with_custom_cors(app):
    """Test adding resource with web.View as handler"""
    request = mock.Mock()
    request.app = app
    view = CustomMethodView(request)

    assert hasattr(view.post, "post_cors_config")
    assert asyncio.iscoroutinefunction(view.post)
    config = view.get_request_config(request, "post")

    assert config.get("www.client1.com") == CUSTOM_CONFIG["www.client1.com"]


def test_method_with_class_config(app):
    """Test adding resource with web.View as handler"""
    request = mock.Mock()
    request.app = app
    view = SimpleViewWithConfig(request)

    assert not hasattr(view.get, "get_cors_config")
    config = view.get_request_config(request, "get")

    assert config.get("*") == CLASS_CONFIG["*"]


def test_method_with_default_config(app):
    """Test adding resource with web.View as handler"""
    request = mock.Mock()
    request.app = app
    view = SimpleView(request)

    assert not hasattr(view.get, "get_cors_config")
    config = view.get_request_config(request, "get")

    assert config.get("*") == DEFAULT_CONFIG["*"]


================================================
FILE: tests/unit/test_preflight_handler.py
================================================
from unittest import mock

import pytest

from aiohttp_cors.preflight_handler import _PreflightHandler


async def test_raises_when_handler_not_extend():
    request = mock.Mock()
    handler = _PreflightHandler()
    with pytest.raises(NotImplementedError):
        await handler._get_config(request, "origin", "GET")


================================================
FILE: tests/unit/test_resource_options.py
================================================
# 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.resource_options unit tests."""

import pytest

from aiohttp_cors.resource_options import ResourceOptions


def test_init_no_args():
    """Test construction without arguments"""
    opts = ResourceOptions()

    assert not opts.allow_credentials
    assert not opts.expose_headers
    assert not opts.allow_headers
    assert opts.max_age is None


def test_comparison():
    assert ResourceOptions() == ResourceOptions()
    assert not (ResourceOptions() != ResourceOptions())
    assert not (ResourceOptions(allow_credentials=True) == ResourceOptions())
    assert ResourceOptions(allow_credentials=True) != ResourceOptions()


def test_allow_methods():
    assert ResourceOptions().allow_methods is None
    assert ResourceOptions(allow_methods="*").allow_methods == "*"
    assert ResourceOptions(allow_methods=[]).allow_methods == frozenset()
    assert ResourceOptions(allow_methods=["get"]).allow_methods == frozenset(["GET"])
    assert ResourceOptions(allow_methods=["get", "Post"]).allow_methods == {
        "GET",
        "POST",
    }
    with pytest.raises(ValueError):
        ResourceOptions(allow_methods="GET")


# TODO: test arguments parsing


================================================
FILE: tests/unit/test_urldispatcher_router_adapter.py
================================================
# 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.urldispatcher_router_adapter unit tests."""

from unittest import mock

import pytest

from aiohttp import web
from aiohttp_cors import ResourceOptions
from aiohttp_cors.urldispatcher_router_adapter import (
    ResourcesUrlDispatcherRouterAdapter,
)


async def _handler(request):
    return web.Response(text="Done")


@pytest.fixture
def app():
    return web.Application()


@pytest.fixture
def adapter(app):
    return ResourcesUrlDispatcherRouterAdapter(
        app.router, defaults={"*": ResourceOptions()}
    )


@pytest.fixture
def get_route(app):
    return app.router.add_route("GET", "/get_path", _handler)


@pytest.fixture
def options_route(app):
    return app.router.add_route("OPTIONS", "/options_path", _handler)


def test_add_get_route(adapter, get_route):
    """Test configuring GET route"""
    result = adapter.add_preflight_handler(get_route.resource, _handler)
    assert result is None

    assert len(adapter._resource_config) == 0
    assert len(adapter._resources_with_preflight_handlers) == 1
    assert len(adapter._preflight_routes) == 1


def test_add_options_route(adapter, options_route):
    """Test configuring OPTIONS route"""

    adapter.add_preflight_handler(options_route, _handler)

    assert not adapter._resources_with_preflight_handlers
    assert not adapter._preflight_routes


def test_get_non_preflight_request_config(adapter, get_route):
    adapter.add_preflight_handler(get_route.resource, _handler)
    adapter.set_config_for_routing_entity(
        get_route.resource,
        {
            "http://example.org": ResourceOptions(),
        },
    )

    adapter.add_preflight_handler(get_route, _handler)
    adapter.set_config_for_routing_entity(
        get_route,
        {
            "http://test.example.org": ResourceOptions(),
        },
    )

    request = mock.Mock()

    with mock.patch(
        "aiohttp_cors.urldispatcher_router_adapter."
        "ResourcesUrlDispatcherRouterAdapter."
        "is_cors_enabled_on_request"
    ) as is_cors_enabled_on_request, mock.patch(
        "aiohttp_cors.urldispatcher_router_adapter."
        "ResourcesUrlDispatcherRouterAdapter."
        "_request_resource"
    ) as _request_resource:
        is_cors_enabled_on_request.return_value = True
        _request_resource.return_value = get_route.resource

        assert adapter.get_non_preflight_request_config(request) == {
            "*": ResourceOptions(),
            "http://example.org": ResourceOptions(),
        }

        request.method = "GET"

        assert adapter.get_non_preflight_request_config(request) == {
            "*": ResourceOptions(),
            "http://example.org": ResourceOptions(),
            "http://test.example.org": ResourceOptions(),
        }
Download .txt
gitextract_8o3pvrw5/

├── .github/
│   ├── dependabot.yml
│   └── workflows/
│       ├── auto-merge.yml
│       ├── ci-cd.yml
│       └── codeql.yml
├── .gitignore
├── .jscs.json
├── .jshintrc
├── .pep8rc
├── .pre-commit-config.yaml
├── .pylintrc
├── .pyup.yml
├── CHANGES.rst
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.rst
├── aiohttp_cors/
│   ├── __about__.py
│   ├── __init__.py
│   ├── abc.py
│   ├── cors_config.py
│   ├── mixin.py
│   ├── preflight_handler.py
│   ├── py.typed
│   ├── resource_options.py
│   └── urldispatcher_router_adapter.py
├── install_python_and_pip.ps1
├── pytest.ini
├── requirements-dev.txt
├── setup.cfg
├── setup.py
└── tests/
    ├── __init__.py
    ├── doc/
    │   ├── __init__.py
    │   └── test_basic_usage.py
    ├── integration/
    │   ├── __init__.py
    │   ├── test_main.py
    │   ├── test_page.html
    │   └── test_real_browser.py
    └── unit/
        ├── __init__.py
        ├── test___about__.py
        ├── test_cors_config.py
        ├── test_mixin.py
        ├── test_preflight_handler.py
        ├── test_resource_options.py
        └── test_urldispatcher_router_adapter.py
Download .txt
SYMBOL INDEX (138 symbols across 17 files)

FILE: aiohttp_cors/__init__.py
  function setup (line 56) | def setup(

FILE: aiohttp_cors/abc.py
  class AbstractRouterAdapter (line 24) | class AbstractRouterAdapter(metaclass=ABCMeta):
    method add_preflight_handler (line 51) | def add_preflight_handler(self, routing_entity, handler, webview: bool...
    method is_preflight_request (line 59) | def is_preflight_request(self, request: web.Request) -> bool:
    method is_cors_enabled_on_request (line 63) | def is_cors_enabled_on_request(self, request: web.Request) -> bool:
    method set_config_for_routing_entity (line 67) | def set_config_for_routing_entity(self, routing_entity, config):
    method get_preflight_request_config (line 78) | async def get_preflight_request_config(
    method get_non_preflight_request_config (line 89) | def get_non_preflight_request_config(self, request: web.Request):

FILE: aiohttp_cors/cors_config.py
  function _parse_config_options (line 47) | def _parse_config_options(
  class _CorsConfigImpl (line 105) | class _CorsConfigImpl(_PreflightHandler):
    method __init__ (line 107) | def __init__(self, app: web.Application, router_adapter: AbstractRoute...
    method add (line 116) | def add(self, routing_entity, config: _ConfigType = None):
    method _on_response_prepare (line 139) | async def _on_response_prepare(
    method _get_config (line 199) | async def _get_config(self, request, origin, request_method):
  class CorsConfig (line 206) | class CorsConfig:
    method __init__ (line 215) | def __init__(
    method add (line 248) | def add(self, routing_entity, config: _ConfigType = None, webview: boo...

FILE: aiohttp_cors/mixin.py
  function custom_cors (line 6) | def custom_cors(config):
  class CorsViewMixin (line 15) | class CorsViewMixin(_PreflightHandler):
    method get_request_config (line 19) | def get_request_config(cls, request, request_method):
    method _get_config (line 44) | async def _get_config(self, request, origin, request_method):
    method options (line 47) | async def options(self):

FILE: aiohttp_cors/preflight_handler.py
  class _PreflightHandler (line 7) | class _PreflightHandler:
    method _parse_request_method (line 10) | def _parse_request_method(request: web.Request):
    method _parse_request_headers (line 25) | def _parse_request_headers(request: web.Request):
    method _get_config (line 42) | async def _get_config(self, request, origin, request_method):
    method _preflight_handler (line 45) | async def _preflight_handler(self, request: web.Request):

FILE: aiohttp_cors/resource_options.py
  function _is_proper_sequence (line 24) | def _is_proper_sequence(seq):
  class ResourceOptions (line 29) | class ResourceOptions(
    method __init__ (line 45) | def __init__(
    method __new__ (line 93) | def __new__(
    method is_method_allowed (line 172) | def is_method_allowed(self, method):

FILE: aiohttp_cors/urldispatcher_router_adapter.py
  class _ResourceConfig (line 78) | class _ResourceConfig:
    method __init__ (line 79) | def __init__(self, default_config):
  function _is_web_view (line 87) | def _is_web_view(entity, strict=True):
  class ResourcesUrlDispatcherRouterAdapter (line 105) | class ResourcesUrlDispatcherRouterAdapter(AbstractRouterAdapter):
    method __init__ (line 117) | def __init__(self, router: web.UrlDispatcher, defaults):
    method add_preflight_handler (line 133) | def add_preflight_handler(
    method is_cors_for_resource (line 202) | def is_cors_for_resource(self, resource: web.Resource) -> bool:
    method _request_route (line 206) | def _request_route(self, request: web.Request) -> web.ResourceRoute:
    method _request_resource (line 211) | def _request_resource(self, request: web.Request) -> web.Resource:
    method is_preflight_request (line 214) | def is_preflight_request(self, request: web.Request) -> bool:
    method is_cors_enabled_on_request (line 221) | def is_cors_enabled_on_request(self, request: web.Request) -> bool:
    method set_config_for_routing_entity (line 226) | def set_config_for_routing_entity(
    method get_preflight_request_config (line 274) | async def get_preflight_request_config(
    method get_non_preflight_request_config (line 300) | def get_non_preflight_request_config(self, request: web.Request):

FILE: setup.py
  function read_file (line 21) | def read_file(filename):

FILE: tests/doc/test_basic_usage.py
  function test_main (line 18) | async def test_main():
  function test_defaults (line 59) | async def test_defaults():

FILE: tests/integration/test_main.py
  function handler (line 30) | async def handler(request: web.Request) -> web.StreamResponse:
  class WebViewHandler (line 39) | class WebViewHandler(web.View, CorsViewMixin):
    method get (line 41) | async def get(self) -> web.StreamResponse:
  function make_app (line 51) | def make_app(request):
  function test_message_roundtrip (line 72) | async def test_message_roundtrip(aiohttp_client):
  function test_dummy_setup (line 87) | async def test_dummy_setup(aiohttp_server):
  function test_dummy_setup_roundtrip (line 95) | async def test_dummy_setup_roundtrip(aiohttp_client):
  function test_dummy_setup_roundtrip_resource (line 111) | async def test_dummy_setup_roundtrip_resource(aiohttp_client):
  function test_simple_no_origin (line 127) | async def test_simple_no_origin(aiohttp_client, make_app):
  function test_simple_allowed_origin (line 145) | async def test_simple_allowed_origin(aiohttp_client, make_app):
  function test_simple_not_allowed_origin (line 169) | async def test_simple_not_allowed_origin(aiohttp_client, make_app):
  function test_simple_explicit_port (line 189) | async def test_simple_explicit_port(aiohttp_client, make_app):
  function test_simple_different_scheme (line 209) | async def test_simple_different_scheme(aiohttp_client, make_app):
  function app_for_credentials (line 235) | def app_for_credentials(make_app, request):
  function test_cred_no_origin (line 239) | async def test_cred_no_origin(aiohttp_client, app_for_credentials):
  function test_cred_allowed_origin (line 257) | async def test_cred_allowed_origin(aiohttp_client, app_for_credentials):
  function test_cred_disallowed_origin (line 281) | async def test_cred_disallowed_origin(aiohttp_client, app_for_credentials):
  function test_simple_expose_headers_no_origin (line 301) | async def test_simple_expose_headers_no_origin(aiohttp_client, make_app):
  function test_simple_expose_headers_allowed_origin (line 326) | async def test_simple_expose_headers_allowed_origin(aiohttp_client, make...
  function test_simple_expose_headers_not_allowed_origin (line 357) | async def test_simple_expose_headers_not_allowed_origin(aiohttp_client, ...
  function test_preflight_default_no_origin (line 384) | async def test_preflight_default_no_origin(aiohttp_client, make_app):
  function test_preflight_default_no_method (line 405) | async def test_preflight_default_no_method(aiohttp_client, make_app):
  function test_preflight_default_origin_and_method (line 432) | async def test_preflight_default_origin_and_method(aiohttp_client, make_...
  function test_preflight_default_disallowed_origin (line 464) | async def test_preflight_default_disallowed_origin(aiohttp_client, make_...
  function test_preflight_default_disallowed_method (line 492) | async def test_preflight_default_disallowed_method(aiohttp_client, make_...
  function test_preflight_req_multiple_routes_with_one_options (line 523) | async def test_preflight_req_multiple_routes_with_one_options(aiohttp_cl...
  function test_preflight_request_mult_routes_with_one_options_resource (line 557) | async def test_preflight_request_mult_routes_with_one_options_resource(a...
  function test_preflight_request_max_age_resource (line 592) | async def test_preflight_request_max_age_resource(aiohttp_client):
  function test_preflight_request_max_age_webview (line 628) | async def test_preflight_request_max_age_webview(aiohttp_client):
  function test_preflight_request_mult_routes_with_one_options_webview (line 671) | async def test_preflight_request_mult_routes_with_one_options_webview(ai...
  function test_preflight_request_headers_webview (line 714) | async def test_preflight_request_headers_webview(aiohttp_client):
  function test_preflight_request_headers_resource (line 786) | async def test_preflight_request_headers_resource(aiohttp_client):
  function test_preflight_request_headers (line 850) | async def test_preflight_request_headers(aiohttp_client):
  function test_static_route (line 915) | async def test_static_route(aiohttp_client):

FILE: tests/integration/test_real_browser.py
  class _ServerDescr (line 37) | class _ServerDescr:
    method __init__ (line 40) | def __init__(self):
  class IntegrationServers (line 48) | class IntegrationServers:
    method __init__ (line 51) | def __init__(self, use_resources, use_webview, *, loop=None):
    method origin_server_url (line 64) | def origin_server_url(self):
    method start_servers (line 67) | async def start_servers(self):
    method stop_servers (line 202) | async def stop_servers(self):
  function _get_chrome_driver (line 211) | def _get_chrome_driver():
  function server (line 223) | def server(request, loop):
  function driver (line 232) | def driver(request):
  function test_in_webdriver (line 242) | async def test_in_webdriver(driver, server):
  function _run_integration_server (line 277) | def _run_integration_server():

FILE: tests/unit/test___about__.py
  function test_version (line 22) | def test_version():

FILE: tests/unit/test_cors_config.py
  function _handler (line 23) | async def _handler(request):
  class _View (line 27) | class _View(web.View, CorsViewMixin):
    method get (line 29) | async def get(self):
  function app (line 34) | def app():
  function cors (line 39) | def cors(app):
  function get_route (line 44) | def get_route(app):
  function options_route (line 49) | def options_route(app):
  function test_add_options_route (line 53) | def test_add_options_route(app, cors, options_route):
  function test_plain_named_route (line 59) | def test_plain_named_route(app, cors):
  function test_dynamic_named_route (line 69) | def test_dynamic_named_route(app, cors):
  function test_static_named_route (line 78) | def test_static_named_route(app, cors):
  function test_static_resource (line 87) | def test_static_resource(app, cors):
  function test_web_view_resource (line 98) | def test_web_view_resource(app, cors):
  function test_web_view_warning (line 107) | def test_web_view_warning(app, cors):
  function test_disable_bare_view (line 114) | def test_disable_bare_view(app, cors):

FILE: tests/unit/test_mixin.py
  class SimpleView (line 22) | class SimpleView(web.View, CorsViewMixin):
    method get (line 23) | async def get(self):
  class SimpleViewWithConfig (line 27) | class SimpleViewWithConfig(web.View, CorsViewMixin):
    method get (line 31) | async def get(self):
  class CustomMethodView (line 35) | class CustomMethodView(web.View, CorsViewMixin):
    method get (line 39) | async def get(self):
    method post (line 43) | async def post(self):
  function _app (line 48) | def _app():
  function cors (line 53) | def cors(_app):
  function app (line 60) | def app(_app, cors):
  function test_raise_exception_when_cors_not_configure (line 65) | def test_raise_exception_when_cors_not_configure():
  function test_raises_forbidden_when_config_not_found (line 74) | async def test_raises_forbidden_when_config_not_found(app):
  function test_method_with_custom_cors (line 85) | def test_method_with_custom_cors(app):
  function test_method_with_class_config (line 98) | def test_method_with_class_config(app):
  function test_method_with_default_config (line 110) | def test_method_with_default_config(app):

FILE: tests/unit/test_preflight_handler.py
  function test_raises_when_handler_not_extend (line 8) | async def test_raises_when_handler_not_extend():

FILE: tests/unit/test_resource_options.py
  function test_init_no_args (line 22) | def test_init_no_args():
  function test_comparison (line 32) | def test_comparison():
  function test_allow_methods (line 39) | def test_allow_methods():

FILE: tests/unit/test_urldispatcher_router_adapter.py
  function _handler (line 28) | async def _handler(request):
  function app (line 33) | def app():
  function adapter (line 38) | def adapter(app):
  function get_route (line 45) | def get_route(app):
  function options_route (line 50) | def options_route(app):
  function test_add_get_route (line 54) | def test_add_get_route(adapter, get_route):
  function test_add_options_route (line 64) | def test_add_options_route(adapter, options_route):
  function test_get_non_preflight_request_config (line 73) | def test_get_non_preflight_request_config(adapter, get_route):
Condensed preview — 44 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (177K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 239,
    "preview": "version: 2\nupdates:\n- package-ecosystem: pip\n  directory: \"/\"\n  schedule:\n    interval: daily\n  open-pull-requests-limit"
  },
  {
    "path": ".github/workflows/auto-merge.yml",
    "chars": 608,
    "preview": "name: Dependabot auto-merge\non: pull_request_target\n\npermissions:\n  pull-requests: write\n  contents: write\n\njobs:\n  depe"
  },
  {
    "path": ".github/workflows/ci-cd.yml",
    "chars": 4175,
    "preview": "name: CI\n\non:\n  push:\n    branches:\n      - master\n      - '[0-9].[0-9]+'  # matches to backport branches, e.g. 3.6\n    "
  },
  {
    "path": ".github/workflows/codeql.yml",
    "chars": 931,
    "preview": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [ \"master\" ]\n  pull_request:\n    branches: [ \"master\" ]\n  schedule:\n    - cron"
  },
  {
    "path": ".gitignore",
    "chars": 697,
    "preview": "#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inj"
  },
  {
    "path": ".jscs.json",
    "chars": 1706,
    "preview": "{\n    \"requireCurlyBraces\": [\n        \"if\",\n        \"else\",\n        \"for\",\n        \"while\",\n        \"do\",\n        \"try\","
  },
  {
    "path": ".jshintrc",
    "chars": 1190,
    "preview": "{\n\t\"asi\": false,\n\t\"bitwise\": false,\n\t\"boss\": false,\n\t\"browser\": true,\n\t\"camelcase\": true,\n\t\"couch\": false,\n\t\"curly\": tru"
  },
  {
    "path": ".pep8rc",
    "chars": 219,
    "preview": "; TODO: This configuration currently not used in pytest-pep8, see\n; <https://bitbucket.org/pytest-dev/pytest-pep8/issues"
  },
  {
    "path": ".pre-commit-config.yaml",
    "chars": 1219,
    "preview": "repos:\n- repo: https://github.com/pre-commit/pre-commit-hooks\n  rev: 'v5.0.0'\n  hooks:\n  - id: check-merge-conflict\n    "
  },
  {
    "path": ".pylintrc",
    "chars": 12570,
    "preview": "[MASTER]\n\n# Specify a configuration file.\n#rcfile=\n\n# Python code to execute, usually for sys.path manipulation such as\n"
  },
  {
    "path": ".pyup.yml",
    "chars": 82,
    "preview": "# Label PRs with `deps-update` label\nlabel_prs: deps-update\n\nschedule: every week\n"
  },
  {
    "path": "CHANGES.rst",
    "chars": 2566,
    "preview": "=========\n CHANGES\n=========\n\n0.8.1 (2025-03-31)\n==================\n\n- Fix packaging to not install on Python 3.8.\n\n0.8."
  },
  {
    "path": "LICENSE",
    "chars": 11368,
    "preview": "                                 Apache License\n                           Version 2.0, January 2004\n                   "
  },
  {
    "path": "MANIFEST.in",
    "chars": 125,
    "preview": "include LICENSE\ninclude CHANGES.rst\ninclude README.rst\nrecursive-include tests *.py\ninclude tests/integration/test_page."
  },
  {
    "path": "Makefile",
    "chars": 72,
    "preview": "all: test\n\n\nlint:\n\tpre-commit run --all-files\n\ntest: lint\n\tpytest tests\n"
  },
  {
    "path": "README.rst",
    "chars": 16626,
    "preview": "========================\nCORS support for aiohttp\n========================\n\n``aiohttp_cors`` library implements\n`Cross O"
  },
  {
    "path": "aiohttp_cors/__about__.py",
    "chars": 989,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "aiohttp_cors/__init__.py",
    "chars": 2268,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "aiohttp_cors/abc.py",
    "chars": 3496,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "aiohttp_cors/cors_config.py",
    "chars": 8902,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "aiohttp_cors/mixin.py",
    "chars": 1331,
    "preview": "import collections\n\nfrom .preflight_handler import _PreflightHandler\n\n\ndef custom_cors(config):\n    def wrapper(function"
  },
  {
    "path": "aiohttp_cors/preflight_handler.py",
    "chars": 4808,
    "preview": "from aiohttp import hdrs, web\n\n# Positive response to Access-Control-Allow-Credentials\n_TRUE = \"true\"\n\n\nclass _Preflight"
  },
  {
    "path": "aiohttp_cors/py.typed",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "aiohttp_cors/resource_options.py",
    "chars": 6160,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "aiohttp_cors/urldispatcher_router_adapter.py",
    "chars": 12271,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "install_python_and_pip.ps1",
    "chars": 3362,
    "preview": "# Sample script to install Python and pip under Windows\n# Authors: Olivier Grisel and Kyle Kastner\n# License: BSD 3 clau"
  },
  {
    "path": "pytest.ini",
    "chars": 35,
    "preview": "[pytest]\nfilterwarnings=\n    error\n"
  },
  {
    "path": "requirements-dev.txt",
    "chars": 151,
    "preview": "-e .\naiohttp==3.13.5\npackaging==26.2\npytest==8.4.2\npytest-aiohttp==0.3.0\npytest-cov==7.1.0\nselenium==4.34.0\ntox==4.30.3\n"
  },
  {
    "path": "setup.cfg",
    "chars": 733,
    "preview": "[tool:pytest]\naddopts= --cov=aiohttp_cors --cov-report=term --cov-report=html --cov-branch --no-cov-on-fail\n\n[flake8]\nex"
  },
  {
    "path": "setup.py",
    "chars": 2647,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/doc/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/doc/test_basic_usage.py",
    "chars": 3416,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "tests/integration/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/integration/test_main.py",
    "chars": 27598,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "tests/integration/test_page.html",
    "chars": 11363,
    "preview": "<!DOCTYPE html>\n<html lang=\"en-US\">\n<head>\n  <meta charset=\"utf-8\">\n  <title>aiohttp_cors testing</title>\n  <style>\n    "
  },
  {
    "path": "tests/integration/test_real_browser.py",
    "chars": 10226,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "tests/unit/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/unit/test___about__.py",
    "chars": 822,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "tests/unit/test_cors_config.py",
    "chars": 3620,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "tests/unit/test_mixin.py",
    "chars": 2816,
    "preview": "import asyncio\nfrom unittest import mock\n\nimport pytest\n\nfrom aiohttp import web\nfrom aiohttp_cors import (\n    APP_CONF"
  },
  {
    "path": "tests/unit/test_preflight_handler.py",
    "chars": 319,
    "preview": "from unittest import mock\n\nimport pytest\n\nfrom aiohttp_cors.preflight_handler import _PreflightHandler\n\n\nasync def test_"
  },
  {
    "path": "tests/unit/test_resource_options.py",
    "chars": 1781,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  },
  {
    "path": "tests/unit/test_urldispatcher_router_adapter.py",
    "chars": 3365,
    "preview": "# Copyright 2015 Vladimir Rutsky <vladimir@rutsky.org>\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\""
  }
]

About this extraction

This page contains the full source code of the aio-libs/aiohttp-cors GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 44 files (163.0 KB), approximately 39.0k tokens, and a symbol index with 138 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!