Full Code of yandex/gixy for AI

master 6f68624a7540 cached
178 files
268.2 KB
72.2k tokens
494 symbols
1 requests
Download .txt
Showing preview only (307K chars total). Download the full file or copy to clipboard to get everything.
Repository: yandex/gixy
Branch: master
Commit: 6f68624a7540
Files: 178
Total size: 268.2 KB

Directory structure:
gitextract__w1ux7cu/

├── .dockerignore
├── .editorconfig
├── .gitignore
├── .travis.yml
├── AUTHORS
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.RU.md
├── README.md
├── docs/
│   ├── en/
│   │   └── plugins/
│   │       ├── addheadermultiline.md
│   │       ├── addheaderredefinition.md
│   │       ├── aliastraversal.md
│   │       ├── hostspoofing.md
│   │       ├── httpsplitting.md
│   │       ├── origins.md
│   │       ├── ssrf.md
│   │       └── validreferers.md
│   └── ru/
│       └── plugins/
│           ├── addheadermultiline.md
│           ├── addheaderredefinition.md
│           ├── aliastraversal.md
│           ├── hostspoofing.md
│           ├── httpsplitting.md
│           ├── origins.md
│           ├── ssrf.md
│           └── validreferers.md
├── gixy/
│   ├── __init__.py
│   ├── cli/
│   │   ├── __init__.py
│   │   ├── argparser.py
│   │   └── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   ├── builtin_variables.py
│   │   ├── config.py
│   │   ├── context.py
│   │   ├── exceptions.py
│   │   ├── issue.py
│   │   ├── manager.py
│   │   ├── plugins_manager.py
│   │   ├── regexp.py
│   │   ├── severity.py
│   │   ├── sre_parse/
│   │   │   ├── __init__.py
│   │   │   ├── sre_constants.py
│   │   │   └── sre_parse.py
│   │   ├── utils.py
│   │   └── variable.py
│   ├── directives/
│   │   ├── __init__.py
│   │   ├── block.py
│   │   └── directive.py
│   ├── formatters/
│   │   ├── __init__.py
│   │   ├── _jinja.py
│   │   ├── base.py
│   │   ├── console.py
│   │   ├── json.py
│   │   ├── templates/
│   │   │   ├── console.j2
│   │   │   └── text.j2
│   │   └── text.py
│   ├── parser/
│   │   ├── __init__.py
│   │   ├── nginx_parser.py
│   │   └── raw_parser.py
│   ├── plugins/
│   │   ├── __init__.py
│   │   ├── add_header_multiline.py
│   │   ├── add_header_redefinition.py
│   │   ├── alias_traversal.py
│   │   ├── host_spoofing.py
│   │   ├── http_splitting.py
│   │   ├── origins.py
│   │   ├── plugin.py
│   │   ├── ssrf.py
│   │   └── valid_referers.py
│   └── utils/
│       ├── __init__.py
│       └── text.py
├── requirements.dev.txt
├── requirements.txt
├── rpm/
│   ├── gixy.spec
│   └── python-argparse.spec
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── asserts.py
│   ├── core/
│   │   ├── __init__.py
│   │   ├── test_context.py
│   │   ├── test_regexp.py
│   │   └── test_variable.py
│   ├── directives/
│   │   ├── __init__.py
│   │   ├── test_block.py
│   │   └── test_directive.py
│   ├── parser/
│   │   ├── __init__.py
│   │   ├── test_nginx_parser.py
│   │   └── test_raw_parser.py
│   ├── plugins/
│   │   ├── __init__.py
│   │   ├── simply/
│   │   │   ├── add_header_multiline/
│   │   │   │   ├── add_header.conf
│   │   │   │   ├── add_header_fp.conf
│   │   │   │   ├── config.json
│   │   │   │   ├── more_set_headers.conf
│   │   │   │   ├── more_set_headers_fp.conf
│   │   │   │   ├── more_set_headers_multiple.conf
│   │   │   │   ├── more_set_headers_replace.conf
│   │   │   │   ├── more_set_headers_replace_fp.conf
│   │   │   │   ├── more_set_headers_status_fp.conf
│   │   │   │   └── more_set_headers_type_fp.conf
│   │   │   ├── add_header_redefinition/
│   │   │   │   ├── config.json
│   │   │   │   ├── duplicate_fp.conf
│   │   │   │   ├── if_replaces.conf
│   │   │   │   ├── location_replaces.conf
│   │   │   │   ├── nested_block.conf
│   │   │   │   ├── non_block_fp.conf
│   │   │   │   ├── not_secure_both_fp.conf
│   │   │   │   ├── not_secure_outer_fp.conf
│   │   │   │   └── step_replaces.conf
│   │   │   ├── alias_traversal/
│   │   │   │   ├── config.json
│   │   │   │   ├── nested.conf
│   │   │   │   ├── nested_fp.conf
│   │   │   │   ├── not_slashed_alias.conf
│   │   │   │   ├── not_slashed_alias_fp.conf
│   │   │   │   ├── simple.conf
│   │   │   │   ├── simple_fp.conf
│   │   │   │   ├── slashed_alias.conf
│   │   │   │   └── slashed_alias_fp.conf
│   │   │   ├── host_spoofing/
│   │   │   │   ├── config.json
│   │   │   │   ├── http_fp.conf
│   │   │   │   ├── http_host.conf
│   │   │   │   ├── http_host_diff_case.conf
│   │   │   │   └── some_arg.conf
│   │   │   ├── http_splitting/
│   │   │   │   ├── add_header_uri.conf
│   │   │   │   ├── config.json
│   │   │   │   ├── dont_report_not_resolved_var_fp.conf
│   │   │   │   ├── proxy_from_location_var.conf
│   │   │   │   ├── proxy_from_location_var_var.conf
│   │   │   │   ├── proxy_from_location_var_var_fp.conf
│   │   │   │   ├── proxy_from_location_var_var_var.conf
│   │   │   │   ├── proxy_pass_cr_fp.conf
│   │   │   │   ├── proxy_pass_ducument_uri.conf
│   │   │   │   ├── proxy_pass_lf.conf
│   │   │   │   ├── proxy_set_header_ducument_uri.conf
│   │   │   │   ├── return_403_fp.conf
│   │   │   │   ├── return_request_uri_fp.conf
│   │   │   │   ├── rewrite_extract_fp.conf
│   │   │   │   ├── rewrite_uri.conf
│   │   │   │   └── rewrite_uri_after_var.conf
│   │   │   ├── origins/
│   │   │   │   ├── config.json
│   │   │   │   ├── metrika.conf
│   │   │   │   ├── origin.conf
│   │   │   │   ├── origin_fp.conf
│   │   │   │   ├── origin_https.conf
│   │   │   │   ├── origin_https_fp.conf
│   │   │   │   ├── origin_w_slash_anchored_fp.conf
│   │   │   │   ├── origin_w_slash_fp.conf
│   │   │   │   ├── origin_wo_slash.conf
│   │   │   │   ├── referer.conf
│   │   │   │   ├── referer_fp.conf
│   │   │   │   ├── referer_subdomain.conf
│   │   │   │   ├── referer_subdomain_fp.conf
│   │   │   │   ├── structure_dot.conf
│   │   │   │   ├── structure_fp.conf
│   │   │   │   ├── structure_prefix.conf
│   │   │   │   ├── structure_suffix.conf
│   │   │   │   └── webvisor.conf
│   │   │   ├── ssrf/
│   │   │   │   ├── config.json
│   │   │   │   ├── have_internal_fp.conf
│   │   │   │   ├── host_w_const_start.conf
│   │   │   │   ├── host_w_const_start_arg.conf
│   │   │   │   ├── not_host_var_fp.conf
│   │   │   │   ├── request_uri_fp.conf
│   │   │   │   ├── request_uri_var_fp.conf
│   │   │   │   ├── scheme_var.conf
│   │   │   │   ├── single_var.conf
│   │   │   │   ├── used_arg.conf
│   │   │   │   ├── vars_from_loc.conf
│   │   │   │   └── with_const_scheme.conf
│   │   │   └── valid_referers/
│   │   │       ├── config.json
│   │   │       ├── none_first.conf
│   │   │       ├── none_last.conf
│   │   │       ├── none_middle.conf
│   │   │       └── wo_none_fp.conf
│   │   └── test_simply.py
│   └── utils.py
└── tox.ini

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

================================================
FILE: .dockerignore
================================================
# Byte-compiled / optimized / DLL files
**/__pycache__/
**/*.py[cod]

# C extensions
***/*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg


# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
cover

# Translations
**/*.mo
**/*.pot

# PyBuilder
target/

venv/
venv3/
.idea/

# 100% unnecessary for docker image
.*
*.md
docs
rpm
Dockerfile


================================================
FILE: .editorconfig
================================================
root = true

[*]
end_of_file = lf
insert_final_newline = true

[*.{py,j2}]
charset = utf-8

[*.py]
indent_style = space
indent_size = 4

[Makefile]
indent_style = tab

[.travis.yml]
indent_style = space
indent_size = 2


================================================
FILE: .gitignore
================================================
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

# C extensions
*.so

# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
*.egg-info/
.installed.cfg
*.egg

