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
====
[](https://github.com/yandex/gixy/blob/master/LICENSE)
[](https://travis-ci.org/yandex/gixy)
[](https://github.com/yandex/gixy/issues/new)
[](https://github.com/yandex/gixy/issues)
[](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
# Что умеет
На текущий момент 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
====
[](https://github.com/yandex/gixy/blob/master/LICENSE)
[](https://travis-ci.org/yandex/gixy)
[](https://github.com/yandex/gixy/issues/new)
[](https://github.com/yandex/gixy/issues)
[](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
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
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[](https"
},
{
"path": "README.md",
"chars": 5065,
"preview": "GIXY\n====\n[](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.