# PyInstaller
#  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.
*.manifest
*.spec
!rpm/*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*,cover
cover

# Translations
*.mo
*.pot

# Django stuff:
*.log

# Sphinx documentation
docs/_build/

# PyBuilder
target/

venv/
venv3/
.idea/


================================================
FILE: .travis.yml
================================================
language: python
dist: xenial
sudo: false
python:
  - "2.7"
  - "3.5"
  - "3.6"
  - "3.7"
  - "pypy"
  - "pypy3"

install:
  - pip install -r requirements.txt
  - pip install -r requirements.dev.txt

script:
  - nosetests --with-coverage --cover-package gixy -v
  - if [[ $TRAVIS_PYTHON_VERSION != '2.6' ]]; then flake8 --max-line-length=120 setup.py gixy; fi


================================================
FILE: AUTHORS
================================================
The following authors have created the source code of "Gixy"
published and distributed by YANDEX LLC as the owner:

Andrew Krasichkov <buglloc@yandex-team.ru>

================================================
FILE: CONTRIBUTING.md
================================================
# Notice to external contributors



## General info

Hello! In order for us (YANDEX LLC) to accept patches and other contributions from you, you will have to adopt our Yandex Contributor License Agreement (the “**CLA**”). The current version of the CLA you may find here:
1) https://yandex.ru/legal/cla/?lang=en (in English) and 
2) https://yandex.ru/legal/cla/?lang=ru (in Russian).

By adopting the CLA, you state the following:

* You obviously wish and are willingly licensing your contributions to us for our open source projects under the terms of the CLA,
* You has read the terms and conditions of the CLA and agree with them in full,
* You are legally able to provide and license your contributions as stated,
* We may use your contributions for our open source projects and for any other our project too,
* We rely on your assurances concerning the rights of third parties in relation to your contributes.

If you agree with these principles, please read and adopt our CLA. By providing us your contributions, you hereby declare that you has already read and adopt our CLA, and we may freely merge your contributions with our corresponding open source project and use it in further in accordance with terms and conditions of the CLA.

## Provide contributions 

If you have already adopted terms and conditions of the CLA, you are able to provide your contributes. When you submit your pull request, please add the following information into it:

`
I hereby agree to the terms of the CLA available at: [link]).
`

Replace the bracketed text as follows:
* [link] is the link at the current version of the CLA (you may add here a link https://yandex.ru/legal/cla/?lang=en (in English) or a link https://yandex.ru/legal/cla/?lang=ru (in Russian).

It is enough to provide us such notification at once. 

## Other questions

If you have any questions, please mail us at opensource@yandex-team.ru.




================================================
FILE: Dockerfile
================================================
FROM python:alpine

ADD . /src

WORKDIR /src

RUN python3 setup.py install

ENTRYPOINT ["gixy"]


================================================
FILE: LICENSE
================================================
(C) YANDEX LLC, 2017

Mozilla Public License Version 2.0
==================================

1. Definitions
--------------

1.1. "Contributor"
    means each individual or legal entity that creates, contributes to
    the creation of, or owns Covered Software.

1.2. "Contributor Version"
    means the combination of the Contributions of others (if any) used
    by a Contributor and that particular Contributor's Contribution.

1.3. "Contribution"
    means Covered Software of a particular Contributor.

1.4. "Covered Software"
    means Source Code Form to which the initial Contributor has attached
    the notice in Exhibit A, the Executable Form of such Source Code
    Form, and Modifications of such Source Code Form, in each case
    including portions thereof.

1.5. "Incompatible With Secondary Licenses"
    means

    (a) that the initial Contributor has attached the notice described
        in Exhibit B to the Covered Software; or

    (b) that the Covered Software was made available under the terms of
        version 1.1 or earlier of the License, but not also under the
        terms of a Secondary License.

1.6. "Executable Form"
    means any form of the work other than Source Code Form.

1.7. "Larger Work"
    means a work that combines Covered Software with other material, in
    a separate file or files, that is not Covered Software.

1.8. "License"
    means this document.

1.9. "Licensable"
    means having the right to grant, to the maximum extent possible,
    whether at the time of the initial grant or subsequently, any and
    all of the rights conveyed by this License.

1.10. "Modifications"
    means any of the following:

    (a) any file in Source Code Form that results from an addition to,
        deletion from, or modification of the contents of Covered
        Software; or

    (b) any new file in Source Code Form that contains any Covered
        Software.

1.11. "Patent Claims" of a Contributor
    means any patent claim(s), including without limitation, method,
    process, and apparatus claims, in any patent Licensable by such
    Contributor that would be infringed, but for the grant of the
    License, by the making, using, selling, offering for sale, having
    made, import, or transfer of either its Contributions or its
    Contributor Version.

1.12. "Secondary License"
    means either the GNU General Public License, Version 2.0, the GNU
    Lesser General Public License, Version 2.1, the GNU Affero General
    Public License, Version 3.0, or any later versions of those
    licenses.

1.13. "Source Code Form"
    means the form of the work preferred for making modifications.

1.14. "You" (or "Your")
    means an individual or a legal entity exercising rights under this
    License. For legal entities, "You" includes any entity that
    controls, is controlled by, or is under common control with You. For
    purposes of this definition, "control" means (a) the power, direct
    or indirect, to cause the direction or management of such entity,
    whether by contract or otherwise, or (b) ownership of more than
    fifty percent (50%) of the outstanding shares or beneficial
    ownership of such entity.

2. License Grants and Conditions
--------------------------------

2.1. Grants

Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:

(a) under intellectual property rights (other than patent or trademark)
    Licensable by such Contributor to use, reproduce, make available,
    modify, display, perform, distribute, and otherwise exploit its
    Contributions, either on an unmodified basis, with Modifications, or
    as part of a Larger Work; and

(b) under Patent Claims of such Contributor to make, use, sell, offer
    for sale, have made, import, and otherwise transfer either its
    Contributions or its Contributor Version.

2.2. Effective Date

The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.

2.3. Limitations on Grant Scope

The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:

(a) for any code that a Contributor has removed from Covered Software;
    or

(b) for infringements caused by: (i) Your and any other third party's
    modifications of Covered Software, or (ii) the combination of its
    Contributions with other software (except as part of its Contributor
    Version); or

(c) under Patent Claims infringed by Covered Software in the absence of
    its Contributions.

This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).

2.4. Subsequent Licenses

No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).

2.5. Representation

Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.

2.6. Fair Use

This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.

2.7. Conditions

Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.

3. Responsibilities
-------------------

3.1. Distribution of Source Form

All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.

3.2. Distribution of Executable Form

If You distribute Covered Software in Executable Form then:

(a) such Covered Software must also be made available in Source Code
    Form, as described in Section 3.1, and You must inform recipients of
    the Executable Form how they can obtain a copy of such Source Code
    Form by reasonable means in a timely manner, at a charge no more
    than the cost of distribution to the recipient; and

(b) You may distribute such Executable Form under the terms of this
    License, or sublicense it under different terms, provided that the
    license for the Executable Form does not attempt to limit or alter
    the recipients' rights in the Source Code Form under this License.

3.3. Distribution of a Larger Work

You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).

3.4. Notices

You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.

3.5. Application of Additional Terms

You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.

4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------

If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.

5. Termination
--------------

5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.

5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.

5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.

************************************************************************
*                                                                      *
*  6. Disclaimer of Warranty                                           *
*  -------------------------                                           *
*                                                                      *
*  Covered Software is provided under this License on an "as is"       *
*  basis, without warranty of any kind, either expressed, implied, or  *
*  statutory, including, without limitation, warranties that the       *
*  Covered Software is free of defects, merchantable, fit for a        *
*  particular purpose or non-infringing. The entire risk as to the     *
*  quality and performance of the Covered Software is with You.        *
*  Should any Covered Software prove defective in any respect, You     *
*  (not any Contributor) assume the cost of any necessary servicing,   *
*  repair, or correction. This disclaimer of warranty constitutes an   *
*  essential part of this License. No use of any Covered Software is   *
*  authorized under this License except under this disclaimer.         *
*                                                                      *
************************************************************************

************************************************************************
*                                                                      *
*  7. Limitation of Liability                                          *
*  --------------------------                                          *
*                                                                      *
*  Under no circumstances and under no legal theory, whether tort      *
*  (including negligence), contract, or otherwise, shall any           *
*  Contributor, or anyone who distributes Covered Software as          *
*  permitted above, be liable to You for any direct, indirect,         *
*  special, incidental, or consequential damages of any character      *
*  including, without limitation, damages for lost profits, loss of    *
*  goodwill, work stoppage, computer failure or malfunction, or any    *
*  and all other commercial damages or losses, even if such party      *
*  shall have been informed of the possibility of such damages. This   *
*  limitation of liability shall not apply to liability for death or   *
*  personal injury resulting from such party's negligence to the       *
*  extent applicable law prohibits such limitation. Some               *
*  jurisdictions do not allow the exclusion or limitation of           *
*  incidental or consequential damages, so this exclusion and          *
*  limitation may not apply to You.                                    *
*                                                                      *
************************************************************************

8. Litigation
-------------

Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.

9. Miscellaneous
----------------

This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.

10. Versions of the License
---------------------------

10.1. New Versions

Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.

10.2. Effect of New Versions

You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.

10.3. Modified Versions

If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).

10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses

If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.

================================================
FILE: MANIFEST.in
================================================
include gixy/formatters/templates/*
graft tests


================================================
FILE: Makefile
================================================
.PHONY: all build publish

all: build publish

build:
	python setup.py bdist_wheel --universal sdist

publish:
	twine upload dist/gixy-`grep -oP "(?<=version\s=\s['\"])[^'\"]*(?=['\"])" gixy/__init__.py`*



================================================
FILE: README.RU.md
================================================
GIXY
====
[![Mozilla Public License 2.0](https://img.shields.io/github/license/yandex/gixy.svg?style=flat-square)](https://github.com/yandex/gixy/blob/master/LICENSE)
[![Build Status](https://img.shields.io/travis/yandex/gixy.svg?style=flat-square)](https://travis-ci.org/yandex/gixy)
[![Your feedback is greatly appreciated](https://img.shields.io/maintenance/yes/2018.svg?style=flat-square)](https://github.com/yandex/gixy/issues/new)
[![GitHub issues](https://img.shields.io/github/issues/yandex/gixy.svg?style=flat-square)](https://github.com/yandex/gixy/issues)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/yandex/gixy.svg?style=flat-square)](https://github.com/yandex/gixy/pulls)

# Overview
<img align="right" width="192" height="192" src="/docs/logo.png">

Gixy — это утилита для анализа конфигурации Nginx.
Большей частью служит для обнаружения проблем безопасности, но может искать и иные ошибки.

Официально поддерживаются версии Python 2.7, 3.5, 3.6 и 3.7

&nbsp;
# Что умеет
На текущий момент Gixy способна обнаружить:
  * [[ssrf] Server Side Request Forgery](https://github.com/yandex/gixy/blob/master/docs/ru/plugins/ssrf.md)
  * [[http_splitting] HTTP Splitting](https://github.com/yandex/gixy/blob/master/docs/ru/plugins/httpsplitting.md)
  * [[origins] Проблемы валидации referrer/origin](https://github.com/yandex/gixy/blob/master/docs/ru/plugins/origins.md)
  * [[add_header_redefinition] Переопределение "вышестоящих" заголовков ответа директивой "add_header"](https://github.com/yandex/gixy/blob/master/docs/ru/plugins/addheaderredefinition.md)
  * [[host_spoofing] Подделка заголовка запроса Host](https://github.com/yandex/gixy/blob/master/docs/ru/plugins/hostspoofing.md)
  * [[valid_referers] none in valid_referers](https://github.com/yandex/gixy/blob/master/docs/ru/plugins/validreferers.md)
  * [[add_header_multiline] Многострочные заголовоки ответа](https://github.com/yandex/gixy/blob/master/docs/ru/plugins/addheadermultiline.md)
  * [[alias_traversal] Path traversal при использовании alias](https://github.com/yandex/gixy/blob/master/docs/ru/plugins/aliastraversal.md)

Проблемы, которым Gixy только учится можно найти в [Issues с меткой "new plugin"](https://github.com/yandex/gixy/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+plugin%22)

# Установка
Наиболее простой способ установки Gixy - воспользоваться pip для установки из [PyPI](https://pypi.python.org/pypi/gixy):
```bash
pip install gixy
```

# Использование
После установки должна стать доступна консольная утилита `gixy`.
По умолчанию Gixy ищет конфигурацию по стандартному пути `/etc/nginx/nginx.conf`, однако вы можете указать специфичное расположение:
```
$ gixy /etc/nginx/nginx.conf

==================== Results ===================

Problem: [http_splitting] Possible HTTP-Splitting vulnerability.
Description: Using variables that can contain "\n" may lead to http injection.
Additional info: https://github.com/yandex/gixy/wiki/ru/httpsplitting
Reason: At least variable "$action" can contain "\n"
Pseudo config:
include /etc/nginx/sites/default.conf;

	server {

		location ~ /v1/((?<action>[^.]*)\.json)?$ {
			add_header X-Action $action;
		}
	}


==================== Summary ===================
Total issues:
    Unspecified: 0
    Low: 0
    Medium: 0
    High: 1
```

Gixy умеет обрабатывать директиву `include` и попробует максимально корректно обработать все зависимости, если что-то пошло не так можно попробовать запустить `gixy` с флагом `-d` для вывода дополнительной информации.
Все доступные опции:
```
$ gixy -h
usage: gixy [-h] [-c CONFIG_FILE] [--write-config CONFIG_OUTPUT_PATH]
            [-v] [-l] [-f {console,text,json}] [-o OUTPUT_FILE] [-d]
            [--tests TESTS] [--skips SKIPS] [--disable-includes]
            [--origins-domains domains]
            [--origins-https-only https_only]
            [--add-header-redefinition-headers headers]
            [nginx.conf]

Gixy - a Nginx configuration [sec]analyzer

positional arguments:
  nginx.conf            Path to nginx.conf, e.g. /etc/nginx/nginx.conf

optional arguments:
  -h, --help            show this help message and exit
  -c CONFIG_FILE, --config CONFIG_FILE
                        config file path
  --write-config CONFIG_OUTPUT_PATH
                        takes the current command line args and writes them
                        out to a config file at the given path, then exits
  -v, --version         show program's version number and exit
  -l, --level           Report issues of a given severity level or higher (-l
                        for LOW, -ll for MEDIUM, -lll for HIGH)
  -f {console,text,json}, --format {console,text,json}
                        Specify output format
  -o OUTPUT_FILE, --output OUTPUT_FILE
                        Write report to file
  -d, --debug           Turn on debug mode
  --tests TESTS         Comma-separated list of tests to run
  --skips SKIPS         Comma-separated list of tests to skip
  --disable-includes    Disable "include" directive processing

plugins options:
  --origins-domains domains
                        Default: *
  --origins-https-only https_only
                        Default: False
  --add-header-redefinition-headers headers
                        Default: content-security-policy,x-xss-
                        protection,x-frame-options,x-content-type-
                        options,strict-transport-security,cache-control


available plugins:
	host_spoofing
	add_header_multiline
	http_splitting
	valid_referers
	origins
	add_header_redefinition
	ssrf
```

# Contributing
Contributions to Gixy are always welcome! You can help us in different ways:
  * Open an issue with suggestions for improvements and errors you're facing;
  * Fork this repository and submit a pull request;
  * Improve the documentation.

Code guidelines:
  * Python code style should follow [pep8](https://www.python.org/dev/peps/pep-0008/) standards whenever possible;
  * Pull requests with new plugins must have unit tests for it.


================================================
FILE: README.md
================================================
GIXY
====
[![Mozilla Public License 2.0](https://img.shields.io/github/license/yandex/gixy.svg?style=flat-square)](https://github.com/yandex/gixy/blob/master/LICENSE)
[![Build Status](https://img.shields.io/travis/yandex/gixy.svg?style=flat-square)](https://travis-ci.org/yandex/gixy)
[![Your feedback is greatly appreciated](https://img.shields.io/maintenance/yes/2019.svg?style=flat-square)](https://github.com/yandex/gixy/issues/new)
[![GitHub issues](https://img.shields.io/github/issues/yandex/gixy.svg?style=flat-square)](https://github.com/yandex/gixy/issues)
[![GitHub pull requests](https://img.shields.io/github/issues-pr/yandex/gixy.svg?style=flat-square)](https://github.com/yandex/gixy/pulls)

# Overview
<img align="right" width="192" height="192" src="/docs/logo.png">

Gixy is a tool to analyze Nginx configuration.
The main goal of Gixy is to prevent security misconfiguration and automate flaw detection.

Currently supported Python versions are 2.7, 3.5, 3.6 and 3.7.

Disclaimer: Gixy is well tested only on GNU/Linux, other OSs may have some issues.

# What it can do
Right now Gixy can find:
  * [[ssrf] Server Side Request Forgery](https://github.com/yandex/gixy/blob/master/docs/en/plugins/ssrf.md)
  * [[http_splitting] HTTP Splitting](https://github.com/yandex/gixy/blob/master/docs/en/plugins/httpsplitting.md)
  * [[origins] Problems with referrer/origin validation](https://github.com/yandex/gixy/blob/master/docs/en/plugins/origins.md)
  * [[add_header_redefinition] Redefining of response headers by  "add_header" directive](https://github.com/yandex/gixy/blob/master/docs/en/plugins/addheaderredefinition.md)
  * [[host_spoofing] Request's Host header forgery](https://github.com/yandex/gixy/blob/master/docs/en/plugins/hostspoofing.md)
  * [[valid_referers] none in valid_referers](https://github.com/yandex/gixy/blob/master/docs/en/plugins/validreferers.md)
  * [[add_header_multiline] Multiline response headers](https://github.com/yandex/gixy/blob/master/docs/en/plugins/addheadermultiline.md)
  * [[alias_traversal] Path traversal via misconfigured alias](https://github.com/yandex/gixy/blob/master/docs/en/plugins/aliastraversal.md)

You can find things that Gixy is learning to detect at [Issues labeled with "new plugin"](https://github.com/yandex/gixy/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+plugin%22)

# Installation
Gixy is distributed on [PyPI](https://pypi.python.org/pypi/gixy). The best way to install it is with pip:
```bash
pip install gixy
```

Run Gixy and check results:
```bash
gixy
```

# Usage
By default Gixy will try to analyze Nginx configuration placed in `/etc/nginx/nginx.conf`.

But you can always specify needed path:
```
$ gixy /etc/nginx/nginx.conf

==================== Results ===================

Problem: [http_splitting] Possible HTTP-Splitting vulnerability.
Description: Using variables that can contain "\n" may lead to http injection.
Additional info: https://github.com/yandex/gixy/blob/master/docs/ru/plugins/httpsplitting.md
Reason: At least variable "$action" can contain "\n"
Pseudo config:
include /etc/nginx/sites/default.conf;

	server {

		location ~ /v1/((?<action>[^.]*)\.json)?$ {
			add_header X-Action $action;
		}
	}


==================== Summary ===================
Total issues:
    Unspecified: 0
    Low: 0
    Medium: 0
    High: 1
```

Or skip some tests:
```
$ gixy --skips http_splitting /etc/nginx/nginx.conf

==================== Results ===================
No issues found.

==================== Summary ===================
Total issues:
    Unspecified: 0
    Low: 0
    Medium: 0
    High: 0
```

Or something else, you can find all other `gixy` arguments with the help command: `gixy --help`

## Docker usage

Gixy is available as a Docker image [from the Docker hub](https://hub.docker.com/r/yandex/gixy/). To
use it, mount the configuration that you want to analyse as a volume and provide the path to the
configuration file when running the Gixy image.
```
$ docker run --rm -v `pwd`/nginx.conf:/etc/nginx/conf/nginx.conf yandex/gixy /etc/nginx/conf/nginx.conf
```

If you have an image that already contains your nginx configuration, you can share the configuration
with the Gixy container as a volume.
```
$  docker run --rm --name nginx -d -v /etc/nginx
nginx:alpinef68f2833e986ae69c0a5375f9980dc7a70684a6c233a9535c2a837189f14e905

$  docker run --rm --volumes-from nginx yandex/gixy /etc/nginx/nginx.conf

==================== Results ===================
No issues found.

==================== Summary ===================
Total issues:
    Unspecified: 0
    Low: 0
    Medium: 0
    High: 0

```

# Contributing
Contributions to Gixy are always welcome! You can help us in different ways:
  * Open an issue with suggestions for improvements and errors you're facing;
  * Fork this repository and submit a pull request;
  * Improve the documentation.

Code guidelines:
  * Python code style should follow [pep8](https://www.python.org/dev/peps/pep-0008/) standards whenever possible;
  * Pull requests with new plugins must have unit tests for it.


================================================
FILE: docs/en/plugins/addheadermultiline.md
================================================
# [add_header_multiline] Multiline response headers

You should avoid using multiline response headers, because:
  * they are deprecated (see [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4));
  * some HTTP-clients and web browser never supported them (e.g. IE/Edge/Nginx).

## How can I find it?
Misconfiguration example:
```nginx
# http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header
add_header Content-Security-Policy "
    default-src: 'none';
    script-src data: https://yastatic.net;
    style-src data: https://yastatic.net;
    img-src data: https://yastatic.net;
    font-src data: https://yastatic.net;";

# https://www.nginx.com/resources/wiki/modules/headers_more/
more_set_headers -t 'text/html text/plain'
    'X-Foo: Bar
        multiline';
```

## What can I do?
The only solution is to never use multiline response headers.


================================================
FILE: docs/en/plugins/addheaderredefinition.md
================================================
# [add_header_redefinition] Redefining of response headers by  "add_header" directive

Unfortunately, many people don't know how the inheritance of directives works. Most often this leads to misuse of the `add_header` directive while trying to add a new response header on the nested level.
This feature is mentioned in Nginx [docs](http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header):
> There could be several `add_header` directives. These directives are inherited from the previous level if and only if there are no `add_header` directives defined on the current level.

The logic is quite simple: if you set headers at one level (for example, in `server` section) and then at a lower level (let's say `location`) you set some other headers, then the first headers will discarded.

It's easy to check:
  - Configuration:
```nginx
server {
  listen 80;
  add_header X-Frame-Options "DENY" always;
  location / {
      return 200 "index";
  }

  location /new-headers {
    # Add special cache control
    add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate" always;
    add_header Pragma "no-cache" always;

    return 200 "new-headers";
  }
}
```
  - Request to location `/` (`X-Frame-Options` header is in server response):
```http
GET / HTTP/1.0

HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Mon, 09 Jan 2017 19:28:33 GMT
Content-Type: application/octet-stream
Content-Length: 5
Connection: close
X-Frame-Options: DENY

index
```
  - Request to location `/new-headers` (headers `Cache-Control` and `Pragma` are present, but there's no `X-Frame-Options`):
```http
GET /new-headers HTTP/1.0


HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Mon, 09 Jan 2017 19:29:46 GMT
Content-Type: application/octet-stream
Content-Length: 11
Connection: close
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache

new-headers
```

## What can I do?
There are several ways to solve this problem:
 - duplicate important headers;
 - set all headers at one level (`server` section is a good choice)
 - use [ngx_headers_more](https://www.nginx.com/resources/wiki/modules/headers_more/) module.


================================================
FILE: docs/en/plugins/aliastraversal.md
================================================
# [alias_traversal] Path traversal via misconfigured alias

The [alias](https://nginx.ru/en/docs/http/ngx_http_core_module.html#alias) directive is used to replace path of the specified location.
For example, with the following configuration:
```nginx
location /i/ {
    alias /data/w3/images/;
}
```
on request of `/i/top.gif`, the file `/data/w3/images/top.gif` will be sent.

But, if the location doesn't ends with directory separator (i.e. `/`):
```nginx
location /i {
    alias /data/w3/images/;
}
```
on request of `/i../app/config.py`, the file `/data/w3/app/config.py` will be sent.

In other words, the incorrect configuration of `alias` could allow an attacker to read file stored outside the target folder.

## What can I do?
It's pretty simple:
  - you must find all the `alias` directives;
  - make sure that the parent prefixed location ends with directory separator.
  - or if you want to map a single file make sure the location starts with a `=`, e.g `=/i.gif` instead of `/i.gif`.


================================================
FILE: docs/en/plugins/hostspoofing.md
================================================
# [host_spoofing] Request's Host header forgery

Often, an application located behind Nginx needs a correct `Host` header for URL generation (redirects, resources, links in emails etc.).
Spoofing of this header, may leads to a variety of problems, from phishing to SSRF.

> Notice: your application may also use the `X-Forwarded-Host` request header for this functionality.
> In this case you have to ensure the header is set correctly;

## How can I find it?
Most of the time it's a result of using `$http_host` variable instead of `$host`.

And they are quite different:
  * `$host` - host in this order of precedence: host name from the request line, or host name from the “Host” request header field, or the server name matching a request;
  * `$http_host` - "Host" request header.

Config sample:
```nginx
location @app {
  proxy_set_header Host $http_host;
  # Other proxy params
  proxy_pass http://backend;
}
```

## What can I do?
Luckily, all is quite obvious:
 * list all the correct server names in `server name` directive;
 * always use `$host` instead of `$http_host`.

## Additional info
  * [Host of Troubles Vulnerabilities](https://hostoftroubles.com/)
  * [Practical HTTP Host header attacks](http://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html)


================================================
FILE: docs/en/plugins/httpsplitting.md
================================================
# [http_splitting] HTTP Splitting

HTTP Splitting - attack that use improper input validation. It usually targets web application located behind Nginx (HTTP Request Splitting) or its users (HTTP Response Splitting).

Vulnerability is created when an attacker can insert newline character `\n` or `\r` into request or into response, created by Nginx.

## How can I find it?
You should always pay attention to:
 - variables that are used in directives, responsible for the request creation (for they may contain CRLF), e.g. `rewrite`, `return`, `add_header`, `proxy_set_header` or `proxy_pass`;
 - `$uri` and `$document_uri` variables, and in which directives they are used, because these variables contain decoded URL-encoded value;
 - variables, that are selected from an exclusive range, e.g. `(?P<myvar>[^.]+)`.


An example of configuration that contains variable, selected from an exclusive range:
```nginx
server {
    listen 80 default;

    location ~ /v1/((?<action>[^.]*)\.json)?$ {
        add_header X-Action $action;
        return 200 "OK";
    }
}
```

Exploitation:
```http
GET /v1/see%20below%0d%0ax-crlf-header:injected.json HTTP/1.0
Host: localhost

HTTP/1.1 200 OK
Server: nginx/1.11.10
Date: Mon, 13 Mar 2017 21:21:29 GMT
Content-Type: application/octet-stream
Content-Length: 2
Connection: close
X-Action: see below
x-crlf-header:injected

OK
```

As you can see, an attacker could add `x-crlf-header: injected` response header. This was possible because:
  - `add_header` doesn't encode or validate input value on suggestion that author knows about the consequences;
  - the path value is normalize before location processing;
  - `$action` value was given from a regexp with an exclusive range: `[^.]*`;
  - as the result, `$action` value is equal to `see below\r\nx-crlf-header:injected` and on its use the response header was added.

## What can I do?
  - try to use safe variables, e.g. `$request_uri` instead of `$uri`;
  - forbid the use of the new line symbol in the exclusive range by using `/some/(?<action>[^/\s]+)` instead of `/some/(?<action>[^/]+`
  - it could be a good idea to validate `$uri` (only if you're sure you know what are you getting into).


================================================
FILE: docs/en/plugins/origins.md
================================================
# [origins] Problems with referrer/origin validation

It's not unusual to use regex for `Referer` or `Origin` headers validation.
Often it is needed for setting the `X-Frame-Options` header (ClickJacking protection) or Cross-Origin Resource Sharing.

The most common errors with this configuration are:
  - regex errors;
  - allow 3rd-party origins.

 > Notice: by default Gixy doesn't check regexes for 3rd-party origins matching.
 > You can pass a list of trusted domains by using the option `--origins-domains example.com,foo.bar`

## How can I find it?
"Eazy"-breezy:
  - you have to find all the `if` directives that are in charge of `$http_origin` or `$http_referer` check;
  - make sure your regexes are a-ok.

Misconfiguration example:
```nginx
if ($http_origin ~* ((^https://www\.yandex\.ru)|(^https://ya\.ru)$)) {
	add_header 'Access-Control-Allow-Origin' "$http_origin";
	add_header 'Access-Control-Allow-Credentials' 'true';
}
```

TODO(buglloc): cover typical regex-writing problems
TODO(buglloc): Regex Ninja?

## What can I do?

  - fix your regex or toss it away :)
  - if you use regex validation for `Referer` request header, then, possibly (not 100%), you could use [ngx_http_referer_module](http://nginx.org/en/docs/http/ngx_http_referer_module.htmll);
  - sometimes it is much better to use the `map` directive without any regex at all.


================================================
FILE: docs/en/plugins/ssrf.md
================================================
# [ssrf] Server Side Request Forgery

Server Side Request Forgery - attack that forces a server to perform arbitrary requests (from Nginx in our case).
It's possible when an attacker controls the address of a proxied server (second argument of the `proxy_pass` directive).


## How can I find it?
There are two types of errors that make a server vulnerable:
  - lack of the [internal](http://nginx.org/en/docs/http/ngx_http_core_module.html#internal) directive. It is used to point out a location that can be used for internal requests only;
  - unsafe internal redirection.

### Lack of the internal directive
Classical misconfiguration, based on lack of the `internal` directive, that makes SSRF possible:
```nginx
location ~ /proxy/(.*)/(.*)/(.*)$ {
    proxy_pass $1://$2/$3;
}
```

An attacker has complete control over the proxied address, that makes sending requests on behalf of Nginx possible.

### Unsafe internal redirection
Let's say you have internal location in your config and that location uses some request data as proxied server's address.

E.g.:
```nginx
location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?<proxy_path>.*)$ {
    internal;

    proxy_pass $proxy_proto://$proxy_host/$proxy_path ;
    proxy_set_header Host $proxy_host;
}
```

According to Nginx docs, internal requests are the following:
>  - requests redirected by the **error_page**, index, random_index, and **try_files** directives;
>  - requests redirected by the “X-Accel-Redirect” response header field from an upstream server;
>  - subrequests formed by the “include virtual” command of the ngx_http_ssi_module module and by the ngx_http_addition_module module directives;
>  - requests changed by the **rewrite** directive

Accordingly, any unsafe rewrite allows an attacker to make an internal request and control a proxied server's address.

Misconfiguration example:
```nginx
rewrite ^/(.*)/some$ /$1/ last;

location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?<proxy_path>.*)$ {
    internal;

    proxy_pass $proxy_proto://$proxy_host/$proxy_path ;
    proxy_set_header Host $proxy_host;
}
```

## What can I do?
There are several rules you better follow when writing such configurations:
  - use only "internal locations" for proxying;
  - if possible, forbid user data transmission;
  - protect proxied server's address:
    * if the quantity of proxied hosts is limited (when you have S3 or smth), you better hardcode them and choose them with `map` or do it some other way;
    * if you can' list all possible hosts to proxy, you should sign the address.


================================================
FILE: docs/en/plugins/validreferers.md
================================================
# [valid_referers] none in valid_referers
Module [ngx_http_referer_module](http://nginx.org/en/docs/http/ngx_http_referer_module.html) allows to block the access to service for requests with wrong `Referer` value.
It's often used for setting `X-Frame-Options` header (ClickJacking protection), but there may be other cases.

Typical problems with this module's config:
  * use of `server_names` with bad server name (`server_name` directive);
  * too broad and/or bad regexes;
  * use of `none`.

> Notice: at the moment, Gixy can only detect the use of `none` as a valid referer.

## Why none is bad?
According to [docs](http://nginx.org/ru/docs/http/ngx_http_referer_module.html#valid_referers):
> `none` - the “Referer” field is missing in the request header;

Still, it's important to remember that any resource can make user's browser to make a request without a `Referer` request header.
E.g.:
  - in case of redirect from HTTPS to HTTP;
  - by setting up the [Referrer Policy](https://www.w3.org/TR/referrer-policy/);
  - a request with opaque origin, `data:` scheme, for example.

So, by using `none` as a valid referer, you nullify any attemps in refferer validation.

================================================
FILE: docs/ru/plugins/addheadermultiline.md
================================================
# [add_header_multiline] Многострочные заголовоки ответа

Многострочных заголовков ответа стоит избегать по нескольким причинам:
  * они признаны устаревшими (см. [RFC 7230](https://tools.ietf.org/html/rfc7230#section-3.2.4));
  * они никогда не поддерживались многими HTTP-клиентами и браузерами. Например, IE/Edge/Nginx.

## Как самостоятельно обнаружить?
Пример плохой конфигурации:
```nginx
# http://nginx.org/ru/docs/http/ngx_http_headers_module.html#add_header
add_header Content-Security-Policy "
    default-src: 'none';
    script-src data: https://yastatic.net;
    style-src data: https://yastatic.net;
    img-src data: https://yastatic.net;
    font-src data: https://yastatic.net;";

# https://www.nginx.com/resources/wiki/modules/headers_more/
more_set_headers -t 'text/html text/plain'
    'X-Foo: Bar
        multiline';
```

## Что делать?
Единственный выход - отказ от многострочных заголовок ответа.

================================================
FILE: docs/ru/plugins/addheaderredefinition.md
================================================
# [add_header_redefinition] Переопределение "вышестоящих" заголовков ответа директивой "add_header"

К сожалению, многие считают что с помощью директивы `add_header` можно произвольно доопределять заголовки ответа.
Это не так, о чем сказано в [документации](http://nginx.org/ru/docs/http/ngx_http_headers_module.html#add_header) к Nginx:
> Директив `add_header` может быть несколько. Директивы наследуются с предыдущего уровня при условии, что на данном уровне не описаны свои директивы `add_header`.

К слову, так работает наследование большинства директив в nginx'е. Если вы задаёте что-то на каком-то уровне конфигурации (например, в локейшене), то наследования с предыдущих уровней (например, с http секции) - не будет.

В этом довольно легко убедится:
  - Конфигурация:
```nginx
server {
  listen 80;
  add_header X-Frame-Options "DENY" always;
  location / {
      return 200 "index";
  }

  location /new-headers {
    # Add special cache control
    add_header Cache-Control "no-cache, no-store, max-age=0, must-revalidate" always;
    add_header Pragma "no-cache" always;

    return 200 "new-headers";
  }
}
```
  - Запрос к локейшену `/` (заголовок `X-Frame-Options` есть в ответе сервера):
```http
GET / HTTP/1.0

HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Mon, 09 Jan 2017 19:28:33 GMT
Content-Type: application/octet-stream
Content-Length: 5
Connection: close
X-Frame-Options: DENY

index
```
  - Запрос к локейшену `/new-headers` (есть заголовки `Cache-Control` и `Pragma`, но нет `X-Frame-Options`):
```http
GET /new-headers HTTP/1.0


HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Mon, 09 Jan 2017 19:29:46 GMT
Content-Type: application/octet-stream
Content-Length: 11
Connection: close
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache

new-headers
```

## Что делать?
Существует несколько способов решить эту проблему:
  - продублировать важные заголовки;
  - устанавливать заголовки на одном уровне, например, в серверной секции;
  - использовать модуль [ngx_headers_more](https://www.nginx.com/resources/wiki/modules/headers_more/).

Каждый из способов имеет свои преимущества и недостатки, какой предпочесть зависит от ваших потребностей. 

================================================
FILE: docs/ru/plugins/aliastraversal.md
================================================
# [alias_traversal] Path traversal при использовании alias

Директива [alias](https://nginx.ru/ru/docs/http/ngx_http_core_module.html#alias) используется для замены пути указанного локейшена.
К примеру, для конфигурации:
```nginx
location /i/ {
    alias /data/w3/images/;
}
```
на запрос `/i/top.gif` будет отдан файл `/data/w3/images/top.gif`.

Однако, если локейшен не оканчивается разделителем директорий (`/`):
```nginx
location /i {
    alias /data/w3/images/;
}
```
то на запрос `/i../app/config.py` будет отдан файл `/data/w3/app/config.py`.

Иными словами, не корректная конфигурация `alias` может позволить злоумышленнику прочесть файл за пределами целевой директории.

## Что делать?
Все довольно просто:
  - необходимо найти все директивы `alias`;
  - убедится что вышестоящий префиксный локейшен оканчивается на `/`.


================================================
FILE: docs/ru/plugins/hostspoofing.md
================================================
# [host_spoofing] Подделка заголовка запроса Host

Зачастую, приложению, стоящему за Nginx, необходимо передать корректный заголовок `Host` для корректной генерации различных URL-адресов (редиректы, ресурсы, ссылки в письмах и т.д.).
Возможность его подмены злоумышленником может повлечь множестве проблем от фишинговых атак до SSRF, поэтому следует избегать таких ситуаций.

> Возможно, ваше приложение так же ориентируется на заголовок запроса `X-Forwarded-Host`.
> В этом случае вам необходимо самостоятельно позаботится о его корректной установке при проксировании.

## Как самостоятельно обнаружить?
Чаще всего эта проблема возникает в результате использования переменной `$http_host` вместо `$host`.

Несмотря на их схожесть, они сильно отличаются:
  * `$host` - хост в порядке приоритета: имя хоста из строки запроса, или имя хоста из заголовка `Host` заголовка запроса, или имя сервера, соответствующего запросу;
  * `$http_host` - заголовок запроса "Host".

Пример такой конфигурации:
```nginx
location @app {
  proxy_set_header Host $http_host;
  # Other proxy params
  proxy_pass http://backend;
}
```

## Что делать?
К счастью, все довольно очевидно:
  * перечислить корректные имена сервера в директиве `server_name`;
  * всегда использовать переменную `$host`, вместо `$http_host`.

## Дополнительная информация
  * [Host of Troubles Vulnerabilities](https://hostoftroubles.com/)
  * [Practical HTTP Host header attacks](http://www.skeletonscribe.net/2013/05/practical-http-host-header-attacks.html)


================================================
FILE: docs/ru/plugins/httpsplitting.md
================================================
# [http_splitting] HTTP Splitting

HTTP Splitting - уязвимость, возникающая из-за неправильной обработки входных данных.
Зачастую может быть для атак на приложение стоящее за Nginx (HTTP Request Splitting) или на клиентов приложения (HTTP Response Splitting).

Уязвимость возникает в случае, когда атакующий может внедрить символ перевода строки `\n` или `\r` в запрос или ответ формируемый Nginx.

## Как самостоятельно обнаружить?
При анализе конфигурации всега стоит обращать внимание на:
  - какие переменные используются в директивах, отвечающих за формирование запросов (могут ли они содержать CRLF), например: `rewrite`, `return`, `add_header`, `proxy_set_header` или `proxy_pass`;
  - используются ли переменные `$uri` и `$document_uri` и если да, то в каких директивах, т.к. они гарантированно содержат урлдекодированное значение;
  - переменные, выделенные из групп с исключающим диапазоном: `(?P<myvar>[^.]+)`.

Пример плохой конфигурации с переменной, полученной из группы с исключающим диапазоном:
```nginx
server {
    listen 80 default;

    location ~ /v1/((?<action>[^.]*)\.json)?$ {
        add_header X-Action $action;
        return 200 "OK";
    }
}
```

Пример эксплуатации данной конфигурации:
```http
GET /v1/see%20below%0d%0ax-crlf-header:injected.json HTTP/1.0
Host: localhost

HTTP/1.1 200 OK
Server: nginx/1.11.10
Date: Mon, 13 Mar 2017 21:21:29 GMT
Content-Type: application/octet-stream
Content-Length: 2
Connection: close
X-Action: see below
x-crlf-header:injected

OK
```

Из примера видно, что злоумышленник смог добавить заголовок ответа `x-crlf-header: injected`. Это случилось благодаря стечению нескольких обстоятельств:
  - `add_header` не кодирует/валидирует переданные ему значения, считая что автор знает о последствиях;
  - значение пути нормализуется перед обработкой локейшена;
  - переменная `$action` была выделена из группы регулярного выражения с исключающим диапазоном: `[^.]*`;
  - таким образом, значение переменной `$action` равно `see below\r\nx-crlf-header:injected` и при её использовании в формировании ответа добавился заголовок.

## Что делать?
  - старайтесь использовать более безопасные переменные, например, `$request_uri` вместо `$uri`;
  - запретите перевод строки в исключающем диапазоне, например, `/some/(?<action>[^/\s]+)` вместо `/some/(?<action>[^/]+`;
  - возможно, хорошей идеей будет добавить валидацию `$uri` (только если вы знаете, что делаете).


================================================
FILE: docs/ru/plugins/origins.md
================================================
# [origins] Проблемы валидации referrer/origin

Нередко валидация заголовка запроса `Referer` или `Origin` делается при помощи регулярного выражения.
Зачастую, это необходимо для условного выставления заголовка `X-Frame-Options` (защита от ClickJacking) или реализации Cross-Origin Resource Sharing.

Наиболее распространенно два класса ошибок конфигурации, которые приводят к этой проблеме:
  - ошибки в составлении регулярного выражения;
  - разрешение не доверенных third-party доменов.

> По умолчанию Gixy не валидирует регулярные выражение на предмет матчинга third-party доменов, т.к. не знает кому можно верить.
Передать список доверенных доменом можно при помощи опции `--origins-domains example.com,foo.bar`

## Как самостоятельно обнаружить?
Все довольно "просто":
  - необходимо найти все директивы `if`, которые делают проверку переменной `$http_origin` или `$http_referer`;
  - убедится что в регулярном выражении нет проблем.

Пример плохой конфигурации:
```nginx
if ($http_origin ~* ((^https://www\.yandex\.ru)|(^https://ya\.ru)$)) {
	add_header 'Access-Control-Allow-Origin' "$http_origin";
	add_header 'Access-Control-Allow-Credentials' 'true';
}
```

TODO(buglloc): описать типичные проблемы при составлении регулярных выражений
TODO(buglloc): Regex Ninja?

## Что делать?
  - исправить регулярное выражение или отказаться от него вовсе :)
  - если вы проверяете заголовок запроса `Referer` то, возможно (имеются противопоказания), лучшим решением было бы воспользоваться модулем [ngx_http_referer_module](http://nginx.org/ru/docs/http/ngx_http_referer_module.html);
  - если вы проверяете заголовов запроса `Origin` то, зачастую, лучше использовать `map` и отказаться от регулярных выражений.


================================================
FILE: docs/ru/plugins/ssrf.md
================================================
# [ssrf] Server Side Request Forgery

Server Side Request Forgery - уязвимость, позволяющая выполнять различного рода запросы от имени веб-приложения (в нашем случае от имени Nginx).
Возникает, когда атакующий может контролировать адрес проксируемого сервера (второй аргумент директивы `proxy_pass`).


## Как самостоятельно обнаружить?
Наиболее распространенно два класса ошибок конфигурации, которые приводят к этой проблеме:
  - отсутствие директивы [internal](http://nginx.org/ru/docs/http/ngx_http_core_module.html#internal). Её смысл заключается в указании того, что определенный location может использоваться только для внутренних запросов;
  - небезопасное внутреннее перенаправление.

### Отсутствие директивы internal
Классический пример уязвимости типа SSRF в виду отсутствия директивы `internal` выглядит следующим образом:
```nginx
location ~ /proxy/(.*)/(.*)/(.*)$ {
    proxy_pass $1://$2/$3;
}
```
Злоумышленник, полностью контролируя адрес проксируемого сервера, может выполнять произвольные запросы от имени Nginx.

### Небезопасное внутреннее перенаправление
Подразумевается, что в вашей конфигурации есть internal location, которые использует какие-либо данные из запроса в качестве адреса проксируемого сервера.

Например:
```nginx
location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?<proxy_path>.*)$ {
    internal;

    proxy_pass $proxy_proto://$proxy_host/$proxy_path ;
    proxy_set_header Host $proxy_host;
}
```

Согласно документации Nginx внутренними запросами являются:
>  - запросы, перенаправленные директивами **error_page**, index, random_index и **try_files**;
>  - запросы, перенаправленные с помощью поля “X-Accel-Redirect” заголовка ответа вышестоящего сервера;
>  - подзапросы, формируемые командой “include virtual” модуля ngx_http_ssi_module и директивами модуля ngx_http_addition_module;
>  - запросы, изменённые директивой **rewrite**.]>

Соответственно, любой "неосторожный" реврайт позволит злоумышленнику сделать внутренний запрос и контролировать адрес проксируемого сервера.

Пример плохой конфигурации:
```nginx
rewrite ^/(.*)/some$ /$1/ last;

location ~* ^/internal-proxy/(?<proxy_proto>https?)/(?<proxy_host>.*?)/(?<proxy_path>.*)$ {
    internal;

    proxy_pass $proxy_proto://$proxy_host/$proxy_path ;
    proxy_set_header Host $proxy_host;
}
```

## Что делать?
Есть несколько правил, которых стоит придерживаться в подобного рода конфигурациях:
  - использовать только internal location для проксирования;
  - по возможности запретить передачу пользовательских данных;
  - обезопасить адрес проксируемого сервера:
    * если количество проксируемых хостов ограниченно (например, у вас S3), то лучше их захардкодить и выбирать при помощи `map` или иным удобным для вас образом;
    * если по какой-то причине нет возможности перечислить все возможные хосты для проксирования, его стоит подписать.


================================================
FILE: docs/ru/plugins/validreferers.md
================================================
# [valid_referers] none in valid_referers

Модуль [ngx_http_referer_module](http://nginx.org/ru/docs/http/ngx_http_referer_module.html) позволяет блокировать доступ к сервису для запросов с неверными значениями заголовка запроса `Referer`.
Зачастую используется для условного выставления заголовка `X-Frame-Options` (защита от ClickJacking), но могут быть и иные случаи.

Типичные проблемы при конфигурировании этого модуля:
  * использование `server_names` при не корректном имени сервера (директива `server_name`);
  * слишком общие и/или не корректные регулярные выражения;
  * использование `none`.

> На текущий момент, Gixy умеет определять только использование `none` в качестве валидного реферера.

## Чем плох none?
Согласно [документации](http://nginx.org/ru/docs/http/ngx_http_referer_module.html#valid_referers):
> `none` - поле “Referer” в заголовке запроса отсутствует;

Однако, важно помнить, что любой ресурс может заставить браузер пользователя выполнить запрос без заголовка запроса `Referer`, к примеру:
  - в случае редиректа с HTTPS на HTTP;
  - указав соответствующую [Referrer Policy](https://www.w3.org/TR/referrer-policy/);
  - обращение с opaque origin, например, используя схему `data:`.

Таким образом, используя `none` в качестве валидного реферера вы сводите на нет любые попытки валидации реферера.

================================================
FILE: gixy/__init__.py
================================================
# flake8: noqa

from gixy.core import severity

version = '0.1.21'


================================================
FILE: gixy/cli/__init__.py
================================================


================================================
FILE: gixy/cli/argparser.py
================================================
# flake8: noqa

from configargparse import *
from six.moves import StringIO

from gixy.core.plugins_manager import PluginsManager

# used while parsing args to keep track of where they came from
_COMMAND_LINE_SOURCE_KEY = 'command_line'
_ENV_VAR_SOURCE_KEY = 'environment_variables'
_CONFIG_FILE_SOURCE_KEY = 'config_file'
_DEFAULTS_SOURCE_KEY = 'defaults'


class GixyConfigParser(DefaultConfigFileParser):
    def get_syntax_description(self):
        return ''

    def parse(self, stream):
        """Parses the keys + values from a config file."""

        items = OrderedDict()
        prefix = ''
        for i, line in enumerate(stream):
            line = line.strip()
            if not line or line[0] in ['#', ';'] or line.startswith('---'):
                continue
            if line[0] == '[':
                prefix = '%s-' % line[1:-1].replace('_', '-')
                continue

            white_space = '\\s*'
            key = '(?P<key>[^:=;#\s]+?)'
            value = white_space + '[:=\s]' + white_space + '(?P<value>.+?)'
            comment = white_space + '(?P<comment>\\s[;#].*)?'

            key_only_match = re.match('^' + key + comment + '$', line)
            if key_only_match:
                key = key_only_match.group('key')
                items[key] = 'true'
                continue

            key_value_match = re.match('^' + key + value + comment + '$', line)
            if key_value_match:
                key = key_value_match.group('key')
                value = key_value_match.group('value')

                if value.startswith('[') and value.endswith(']'):
                    # handle special case of lists
                    value = [elem.strip() for elem in value[1:-1].split(',')]

                items[prefix + key] = value
                continue

            raise ConfigFileParserException('Unexpected line %s in %s: %s' % (i,
                                                                              getattr(stream, 'name', 'stream'), line))
        return items

    def serialize(self, items):
        """Does the inverse of config parsing by taking parsed values and
        converting them back to a string representing config file contents.
        """
        r = StringIO()
        for key, value in items.items():
            if type(value) == OrderedDict:
                r.write('\n[%s]\n' % key)
                r.write(self.serialize(value))
            else:
                value, help = value
                if help:
                    r.write('; %s\n' % help)
                r.write('%s = %s\n' % (key, value))
        return r.getvalue()


class GixyHelpFormatter(HelpFormatter):
    def format_help(self):
        manager = PluginsManager()
        help_message = super(GixyHelpFormatter, self).format_help()
        if 'plugins options:' in help_message:
            # Print available blugins _only_ if we prints options for it
            plugins = '\n'.join('\t' + plugin.__name__ for plugin in manager.plugins_classes)
            help_message = '{orig}\n\navailable plugins:\n{plugins}\n'.format(orig=help_message, plugins=plugins)
        return help_message


class ArgsParser(ArgumentParser):
    def get_possible_config_keys(self, action):
        """This method decides which actions can be set in a config file and
        what their keys will be. It returns a list of 0 or more config keys that
        can be used to set the given action's value in a config file.
        """
        keys = []
        for arg in action.option_strings:
            if arg in ['--config', '--write-config', '--version']:
                continue
            if any([arg.startswith(2 * c) for c in self.prefix_chars]):
                keys += [arg[2:], arg]  # eg. for '--bla' return ['bla', '--bla']

        return keys

    def get_items_for_config_file_output(self, source_to_settings,
                                         parsed_namespace):
        """Converts the given settings back to a dictionary that can be passed
        to ConfigFormatParser.serialize(..).

        Args:
            source_to_settings: the dictionary described in parse_known_args()
            parsed_namespace: namespace object created within parse_known_args()
        Returns:
            an OrderedDict where keys are strings and values are either strings
            or lists
        """
        config_file_items = OrderedDict()
        for source, settings in source_to_settings.items():
            if source == _COMMAND_LINE_SOURCE_KEY:
                _, existing_command_line_args = settings['']
                for action in self._actions:
                    config_file_keys = self.get_possible_config_keys(action)
                    if config_file_keys and not action.is_positional_arg and \
                        already_on_command_line(existing_command_line_args,
                                                action.option_strings):
                        value = getattr(parsed_namespace, action.dest, None)
                        if value is not None:
                            if type(value) is bool:
                                value = str(value).lower()
                            if ':' in action.dest:
                                section, key = action.dest.split(':', 2)
                                key = key.replace('_', '-')
                                if section not in config_file_items:
                                    config_file_items[section] = OrderedDict()
                                config_file_items[section][key] = (value, action.help)
                            else:
                                config_file_items[config_file_keys[0]] = (value, action.help)
            elif source.startswith(_CONFIG_FILE_SOURCE_KEY):
                for key, (action, value) in settings.items():
                    if ':' in action.dest:
                        section, key = action.dest.split(':', 2)
                        key = key.replace('_', '-')
                        if section not in config_file_items:
                            config_file_items[section] = OrderedDict()
                        config_file_items[section][key] = (value, action.help)
                    else:
                        config_file_items[key] = (value, action.help)
        return config_file_items


def create_parser():
    return ArgsParser(
        description='Gixy - a Nginx configuration [sec]analyzer\n\n',
        formatter_class=GixyHelpFormatter,
        config_file_parser_class=GixyConfigParser,
        auto_env_var_prefix='GIXY_',
        add_env_var_help=False,
        default_config_files=['/etc/gixy/gixy.cfg', '~/.config/gixy/gixy.conf'],
        args_for_setting_config_path=['-c', '--config'],
        args_for_writing_out_config_file=['--write-config'],
        add_config_file_help=False
    )


================================================
FILE: gixy/cli/main.py
================================================
import os
import sys
import logging
import copy

import gixy
from gixy.core.manager import Manager as Gixy
from gixy.formatters import get_all as formatters
from gixy.core.plugins_manager import PluginsManager
from gixy.core.config import Config
from gixy.cli.argparser import create_parser
from gixy.core.exceptions import InvalidConfiguration

LOG = logging.getLogger()


def _init_logger(debug=False):
    LOG.handlers = []
    log_level = logging.DEBUG if debug else logging.INFO

    LOG.setLevel(log_level)
    handler = logging.StreamHandler(sys.stderr)
    handler.setFormatter(logging.Formatter('[%(module)s]\t%(levelname)s\t%(message)s'))
    LOG.addHandler(handler)
    LOG.debug("logging initialized")


def _create_plugin_help(option):
    if isinstance(option, (tuple, list, set)):
        default = ','.join(list(option))
    else:
        default = str(option)

    return 'Default: {0}'.format(default)


def _get_cli_parser():
    parser = create_parser()
    parser.add_argument('nginx_files', nargs='*', type=str, default=['/etc/nginx/nginx.conf'], metavar='nginx.conf',
                        help='Path to nginx.conf, e.g. /etc/nginx/nginx.conf')

    parser.add_argument(
        '-v', '--version', action='version',
        version='Gixy v{0}'.format(gixy.version))

    parser.add_argument(
        '-l', '--level', dest='level', action='count', default=0,
        help='Report issues of a given severity level or higher (-l for LOW, -ll for MEDIUM, -lll for HIGH)')

    default_formatter = 'console' if sys.stdout.isatty() else 'text'
    available_formatters = formatters().keys()
    parser.add_argument(
        '-f', '--format', dest='output_format', choices=available_formatters, default=default_formatter,
        type=str, help='Specify output format')

    parser.add_argument(
        '-o', '--output', dest='output_file', type=str,
        help='Write report to file')

    parser.add_argument(
        '-d', '--debug', dest='debug', action='store_true', default=False,
        help='Turn on debug mode')

    parser.add_argument(
        '--tests', dest='tests', type=str,
        help='Comma-separated list of tests to run')

    parser.add_argument(
        '--skips', dest='skips', type=str,
        help='Comma-separated list of tests to skip')

    parser.add_argument(
        '--disable-includes', dest='disable_includes', action='store_true', default=False,
        help='Disable "include" directive processing')

    group = parser.add_argument_group('plugins options')
    for plugin_cls in PluginsManager().plugins_classes:
        name = plugin_cls.__name__
        if not plugin_cls.options:
            continue

        options = copy.deepcopy(plugin_cls.options)
        for opt_key, opt_val in options.items():
            option_name = '--{plugin}-{key}'.format(plugin=name, key=opt_key).replace('_', '-')
            dst_name = '{plugin}:{key}'.format(plugin=name, key=opt_key)
            opt_type = str if isinstance(opt_val, (tuple, list, set)) else type(opt_val)
            group.add_argument(
                option_name, metavar=opt_key, dest=dst_name, type=opt_type,
                help=_create_plugin_help(opt_val)
            )

    return parser


def main():
    parser = _get_cli_parser()
    args = parser.parse_args()
    _init_logger(args.debug)

    if len(args.nginx_files) == 1 and args.nginx_files[0] != '-':
        path = os.path.expanduser(args.nginx_files[0])
        if not os.path.exists(path):
            sys.stderr.write('File {path!r} was not found.\nPlease specify correct path to configuration.\n'.format(
                path=path))
            sys.exit(1)

    try:
        severity = gixy.severity.ALL[args.level]
    except IndexError:
        sys.stderr.write('Too high level filtering. Maximum level: -{0}\n'.format('l' * (len(gixy.severity.ALL) - 1)))
        sys.exit(1)

    if args.tests:
        tests = [x.strip() for x in args.tests.split(',')]
    else:
        tests = None

    if args.skips:
        skips = [x.strip() for x in args.skips.split(',')]
    else:
        skips = None

    config = Config(
        severity=severity,
        output_format=args.output_format,
        output_file=args.output_file,
        plugins=tests,
        skips=skips,
        allow_includes=not args.disable_includes
    )

    for plugin_cls in PluginsManager().plugins_classes:
        name = plugin_cls.__name__
        options = copy.deepcopy(plugin_cls.options)
        for opt_key, opt_val in options.items():
            option_name = '{name}:{key}'.format(name=name, key=opt_key)
            if option_name not in args:
                continue

            val = getattr(args, option_name)
            if val is None:
                continue

            if isinstance(opt_val, tuple):
                val = tuple([x.strip() for x in val.split(',')])
            elif isinstance(opt_val, set):
                val = set([x.strip() for x in val.split(',')])
            elif isinstance(opt_val, list):
                val = [x.strip() for x in val.split(',')]
            options[opt_key] = val
        config.set_for(name, options)

    formatter = formatters()[config.output_format]()
    failed = False
    for input_path in args.nginx_files:
        path = os.path.abspath(os.path.expanduser(input_path))
        if not os.path.exists(path):
            LOG.error('File %s was not found', path)
            continue

        with Gixy(config=config) as yoda:
            try:
                if path == '-':
                    with os.fdopen(sys.stdin.fileno(), 'rb') as fdata:
                        yoda.audit('<stdin>', fdata, is_stdin=True)
                else:
                    with open(path, mode='rb') as fdata:
                        yoda.audit(path, fdata, is_stdin=False)
            except InvalidConfiguration:
                failed = True
            formatter.feed(path, yoda)
            failed = failed or sum(yoda.stats.values()) > 0

    if args.output_file:
        with open(config.output_file, 'w') as f:
            f.write(formatter.flush())
    else:
        print(formatter.flush())

    if failed:
        # If something found - exit code must be 1, otherwise 0
        sys.exit(1)
    sys.exit(0)


================================================
FILE: gixy/core/__init__.py
================================================


================================================
FILE: gixy/core/builtin_variables.py
================================================
from gixy.core.regexp import Regexp
from gixy.core.variable import Variable

BUILTIN_VARIABLES = {
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_uri
    'uri': r'/[^\x20\t]*',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_document_uri
    'document_uri': r'/[^\x20\t]*',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_arg_
    'arg_': r'[^\s&]+',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_args
    'args': r'[^\s]+',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_query_string
    'query_string': r'[^\s]+',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_uri
    'request_uri': r'/[^\s]*',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_http_
    'http_': r'[\x21-\x7e]',

    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_http_
    'upstream_http_': '',
    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_cookie_
    'upstream_cookie_': '',
    # http://nginx.org/en/docs/http/ngx_http_proxy_module.html#var_proxy_add_x_forwarded_for
    'proxy_add_x_forwarded_for': '',
    # http://nginx.org/en/docs/http/ngx_http_proxy_module.html#var_proxy_host
    'proxy_host': '',
    # http://nginx.org/en/docs/http/ngx_http_proxy_module.html#var_proxy_port
    'proxy_port': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_proxy_protocol_addr
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_proxy_protocol_addr
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_proxy_protocol_port
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_proxy_protocol_port
    'proxy_protocol_port': '',
    # http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#var_fastcgi_path_info
    'fastcgi_path_info': '',
    # http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#var_fastcgi_script_name
    'fastcgi_script_name': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_content_type
    'content_type': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_cookie_
    'cookie_': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_host
    'host': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_hostname
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_hostname
    'hostname': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_limit_rate
    'limit_rate': '',
    # http://nginx.org/en/docs/http/ngx_http_memcached_module.html#var_memcached_key
    'memcached_key': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_realpath_root
    'realpath_root': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_remote_user
    'remote_user': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request
    'request': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_body
    'request_body': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_completion
    'request_completion': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_filename
    'request_filename': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_id
    'request_id': '',
    # http://nginx.org/en/docs/http/ngx_http_slice_module.html#var_slice_range
    'slice_range': '',
    # http://nginx.org/en/docs/http/ngx_http_secure_link_module.html#var_secure_link
    'secure_link': '',
    # http://nginx.org/en/docs/http/ngx_http_secure_link_module.html#var_secure_link_expires
    'secure_link_expires': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_sent_http_
    'sent_http_': '',
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_server_name
    'server_name': '',

    # "Secure" variables that can't content or strictly limited user input

    # http://nginx.org/en/docs/http/ngx_http_browser_module.html#var_ancient_browser
    'ancient_browser': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_binary_remote_addr
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_binary_remote_addr
    'binary_remote_addr': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_body_bytes_sent
    'body_bytes_sent': None,
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_bytes_received
    'bytes_received': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_bytes_sent
    # http://nginx.org/en/docs/http/ngx_http_log_module.html#var_bytes_sent
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_bytes_sent
    'bytes_sent': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_connection
    # http://nginx.org/en/docs/http/ngx_http_log_module.html#var_connection
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_connection
    'connection': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_connection_requests
    # http://nginx.org/en/docs/http/ngx_http_log_module.html#var_connection_requests
    'connection_requests': None,
    # http://nginx.org/en/docs/http/ngx_http_stub_status_module.html#var_connections_active
    'connections_active': None,
    # http://nginx.org/en/docs/http/ngx_http_stub_status_module.html#var_connections_reading
    'connections_reading': None,
    # http://nginx.org/en/docs/http/ngx_http_stub_status_module.html#var_connections_waiting
    'connections_waiting': None,
    # http://nginx.org/en/docs/http/ngx_http_stub_status_module.html#var_connections_writing
    'connections_writing': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_content_length
    'content_length': None,
    # http://nginx.org/en/docs/http/ngx_http_ssi_module.html#var_date_gmt
    'date_gmt': None,
    # http://nginx.org/en/docs/http/ngx_http_ssi_module.html#var_date_local
    'date_local': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_document_root
    'document_root': '/etc/nginx',
    # http://nginx.org/en/docs/http/ngx_http_geoip_module.html
    # http://nginx.org/en/docs/stream/ngx_stream_geoip_module.html
    'geoip_': None,
    # http://nginx.org/en/docs/http/ngx_http_gzip_module.html#var_gzip_ratio
    'gzip_ratio': None,
    # http://nginx.org/en/docs/http/ngx_http_v2_module.html#var_http2
    'http2': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_https
    'https': None,
    # http://nginx.org/en/docs/http/ngx_http_referer_module.html#var_invalid_referer
    'invalid_referer': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_is_args
    'is_args': None,
    # http://nginx.org/en/docs/http/ngx_http_auth_jwt_module.html
    'jwt_': None,
    # http://nginx.org/en/docs/http/ngx_http_browser_module.html#var_modern_browser
    'modern_browser': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_msec
    # http://nginx.org/en/docs/http/ngx_http_log_module.html#var_msec
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_msec
    'msec': None,
    # http://nginx.org/en/docs/http/ngx_http_browser_module.html#var_msie
    'msie': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_nginx_version
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_nginx_version
    'nginx_version': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_pid
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_pid
    'pid': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_pipe
    # http://nginx.org/en/docs/http/ngx_http_log_module.html#var_pipe
    'pipe': None,
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_protocol
    'protocol': None,
    # http://nginx.org/en/docs/http/ngx_http_realip_module.html#var_realip_remote_addr
    # http://nginx.org/en/docs/stream/ngx_stream_realip_module.html#var_realip_remote_addr
    # http://nginx.org/en/docs/http/ngx_http_realip_module.html#var_realip_remote_port
    # http://nginx.org/en/docs/stream/ngx_stream_realip_module.html#var_realip_remote_port
    'realip_remote_port': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_remote_addr
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_remote_addr
    'remote_addr': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_remote_port
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_remote_port
    'remote_port': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_body_file
    'request_body_file': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_length
    # http://nginx.org/en/docs/http/ngx_http_log_module.html#var_request_length
    'request_length': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_method
    'request_method': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_request_time
    # http://nginx.org/en/docs/http/ngx_http_log_module.html#var_request_time
    'request_time': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_scheme
    'scheme': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_server_addr
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_server_addr
    'server_addr': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_server_port
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_server_port
    'server_port': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_server_protocol
    'server_protocol': None,
    # http://nginx.org/en/docs/http/ngx_http_session_log_module.html#var_session_log_binary_id
    'session_log_binary_id': None,
    # http://nginx.org/en/docs/http/ngx_http_session_log_module.html#var_session_log_id
    'session_log_id': None,
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_session_time
    'session_time': None,
    # http://nginx.org/en/docs/http/ngx_http_spdy_module.html#var_spdy
    'spdy': None,
    # http://nginx.org/en/docs/http/ngx_http_spdy_module.html#var_spdy_request_priority
    'spdy_request_priority': None,
    # http://nginx.org/en/docs/http/ngx_http_ssl_module.html
    # http://nginx.org/en/docs/stream/ngx_stream_ssl_module.html
    'ssl_': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html#var_status
    # http://nginx.org/en/docs/http/ngx_http_log_module.html#var_status
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html#var_status
    'status': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html
    'tcpinfo_': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html
    # http://nginx.org/en/docs/http/ngx_http_log_module.html
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html
    'time_iso8601': None,
    # http://nginx.org/en/docs/http/ngx_http_core_module.html
    # http://nginx.org/en/docs/http/ngx_http_log_module.html
    # http://nginx.org/en/docs/stream/ngx_stream_core_module.html
    'time_local': None,
    # http://nginx.org/en/docs/http/ngx_http_userid_module.html#var_uid_got
    'uid_got': None,
    # http://nginx.org/en/docs/http/ngx_http_userid_module.html#var_uid_reset
    'uid_reset': None,
    # http://nginx.org/en/docs/http/ngx_http_userid_module.html#var_uid_set
    'uid_set': None,
    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_addr
    # http://nginx.org/en/docs/stream/ngx_stream_upstream_module.html#var_upstream_addr
    'upstream_addr': None,
    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_bytes_received
    # http://nginx.org/en/docs/stream/ngx_stream_upstream_module.html#var_upstream_bytes_received
    'upstream_bytes_received': None,
    # http://nginx.org/en/docs/stream/ngx_stream_upstream_module.html#var_upstream_bytes_sent
    'upstream_bytes_sent': None,
    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_cache_status
    'upstream_cache_status': None,
    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_connect_time
    # http://nginx.org/en/docs/stream/ngx_stream_upstream_module.html#var_upstream_connect_time
    'upstream_connect_time': None,
    # http://nginx.org/en/docs/stream/ngx_stream_upstream_module.html#var_upstream_first_byte_time
    'upstream_first_byte_time': None,
    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_header_time
    'upstream_header_time': None,
    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_response_length
    'upstream_response_length': None,
    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_response_time
    'upstream_response_time': None,
    # http://nginx.org/en/docs/stream/ngx_stream_upstream_module.html#var_upstream_session_time
    'upstream_session_time': None,
    # http://nginx.org/en/docs/http/ngx_http_upstream_module.html#var_upstream_status
    'upstream_status': None
}


def is_builtin(name):
    if isinstance(name, int):
        # Indexed variables can't be builtin
        return False
    for builtin in BUILTIN_VARIABLES:
        if builtin.endswith('_'):
            if name.startswith(builtin):
                return True
        elif name == builtin:
            return True
    return False


def builtin_var(name):
    for builtin, regexp in BUILTIN_VARIABLES.items():
        if builtin.endswith('_'):
            if not name.startswith(builtin):
                continue
        elif name != builtin:
            continue

        if regexp:
            return Variable(name=name, value=Regexp(regexp, strict=True, case_sensitive=False))
        return Variable(name=name, value='builtin', have_script=False)
    return None


================================================
FILE: gixy/core/config.py
================================================
import gixy


class Config(object):
    def __init__(self,
                 plugins=None,
                 skips=None,
                 severity=gixy.severity.UNSPECIFIED,
                 output_format=None,
                 output_file=None,
                 allow_includes=True):
        self.severity = severity
        self.output_format = output_format
        self.output_file = output_file
        self.plugins = plugins
        self.skips = skips
        self.allow_includes = allow_includes
        self.plugins_options = {}

    def set_for(self, name, options):
        self.plugins_options[name] = options

    def get_for(self, name):
        if self.has_for(name):
            return self.plugins_options[name]
        return {}

    def has_for(self, name):
        return name in self.plugins_options


================================================
FILE: gixy/core/context.py
================================================
import logging
import copy

from gixy.core.utils import is_indexed_name

LOG = logging.getLogger(__name__)

CONTEXTS = []


def get_context():
    return CONTEXTS[-1]


def purge_context():
    del CONTEXTS[:]


def push_context(block):
    if len(CONTEXTS):
        context = copy.deepcopy(get_context())
    else:
        context = Context()
    context.set_block(block)
    CONTEXTS.append(context)
    return context


def pop_context():
    return CONTEXTS.pop()


class Context(object):
    def __init__(self):
        self.block = None
        self.variables = {
            'index': {},
            'name': {}
        }

    def set_block(self, directive):
        self.block = directive
        return self

    def clear_index_vars(self):
        self.variables['index'] = {}
        return self

    def add_var(self, name, var):
        if is_indexed_name(name):
            var_type = 'index'
            name = int(name)
        else:
            var_type = 'name'

        self.variables[var_type][name] = var
        return self

    def get_var(self, name):
        if is_indexed_name(name):
            var_type = 'index'
            name = int(name)
        else:
            var_type = 'name'

        result = None
        try:
            result = self.variables[var_type][name]
        except KeyError:
            if var_type == 'name':
                # Only named variables can be builtins
                import gixy.core.builtin_variables as builtins

                if builtins.is_builtin(name):
                    result = builtins.builtin_var(name)

        if not result:
            LOG.info("Can't find variable '{0}'".format(name))
        return result

    def __deepcopy__(self, memo):
        cls = self.__class__
        result = cls.__new__(cls)
        memo[id(self)] = result
        result.block = copy.copy(self.block)
        result.variables = {
            'index': copy.copy(self.variables['index']),
            'name': copy.copy(self.variables['name'])
        }
        return result


================================================
FILE: gixy/core/exceptions.py
================================================
class InvalidConfiguration(Exception):
    pass


================================================
FILE: gixy/core/issue.py
================================================
class Issue(object):
    def __init__(self, plugin, summary=None, description=None,
                 severity=None, reason=None, help_url=None, directives=None):
        self.plugin = plugin
        self.summary = summary
        self.description = description
        self.severity = severity
        self.reason = reason
        self.help_url = help_url
        if not directives:
            self.directives = []
        elif not hasattr(directives, '__iter__'):
            self.directives = [directives]
        else:
            self.directives = directives


================================================
FILE: gixy/core/manager.py
================================================
import os
import logging

import gixy
from gixy.core.plugins_manager import PluginsManager
from gixy.core.context import get_context, pop_context, push_context, purge_context
from gixy.parser.nginx_parser import NginxParser
from gixy.core.config import Config

LOG = logging.getLogger(__name__)


class Manager(object):
    def __init__(self, config=None):
        self.root = None
        self.config = config or Config()
        self.auditor = PluginsManager(config=self.config)

    def audit(self, file_path, file_data, is_stdin=False):
        LOG.debug("Audit config file: {fname}".format(fname=file_path))
        parser = NginxParser(
            cwd=os.path.dirname(file_path) if not is_stdin else '',
            allow_includes=self.config.allow_includes)
        self.root = parser.parse(content=file_data.read(), path_info=file_path)

        push_context(self.root)
        self._audit_recursive(self.root.children)

    @property
    def results(self):
        for plugin in self.auditor.plugins:
            if plugin.issues:
                yield plugin

    @property
    def stats(self):
        stats = dict.fromkeys(gixy.severity.ALL, 0)
        for plugin in self.auditor.plugins:
            base_severity = plugin.severity
            for issue in plugin.issues:
                # TODO(buglloc): encapsulate into Issue class?
                severity = issue.severity if issue.severity else base_severity
                stats[severity] += 1
        return stats

    def _audit_recursive(self, tree):
        for directive in tree:
            self._update_variables(directive)
            self.auditor.audit(directive)
            if directive.is_block:
                if directive.self_context:
                    push_context(directive)
                self._audit_recursive(directive.children)
                if directive.self_context:
                    pop_context()

    def _update_variables(self, directive):
        # TODO(buglloc): finish him!
        if not directive.provide_variables:
            return

        context = get_context()
        for var in directive.variables:
            if var.name == 0:
                # All regexps must clean indexed variables
                context.clear_index_vars()
            context.add_var(var.name, var)

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        purge_context()


================================================
FILE: gixy/core/plugins_manager.py
================================================
import os

import gixy
from gixy.plugins.plugin import Plugin


class PluginsManager(object):
    def __init__(self, config=None):
        self.imported = False
        self.config = config
        self._plugins = []

    def import_plugins(self):
        if self.imported:
            return

        files_list = os.listdir(os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'plugins'))
        for plugin_file in files_list:
            if not plugin_file.endswith('.py') or plugin_file.startswith('_'):
                continue
            __import__('gixy.plugins.' + os.path.splitext(plugin_file)[0], None, None, [''])

        self.imported = True

    def init_plugins(self):
        self.import_plugins()

        exclude = self.config.skips if self.config else None
        include = self.config.plugins if self.config else None
        severity = self.config.severity if self.config else None
        for plugin_cls in Plugin.__subclasses__():
            name = plugin_cls.__name__
            if include and name not in include:
                # Skip not needed plugins
                continue
            if exclude and name in exclude:
                # Skipped plugins
                continue
            if severity and not gixy.severity.is_acceptable(plugin_cls.severity, severity):
                # Skip plugin by severity level
                continue
            if self.config and self.config.has_for(name):
                options = self.config.get_for(name)
            else:
                options = plugin_cls.options
            self._plugins.append(plugin_cls(options))

    @property
    def plugins(self):
        if not self._plugins:
            self.init_plugins()
        return self._plugins

    @property
    def plugins_classes(self):
        self.import_plugins()
        return Plugin.__subclasses__()

    def get_plugins_descriptions(self):
        return map(lambda a: a.name, self.plugins)

    def audit(self, directive):
        for plugin in self.plugins:
            if plugin.directives and directive.name not in plugin.directives:
                continue
            plugin.audit(directive)

    def issues(self):
        result = []
        for plugin in self.plugins:
            if not plugin.issues:
                continue
            result.extend(plugin.issues)
        return result


================================================
FILE: gixy/core/regexp.py
================================================
import six
import logging
import re
import random
import itertools
from cached_property import cached_property

import gixy.core.sre_parse.sre_parse as sre_parse

LOG = logging.getLogger(__name__)


def _build_reverse_list(original):
    result = []
    for c in range(1, 126):
        c = six.unichr(c)
        if c not in original:
            result.append(c)
    return frozenset(result)


FIX_NAMED_GROUPS_RE = re.compile(r"(?<!\\)\(\?(?:<|')(\w+)(?:>|')")

CATEGORIES = {
    # TODO(buglloc): unicode?
    sre_parse.CATEGORY_SPACE: sre_parse.WHITESPACE,
    sre_parse.CATEGORY_NOT_SPACE: _build_reverse_list(sre_parse.WHITESPACE),
    sre_parse.CATEGORY_DIGIT: sre_parse.DIGITS,
    sre_parse.CATEGORY_NOT_DIGIT: _build_reverse_list(sre_parse.DIGITS),
    sre_parse.CATEGORY_WORD: frozenset('abcdefghijklmnopqrstuvwxyz'
                                       'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                                       '0123456789_'),
    sre_parse.CATEGORY_NOT_WORD: _build_reverse_list(frozenset('abcdefghijklmnopqrstuvwxyz'
                                                               'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
                                                               '0123456789_')),
    sre_parse.CATEGORY_LINEBREAK: frozenset('\n'),
    sre_parse.CATEGORY_NOT_LINEBREAK: _build_reverse_list(frozenset('\n')),
    'ANY': [six.unichr(x) for x in range(1, 127) if x != 10]
}

CATEGORIES_NAMES = {
    sre_parse.CATEGORY_DIGIT: r'\d',
    sre_parse.CATEGORY_NOT_DIGIT: r'\D',
    sre_parse.CATEGORY_SPACE: r'\s',
    sre_parse.CATEGORY_NOT_SPACE: r'\S',
    sre_parse.CATEGORY_WORD: r'\w',
    sre_parse.CATEGORY_NOT_WORD: r'\W',
}


def extract_groups(parsed, top=True):
    result = {}
    if top:
        result[0] = parsed
    for token in parsed:
        if not token:
            # Skip empty tokens
            pass
        elif token[0] == sre_parse.SUBPATTERN:
            if isinstance(token[1][0], int):
                # Captured group index can't be a string. E.g. for pattern "(?:la)" group name is "None"
                result[token[1][0]] = token[1][1]
            result.update(extract_groups(token[1][1], False))
        elif token[0] == sre_parse.MIN_REPEAT:
            result.update(extract_groups(token[1][2], False))
        elif token[0] == sre_parse.MAX_REPEAT:
            result.update(extract_groups(token[1][2], False))
        elif token[0] == sre_parse.BRANCH:
            result.update(extract_groups(token[1][1], False))
        elif token[0] == sre_parse.SUBPATTERN:
            result.update(extract_groups(token[1][1], False))
        elif token[0] == sre_parse.IN:
            result.update(extract_groups(token[1], False))
        elif isinstance(token, sre_parse.SubPattern):
            result.update(extract_groups(token, False))
    return result


def _gen_combinator(variants, _merge=True):
    if not hasattr(variants, '__iter__'):
        return [variants] if variants is not None else []

    res = []
    need_product = False
    for var in variants:
        if isinstance(var, list):
            sol = _gen_combinator(var, _merge=False)
            res.append(sol)
            need_product = True
        elif var is not None:
            res.append(var)

    if need_product:
        producted = itertools.product(*res)
        if _merge:
            # TODO(buglloc): ??!
            return list(six.moves.map(_merge_variants, producted))
        return producted
    elif _merge:
        return list(six.moves.map(_merge_variants, [res]))
    return res


def _merge_variants(variants):
    result = []
    for var in variants:
        if isinstance(var, tuple):
            result.append(_merge_variants(var))
        else:
            result.append(var)
    return ''.join(result)


class Token(object):
    type = None

    def __init__(self, token, parent, regexp):
        self.token = token
        self.childs = None
        self.parent = parent
        self.regexp = regexp
        self._parse()

    def parse(self):
        pass

    def _parse(self):
        pass

    def _parse_childs(self, childs):
        self.childs = parse(childs, self, regexp=self.regexp)

    def _get_group(self, gid):
        return self.regexp.group(gid)

    def _reg_group(self, gid):
        self.regexp.reg_group(gid, self)

    def can_contain(self, char, skip_literal=True):
        raise NotImplementedError('can_contain must be implemented')

    def can_startswith(self, char, strict=False):
        return self.can_contain(char, skip_literal=False)

    def must_contain(self, char):
        raise NotImplementedError('must_contain must be implemented')

    def must_startswith(self, char, strict=False):
        return self.must_contain(char)

    def generate(self, context):
        raise NotImplementedError('generate must be implemented')

    def __str__(self):
        raise NotImplementedError('__str__ must be implemented')


class AnyToken(Token):
    type = sre_parse.ANY

    def can_contain(self, char, skip_literal=True):
        return char in CATEGORIES['ANY']

    def must_contain(self, char, skip_literal=True):
        # Char may not be present in ANY token
        return False

    def generate(self, context):
        if context.char in CATEGORIES['ANY']:
            return context.char
        return 'a'

    def __str__(self):
        return '.'


class LiteralToken(Token):
    type = sre_parse.LITERAL

    def _parse(self):
        self.char = six.unichr(self.token[1])

    def can_contain(self, char, skip_literal=True):
        if skip_literal:
            return False
        return self.char == char

    def must_contain(self, char, skip_literal=True):
        return self.char == char

    def generate(self, context):
        return self.char

    def __str__(self):
        return re.escape(self.char)


class NotLiteralToken(Token):
    type = sre_parse.NOT_LITERAL

    def _parse(self):
        self.char = six.unichr(self.token[1])
        self.gen_char_list = list(_build_reverse_list(frozenset(self.char)))

    def can_contain(self, char, skip_literal=True):
        return self.char != char

    def must_contain(self, char):
        # Any char MAY not be present in NotLiteral, e.g.: "a" not present in "[^b]"
        return False

    def generate(self, context):
        if self.can_contain(context.char):
            return context.char

        return random.choice(self.gen_char_list)

    def __str__(self):
        return '[^{char}]'.format(char=self.char)


class RangeToken(Token):
    type = sre_parse.RANGE

    def _parse(self):
        self.left_code = self.token[1][0]
        self.right_code = self.token[1][1]
        self.left = six.unichr(self.left_code)
        self.right = six.unichr(self.right_code)

    def can_contain(self, char, skip_literal=True):
        return self.left <= char <= self.right

    def must_contain(self, char, skip_literal=True):
        return self.left == char == self.right

    def generate(self, context):
        if self.can_contain(context.char):
            return context.char

        return six.unichr(random.randint(self.token[1][0], self.token[1][1]))

    def __str__(self):
        return '{left}-{right}'.format(left=self.left, right=self.right)


class CategoryToken(Token):
    type = sre_parse.CATEGORY

    def _parse(self):
        self.char_list = CATEGORIES.get(self.token[1], [''])

    def can_contain(self, char, skip_literal=True):
        return char in self.char_list

    def must_contain(self, char, skip_literal=True):
        return frozenset([char]) == self.char_list

    def generate(self, context):
        if self.can_contain(context.char):
            return context.char

        for c in self.char_list:
            return c

    def __str__(self):
        return CATEGORIES_NAMES.get(self.token[1], '\\C')


class MinRepeatToken(Token):
    type = sre_parse.MIN_REPEAT

    def _parse(self):
        self._parse_childs(self.token[1][2])
        self.min = self.token[1][0]
        self.max = self.token[1][1]

    def can_contain(self, char, skip_literal=True):
        if self.max == 0:
            # [a-z]{0}
            return False
        for child in self.childs:
            if child.can_contain(char, skip_literal=skip_literal):
                return True
        return False

    def must_contain(self, char):
        if self.max == 0:
            # [a-z]{0}
            return False
        if self.min == 0:
            # [a-z]*?
            return False
        for child in self.childs:
            if child.must_contain(char):
                return True
        return False

    def can_startswith(self, char, strict=False):
        if self.max == 0:
            # [a-z]{0}
            if self.childs[0].can_startswith(char, strict):
                return False
            return None
        return self.childs[0].can_startswith(char, strict)

    def must_startswith(self, char, strict=False):
        if self.min == 0:
            # [a-z]*?
            return None
        if self.max == 0:
            # [a-z]{0}
            return None
        return self.childs[0].must_startswith(char, strict=strict)

    def generate(self, context):
        res = []
        if self.min == 0:
            # [a-z]*
            res.append('')
        if self.max == 0:
            # [a-z]{0}
            return res

        for child in self.childs:
            res.extend(child.generate(context))

        result = []
        repeat = self.max if self.max <= context.max_repeat else context.max_repeat
        for val in _gen_combinator([res]):
            result.append(val * repeat)
        return result

    def __str__(self):
        childs = ''.join(str(x) for x in self.childs)
        if self.min == self.max:
            return '{childs}{{{count}}}?'.format(childs=childs, count=self.min)
        if self.min == 0 and self.max == 1:
            return '{childs}?'.format(childs=childs)
        if self.min == 0 and self.max == sre_parse.MAXREPEAT:
            return '{childs}*?'.format(childs=childs)
        if self.min == 1 and self.max == sre_parse.MAXREPEAT:
            return '{childs}+?'.format(childs=childs)
        return '{childs}{{{min},{max}}}?'.format(childs=childs, min=self.min, max=self.max)


class MaxRepeatToken(Token):
    type = sre_parse.MAX_REPEAT

    def _parse(self):
        self._parse_childs(self.token[1][2])
        self.min = self.token[1][0]
        self.max = self.token[1][1]

    def can_contain(self, char, skip_literal=True):
        if self.max == 0:
            # [a-z]{0}
            return False
        for child in self.childs:
            if child.can_contain(char, skip_literal=skip_literal):
                return True
        return False

    def must_contain(self, char):
        if self.max == 0:
            # [a-z]{0}
            return False
        if self.min == 0:
            # [a-z]?
            return False
        for child in self.childs:
            if child.must_contain(char):
                return True
        return False

    def can_startswith(self, char, strict=False):
        if self.max == 0:
            # [a-z]{0}
            if self.childs[0].can_startswith(char, strict):
                return False
            return None
        return self.childs[0].can_startswith(char, strict)

    def must_startswith(self, char, strict=False):
        if self.min == 0:
            # [a-z]*
            return None
        if self.max == 0:
            # [a-z]{0}
            return None
        return self.childs[0].must_startswith(char, strict=strict)

    def generate(self, context):
        res = []
        if self.min == 0:
            # [a-z]*
            res.append('')
        if self.max == 0:
            # [a-z]{0}
            return res

        for child in self.childs:
            res.extend(child.generate(context))

        result = []
        repeat = self.max if self.max <= context.max_repeat else context.max_repeat
        for val in _gen_combinator([res]):
            result.append(val * repeat)
        return result

    def __str__(self):
        childs = ''.join(str(x) for x in self.childs)
        if self.min == self.max:
            return '{childs}{{{count}}}'.format(childs=childs, count=self.min)
        if self.min == 0 and self.max == 1:
            return '{childs}?'.format(childs=childs)
        if self.min == 0 and self.max == sre_parse.MAXREPEAT:
            return '{childs}*'.format(childs=childs)
        if self.min == 1 and self.max == sre_parse.MAXREPEAT:
            return '{childs}+'.format(childs=childs)
        return '{childs}{{{min},{max}}}'.format(childs=childs, min=self.min, max=self.max)


class BranchToken(Token):
    type = sre_parse.BRANCH

    def _parse(self):
        self.childs = []
        for token in self.token[1][1]:
            if not token:
                self.childs.append(EmptyToken(token=token, parent=self.parent, regexp=self.regexp))
            elif isinstance(token, sre_parse.SubPattern):
                self.childs.append(InternalSubpatternToken(token=token, parent=self.parent, regexp=self.regexp))
            else:
                raise RuntimeError('Unexpected token {0} in branch'.format(token))

    def can_contain(self, char, skip_literal=True):
        for child in self.childs:
            if child.can_contain(char, skip_literal=skip_literal):
                return True
        return False

    def must_contain(self, char):
        return all(child.must_contain(char) for child in self.childs)

    def can_startswith(self, char, strict=False):
        return any(x.can_startswith(char, strict) for x in self.childs)

    def must_startswith(self, char, strict=False):
        return all(x.must_startswith(char, strict) for x in self.childs)

    def generate(self, context):
        res = []
        for child in self.childs:
            values = child.generate(context)
            if isinstance(values, list):
                res.extend(child.generate(context))
            else:
                res.append(values)

        return res

    def __str__(self):
        return '(?:{0})'.format('|'.join(str(x) for x in self.childs))


class SubpatternToken(Token):
    type = sre_parse.SUBPATTERN

    def _parse(self):
        self._parse_childs(self.token[1][1])
        self.group = self.token[1][0]
        if isinstance(self.group, int):
            # Captured group index can't be a string. E.g. for pattern "(?:la)" group name is "None"
            self._reg_group(self.group)

    def can_contain(self, char, skip_literal=True):
        for child in self.childs:
            if child.can_contain(char, skip_literal=skip_literal):
                return True
        return False

    def must_contain(self, char):
        for child in self.childs:
            if child.must_contain(char):
                return True
        return False

    def can_startswith(self, char, strict=False):
        if isinstance(self.childs[0], AtToken):
            if len(self.childs) > 1:
                for child in self.childs[1:]:
                    can = child.can_startswith(char, strict)
                    if can is None:
                        continue
                    return can
            return False
        elif not strict and not isinstance(self.childs[0], (SubpatternToken, InternalSubpatternToken)):
            # Not strict regexp w/o ^ can starts with any character
            return char in CATEGORIES['ANY']

        for child in self.childs:
            can = child.can_startswith(char, strict)
            if can is None:
                continue
            return can
        return None

    def must_startswith(self, char, strict=False):
        if isinstance(self.childs[0], AtToken):
            if len(self.childs) > 1:
                for child in self.childs[1:]:
                    must = child.must_startswith(char, strict=True)
                    if must is None:
                        continue
                    return must
            return False
        elif not strict and not isinstance(self.childs[0], (SubpatternToken, InternalSubpatternToken)):
            # Not strict regexp w/o ^ MAY NOT starts with any character
            return False

        for child in self.childs:
            must = child.must_startswith(char, strict=strict)
            if must is None:
                continue
            return must
        return None

    def generate(self, context):
        res = []
        for child in self.childs:
            res.append(child.generate(context))

        return _gen_combinator(res)

    def __str__(self):
        childs = ''.join(str(x) for x in self.childs)
        if self.group is None:
            return '(?:{childs})'.format(childs=childs)
        return '({childs})'.format(childs=childs)


class InternalSubpatternToken(Token):
    type = sre_parse.SUBPATTERN

    def _parse(self):
        self._parse_childs(self.token)
        self.group = None

    def can_contain(self, char, skip_literal=True):
        for child in self.childs:
            if child.can_contain(char, skip_literal=skip_literal):
                return True
        return False

    def must_contain(self, char):
        for child in self.childs:
            if child.must_contain(char):
                return True
        return False

    def can_startswith(self, char, strict=False):
        if isinstance(self.childs[0], AtToken):
            if len(self.childs) > 1:
                for child in self.childs[1:]:
                    can = child.can_startswith(char, strict)
                    if can is None:
                        continue
                    return can
            return False
        elif not strict and not isinstance(self.childs[0], (SubpatternToken, InternalSubpatternToken)):
            # Not strict regexp w/o ^ can starts with any character
            return char in CATEGORIES['ANY']

        for child in self.childs:
            can = child.can_startswith(char, strict)
            if can is None:
                continue
            return can
        return None

    def must_startswith(self, char, strict=False):
        if isinstance(self.childs[0], AtToken):
            if len(self.childs) > 1:
                for child in self.childs[1:]:
                    must = child.must_startswith(char, strict=True)
                    if must is None:
                        continue
                    return must
            return False
        elif not strict and not isinstance(self.childs[0], (SubpatternToken, InternalSubpatternToken)):
            # Not strict regexp w/o ^ MAY NOT starts with any character
            return False

        for child in self.childs:
            must = child.must_startswith(char, strict=strict)
            if must is None:
                continue
            return must
        return None

    def generate(self, context):
        res = []
        for child in self.childs:
            res.append(child.generate(context))

        return _gen_combinator(res)

    def __str__(self):
        return ''.join(str(x) for x in self.childs)


class InToken(Token):
    type = sre_parse.IN

    def _parse(self):
        self.childs = parse(self.token[1], self)

    def can_contain(self, char, skip_literal=True):
        can = False
        negative = False
        for child in self.childs:
            if isinstance(child, NegateToken):
                negative = True
            else:
                can = child.can_contain(char, skip_literal=False)

            if can:
                break
        if can and not negative:
            # a in [a-z]
            return True
        if not can and negative:
            # a in [^b-z]
            return True
        return False

    def must_contain(self, char):
        # Any character MAY not be present in IN
        return False

    def _generate_positive(self, context):
        result = []
        for child in self.childs:
            if isinstance(child, (NegateToken, EmptyToken)):
                pass
            else:
                result.append(child.generate(context=context))
        return result

    def _generate_negative(self, context):
        blacklisted = set()
        # TODO(buglloc): move chars list into the tokens?
        for child in self.childs:
            if isinstance(child, (NegateToken, EmptyToken)):
                pass
            elif isinstance(child, LiteralToken):
                blacklisted.add(child.char)
            elif isinstance(child, RangeToken):
                blacklisted.update(six.unichr(c) for c in six.moves.range(child.left_code, child.right_code + 1))
            elif isinstance(child, CategoryToken):
                blacklisted.update(child.char_list)
            else:
                LOG.info('Unexpected child "{0!r}"'.format(child))

        for char in _build_reverse_list(set()):
            if char not in blacklisted:
                return char

    def generate(self, context):
        if self.can_contain(context.char, skip_literal=False):
            return context.char

        is_negative = self.childs and isinstance(self.childs[0], NegateToken)
        if is_negative:
            # [^a-z]
            return self._generate_negative(context)
        # [a-z]
        return self._generate_positive(context)

    def __str__(self):
        return '[{childs}]'.format(childs=''.join(str(x) for x in self.childs))


class AtToken(Token):
    type = sre_parse.AT

    def _parse(self):
        self.begin = self.token[1] == sre_parse.AT_BEGINNING
        self.end = self.token[1] == sre_parse.AT_END

    def can_contain(self, char, skip_literal=True):
        return False

    def must_contain(self, char):
        return False

    def generate(self, context):
        if context.anchored:
            if self.begin:
                return '^'
            if self.end:
                return '$'
        return None

    def __str__(self):
        if self.begin:
            return '^'
        if self.end:
            return '$'
        LOG.warn('unexpected AT token: %s', self.token)


class NegateToken(Token):
    type = sre_parse.NEGATE

    def can_contain(self, char, skip_literal=True):
        return False

    def must_contain(self, char):
        return False

    def can_startswith(self, char, strict=False):
        return None

    def must_startswith(self, char, strict=False):
        return None

    def generate(self, context):
        return None

    def __str__(self):
        return '^'


class GroupRefToken(Token):
    type = sre_parse.GROUPREF

    def _parse(self):
        self.id = self.token[1]
        self.group = self._get_group(self.id)

    def can_contain(self, char, skip_literal=True):
        return self.group.can_contain(char, skip_literal=skip_literal)

    def must_contain(self, char):
        return self.group.must_contain(char)

    def can_startswith(self, char, strict=False):
        return self.group.can_startswith(char, strict=strict)

    def must_startswith(self, char, strict=False):
        return self.group.must_startswith(char, strict=strict)

    def generate(self, context):
        return self.group.generate(context)

    def __str__(self):
        return '\\\\{0}'.format(self.id)


class AssertToken(Token):
    type = sre_parse.ASSERT

    def can_contain(self, char, skip_literal=True):
        # TODO(buglloc): Do it!
        return False

    def must_contain(self, char):
        # TODO(buglloc): Do it!
        return False

    def can_startswith(self, char, strict=False):
        return None

    def must_startswith(self, char, strict=False):
        return None


class AssertNotToken(Token):
    type = sre_parse.ASSERT_NOT

    def can_contain(self, char, skip_literal=True):
        # TODO(buglloc): Do it!
        return False

    def must_contain(self, char):
        # TODO(buglloc): Do it!
        return False

    def can_startswith(self, char, strict=False):
        return None

    def must_startswith(self, char, strict=False):
        return None


class EmptyToken(Token):
    type = None

    def can_contain(self, char, skip_literal=True):
        return False

    def must_contain(self, char):
        # TODO(buglloc): Do it!
        return False

    def can_startswith(self, char, strict=False):
        return None

    def must_startswith(self, char, strict=False):
        return None

    def generate(self, context):
        return ''

    def __str__(self):
        return ''


def parse(sre_obj, parent=None, regexp=None):
    result = []
    for token in sre_obj:
        if not token:
            result.append(EmptyToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.ANY:
            result.append(AnyToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.LITERAL:
            result.append(LiteralToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.NOT_LITERAL:
            result.append(NotLiteralToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.RANGE:
            result.append(RangeToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.CATEGORY:
            result.append(CategoryToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.MIN_REPEAT:
            result.append(MinRepeatToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.MAX_REPEAT:
            result.append(MaxRepeatToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.BRANCH:
            result.append(BranchToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.SUBPATTERN:
            result.append(SubpatternToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.IN:
            result.append(InToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.NEGATE:
            result.append(NegateToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.AT:
            result.append(AtToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.GROUPREF:
            result.append(GroupRefToken(token=token, parent=parent, regexp=regexp))
        elif token[0] == sre_parse.ASSERT:
            pass  # TODO(buglloc): Do it!
        elif token[0] == sre_parse.ASSERT_NOT:
            pass  # TODO(buglloc): Do it!
        else:
            LOG.info('Unexpected token "{0}"'.format(token[0]))

    return result


class GenerationContext(object):
    def __init__(self, char, max_repeat=5, strict=False, anchored=True):
        self.char = char
        self.max_repeat = max_repeat
        self.strict = strict
        self.anchored = anchored


class Regexp(object):
    def __init__(self, source, strict=False, case_sensitive=True, _root=None, _parsed=None):
        """
        Gixy Regexp class, parse and provide helpers to work with it.

        :param str source: regexp, e.g. ^foo$.
        :param bool strict: anchored or not.
        :param bool case_sensitive: case sensitive or not.
        """

        self.source = source
        self.strict = strict
        self.case_sensitive = case_sensitive
        self._root = _root
        self._parsed = _parsed
        self._groups = {}

    def can_startswith(self, char):
        """
        Checks if regex can starts with the specified char.
        Example:
          Regexp('[a-z][0-9]').can_startswith('s') -> True
          Regexp('[a-z][0-9]').can_startswith('0') -> True
          Regexp('^[a-z][0-9]').can_startswith('0') -> False
          Regexp('[a-z][0-9]', strict=True).can_startswith('0') -> False

        :param str char: character to test.
        :return bool: True if regex can starts with the specified char, False otherwise.
        """

        return self.root.can_startswith(
            char=char if self.case_sensitive else char.lower(),
            strict=self.strict
        )

    def can_contain(self, char, skip_literal=True):
        """
        Checks if regex can contain the specified char.
        Example:
          Regexp('[a-z][0-9]').can_contain('s') -> True
          Regexp('[a-z][0-9]').can_contain('0') -> True
          Regexp('[a-z][0-9]').can_contain('/') -> False
          Regexp('[a-z][0-9]/').can_contain('/') -> False
          Regexp('[a-z][0-9]/').can_contain('/', skip_literal=False) -> True

        :param str char: character to test.
        :param bool skip_literal: skip literal tokens.
        :return bool: True if regex can contain the specified char, False otherwise.
        """

        return self.root.can_contain(
            char=char if self.case_sensitive else char.lower(),
            skip_literal=skip_literal
        )

    def must_startswith(self, char):
        """
        Checks if regex MUST starts with the specified char.
        Example:
          Regexp('[a-z][0-9]').must_startswith('s') -> False
          Regexp('s[a-z]').must_startswith('s') -> False
          Regexp('^s[a-z]').must_startswith('s') -> True
          Regexp('s[a-z]', strict=True).must_startswith('s') -> True

        :param str char: character to test.
        :return bool: True if regex must starts with the specified char, False otherwise.
        """

        return self.root.must_startswith(
            char=char if self.case_sensitive else char.lower(),
            strict=self.strict
        )

    def must_contain(self, char):
        """
        Checks if regex MUST contain the specified char.
        Example:
          Regexp('[a-z][0-9]').must_contain('s') -> False
          Regexp('[a-z][0-9]s').must_contain('s') -> True

        :param str char: character to test.
        :return bool: True if regex MUST contain the specified char, False otherwise.
        """

        return self.root.must_contain(
            char=char if self.case_sensitive else char.lower()
        )

    def generate(self, char, anchored=False, max_repeat=5):
        """
        Generate values that match regex.
        Example:
          Regexp('.a?').generate('s') -> ['s', 'sa']
          Regexp('(?:^http|https)://.').generate('s') -> ['http://s', 'https://s']
          Regexp('(?:^http|https)://.').generate('s', anchored=True) -> ['^http://s', 'https://s']


        :param str char: "dangerous" character, generator try to place it wherever possible.
        :param bool anchored: place anchors in generated values.
        :param int max_repeat: maximum count of repeated group (e.g. "a+" provides "aaaaa").
        :return list of str: True if regex can contain the specified char, False otherwise.
        """

        context = GenerationContext(char, anchored=anchored, max_repeat=max_repeat)
        for val in self.root.generate(context=context):
            if anchored and self.strict and not val.startswith('^'):
                yield '^' + val
            else:
                yield val

    def group(self, name):
        """
        Returns group by specified name.

        :param name: name of the group.
        :return Regexp: Regexp object for this group.
        """

        if name in self.groups:
            return self.groups[name]
        return Regexp('')

    def reg_group(self, gid, token):
        self._groups[gid] = token

    def get_group(self, gid):
        return self._groups[gid]

    @cached_property
    def groups(self):
        # self.root.parse()
        result = {}
        # for name, token in self._groups.items():
        #     result[name] = Regexp(str(self), root=token, strict=True, case_sensitive=self.case_sensitive)
        for name, parsed in extract_groups(self.parsed).items():
            result[name] = Regexp('compiled', _parsed=parsed, strict=True, case_sensitive=self.case_sensitive)
        for name, group in self.parsed.pattern.groupdict.items():
            result[name] = result[group]
        return result

    @property
    def root(self):
        if self._root:
            return self._root

        self._root = InternalSubpatternToken(self.parsed, parent=None, regexp=self)
        self._groups[0] = self._root
        return self._root

    @property
    def parsed(self):
        # TODO(buglloc): Ugly hack!
        if self._parsed:
            return self._parsed

        try:
            self._parsed = sre_parse.parse(FIX_NAMED_GROUPS_RE.sub('(?P<\\1>', self.source))
        except sre_parse.error as e:
            LOG.fatal('Failed to parse regex: %s (%s)', self.source, str(e))
            raise e

        return self._parsed

    def __str__(self):
        return str(self.root)


================================================
FILE: gixy/core/severity.py
================================================
UNSPECIFIED = 'UNSPECIFIED'
LOW = 'LOW'
MEDIUM = 'MEDIUM'
HIGH = 'HIGH'
ALL = [UNSPECIFIED, LOW, MEDIUM, HIGH]


def is_acceptable(current_severity, min_severity):
    return ALL.index(current_severity) >= ALL.index(min_severity)


================================================
FILE: gixy/core/sre_parse/__init__.py
================================================


================================================
FILE: gixy/core/sre_parse/sre_constants.py
================================================
# flake8: noqa

#
# Secret Labs' Regular Expression Engine
#
# various symbols used by the regular expression engine.
# run this script to update the _sre include files!
#
# Copyright (c) 1998-2001 by Secret Labs AB.  All rights reserved.
#
# See the sre.py file for information on usage and redistribution.
#

"""Internal support module for sre"""

# update when constants are added or removed

MAGIC = 20031017

try:
    from _sre import MAXREPEAT
except ImportError:
    import _sre

    MAXREPEAT = _sre.MAXREPEAT = 65535


# SRE standard exception (access as sre.error)
# should this really be here?

class error(Exception):
    pass


# operators

FAILURE = "failure"
SUCCESS = "success"

ANY = "any"
ANY_ALL = "any_all"
ASSERT = "assert"
ASSERT_NOT = "assert_not"
AT = "at"
BIGCHARSET = "bigcharset"
BRANCH = "branch"
CALL = "call"
CATEGORY = "category"
CHARSET = "charset"
GROUPREF = "groupref"
GROUPREF_IGNORE = "groupref_ignore"
GROUPREF_EXISTS = "groupref_exists"
IN = "in"
IN_IGNORE = "in_ignore"
INFO = "info"
JUMP = "jump"
LITERAL = "literal"
LITERAL_IGNORE = "literal_ignore"
MARK = "mark"
MAX_REPEAT = "max_repeat"
MAX_UNTIL = "max_until"
MIN_REPEAT = "min_repeat"
MIN_UNTIL = "min_until"
NEGATE = "negate"
NOT_LITERAL = "not_literal"
NOT_LITERAL_IGNORE = "not_literal_ignore"
RANGE = "range"
REPEAT = "repeat"
REPEAT_ONE = "repeat_one"
SUBPATTERN = "subpattern"
MIN_REPEAT_ONE = "min_repeat_one"

# positions
AT_BEGINNING = "at_beginning"
AT_BEGINNING_LINE = "at_beginning_line"
AT_BEGINNING_STRING = "at_beginning_string"
AT_BOUNDARY = "at_boundary"
AT_NON_BOUNDARY = "at_non_boundary"
AT_END = "at_end"
AT_END_LINE = "at_end_line"
AT_END_STRING = "at_end_string"
AT_LOC_BOUNDARY = "at_loc_boundary"
AT_LOC_NON_BOUNDARY = "at_loc_non_boundary"
AT_UNI_BOUNDARY = "at_uni_boundary"
AT_UNI_NON_BOUNDARY = "at_uni_non_boundary"

# categories
CATEGORY_DIGIT = "category_digit"
CATEGORY_NOT_DIGIT = "category_not_digit"
CATEGORY_SPACE = "category_space"
CATEGORY_NOT_SPACE = "category_not_space"
CATEGORY_WORD = "category_word"
CATEGORY_NOT_WORD = "category_not_word"
CATEGORY_LINEBREAK = "category_linebreak"
CATEGORY_NOT_LINEBREAK = "category_not_linebreak"
CATEGORY_LOC_WORD = "category_loc_word"
CATEGORY_LOC_NOT_WORD = "category_loc_not_word"
CATEGORY_UNI_DIGIT = "category_uni_digit"
CATEGORY_UNI_NOT_DIGIT = "category_uni_not_digit"
CATEGORY_UNI_SPACE = "category_uni_space"
CATEGORY_UNI_NOT_SPACE = "category_uni_not_space"
CATEGORY_UNI_WORD = "category_uni_word"
CATEGORY_UNI_NOT_WORD = "category_uni_not_word"
CATEGORY_UNI_LINEBREAK = "category_uni_linebreak"
CATEGORY_UNI_NOT_LINEBREAK = "category_uni_not_linebreak"

OPCODES = [

    # failure=0 success=1 (just because it looks better that way :-)
    FAILURE, SUCCESS,

    ANY, ANY_ALL,
    ASSERT, ASSERT_NOT,
    AT,
    BRANCH,
    CALL,
    CATEGORY,
    CHARSET, BIGCHARSET,
    GROUPREF, GROUPREF_EXISTS, GROUPREF_IGNORE,
    IN, IN_IGNORE,
    INFO,
    JUMP,
    LITERAL, LITERAL_IGNORE,
    MARK,
    MAX_UNTIL,
    MIN_UNTIL,
    NOT_LITERAL, NOT_LITERAL_IGNORE,
    NEGATE,
    RANGE,
    REPEAT,
    REPEAT_ONE,
    SUBPATTERN,
    MIN_REPEAT_ONE

]

ATCODES = [
    AT_BEGINNING, AT_BEGINNING_LINE, AT_BEGINNING_STRING, AT_BOUNDARY,
    AT_NON_BOUNDARY, AT_END, AT_END_LINE, AT_END_STRING,
    AT_LOC_BOUNDARY, AT_LOC_NON_BOUNDARY, AT_UNI_BOUNDARY,
    AT_UNI_NON_BOUNDARY
]

CHCODES = [
    CATEGORY_DIGIT, CATEGORY_NOT_DIGIT, CATEGORY_SPACE,
    CATEGORY_NOT_SPACE, CATEGORY_WORD, CATEGORY_NOT_WORD,
    CATEGORY_LINEBREAK, CATEGORY_NOT_LINEBREAK, CATEGORY_LOC_WORD,
    CATEGORY_LOC_NOT_WORD, CATEGORY_UNI_DIGIT, CATEGORY_UNI_NOT_DIGIT,
    CATEGORY_UNI_SPACE, CATEGORY_UNI_NOT_SPACE, CATEGORY_UNI_WORD,
    CATEGORY_UNI_NOT_WORD, CATEGORY_UNI_LINEBREAK,
    CATEGORY_UNI_NOT_LINEBREAK
]


def makedict(list):
    d = {}
    i = 0
    for item in list:
        d[item] = i
        i = i + 1
    return d


OPCODES = makedict(OPCODES)
ATCODES = makedict(ATCODES)
CHCODES = makedict(CHCODES)

# replacement operations for "ignore case" mode
OP_IGNORE = {
    GROUPREF: GROUPREF_IGNORE,
    IN: IN_IGNORE,
    LITERAL: LITERAL_IGNORE,
    NOT_LITERAL: NOT_LITERAL_IGNORE
}

AT_MULTILINE = {
    AT_BEGINNING: AT_BEGINNING_LINE,
    AT_END: AT_END_LINE
}

AT_LOCALE = {
    AT_BOUNDARY: AT_LOC_BOUNDARY,
    AT_NON_BOUNDARY: AT_LOC_NON_BOUNDARY
}

AT_UNICODE = {
    AT_BOUNDARY: AT_UNI_BOUNDARY,
    AT_NON_BOUNDARY: AT_UNI_NON_BOUNDARY
}

CH_LOCALE = {
    CATEGORY_DIGIT: CATEGORY_DIGIT,
    CATEGORY_NOT_DIGIT: CATEGORY_NOT_DIGIT,
    CATEGORY_SPACE: CATEGORY_SPACE,
    CATEGORY_NOT_SPACE: CATEGORY_NOT_SPACE,
    CATEGORY_WORD: CATEGORY_LOC_WORD,
    CATEGORY_NOT_WORD: CATEGORY_LOC_NOT_WORD,
    CATEGORY_LINEBREAK: CATEGORY_LINEBREAK,
    CATEGORY_NOT_LINEBREAK: CATEGORY_NOT_LINEBREAK
}

CH_UNICODE = {
    CATEGORY_DIGIT: CATEGORY_UNI_DIGIT,
    CATEGORY_NOT_DIGIT: CATEGORY_UNI_NOT_DIGIT,
    CATEGORY_SPACE: CATEGORY_UNI_SPACE,
    CATEGORY_NOT_SPACE: CATEGORY_UNI_NOT_SPACE,
    CATEGORY_WORD: CATEGORY_UNI_WORD,
    CATEGORY_NOT_WORD: CATEGORY_UNI_NOT_WORD,
    CATEGORY_LINEBREAK: CATEGORY_UNI_LINEBREAK,
    CATEGORY_NOT_LINEBREAK: CATEGORY_UNI_NOT_LINEBREAK
}

# flags
SRE_FLAG_TEMPLATE = 1  # template mode (disable backtracking)
SRE_FLAG_IGNORECASE = 2  # case insensitive
SRE_FLAG_LOCALE = 4  # honour system locale
SRE_FLAG_MULTILINE = 8  # treat target as multiline string
SRE_FLAG_DOTALL = 16  # treat target as a single string
SRE_FLAG_UNICODE = 32  # use unicode locale
SRE_FLAG_VERBOSE = 64  # ignore whitespace and comments
SRE_FLAG_DEBUG = 128  # debugging

# flags for INFO primitive
SRE_INFO_PREFIX = 1  # has prefix
SRE_INFO_LITERAL = 2  # entire pattern is literal (given by prefix)
SRE_INFO_CHARSET = 4  # pattern starts with character from given set


================================================
FILE: gixy/core/sre_parse/sre_parse.py
================================================
# flake8: noqa

#
# Secret Labs' Regular Expression Engine
#
# convert re-style regular expression to sre pattern
#
# Copyright (c) 1998-2001 by Secret Labs AB.  All rights reserved.
#
# See the sre.py file for information on usage and redistribution.
#

from __future__ import print_function

"""Internal support module for sre"""

from sre_constants import *

SPECIAL_CHARS = ".\\[{()*+?^$|"
REPEAT_CHARS = "*+?{"

DIGITS = set("0123456789")

OCTDIGITS = set("01234567")
HEXDIGITS = set("0123456789abcdefABCDEF")

WHITESPACE = set(" \t\n\r\v\f")

ESCAPES = {
    r"\a": (LITERAL, ord("\a")),
    r"\b": (LITERAL, ord("\b")),
    r"\f": (LITERAL, ord("\f")),
    r"\n": (LITERAL, ord("\n")),
    r"\r": (LITERAL, ord("\r")),
    r"\t": (LITERAL, ord("\t")),
    r"\v": (LITERAL, ord("\v")),
    r"\\": (LITERAL, ord("\\"))
}

CATEGORIES = {
    r"\A": (AT, AT_BEGINNING_STRING),  # start of string
    r"\b": (AT, AT_BOUNDARY),
    r"\B": (AT, AT_NON_BOUNDARY),
    r"\d": (IN, [(CATEGORY, CATEGORY_DIGIT)]),
    r"\D": (IN, [(CATEGORY, CATEGORY_NOT_DIGIT)]),
    r"\s": (IN, [(CATEGORY, CATEGORY_SPACE)]),
    r"\S": (IN, [(CATEGORY, CATEGORY_NOT_SPACE)]),
    r"\w": (IN, [(CATEGORY, CATEGORY_WORD)]),
    r"\W": (IN, [(CATEGORY, CATEGORY_NOT_WORD)]),
    r"\Z": (AT, AT_END_STRING),  # end of string
}

FLAGS = {
    # standard flags
    "i": SRE_FLAG_IGNORECASE,
    "L": SRE_FLAG_LOCALE,
    "m": SRE_FLAG_MULTILINE,
    "s": SRE_FLAG_DOTALL,
    "x": SRE_FLAG_VERBOSE,
    # extensions
    "t": SRE_FLAG_TEMPLATE,
    "u": SRE_FLAG_UNICODE,
}


class Pattern:
    # master pattern object.  keeps track of global attributes
    def __init__(self):
        self.flags = 0
        self.open = []
        self.groups = 1
        self.groupdict = {}
        self.lookbehind = 0

    def opengroup(self, name=None):
        gid = self.groups
        self.groups = gid + 1
        if name is not None:
            ogid = self.groupdict.get(name, None)
            if ogid is not None:
                raise error(("redefinition of group name %s as group %d; "
                             "was group %d" % (repr(name), gid, ogid)))
            self.groupdict[name] = gid
        self.open.append(gid)
        return gid

    def closegroup(self, gid):
        self.open.remove(gid)

    def checkgroup(self, gid):
        return gid < self.groups and gid not in self.open


class SubPattern:
    # a subpattern, in intermediate form
    def __init__(self, pattern, data=None):
        self.pattern = pattern
        if data is None:
            data = []
        self.data = data
        self.width = None

    def __repr__(self):
        return repr(self.data)

    def __len__(self):
        return len(self.data)

    def __delitem__(self, index):
        del self.data[index]

    def __getitem__(self, index):
        if isinstance(index, slice):
            return SubPattern(self.pattern, self.data[index])
        return self.data[index]

    def __setitem__(self, index, code):
        self.data[index] = code

    def insert(self, index, code):
        self.data.insert(index, code)

    def append(self, code):
        self.data.append(code)

    def getwidth(self):
        # determine the width (min, max) for this subpattern
        if self.width:
            return self.width
        lo = hi = 0
        UNITCODES = (ANY, RANGE, IN, LITERAL, NOT_LITERAL, CATEGORY)
        REPEATCODES = (MIN_REPEAT, MAX_REPEAT)
        for op, av in self.data:
            if op is BRANCH:
                i = MAXREPEAT - 1
                j = 0
                for av in av[1]:
                    l, h = av.getwidth()
                    i = min(i, l)
                    j = max(j, h)
                lo = lo + i
                hi = hi + j
            elif op is CALL:
                i, j = av.getwidth()
                lo = lo + i
                hi = hi + j
            elif op is SUBPATTERN:
                i, j = av[1].getwidth()
                lo = lo + i
                hi = hi + j
            elif op in REPEATCODES:
                i, j = av[2].getwidth()
                lo = lo + i * av[0]
                hi = hi + j * av[1]
            elif op in UNITCODES:
                lo = lo + 1
                hi = hi + 1
            elif op == SUCCESS:
                break
        self.width = min(lo, MAXREPEAT - 1), min(hi, MAXREPEAT)
        return self.width


class Tokenizer:
    def __init__(self, string):
        self.string = string
        self.index = 0
        self.__next()

    def __next(self):
        if self.index >= len(self.string):
            self.next = None
            return
        char = self.string[self.index]
        if char[0] == "\\":
            try:
                c = self.string[self.index + 1]
            except IndexError:
                raise error("bogus escape (end of line)")
            char = char + c
        self.index = self.index + len(char)
        self.next = char

    def match(self, char, skip=1):
        if char == self.next:
            if skip:
                self.__next()
            return 1
        return 0

    def get(self):
        this = self.next
        self.__next()
        return this

    def tell(self):
        return self.index, self.next

    def seek(self, index):
        self.index, self.next = index


def isident(char):
    return "a" <= char <= "z" or "A" <= char <= "Z" or char == "_"


def isdigit(char):
    return "0" <= char <= "9"


def isname(name):
    # check that group name is a valid string
    if not isident(name[0]):
        return False
    for char in name[1:]:
        if not isident(char) and not isdigit(char):
            return False
    return True


def _class_escape(source, escape):
    # handle escape code inside character class
    code = ESCAPES.get(escape)
    if code:
        return code
    code = CATEGORIES.get(escape)
    if code and code[0] == IN:
        return code
    try:
        c = escape[1:2]
        if c == "x":
            # hexadecimal escape (exactly two digits)
            while source.next in HEXDIGITS and len(escape) < 4:
                escape = escape + source.get()
            escape = escape[2:]
            if len(escape) != 2:
                raise error("bogus escape: %s" % repr("\\" + escape))
            return LITERAL, int(escape, 16) & 0xff
        elif c in OCTDIGITS:
            # octal escape (up to three digits)
            while source.next in OCTDIGITS and len(escape) < 4:
                escape = escape + source.get()
            escape = escape[1:]
            return LITERAL, int(escape, 8) & 0xff
        elif c in DIGITS:
            raise error("bogus escape: %s" % repr(escape))
        if len(escape) == 2:
            return LITERAL, ord(escape[1])
    except ValueError:
        pass
    raise error("bogus escape: %s" % repr(escape))


def _escape(source, escape, state):
    # handle escape code in expression
    code = CATEGORIES.get(escape)
    if code:
        return code
    code = ESCAPES.get(escape)
    if code:
        return code
    try:
        c = escape[1:2]
        if c == "x":
            # hexadecimal escape
            while source.next in HEXDIGITS and len(escape) < 4:
                escape = escape + source.get()
            if len(escape) != 4:
                raise ValueError
            return LITERAL, int(escape[2:], 16) & 0xff
        elif c == "0":
            # octal escape
            while source.next in OCTDIGITS and len(escape) < 4:
                escape = escape + source.get()
            return LITERAL, int(escape[1:], 8) & 0xff
        elif c in DIGITS:
            # octal escape *or* decimal group reference (sigh)
            if source.next in DIGITS:
                escape = escape + source.get()
                if (escape[1] in OCTDIGITS and escape[2] in OCTDIGITS and
                            source.next in OCTDIGITS):
                    # got three octal digits; this is an octal escape
                    escape = escape + source.get()
                    return LITERAL, int(escape[1:], 8) & 0xff
            # not an octal escape, so this is a group reference
            group = int(escape[1:])
            if group < state.groups:
                if not state.checkgroup(group):
                    raise error("cannot refer to open group")
                if state.lookbehind:
                    import warnings
                    warnings.warn('group references in lookbehind '
                                  'assertions are not supported',
                                  RuntimeWarning)
                return GROUPREF, group
            raise ValueError
        if len(escape) == 2:
            return LITERAL, ord(escape[1])
    except ValueError:
        pass
    raise error("bogus escape: %s" % repr(escape))


def _parse_sub(source, state, nested=1):
    # parse an alternation: a|b|c

    items = []
    itemsappend = items.append
    sourcematch = source.match
    while 1:
        itemsappend(_parse(source, state))
        if sourcematch("|"):
            continue
        if not nested:
            break
        if not source.next or sourcematch(")", 0):
            break
        else:
            raise error("pattern not properly closed")

    if len(items) == 1:
        return items[0]

    subpattern = SubPattern(state)
    subpatternappend = subpattern.append

    # check if all items share a common prefix
    while 1:
        prefix = None
        for item in items:
            if not item:
                break
            if prefix is None:
                prefix = item[0]
            elif item[0] != prefix:
                break
        else:
            # all subitems start with a common "prefix".
            # move it out of the branch
            for item in items:
                del item[0]
            subpatternappend(prefix)
            continue  # check next one
        break

    # check if the branch can be replaced by a character set
    for item in items:
        if len(item) != 1 or item[0][0] != LITERAL:
            break
    else:
        # we can store this as a character set instead of a
        # branch (the compiler may optimize this even more)
        set = []
        setappend = set.append
        for item in items:
            setappend(item[0])
        subpatternappend((IN, set))
        return subpattern

    subpattern.append((BRANCH, (None, items)))
    return subpattern


def _parse_sub_cond(source, state, condgroup):
    item_yes = _parse(source, state)
    if source.match("|"):
        item_no = _parse(source, state)
        if source.match("|"):
            raise error("conditional backref with more than two branches")
    else:
        item_no = None
    if source.next and not source.match(")", 0):
        raise error("pattern not properly closed")
    subpattern = SubPattern(state)
    subpattern.append((GROUPREF_EXISTS, (condgroup, item_yes, item_no)))
    return subpattern


_PATTERNENDERS = set("|)")
_ASSERTCHARS = set("=!<")
_LOOKBEHINDASSERTCHARS = set("=!")
_REPEATCODES = set([MIN_REPEAT, MAX_REPEAT])


def _parse(source, state):
    # parse a simple pattern
    subpattern = SubPattern(state)

    # precompute constants into local variables
    subpatternappend = subpattern.append
    sourceget = source.get
    sourcematch = source.match
    _len = len
    PATTERNENDERS = _PATTERNENDERS
    ASSERTCHARS = _ASSERTCHARS
    LOOKBEHINDASSERTCHARS = _LOOKBEHINDASSERTCHARS
    REPEATCODES = _REPEATCODES

    while 1:

        if source.next in PATTERNENDERS:
            break  # end of subpattern
        this = sourceget()
        if this is None:
            break  # end of pattern

        if state.flags & SRE_FLAG_VERBOSE:
            # skip whitespace and comments
            if this in WHITESPACE:
                continue
            if this == "#":
                while 1:
                    this = sourceget()
                    if this in (None, "\n"):
                        break
                continue

        if this and this[0] not in SPECIAL_CHARS:
            subpatternappend((LITERAL, ord(this)))

        elif this == "[":
            # character set
            set = []
            setappend = set.append
            ##          if sourcematch(":"):
            ##              pass # handle character classes
            if sourcematch("^"):
                setappend((NEGATE, None))
            # check remaining characters
            start = set[:]
            while 1:
                this = sourceget()
                if this == "]" and set != start:
                    break
                elif this and this[0] == "\\":
                    code1 = _class_escape(source, this)
                elif this:
                    code1 = LITERAL, ord(this)
                else:
                    raise error("unexpected end of regular expression")
                if sourcematch("-"):
                    # potential range
                    this = sourceget()
                    if this == "]":
                        if code1[0] is IN:
                            code1 = code1[1][0]
                        setappend(code1)
                        setappend((LITERAL, ord("-")))
                        break
                    elif this:
                        if this[0] == "\\":
                            code2 = _class_escape(source, this)
                        else:
                            code2 = LITERAL, ord(this)
                        if code1[0] != LITERAL or code2[0] != LITERAL:
                            raise error("bad character range")
                        lo = code1[1]
                        hi = code2[1]
                        if hi < lo:
                            raise error("bad character range")
                        setappend((RANGE, (lo, hi)))
                    else:
                        raise error("unexpected end of regular expression")
                else:
                    if code1[0] is IN:
                        code1 = code1[1][0]
                    setappend(code1)

            # XXX: <fl> should move set optimization to compiler!
            if _len(set) == 1 and set[0][0] is LITERAL:
                subpatternappend(set[0])  # optimization
            elif _len(set) == 2 and set[0][0] is NEGATE and set[1][0] is LITERAL:
                subpatternappend((NOT_LITERAL, set[1][1]))  # optimization
            else:
                # XXX: <fl> should add charmap optimization here
                subpatternappend((IN, set))

        elif this and this[0] in REPEAT_CHARS:
            # repeat previous item
            if this == "?":
                min, max = 0, 1
            elif this == "*":
                min, max = 0, MAXREPEAT

            elif this == "+":
                min, max = 1, MAXREPEAT
            elif this == "{":
                if source.next == "}":
                    subpatternappend((LITERAL, ord(this)))
                    continue
                here = source.tell()
                min, max = 0, MAXREPEAT
                lo = hi = ""
                while source.next in DIGITS:
                    lo = lo + source.get()
                if sourcematch(","):
                    while source.next in DIGITS:
                        hi = hi + sourceget()
                else:
                    hi = lo
                if not sourcematch("}"):
                    subpatternappend((LITERAL, ord(this)))
                    source.seek(here)
                    continue
                if lo:
                    min = int(lo)
                    if min >= MAXREPEAT:
                        raise OverflowError("the repetition number is too large")
                if hi:
                    max = int(hi)
                    if max >= MAXREPEAT:
                        raise OverflowError("the repetition number is too large")
                    if max < min:
                        raise error("bad repeat interval")
            else:
                raise error("not supported")
            # figure out which item to repeat
            if subpattern:
                item = subpattern[-1:]
            else:
                item = None
            if not item or (_len(item) == 1 and item[0][0] == AT):
                raise error("nothing to repeat")
            if item[0][0] in REPEATCODES:
                raise error("multiple repeat")
            if sourcematch("?"):
                subpattern[-1] = (MIN_REPEAT, (min, max, item))
            else:
                subpattern[-1] = (MAX_REPEAT, (min, max, item))

        elif this == ".":
            subpatternappend((ANY, None))

        elif this == "(":
            group = 1
            name = None
            condgroup = None
            if sourcematch("?"):
                group = 0
                # options
                if sourcematch("P"):
                    # python extensions
                    if sourcematch("<"):
                        # named group: skip forward to end of name
                        name = ""
                        while 1:
                            char = sourceget()
                            if char is None:
                                raise error("unterminated name")
                            if char == ">":
                                break
                            name = name + char
                        group = 1
                        if not name:
                            raise error("missing group name")
                        if not isname(name):
                            raise error("bad character in group name %r" %
                                        name)
                    elif sourcematch("="):
                        # named backreference
                        name = ""
                        while 1:
                            char = sourceget()
                            if char is None:
                                raise error("unterminated name")
                            if char == ")":
                                break
                            name = name + char
                        if not name:
                            raise error("missing group name")
                        if not isname(name):
                            raise error("bad character in backref group name "
                                        "%r" % name)
                        gid = state.groupdict.get(name)
                        if gid is None:
                            msg = "unknown group name: {0!r}".format(name)
                            raise error(msg)
                        if state.lookbehind:
                            import warnings
                            warnings.warn('group references in lookbehind '
                                          'assertions are not supported',
                                          RuntimeWarning)
                        subpatternappend((GROUPREF, gid))
                        continue
                    else:
                        char = sourceget()
                        if char is None:
                            raise error("unexpected end of pattern")
                        raise error("unknown specifier: ?P%s" % char)
                elif sourcematch(":"):
                    # non-capturing group
                    group = 2
                elif sourcematch("#"):
                    # comment
                    while 1:
                        if source.next is None or source.next == ")":
                            break
                        sourceget()
                    if not sourcematch(")"):
                        raise error("unbalanced parenthesis")
                    continue
                elif source.next in ASSERTCHARS:
                    # lookahead assertions
                    char = sourceget()
                    dir = 1
                    if char == "<":
                        if source.next not in LOOKBEHINDASSERTCHARS:
                            raise error("syntax error")
                        dir = -1  # lookbehind
                        char = sourceget()
                        state.lookbehind += 1
                    p = _parse_sub(source, state)
                    if dir < 0:
                        state.lookbehind -= 1
                    if not sourcematch(")"):
                        raise error("unbalanced parenthesis")
                    if char == "=":
                        subpatternappend((ASSERT, (dir, p)))
                    else:
                        subpatternappend((ASSERT_NOT, (dir, p)))
                    continue
                elif sourcematch("("):
                    # conditional backreference group
                    condname = ""
                    while 1:
                        char = sourceget()
                        if char is None:
                            raise error("unterminated name")
                        if char == ")":
                            break
                        condname = condname + char
                    group = 2
                    if not condname:
                        raise error("missing group name")
                    if isname(condname):
                        condgroup = state.groupdict.get(condname)
                        if condgroup is None:
                            msg = "unknown group name: {0!r}".format(condname)
                            raise error(msg)
                    else:
                        try:
                            condgroup = int(condname)
                        except ValueError:
                            raise error("bad character in group name")
                    if state.lookbehind:
                        import warnings
                        warnings.warn('group references in lookbehind '
                                      'assertions are not supported',
                                      RuntimeWarning)
                else:
                    # flags
                    if not source.next in FLAGS:
                        raise error("unexpected end of pattern")
                    while source.next in FLAGS:
                        state.flags = state.flags | FLAGS[sourceget()]
            if group:
                # parse group contents
                if group == 2:
                    # anonymous group
                    group = None
                else:
                    group = state.opengroup(name)
                if condgroup:
                    p = _parse_sub_cond(source, state, condgroup)
                else:
                    p = _parse_sub(source, state)
                if not sourcematch(")"):
                    raise error("unbalanced parenthesis")
                if group is not None:
                    state.closegroup(group)
                subpatternappend((SUBPATTERN, (group, p)))
            else:
                while 1:
                    char = sourceget()
                    if char is None:
                        raise error("unexpected end of pattern")
                    if char == ")":
                        break
                    raise error("unknown extension")

        elif this == "^":
            subpatternappend((AT, AT_BEGINNING))

        elif this == "$":
            subpattern.append((AT, AT_END))

        elif this and this[0] == "\\":
            code = _escape(source, this, state)
            subpatternappend(code)

        else:
            raise error("parser error")

    return subpattern


def parse(str, flags=0, pattern=None):
    # parse 're' pattern into list of (opcode, argument) tuples

    source = Tokenizer(str)

    if pattern is None:
        pattern = Pattern()
    pattern.flags = flags
    pattern.str = str

    p = _parse_sub(source, pattern, 0)

    tail = source.get()
    if tail == ")":
        raise error("unbalanced parenthesis")
    elif tail:
        raise error("bogus characters at end of regular expression")

    if not (flags & SRE_FLAG_VERBOSE) and p.pattern.flags & SRE_FLAG_VERBOSE:
        # the VERBOSE flag was switched on inside the pattern.  to be
        # on the safe side, we'll parse the whole thing again...
        return parse(str, p.pattern.flags)

    if flags & SRE_FLAG_DEBUG:
        p.dump()

    return p


def parse_template(source, pattern):
    # parse 're' replacement string into list of literals and
    # group references
    s = Tokenizer(source)
    sget = s.get
    p = []
    a = p.append

    def literal(literal, p=p, pappend=a):
        if p and p[-1][0] is LITERAL:
            p[-1] = LITERAL, p[-1][1] + literal
        else:
            pappend((LITERAL, literal))

    sep = source[:0]
    if type(sep) is type(""):
        makechar = chr
    else:
        makechar = unichr
    while 1:
        this = sget()
        if this is None:
            break  # end of replacement string
        if this and this[0] == "\\":
            # group
            c = this[1:2]
            if c == "g":
                name = ""
                if s.match("<"):
                    while 1:
                        char = sget()
                        if char is None:
                            raise error("unterminated group name")
                        if char == ">":
                            break
                        name = name + char
                if not name:
                    raise error("missing group name")
                try:
                    index = int(name)
                    if index < 0:
                        raise error("negative group number")
                except ValueError:
                    if not isname(name):
                        raise error("bad character in group name")
                    try:
                        index = pattern.groupindex[name]
                    except KeyError:
                        msg = "unknown group name: {0!r}".format(name)
                        raise IndexError(msg)
                a((MARK, index))
            elif c == "0":
                if s.next in OCTDIGITS:
                    this = this + sget()
                    if s.next in OCTDIGITS:
                        this = this + sget()
                literal(makechar(int(this[1:], 8) & 0xff))
            elif c in DIGITS:
                isoctal = False
                if s.next in DIGITS:
                    this = this + sget()
                    if (c in OCTDIGITS and this[2] in OCTDIGITS and
                                s.next in OCTDIGITS):
                        this = this + sget()
                        isoctal = True
                        literal(makechar(int(this[1:], 8) & 0xff))
                if not isoctal:
                    a((MARK, int(this[1:])))
            else:
                try:
                    this = makechar(ESCAPES[this][1])
                except KeyError:
                    pass
                literal(this)
        else:
            literal(this)
    # convert template to groups and literals lists
    i = 0
    groups = []
    groupsappend = groups.append
    literals = [None] * len(p)
    for c, s in p:
        if c is MARK:
            groupsappend((i, s))
            # literal[i] is already None
        else:
            literals[i] = s
        i = i + 1
    return groups, literals


def expand_template(template, match):
    g = match.group
    sep = match.string[:0]
    groups, literals = template
    literals = literals[:]
    try:
        for index, group in groups:
            literals[index] = s = g(group)
            if s is None:
                raise error("unmatched group")
    except IndexError:
        raise error("invalid group reference")
    return sep.join(literals)


================================================
FILE: gixy/core/utils.py
================================================
def is_indexed_name(name):
    return isinstance(name, int) or (len(name) == 1 and '1' <= name <= '9')


================================================
FILE: gixy/core/variable.py
================================================
import re
import logging

from gixy.core.regexp import Regexp
from gixy.core.context import get_context

LOG = logging.getLogger(__name__)
# See ngx_http_script_compile in http/ngx_http_script.c
EXTRACT_RE = re.compile(r'\$([1-9]|[a-z_][a-z0-9_]*|\{[a-z0-9_]+\})', re.IGNORECASE)


def compile_script(script):
    """
    Compile Nginx script to list of variables.
    Example:
        compile_script('http://$foo:$bar') ->
            [Variable('http://'), Variable($foo), Variable(':', Variable($bar).

    :param str script: Nginx scrip.
    :return Variable[]: list of variable.
    """
    depends = []
    context = get_context()
    for i, var in enumerate(EXTRACT_RE.split(str(script))):
        if i % 2:
            # Variable
            var = var.strip('{}\x20')
            var = context.get_var(var)
            if var:
                depends.append(var)
        elif var:
            # Literal
            depends.append(Variable(name=None, value=var, have_script=False))
    return depends


class Variable(object):
    def __init__(self, name, value=None, boundary=None, provider=None, have_script=True):
        """
        Gixy Nginx variable class - parse and provide helpers to work with it.

        :param str|None name: variable name.
        :param str|Regexp value: variable value..
        :param Regexp boundary: variable boundary set.
        :param Directive provider: directive that provide variable (e.g. if, location, rewrite, etc).
        :param bool have_script: may variable have nginx script or not (mostly used to indicate a string literal).
        """

        self.name = name
        self.value = value
        self.regexp = None
        self.depends = None
        self.boundary = boundary
        self.provider = provider
        if isinstance(value, Regexp):
            self.regexp = value
        elif have_script:
            self.depends = compile_script(value)

    def can_contain(self, char):
        """
        Checks if variable can contain the specified char.

        :param str char: character to test.
        :return: True if variable can contain the specified char, False otherwise.
        """

        # First of all check boundary set
        if self.boundary and not self.boundary.can_contain(char):
            return False

        # Then regexp
        if self.regexp:
            return self.regexp.can_contain(char, skip_literal=True)

        # Then dependencies
        if self.depends:
            return any(dep.can_contain(char) for dep in self.depends)

        # Otherwise user can't control value of this variable
        return False

    def can_startswith(self, char):
        """
        Checks if variable can starts with the specified char.

        :param str char: character to test.
        :return: True if variable can starts with the specified char, False otherwise.
        """

        # First of all check boundary set
        if self.boundary and not self.boundary.can_startswith(char):
            return False

        # Then regexp
        if self.regexp:
            return self.regexp.can_startswith(char)

        # Then dependencies
        if self.depends:
            return self.depends[0].can_startswith(char)

        # Otherwise user can't control value of this variable
        return False

    def must_contain(self, char):
        """
        Checks if variable MUST contain the specified char.

        :param str char: character to test.
        :return: True if variable must contain the specified char, False otherwise.
        """

        # First of all check boundary set
        if self.boundary and self.boundary.must_contain(char):
            return True

        # Then regexp
        if self.regexp:
            return self.regexp.must_contain(char)

        # Then dependencies
        if self.depends:
            return any(dep.must_contain(char) for dep in self.depends)

        # Otherwise checks literal
        return self.value and char in self.value

    def must_startswith(self, char):
        """
        Checks if variable MUST starts with the specified char.

        :param str char: character to test.
        :return: True if variable must starts with the specified char.
        """

        # First of all check boundary set
        if self.boundary and self.boundary.must_startswith(char):
            return True

        # Then regexp
        if self.regexp:
            return self.regexp.must_startswith(char)

        # Then dependencies
        if self.depends:
            return self.depends[0].must_startswith(char)

        # Otherwise checks literal
        return self.value and self.value[0] == char

    @property
    def providers(self):
        """
        Returns list of variable provides.

        :return Directive[]: providers.
        """
        result = []
        if self.provider:
            result.append(self.provider)
        if self.depends:
            for dep in self.depends:
                result += dep.providers
        return result


================================================
FILE: gixy/directives/__init__.py
================================================
import os
from gixy.directives.directive import Directive

DIRECTIVES = {}


def import_directives():
    files_list = os.listdir(os.path.dirname(__file__))
    for directive_file in files_list:
        if not directive_file.endswith(".py") or directive_file.startswith('_'):
            continue
        __import__('gixy.directives.' + os.path.splitext(directive_file)[0], None, None, [''])


def get_all():
    if len(DIRECTIVES):
        return DIRECTIVES

    import_directives()
    for klass in Directive.__subclasses__():
        if not klass.nginx_name:
            continue
        DIRECTIVES[klass.nginx_name] = klass

    return DIRECTIVES


================================================
FILE: gixy/directives/block.py
================================================
from cached_property import cached_property

from gixy.directives.directive import Directive
from gixy.core.variable import Variable
from gixy.core.regexp import Regexp


def get_overrides():
    result = {}
    for klass in Block.__subclasses__():
        if not klass.nginx_name:
            continue

        if not klass.__name__.endswith('Block'):
            continue

        result[klass.nginx_name] = klass
    return result


class Block(Directive):
    nginx_name = None
    is_block = True
    self_context = True

    def __init__(self, name, args):
        super(Block, self).__init__(name, args)
        self.children = []

    def some(self, name, flat=True):
        for child in self.children:
            if child.name == name:
                return child
            if flat and child.is_block and not child.self_context:
                result = child.some(name, flat=flat)
                if result:
                    return result
        return None

    def find(self, name, flat=False):
        result = []
        for child in self.children:
            if child.name == name:
                result.append(child)
            if flat and child.is_block and not child.self_context:
                result += child.find(name)
        return result

    def find_recursive(self, name):
        result = []
        for child in self.children:
            if child.name == name:
                result.append(child)
            if child.is_block:
                result += child.find_recursive(name)
        return result

    def append(self, directive):
        directive.set_parent(self)
        self.children.append(directive)

    def __str__(self):
        return '{name} {args} {{'.format(name=self.name, args=' '.join(self.args))


class Root(Block):
    nginx_name = None

    def __init__(self):
        super(Root, self).__init__(None, [])


class HttpBlock(Block):
    nginx_name = 'http'

    def __init__(self, name, args):
        super(HttpBlock, self).__init__(name, args)


class ServerBlock(Block):
    nginx_name = 'server'

    def __init__(self, name, args):
        super(ServerBlock, self).__init__(name, args)

    def get_names(self):
        return self.find('server_name')

    def __str__(self):
        server_names = [str(sn) for sn in self.find('server_name')]
        if server_names:
            return 'server {{\n{0}'.format('\n'.join(server_names[:2]))
        return 'server {'


class LocationBlock(Block):
    nginx_name = 'location'
    provide_variables = True

    def __init__(self, name, args):
        super(LocationBlock, self).__init__(name, args)
        if len(args) == 2:
            self.modifier, self.path = args
        else:
            self.modifier = None
            self.path = args[0]

    @property
    def is_internal(self):
        return self.some('internal') is not None

    @cached_property
    def variables(self):
        if not self.modifier or self.modifier not in ('~', '~*'):
            return []

        regexp = Regexp(self.path, case_sensitive=self.modifier == '~')
        result = []
        for name, group in regexp.groups.items():
            result.append(Variable(name=name, value=group, boundary=None, provider=self))
        return result


class IfBlock(Block):
    nginx_name = 'if'
    self_context = False

    def __init__(self, name, args):
        super(IfBlock, self).__init__(name, args)
        self.operand = None
        self.value = None
        self.variable = None

        if len(args) == 1:
            # if ($slow)
            self.variable = args[0]
        elif len(args) == 2:
            # if (!-e $foo)
            self.operand, self.value = args
        elif len(args) == 3:
            # if ($request_method = POST)
            self.variable, self.operand, self.value = args
        else:
            raise Exception('Unknown "if" definition, args: {0!r}'.format(args))

    def __str__(self):
        return '{name} ({args}) {{'.format(name=self.name, args=' '.join(self.args))


class IncludeBlock(Block):
    nginx_name = 'include'
    self_context = False

    def __init__(self, name, args):
        super(IncludeBlock, self).__init__(name, args)
        self.file_path = args[0]

    def __str__(self):
        return 'include {0};'.format(self.file_path)


class MapBlock(Block):
    nginx_name = 'map'
    self_context = False
    provide_variables = True

    def __init__(self, name, args):
        super(MapBlock, self).__init__(name, args)
        self.source = args[0]
        self.variable = args[1].strip('$')

    @cached_property
    def variables(self):
        # TODO(buglloc): Finish him!
        return [Variable(name=self.variable, value='', boundary=None, provider=self, have_script=False)]


class GeoBlock(Block):
    nginx_name = 'geo'
    self_context = False
    provide_variables = True

    def __init__(self, name, args):
        super(GeoBlock, self).__init__(name, args)
        if len(args) == 1:  # geo uses $remote_addr as default source of the value
            source = '$remote_addr'
            variable = args[0].strip('$')
        else:
            source = args[0]
            variable = args[1].strip('$')
        self.source = source
        self.variable = variable

    @cached_property
    def variables(self):
        # TODO(buglloc): Finish him! -- same as in MapBlock
        return [Variable(name=self.variable, value='', boundary=None, provider=self, have_script=False)]


================================================
FILE: gixy/directives/directive.py
================================================
from gixy.core.variable import Variable
from gixy.core.regexp import Regexp


def get_overrides():
    result = {}
    for klass in Directive.__subclasses__():
        if not klass.nginx_name:
            continue

        if not klass.__name__.endswith('Directive'):
            continue

        result[klass.nginx_name] = klass
    return result


class Directive(object):
    nginx_name = None
    is_block = False
    provide_variables = False

    def __init__(self, name, args, raw=None):
        self.name = name
        self.parent = None
        self.args = args
        self._raw = raw

    def set_parent(self, parent):
        self.parent = parent

    @property
    def parents(self):
        parent = self.parent
        while parent:
            yield parent
            parent = parent.parent

    @property
    def variables(self):
        raise NotImplementedError()

    def __str__(self):
        return '{name} {args};'.format(name=self.name, args=' '.join(self.args))


class AddHeaderDirective(Directive):
    nginx_name = 'add_header'

    def __init__(self, name, args):
        super(AddHeaderDirective, self).__init__(name, args)
        self.header = args[0].lower()
        self.value = args[1]
        self.always = False
        if len(args) > 2 and args[2] == 'always':
            self.always = True


class SetDirective(Directive):
    nginx_name = 'set'
    provide_variables = True

    def __init__(self, name, args):
        super(SetDirective, self).__init__(name, args)
        self.variable = args[0].strip('$')
        self.value = args[1]

    @property
    def variables(self):
        return [Variable(name=self.variable, value=self.value, provider=self)]


class AuthRequestSetDirective(Directive):
    nginx_name = 'auth_request_set'
    provide_variables = True

    def __init__(self, name, args):
        super(AuthRequestSetDirective, self).__init__(name, args)
        self.variable = args[0].strip('$')
        self.value = args[1]

    @property
    def variables(self):
        return [Variable(name=self.variable, value=self.value, provider=self)]


class PerlSetDirective(Directive):
    nginx_name = 'perl_set'
    provide_variables = True

    def __init__(self, name, args):
        super(PerlSetDirective, self).__init__(name, args)
        self.variable = args[0].strip('$')
        self.value = args[1]

    @property
    def variables(self):
        return [Variable(name=self.variable, provider=self, have_script=False)]


class SetByLuaDirective(Directive):
    nginx_name = 'set_by_lua'
    provide_variables = True

    def __init__(self, name, args):
        super(SetByLuaDirective, self).__init__(name, args)
        self.variable = args[0].strip('$')
        self.value = args[1]

    @property
    def variables(self):
        return [Variable(name=self.variable, provider=self, have_script=False)]


class RewriteDirective(Directive):
    nginx_name = 'rewrite'
    provide_variables = True
    boundary = Regexp(r'[^\s\r\n]')

    def __init__(self, name, args):
        super(RewriteDirective, self).__init__(name, args)
        self.pattern = args[0]
        self.replace = args[1]
        self.flag = None
        if len(args) > 2:
            self.flag = args[2]

    @property
    def variables(self):
        regexp = Regexp(self.pattern, case_sensitive=True)
        result = []
        for name, group in regexp.groups.items():
            result.append(Variable(name=name, value=group, boundary=self.boundary, provider=self))
        return result


class RootDirective(Directive):
    nginx_name = 'root'
    provide_variables = True

    def __init__(self, name, args):
        super(RootDirective, self).__init__(name, args)
        self.path = args[0]

    @property
    def variables(self):
        return [Variable(name='document_root', value=self.path, provider=self)]


class AliasDirective(Directive):
    nginx_name = 'alias'

    def __init__(self, name, args):
        super(AliasDirective, self).__init__(name, args)
        self.path = args[0]


================================================
FILE: gixy/formatters/__init__.py
================================================
import os
from gixy.formatters.base import BaseFormatter

FORMATTERS = {}


def import_formatters():
    files_list = os.listdir(os.path.dirname(__file__))
    for formatter_file in files_list:
        if not formatter_file.endswith(".py") or formatter_file.startswith('_'):
            continue
        __import__('gixy.formatters.' + os.path.splitext(formatter_file)[0], None, None, [''])


def get_all():
    if len(FORMATTERS):
        return FORMATTERS

    import_formatters()
    for klass in BaseFormatter.__subclasses__():
        FORMATTERS[klass.__name__.replace('Formatter', '').lower()] = klass

    return FORMATTERS


================================================
FILE: gixy/formatters/_jinja.py
================================================
from __future__ import absolute_import
from jinja2 import Environment, PackageLoader

from gixy.utils.text import to_text


def load_template(name):
    env = Environment(loader=PackageLoader('gixy', 'formatters/templates'), trim_blocks=True, lstrip_blocks=True)
    env.filters['to_text'] = to_text_filter
    return env.get_template(name)


def to_text_filter(text):
    try:
        return text.encode('latin1').decode('utf-8')
    except UnicodeEncodeError:
        return to_text(text)


================================================
FILE: gixy/formatters/base.py
================================================
from __future__ import absolute_import

import gixy
from gixy.directives import block


class BaseFormatter(object):
    skip_parents = set([block.Root, block.HttpBlock])

    def __init__(self):
        self.reports = {}
        self.stats = dict.fromkeys(gixy.severity.ALL, 0)

    def format_reports(self, reports, stats):
        raise NotImplementedError("Formatter must override format_reports function")

    def feed(self, path, manager):
        for severity in gixy.severity.ALL:
            self.stats[severity] += manager.stats[severity]

        self.reports[path] = []
        for result in manager.results:
            report = self._prepare_result(manager.root,
                                          summary=result.summary,
                                          severity=result.severity,
                                          description=result.description,
                                          issues=result.issues,
                                          plugin=result.name,
                                          help_url=result.help_url)
            self.reports[path].extend(report)

    def flush(self):
        return self.format_reports(self.reports, self.stats)

    def _prepare_result(self, root, issues, severity, summary, description, plugin, help_url):
        result = {}
        for issue in issues:
            report = dict(
                plugin=plugin,
                summary=issue.summary or summary,
                severity=issue.severity or severity,
                description=issue.description or description,
                help_url=issue.help_url or help_url,
                reason=issue.reason or '',
            )
            key = ''.join(report.values())
            report['directives'] = issue.directives
            if key in result:
                result[key]['directives'].extend(report['directives'])
            else:
                result[key] = report

        for report in result.values():
            if report['directives']:
                config = self._resolve_config(root, report['directives'])
            else:
                config = ''

            del report['directives']
            report['config'] = config
            yield report

    def _resolve_config(self, root, directives):
        points = set()
        for directive in directives:
            points.add(directive)
            points.update(p for p in directive.parents)

        result = self._traverse_tree(root, points, 0)
        return '\n'.join(result)

    def _traverse_tree(self, tree, points, level):
        result = []
        for leap in tree.children:
            if leap not in points:
                continue
            printable = type(leap) not in self.skip_parents
            # Special hack for includes
            # TODO(buglloc): fix me
            have_parentheses = type(leap) != block.IncludeBlock

            if printable:
                if leap.is_block:
                    result.append('')
                directive = str(leap).replace('\n', '\n' + '\t' * (level + 1))
                result.append('{indent:s}{dir:s}'.format(indent='\t' * level, dir=directive))

            if leap.is_block:
                result.extend(self._traverse_tree(leap, points, level + 1 if printable else level))
                if printable and have_parentheses:
                    result.append('{indent:s}}}'.format(indent='\t' * level))

        return result


================================================
FILE: gixy/formatters/console.py
================================================
from __future__ import absolute_import

from gixy.formatters.base import BaseFormatter
from gixy.formatters._jinja import load_template


class ConsoleFormatter(BaseFormatter):
    def __init__(self):
        super(ConsoleFormatter, self).__init__()
        self.template = load_template('console.j2')

    def format_reports(self, reports, stats):
        return self.template.render(reports=reports, stats=stats)


================================================
FILE: gixy/formatters/json.py
================================================
from __future__ import absolute_import

import json

from gixy.formatters.base import BaseFormatter


class JsonFormatter(BaseFormatter):
    def format_reports(self, reports, stats):
        result = []
        for path, issues in reports.items():
            for issue in issues:
                result.append(dict(
                    path=path,
                    plugin=issue['plugin'],
                    summary=issue['summary'],
                    severity=issue['severity'],
                    description=issue['description'],
                    reference=issue['help_url'],
                    reason=issue['reason'],
                    config=issue['config']
                ))

        return json.dumps(result, sort_keys=True, indent=2, separators=(',', ': '))


================================================
FILE: gixy/formatters/templates/console.j2
================================================
{% set colors = {'DEF': '\033[0m', 'TITLE': '\033[95m', 'UNSPECIFIED': '\033[0m', 'LOW': '\033[94m', 'MEDIUM': '\033[93m', 'HIGH': '\033[91m'} %}

{{ colors.TITLE }}==================== Results ==================={{ colors.DEF }}
{% for path, issues in reports.items() %}
{% if reports|length > 1 %}
File path: {{ path }}
{% endif %}
{% if not issues %}
No issues found.

{% else %}

{% for issue in issues|sort(attribute='severity') %}
{{ colors[issue.severity] }}>> Problem: [{{ issue.plugin }}] {{ issue.summary }}{{ colors.DEF }}
{% if issue.description %}
Description: {{ issue.description }}
{% endif %}
{% if issue.help_url %}
Additional info: {{ issue.help_url }}
{% endif %}
{% if issue.reason %}
Reason: {{ issue.reason }}
{% endif %}
Pseudo config:
{{ issue.config | to_text }}

{% if not loop.last %}
------------------------------------------------

{% endif %}
{% endfor %}
{% endif %}
{% if not loop.last %}
--------8<--------8<--------8<--------8<--------
{% endif %}
{% endfor %}
{% if stats %}
{{ colors.TITLE }}==================== Summary ==================={{ colors.DEF }}
Total issues:
    Unspecified: {{ stats.UNSPECIFIED }}
    Low: {{ stats.LOW }}
    Medium: {{ stats.MEDIUM }}
    High: {{ stats.HIGH }}
{% endif %}


================================================
FILE: gixy/formatters/templates/text.j2
================================================

==================== Results ===================
{% for path, issues in reports.items() %}
{% if reports|length > 1 %}
File path: {{ path }}
{% endif %}
{% if not issues %}
No issues found.

{% else %}

{% for issue in issues|sort(attribute='severity') %}
>> Problem: [{{ issue.plugin }}] {{ issue.summary }}
Severity: {{ issue.severity }}
{% if issue.description %}
Description: {{ issue.description }}
{% endif %}
{% if issue.help_url %}
Additional info: {{ issue.help_url }}
{% endif %}
{% if issue.reason %}
Reason: {{ issue.reason }}
{% endif %}
Pseudo config:
{{ issue.config | to_text }}

{% if not loop.last %}
------------------------------------------------

{% endif %}
{% endfor %}
{% endif %}
{% if not loop.last %}
--------8<--------8<--------8<--------8<--------
{% endif %}
{% endfor %}
{% if stats %}
==================== Summary ===================
Total issues:
    Unspecified: {{ stats.UNSPECIFIED }}
    Low: {{ stats.LOW }}
    Medium: {{ stats.MEDIUM }}
    High: {{ stats.HIGH }}
{% endif %}


================================================
FILE: gixy/formatters/text.py
================================================
from __future__ import absolute_import

from gixy.formatters.base import BaseFormatter
from gixy.formatters._jinja import load_template


class TextFormatter(BaseFormatter):
    def __init__(self):
        super(TextFormatter, self).__init__()
        self.template = load_template('text.j2')

    def format_reports(self, reports, stats):
        return self.template.render(reports=reports, stats=stats)


================================================
FILE: gixy/parser/__init__.py
================================================


================================================
FILE: gixy/parser/nginx_parser.py
================================================
import os
import glob
import logging
import fnmatch

from pyparsing import ParseException
from gixy.core.exceptions import InvalidConfiguration
from gixy.parser import raw_parser
from gixy.directives import block, directive
from gixy.utils.text import to_native

LOG = logging.getLogger(__name__)


class NginxParser(object):
    def __init__(self, cwd='', allow_includes=True):
        self.cwd = cwd
        self.configs = {}
        self.is_dump = False
        self.allow_includes = allow_includes
        self.directives = {}
        self.parser = raw_parser.RawParser()
        self._init_directives()

    def parse_file(self, path, root=None):
        LOG.debug("Parse file: {0}".format(path))
        content = open(path).read()
        return self.parse(content=content, root=root, path_info=path)

    def parse(self, content, root=None, path_info=None):
        if not root:
            root = block.Root()
        try:
            parsed = self.parser.parse(content)
        except ParseException as e:
            error_msg = 'char {char} (line:{line}, col:{col})'.format(char=e.loc, line=e.lineno, col=e.col)
            if path_info:
                LOG.error('Failed to parse config "{file}": {error}'.format(file=path_info, error=error_msg))
            else:
                LOG.error('Failed to parse config: {error}'.format(error=error_msg))
            raise InvalidConfiguration(error_msg)

        if len(parsed) and parsed[0].getName() == 'file_delimiter':
            #  Were parse nginx dump
            LOG.info('Switched to parse nginx configuration dump.')
            root_filename = self._prepare_dump(parsed)
            self.is_dump = True
            self.cwd = os.path.dirname(root_filename)
            parsed = self.configs[root_filename]

        self.parse_block(parsed, root)
        return root

    def parse_block(self, parsed_block, parent):
        for parsed in parsed_block:
            parsed_type = parsed.getName()
            parsed_name = parsed[0]
            parsed_args = parsed[1:]
            if parsed_type == 'include':
                # TODO: WTF?!
                self._resolve_include(parsed_args, parent)
            else:
                directive_inst = self.directive_factory(parsed_type, parsed_name, parsed_args)
                if directive_inst:
                    parent.append(directive_inst)

    def directive_factory(self, parsed_type, parsed_name, parsed_args):
        klass = self._get_directive_class(parsed_type, parsed_name)
        if not klass:
            return None

        if klass.is_block:
            args = [to_native(v).strip() for v in parsed_args[0]]
            children = parsed_args[1]

            inst = klass(parsed_name, args)
            self.parse_block(children, inst)
            return inst
        else:
            args = [to_native(v).strip() for v in parsed_args]
            return klass(parsed_name, args)

    def _get_directive_class(self, parsed_type, parsed_name):
        if parsed_type in self.directives and parsed_name in self.directives[parsed_type]:
            return self.directives[parsed_type][parsed_name]
        elif parsed_type == 'block':
            return block.Block
        elif parsed_type == 'directive':
            return directive.Directive
        elif parsed_type == 'unparsed_block':
            LOG.warning('Skip unparseable block: "%s"', parsed_name)
            return None
        else:
            return None

    def _init_directives(self):
        self.directives['block'] = block.get_overrides()
        self.directives['directive'] = directive.get_overrides()

    def _resolve_include(self, args, parent):
        pattern = args[0]
        #  TODO(buglloc): maybe file providers?
        if self.is_dump:
            return self._resolve_dump_include(pattern=pattern, parent=parent)
        if not self.allow_includes:
            LOG.debug('Includes are disallowed, skip: {0}'.format(pattern))
            return

        return self._resolve_file_include(pattern=pattern, parent=parent)

    def _resolve_file_include(self, pattern, parent):
        path = os.path.join(self.cwd, pattern)
        exists = False
        for file_path in glob.iglob(path):
            if not os.path.exists(file_path):
                continue
            exists = True
            include = block.IncludeBlock('include', [file_path])
            parent.append(include)
            self.parse_file(file_path, include)

        if not exists:
            LOG.warning('File not found: {0}'.format(path))

    def _resolve_dump_include(self, pattern, parent):
        path = os.path.join(self.cwd, pattern)
        founded = False
        for file_path, parsed in self.configs.items():
            if fnmatch.fnmatch(file_path, path):
                founded = True
                include = block.IncludeBlock('include', [file_path])
                parent.append(include)
                self.parse_block(parsed, include)

        if not founded:
            LOG.warning("File not found: {0}".format(path))

    def _prepare_dump(self, parsed_block):
        filename = ''
        root_filename = ''
        for parsed in parsed_block:
            if parsed.getName() == 'file_delimiter':
                if not filename:
                    root_filename = parsed[0]
                filename = parsed[0]
                self.configs[filename] = []
                continue
            self.configs[filename].append(parsed)
        return root_filename


================================================
FILE: gixy/parser/raw_parser.py
================================================
import logging
import codecs
import six
from cached_property import cached_property

from pyparsing import (
    Literal, Suppress, White, Word, alphanums, Forward, Group, Optional, Combine,
    Keyword, OneOrMore, ZeroOrMore, Regex, QuotedString, nestedExpr, ParseResults)

LOG = logging.getLogger(__name__)


class NginxQuotedString(QuotedString):
    def __init__(self, quoteChar):
        super(NginxQuotedString, self).__init__(quoteChar, escChar='\\', multiline=True)
        # Nginx parse quoted values in special manner:
        # '^https?:\/\/yandex\.ru\/\00\'\"' -> ^https?:\/\/yandex\.ru\/\00'"
        # TODO(buglloc): research and find another special characters!

        self.escCharReplacePattern = '\\\\(\'|")'


class RawParser(object):
    """
    A class that parses nginx configuration with pyparsing
    """

    def parse(self, data):
        """
        Returns the parsed tree.
        """
        if isinstance(data, six.binary_type):
            if data[:3] == codecs.BOM_UTF8:
                encoding = 'utf-8-sig'
            else:
                encoding = 'latin1'
            content = data.decode(encoding).strip()
        else:
            content = data.strip()

        if not content:
            return ParseResults()

        return self.script.parseString(content, parseAll=True)

    @cached_property
    def script(self):
        # constants
        left_bracket = Suppress("{")
        right_bracket = Suppress("}")
        semicolon = Suppress(";")
        space = White().suppress()
        keyword = Word(alphanums + ".+-_/")
        path = Word(alphanums + ".-_/")
        variable = Word("$_-" + alphanums)
        value_wq = Regex(r'(?:\([^\s;]*\)|\$\{\w+\}|[^\s;(){}])+')
        value_sq = NginxQuotedString(quoteChar="'")
        value_dq = NginxQuotedString(quoteChar='"')
        value = (value_dq | value_sq | value_wq)
        # modifier for location uri [ = | ~ | ~* | ^~ ]
        location_modifier = (
            Keyword("=") |
            Keyword("~*") | Keyword("~") |
            Keyword("^~"))
        # modifier for if statement
        if_modifier = Combine(Optional("!") + (
            Keyword("=") |
            Keyword("~*") | Keyword("~") |
            (Literal("-") + (Literal("f") | Literal("d") | Literal("e") | Literal("x")))))
        # This ugly workaround needed to parse unquoted regex with nested parentheses
        # so we capture all content between parentheses and then parse it :(
        # TODO(buglloc): may be use something better?
        condition_body = (
            (if_modifier + Optional(space) + value) |
            (variable + Optional(space + if_modifier + Optional(space) + value))
        )
        condition = Regex(r'\((?:[^()\n\r\\]|(?:\(.*\))|(?:\\.))+?\)')\
            .setParseAction(lambda s, l, t: condition_body.parseString(t[0][1:-1]))

        # rules
        include = (
            Keyword("include") +
            space +
            value +
            semicolon
        )("include")

        directive = (
            keyword +
            ZeroOrMore(space + value) +
            semicolon
        )("directive")

        file_delimiter = (
            Suppress("# configuration file ") +
            path +
            Su
Download .txt
gitextract__w1ux7cu/

├── .dockerignore
├── .editorconfig
├── .gitignore
├── .travis.yml
├── AUTHORS
├── CONTRIBUTING.md
├── Dockerfile
├── LICENSE
├── MANIFEST.in
├── Makefile
├── README.RU.md
├── README.md
├── docs/
│   ├── en/
│   │   └── plugins/
│   │       ├── addheadermultiline.md
│   │       ├── addheaderredefinition.md
│   │       ├── aliastraversal.md
│   │       ├── hostspoofing.md
│   │       ├── httpsplitting.md
│   │       ├── origins.md
│   │       ├── ssrf.md
│   │       └── validreferers.md
│   └── ru/
│       └── plugins/
│           ├── addheadermultiline.md
│           ├── addheaderredefinition.md
│           ├── aliastraversal.md
│           ├── hostspoofing.md
│           ├── httpsplitting.md
│           ├── origins.md
│           ├── ssrf.md
│           └── validreferers.md
├── gixy/
│   ├── __init__.py
│   ├── cli/
│   │   ├── __init__.py
│   │   ├── argparser.py
│   │   └── main.py
│   ├── core/
│   │   ├── __init__.py
│   │   ├── builtin_variables.py
│   │   ├── config.py
│   │   ├── context.py
│   │   ├── exceptions.py
│   │   ├── issue.py
│   │   ├── manager.py
│   │   ├── plugins_manager.py
│   │   ├── regexp.py
│   │   ├── severity.py
│   │   ├── sre_parse/
│   │   │   ├── __init__.py
│   │   │   ├── sre_constants.py
│   │   │   └── sre_parse.py
│   │   ├── utils.py
│   │   └── variable.py
│   ├── directives/
│   │   ├── __init__.py
│   │   ├── block.py
│   │   └── directive.py
│   ├── formatters/
│   │   ├── __init__.py
│   │   ├── _jinja.py
│   │   ├── base.py
│   │   ├── console.py
│   │   ├── json.py
│   │   ├── templates/
│   │   │   ├── console.j2
│   │   │   └── text.j2
│   │   └── text.py
│   ├── parser/
│   │   ├── __init__.py
│   │   ├── nginx_parser.py
│   │   └── raw_parser.py
│   ├── plugins/
│   │   ├── __init__.py
│   │   ├── add_header_multiline.py
│   │   ├── add_header_redefinition.py
│   │   ├── alias_traversal.py
│   │   ├── host_spoofing.py
│   │   ├── http_splitting.py
│   │   ├── origins.py
│   │   ├── plugin.py
│   │   ├── ssrf.py
│   │   └── valid_referers.py
│   └── utils/
│       ├── __init__.py
│       └── text.py
├── requirements.dev.txt
├── requirements.txt
├── rpm/
│   ├── gixy.spec
│   └── python-argparse.spec
├── setup.py
├── tests/
│   ├── __init__.py
│   ├── asserts.py
│   ├── core/
│   │   ├── __init__.py
│   │   ├── test_context.py
│   │   ├── test_regexp.py
│   │   └── test_variable.py
│   ├── directives/
│   │   ├── __init__.py
│   │   ├── test_block.py
│   │   └── test_directive.py
│   ├── parser/
│   │   ├── __init__.py
│   │   ├── test_nginx_parser.py
│   │   └── test_raw_parser.py
│   ├── plugins/
│   │   ├── __init__.py
│   │   ├── simply/
│   │   │   ├── add_header_multiline/
│   │   │   │   ├── add_header.conf
│   │   │   │   ├── add_header_fp.conf
│   │   │   │   ├── config.json
│   │   │   │   ├── more_set_headers.conf
│   │   │   │   ├── more_set_headers_fp.conf
│   │   │   │   ├── more_set_headers_multiple.conf
│   │   │   │   ├── more_set_headers_replace.conf
│   │   │   │   ├── more_set_headers_replace_fp.conf
│   │   │   │   ├── more_set_headers_status_fp.conf
│   │   │   │   └── more_set_headers_type_fp.conf
│   │   │   ├── add_header_redefinition/
│   │   │   │   ├── config.json
│   │   │   │   ├── duplicate_fp.conf
│   │   │   │   ├── if_replaces.conf
│   │   │   │   ├── location_replaces.conf
│   │   │   │   ├── nested_block.conf
│   │   │   │   ├── non_block_fp.conf
│   │   │   │   ├── not_secure_both_fp.conf
│   │   │   │   ├── not_secure_outer_fp.conf
│   │   │   │   └── step_replaces.conf
│   │   │   ├── alias_traversal/
│   │   │   │   ├── config.json
│   │   │   │   ├── nested.conf
│   │   │   │   ├── nested_fp.conf
│   │   │   │   ├── not_slashed_alias.conf
│   │   │   │   ├── not_slashed_alias_fp.conf
│   │   │   │   ├── simple.conf
│   │   │   │   ├── simple_fp.conf
│   │   │   │   ├── slashed_alias.conf
│   │   │   │   └── slashed_alias_fp.conf
│   │   │   ├── host_spoofing/
│   │   │   │   ├── config.json
│   │   │   │   ├── http_fp.conf
│   │   │   │   ├── http_host.conf
│   │   │   │   ├── http_host_diff_case.conf
│   │   │   │   └── some_arg.conf
│   │   │   ├── http_splitting/
│   │   │   │   ├── add_header_uri.conf
│   │   │   │   ├── config.json
│   │   │   │   ├── dont_report_not_resolved_var_fp.conf
│   │   │   │   ├── proxy_from_location_var.conf
│   │   │   │   ├── proxy_from_location_var_var.conf
│   │   │   │   ├── proxy_from_location_var_var_fp.conf
│   │   │   │   ├── proxy_from_location_var_var_var.conf
│   │   │   │   ├── proxy_pass_cr_fp.conf
│   │   │   │   ├── proxy_pass_ducument_uri.conf
│   │   │   │   ├── proxy_pass_lf.conf
│   │   │   │   ├── proxy_set_header_ducument_uri.conf
│   │   │   │   ├── return_403_fp.conf
│   │   │   │   ├── return_request_uri_fp.conf
│   │   │   │   ├── rewrite_extract_fp.conf
│   │   │   │   ├── rewrite_uri.conf
│   │   │   │   └── rewrite_uri_after_var.conf
│   │   │   ├── origins/
│   │   │   │   ├── config.json
│   │   │   │   ├── metrika.conf
│   │   │   │   ├── origin.conf
│   │   │   │   ├── origin_fp.conf
│   │   │   │   ├── origin_https.conf
│   │   │   │   ├── origin_https_fp.conf
│   │   │   │   ├── origin_w_slash_anchored_fp.conf
│   │   │   │   ├── origin_w_slash_fp.conf
│   │   │   │   ├── origin_wo_slash.conf
│   │   │   │   ├── referer.conf
│   │   │   │   ├── referer_fp.conf
│   │   │   │   ├── referer_subdomain.conf
│   │   │   │   ├── referer_subdomain_fp.conf
│   │   │   │   ├── structure_dot.conf
│   │   │   │   ├── structure_fp.conf
│   │   │   │   ├── structure_prefix.conf
│   │   │   │   ├── structure_suffix.conf
│   │   │   │   └── webvisor.conf
│   │   │   ├── ssrf/
│   │   │   │   ├── config.json
│   │   │   │   ├── have_internal_fp.conf
│   │   │   │   ├── host_w_const_start.conf
│   │   │   │   ├── host_w_const_start_arg.conf
│   │   │   │   ├── not_host_var_fp.conf
│   │   │   │   ├── request_uri_fp.conf
│   │   │   │   ├── request_uri_var_fp.conf
│   │   │   │   ├── scheme_var.conf
│   │   │   │   ├── single_var.conf
│   │   │   │   ├── used_arg.conf
│   │   │   │   ├── vars_from_loc.conf
│   │   │   │   └── with_const_scheme.conf
│   │   │   └── valid_referers/
│   │   │       ├── config.json
│   │   │       ├── none_first.conf
│   │   │       ├── none_last.conf
│   │   │       ├── none_middle.conf
│   │   │       └── wo_none_fp.conf
│   │   └── test_simply.py
│   └── utils.py
└── tox.ini
Download .txt
SYMBOL INDEX (494 symbols across 46 files)

FILE: gixy/cli/argparser.py
  class GixyConfigParser (line 15) | class GixyConfigParser(DefaultConfigFileParser):
    method get_syntax_description (line 16) | def get_syntax_description(self):
    method parse (line 19) | def parse(self, stream):
    method serialize (line 59) | def serialize(self, items):
  class GixyHelpFormatter (line 76) | class GixyHelpFormatter(HelpFormatter):
    method format_help (line 77) | def format_help(self):
  class ArgsParser (line 87) | class ArgsParser(ArgumentParser):
    method get_possible_config_keys (line 88) | def get_possible_config_keys(self, action):
    method get_items_for_config_file_output (line 102) | def get_items_for_config_file_output(self, source_to_settings,
  function create_parser (line 148) | def create_parser():

FILE: gixy/cli/main.py
  function _init_logger (line 17) | def _init_logger(debug=False):
  function _create_plugin_help (line 28) | def _create_plugin_help(option):
  function _get_cli_parser (line 37) | def _get_cli_parser():
  function main (line 95) | def main():

FILE: gixy/core/builtin_variables.py
  function is_builtin (line 241) | def is_builtin(name):
  function builtin_var (line 254) | def builtin_var(name):

FILE: gixy/core/config.py
  class Config (line 4) | class Config(object):
    method __init__ (line 5) | def __init__(self,
    method set_for (line 20) | def set_for(self, name, options):
    method get_for (line 23) | def get_for(self, name):
    method has_for (line 28) | def has_for(self, name):

FILE: gixy/core/context.py
  function get_context (line 11) | def get_context():
  function purge_context (line 15) | def purge_context():
  function push_context (line 19) | def push_context(block):
  function pop_context (line 29) | def pop_context():
  class Context (line 33) | class Context(object):
    method __init__ (line 34) | def __init__(self):
    method set_block (line 41) | def set_block(self, directive):
    method clear_index_vars (line 45) | def clear_index_vars(self):
    method add_var (line 49) | def add_var(self, name, var):
    method get_var (line 59) | def get_var(self, name):
    method __deepcopy__ (line 81) | def __deepcopy__(self, memo):

FILE: gixy/core/exceptions.py
  class InvalidConfiguration (line 1) | class InvalidConfiguration(Exception):

FILE: gixy/core/issue.py
  class Issue (line 1) | class Issue(object):
    method __init__ (line 2) | def __init__(self, plugin, summary=None, description=None,

FILE: gixy/core/manager.py
  class Manager (line 13) | class Manager(object):
    method __init__ (line 14) | def __init__(self, config=None):
    method audit (line 19) | def audit(self, file_path, file_data, is_stdin=False):
    method results (line 30) | def results(self):
    method stats (line 36) | def stats(self):
    method _audit_recursive (line 46) | def _audit_recursive(self, tree):
    method _update_variables (line 57) | def _update_variables(self, directive):
    method __enter__ (line 69) | def __enter__(self):
    method __exit__ (line 72) | def __exit__(self, exc_type, exc_val, exc_tb):

FILE: gixy/core/plugins_manager.py
  class PluginsManager (line 7) | class PluginsManager(object):
    method __init__ (line 8) | def __init__(self, config=None):
    method import_plugins (line 13) | def import_plugins(self):
    method init_plugins (line 25) | def init_plugins(self):
    method plugins (line 49) | def plugins(self):
    method plugins_classes (line 55) | def plugins_classes(self):
    method get_plugins_descriptions (line 59) | def get_plugins_descriptions(self):
    method audit (line 62) | def audit(self, directive):
    method issues (line 68) | def issues(self):

FILE: gixy/core/regexp.py
  function _build_reverse_list (line 13) | def _build_reverse_list(original):
  function extract_groups (line 51) | def extract_groups(parsed, top=True):
  function _gen_combinator (line 79) | def _gen_combinator(variants, _merge=True):
  function _merge_variants (line 104) | def _merge_variants(variants):
  class Token (line 114) | class Token(object):
    method __init__ (line 117) | def __init__(self, token, parent, regexp):
    method parse (line 124) | def parse(self):
    method _parse (line 127) | def _parse(self):
    method _parse_childs (line 130) | def _parse_childs(self, childs):
    method _get_group (line 133) | def _get_group(self, gid):
    method _reg_group (line 136) | def _reg_group(self, gid):
    method can_contain (line 139) | def can_contain(self, char, skip_literal=True):
    method can_startswith (line 142) | def can_startswith(self, char, strict=False):
    method must_contain (line 145) | def must_contain(self, char):
    method must_startswith (line 148) | def must_startswith(self, char, strict=False):
    method generate (line 151) | def generate(self, context):
    method __str__ (line 154) | def __str__(self):
  class AnyToken (line 158) | class AnyToken(Token):
    method can_contain (line 161) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 164) | def must_contain(self, char, skip_literal=True):
    method generate (line 168) | def generate(self, context):
    method __str__ (line 173) | def __str__(self):
  class LiteralToken (line 177) | class LiteralToken(Token):
    method _parse (line 180) | def _parse(self):
    method can_contain (line 183) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 188) | def must_contain(self, char, skip_literal=True):
    method generate (line 191) | def generate(self, context):
    method __str__ (line 194) | def __str__(self):
  class NotLiteralToken (line 198) | class NotLiteralToken(Token):
    method _parse (line 201) | def _parse(self):
    method can_contain (line 205) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 208) | def must_contain(self, char):
    method generate (line 212) | def generate(self, context):
    method __str__ (line 218) | def __str__(self):
  class RangeToken (line 222) | class RangeToken(Token):
    method _parse (line 225) | def _parse(self):
    method can_contain (line 231) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 234) | def must_contain(self, char, skip_literal=True):
    method generate (line 237) | def generate(self, context):
    method __str__ (line 243) | def __str__(self):
  class CategoryToken (line 247) | class CategoryToken(Token):
    method _parse (line 250) | def _parse(self):
    method can_contain (line 253) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 256) | def must_contain(self, char, skip_literal=True):
    method generate (line 259) | def generate(self, context):
    method __str__ (line 266) | def __str__(self):
  class MinRepeatToken (line 270) | class MinRepeatToken(Token):
    method _parse (line 273) | def _parse(self):
    method can_contain (line 278) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 287) | def must_contain(self, char):
    method can_startswith (line 299) | def can_startswith(self, char, strict=False):
    method must_startswith (line 307) | def must_startswith(self, char, strict=False):
    method generate (line 316) | def generate(self, context):
    method __str__ (line 334) | def __str__(self):
  class MaxRepeatToken (line 347) | class MaxRepeatToken(Token):
    method _parse (line 350) | def _parse(self):
    method can_contain (line 355) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 364) | def must_contain(self, char):
    method can_startswith (line 376) | def can_startswith(self, char, strict=False):
    method must_startswith (line 384) | def must_startswith(self, char, strict=False):
    method generate (line 393) | def generate(self, context):
    method __str__ (line 411) | def __str__(self):
  class BranchToken (line 424) | class BranchToken(Token):
    method _parse (line 427) | def _parse(self):
    method can_contain (line 437) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 443) | def must_contain(self, char):
    method can_startswith (line 446) | def can_startswith(self, char, strict=False):
    method must_startswith (line 449) | def must_startswith(self, char, strict=False):
    method generate (line 452) | def generate(self, context):
    method __str__ (line 463) | def __str__(self):
  class SubpatternToken (line 467) | class SubpatternToken(Token):
    method _parse (line 470) | def _parse(self):
    method can_contain (line 477) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 483) | def must_contain(self, char):
    method can_startswith (line 489) | def can_startswith(self, char, strict=False):
    method must_startswith (line 509) | def must_startswith(self, char, strict=False):
    method generate (line 529) | def generate(self, context):
    method __str__ (line 536) | def __str__(self):
  class InternalSubpatternToken (line 543) | class InternalSubpatternToken(Token):
    method _parse (line 546) | def _parse(self):
    method can_contain (line 550) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 556) | def must_contain(self, char):
    method can_startswith (line 562) | def can_startswith(self, char, strict=False):
    method must_startswith (line 582) | def must_startswith(self, char, strict=False):
    method generate (line 602) | def generate(self, context):
    method __str__ (line 609) | def __str__(self):
  class InToken (line 613) | class InToken(Token):
    method _parse (line 616) | def _parse(self):
    method can_contain (line 619) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 638) | def must_contain(self, char):
    method _generate_positive (line 642) | def _generate_positive(self, context):
    method _generate_negative (line 651) | def _generate_negative(self, context):
    method generate (line 670) | def generate(self, context):
    method __str__ (line 681) | def __str__(self):
  class AtToken (line 685) | class AtToken(Token):
    method _parse (line 688) | def _parse(self):
    method can_contain (line 692) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 695) | def must_contain(self, char):
    method generate (line 698) | def generate(self, context):
    method __str__ (line 706) | def __str__(self):
  class NegateToken (line 714) | class NegateToken(Token):
    method can_contain (line 717) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 720) | def must_contain(self, char):
    method can_startswith (line 723) | def can_startswith(self, char, strict=False):
    method must_startswith (line 726) | def must_startswith(self, char, strict=False):
    method generate (line 729) | def generate(self, context):
    method __str__ (line 732) | def __str__(self):
  class GroupRefToken (line 736) | class GroupRefToken(Token):
    method _parse (line 739) | def _parse(self):
    method can_contain (line 743) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 746) | def must_contain(self, char):
    method can_startswith (line 749) | def can_startswith(self, char, strict=False):
    method must_startswith (line 752) | def must_startswith(self, char, strict=False):
    method generate (line 755) | def generate(self, context):
    method __str__ (line 758) | def __str__(self):
  class AssertToken (line 762) | class AssertToken(Token):
    method can_contain (line 765) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 769) | def must_contain(self, char):
    method can_startswith (line 773) | def can_startswith(self, char, strict=False):
    method must_startswith (line 776) | def must_startswith(self, char, strict=False):
  class AssertNotToken (line 780) | class AssertNotToken(Token):
    method can_contain (line 783) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 787) | def must_contain(self, char):
    method can_startswith (line 791) | def can_startswith(self, char, strict=False):
    method must_startswith (line 794) | def must_startswith(self, char, strict=False):
  class EmptyToken (line 798) | class EmptyToken(Token):
    method can_contain (line 801) | def can_contain(self, char, skip_literal=True):
    method must_contain (line 804) | def must_contain(self, char):
    method can_startswith (line 808) | def can_startswith(self, char, strict=False):
    method must_startswith (line 811) | def must_startswith(self, char, strict=False):
    method generate (line 814) | def generate(self, context):
    method __str__ (line 817) | def __str__(self):
  function parse (line 821) | def parse(sre_obj, parent=None, regexp=None):
  class GenerationContext (line 862) | class GenerationContext(object):
    method __init__ (line 863) | def __init__(self, char, max_repeat=5, strict=False, anchored=True):
  class Regexp (line 870) | class Regexp(object):
    method __init__ (line 871) | def __init__(self, source, strict=False, case_sensitive=True, _root=No...
    method can_startswith (line 887) | def can_startswith(self, char):
    method can_contain (line 905) | def can_contain(self, char, skip_literal=True):
    method must_startswith (line 925) | def must_startswith(self, char):
    method must_contain (line 943) | def must_contain(self, char):
    method generate (line 958) | def generate(self, char, anchored=False, max_repeat=5):
    method group (line 980) | def group(self, name):
    method reg_group (line 992) | def reg_group(self, gid, token):
    method get_group (line 995) | def get_group(self, gid):
    method groups (line 999) | def groups(self):
    method root (line 1011) | def root(self):
    method parsed (line 1020) | def parsed(self):
    method __str__ (line 1033) | def __str__(self):

FILE: gixy/core/severity.py
  function is_acceptable (line 8) | def is_acceptable(current_severity, min_severity):

FILE: gixy/core/sre_parse/sre_constants.py
  class error (line 31) | class error(Exception):
  function makedict (line 155) | def makedict(list):

FILE: gixy/core/sre_parse/sre_parse.py
  class Pattern (line 66) | class Pattern:
    method __init__ (line 68) | def __init__(self):
    method opengroup (line 75) | def opengroup(self, name=None):
    method closegroup (line 87) | def closegroup(self, gid):
    method checkgroup (line 90) | def checkgroup(self, gid):
  class SubPattern (line 94) | class SubPattern:
    method __init__ (line 96) | def __init__(self, pattern, data=None):
    method __repr__ (line 103) | def __repr__(self):
    method __len__ (line 106) | def __len__(self):
    method __delitem__ (line 109) | def __delitem__(self, index):
    method __getitem__ (line 112) | def __getitem__(self, index):
    method __setitem__ (line 117) | def __setitem__(self, index, code):
    method insert (line 120) | def insert(self, index, code):
    method append (line 123) | def append(self, code):
    method getwidth (line 126) | def getwidth(self):
  class Tokenizer (line 164) | class Tokenizer:
    method __init__ (line 165) | def __init__(self, string):
    method __next (line 170) | def __next(self):
    method match (line 184) | def match(self, char, skip=1):
    method get (line 191) | def get(self):
    method tell (line 196) | def tell(self):
    method seek (line 199) | def seek(self, index):
  function isident (line 203) | def isident(char):
  function isdigit (line 207) | def isdigit(char):
  function isname (line 211) | def isname(name):
  function _class_escape (line 221) | def _class_escape(source, escape):
  function _escape (line 254) | def _escape(source, escape, state):
  function _parse_sub (line 304) | def _parse_sub(source, state, nested=1):
  function _parse_sub_cond (line 364) | def _parse_sub_cond(source, state, condgroup):
  function _parse (line 385) | def _parse(source, state):
  function parse (line 697) | def parse(str, flags=0, pattern=None):
  function parse_template (line 726) | def parse_template(source, pattern):
  function expand_template (line 817) | def expand_template(template, match):

FILE: gixy/core/utils.py
  function is_indexed_name (line 1) | def is_indexed_name(name):

FILE: gixy/core/variable.py
  function compile_script (line 12) | def compile_script(script):
  class Variable (line 37) | class Variable(object):
    method __init__ (line 38) | def __init__(self, name, value=None, boundary=None, provider=None, hav...
    method can_contain (line 60) | def can_contain(self, char):
    method can_startswith (line 83) | def can_startswith(self, char):
    method must_contain (line 106) | def must_contain(self, char):
    method must_startswith (line 129) | def must_startswith(self, char):
    method providers (line 153) | def providers(self):

FILE: gixy/directives/__init__.py
  function import_directives (line 7) | def import_directives():
  function get_all (line 15) | def get_all():

FILE: gixy/directives/block.py
  function get_overrides (line 8) | def get_overrides():
  class Block (line 21) | class Block(Directive):
    method __init__ (line 26) | def __init__(self, name, args):
    method some (line 30) | def some(self, name, flat=True):
    method find (line 40) | def find(self, name, flat=False):
    method find_recursive (line 49) | def find_recursive(self, name):
    method append (line 58) | def append(self, directive):
    method __str__ (line 62) | def __str__(self):
  class Root (line 66) | class Root(Block):
    method __init__ (line 69) | def __init__(self):
  class HttpBlock (line 73) | class HttpBlock(Block):
    method __init__ (line 76) | def __init__(self, name, args):
  class ServerBlock (line 80) | class ServerBlock(Block):
    method __init__ (line 83) | def __init__(self, name, args):
    method get_names (line 86) | def get_names(self):
    method __str__ (line 89) | def __str__(self):
  class LocationBlock (line 96) | class LocationBlock(Block):
    method __init__ (line 100) | def __init__(self, name, args):
    method is_internal (line 109) | def is_internal(self):
    method variables (line 113) | def variables(self):
  class IfBlock (line 124) | class IfBlock(Block):
    method __init__ (line 128) | def __init__(self, name, args):
    method __str__ (line 146) | def __str__(self):
  class IncludeBlock (line 150) | class IncludeBlock(Block):
    method __init__ (line 154) | def __init__(self, name, args):
    method __str__ (line 158) | def __str__(self):
  class MapBlock (line 162) | class MapBlock(Block):
    method __init__ (line 167) | def __init__(self, name, args):
    method variables (line 173) | def variables(self):
  class GeoBlock (line 178) | class GeoBlock(Block):
    method __init__ (line 183) | def __init__(self, name, args):
    method variables (line 195) | def variables(self):

FILE: gixy/directives/directive.py
  function get_overrides (line 5) | def get_overrides():
  class Directive (line 18) | class Directive(object):
    method __init__ (line 23) | def __init__(self, name, args, raw=None):
    method set_parent (line 29) | def set_parent(self, parent):
    method parents (line 33) | def parents(self):
    method variables (line 40) | def variables(self):
    method __str__ (line 43) | def __str__(self):
  class AddHeaderDirective (line 47) | class AddHeaderDirective(Directive):
    method __init__ (line 50) | def __init__(self, name, args):
  class SetDirective (line 59) | class SetDirective(Directive):
    method __init__ (line 63) | def __init__(self, name, args):
    method variables (line 69) | def variables(self):
  class AuthRequestSetDirective (line 73) | class AuthRequestSetDirective(Directive):
    method __init__ (line 77) | def __init__(self, name, args):
    method variables (line 83) | def variables(self):
  class PerlSetDirective (line 87) | class PerlSetDirective(Directive):
    method __init__ (line 91) | def __init__(self, name, args):
    method variables (line 97) | def variables(self):
  class SetByLuaDirective (line 101) | class SetByLuaDirective(Directive):
    method __init__ (line 105) | def __init__(self, name, args):
    method variables (line 111) | def variables(self):
  class RewriteDirective (line 115) | class RewriteDirective(Directive):
    method __init__ (line 120) | def __init__(self, name, args):
    method variables (line 129) | def variables(self):
  class RootDirective (line 137) | class RootDirective(Directive):
    method __init__ (line 141) | def __init__(self, name, args):
    method variables (line 146) | def variables(self):
  class AliasDirective (line 150) | class AliasDirective(Directive):
    method __init__ (line 153) | def __init__(self, name, args):

FILE: gixy/formatters/__init__.py
  function import_formatters (line 7) | def import_formatters():
  function get_all (line 15) | def get_all():

FILE: gixy/formatters/_jinja.py
  function load_template (line 7) | def load_template(name):
  function to_text_filter (line 13) | def to_text_filter(text):

FILE: gixy/formatters/base.py
  class BaseFormatter (line 7) | class BaseFormatter(object):
    method __init__ (line 10) | def __init__(self):
    method format_reports (line 14) | def format_reports(self, reports, stats):
    method feed (line 17) | def feed(self, path, manager):
    method flush (line 32) | def flush(self):
    method _prepare_result (line 35) | def _prepare_result(self, root, issues, severity, summary, description...
    method _resolve_config (line 63) | def _resolve_config(self, root, directives):
    method _traverse_tree (line 72) | def _traverse_tree(self, tree, points, level):

FILE: gixy/formatters/console.py
  class ConsoleFormatter (line 7) | class ConsoleFormatter(BaseFormatter):
    method __init__ (line 8) | def __init__(self):
    method format_reports (line 12) | def format_reports(self, reports, stats):

FILE: gixy/formatters/json.py
  class JsonFormatter (line 8) | class JsonFormatter(BaseFormatter):
    method format_reports (line 9) | def format_reports(self, reports, stats):

FILE: gixy/formatters/text.py
  class TextFormatter (line 7) | class TextFormatter(BaseFormatter):
    method __init__ (line 8) | def __init__(self):
    method format_reports (line 12) | def format_reports(self, reports, stats):

FILE: gixy/parser/nginx_parser.py
  class NginxParser (line 15) | class NginxParser(object):
    method __init__ (line 16) | def __init__(self, cwd='', allow_includes=True):
    method parse_file (line 25) | def parse_file(self, path, root=None):
    method parse (line 30) | def parse(self, content, root=None, path_info=None):
    method parse_block (line 54) | def parse_block(self, parsed_block, parent):
    method directive_factory (line 67) | def directive_factory(self, parsed_type, parsed_name, parsed_args):
    method _get_directive_class (line 83) | def _get_directive_class(self, parsed_type, parsed_name):
    method _init_directives (line 96) | def _init_directives(self):
    method _resolve_include (line 100) | def _resolve_include(self, args, parent):
    method _resolve_file_include (line 111) | def _resolve_file_include(self, pattern, parent):
    method _resolve_dump_include (line 125) | def _resolve_dump_include(self, pattern, parent):
    method _prepare_dump (line 138) | def _prepare_dump(self, parsed_block):

FILE: gixy/parser/raw_parser.py
  class NginxQuotedString (line 13) | class NginxQuotedString(QuotedString):
    method __init__ (line 14) | def __init__(self, quoteChar):
  class RawParser (line 23) | class RawParser(object):
    method parse (line 28) | def parse(self, data):
    method script (line 47) | def script(self):
  function _fix_comment (line 176) | def _fix_comment(string, location, tokens):

FILE: gixy/plugins/add_header_multiline.py
  class add_header_multiline (line 5) | class add_header_multiline(Plugin):
    method audit (line 20) | def audit(self, directive):
  function get_header_values (line 28) | def get_header_values(directive):

FILE: gixy/plugins/add_header_redefinition.py
  class add_header_redefinition (line 5) | class add_header_redefinition(Plugin):
    method __init__ (line 28) | def __init__(self, config):
    method audit (line 32) | def audit(self, directive):
    method _report_issue (line 53) | def _report_issue(self, current, parent, diff):
  function get_headers (line 63) | def get_headers(directive):

FILE: gixy/plugins/alias_traversal.py
  class alias_traversal (line 5) | class alias_traversal(Plugin):
    method audit (line 19) | def audit(self, directive):

FILE: gixy/plugins/host_spoofing.py
  class host_spoofing (line 5) | class host_spoofing(Plugin):
    method audit (line 16) | def audit(self, directive):

FILE: gixy/plugins/http_splitting.py
  class http_splitting (line 6) | class http_splitting(Plugin):
    method audit (line 26) | def audit(self, directive):
  function _get_value (line 44) | def _get_value(directive):

FILE: gixy/plugins/origins.py
  class origins (line 10) | class origins(Plugin):
    method __init__ (line 27) | def __init__(self, config):
    method audit (line 41) | def audit(self, directive):

FILE: gixy/plugins/plugin.py
  class Plugin (line 5) | class Plugin(object):
    method __init__ (line 13) | def __init__(self, config):
    method add_issue (line 17) | def add_issue(self, directive, summary=None, severity=None, descriptio...
    method audit (line 21) | def audit(self, directive):
    method issues (line 25) | def issues(self):
    method name (line 29) | def name(self):

FILE: gixy/plugins/ssrf.py
  class ssrf (line 9) | class ssrf(Plugin):
    method __init__ (line 30) | def __init__(self, config):
    method audit (line 34) | def audit(self, directive):
    method _check_script (line 52) | def _check_script(self, script, directive):

FILE: gixy/plugins/valid_referers.py
  class valid_referers (line 5) | class valid_referers(Plugin):
    method audit (line 16) | def audit(self, directive):

FILE: gixy/utils/text.py
  function to_bytes (line 5) | def to_bytes(obj, encoding='latin1', errors='strict', nonstring='replace'):
  function to_text (line 38) | def to_text(obj, encoding='latin1', errors='strict', nonstring='replace'):

FILE: tests/asserts.py
  function assert_is_instance (line 10) | def assert_is_instance(obj, cls, msg=None):
  function assert_is_none (line 18) | def assert_is_none(obj, msg=None):
  function assert_is_not_none (line 25) | def assert_is_not_none(obj, msg=None):
  function assert_in (line 32) | def assert_in(member, container, msg=None):

FILE: tests/core/test_context.py
  function setup (line 8) | def setup():
  function tear_down (line 12) | def tear_down():
  function test_push_pop_context (line 17) | def test_push_pop_context():
  function test_push_get_purge_context (line 34) | def test_push_get_purge_context():
  function test_add_variables (line 49) | def test_add_variables():
  function test_get_variables (line 72) | def test_get_variables():
  function test_context_depend_variables (line 95) | def test_context_depend_variables():
  function test_push_failed_with_regexp_py35_gixy_10 (line 128) | def test_push_failed_with_regexp_py35_gixy_10():

FILE: tests/core/test_regexp.py
  function test_positive_contains (line 16) | def test_positive_contains():
  function test_negative_contains (line 45) | def test_negative_contains():
  function test_groups_names (line 73) | def test_groups_names():
  function test_to_string (line 86) | def test_to_string():
  function test_positive_startswith (line 106) | def test_positive_startswith():
  function test_negative_startswith (line 128) | def test_negative_startswith():
  function test_positive_must_contain (line 149) | def test_positive_must_contain():
  function test_negative_must_contain (line 165) | def test_negative_must_contain():
  function test_positive_must_startswith (line 197) | def test_positive_must_startswith():
  function test_negative_must_startswith (line 212) | def test_negative_must_startswith():
  function test_generate (line 233) | def test_generate():
  function test_strict_generate (line 259) | def test_strict_generate():
  function test_gen_anchor (line 264) | def test_gen_anchor():
  function test_group_can_contains (line 283) | def test_group_can_contains():
  function check_positive_contain (line 302) | def check_positive_contain(regexp, char):
  function check_negative_contain (line 313) | def check_negative_contain(regexp, char):
  function check_positive_startswith (line 324) | def check_positive_startswith(regexp, char, strict):
  function check_negative_startswith (line 335) | def check_negative_startswith(regexp, char, strict):
  function check_groups_names (line 346) | def check_groups_names(regexp, groups):
  function check_to_string (line 351) | def check_to_string(regexp, string):
  function check_positive_must_contain (line 356) | def check_positive_must_contain(regexp, char):
  function check_negative_must_contain (line 367) | def check_negative_must_contain(regexp, char):
  function check_positive_must_startswith (line 378) | def check_positive_must_startswith(regexp, char, strict):
  function check_negative_must_startswith (line 389) | def check_negative_must_startswith(regexp, char, strict):
  function check_generate (line 400) | def check_generate(regexp, values):

FILE: tests/core/test_variable.py
  function setup (line 7) | def setup():
  function tear_down (line 11) | def tear_down():
  function test_literal (line 16) | def test_literal():
  function test_regexp (line 32) | def test_regexp():
  function test_script (line 48) | def test_script():
  function test_regexp_boundary (line 66) | def test_regexp_boundary():
  function test_script_boundary (line 84) | def test_script_boundary():

FILE: tests/directives/test_block.py
  function _get_parsed (line 9) | def _get_parsed(config):
  function test_block (line 14) | def test_block():
  function test_http (line 24) | def test_http():
  function test_server (line 40) | def test_server():
  function test_location (line 58) | def test_location():
  function test_location_internal (line 74) | def test_location_internal():
  function test_location_modifier (line 86) | def test_location_modifier():
  function test_if (line 98) | def test_if():
  function test_if_modifier (line 114) | def test_if_modifier():
  function test_if_variable (line 127) | def test_if_variable():
  function test_block_some_flat (line 140) | def test_block_some_flat():
  function test_block_some_not_flat (line 158) | def test_block_some_not_flat():
  function test_block_find_flat (line 174) | def test_block_find_flat():
  function test_block_find_not_flat (line 191) | def test_block_find_not_flat():
  function test_block_map (line 208) | def test_block_map():
  function test_block_geo_two_vars (line 224) | def test_block_geo_two_vars():
  function test_block_geo_one_var (line 240) | def test_block_geo_one_var():

FILE: tests/directives/test_directive.py
  function _get_parsed (line 7) | def _get_parsed(config):
  function test_directive (line 12) | def test_directive():
  function test_add_header (line 22) | def test_add_header():
  function test_add_header_always (line 35) | def test_add_header_always():
  function test_set (line 48) | def test_set():
  function test_rewrite (line 61) | def test_rewrite():
  function test_rewrite_flags (line 76) | def test_rewrite_flags():
  function test_root (line 91) | def test_root():

FILE: tests/parser/test_nginx_parser.py
  function _parse (line 8) | def _parse(config):
  function test_directive (line 12) | def test_directive():
  function test_blocks (line 36) | def test_blocks():
  function test_dump_simple (line 51) | def test_dump_simple():
  function test_encoding (line 102) | def test_encoding():
  function assert_config (line 111) | def assert_config(config, expected):

FILE: tests/parser/test_raw_parser.py
  function test_directive (line 5) | def test_directive():
  function test_block (line 32) | def test_block():
  function test_block_with_child (line 42) | def test_block_with_child():
  function test_location_simple (line 53) | def test_location_simple():
  function test_quoted_strings (line 81) | def test_quoted_strings():
  function test_location_child (line 93) | def test_location_child():
  function test_nested_location (line 106) | def test_nested_location():
  function test_hash_block (line 133) | def test_hash_block():
  function test_hash_block_in_location (line 159) | def test_hash_block_in_location():
  function test_named_location (line 183) | def test_named_location():
  function test_if (line 197) | def test_if():
  function test_hash_block_map (line 278) | def test_hash_block_map():
  function test_upstream (line 322) | def test_upstream():
  function test_issue_8 (line 360) | def test_issue_8():
  function test_issue_11 (line 378) | def test_issue_11():
  function test_lua_block (line 392) | def test_lua_block():
  function test_lua_block_brackets (line 428) | def test_lua_block_brackets():
  function test_file_delims (line 454) | def test_file_delims():
  function test_comments (line 479) | def test_comments():
  function test_upstream_dot (line 515) | def test_upstream_dot():
  function test_empty_config (line 531) | def test_empty_config():
  function test_utfbom_decoding (line 540) | def test_utfbom_decoding():
  function test_national_comment_decoding (line 552) | def test_national_comment_decoding():
  function assert_config (line 562) | def assert_config(config, expected):

FILE: tests/plugins/test_simply.py
  function setup_module (line 13) | def setup_module():
  function teardown_module (line 17) | def teardown_module():
  function test_from_config (line 21) | def test_from_config():
  function parse_plugin_options (line 63) | def parse_plugin_options(config_path):
  function yoda_provider (line 71) | def yoda_provider(plugin, plugin_options=None):
  function check_configuration (line 81) | def check_configuration(plugin, config_path, test_config):
  function check_configuration_fp (line 105) | def check_configuration_fp(plugin, config_path, test_config):

FILE: tests/utils.py
  class LogHandler (line 4) | class LogHandler(BufferingHandler):
    method __init__ (line 5) | def __init__(self, matcher):
    method shouldFlush (line 14) | def shouldFlush(self, **kwargs):
    method emit (line 17) | def emit(self, record):
    method matches (line 20) | def matches(self, **kwargs):
  class Matcher (line 32) | class Matcher(object):
    method matches (line 36) | def matches(self, d, **kwargs):
    method match_value (line 53) | def match_value(self, k, dv, v):
Condensed preview — 178 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (297K chars).
[
  {
    "path": ".dockerignore",
    "chars": 560,
    "preview": "# Byte-compiled / optimized / DLL files\n**/__pycache__/\n**/*.py[cod]\n\n# C extensions\n***/*.so\n\n# Distribution / packagin"
  },
  {
    "path": ".editorconfig",
    "chars": 219,
    "preview": "root = true\n\n[*]\nend_of_file = lf\ninsert_final_newline = true\n\n[*.{py,j2}]\ncharset = utf-8\n\n[*.py]\nindent_style = space\n"
  },
  {
    "path": ".gitignore",
    "chars": 741,
    "preview": "# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\n"
  },
  {
    "path": ".travis.yml",
    "chars": 360,
    "preview": "language: python\ndist: xenial\nsudo: false\npython:\n  - \"2.7\"\n  - \"3.5\"\n  - \"3.6\"\n  - \"3.7\"\n  - \"pypy\"\n  - \"pypy3\"\n\ninstal"
  },
  {
    "path": "AUTHORS",
    "chars": 158,
    "preview": "The following authors have created the source code of \"Gixy\"\npublished and distributed by YANDEX LLC as the owner:\n\nAndr"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 1906,
    "preview": "# Notice to external contributors\n\n\n\n## General info\n\nHello! In order for us (YANDEX LLC) to accept patches and other co"
  },
  {
    "path": "Dockerfile",
    "chars": 96,
    "preview": "FROM python:alpine\n\nADD . /src\n\nWORKDIR /src\n\nRUN python3 setup.py install\n\nENTRYPOINT [\"gixy\"]\n"
  },
  {
    "path": "LICENSE",
    "chars": 15923,
    "preview": "(C) YANDEX LLC, 2017\n\nMozilla Public License Version 2.0\n==================================\n\n1. Definitions\n------------"
  },
  {
    "path": "MANIFEST.in",
    "chars": 48,
    "preview": "include gixy/formatters/templates/*\ngraft tests\n"
  },
  {
    "path": "Makefile",
    "chars": 206,
    "preview": ".PHONY: all build publish\n\nall: build publish\n\nbuild:\n\tpython setup.py bdist_wheel --universal sdist\n\npublish:\n\ttwine up"
  },
  {
    "path": "README.RU.md",
    "chars": 6011,
    "preview": "GIXY\n====\n[![Mozilla Public License 2.0](https://img.shields.io/github/license/yandex/gixy.svg?style=flat-square)](https"
  },
  {
    "path": "README.md",
    "chars": 5065,
    "preview": "GIXY\n====\n[![Mozilla Public License 2.0](https://img.shields.io/github/license/yandex/gixy.svg?style=flat-square)](https"
  },
  {
    "path": "docs/en/plugins/addheadermultiline.md",
    "chars": 873,
    "preview": "# [add_header_multiline] Multiline response headers\n\nYou should avoid using multiline response headers, because:\n  * the"
  },
  {
    "path": "docs/en/plugins/addheaderredefinition.md",
    "chars": 2138,
    "preview": "# [add_header_redefinition] Redefining of response headers by  \"add_header\" directive\n\nUnfortunately, many people don't "
  },
  {
    "path": "docs/en/plugins/aliastraversal.md",
    "chars": 999,
    "preview": "# [alias_traversal] Path traversal via misconfigured alias\n\nThe [alias](https://nginx.ru/en/docs/http/ngx_http_core_modu"
  },
  {
    "path": "docs/en/plugins/hostspoofing.md",
    "chars": 1291,
    "preview": "# [host_spoofing] Request's Host header forgery\n\nOften, an application located behind Nginx needs a correct `Host` heade"
  },
  {
    "path": "docs/en/plugins/httpsplitting.md",
    "chars": 2188,
    "preview": "# [http_splitting] HTTP Splitting\n\nHTTP Splitting - attack that use improper input validation. It usually targets web ap"
  },
  {
    "path": "docs/en/plugins/origins.md",
    "chars": 1358,
    "preview": "# [origins] Problems with referrer/origin validation\n\nIt's not unusual to use regex for `Referer` or `Origin` headers va"
  },
  {
    "path": "docs/en/plugins/ssrf.md",
    "chars": 2601,
    "preview": "# [ssrf] Server Side Request Forgery\n\nServer Side Request Forgery - attack that forces a server to perform arbitrary req"
  },
  {
    "path": "docs/en/plugins/validreferers.md",
    "chars": 1176,
    "preview": "# [valid_referers] none in valid_referers\nModule [ngx_http_referer_module](http://nginx.org/en/docs/http/ngx_http_refere"
  },
  {
    "path": "docs/ru/plugins/addheadermultiline.md",
    "chars": 919,
    "preview": "# [add_header_multiline] Многострочные заголовоки ответа\n\nМногострочных заголовков ответа стоит избегать по нескольким п"
  },
  {
    "path": "docs/ru/plugins/addheaderredefinition.md",
    "chars": 2187,
    "preview": "# [add_header_redefinition] Переопределение \"вышестоящих\" заголовков ответа директивой \"add_header\"\n\nК сожалению, многие"
  },
  {
    "path": "docs/ru/plugins/aliastraversal.md",
    "chars": 830,
    "preview": "# [alias_traversal] Path traversal при использовании alias\n\nДиректива [alias](https://nginx.ru/ru/docs/http/ngx_http_cor"
  },
  {
    "path": "docs/ru/plugins/hostspoofing.md",
    "chars": 1514,
    "preview": "# [host_spoofing] Подделка заголовка запроса Host\n\nЗачастую, приложению, стоящему за Nginx, необходимо передать корректн"
  },
  {
    "path": "docs/ru/plugins/httpsplitting.md",
    "chars": 2421,
    "preview": "# [http_splitting] HTTP Splitting\n\nHTTP Splitting - уязвимость, возникающая из-за неправильной обработки входных данных."
  },
  {
    "path": "docs/ru/plugins/origins.md",
    "chars": 1713,
    "preview": "# [origins] Проблемы валидации referrer/origin\n\nНередко валидация заголовка запроса `Referer` или `Origin` делается при "
  },
  {
    "path": "docs/ru/plugins/ssrf.md",
    "chars": 2875,
    "preview": "# [ssrf] Server Side Request Forgery\n\nServer Side Request Forgery - уязвимость, позволяющая выполнять различного рода за"
  },
  {
    "path": "docs/ru/plugins/validreferers.md",
    "chars": 1329,
    "preview": "# [valid_referers] none in valid_referers\n\nМодуль [ngx_http_referer_module](http://nginx.org/ru/docs/http/ngx_http_refer"
  },
  {
    "path": "gixy/__init__.py",
    "chars": 67,
    "preview": "# flake8: noqa\n\nfrom gixy.core import severity\n\nversion = '0.1.21'\n"
  },
  {
    "path": "gixy/cli/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "gixy/cli/argparser.py",
    "chars": 6849,
    "preview": "# flake8: noqa\n\nfrom configargparse import *\nfrom six.moves import StringIO\n\nfrom gixy.core.plugins_manager import Plugi"
  },
  {
    "path": "gixy/cli/main.py",
    "chars": 6241,
    "preview": "import os\nimport sys\nimport logging\nimport copy\n\nimport gixy\nfrom gixy.core.manager import Manager as Gixy\nfrom gixy.for"
  },
  {
    "path": "gixy/core/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "gixy/core/builtin_variables.py",
    "chars": 14093,
    "preview": "from gixy.core.regexp import Regexp\nfrom gixy.core.variable import Variable\n\nBUILTIN_VARIABLES = {\n    # http://nginx.or"
  },
  {
    "path": "gixy/core/config.py",
    "chars": 818,
    "preview": "import gixy\n\n\nclass Config(object):\n    def __init__(self,\n                 plugins=None,\n                 skips=None,\n "
  },
  {
    "path": "gixy/core/context.py",
    "chars": 2038,
    "preview": "import logging\nimport copy\n\nfrom gixy.core.utils import is_indexed_name\n\nLOG = logging.getLogger(__name__)\n\nCONTEXTS = ["
  },
  {
    "path": "gixy/core/exceptions.py",
    "chars": 48,
    "preview": "class InvalidConfiguration(Exception):\n    pass\n"
  },
  {
    "path": "gixy/core/issue.py",
    "chars": 564,
    "preview": "class Issue(object):\n    def __init__(self, plugin, summary=None, description=None,\n                 severity=None, reas"
  },
  {
    "path": "gixy/core/manager.py",
    "chars": 2416,
    "preview": "import os\nimport logging\n\nimport gixy\nfrom gixy.core.plugins_manager import PluginsManager\nfrom gixy.core.context import"
  },
  {
    "path": "gixy/core/plugins_manager.py",
    "chars": 2361,
    "preview": "import os\n\nimport gixy\nfrom gixy.plugins.plugin import Plugin\n\n\nclass PluginsManager(object):\n    def __init__(self, con"
  },
  {
    "path": "gixy/core/regexp.py",
    "chars": 32674,
    "preview": "import six\nimport logging\nimport re\nimport random\nimport itertools\nfrom cached_property import cached_property\n\nimport g"
  },
  {
    "path": "gixy/core/severity.py",
    "chars": 230,
    "preview": "UNSPECIFIED = 'UNSPECIFIED'\nLOW = 'LOW'\nMEDIUM = 'MEDIUM'\nHIGH = 'HIGH'\nALL = [UNSPECIFIED, LOW, MEDIUM, HIGH]\n\n\ndef is_"
  },
  {
    "path": "gixy/core/sre_parse/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "gixy/core/sre_parse/sre_constants.py",
    "chars": 5773,
    "preview": "# flake8: noqa\n\n#\n# Secret Labs' Regular Expression Engine\n#\n# various symbols used by the regular expression engine.\n# "
  },
  {
    "path": "gixy/core/sre_parse/sre_parse.py",
    "chars": 27718,
    "preview": "# flake8: noqa\n\n#\n# Secret Labs' Regular Expression Engine\n#\n# convert re-style regular expression to sre pattern\n#\n# Co"
  },
  {
    "path": "gixy/core/utils.py",
    "chars": 103,
    "preview": "def is_indexed_name(name):\n    return isinstance(name, int) or (len(name) == 1 and '1' <= name <= '9')\n"
  },
  {
    "path": "gixy/core/variable.py",
    "chars": 5019,
    "preview": "import re\nimport logging\n\nfrom gixy.core.regexp import Regexp\nfrom gixy.core.context import get_context\n\nLOG = logging.g"
  },
  {
    "path": "gixy/directives/__init__.py",
    "chars": 651,
    "preview": "import os\nfrom gixy.directives.directive import Directive\n\nDIRECTIVES = {}\n\n\ndef import_directives():\n    files_list = o"
  },
  {
    "path": "gixy/directives/block.py",
    "chars": 5462,
    "preview": "from cached_property import cached_property\n\nfrom gixy.directives.directive import Directive\nfrom gixy.core.variable imp"
  },
  {
    "path": "gixy/directives/directive.py",
    "chars": 4043,
    "preview": "from gixy.core.variable import Variable\nfrom gixy.core.regexp import Regexp\n\n\ndef get_overrides():\n    result = {}\n    f"
  },
  {
    "path": "gixy/formatters/__init__.py",
    "chars": 631,
    "preview": "import os\nfrom gixy.formatters.base import BaseFormatter\n\nFORMATTERS = {}\n\n\ndef import_formatters():\n    files_list = os"
  },
  {
    "path": "gixy/formatters/_jinja.py",
    "chars": 491,
    "preview": "from __future__ import absolute_import\nfrom jinja2 import Environment, PackageLoader\n\nfrom gixy.utils.text import to_tex"
  },
  {
    "path": "gixy/formatters/base.py",
    "chars": 3446,
    "preview": "from __future__ import absolute_import\n\nimport gixy\nfrom gixy.directives import block\n\n\nclass BaseFormatter(object):\n   "
  },
  {
    "path": "gixy/formatters/console.py",
    "chars": 415,
    "preview": "from __future__ import absolute_import\n\nfrom gixy.formatters.base import BaseFormatter\nfrom gixy.formatters._jinja impor"
  },
  {
    "path": "gixy/formatters/json.py",
    "chars": 781,
    "preview": "from __future__ import absolute_import\n\nimport json\n\nfrom gixy.formatters.base import BaseFormatter\n\n\nclass JsonFormatte"
  },
  {
    "path": "gixy/formatters/templates/console.j2",
    "chars": 1245,
    "preview": "{% set colors = {'DEF': '\\033[0m', 'TITLE': '\\033[95m', 'UNSPECIFIED': '\\033[0m', 'LOW': '\\033[94m', 'MEDIUM': '\\033[93m"
  },
  {
    "path": "gixy/formatters/templates/text.j2",
    "chars": 1018,
    "preview": "\n==================== Results ===================\n{% for path, issues in reports.items() %}\n{% if reports|length > 1 %}\n"
  },
  {
    "path": "gixy/formatters/text.py",
    "chars": 406,
    "preview": "from __future__ import absolute_import\n\nfrom gixy.formatters.base import BaseFormatter\nfrom gixy.formatters._jinja impor"
  },
  {
    "path": "gixy/parser/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "gixy/parser/nginx_parser.py",
    "chars": 5493,
    "preview": "import os\nimport glob\nimport logging\nimport fnmatch\n\nfrom pyparsing import ParseException\nfrom gixy.core.exceptions impo"
  },
  {
    "path": "gixy/parser/raw_parser.py",
    "chars": 5861,
    "preview": "import logging\nimport codecs\nimport six\nfrom cached_property import cached_property\n\nfrom pyparsing import (\n    Literal"
  },
  {
    "path": "gixy/plugins/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "gixy/plugins/add_header_multiline.py",
    "chars": 1586,
    "preview": "import gixy\nfrom gixy.plugins.plugin import Plugin\n\n\nclass add_header_multiline(Plugin):\n    \"\"\"\n    Insecure example:\na"
  },
  {
    "path": "gixy/plugins/add_header_redefinition.py",
    "chars": 2321,
    "preview": "import gixy\nfrom gixy.plugins.plugin import Plugin\n\n\nclass add_header_redefinition(Plugin):\n    \"\"\"\n    Insecure example"
  },
  {
    "path": "gixy/plugins/alias_traversal.py",
    "chars": 1126,
    "preview": "import gixy\nfrom gixy.plugins.plugin import Plugin\n\n\nclass alias_traversal(Plugin):\n    \"\"\"\n    Insecure example:\n      "
  },
  {
    "path": "gixy/plugins/host_spoofing.py",
    "chars": 739,
    "preview": "import gixy\nfrom gixy.plugins.plugin import Plugin\n\n\nclass host_spoofing(Plugin):\n    \"\"\"\n    Insecure example:\n        "
  },
  {
    "path": "gixy/plugins/http_splitting.py",
    "chars": 1660,
    "preview": "import gixy\nfrom gixy.plugins.plugin import Plugin\nfrom gixy.core.variable import compile_script\n\n\nclass http_splitting("
  },
  {
    "path": "gixy/plugins/origins.py",
    "chars": 2736,
    "preview": "import re\nimport logging\nimport gixy\nfrom gixy.plugins.plugin import Plugin\nfrom gixy.core.regexp import Regexp\n\nLOG = l"
  },
  {
    "path": "gixy/plugins/plugin.py",
    "chars": 781,
    "preview": "import gixy\nfrom gixy.core.issue import Issue\n\n\nclass Plugin(object):\n    summary = ''\n    description = ''\n    help_url"
  },
  {
    "path": "gixy/plugins/ssrf.py",
    "chars": 2025,
    "preview": "import re\n\nimport gixy\nfrom gixy.plugins.plugin import Plugin\nfrom gixy.core.context import get_context\nfrom gixy.core.v"
  },
  {
    "path": "gixy/plugins/valid_referers.py",
    "chars": 556,
    "preview": "import gixy\nfrom gixy.plugins.plugin import Plugin\n\n\nclass valid_referers(Plugin):\n    \"\"\"\n    Insecure example:\n       "
  },
  {
    "path": "gixy/utils/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "gixy/utils/text.py",
    "chars": 2034,
    "preview": "from __future__ import absolute_import\nfrom six import PY3, text_type, binary_type\n\n\ndef to_bytes(obj, encoding='latin1'"
  },
  {
    "path": "requirements.dev.txt",
    "chars": 60,
    "preview": "nose>=1.3.7\nmock>=2.0.0\ncoverage>=4.3\nflake8>=3.2\ntox>=2.7.0"
  },
  {
    "path": "requirements.txt",
    "chars": 104,
    "preview": "pyparsing>=1.5.5,<3\ncached-property>=1.2.0\nargparse>=1.4.0\nsix>=1.1.0\nJinja2>=2.8\nConfigArgParse>=0.11.0"
  },
  {
    "path": "rpm/gixy.spec",
    "chars": 2366,
    "preview": "########################################################################################\n\nSummary:        Nginx configur"
  },
  {
    "path": "rpm/python-argparse.spec",
    "chars": 2643,
    "preview": "########################################################################################\n\n%{!?python_sitelib: %global py"
  },
  {
    "path": "setup.py",
    "chars": 1320,
    "preview": "import re\nfrom setuptools import setup, find_packages\n\nwith open('gixy/__init__.py', 'r') as fd:\n    version = re.search"
  },
  {
    "path": "tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/asserts.py",
    "chars": 1178,
    "preview": "from nose.tools import assert_true, assert_false\n\n\n'''\nVarious nose.tools helpers that doesn't exists in Python 2.6 Unit"
  },
  {
    "path": "tests/core/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/core/test_context.py",
    "chars": 4629,
    "preview": "from nose.tools import with_setup, assert_equals, assert_not_equals, assert_true\nfrom gixy.core.context import get_conte"
  },
  {
    "path": "tests/core/test_regexp.py",
    "chars": 12424,
    "preview": "from nose.tools import assert_true, assert_false, assert_equals\nfrom gixy.core.regexp import Regexp\n\n'''\nCATEGORIES:\n   "
  },
  {
    "path": "tests/core/test_variable.py",
    "chars": 3274,
    "preview": "from nose.tools import assert_true, assert_false, assert_equals, with_setup\nfrom gixy.core.context import get_context, p"
  },
  {
    "path": "tests/directives/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/directives/test_block.py",
    "chars": 5871,
    "preview": "from nose.tools import assert_equals, assert_true, assert_false\nfrom tests.asserts import assert_is_instance, assert_is_"
  },
  {
    "path": "tests/directives/test_directive.py",
    "chars": 3362,
    "preview": "from nose.tools import assert_equals, assert_false, assert_true\nfrom tests.asserts import assert_is_instance\nfrom gixy.p"
  },
  {
    "path": "tests/parser/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/parser/test_nginx_parser.py",
    "chars": 3120,
    "preview": "from nose.tools import assert_equal\nfrom tests.asserts import assert_is_instance\nfrom gixy.parser.nginx_parser import Ng"
  },
  {
    "path": "tests/parser/test_raw_parser.py",
    "chars": 12480,
    "preview": "from nose.tools import assert_equals\nfrom gixy.parser.raw_parser import *\n\n\ndef test_directive():\n    config = '''\nacces"
  },
  {
    "path": "tests/plugins/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tests/plugins/simply/add_header_multiline/add_header.conf",
    "chars": 104,
    "preview": "add_header Content-Security-Policy \"\n    default-src: 'none';\n    font-src data: https://yastatic.net;\";"
  },
  {
    "path": "tests/plugins/simply/add_header_multiline/add_header_fp.conf",
    "chars": 21,
    "preview": "add_header X-Foo foo;"
  },
  {
    "path": "tests/plugins/simply/add_header_multiline/config.json",
    "chars": 23,
    "preview": "{\n  \"severity\": \"LOW\"\n}"
  },
  {
    "path": "tests/plugins/simply/add_header_multiline/more_set_headers.conf",
    "chars": 78,
    "preview": "more_set_headers -t 'text/html text/plain'\n    'X-Foo: Bar\n        multiline';"
  },
  {
    "path": "tests/plugins/simply/add_header_multiline/more_set_headers_fp.conf",
    "chars": 70,
    "preview": "more_set_headers -t 'text/html text/plain'\n    'X-Foo: Bar multiline';"
  },
  {
    "path": "tests/plugins/simply/add_header_multiline/more_set_headers_multiple.conf",
    "chars": 151,
    "preview": "more_set_headers -t 'text/html text/plain'\n    'X-Foo: some\n        multiline'\n    'X-Bar: some\n        multiline'\n    '"
  },
  {
    "path": "tests/plugins/simply/add_header_multiline/more_set_headers_replace.conf",
    "chars": 41,
    "preview": "more_set_headers -r 'Foo:\n    multiline';"
  },
  {
    "path": "tests/plugins/simply/add_header_multiline/more_set_headers_replace_fp.conf",
    "chars": 37,
    "preview": "more_set_headers -r 'Foo: multiline';"
  },
  {
    "path": "tests/plugins/simply/add_header_multiline/more_set_headers_status_fp.conf",
    "chars": 48,
    "preview": "more_set_headers -s 404 -s '500 503' 'Foo: bar';"
  },
  {
    "path": "tests/plugins/simply/add_header_multiline/more_set_headers_type_fp.conf",
    "chars": 61,
    "preview": "more_set_headers -t 'text/html\n    text/plain' 'X-Foo: some';"
  },
  {
    "path": "tests/plugins/simply/add_header_redefinition/config.json",
    "chars": 26,
    "preview": "{\n  \"severity\": \"MEDIUM\"\n}"
  },
  {
    "path": "tests/plugins/simply/add_header_redefinition/duplicate_fp.conf",
    "chars": 175,
    "preview": "http {\nadd_header X-Frame-Options \"DENY\" always;\n  server {\n    location /new-headers {\n      add_header X-Frame-Options"
  },
  {
    "path": "tests/plugins/simply/add_header_redefinition/if_replaces.conf",
    "chars": 77,
    "preview": "add_header X-Frame-Options \"DENY\" always;\n\nif (1) {\n  add_header X-Foo foo;\n}"
  },
  {
    "path": "tests/plugins/simply/add_header_redefinition/location_replaces.conf",
    "chars": 92,
    "preview": "add_header X-Frame-Options \"DENY\" always;\n\nlocation /new-headers {\n  add_header X-Foo foo;\n}"
  },
  {
    "path": "tests/plugins/simply/add_header_redefinition/nested_block.conf",
    "chars": 207,
    "preview": "server {\n  add_header X-Frame-Options \"DENY\" always;\n  location / {\n    location /some {\n      add_header X-Frame-Option"
  },
  {
    "path": "tests/plugins/simply/add_header_redefinition/non_block_fp.conf",
    "chars": 79,
    "preview": "add_header X-Frame-Options \"DENY\" always;\nserver \"some\";\nadd_header X-Foo foo;\n"
  },
  {
    "path": "tests/plugins/simply/add_header_redefinition/not_secure_both_fp.conf",
    "chars": 72,
    "preview": "add_header X-Bar bar;\n\nlocation /new-headers {\n  add_header X-Foo foo;\n}"
  },
  {
    "path": "tests/plugins/simply/add_header_redefinition/not_secure_outer_fp.conf",
    "chars": 92,
    "preview": "add_header X-Bar bar;\n\nlocation /new-headers {\n  add_header X-Frame-Options \"DENY\" always;\n}"
  },
  {
    "path": "tests/plugins/simply/add_header_redefinition/step_replaces.conf",
    "chars": 127,
    "preview": "http {\nadd_header X-Frame-Options \"DENY\" always;\n  server {\n    location /new-headers {\n      add_header X-Foo foo;\n    "
  },
  {
    "path": "tests/plugins/simply/alias_traversal/config.json",
    "chars": 37,
    "preview": "{\n  \"severity\": [\"MEDIUM\", \"HIGH\"]\n}\n"
  },
  {
    "path": "tests/plugins/simply/alias_traversal/nested.conf",
    "chars": 78,
    "preview": "location /files/ {\n    location /files/images {\n        alias /home/;\n    }\n}\n"
  },
  {
    "path": "tests/plugins/simply/alias_traversal/nested_fp.conf",
    "chars": 74,
    "preview": "location /files/ {\n    location /files/images {\n    }\n    alias /home/;\n}\n"
  },
  {
    "path": "tests/plugins/simply/alias_traversal/not_slashed_alias.conf",
    "chars": 37,
    "preview": "location /files {\n    alias /home;\n}\n"
  },
  {
    "path": "tests/plugins/simply/alias_traversal/not_slashed_alias_fp.conf",
    "chars": 38,
    "preview": "location /files/ {\n    alias /home;\n}\n"
  },
  {
    "path": "tests/plugins/simply/alias_traversal/simple.conf",
    "chars": 38,
    "preview": "location /files {\n    alias /home/;\n}\n"
  },
  {
    "path": "tests/plugins/simply/alias_traversal/simple_fp.conf",
    "chars": 39,
    "preview": "location /files/ {\n    alias /home/;\n}\n"
  },
  {
    "path": "tests/plugins/simply/alias_traversal/slashed_alias.conf",
    "chars": 38,
    "preview": "location /files {\n    alias /home/;\n}\n"
  },
  {
    "path": "tests/plugins/simply/alias_traversal/slashed_alias_fp.conf",
    "chars": 39,
    "preview": "location /files/ {\n    alias /home/;\n}\n"
  },
  {
    "path": "tests/plugins/simply/host_spoofing/config.json",
    "chars": 26,
    "preview": "{\n  \"severity\": \"MEDIUM\"\n}"
  },
  {
    "path": "tests/plugins/simply/host_spoofing/http_fp.conf",
    "chars": 28,
    "preview": "proxy_set_header Host $host;"
  },
  {
    "path": "tests/plugins/simply/host_spoofing/http_host.conf",
    "chars": 33,
    "preview": "proxy_set_header Host $http_host;"
  },
  {
    "path": "tests/plugins/simply/host_spoofing/http_host_diff_case.conf",
    "chars": 33,
    "preview": "proxy_set_header HoSt $http_host;"
  },
  {
    "path": "tests/plugins/simply/host_spoofing/some_arg.conf",
    "chars": 32,
    "preview": "proxy_set_header host $arg_host;"
  },
  {
    "path": "tests/plugins/simply/http_splitting/add_header_uri.conf",
    "chars": 22,
    "preview": "add_header X-Uri $uri;"
  },
  {
    "path": "tests/plugins/simply/http_splitting/config.json",
    "chars": 24,
    "preview": "{\n  \"severity\": \"HIGH\"\n}"
  },
  {
    "path": "tests/plugins/simply/http_splitting/dont_report_not_resolved_var_fp.conf",
    "chars": 71,
    "preview": "location ~ /proxy/(a|b)/(\\W*)$ {\n    proxy_pass http://storage/$some;\n}"
  },
  {
    "path": "tests/plugins/simply/http_splitting/proxy_from_location_var.conf",
    "chars": 68,
    "preview": "location ~ /proxy/(a|b)/(\\W*)$ {\n    proxy_pass http://storage/$2;\n}"
  },
  {
    "path": "tests/plugins/simply/http_splitting/proxy_from_location_var_var.conf",
    "chars": 83,
    "preview": "location ~ /proxy/(a|b)/(\\W*)$ {\n    set $p $2;\n    proxy_pass http://storage/$p;\n}"
  },
  {
    "path": "tests/plugins/simply/http_splitting/proxy_from_location_var_var_fp.conf",
    "chars": 83,
    "preview": "location ~ /proxy/(a|b)/(\\W*)$ {\n    set $p $1;\n    proxy_pass http://storage/$p;\n}"
  },
  {
    "path": "tests/plugins/simply/http_splitting/proxy_from_location_var_var_var.conf",
    "chars": 99,
    "preview": "location ~ /proxy/(a|b)/(?<p>\\W*)$ {\n    set $upstream \"http://$1/$p?\";\n    proxy_pass $upstream;\n}"
  },
  {
    "path": "tests/plugins/simply/http_splitting/proxy_pass_cr_fp.conf",
    "chars": 66,
    "preview": "location ~* ^/test/(.*) {\n    proxy_pass http://10.10.10.10/$1;\n}\n"
  },
  {
    "path": "tests/plugins/simply/http_splitting/proxy_pass_ducument_uri.conf",
    "chars": 40,
    "preview": "proxy_pass http://upstream$document_uri;"
  },
  {
    "path": "tests/plugins/simply/http_splitting/proxy_pass_lf.conf",
    "chars": 70,
    "preview": "location ~* ^/test/([^/]+)/ {\n    proxy_pass http://10.10.10.10/$1;\n}\n"
  },
  {
    "path": "tests/plugins/simply/http_splitting/proxy_set_header_ducument_uri.conf",
    "chars": 48,
    "preview": "proxy_set_header \"X-Original-Uri\" $document_uri;"
  },
  {
    "path": "tests/plugins/simply/http_splitting/return_403_fp.conf",
    "chars": 11,
    "preview": "return 403;"
  },
  {
    "path": "tests/plugins/simply/http_splitting/return_request_uri_fp.conf",
    "chars": 36,
    "preview": "return 301 https://some$request_uri;"
  },
  {
    "path": "tests/plugins/simply/http_splitting/rewrite_extract_fp.conf",
    "chars": 66,
    "preview": "rewrite ^/proxy/(a|b)/(?<path>\\W*)$ http://storage/$path redirect;"
  },
  {
    "path": "tests/plugins/simply/http_splitting/rewrite_uri.conf",
    "chars": 26,
    "preview": "rewrite ^ http://some$uri;"
  },
  {
    "path": "tests/plugins/simply/http_splitting/rewrite_uri_after_var.conf",
    "chars": 29,
    "preview": "return 301 https://$host$uri;"
  },
  {
    "path": "tests/plugins/simply/origins/config.json",
    "chars": 36,
    "preview": "{\n  \"severity\": [\"MEDIUM\", \"HIGH\"]\n}"
  },
  {
    "path": "tests/plugins/simply/origins/metrika.conf",
    "chars": 154,
    "preview": "if ($http_referer !~ \"^https?://([^/]+metrika.*yandex\\.(ru|ua|com|com\\.tr|by|kz)|([^/]+\\.)?webvisor\\.com)/\"){\n    add_he"
  },
  {
    "path": "tests/plugins/simply/origins/origin.conf",
    "chars": 51,
    "preview": "if ($http_origin !~ '^https?:\\/\\/yandex.ru\\/') {\n\n}"
  },
  {
    "path": "tests/plugins/simply/origins/origin_fp.conf",
    "chars": 52,
    "preview": "if ($http_origin !~ '^https?:\\/\\/yandex\\.ru\\/') {\n\n}"
  },
  {
    "path": "tests/plugins/simply/origins/origin_https.conf",
    "chars": 111,
    "preview": "# Options: {\"domains\": [\"yandex.ru\"], \"https_only\": true}\n\nif ($http_origin !~ '^https?:\\/\\/yandex\\.ru\\/') {\n\n}"
  },
  {
    "path": "tests/plugins/simply/origins/origin_https_fp.conf",
    "chars": 110,
    "preview": "# Options: {\"domains\": [\"yandex.ru\"], \"https_only\": true}\n\nif ($http_origin !~ '^https:\\/\\/yandex\\.ru\\/') {\n\n}"
  },
  {
    "path": "tests/plugins/simply/origins/origin_w_slash_anchored_fp.conf",
    "chars": 52,
    "preview": "if ($http_origin !~ '^https?:\\/\\/yandex\\.ru/$') {\n\n}"
  },
  {
    "path": "tests/plugins/simply/origins/origin_w_slash_fp.conf",
    "chars": 51,
    "preview": "if ($http_origin !~ '^https?:\\/\\/yandex\\.ru/') {\n\n}"
  },
  {
    "path": "tests/plugins/simply/origins/origin_wo_slash.conf",
    "chars": 98,
    "preview": "# Options: {\"domains\": [\"yandex.ru\"]}\n\nhttp {\nif ($http_origin !~ '^https?:\\/\\/yandex\\.ru') {\n\n}\n}"
  },
  {
    "path": "tests/plugins/simply/origins/referer.conf",
    "chars": 52,
    "preview": "if ($http_referer !~ '^https?:\\/\\/yandex.ru\\/') {\n\n}"
  },
  {
    "path": "tests/plugins/simply/origins/referer_fp.conf",
    "chars": 53,
    "preview": "if ($http_referer !~ '^https?:\\/\\/yandex\\.ru\\/') {\n\n}"
  },
  {
    "path": "tests/plugins/simply/origins/referer_subdomain.conf",
    "chars": 58,
    "preview": "if ($http_referer !~ '^https?:\\/\\/some.yandex\\.ru\\/') {\n\n}"
  },
  {
    "path": "tests/plugins/simply/origins/referer_subdomain_fp.conf",
    "chars": 59,
    "preview": "if ($http_referer !~ '^https?:\\/\\/some\\.yandex\\.ru\\/') {\n\n}"
  },
  {
    "path": "tests/plugins/simply/origins/structure_dot.conf",
    "chars": 91,
    "preview": "if ($http_referer !~ \"^https://example.com/\"){\n    add_header X-Frame-Options SAMEORIGIN;\n}"
  },
  {
    "path": "tests/plugins/simply/origins/structure_fp.conf",
    "chars": 92,
    "preview": "if ($http_referer !~ \"^https://example\\.com/\"){\n    add_header X-Frame-Options SAMEORIGIN;\n}"
  },
  {
    "path": "tests/plugins/simply/origins/structure_prefix.conf",
    "chars": 91,
    "preview": "if ($http_referer !~ \"https://example\\.com/\"){\n    add_header X-Frame-Options SAMEORIGIN;\n}"
  },
  {
    "path": "tests/plugins/simply/origins/structure_suffix.conf",
    "chars": 91,
    "preview": "if ($http_referer !~ \"^https://example\\.com\"){\n    add_header X-Frame-Options SAMEORIGIN;\n}"
  },
  {
    "path": "tests/plugins/simply/origins/webvisor.conf",
    "chars": 183,
    "preview": "# Options: {\"domains\": [\"webvisor.com\", \"yandex.com\"]}\n\nif ($http_referer !~ \"^https?://([^/]+\\.)?yandex\\.com/|([^/]+\\.)"
  },
  {
    "path": "tests/plugins/simply/ssrf/config.json",
    "chars": 24,
    "preview": "{\n  \"severity\": \"HIGH\"\n}"
  },
  {
    "path": "tests/plugins/simply/ssrf/have_internal_fp.conf",
    "chars": 60,
    "preview": "location /proxy/ {\n    internal;\n    proxy_pass $arg_some;\n}"
  },
  {
    "path": "tests/plugins/simply/ssrf/host_w_const_start.conf",
    "chars": 70,
    "preview": "location ~* ^/backend/(?<path>.*) {\n    proxy_pass http://some$path;\n}"
  },
  {
    "path": "tests/plugins/simply/ssrf/host_w_const_start_arg.conf",
    "chars": 64,
    "preview": "location /backend/ {\n    proxy_pass http://some${arg_la}.shit;\n}"
  },
  {
    "path": "tests/plugins/simply/ssrf/not_host_var_fp.conf",
    "chars": 66,
    "preview": "location ~ /proxy/(.*)$ {\n    proxy_pass http://yastatic.net/$1;\n}"
  },
  {
    "path": "tests/plugins/simply/ssrf/request_uri_fp.conf",
    "chars": 62,
    "preview": "location /backend/ {\n    proxy_pass http://some$request_uri;\n}"
  },
  {
    "path": "tests/plugins/simply/ssrf/request_uri_var_fp.conf",
    "chars": 85,
    "preview": "location / {\n    set $upstream \"http://some$request_uri\";\n    proxy_pass $upstream;\n}"
  },
  {
    "path": "tests/plugins/simply/ssrf/scheme_var.conf",
    "chars": 75,
    "preview": "location ~ /proxy/$ {\n    proxy_pass $http_proxy_scheme://some/file.conf;\n}"
  },
  {
    "path": "tests/plugins/simply/ssrf/single_var.conf",
    "chars": 59,
    "preview": "location ~ /proxy/(?P<proxy>.*)$ {\n    proxy_pass $proxy;\n}"
  },
  {
    "path": "tests/plugins/simply/ssrf/used_arg.conf",
    "chars": 46,
    "preview": "location /proxy/ {\n    proxy_pass $arg_some;\n}"
  },
  {
    "path": "tests/plugins/simply/ssrf/vars_from_loc.conf",
    "chars": 131,
    "preview": "location ~ /proxy/(.*)/(.*)/(.*)$ {\n    set $scheme $1;\n    set $host $2;\n    set $path $3;\n    proxy_pass $scheme://$ho"
  },
  {
    "path": "tests/plugins/simply/ssrf/with_const_scheme.conf",
    "chars": 260,
    "preview": "location ~* ^/internal-proxy/(https?)/(.*?)/(.*) {\n    resolver 127.0.0.1;\n\n    set $proxy_protocol $1;\n    set $proxy_h"
  },
  {
    "path": "tests/plugins/simply/valid_referers/config.json",
    "chars": 24,
    "preview": "{\n  \"severity\": \"HIGH\"\n}"
  },
  {
    "path": "tests/plugins/simply/valid_referers/none_first.conf",
    "chars": 48,
    "preview": "valid_referers none server_names *.webvisor.com;"
  },
  {
    "path": "tests/plugins/simply/valid_referers/none_last.conf",
    "chars": 71,
    "preview": "valid_referers server_names\n               foo.com\n               none;"
  },
  {
    "path": "tests/plugins/simply/valid_referers/none_middle.conf",
    "chars": 64,
    "preview": "valid_referers server_names foo.com\n               none bar.com;"
  },
  {
    "path": "tests/plugins/simply/valid_referers/wo_none_fp.conf",
    "chars": 63,
    "preview": "valid_referers server_names foo.com bar.com *.none.com none.ru;"
  },
  {
    "path": "tests/plugins/test_simply.py",
    "chars": 3658,
    "preview": "from nose.tools import assert_equals, assert_true\nfrom tests.asserts import assert_in\nimport os\nfrom os import path\nimpo"
  },
  {
    "path": "tests/utils.py",
    "chars": 1898,
    "preview": "from logging.handlers import BufferingHandler\n\n\nclass LogHandler(BufferingHandler):\n    def __init__(self, matcher):\n   "
  },
  {
    "path": "tox.ini",
    "chars": 309,
    "preview": "[tox]\nenvlist = py26, py27, py34, py35, py36, py37, flake8\nskip_missing_interpreters = True\n\n[testenv]\ndeps =\n    -rrequ"
  }
]

About this extraction

This page contains the full source code of the yandex/gixy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 178 files (268.2 KB), approximately 72.2k tokens, and a symbol index with 494 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!