diff --git a/.gitignore b/.gitignore index 2b56ca7c..e1e4eda7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,10 +3,10 @@ userdata/* !userdata/gitkeep logs/* watcher.zip -core/version -core/rss/imdb +watcher3/version +watcher3/rss/imdb db/* -core/cp_plugins/tasks.json +watcher3/cp_plugins/tasks.json .editorconfig # Compiled diff --git a/core/localization.py b/core/localization.py deleted file mode 100644 index 9784f252..00000000 --- a/core/localization.py +++ /dev/null @@ -1,32 +0,0 @@ -import gettext -import os -import core - -locale_dir = os.path.join(core.PROG_PATH, 'locale') - - -def get(): - ''' Sets core.LANGUAGES to a dict {'en': } - - Does not return - ''' - for lang in os.listdir(locale_dir): - if not os.path.isdir(os.path.join(locale_dir, lang)): - continue - - core.LANGUAGES[lang] = gettext.translation('watcher', localedir=locale_dir, languages=[lang]) - - core.LANGUAGES['en'] = gettext.translation('watcher', localedir=locale_dir, languages=[''], fallback=True) - - -def install(lang=None): - ''' Set/install language of choice - lang (str): language code of language to apply - - Does not return - ''' - - lang = core.CONFIG['Server']['language'] if not lang else lang - - core.LANGUAGES.get(lang, core.LANGUAGES['en']).install() - core.LANGUAGE = lang diff --git a/lib/CherryPy-18.8.0.dist-info/INSTALLER b/lib/CherryPy-18.8.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e3..00000000 --- a/lib/CherryPy-18.8.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/lib/CherryPy-18.8.0.dist-info/LICENSE.md b/lib/CherryPy-18.8.0.dist-info/LICENSE.md deleted file mode 100644 index ce28cf8f..00000000 --- a/lib/CherryPy-18.8.0.dist-info/LICENSE.md +++ /dev/null @@ -1,30 +0,0 @@ -Copyright © 2004-2019, CherryPy Team (team@cherrypy.dev) - -All rights reserved. - -* * * - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of CherryPy nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/CherryPy-18.8.0.dist-info/METADATA b/lib/CherryPy-18.8.0.dist-info/METADATA deleted file mode 100644 index 0f66b23e..00000000 --- a/lib/CherryPy-18.8.0.dist-info/METADATA +++ /dev/null @@ -1,194 +0,0 @@ -Metadata-Version: 2.1 -Name: CherryPy -Version: 18.8.0 -Summary: Object-Oriented HTTP framework -Home-page: https://www.cherrypy.dev -Author: CherryPy Team -Author-email: team@cherrypy.dev -Project-URL: CI: AppVeyor, https://ci.appveyor.com/project/cherrypy/cherrypy -Project-URL: CI: Travis, https://travis-ci.org/cherrypy/cherrypy -Project-URL: CI: Circle, https://circleci.com/gh/cherrypy/cherrypy -Project-URL: Docs: RTD, https://docs.cherrypy.dev -Project-URL: GitHub: issues, https://github.com/cherrypy/cherrypy/issues -Project-URL: GitHub: repo, https://github.com/cherrypy/cherrypy -Project-URL: Tidelift: funding, https://tidelift.com/subscription/pkg/pypi-cherrypy?utm_source=pypi-cherrypy&utm_medium=referral&utm_campaign=pypi -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Web Environment -Classifier: Intended Audience :: Developers -Classifier: License :: Freely Distributable -Classifier: Operating System :: OS Independent -Classifier: Framework :: CherryPy -Classifier: License :: OSI Approved :: BSD License -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: Implementation -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: Jython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Internet :: WWW/HTTP -Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content -Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers -Classifier: Topic :: Internet :: WWW/HTTP :: WSGI -Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application -Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server -Classifier: Topic :: Software Development :: Libraries :: Application Frameworks -Requires-Python: >=3.6 -License-File: LICENSE.md -Requires-Dist: cheroot (>=8.2.1) -Requires-Dist: portend (>=2.1.1) -Requires-Dist: more-itertools -Requires-Dist: zc.lockfile -Requires-Dist: jaraco.collections -Requires-Dist: pywin32 (>=227) ; sys_platform == "win32" and implementation_name == "cpython" and python_version < "3.10" -Provides-Extra: docs -Requires-Dist: sphinx ; extra == 'docs' -Requires-Dist: docutils ; extra == 'docs' -Requires-Dist: alabaster ; extra == 'docs' -Requires-Dist: sphinxcontrib-apidoc (>=0.3.0) ; extra == 'docs' -Requires-Dist: rst.linker (>=1.11) ; extra == 'docs' -Requires-Dist: jaraco.packaging (>=3.2) ; extra == 'docs' -Requires-Dist: setuptools ; extra == 'docs' -Provides-Extra: json -Requires-Dist: simplejson ; extra == 'json' -Provides-Extra: memcached_session -Requires-Dist: python-memcached (>=1.58) ; extra == 'memcached_session' -Provides-Extra: routes_dispatcher -Requires-Dist: routes (>=2.3.1) ; extra == 'routes_dispatcher' -Provides-Extra: ssl -Requires-Dist: pyOpenSSL ; extra == 'ssl' -Provides-Extra: testing -Requires-Dist: coverage ; extra == 'testing' -Requires-Dist: codecov ; extra == 'testing' -Requires-Dist: objgraph ; extra == 'testing' -Requires-Dist: pytest (>=5.3.5) ; extra == 'testing' -Requires-Dist: pytest-cov ; extra == 'testing' -Requires-Dist: pytest-forked ; extra == 'testing' -Requires-Dist: pytest-sugar ; extra == 'testing' -Requires-Dist: path.py ; extra == 'testing' -Requires-Dist: requests-toolbelt ; extra == 'testing' -Requires-Dist: pytest-services (>=2) ; extra == 'testing' -Requires-Dist: setuptools ; extra == 'testing' -Provides-Extra: xcgi -Requires-Dist: flup ; extra == 'xcgi' - -.. image:: https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg - :target: https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md - :alt: SWUbanner - -.. image:: https://img.shields.io/pypi/v/cherrypy.svg - :target: https://pypi.org/project/cherrypy - -.. image:: https://tidelift.com/badges/package/pypi/CherryPy - :target: https://tidelift.com/subscription/pkg/pypi-cherrypy?utm_source=pypi-cherrypy&utm_medium=readme - :alt: CherryPy is available as part of the Tidelift Subscription - -.. image:: https://img.shields.io/badge/Python%203%20only-pip%20install%20%22%3E%3D18.0.0%22-%234da45e.svg - :target: https://python3statement.org/ - -.. image:: https://img.shields.io/badge/Python%203%20and%202-pip%20install%20%22%3C18.0.0%22-%2349a7e9.svg - :target: https://python3statement.org/#sections40-timeline - - - -.. image:: https://readthedocs.org/projects/cherrypy/badge/?version=latest - :target: https://docs.cherrypy.dev/en/latest/?badge=latest - -.. image:: https://img.shields.io/badge/StackOverflow-CherryPy-blue.svg - :target: https://stackoverflow.com/questions/tagged/cheroot+or+cherrypy - -.. image:: https://img.shields.io/badge/Mailing%20list-cherrypy--users-orange.svg - :target: https://groups.google.com/group/cherrypy-users - -.. image:: https://img.shields.io/gitter/room/cherrypy/cherrypy.svg - :target: https://gitter.im/cherrypy/cherrypy - -.. image:: https://img.shields.io/travis/cherrypy/cherrypy/master.svg?label=Linux%20build%20%40%20Travis%20CI - :target: https://travis-ci.org/cherrypy/cherrypy - -.. image:: https://circleci.com/gh/cherrypy/cherrypy/tree/master.svg?style=svg - :target: https://circleci.com/gh/cherrypy/cherrypy/tree/master - -.. image:: https://img.shields.io/appveyor/ci/CherryPy/cherrypy/master.svg?label=Windows%20build%20%40%20Appveyor - :target: https://ci.appveyor.com/project/CherryPy/cherrypy/branch/master - -.. image:: https://img.shields.io/badge/license-BSD-blue.svg?maxAge=3600 - :target: https://pypi.org/project/cheroot - -.. image:: https://img.shields.io/pypi/pyversions/cherrypy.svg - :target: https://pypi.org/project/cherrypy - -.. image:: https://badges.github.io/stability-badges/dist/stable.svg - :target: https://github.com/badges/stability-badges - :alt: stable - -.. image:: https://api.codacy.com/project/badge/Grade/48b11060b5d249dc86e52dac2be2c715 - :target: https://www.codacy.com/app/webknjaz/cherrypy-upstream?utm_source=github.com&utm_medium=referral&utm_content=cherrypy/cherrypy&utm_campaign=Badge_Grade - -.. image:: https://codecov.io/gh/cherrypy/cherrypy/branch/master/graph/badge.svg - :target: https://codecov.io/gh/cherrypy/cherrypy - :alt: codecov - -Welcome to the GitHub repository of `CherryPy `_! - -CherryPy is a pythonic, object-oriented HTTP framework. - -1. It allows building web applications in much the same way one would - build any other object-oriented program. -2. This design results in more concise and readable code developed faster. - It's all just properties and methods. -3. It is now more than ten years old and has proven fast and very - stable. -4. It is being used in production by many sites, from the simplest to - the most demanding. -5. And perhaps most importantly, it is fun to work with :-) - -Here's how easy it is to write "Hello World" in CherryPy: - -.. code:: python - - import cherrypy - - class HelloWorld(object): - @cherrypy.expose - def index(self): - return "Hello World!" - - cherrypy.quickstart(HelloWorld()) - -And it continues to work that intuitively when systems grow, allowing -for the Python object model to be dynamically presented as a website -and/or API. - -While CherryPy is one of the easiest and most intuitive frameworks out -there, the prerequisite for understanding the `CherryPy -documentation `_ is that you have -a general understanding of Python and web development. -Additionally: - -- Tutorials are included in the repository: - https://github.com/cherrypy/cherrypy/tree/master/cherrypy/tutorial -- A general wiki at: - https://github.com/cherrypy/cherrypy/wiki - -If the docs are insufficient to address your needs, the CherryPy -community has several `avenues for support -`_. - -For Enterprise --------------- - -CherryPy is available as part of the Tidelift Subscription. - -The CherryPy maintainers and the maintainers of thousands of other packages -are working with Tidelift to deliver one enterprise subscription that covers -all of the open source you use. - -`Learn more `_. - -Contributing ------------- - -Please follow the `contribution guidelines -`_. -And by all means, absorb the `Zen of -CherryPy `_. diff --git a/lib/CherryPy-18.8.0.dist-info/RECORD b/lib/CherryPy-18.8.0.dist-info/RECORD deleted file mode 100644 index b3e78172..00000000 --- a/lib/CherryPy-18.8.0.dist-info/RECORD +++ /dev/null @@ -1,235 +0,0 @@ -../../bin/cherryd.exe,sha256=SU1950AhOxbTA5-50MkYJNEHGPmKy4sj7_lFNeUvyrs,108419 -CherryPy-18.8.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -CherryPy-18.8.0.dist-info/LICENSE.md,sha256=Ra3pM8KA7ON-PgHTqRr-7ZUFdGpFb3KtELCJfEnmUQQ,1511 -CherryPy-18.8.0.dist-info/METADATA,sha256=z8rRzQuO9jzE1-ommQF_XhPNlRQ3a7YKlNQf2ruAZCs,8398 -CherryPy-18.8.0.dist-info/RECORD,, -CherryPy-18.8.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -CherryPy-18.8.0.dist-info/WHEEL,sha256=z9j0xAa_JmUKMpmz72K0ZGALSM_n-wQVmGbleXx2VHg,110 -CherryPy-18.8.0.dist-info/entry_points.txt,sha256=2iMzTbV4_iDQUVEBHZGAuXaFAbW9naJoL4O9Gn7eDks,50 -CherryPy-18.8.0.dist-info/top_level.txt,sha256=mOBE-r7Ej1kFrXNKlOj3yY9QfGA6Xkz6vZK7VNJF3YE,9 -cherrypy/__init__.py,sha256=BHFq6fz2ILLDGq7OCALzmX8MaxPGI6AiR-fYLJo1kIw,11285 -cherrypy/__main__.py,sha256=gb89ZhhQ1kjYvU61axGuZt5twA4q3kfBa3WKrPT6D2k,107 -cherrypy/__pycache__/__init__.cpython-311.pyc,, -cherrypy/__pycache__/__main__.cpython-311.pyc,, -cherrypy/__pycache__/_cpchecker.cpython-311.pyc,, -cherrypy/__pycache__/_cpcompat.cpython-311.pyc,, -cherrypy/__pycache__/_cpconfig.cpython-311.pyc,, -cherrypy/__pycache__/_cpdispatch.cpython-311.pyc,, -cherrypy/__pycache__/_cperror.cpython-311.pyc,, -cherrypy/__pycache__/_cplogging.cpython-311.pyc,, -cherrypy/__pycache__/_cpmodpy.cpython-311.pyc,, -cherrypy/__pycache__/_cpnative_server.cpython-311.pyc,, -cherrypy/__pycache__/_cpreqbody.cpython-311.pyc,, -cherrypy/__pycache__/_cprequest.cpython-311.pyc,, -cherrypy/__pycache__/_cpserver.cpython-311.pyc,, -cherrypy/__pycache__/_cptools.cpython-311.pyc,, -cherrypy/__pycache__/_cptree.cpython-311.pyc,, -cherrypy/__pycache__/_cpwsgi.cpython-311.pyc,, -cherrypy/__pycache__/_cpwsgi_server.cpython-311.pyc,, -cherrypy/__pycache__/_helper.cpython-311.pyc,, -cherrypy/__pycache__/_json.cpython-311.pyc,, -cherrypy/__pycache__/daemon.cpython-311.pyc,, -cherrypy/_cpchecker.py,sha256=aAZfQTmW7_f0MfKZmmaHKs_7IjeOi8j-voeDmSbLEA0,14566 -cherrypy/_cpcompat.py,sha256=2gZtFj0h6RRGlkY-R6WoEl-S8BBSIwCeuj7F2Tf3mU0,1992 -cherrypy/_cpconfig.py,sha256=abftUSXmOWoqpak3VHOR50XBBo6wxy8qngz5VmsRXDQ,9647 -cherrypy/_cpdispatch.py,sha256=BRpBxNHxyPmfuRCdjwUNiwgkwyPnpQQ1p4_htSDicoU,25216 -cherrypy/_cperror.py,sha256=Maxb8U0CJfEHkwI678a-H8PXnZvWcObnr7_GW1nVvNc,23103 -cherrypy/_cplogging.py,sha256=RyNDWhVt2K_QRsv1vOi2gG-yYOgW3UxjOhvQJ1Tv6qM,16402 -cherrypy/_cpmodpy.py,sha256=lUqckVgqRr972jrFyA0y2TD79gA8NaRj5nboDuuARDY,11096 -cherrypy/_cpnative_server.py,sha256=BtmMuMtdBmjb_kavTt6NdoREmqkHRhi1bUAG9EdhQuU,6677 -cherrypy/_cpreqbody.py,sha256=85XtrtUq6wd1XeyRK2i2wiSY3QvJfaPnoLGMhhaks68,36382 -cherrypy/_cprequest.py,sha256=fIGuIbJ7Eb_-zriV4300VG7x7f0f7xXzTzxVez6fZVA,34408 -cherrypy/_cpserver.py,sha256=sai48_tDCkUrhqLKaSZSyk3aSIRxgUiMv6FNUe03PBY,8320 -cherrypy/_cptools.py,sha256=Y35JizxKfVy3JNCbVido0rSXlyIzv4MrYDxdRuYG0bQ,18163 -cherrypy/_cptree.py,sha256=_DLxtDfnPksYk5wlkLzsyCIk5TqrwE4wgkF1yFPdbko,10977 -cherrypy/_cpwsgi.py,sha256=W3u6BKh1P_TZw9d5DzuwZhINXeQcMGvUVshN0iaFEOk,16394 -cherrypy/_cpwsgi_server.py,sha256=ZILHc_ouGC_eARqrVTA1z5pdm9T5QLGURN4Idr0eiJQ,4187 -cherrypy/_helper.py,sha256=bTZvfBpsZ0FDAGqtuuSblaiDloogxCpOyBy-XJF39vE,11653 -cherrypy/_json.py,sha256=zxl6rLuW4xE5aiXJ0UPeI22NhdeLG-gDN_FFOMGikg0,440 -cherrypy/daemon.py,sha256=kgiqlnWFx-PkLEcPPRJmXmB6zbI7X0cN2cCRtosow4c,3950 -cherrypy/favicon.ico,sha256=jrNK5SnKfbnSFgX_xQyLX3khmeImw8IbbHgJVIsGci0,1406 -cherrypy/lib/__init__.py,sha256=5_heysJFsUQMDLVNEFuCUbH03wWbA3lqsErmXLvPfFU,2745 -cherrypy/lib/__pycache__/__init__.cpython-311.pyc,, -cherrypy/lib/__pycache__/auth_basic.cpython-311.pyc,, -cherrypy/lib/__pycache__/auth_digest.cpython-311.pyc,, -cherrypy/lib/__pycache__/caching.cpython-311.pyc,, -cherrypy/lib/__pycache__/covercp.cpython-311.pyc,, -cherrypy/lib/__pycache__/cpstats.cpython-311.pyc,, -cherrypy/lib/__pycache__/cptools.cpython-311.pyc,, -cherrypy/lib/__pycache__/encoding.cpython-311.pyc,, -cherrypy/lib/__pycache__/gctools.cpython-311.pyc,, -cherrypy/lib/__pycache__/httputil.cpython-311.pyc,, -cherrypy/lib/__pycache__/jsontools.cpython-311.pyc,, -cherrypy/lib/__pycache__/locking.cpython-311.pyc,, -cherrypy/lib/__pycache__/profiler.cpython-311.pyc,, -cherrypy/lib/__pycache__/reprconf.cpython-311.pyc,, -cherrypy/lib/__pycache__/sessions.cpython-311.pyc,, -cherrypy/lib/__pycache__/static.cpython-311.pyc,, -cherrypy/lib/__pycache__/xmlrpcutil.cpython-311.pyc,, -cherrypy/lib/auth_basic.py,sha256=FkySKMk0WC7OeLIefylkNyGORLpr9Rf98jQuxdBGHsA,4421 -cherrypy/lib/auth_digest.py,sha256=WO9aXkVvqj0nAsD1jRYN4zy9OvhUo7FdiXkgvfx3iYE,15351 -cherrypy/lib/caching.py,sha256=rJ_fPGlZ3Dpbg-BEDZQHL-m4_LJHJg69tBrAqgfzOW8,17517 -cherrypy/lib/covercp.py,sha256=l3zwygAxDBZ8x617-HPk4lUXN20wRqXdblTm_v2dPOc,11599 -cherrypy/lib/cpstats.py,sha256=luAeI7Dttzb3HhWUNsZUrNCrFp9LDXiBIOeVEzVmxiU,22854 -cherrypy/lib/cptools.py,sha256=fzdi6K6TzlwLKFFKfQp9OCncwMpH5t9ThaXL2IGd20M,23402 -cherrypy/lib/encoding.py,sha256=pnUVBiRioH9LpLU-K81u47yCviPebPNyX3P_Pzvv6Lw,17047 -cherrypy/lib/gctools.py,sha256=5SI3w507J3JJConDjp8Jmgo6FI9uR59S_mm44cGB_Lw,7346 -cherrypy/lib/httputil.py,sha256=304ixadmh6Tm5CGRqk7JaDKruKzeDJ7vJCdwRXPRBzQ,18007 -cherrypy/lib/jsontools.py,sha256=YQQmGQN4XKnSgB4u0lRl2MB9YkVeNR3-6YWyU76jUFQ,3641 -cherrypy/lib/locking.py,sha256=RLUwVj07-xNnBavmcRkjs-fmg712S7wSk3Gnox9TyEg,1224 -cherrypy/lib/profiler.py,sha256=1apBGddxzooel45y5TwH7VikJrwB8pvPk-6FjIs0R1U,6539 -cherrypy/lib/reprconf.py,sha256=i-2huPOtI9t69ojl1kHf2em-4R4lYyyIEvW6X4K1ph4,12326 -cherrypy/lib/sessions.py,sha256=CEruNzLRCPiDbHWVL_FUTJHr0xVWvWdy05-ATTJNk60,30896 -cherrypy/lib/static.py,sha256=Zd-2RVUM3lrgU7Ag-HxAEWlMb22CzZ7fU5xatowfGGo,16592 -cherrypy/lib/xmlrpcutil.py,sha256=UZqJsoBboSHjgsSiOfFcDwlubtSruhYyxpKdxutazQk,1684 -cherrypy/process/__init__.py,sha256=RjaRqUG5U-ZhxAs7GBWN9PFR5hIK-9a9x3ZFwFjyW4Y,547 -cherrypy/process/__pycache__/__init__.cpython-311.pyc,, -cherrypy/process/__pycache__/plugins.cpython-311.pyc,, -cherrypy/process/__pycache__/servers.cpython-311.pyc,, -cherrypy/process/__pycache__/win32.cpython-311.pyc,, -cherrypy/process/__pycache__/wspbus.cpython-311.pyc,, -cherrypy/process/plugins.py,sha256=hZ0r1cpK5qQZCFmTG28GC6GTS1Gcd01bU06DYQc2ucw,26846 -cherrypy/process/servers.py,sha256=KojCzP2AxBV6D3URQX_blU8dfpkIL78OpfdwcQwRtt8,13442 -cherrypy/process/win32.py,sha256=o_aT4XrUL6WcyVPti4O59apFd5ARoJz75VbiktdqqJk,5787 -cherrypy/process/wspbus.py,sha256=wcDsxWtOBXyAAhcl2WZ6_mybs3N79GRtEeRu-zjXQ-8,21460 -cherrypy/scaffold/__init__.py,sha256=sLK5vjy-_f6ikJ7-Lhj7HT8VGAIi6olqPuZMTD-RpAQ,1997 -cherrypy/scaffold/__pycache__/__init__.cpython-311.pyc,, -cherrypy/scaffold/apache-fcgi.conf,sha256=0M10HHX8i2Or3r-gHoDglSQK4dZHd4Jhx4WhamJtuwc,930 -cherrypy/scaffold/example.conf,sha256=EAqr2Sb1B1osc198dY1FV2A0wgnBmGsJf99_-GGexVU,62 -cherrypy/scaffold/site.conf,sha256=pjUhF-ir1xzSsV7LqXGfyR6Ns_r_n3ATWw8OlfbgT3w,426 -cherrypy/scaffold/static/made_with_cherrypy_small.png,sha256=VlSRvYj-pZzls-peicQhWpbqkdsZHtNhPtSfZV12BFQ,6347 -cherrypy/test/__init__.py,sha256=jWQJbuVAcOuCITN7V8yrRz7U4rcK_ACx35Ephl4xwqY,396 -cherrypy/test/__pycache__/__init__.cpython-311.pyc,, -cherrypy/test/__pycache__/_test_decorators.cpython-311.pyc,, -cherrypy/test/__pycache__/_test_states_demo.cpython-311.pyc,, -cherrypy/test/__pycache__/benchmark.cpython-311.pyc,, -cherrypy/test/__pycache__/checkerdemo.cpython-311.pyc,, -cherrypy/test/__pycache__/helper.cpython-311.pyc,, -cherrypy/test/__pycache__/logtest.cpython-311.pyc,, -cherrypy/test/__pycache__/modfastcgi.cpython-311.pyc,, -cherrypy/test/__pycache__/modfcgid.cpython-311.pyc,, -cherrypy/test/__pycache__/modpy.cpython-311.pyc,, -cherrypy/test/__pycache__/modwsgi.cpython-311.pyc,, -cherrypy/test/__pycache__/sessiondemo.cpython-311.pyc,, -cherrypy/test/__pycache__/test_auth_basic.cpython-311.pyc,, -cherrypy/test/__pycache__/test_auth_digest.cpython-311.pyc,, -cherrypy/test/__pycache__/test_bus.cpython-311.pyc,, -cherrypy/test/__pycache__/test_caching.cpython-311.pyc,, -cherrypy/test/__pycache__/test_config.cpython-311.pyc,, -cherrypy/test/__pycache__/test_config_server.cpython-311.pyc,, -cherrypy/test/__pycache__/test_conn.cpython-311.pyc,, -cherrypy/test/__pycache__/test_core.cpython-311.pyc,, -cherrypy/test/__pycache__/test_dynamicobjectmapping.cpython-311.pyc,, -cherrypy/test/__pycache__/test_encoding.cpython-311.pyc,, -cherrypy/test/__pycache__/test_etags.cpython-311.pyc,, -cherrypy/test/__pycache__/test_http.cpython-311.pyc,, -cherrypy/test/__pycache__/test_httputil.cpython-311.pyc,, -cherrypy/test/__pycache__/test_iterator.cpython-311.pyc,, -cherrypy/test/__pycache__/test_json.cpython-311.pyc,, -cherrypy/test/__pycache__/test_logging.cpython-311.pyc,, -cherrypy/test/__pycache__/test_mime.cpython-311.pyc,, -cherrypy/test/__pycache__/test_misc_tools.cpython-311.pyc,, -cherrypy/test/__pycache__/test_native.cpython-311.pyc,, -cherrypy/test/__pycache__/test_objectmapping.cpython-311.pyc,, -cherrypy/test/__pycache__/test_params.cpython-311.pyc,, -cherrypy/test/__pycache__/test_plugins.cpython-311.pyc,, -cherrypy/test/__pycache__/test_proxy.cpython-311.pyc,, -cherrypy/test/__pycache__/test_refleaks.cpython-311.pyc,, -cherrypy/test/__pycache__/test_request_obj.cpython-311.pyc,, -cherrypy/test/__pycache__/test_routes.cpython-311.pyc,, -cherrypy/test/__pycache__/test_session.cpython-311.pyc,, -cherrypy/test/__pycache__/test_sessionauthenticate.cpython-311.pyc,, -cherrypy/test/__pycache__/test_states.cpython-311.pyc,, -cherrypy/test/__pycache__/test_static.cpython-311.pyc,, -cherrypy/test/__pycache__/test_tools.cpython-311.pyc,, -cherrypy/test/__pycache__/test_tutorials.cpython-311.pyc,, -cherrypy/test/__pycache__/test_virtualhost.cpython-311.pyc,, -cherrypy/test/__pycache__/test_wsgi_ns.cpython-311.pyc,, -cherrypy/test/__pycache__/test_wsgi_unix_socket.cpython-311.pyc,, -cherrypy/test/__pycache__/test_wsgi_vhost.cpython-311.pyc,, -cherrypy/test/__pycache__/test_wsgiapps.cpython-311.pyc,, -cherrypy/test/__pycache__/test_xmlrpc.cpython-311.pyc,, -cherrypy/test/__pycache__/webtest.cpython-311.pyc,, -cherrypy/test/_test_decorators.py,sha256=U5y0mhWwTcCrYLjY3EWSkkz_Vw-z0bPSP5ujwb6b79Y,951 -cherrypy/test/_test_states_demo.py,sha256=lVpbqHgHcIfdz1-i6xDbmzmD87V3JbwD_e4lK0Bagnw,1876 -cherrypy/test/benchmark.py,sha256=GoTeD98I5tFaDDSQTxmy1OrmTM5XS8bybg41i2j3rB4,12563 -cherrypy/test/checkerdemo.py,sha256=H8muNg9uHSAlDVYc-rICkcUKtJqzlTQfjYmMzC1Fuv4,1861 -cherrypy/test/fastcgi.conf,sha256=0YsIPLmOg-NdGGqCCPpBERKGYy_zBU6LkRDyR41nvBE,686 -cherrypy/test/fcgi.conf,sha256=neiD1sjiFblAJLUdlOSKiZ1uVl1eK3zM2_7LZQigkTs,486 -cherrypy/test/helper.py,sha256=y7PwhV4kZoJsXTUMXDtqGmZxk-7RfqWec7SvXWCOYto,16398 -cherrypy/test/logtest.py,sha256=zAMSL0AGGYwXUFLm56ztev6yb9GR94hL_wArtXC0Sic,8325 -cherrypy/test/modfastcgi.py,sha256=SblRPAhbo9uJLHISamWE7AyW2slrlWms76VvpnCqzLo,4607 -cherrypy/test/modfcgid.py,sha256=7a5iRZ9VQyr5O2Pajgo_73j6rbMr_i02FG6sO1-Q-lM,4192 -cherrypy/test/modpy.py,sha256=F6Ar3uxr1ckp5V5l6FNJEZnNHSB0SDtR5hGmYU010oY,4933 -cherrypy/test/modwsgi.py,sha256=DnKeIzaYSY2_fBgGb2ojjWS_A9A2UcuZUgneSalPO08,4789 -cherrypy/test/sessiondemo.py,sha256=_vVruGha_8f6Fim_n9LgxwNxebbM6JgiqBvyX0WaEQc,5425 -cherrypy/test/static/404.html,sha256=9jnU0KKbdzHVq9EvhkGOEly92qNJbtCSXOsH0KFTL4U,92 -cherrypy/test/static/dirback.jpg,sha256=eS_X3BSeu8OSu-GTYndM1tJkWoW_oVJp1O_mmUUGeo8,16585 -cherrypy/test/static/index.html,sha256=cB6ALrLhcxEGyMNgOHzmnBvmRnPCW_u3ebZUqdCiHkQ,14 -cherrypy/test/style.css,sha256=2Ypw_ziOWlY4dTZJlwsrorDLqLA1z485lgagaGemtKQ,17 -cherrypy/test/test.pem,sha256=x6LrLPw2dBRyZwHXk6FhdSDNM3-Cv7DBXc8o4A19RhI,2254 -cherrypy/test/test_auth_basic.py,sha256=JlLdHeyaEutMzWK7LX3HfSOVQWAKZXoUNL67sSgUMZQ,4499 -cherrypy/test/test_auth_digest.py,sha256=pp49xOWFS_i8N_s8MQ6h_exbOxlQZjfjT-cxDvi701E,4454 -cherrypy/test/test_bus.py,sha256=K_6pYUz4q6xBOh5tMK67d3n_X1VF1LJhS1mRsGh0JnQ,9960 -cherrypy/test/test_caching.py,sha256=s6aA_P6mcasaEfPwX50c9A-UqwSiYdzMVp5GFQH08uQ,14386 -cherrypy/test/test_config.py,sha256=lUxRUCBmDVBo8LK7yi8w5qvFcS3vw4YpFwl66TdRskQ,8836 -cherrypy/test/test_config_server.py,sha256=D7jLqZawCOh2z6vGU-WjkupiJA0BxPywb8LuILL2JGA,4037 -cherrypy/test/test_conn.py,sha256=g2e2CCaB_2UiFWVLniVZbk2YNrXqN9_J0M1FymMZ_F8,30744 -cherrypy/test/test_core.py,sha256=u9lxQENZoTczEzx677_MxyV3i2tmZxl0Wd9yQ-mtIUI,30395 -cherrypy/test/test_dynamicobjectmapping.py,sha256=0VpMCJq1APhGjqwYoYvx0irFJ4yb3NWqdE5RtK11uDM,12331 -cherrypy/test/test_encoding.py,sha256=lGUiNKrWQlHma4fV-mjR0vv-xylrw_v1E6j61CT6vBc,17535 -cherrypy/test/test_etags.py,sha256=mzuKNjFXx67gHoqS_jaGEzjxJ025mzlLgepsW2DYETI,3093 -cherrypy/test/test_http.py,sha256=s5we0qreW82SubkSiPPXPgtRtTD86m5amwOGZkjUJ24,10939 -cherrypy/test/test_httputil.py,sha256=gA3u7bt1vV2Av3T1I0CaEGEdYV9kGDEPD80al1IbPE8,2412 -cherrypy/test/test_iterator.py,sha256=hBKYnKCxcr6DnLCRrwMGyR14t551qdYhrlEHKX7gFCQ,5724 -cherrypy/test/test_json.py,sha256=rVfzyCwSMf79bcZ8aYBA_180FJxcHY9jFT5_0M6-pSc,2860 -cherrypy/test/test_logging.py,sha256=yIL8C6vuENvNv58UgFsTIGA7M_mo0GvmyfJ2nBdl1F8,8315 -cherrypy/test/test_mime.py,sha256=-6HpcAIGtN56nWRQclxvSKhNLW5e_bYRO6kDtM3DlN8,4538 -cherrypy/test/test_misc_tools.py,sha256=Ixjq2IAJZ1BTuV-i_AUKw_OsjYru_i9RPm1gIWyWt_E,7094 -cherrypy/test/test_native.py,sha256=rtow-ShYRkd2oEBtDksU6e06_L0BvZToFJFngeeGF34,971 -cherrypy/test/test_objectmapping.py,sha256=KTMqAizhWBGCgDFp1n8msno4ylYPnmvWZ1cWHzzUgO0,14504 -cherrypy/test/test_params.py,sha256=p4DfugiKWxF9nPX5Gs7arGhUmpx_eeZhWyS5yCXCUj4,1862 -cherrypy/test/test_plugins.py,sha256=afO6r6XOLYaWPO-Crbei1W0xe_VDA1ippMkCtuo_ypc,334 -cherrypy/test/test_proxy.py,sha256=XPdi3O_izRLtvu3UTJE-WTBVmn4DEng7irrfUD69srU,5630 -cherrypy/test/test_refleaks.py,sha256=HK55E9JtRFc28FhnnTLV9DrM1k82ZmPEVdHYyp525K0,1555 -cherrypy/test/test_request_obj.py,sha256=C3dOHeeqVGxXQjAhDS3Q_7dIzfpyR4WFo6eZ61W8vKI,37404 -cherrypy/test/test_routes.py,sha256=m0MvSqurFk42PuSp5vF8ue40-vnhPNwC2EGTqkDozo4,2583 -cherrypy/test/test_session.py,sha256=Fxkm1kuth5tRb6FKiToFLETDmFwnQeVQKmDYLdo6pU0,17949 -cherrypy/test/test_sessionauthenticate.py,sha256=zhVUpN3FWPaZbMKQcTrDQiaI-RXjlwrJi7ssqbzhmU8,2013 -cherrypy/test/test_states.py,sha256=2E9S0L_JvJUtVBoJLOCpLgAICoOvE4HpFp3EP0gCLy8,16744 -cherrypy/test/test_static.py,sha256=4VQpLumcxWGNFwaLuCgOS11pyla1gpGgQNlKhMOopws,16702 -cherrypy/test/test_tools.py,sha256=cQELdifYvLfwJPg_0JoGQwyFvsgbuFXIJLUd8ejrgZA,17851 -cherrypy/test/test_tutorials.py,sha256=IszX5SdX3Vlnoq45b9vkdrAIWgc37um4DERFVpP6KcI,6964 -cherrypy/test/test_virtualhost.py,sha256=ap_e1gM7PERVN4mU70zc5RD1pVoSdN-te-B_uIAlV8g,4021 -cherrypy/test/test_wsgi_ns.py,sha256=PuoUe2EUwZk5z0yLIw9NdRkd_P4VKU7Cckj8n0QKSJo,2812 -cherrypy/test/test_wsgi_unix_socket.py,sha256=8guEkMHcOBhOGXYBfAThqCzpIBmUTNpqsilOpaRgD2c,2228 -cherrypy/test/test_wsgi_vhost.py,sha256=4uZ8_luFHiQJ6uxQeHFJtjemug8UiPcKmnwwclj0dkw,1034 -cherrypy/test/test_wsgiapps.py,sha256=1SdQGuWVcVCTiSvizDdIOekuvQLCybRXUKF2dpV2NTs,3997 -cherrypy/test/test_xmlrpc.py,sha256=DQfgzjIMcQP_gOi8el1QQ5dkfPaRbyZ6CDdPL9ZIgWo,4584 -cherrypy/test/webtest.py,sha256=uRwMw_why3KeXGZXdHE7-GfJag4ziL9KZmDGx4Q7Jbg,262 -cherrypy/tutorial/README.rst,sha256=v77BbhuiK44TxqeYPk3PwqV09Dg5AKWFdp7re04KdEo,617 -cherrypy/tutorial/__init__.py,sha256=cmLXfvQI0L6trCXwDzR0WE1bu4JZYt301HJRNhjZOBc,85 -cherrypy/tutorial/__pycache__/__init__.cpython-311.pyc,, -cherrypy/tutorial/__pycache__/tut01_helloworld.cpython-311.pyc,, -cherrypy/tutorial/__pycache__/tut02_expose_methods.cpython-311.pyc,, -cherrypy/tutorial/__pycache__/tut03_get_and_post.cpython-311.pyc,, -cherrypy/tutorial/__pycache__/tut04_complex_site.cpython-311.pyc,, -cherrypy/tutorial/__pycache__/tut05_derived_objects.cpython-311.pyc,, -cherrypy/tutorial/__pycache__/tut06_default_method.cpython-311.pyc,, -cherrypy/tutorial/__pycache__/tut07_sessions.cpython-311.pyc,, -cherrypy/tutorial/__pycache__/tut08_generators_and_yield.cpython-311.pyc,, -cherrypy/tutorial/__pycache__/tut09_files.cpython-311.pyc,, -cherrypy/tutorial/__pycache__/tut10_http_errors.cpython-311.pyc,, -cherrypy/tutorial/custom_error.html,sha256=9cMEb83zwct9i-fJlyl7yvBSNexF7yEIWOoxH8lpllQ,404 -cherrypy/tutorial/pdf_file.pdf,sha256=-WuAfJ9i1vbUT9EcKKvUaNGfbPQsNeFx-qsB4gsxWUg,11961 -cherrypy/tutorial/tut01_helloworld.py,sha256=Au6IDz0Kd1XMPzb-vJL-1y_BWz0GgrljOtqOpzDZeOk,1015 -cherrypy/tutorial/tut02_expose_methods.py,sha256=ikz6QOGLknEZm0k-f9BR18deItQ3UO8yYg1NW13kB8g,801 -cherrypy/tutorial/tut03_get_and_post.py,sha256=bY_cTha4zIkokv585yioQkM-S2a7GfetTE3ovl3-7cw,1587 -cherrypy/tutorial/tut04_complex_site.py,sha256=PCxyUVKG-L_YUgt1D5Q5t0_IUiGXbcGyf_L00K8cH8c,2948 -cherrypy/tutorial/tut05_derived_objects.py,sha256=u0LBnUTW8DnexEAtowhJ0sjeAqp6rS065TM2QXfDY1I,2141 -cherrypy/tutorial/tut06_default_method.py,sha256=3Wx34fL_4P3M_dhu6RQdpXQeSriIJpSsy72IqpNQ6ns,2264 -cherrypy/tutorial/tut07_sessions.py,sha256=fQo-v_ol5CjXiq4vdsm7Dh1us6DVDzbqAIouRkmLeR8,1228 -cherrypy/tutorial/tut08_generators_and_yield.py,sha256=m5GfOtNDoGxMd1rw5tCRg3o9cTyt-e1gR-ALoLRLiQw,1288 -cherrypy/tutorial/tut09_files.py,sha256=qcelN09_k62zWVC-daId5I4i-fw6TWA4WddUbG4j8B8,3463 -cherrypy/tutorial/tut10_http_errors.py,sha256=6GllO8SI-6Xs6R8hRwHe7jUzGxYJxLlxwxgaOJC9i8Y,2706 -cherrypy/tutorial/tutorial.conf,sha256=9ENgfRDyopHuignr_aHeMaWoC562xThbmlgF6zg4oEE,96 diff --git a/lib/CherryPy-18.8.0.dist-info/REQUESTED b/lib/CherryPy-18.8.0.dist-info/REQUESTED deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/CherryPy-18.8.0.dist-info/WHEEL b/lib/CherryPy-18.8.0.dist-info/WHEEL deleted file mode 100644 index 0b18a281..00000000 --- a/lib/CherryPy-18.8.0.dist-info/WHEEL +++ /dev/null @@ -1,6 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.37.1) -Root-Is-Purelib: true -Tag: py2-none-any -Tag: py3-none-any - diff --git a/lib/CherryPy-18.8.0.dist-info/entry_points.txt b/lib/CherryPy-18.8.0.dist-info/entry_points.txt deleted file mode 100644 index 60ca207b..00000000 --- a/lib/CherryPy-18.8.0.dist-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -cherryd = cherrypy.__main__:run diff --git a/lib/CherryPy-18.8.0.dist-info/top_level.txt b/lib/CherryPy-18.8.0.dist-info/top_level.txt deleted file mode 100644 index d7187069..00000000 --- a/lib/CherryPy-18.8.0.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -cherrypy diff --git a/lib/CherrypyScheduler-0.1-py3.11.egg-info/PKG-INFO b/lib/CherrypyScheduler-0.1-py3.11.egg-info/PKG-INFO deleted file mode 100644 index 8034bc42..00000000 --- a/lib/CherrypyScheduler-0.1-py3.11.egg-info/PKG-INFO +++ /dev/null @@ -1,112 +0,0 @@ -Metadata-Version: 2.1 -Name: CherrypyScheduler -Version: 0.1 -Summary: Repeating task scheduler for Cherrypy webserver! -Home-page: https://github.com/sawyersteven/CherrypyScheduler -Author: SawyerSteven -Author-email: sawyerstevenk@gmail.com -Classifier: Programming Language :: Python 3 -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Description-Content-Type: text/x-rst -License-File: LICENSE - - - -.. image:: https://raw.githubusercontent.com/sawyersteven/CherrypyScheduler/master/img/Logo_wide.png - :target: https://raw.githubusercontent.com/sawyersteven/CherrypyScheduler/master/img/Logo_wide.png - :alt: Logo - - -CherrypyScheduler -================= - -CherrypyScheduler creates a simple interface for creating repeating events on a `Cherrypy `_ webserver. - -CherrypyScheduler is not associated with the Cherrypy development team. - -Installation -^^^^^^^^^^^^ - -CherrypyScheduler has been tested on Python 3.4.0 and is not guaranteed to run at all on lower versions. - -Install via pip with ``pip install CherrypyScheduler`` - -Usage -^^^^^ - -CherrypyScheduler operates as a SimplePlugin in Cherrypy and is easy to integrate. - -.. code-block:: - - import cherrypy - from cherrypyscheduler import SchedulerPlugin - - import datetime - - - class ServerRoot(object): - - tasks_completed = 0 - - @cherrypy.expose - def index(self): - return 'Hello World!' - - - SchedulerPlugin(cherrypy.engine).subscribe() - - cherrypy.quickstart(ServerRoot(), '/') - - - -At this point the scheduler plugin is running and listening for signals, but doesn't have any tasks to run. Let's give it a job and a way to see what is happening: - -.. code-block:: - - import cherrypy - from cherrypyscheduler import SchedulerPlugin - - - class ServerRoot(object): - - tasks_completed = 0 - - @cherrypy.expose - def index(self): - return 'We\'ve executed {} tasks!'.format(self.tasks_completed) - - - def my_scheduled_task(): - ServerRoot.tasks_completed += 1 - - - SchedulerPlugin(cherrypy.engine).subscribe() - - SchedulerPlugin.ScheduledTask(0, 0, 30, my_scheduled_task) - - cherrypy.quickstart(ServerRoot(), '/') - - - -This tells the plugin to run ``my_scheduled_task``. The start time is ``0:00`` (midnight) and the task will be called every ``30`` seconds. The scheduler will call ``my_scheduled_task`` at the next possible 30 second interval from midnight. - -Open ``localhost:8080`` in your browser and you'll see the counter increase every 30 seconds. - -Many more methods are available for interacting with the scheduler and scheduled tasks. See the `wiki `_ for more information. - -How it Works -^^^^^^^^^^^^ - -CherrypyScheduler attempts to be accurate to +- 1 second. Typically a task is called within several thousandths of a second of its target time. Due to the nature of Python and the garbage collector you should never expect a high level of precision. - -Tasks are called using a threading.Timer, so the main loop of the Cherrypy server is completely unaffected. Perhaps the largest side-effect of this is that exceptions will cause only the Timer thread to fail, so it is suggested to take advantage of good logging practices to catch anything that might not be working as expected. Timers will restart whether or not the task raises an exception. This allows tasks that rely on external input to fail without preventing future tasks from starting. - -A record is kept for each task indicating the time-stamp of the last time the task was executed. This prevents tasks from being skipped if their start time is set to the start time of the server but the server has reset before their next execution would have been called. - -Much more information is available in the `wiki `_. - -Testing -^^^^^^^ - -Tests may be run with ``python3 tests/test.py``. Due to the nature of a scheduler, tests make take several minutes to complete as we wait for the scheduler to call the task function. Tests require Cherrypy and Cheroot. diff --git a/lib/CherrypyScheduler-0.1-py3.11.egg-info/SOURCES.txt b/lib/CherrypyScheduler-0.1-py3.11.egg-info/SOURCES.txt deleted file mode 100644 index 6af3b239..00000000 --- a/lib/CherrypyScheduler-0.1-py3.11.egg-info/SOURCES.txt +++ /dev/null @@ -1,7 +0,0 @@ -LICENSE -README.md -setup.py -CherrypyScheduler.egg-info/PKG-INFO -CherrypyScheduler.egg-info/SOURCES.txt -CherrypyScheduler.egg-info/dependency_links.txt -CherrypyScheduler.egg-info/top_level.txt \ No newline at end of file diff --git a/lib/CherrypyScheduler-0.1-py3.11.egg-info/dependency_links.txt b/lib/CherrypyScheduler-0.1-py3.11.egg-info/dependency_links.txt deleted file mode 100644 index 8b137891..00000000 --- a/lib/CherrypyScheduler-0.1-py3.11.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/CherrypyScheduler-0.1-py3.11.egg-info/installed-files.txt b/lib/CherrypyScheduler-0.1-py3.11.egg-info/installed-files.txt deleted file mode 100644 index be300dfc..00000000 --- a/lib/CherrypyScheduler-0.1-py3.11.egg-info/installed-files.txt +++ /dev/null @@ -1,4 +0,0 @@ -PKG-INFO -SOURCES.txt -dependency_links.txt -top_level.txt diff --git a/lib/CherrypyScheduler-0.1-py3.11.egg-info/top_level.txt b/lib/CherrypyScheduler-0.1-py3.11.egg-info/top_level.txt deleted file mode 100644 index 8b137891..00000000 --- a/lib/CherrypyScheduler-0.1-py3.11.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/Mako-1.2.4.dist-info/INSTALLER b/lib/Mako-1.2.4.dist-info/INSTALLER deleted file mode 100644 index a1b589e3..00000000 --- a/lib/Mako-1.2.4.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/lib/Mako-1.2.4.dist-info/LICENSE b/lib/Mako-1.2.4.dist-info/LICENSE deleted file mode 100644 index be84a38e..00000000 --- a/lib/Mako-1.2.4.dist-info/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright 2006-2022 the Mako authors and contributors . - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/lib/Mako-1.2.4.dist-info/METADATA b/lib/Mako-1.2.4.dist-info/METADATA deleted file mode 100644 index 33a75808..00000000 --- a/lib/Mako-1.2.4.dist-info/METADATA +++ /dev/null @@ -1,87 +0,0 @@ -Metadata-Version: 2.1 -Name: Mako -Version: 1.2.4 -Summary: A super-fast templating language that borrows the best ideas from the existing templating languages. -Home-page: https://www.makotemplates.org/ -Author: Mike Bayer -Author-email: mike@zzzcomputing.com -License: MIT -Project-URL: Documentation, https://docs.makotemplates.org -Project-URL: Issue Tracker, https://github.com/sqlalchemy/mako -Classifier: Development Status :: 5 - Production/Stable -Classifier: License :: OSI Approved :: MIT License -Classifier: Environment :: Web Environment -Classifier: Intended Audience :: Developers -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content -Requires-Python: >=3.7 -Description-Content-Type: text/x-rst -License-File: LICENSE -Requires-Dist: MarkupSafe (>=0.9.2) -Requires-Dist: importlib-metadata ; python_version < "3.8" -Provides-Extra: babel -Requires-Dist: Babel ; extra == 'babel' -Provides-Extra: lingua -Requires-Dist: lingua ; extra == 'lingua' -Provides-Extra: testing -Requires-Dist: pytest ; extra == 'testing' - -========================= -Mako Templates for Python -========================= - -Mako is a template library written in Python. It provides a familiar, non-XML -syntax which compiles into Python modules for maximum performance. Mako's -syntax and API borrows from the best ideas of many others, including Django -templates, Cheetah, Myghty, and Genshi. Conceptually, Mako is an embedded -Python (i.e. Python Server Page) language, which refines the familiar ideas -of componentized layout and inheritance to produce one of the most -straightforward and flexible models available, while also maintaining close -ties to Python calling and scoping semantics. - -Nutshell -======== - -:: - - <%inherit file="base.html"/> - <% - rows = [[v for v in range(0,10)] for row in range(0,10)] - %> - - % for row in rows: - ${makerow(row)} - % endfor -
- - <%def name="makerow(row)"> - - % for name in row: - ${name}\ - % endfor - - - -Philosophy -=========== - -Python is a great scripting language. Don't reinvent the wheel...your templates can handle it ! - -Documentation -============== - -See documentation for Mako at https://docs.makotemplates.org/en/latest/ - -License -======== - -Mako is licensed under an MIT-style license (see LICENSE). -Other incorporated projects may be licensed under different licenses. -All licenses allow for non-commercial and commercial use. diff --git a/lib/Mako-1.2.4.dist-info/RECORD b/lib/Mako-1.2.4.dist-info/RECORD deleted file mode 100644 index edc730c6..00000000 --- a/lib/Mako-1.2.4.dist-info/RECORD +++ /dev/null @@ -1,75 +0,0 @@ -../../bin/mako-render.exe,sha256=F5mMh2_NzUtswHQlgeF6kCl7rgWbRlRtnvMvXJQDfEM,108418 -Mako-1.2.4.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -Mako-1.2.4.dist-info/LICENSE,sha256=dg8is-nqSlDrmSAb2N0RiGnygQjPtkzM5tGzBc-a6fo,1098 -Mako-1.2.4.dist-info/METADATA,sha256=MlPkZcQ5bASEMtzkRaH8aRSQE6gmLH3KTnASUawz6eA,2909 -Mako-1.2.4.dist-info/RECORD,, -Mako-1.2.4.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -Mako-1.2.4.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 -Mako-1.2.4.dist-info/entry_points.txt,sha256=LsKkUsOsJQYbJ2M72hZCm968wi5K8Ywb5uFxCuN8Obk,512 -Mako-1.2.4.dist-info/top_level.txt,sha256=LItdH8cDPetpUu8rUyBG3DObS6h9Gcpr9j_WLj2S-R0,5 -mako/__init__.py,sha256=R1cQoVGhYA-fl43kNSPKm6kzdJOs28e8sq8WYMHctMQ,242 -mako/__pycache__/__init__.cpython-311.pyc,, -mako/__pycache__/_ast_util.cpython-311.pyc,, -mako/__pycache__/ast.cpython-311.pyc,, -mako/__pycache__/cache.cpython-311.pyc,, -mako/__pycache__/cmd.cpython-311.pyc,, -mako/__pycache__/codegen.cpython-311.pyc,, -mako/__pycache__/compat.cpython-311.pyc,, -mako/__pycache__/exceptions.cpython-311.pyc,, -mako/__pycache__/filters.cpython-311.pyc,, -mako/__pycache__/lexer.cpython-311.pyc,, -mako/__pycache__/lookup.cpython-311.pyc,, -mako/__pycache__/parsetree.cpython-311.pyc,, -mako/__pycache__/pygen.cpython-311.pyc,, -mako/__pycache__/pyparser.cpython-311.pyc,, -mako/__pycache__/runtime.cpython-311.pyc,, -mako/__pycache__/template.cpython-311.pyc,, -mako/__pycache__/util.cpython-311.pyc,, -mako/_ast_util.py,sha256=BcwJLuE4E-aiFXi_fanO378Cn3Ou03bJxc6Incjse4Y,20247 -mako/ast.py,sha256=h07xBpz2l19RSwpejrhkhgB4r5efpwGmsYOy_L8xvUc,6642 -mako/cache.py,sha256=jkspun9tLgu0IVKSmo_fkL_DAbSTl2P5a5zkMBkjZvk,7680 -mako/cmd.py,sha256=vQg9ip89KMsuZEGamCRAPg7UyDNlpMmnG3XHDNLHS5o,2814 -mako/codegen.py,sha256=h1z8DGLkB92nbUz2OZGVmUKqPr9kVNbnNL8KnLizYAk,47309 -mako/compat.py,sha256=Sa3Rzrjl44xo25nXUHbhfIrEoMgceq5-Ohl0FO6cCHk,1913 -mako/exceptions.py,sha256=xQZKYdb-4d8rcrNFsFzjGSEuNG4upFqGNPErtSCDqfI,12530 -mako/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -mako/ext/__pycache__/__init__.cpython-311.pyc,, -mako/ext/__pycache__/autohandler.cpython-311.pyc,, -mako/ext/__pycache__/babelplugin.cpython-311.pyc,, -mako/ext/__pycache__/beaker_cache.cpython-311.pyc,, -mako/ext/__pycache__/extract.cpython-311.pyc,, -mako/ext/__pycache__/linguaplugin.cpython-311.pyc,, -mako/ext/__pycache__/preprocessors.cpython-311.pyc,, -mako/ext/__pycache__/pygmentplugin.cpython-311.pyc,, -mako/ext/__pycache__/turbogears.cpython-311.pyc,, -mako/ext/autohandler.py,sha256=-hNv4VHbQplLGDt5e4mFsBC-QpfWMjKokOe0axDP308,1885 -mako/ext/babelplugin.py,sha256=s6ZIAh1hUhsJIiF3j4soVHrFN_1cRJ_e3sEbz7ein7k,2091 -mako/ext/beaker_cache.py,sha256=D6gh_ke7QOKiSJtq9v67RvmqCRMDJx-IwTcd-NDjKvk,2578 -mako/ext/extract.py,sha256=EhXglj2eW5u80T3xWWB7jMgL8oNDfAQaD5E5IRiL9N0,4659 -mako/ext/linguaplugin.py,sha256=iLip2gZ0ya5pooHrxwZrP8VFQfJidXmgPZ5h1j30Kow,1935 -mako/ext/preprocessors.py,sha256=pEUbmfSO2zb4DuCt_-_oYnWypWiXs4MnJHxjTMiks5A,576 -mako/ext/pygmentplugin.py,sha256=GuOd93TjetzpTfW5oUEtuPS7jKDHgJIH3Faiaq76S0c,4753 -mako/ext/turbogears.py,sha256=mxFDF59NFK6cm__3qwGjZ1VAW0qdjJWNj23l6dcwqEg,2141 -mako/filters.py,sha256=rlHJ2L5RFr5Gf-MyOJKZI7TSJpM5oBXH58niJWCp2-4,4658 -mako/lexer.py,sha256=GOHNLeSlTIEa_yV8W5Qr27SjaPlJcO0Kij7Z2rpUkCA,15982 -mako/lookup.py,sha256=_2VPSA2CgCiT0Vd9GnSIjyY5wlpXiB2C5luXJP7gym8,12429 -mako/parsetree.py,sha256=pXbZP0orsT3iBIgWa9yD1TEfvytsCaXu2Ttws8RTMGM,19007 -mako/pygen.py,sha256=K-l_hsvXfWdMTunfHyVxvA5EG4Uzr4Qaw6IUc3hw8zI,10416 -mako/pyparser.py,sha256=diSXgo-ZwdZxbRsNZ1DmARQKVnlOFc6Qgx9Dc3wZB_U,7032 -mako/runtime.py,sha256=MwO5T1rGy0yLeJiFh2hh5cO_kfd5_9fJq_vfBzLFe_0,27806 -mako/template.py,sha256=gEhMPjKZ1Q_sYWWg6PLnRX-KBeTF0kBnyRZimlmgQks,23858 -mako/testing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -mako/testing/__pycache__/__init__.cpython-311.pyc,, -mako/testing/__pycache__/_config.cpython-311.pyc,, -mako/testing/__pycache__/assertions.cpython-311.pyc,, -mako/testing/__pycache__/config.cpython-311.pyc,, -mako/testing/__pycache__/exclusions.cpython-311.pyc,, -mako/testing/__pycache__/fixtures.cpython-311.pyc,, -mako/testing/__pycache__/helpers.cpython-311.pyc,, -mako/testing/_config.py,sha256=k-qpnsnbXUoN-ykMN5BRpg84i1x0p6UsAddKQnrIytU,3566 -mako/testing/assertions.py,sha256=XnYDPSnDFiEX9eO95OZ5LndZrUpJ6_xGofe6qDzJxqU,5162 -mako/testing/config.py,sha256=wmYVZfzGvOK3mJUZpzmgO8-iIgvaCH41Woi4yDpxq6E,323 -mako/testing/exclusions.py,sha256=_t6ADKdatk3f18tOfHV_ZY6u_ZwQsKphZ2MXJVSAOcI,1553 -mako/testing/fixtures.py,sha256=nEp7wTusf7E0n3Q-BHJW2s_t1vx0KB9poadQ1BmIJzE,3044 -mako/testing/helpers.py,sha256=kTaIg8OL1uvcuLptbRA_aJtGndIDDaxAzacYbv_Km1Q,1521 -mako/util.py,sha256=XmYQmq6WfMAt-BPM7zhT9lybEqHVIWCM9wF1ukzqpew,10638 diff --git a/lib/Mako-1.2.4.dist-info/REQUESTED b/lib/Mako-1.2.4.dist-info/REQUESTED deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/Mako-1.2.4.dist-info/WHEEL b/lib/Mako-1.2.4.dist-info/WHEEL deleted file mode 100644 index 57e3d840..00000000 --- a/lib/Mako-1.2.4.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.38.4) -Root-Is-Purelib: true -Tag: py3-none-any - diff --git a/lib/Mako-1.2.4.dist-info/entry_points.txt b/lib/Mako-1.2.4.dist-info/entry_points.txt deleted file mode 100644 index 30f31b2b..00000000 --- a/lib/Mako-1.2.4.dist-info/entry_points.txt +++ /dev/null @@ -1,18 +0,0 @@ -[babel.extractors] -mako = mako.ext.babelplugin:extract [babel] - -[console_scripts] -mako-render = mako.cmd:cmdline - -[lingua.extractors] -mako = mako.ext.linguaplugin:LinguaMakoExtractor [lingua] - -[pygments.lexers] -css+mako = mako.ext.pygmentplugin:MakoCssLexer -html+mako = mako.ext.pygmentplugin:MakoHtmlLexer -js+mako = mako.ext.pygmentplugin:MakoJavascriptLexer -mako = mako.ext.pygmentplugin:MakoLexer -xml+mako = mako.ext.pygmentplugin:MakoXmlLexer - -[python.templating.engines] -mako = mako.ext.turbogears:TGPlugin diff --git a/lib/Mako-1.2.4.dist-info/top_level.txt b/lib/Mako-1.2.4.dist-info/top_level.txt deleted file mode 100644 index 2951cdd4..00000000 --- a/lib/Mako-1.2.4.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -mako diff --git a/lib/MarkupSafe-2.1.1.dist-info/INSTALLER b/lib/MarkupSafe-2.1.1.dist-info/INSTALLER deleted file mode 100644 index a1b589e3..00000000 --- a/lib/MarkupSafe-2.1.1.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/lib/MarkupSafe-2.1.1.dist-info/LICENSE.rst b/lib/MarkupSafe-2.1.1.dist-info/LICENSE.rst deleted file mode 100644 index 9d227a0c..00000000 --- a/lib/MarkupSafe-2.1.1.dist-info/LICENSE.rst +++ /dev/null @@ -1,28 +0,0 @@ -Copyright 2010 Pallets - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/MarkupSafe-2.1.1.dist-info/METADATA b/lib/MarkupSafe-2.1.1.dist-info/METADATA deleted file mode 100644 index 485a5e06..00000000 --- a/lib/MarkupSafe-2.1.1.dist-info/METADATA +++ /dev/null @@ -1,101 +0,0 @@ -Metadata-Version: 2.1 -Name: MarkupSafe -Version: 2.1.1 -Summary: Safely add untrusted strings to HTML/XML markup. -Home-page: https://palletsprojects.com/p/markupsafe/ -Author: Armin Ronacher -Author-email: armin.ronacher@active-4.com -Maintainer: Pallets -Maintainer-email: contact@palletsprojects.com -License: BSD-3-Clause -Project-URL: Donate, https://palletsprojects.com/donate -Project-URL: Documentation, https://markupsafe.palletsprojects.com/ -Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/ -Project-URL: Source Code, https://github.com/pallets/markupsafe/ -Project-URL: Issue Tracker, https://github.com/pallets/markupsafe/issues/ -Project-URL: Twitter, https://twitter.com/PalletsTeam -Project-URL: Chat, https://discord.gg/pallets -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Web Environment -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: BSD License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content -Classifier: Topic :: Text Processing :: Markup :: HTML -Requires-Python: >=3.7 -Description-Content-Type: text/x-rst -License-File: LICENSE.rst - -MarkupSafe -========== - -MarkupSafe implements a text object that escapes characters so it is -safe to use in HTML and XML. Characters that have special meanings are -replaced so that they display as the actual characters. This mitigates -injection attacks, meaning untrusted user input can safely be displayed -on a page. - - -Installing ----------- - -Install and update using `pip`_: - -.. code-block:: text - - pip install -U MarkupSafe - -.. _pip: https://pip.pypa.io/en/stable/getting-started/ - - -Examples --------- - -.. code-block:: pycon - - >>> from markupsafe import Markup, escape - - >>> # escape replaces special characters and wraps in Markup - >>> escape("") - Markup('<script>alert(document.cookie);</script>') - - >>> # wrap in Markup to mark text "safe" and prevent escaping - >>> Markup("Hello") - Markup('hello') - - >>> escape(Markup("Hello")) - Markup('hello') - - >>> # Markup is a str subclass - >>> # methods and operators escape their arguments - >>> template = Markup("Hello {name}") - >>> template.format(name='"World"') - Markup('Hello "World"') - - -Donate ------- - -The Pallets organization develops and supports MarkupSafe and other -popular packages. In order to grow the community of contributors and -users, and allow the maintainers to devote more time to the projects, -`please donate today`_. - -.. _please donate today: https://palletsprojects.com/donate - - -Links ------ - -- Documentation: https://markupsafe.palletsprojects.com/ -- Changes: https://markupsafe.palletsprojects.com/changes/ -- PyPI Releases: https://pypi.org/project/MarkupSafe/ -- Source Code: https://github.com/pallets/markupsafe/ -- Issue Tracker: https://github.com/pallets/markupsafe/issues/ -- Website: https://palletsprojects.com/p/markupsafe/ -- Twitter: https://twitter.com/PalletsTeam -- Chat: https://discord.gg/pallets - - diff --git a/lib/MarkupSafe-2.1.1.dist-info/RECORD b/lib/MarkupSafe-2.1.1.dist-info/RECORD deleted file mode 100644 index d78a6361..00000000 --- a/lib/MarkupSafe-2.1.1.dist-info/RECORD +++ /dev/null @@ -1,14 +0,0 @@ -MarkupSafe-2.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -MarkupSafe-2.1.1.dist-info/LICENSE.rst,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 -MarkupSafe-2.1.1.dist-info/METADATA,sha256=4AyAYS_nz93r65Zl5mKaxgrnXYmdzS8qRTgAOo3zkeI,3343 -MarkupSafe-2.1.1.dist-info/RECORD,, -MarkupSafe-2.1.1.dist-info/WHEEL,sha256=wklNeoByNLhdCl-oEQTdaHIeDl4q9zaQVqAlPxUEgLU,102 -MarkupSafe-2.1.1.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11 -markupsafe/__init__.py,sha256=xfaUQkKNRTdYWe6HnnJ2HjguFmS-C_0H6g8-Q9VAfkQ,9284 -markupsafe/__pycache__/__init__.cpython-311.pyc,, -markupsafe/__pycache__/_native.cpython-311.pyc,, -markupsafe/_native.py,sha256=GR86Qvo_GcgKmKreA1WmYN9ud17OFwkww8E-fiW-57s,1713 -markupsafe/_speedups.c,sha256=X2XvQVtIdcK4Usz70BvkzoOfjTCmQlDkkjYSn-swE0g,7083 -markupsafe/_speedups.cp311-win_amd64.pyd,sha256=dMgq-tu-omm--8IBWdKn2t--Gta_BmX9-EzOuWpI1YQ,15872 -markupsafe/_speedups.pyi,sha256=vfMCsOgbAXRNLUXkyuyonG8uEWKYU4PDqNuMaDELAYw,229 -markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/lib/MarkupSafe-2.1.1.dist-info/WHEEL b/lib/MarkupSafe-2.1.1.dist-info/WHEEL deleted file mode 100644 index 7d9ab249..00000000 --- a/lib/MarkupSafe-2.1.1.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.38.4) -Root-Is-Purelib: false -Tag: cp311-cp311-win_amd64 - diff --git a/lib/MarkupSafe-2.1.1.dist-info/top_level.txt b/lib/MarkupSafe-2.1.1.dist-info/top_level.txt deleted file mode 100644 index 75bf7292..00000000 --- a/lib/MarkupSafe-2.1.1.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -markupsafe diff --git a/lib/PTN/__init__.py b/lib/PTN/__init__.py deleted file mode 100644 index e4424a23..00000000 --- a/lib/PTN/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -from .parse import PTN - -__author__ = 'Divij Bindlish' -__email__ = 'dvjbndlsh93@gmail.com' -__version__ = '1.1.1' -__license__ = 'MIT' - -ptn = PTN() - - -def parse(name): - return ptn.parse(name) diff --git a/lib/PTN/parse.py b/lib/PTN/parse.py deleted file mode 100644 index 688dbe38..00000000 --- a/lib/PTN/parse.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -import re -from .patterns import patterns, types - - -class PTN(object): - def _escape_regex(self, string): - return re.sub('[\-\[\]{}()*+?.,\\\^$|#\s]', '\\$&', string) - - def __init__(self): - self.torrent = None - self.excess_raw = None - self.group_raw = None - self.start = None - self.end = None - self.title_raw = None - self.parts = None - - def _part(self, name, match, raw, clean): - # The main core instructuions - self.parts[name] = clean - - if len(match) != 0: - # The instructions for extracting title - index = self.torrent['name'].find(match[0]) - if index == 0: - self.start = len(match[0]) - elif self.end is None or index < self.end: - self.end = index - - if name != 'excess': - # The instructions for adding excess - if name == 'group': - self.group_raw = raw - if raw is not None: - self.excess_raw = self.excess_raw.replace(raw, '') - - def _late(self, name, clean): - if name == 'group': - self._part(name, [], None, clean) - elif name == 'episodeName': - clean = re.sub('[\._]', ' ', clean) - clean = re.sub('_+$', '', clean) - self._part(name, [], None, clean.strip()) - - def parse(self, name): - self.parts = {} - self.torrent = {'name': name} - self.excess_raw = name - self.group_raw = '' - self.start = 0 - self.end = None - self.title_raw = None - - for key, pattern in patterns: - if key not in ('season', 'episode', 'website'): - pattern = r'\b%s\b' % pattern - - clean_name = re.sub('_', ' ', self.torrent['name']) - match = re.findall(pattern, clean_name, re.I) - if len(match) == 0: - continue - - index = {} - if isinstance(match[0], tuple): - match = list(match[0]) - if len(match) > 1: - index['raw'] = 0 - index['clean'] = 1 - else: - index['raw'] = 0 - index['clean'] = 0 - - if key in types.keys() and types[key] == 'boolean': - clean = True - else: - clean = match[index['clean']] - if key in types.keys() and types[key] == 'integer': - clean = int(clean) - if key == 'group': - if re.search(patterns[5][1], clean, re.I) \ - or re.search(patterns[4][1], clean): - continue # Codec and quality. - if re.match('[^ ]+ [^ ]+ .+', clean): - key = 'episodeName' - if key == 'episode': - sub_pattern = self._escape_regex(match[index['raw']]) - self.torrent['map'] = re.sub( - sub_pattern, '{episode}', self.torrent['name'] - ) - self._part(key, match, match[index['raw']], clean) - - # Start process for title - raw = self.torrent['name'] - if self.end is not None: - raw = raw[self.start:self.end].split('(')[0] - - clean = re.sub('^ -', '', raw) - if clean.find(' ') == -1 and clean.find('.') != -1: - clean = re.sub('\.', ' ', clean) - clean = re.sub('_', ' ', clean) - clean = re.sub('([\[\(_]|- )$', '', clean).strip() - - self._part('title', [], raw, clean) - - # Start process for end - clean = re.sub('(^[-\. ()]+)|([-\. ]+$)', '', self.excess_raw) - clean = re.sub('[\(\)\/]', ' ', clean) - match = re.split('\.\.+| +', clean) - if len(match) > 0 and isinstance(match[0], tuple): - match = list(match[0]) - - clean = filter(bool, match) - clean = [item for item in filter(lambda a: a != '-', clean)] - clean = [item.strip('-') for item in clean] - if len(clean) != 0: - group_pattern = clean[-1] + self.group_raw - if self.torrent['name'].find(group_pattern) == \ - len(self.torrent['name']) - len(group_pattern): - self._late('group', clean.pop() + self.group_raw) - - if 'map' in self.torrent.keys() and len(clean) != 0: - episode_name_pattern = ( - '{episode}' - '' + re.sub('_+$', '', clean[0]) - ) - if self.torrent['map'].find(episode_name_pattern) != -1: - self._late('episodeName', clean.pop(0)) - - if len(clean) != 0: - if len(clean) == 1: - clean = clean[0] - self._part('excess', [], self.excess_raw, clean) - return self.parts diff --git a/lib/PTN/patterns.py b/lib/PTN/patterns.py deleted file mode 100644 index 05261e30..00000000 --- a/lib/PTN/patterns.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -patterns = [ - ('season', '(s?([0-9]{1,2}))[ex]'), - ('episode', '([ex]([0-9]{2})(?:[^0-9]|$))'), - ('year', '([\[\(]?((?:19[0-9]|20[01])[0-9])[\]\)]?)'), - ('resolution', '([0-9]{3,4}p)'), - ('quality', ('((?:PPV\.)?[HP]DTV|(?:HD)?CAM|B[DR]Rip|(?:HD-?)?TS|' - '(?:PPV )?WEB-?DL(?: DVDRip)?|HDRip|DVDRip|DVDRIP|' - 'CamRip|W[EB]BRip|BluRay|DvDScr|hdtv|telesync)')), - ('codec', '(xvid|[hx]\.?26[45])'), - ('audio', ('(MP3|DD5\.?1|Dual[\- ]Audio|LiNE|DTS|' - 'AAC[.-]LC|AAC(?:\.?2\.0)?|' - 'AC3(?:\.5\.1)?)')), - ('group', '(- ?([^-]+(?:-={[^-]+-?$)?))$'), - ('region', 'R[0-9]'), - ('extended', '(EXTENDED(:?.CUT)?)'), - ('hardcoded', 'HC'), - ('proper', 'PROPER'), - ('repack', 'REPACK'), - ('container', '(MKV|AVI|MP4)'), - ('widescreen', 'WS'), - ('website', '^(\[ ?([^\]]+?) ?\])'), - ('language', '(rus\.eng|ita\.eng)'), - ('sbs', '(?:Half-)?SBS'), - ('unrated', 'UNRATED'), - ('size', '(\d+(?:\.\d+)?(?:GB|MB))'), - ('3d', '3D') -] - -types = { - 'season': 'integer', - 'episode': 'integer', - 'year': 'integer', - 'extended': 'boolean', - 'hardcoded': 'boolean', - 'proper': 'boolean', - 'repack': 'boolean', - 'widescreen': 'boolean', - 'unrated': 'boolean', - '3d': 'boolean' -} diff --git a/lib/SQLAlchemy-1.4.46.dist-info/INSTALLER b/lib/SQLAlchemy-1.4.46.dist-info/INSTALLER deleted file mode 100644 index a1b589e3..00000000 --- a/lib/SQLAlchemy-1.4.46.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/lib/SQLAlchemy-1.4.46.dist-info/LICENSE b/lib/SQLAlchemy-1.4.46.dist-info/LICENSE deleted file mode 100644 index 7bf9bbe9..00000000 --- a/lib/SQLAlchemy-1.4.46.dist-info/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright 2005-2023 SQLAlchemy authors and contributors . - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/lib/SQLAlchemy-1.4.46.dist-info/METADATA b/lib/SQLAlchemy-1.4.46.dist-info/METADATA deleted file mode 100644 index dc6af2d3..00000000 --- a/lib/SQLAlchemy-1.4.46.dist-info/METADATA +++ /dev/null @@ -1,238 +0,0 @@ -Metadata-Version: 2.1 -Name: SQLAlchemy -Version: 1.4.46 -Summary: Database Abstraction Library -Home-page: https://www.sqlalchemy.org -Author: Mike Bayer -Author-email: mike_mp@zzzcomputing.com -License: MIT -Project-URL: Documentation, https://docs.sqlalchemy.org -Project-URL: Issue Tracker, https://github.com/sqlalchemy/sqlalchemy/ -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: MIT License -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Database :: Front-Ends -Requires-Python: !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7 -Description-Content-Type: text/x-rst -License-File: LICENSE -Requires-Dist: importlib-metadata ; python_version < "3.8" -Requires-Dist: greenlet (!=0.4.17) ; python_version >= "3" and (platform_machine == "aarch64" or (platform_machine == "ppc64le" or (platform_machine == "x86_64" or (platform_machine == "amd64" or (platform_machine == "AMD64" or (platform_machine == "win32" or platform_machine == "WIN32")))))) -Provides-Extra: aiomysql -Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'aiomysql' -Requires-Dist: aiomysql ; (python_version >= "3") and extra == 'aiomysql' -Provides-Extra: aiosqlite -Requires-Dist: typing-extensions (!=3.10.0.1) ; extra == 'aiosqlite' -Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'aiosqlite' -Requires-Dist: aiosqlite ; (python_version >= "3") and extra == 'aiosqlite' -Provides-Extra: asyncio -Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'asyncio' -Provides-Extra: asyncmy -Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'asyncmy' -Requires-Dist: asyncmy (!=0.2.4,>=0.2.3) ; (python_version >= "3") and extra == 'asyncmy' -Provides-Extra: mariadb_connector -Requires-Dist: mariadb (!=1.1.2,>=1.0.1) ; (python_version >= "3") and extra == 'mariadb_connector' -Provides-Extra: mssql -Requires-Dist: pyodbc ; extra == 'mssql' -Provides-Extra: mssql_pymssql -Requires-Dist: pymssql ; extra == 'mssql_pymssql' -Provides-Extra: mssql_pyodbc -Requires-Dist: pyodbc ; extra == 'mssql_pyodbc' -Provides-Extra: mypy -Requires-Dist: sqlalchemy2-stubs ; extra == 'mypy' -Requires-Dist: mypy (>=0.910) ; (python_version >= "3") and extra == 'mypy' -Provides-Extra: mysql -Requires-Dist: mysqlclient (<2,>=1.4.0) ; (python_version < "3") and extra == 'mysql' -Requires-Dist: mysqlclient (>=1.4.0) ; (python_version >= "3") and extra == 'mysql' -Provides-Extra: mysql_connector -Requires-Dist: mysql-connector-python ; extra == 'mysql_connector' -Provides-Extra: oracle -Requires-Dist: cx-oracle (<8,>=7) ; (python_version < "3") and extra == 'oracle' -Requires-Dist: cx-oracle (>=7) ; (python_version >= "3") and extra == 'oracle' -Provides-Extra: postgresql -Requires-Dist: psycopg2 (>=2.7) ; extra == 'postgresql' -Provides-Extra: postgresql_asyncpg -Requires-Dist: greenlet (!=0.4.17) ; (python_version >= "3") and extra == 'postgresql_asyncpg' -Requires-Dist: asyncpg ; (python_version >= "3") and extra == 'postgresql_asyncpg' -Provides-Extra: postgresql_pg8000 -Requires-Dist: pg8000 (!=1.29.0,>=1.16.6) ; extra == 'postgresql_pg8000' -Provides-Extra: postgresql_psycopg2binary -Requires-Dist: psycopg2-binary ; extra == 'postgresql_psycopg2binary' -Provides-Extra: postgresql_psycopg2cffi -Requires-Dist: psycopg2cffi ; extra == 'postgresql_psycopg2cffi' -Provides-Extra: pymysql -Requires-Dist: pymysql (<1) ; (python_version < "3") and extra == 'pymysql' -Requires-Dist: pymysql ; (python_version >= "3") and extra == 'pymysql' -Provides-Extra: sqlcipher -Requires-Dist: sqlcipher3-binary ; (python_version >= "3") and extra == 'sqlcipher' - -SQLAlchemy -========== - -|PyPI| |Python| |Downloads| - -.. |PyPI| image:: https://img.shields.io/pypi/v/sqlalchemy - :target: https://pypi.org/project/sqlalchemy - :alt: PyPI - -.. |Python| image:: https://img.shields.io/pypi/pyversions/sqlalchemy - :target: https://pypi.org/project/sqlalchemy - :alt: PyPI - Python Version - -.. |Downloads| image:: https://img.shields.io/pypi/dm/sqlalchemy - :target: https://pypi.org/project/sqlalchemy - :alt: PyPI - Downloads - - -The Python SQL Toolkit and Object Relational Mapper - -Introduction -------------- - -SQLAlchemy is the Python SQL toolkit and Object Relational Mapper -that gives application developers the full power and -flexibility of SQL. SQLAlchemy provides a full suite -of well known enterprise-level persistence patterns, -designed for efficient and high-performing database -access, adapted into a simple and Pythonic domain -language. - -Major SQLAlchemy features include: - -* An industrial strength ORM, built - from the core on the identity map, unit of work, - and data mapper patterns. These patterns - allow transparent persistence of objects - using a declarative configuration system. - Domain models - can be constructed and manipulated naturally, - and changes are synchronized with the - current transaction automatically. -* A relationally-oriented query system, exposing - the full range of SQL's capabilities - explicitly, including joins, subqueries, - correlation, and most everything else, - in terms of the object model. - Writing queries with the ORM uses the same - techniques of relational composition you use - when writing SQL. While you can drop into - literal SQL at any time, it's virtually never - needed. -* A comprehensive and flexible system - of eager loading for related collections and objects. - Collections are cached within a session, - and can be loaded on individual access, all - at once using joins, or by query per collection - across the full result set. -* A Core SQL construction system and DBAPI - interaction layer. The SQLAlchemy Core is - separate from the ORM and is a full database - abstraction layer in its own right, and includes - an extensible Python-based SQL expression - language, schema metadata, connection pooling, - type coercion, and custom types. -* All primary and foreign key constraints are - assumed to be composite and natural. Surrogate - integer primary keys are of course still the - norm, but SQLAlchemy never assumes or hardcodes - to this model. -* Database introspection and generation. Database - schemas can be "reflected" in one step into - Python structures representing database metadata; - those same structures can then generate - CREATE statements right back out - all within - the Core, independent of the ORM. - -SQLAlchemy's philosophy: - -* SQL databases behave less and less like object - collections the more size and performance start to - matter; object collections behave less and less like - tables and rows the more abstraction starts to matter. - SQLAlchemy aims to accommodate both of these - principles. -* An ORM doesn't need to hide the "R". A relational - database provides rich, set-based functionality - that should be fully exposed. SQLAlchemy's - ORM provides an open-ended set of patterns - that allow a developer to construct a custom - mediation layer between a domain model and - a relational schema, turning the so-called - "object relational impedance" issue into - a distant memory. -* The developer, in all cases, makes all decisions - regarding the design, structure, and naming conventions - of both the object model as well as the relational - schema. SQLAlchemy only provides the means - to automate the execution of these decisions. -* With SQLAlchemy, there's no such thing as - "the ORM generated a bad query" - you - retain full control over the structure of - queries, including how joins are organized, - how subqueries and correlation is used, what - columns are requested. Everything SQLAlchemy - does is ultimately the result of a developer- - initiated decision. -* Don't use an ORM if the problem doesn't need one. - SQLAlchemy consists of a Core and separate ORM - component. The Core offers a full SQL expression - language that allows Pythonic construction - of SQL constructs that render directly to SQL - strings for a target database, returning - result sets that are essentially enhanced DBAPI - cursors. -* Transactions should be the norm. With SQLAlchemy's - ORM, nothing goes to permanent storage until - commit() is called. SQLAlchemy encourages applications - to create a consistent means of delineating - the start and end of a series of operations. -* Never render a literal value in a SQL statement. - Bound parameters are used to the greatest degree - possible, allowing query optimizers to cache - query plans effectively and making SQL injection - attacks a non-issue. - -Documentation -------------- - -Latest documentation is at: - -https://www.sqlalchemy.org/docs/ - -Installation / Requirements ---------------------------- - -Full documentation for installation is at -`Installation `_. - -Getting Help / Development / Bug reporting ------------------------------------------- - -Please refer to the `SQLAlchemy Community Guide `_. - -Code of Conduct ---------------- - -Above all, SQLAlchemy places great emphasis on polite, thoughtful, and -constructive communication between users and developers. -Please see our current Code of Conduct at -`Code of Conduct `_. - -License -------- - -SQLAlchemy is distributed under the `MIT license -`_. - diff --git a/lib/SQLAlchemy-1.4.46.dist-info/RECORD b/lib/SQLAlchemy-1.4.46.dist-info/RECORD deleted file mode 100644 index 488140cc..00000000 --- a/lib/SQLAlchemy-1.4.46.dist-info/RECORD +++ /dev/null @@ -1,486 +0,0 @@ -SQLAlchemy-1.4.46.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -SQLAlchemy-1.4.46.dist-info/LICENSE,sha256=ZbcQGZNtpoLy8YjvH-nyoobTdOwtEgtXopPVzxy6pCo,1119 -SQLAlchemy-1.4.46.dist-info/METADATA,sha256=QBRUx1GHQMj6My8tZ9nwFQ-7DF5YHd6gNuHwid0meVg,10261 -SQLAlchemy-1.4.46.dist-info/RECORD,, -SQLAlchemy-1.4.46.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -SQLAlchemy-1.4.46.dist-info/WHEEL,sha256=wklNeoByNLhdCl-oEQTdaHIeDl4q9zaQVqAlPxUEgLU,102 -SQLAlchemy-1.4.46.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11 -sqlalchemy/__init__.py,sha256=juK7axiAxYhodnAYtqT9oI61t0gLd9rNRaKWydcO-eE,4272 -sqlalchemy/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/__pycache__/events.cpython-311.pyc,, -sqlalchemy/__pycache__/exc.cpython-311.pyc,, -sqlalchemy/__pycache__/inspection.cpython-311.pyc,, -sqlalchemy/__pycache__/log.cpython-311.pyc,, -sqlalchemy/__pycache__/processors.cpython-311.pyc,, -sqlalchemy/__pycache__/schema.cpython-311.pyc,, -sqlalchemy/__pycache__/types.cpython-311.pyc,, -sqlalchemy/cimmutabledict.cp311-win_amd64.pyd,sha256=Q0IV9Zti4TdKOpIWEiy8VnQdVmu00nxg45bV9j65pQM,14848 -sqlalchemy/connectors/__init__.py,sha256=nJTm0nEUn0GSp4eY6QlX2_5vZNtsOQQpJqvFD8aYuv0,289 -sqlalchemy/connectors/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/connectors/__pycache__/mxodbc.cpython-311.pyc,, -sqlalchemy/connectors/__pycache__/pyodbc.cpython-311.pyc,, -sqlalchemy/connectors/mxodbc.py,sha256=C-of_Xqh1PL3wJ467X1YtRpBpwrtA65mUpGe0Z8yMFc,5950 -sqlalchemy/connectors/pyodbc.py,sha256=DRvigwNxY5G0EZKAryHlW2C2HRY0tpG-JTd0AEU5Y50,7048 -sqlalchemy/cprocessors.cp311-win_amd64.pyd,sha256=OXLXXeDuFwJFUkaKB9mVDRDOSmsAxWrxTUaeQwTKt8g,16896 -sqlalchemy/cresultproxy.cp311-win_amd64.pyd,sha256=0-zzi2cgZI9LvtWqeUmecIeS5bWd68YbyYFjhgw4-OQ,20992 -sqlalchemy/databases/__init__.py,sha256=ty2I6gz_YTHslu-Ql73twgwBUUrdsn7nOZuYyVrygbA,1048 -sqlalchemy/databases/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/dialects/__init__.py,sha256=PhR0_VYlEEwYbIIHLVRIdpdkrMQONAbWGf96ChUAn4Q,2157 -sqlalchemy/dialects/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/dialects/firebird/__init__.py,sha256=UtfdsVEcOpeawUgRoU0IUOJTfH6IlzsmkcM8AFFiEhw,1194 -sqlalchemy/dialects/firebird/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/dialects/firebird/__pycache__/base.cpython-311.pyc,, -sqlalchemy/dialects/firebird/__pycache__/fdb.cpython-311.pyc,, -sqlalchemy/dialects/firebird/__pycache__/kinterbasdb.cpython-311.pyc,, -sqlalchemy/dialects/firebird/base.py,sha256=oNML-3GR7RphQaxTRVdzorYcc7lETxXQ6A1sOOvAO8I,32160 -sqlalchemy/dialects/firebird/fdb.py,sha256=tDyJwkQzTk5lMBo-rwfhXQnfp8jjCSDp_WBlqqmwDDs,4228 -sqlalchemy/dialects/firebird/kinterbasdb.py,sha256=M09I0hUNW9szSq-v3eJY998KOwm3rlkssOCJffTOre8,6681 -sqlalchemy/dialects/mssql/__init__.py,sha256=NsGszAM95JZjpXsyX8j1CclXAf8JIdm36W42bJhZOJg,1873 -sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/dialects/mssql/__pycache__/base.cpython-311.pyc,, -sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-311.pyc,, -sqlalchemy/dialects/mssql/__pycache__/json.cpython-311.pyc,, -sqlalchemy/dialects/mssql/__pycache__/mxodbc.cpython-311.pyc,, -sqlalchemy/dialects/mssql/__pycache__/provision.cpython-311.pyc,, -sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-311.pyc,, -sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-311.pyc,, -sqlalchemy/dialects/mssql/base.py,sha256=yX_hFJar_mEkxyHmUWv91VVCg7inCanmrioFtSp1Rys,121859 -sqlalchemy/dialects/mssql/information_schema.py,sha256=R9DgK6wInKF8vqG0qs-ZWO7ipOd3Gkvt7jftc9p1ieE,7816 -sqlalchemy/dialects/mssql/json.py,sha256=bAi9z-htrC6adJUiBRetVj3RwtJ2GNN5I_KGeHFL8_s,4683 -sqlalchemy/dialects/mssql/mxodbc.py,sha256=vRBdps1BB0BypGD9XmdD5o2Rbn7d9E-eLp7v9XhN_k4,4958 -sqlalchemy/dialects/mssql/provision.py,sha256=oTGw0l5RNcXIWkA0s2erVgk2XKiNw26IRzZE84tN2uE,4371 -sqlalchemy/dialects/mssql/pymssql.py,sha256=YUNzs8Zi7XQATOxZdsqMwbv8dc-fXW8EDljcEPoVZHo,3983 -sqlalchemy/dialects/mssql/pyodbc.py,sha256=Xo9xT0wFimeAqPJ-UjEfwt38XOMe5Ol3BGFS1p0wHT0,25125 -sqlalchemy/dialects/mysql/__init__.py,sha256=lU-MFfFDxiQpuxEMV5F5mZlcfjG3JdrC3zt49eN0cKE,2293 -sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/base.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/dml.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/expression.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/json.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/oursql.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/provision.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-311.pyc,, -sqlalchemy/dialects/mysql/__pycache__/types.cpython-311.pyc,, -sqlalchemy/dialects/mysql/aiomysql.py,sha256=zF04l5v2nQOdiSzbftfoHW0kOdqviegSo61Cs-DVTiA,9926 -sqlalchemy/dialects/mysql/asyncmy.py,sha256=8x-O9jgbay_UGKc72oOc0dqiBgp-uk_cTmzep68bW_g,10213 -sqlalchemy/dialects/mysql/base.py,sha256=R_DRsUPvoCz9RYtba4T9uTecNG122aHzFkSf_g7TsLc,118510 -sqlalchemy/dialects/mysql/cymysql.py,sha256=JQ4wecPTcnpq8w8JbOw7fJfN1wqguu4SHmnGk1XdESM,2353 -sqlalchemy/dialects/mysql/dml.py,sha256=d4dcIINNagmxaB-4APUKefPzq_rGFjav77G-NhUAP-8,6401 -sqlalchemy/dialects/mysql/enumerated.py,sha256=Y_wC8Pa5v0tKocYLsdtfHZnh9zs6mNav5o2R274Gchs,9627 -sqlalchemy/dialects/mysql/expression.py,sha256=opg17QfNs805tLjcxdTLs_nGthpL-vSEiXl25iAPOO4,3871 -sqlalchemy/dialects/mysql/json.py,sha256=tIDsUgeTZqGkyXw-D9q1fL8U8Qxh_M9NqbPOh75fLn4,2397 -sqlalchemy/dialects/mysql/mariadb.py,sha256=4NzNKjpGudJJLQQIYg0oYhlx4WKEPuncCPSTsfV6t6g,610 -sqlalchemy/dialects/mysql/mariadbconnector.py,sha256=gk3vZwEsGZXnBxe4N4B12OHhTERJroGVk9DKb0eYM0Y,7803 -sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=HpwmjYsBaPquqwIly1OrsjGeJGpN9s6CphwtXSmzGuI,7930 -sqlalchemy/dialects/mysql/mysqldb.py,sha256=qFlXFkA1r2WC-c_ig7uuY_RLt5fhkgBU68JRrpEDePE,10756 -sqlalchemy/dialects/mysql/oursql.py,sha256=XDAJHIsThsoDB4Fjuai5EJyexTPbOvUA4v6QzafOf3U,8796 -sqlalchemy/dialects/mysql/provision.py,sha256=-gCG7MwEMtztSmNU107Q563t-nN-oqTVkCiay-HRRQI,2727 -sqlalchemy/dialects/mysql/pymysql.py,sha256=0_235s8k-44sckNmCOCAtnVc3WlCPVNGUhIGCxmFeJA,2868 -sqlalchemy/dialects/mysql/pyodbc.py,sha256=dEUoPAsSHg3ku_cjDkMwPd92vxIFBEEUSPf8Dvl7ra4,4426 -sqlalchemy/dialects/mysql/reflection.py,sha256=hK4SwKrd54G6V07Hohzzk77qHJz1__Dwne4knSITZsw,19273 -sqlalchemy/dialects/mysql/reserved_words.py,sha256=hBqBmz4D3prw_hRZHzTGi3ja1UBmvyoYyKtkWeHCzQo,9668 -sqlalchemy/dialects/mysql/types.py,sha256=YhPL101rrjq8PvfZ-Ve4J2Xav8pxE_4iGQbcazOYAhU,25362 -sqlalchemy/dialects/oracle/__init__.py,sha256=lrrxHVuGBU5K9uFjDn7crEA4MvpMvphOfHo3CohYF3o,1287 -sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/dialects/oracle/__pycache__/base.cpython-311.pyc,, -sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-311.pyc,, -sqlalchemy/dialects/oracle/__pycache__/provision.cpython-311.pyc,, -sqlalchemy/dialects/oracle/base.py,sha256=7ypFEBugFxVeJFXWgwBYg-0iocT3hdUyV9HRQQEL2eU,90037 -sqlalchemy/dialects/oracle/cx_oracle.py,sha256=2sJ_1rNC7OiVSMgVMlf6LWPVPGpijyTRnvQrzhGlPPw,57275 -sqlalchemy/dialects/oracle/provision.py,sha256=SmVRWQL5WhTMhEyesNt_1CyROyQAKCoZz7-2IAKTb3I,5966 -sqlalchemy/dialects/postgresql/__init__.py,sha256=sGIekkYKS_RJuKAbA9CzAZJImjvzPadLJwYupIejI4A,2671 -sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/array.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/base.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/json.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/pygresql.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/pypostgresql.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-311.pyc,, -sqlalchemy/dialects/postgresql/array.py,sha256=16uQxX2DmoNB6BKDZHnLhh3uBINUEQojL-jBbGrFqwA,14635 -sqlalchemy/dialects/postgresql/asyncpg.py,sha256=RST8YfNs8pNIQ-SEzqVAQWJKTZDK4W-K0EjDu3GPmj4,36564 -sqlalchemy/dialects/postgresql/base.py,sha256=KTBS8lBWrAMH-6VPrBYSTHo5vLxebeX2NUlPi29ymd8,167072 -sqlalchemy/dialects/postgresql/dml.py,sha256=xYoQKVaYxbddf5FjAl_EYAhExmKeudlis6BXbyviePA,9842 -sqlalchemy/dialects/postgresql/ext.py,sha256=yNex91Rzck5Exg270mHTqf3u_-zZRtb9tbmKdVo-l_A,8934 -sqlalchemy/dialects/postgresql/hstore.py,sha256=5DBQPs0wS2T1QMMfJn30LstjZihYWQ1ocBbU1T7m2Lc,13332 -sqlalchemy/dialects/postgresql/json.py,sha256=3Wvn6O-fZwzOzeYuyLWE9sl21ZkBbGWd_hCrzPEuy04,10883 -sqlalchemy/dialects/postgresql/pg8000.py,sha256=XaEbTQk9ORnZP80Qrn5XGaOHu4J9psH8MBaUghtUZ7M,17638 -sqlalchemy/dialects/postgresql/provision.py,sha256=EI5cIapBexG9QGJSAwat98fGkaHuz0XC9JoQV9nN58w,4443 -sqlalchemy/dialects/postgresql/psycopg2.py,sha256=85nXt4lbo1dSoQy69hhE2zA-xvYpm08CIHjLETiwIBQ,41428 -sqlalchemy/dialects/postgresql/psycopg2cffi.py,sha256=Pa9ke6Wv7By0FPMQ_Sn1AjvrT8jn9So9sww3JV7CGLI,1751 -sqlalchemy/dialects/postgresql/pygresql.py,sha256=6ofiWQHkIYjFX6GwVDjyS30f5HqnRGfBxf0AICxerpE,8863 -sqlalchemy/dialects/postgresql/pypostgresql.py,sha256=OSBz9Q_4GV9fSTHs3XlhzQtrcsjHCQmbH1frAQO8COo,3819 -sqlalchemy/dialects/postgresql/ranges.py,sha256=J2L3wHmfhZBRO2jyqozXJR4PAG0Vg6ARU5NJ6q03QGc,4901 -sqlalchemy/dialects/sqlite/__init__.py,sha256=cQfTXEjjoo15pSPaIA7DT-Z83C8YvHwJ1u-YG_mvnBQ,1256 -sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-311.pyc,, -sqlalchemy/dialects/sqlite/__pycache__/base.cpython-311.pyc,, -sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-311.pyc,, -sqlalchemy/dialects/sqlite/__pycache__/json.cpython-311.pyc,, -sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-311.pyc,, -sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-311.pyc,, -sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-311.pyc,, -sqlalchemy/dialects/sqlite/aiosqlite.py,sha256=rcbCyHDVlWCJehxbDBeUhIhBEOYJ0_p9ruXt7UkqngA,10566 -sqlalchemy/dialects/sqlite/base.py,sha256=htkLJpeTExrxTNL96iAynOhRMaApoSlkaKf4-iPKlcY,93370 -sqlalchemy/dialects/sqlite/dml.py,sha256=5ZLcIDfm8RxHjBoO8O6dNE2kr_9gtFbAX9heOGsAQZo,7065 -sqlalchemy/dialects/sqlite/json.py,sha256=bz_1axFG5YI9kLszE-oiCN3-z95zIPPcLgVwug_-AT4,2602 -sqlalchemy/dialects/sqlite/provision.py,sha256=3F5ZX2dYGMFAGra99UVavCihth1_XazJXX9XAet8gbw,4818 -sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=0tMlKQ5H9cmhkQC1EYbB0L6H8EBnT8e3nWFHQfVbGEI,5769 -sqlalchemy/dialects/sqlite/pysqlite.py,sha256=70Whr9LJROQq9eiNE45PqBU4VfSdXG8vA7w0ACFHvUg,25099 -sqlalchemy/dialects/sybase/__init__.py,sha256=GCM2Tvul0zSBkBYehkO6WcDAkmE2en8aus5WzhRFRIk,1431 -sqlalchemy/dialects/sybase/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/dialects/sybase/__pycache__/base.cpython-311.pyc,, -sqlalchemy/dialects/sybase/__pycache__/mxodbc.cpython-311.pyc,, -sqlalchemy/dialects/sybase/__pycache__/pyodbc.cpython-311.pyc,, -sqlalchemy/dialects/sybase/__pycache__/pysybase.cpython-311.pyc,, -sqlalchemy/dialects/sybase/base.py,sha256=IXTFalnlkAchFnCh_eIuG5FNQPfT5ME-Wwz0zQ9sPkQ,33521 -sqlalchemy/dialects/sybase/mxodbc.py,sha256=aDPnDO6iZwepi-6b3l5Y1oMWJOEXz0bNUHti_gTqdnc,973 -sqlalchemy/dialects/sybase/pyodbc.py,sha256=-dsF6svPJONseb-rilossTc2jZVhqI3comEI-ab03_k,2319 -sqlalchemy/dialects/sybase/pysybase.py,sha256=AL1siwxepJ_vyuYlg9UdJPJzDZg52fnKvnKF5kt_UEM,3476 -sqlalchemy/engine/__init__.py,sha256=lgQdOlLDeKMXuhJczDBAWLfxKk5_R0dNy67zbR_RcBQ,2170 -sqlalchemy/engine/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/base.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/characteristics.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/create.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/cursor.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/default.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/events.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/interfaces.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/mock.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/reflection.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/result.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/row.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/strategies.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/url.cpython-311.pyc,, -sqlalchemy/engine/__pycache__/util.cpython-311.pyc,, -sqlalchemy/engine/base.py,sha256=g2Wro7z1x43Ws5VDrgH7GdzjSAjUkOQ61d-s0MWqKpM,128057 -sqlalchemy/engine/characteristics.py,sha256=DrhLcmpnkMpmgo_kgQpcujoD4nW81vTt-GkuWqW9QaY,1873 -sqlalchemy/engine/create.py,sha256=eWW1iLuIzOe1UtMyglfUYBhLfv6SJvszKAlHrJq0TVY,31529 -sqlalchemy/engine/cursor.py,sha256=wJ7MpDDFqkY80DmVL49qBMzQjRsgAI5nSwOqmyP7iYk,70721 -sqlalchemy/engine/default.py,sha256=Ujrxa9cEgvdLbQpNJKtKPDPgmL5snodU3SwPdEBSgF0,68964 -sqlalchemy/engine/events.py,sha256=JZ3AzjQR7ZO5vDR_KE2y1anSLy-U5dMZ9NDefNWyjkw,34257 -sqlalchemy/engine/interfaces.py,sha256=5yv6sgvuIUP89zNidWRCy3irnv3UE6_Ag-FKOb5FFuc,60708 -sqlalchemy/engine/mock.py,sha256=OSg4mjwWpClhhATHKw9nIKJZmV3ANXde1Kx4DcypS20,3744 -sqlalchemy/engine/reflection.py,sha256=UU5OTSCWZLEU3-1fNadUpFtPW1OQabJIMK18KHHrqNg,40090 -sqlalchemy/engine/result.py,sha256=5LPLIv0HI99naAr6ycOapKpHzWovcza44N4DCKBy104,62574 -sqlalchemy/engine/row.py,sha256=Bsm8TP1dGR1oOSlNTqslXkN-ABkSh0TirBlytL6-fdQ,19311 -sqlalchemy/engine/strategies.py,sha256=i-iOSYW2fVTLHJrUjCAl25HHHYm0PQ8UuuN1pPVyrC8,431 -sqlalchemy/engine/url.py,sha256=3jNcD_C7MZmfHnrbX7NdQtZTiOoarLETwTmRmcxQKho,27978 -sqlalchemy/engine/util.py,sha256=VuZ8HSsSkgQA-vZlVJtVZ1QPTC9RodigKUwk91mWO0w,8695 -sqlalchemy/event/__init__.py,sha256=BY6SUypm_e5r2hC0IHKE4LTeWpe3YYYYCMpawetfvHc,534 -sqlalchemy/event/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/event/__pycache__/api.cpython-311.pyc,, -sqlalchemy/event/__pycache__/attr.cpython-311.pyc,, -sqlalchemy/event/__pycache__/base.cpython-311.pyc,, -sqlalchemy/event/__pycache__/legacy.cpython-311.pyc,, -sqlalchemy/event/__pycache__/registry.cpython-311.pyc,, -sqlalchemy/event/api.py,sha256=wyY1zfwrpoyGxkbnrrcGYqmDl7q-3Fe19VW6eKssmKs,8262 -sqlalchemy/event/attr.py,sha256=zkZEkvEicW0XG2fuDPo8jwoLfldjvVAlBgArIBRvPoE,14568 -sqlalchemy/event/base.py,sha256=wb4TGkflIGuSN_ZxLag_PoCqg18Lio1mKPLfeNnmFSQ,11281 -sqlalchemy/event/legacy.py,sha256=tpsN6e6FLlcQBIEd8hGwFGUm2OOEDWl9TrcGhByHBK4,6788 -sqlalchemy/event/registry.py,sha256=vYOkhh8UMYsuWvKNsRwKZCrtrrWWlnRlTbEgg0Ia4gY,8783 -sqlalchemy/events.py,sha256=nK1Jb3bcbAMGArpfWeimQaYdsBkMmQEyCvKv6psaj_4,481 -sqlalchemy/exc.py,sha256=hx_flLUIV6f12hQa6kTGxZ2ozxBPKThdYdmAahItcmo,21849 -sqlalchemy/ext/__init__.py,sha256=2ow4CHEH4B_6wyAWKh1wqEbAUXG5ia2z2zASTa0Oqdk,333 -sqlalchemy/ext/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/associationproxy.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/automap.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/baked.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/compiler.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/horizontal_shard.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/hybrid.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/indexable.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/instrumentation.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/mutable.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/orderinglist.cpython-311.pyc,, -sqlalchemy/ext/__pycache__/serializer.cpython-311.pyc,, -sqlalchemy/ext/associationproxy.py,sha256=svTOQel8Cn2hC2Xb6ZMvjhlpwKhAtSc-nkUyzNrEJeo,52766 -sqlalchemy/ext/asyncio/__init__.py,sha256=q-LE-IgmfAXROlwDbcPeuzwP1ON03cqHCLyR_L7fGyw,845 -sqlalchemy/ext/asyncio/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/ext/asyncio/__pycache__/base.cpython-311.pyc,, -sqlalchemy/ext/asyncio/__pycache__/engine.cpython-311.pyc,, -sqlalchemy/ext/asyncio/__pycache__/events.cpython-311.pyc,, -sqlalchemy/ext/asyncio/__pycache__/exc.cpython-311.pyc,, -sqlalchemy/ext/asyncio/__pycache__/result.cpython-311.pyc,, -sqlalchemy/ext/asyncio/__pycache__/scoping.cpython-311.pyc,, -sqlalchemy/ext/asyncio/__pycache__/session.cpython-311.pyc,, -sqlalchemy/ext/asyncio/base.py,sha256=nZJsqzt2NTUGEzpa0DAhl7s5cLBDkYsCkSyuhwYTBm8,2615 -sqlalchemy/ext/asyncio/engine.py,sha256=dM2ufkPoQ9ezJu-PL3jkFYL0ZWIW_SV6e6WnqgIoMzQ,27485 -sqlalchemy/ext/asyncio/events.py,sha256=BIuYA6c9O6NTatDceMSylVlQLM3wQ8NQeJEYWAngs0c,1467 -sqlalchemy/ext/asyncio/exc.py,sha256=AeGYi7BtwZGHv4ZWaJl7wzE4u8dRlzi_06V_ipNPdfU,660 -sqlalchemy/ext/asyncio/result.py,sha256=frStM0TFDi77GkLHO6lPvoqJfLWkwMNsOtoNgMKsg0E,21321 -sqlalchemy/ext/asyncio/scoping.py,sha256=NdWEyThFoOgCfxirn8lNuuj6ZcXEOjgAkOTuWbLFq2I,3067 -sqlalchemy/ext/asyncio/session.py,sha256=4H-OfZNL_3HH8Tentb7de3XeQcz0V50s6-aZFWE9G5c,24900 -sqlalchemy/ext/automap.py,sha256=jTynz9Hz7ye6RuFxPBHyoKtm-RgBOi3CU-R3GqYtBBk,47016 -sqlalchemy/ext/baked.py,sha256=LaIYr8DXaQD8UyHJx_FN616YLUKdkRu3-21pleiXVUo,20617 -sqlalchemy/ext/compiler.py,sha256=tSb0N2JoRoIu2aLaOzckEKdRaQDxQ_ljcZv7eJ4PwNQ,23242 -sqlalchemy/ext/declarative/__init__.py,sha256=N3C0wblQCOrIyI-__5nvKNTbqFa0U_bEQgajTLQqaB8,1906 -sqlalchemy/ext/declarative/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/ext/declarative/__pycache__/extensions.cpython-311.pyc,, -sqlalchemy/ext/declarative/extensions.py,sha256=OXA7PH6wUlysoALc0WF1_UcV1e8OXNbWMEOwFrrz4B4,17004 -sqlalchemy/ext/horizontal_shard.py,sha256=3uZbW_8xVPK_Yvo-bLtN5tQvAGqnmc1jump8FyYVfzY,9178 -sqlalchemy/ext/hybrid.py,sha256=YaadILSTJ9_hsLC1kM6evpjUrpnTYnyBlHWqOJz56BA,43145 -sqlalchemy/ext/indexable.py,sha256=7mpJrTUPa9oOzh97b8fg-XrXkczTc1dU6dgZpd-F7zU,11607 -sqlalchemy/ext/instrumentation.py,sha256=kx2zn8mN30F-CbWqiBxb7HgnMSJChLcvQhJ8Tb8VIXo,14802 -sqlalchemy/ext/mutable.py,sha256=g3XbzCGXZBA-QuA81ON9wnALD3ZzW2izrhN6NWL8a6s,33458 -sqlalchemy/ext/mypy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -sqlalchemy/ext/mypy/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/ext/mypy/__pycache__/apply.cpython-311.pyc,, -sqlalchemy/ext/mypy/__pycache__/decl_class.cpython-311.pyc,, -sqlalchemy/ext/mypy/__pycache__/infer.cpython-311.pyc,, -sqlalchemy/ext/mypy/__pycache__/names.cpython-311.pyc,, -sqlalchemy/ext/mypy/__pycache__/plugin.cpython-311.pyc,, -sqlalchemy/ext/mypy/__pycache__/util.cpython-311.pyc,, -sqlalchemy/ext/mypy/apply.py,sha256=91CSdH2xynaH021h-3N8OgROti9AKEtr3vS1il7NpXU,9909 -sqlalchemy/ext/mypy/decl_class.py,sha256=Prj8raNTj62sthYQM9YkAN5i253ZtsjkOerI13Q0g-Y,17859 -sqlalchemy/ext/mypy/infer.py,sha256=FGlMhNssaytzeV2mY5eMzXPhFmqovjfNkC9jH-PoJbw,18584 -sqlalchemy/ext/mypy/names.py,sha256=x7eRwmrR-Doo3TeiCmOdWKIHw0bINnSRnILzUStdvxM,8183 -sqlalchemy/ext/mypy/plugin.py,sha256=-NJjASfM5fLPA85Mq7LN_78x565FetYx-WExRVHVyNM,9529 -sqlalchemy/ext/mypy/util.py,sha256=zDlbABxIbMOFzeSswlAI2RFnWUqBWGdfBn2otOiTLVI,8547 -sqlalchemy/ext/orderinglist.py,sha256=5CdaXLMQsh6NFmBu0UObYij0A2WT7wx9HZ0nKiyQpN4,14263 -sqlalchemy/ext/serializer.py,sha256=8I4A7T8lLo844qMZxLGjpBIZQLccespH4BMjokjKLLw,6367 -sqlalchemy/future/__init__.py,sha256=pLdg3e_NRmipQd5rPxZaTiI2awAEkUif-ya2_QBe1rA,543 -sqlalchemy/future/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/future/__pycache__/engine.cpython-311.pyc,, -sqlalchemy/future/engine.py,sha256=mSCb1fWX81P7iHKlulgZPnwLGlfcjB8Y5vhOB_fvOL4,16597 -sqlalchemy/future/orm/__init__.py,sha256=uxJ7YL-Z8JsdajGW2ddS4b8Iqw7Llz8fRZo9q4rJDWU,299 -sqlalchemy/future/orm/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/inspection.py,sha256=LTjmXwoJYSbYs7kNUAcGbeeH3KayZG6dFi5vMpFZkIc,3144 -sqlalchemy/log.py,sha256=rg_D-l3jb609f1fagRlslXW3OoLu0wPHQerwLPtepb0,7384 -sqlalchemy/orm/__init__.py,sha256=wxX6t0hgDLFkZhhAp84mUlExW1YLbpxczvn8NOc9Xis,11308 -sqlalchemy/orm/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/attributes.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/base.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/clsregistry.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/collections.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/context.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/decl_api.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/decl_base.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/dependency.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/descriptor_props.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/dynamic.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/evaluator.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/events.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/exc.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/identity.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/instrumentation.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/interfaces.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/loading.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/mapper.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/path_registry.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/persistence.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/properties.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/query.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/relationships.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/scoping.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/session.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/state.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/strategies.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/strategy_options.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/sync.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/unitofwork.cpython-311.pyc,, -sqlalchemy/orm/__pycache__/util.cpython-311.pyc,, -sqlalchemy/orm/attributes.py,sha256=AfcTEPQ_GLVGK4ntVD1J22sqhErUimf4E2ZZkjMrAss,79615 -sqlalchemy/orm/base.py,sha256=9rW-cy7aji9IPuNEv7uGY_GyqWjEFBLZLVIN1CcOVZY,15816 -sqlalchemy/orm/clsregistry.py,sha256=GCqOMRZW0iOtYKyj_SSiFB1BFdaPxL3314u1loBMO-8,13727 -sqlalchemy/orm/collections.py,sha256=mmhWsOdQTsQgfGxvYkRc1rvc6QpdxRr9dJBtefrj-JM,56429 -sqlalchemy/orm/context.py,sha256=KOcn590QTJwEboCySaFc-_6UxaS65VKgdTToiekFVjE,114392 -sqlalchemy/orm/decl_api.py,sha256=YAhCD_KXvMKTU3XlB1V-si-9sHE464QJIS3cPfvCVv0,36626 -sqlalchemy/orm/decl_base.py,sha256=_WuSa4vbh-nv3EEStZsnEyTfGFBWEuVioH6buKmxW7M,45956 -sqlalchemy/orm/dependency.py,sha256=V7j7hwZ25Eeh_Ov6pzHV3FSJW5kD6pWvcVFNI0e20-A,48277 -sqlalchemy/orm/descriptor_props.py,sha256=_TwNvyUhuArWqOwnYDzGe-gXgZRXiQLtBRhZFPyR73g,26732 -sqlalchemy/orm/dynamic.py,sha256=hEHuZDK47Dh73kNIhfXP_EZBA3cTeWLNtcqYtS8XPLU,16448 -sqlalchemy/orm/evaluator.py,sha256=27jFHVtZ8OnN6x28bsNrORKeXaLtt3VXuyKLi1u29mg,8217 -sqlalchemy/orm/events.py,sha256=47Qsb7EUvd-MwR-lkmAqv0sUtMC4sZscChDxc748jf4,115173 -sqlalchemy/orm/exc.py,sha256=E1gKo5g7hM-L84Ywo4X3AiwaBKlQlYIYwlgqjEKt9ig,6736 -sqlalchemy/orm/identity.py,sha256=3QR0xt3kefqxR1MIVRAI9VAM3x7edxRiMBslep5X-mA,7487 -sqlalchemy/orm/instrumentation.py,sha256=zoBSKC5ib4F1vJ5seqOnwt6QeOUKP0BU3vIvXjZB4R8,21030 -sqlalchemy/orm/interfaces.py,sha256=OMvU416PONNTrNSPHB--OX5HnANqf2i-yq8b8TYHrPE,33362 -sqlalchemy/orm/loading.py,sha256=rsOEnxId2bFuEL_TrFHrr_HDHC-mYksba22ogh4aaV0,50782 -sqlalchemy/orm/mapper.py,sha256=IAKQSjtZep_Eam9saKZsyAqnjtU6RplZOdenE1SYoFU,148083 -sqlalchemy/orm/path_registry.py,sha256=ocNdufotJ6v9GYYnyiT_Ou9a9Ri8LCNS0Zqi20BMaK8,16930 -sqlalchemy/orm/persistence.py,sha256=ofXQK2qUQN7Kj6vyppLWA3sl1_b8_OAR5yy2JmvWdLw,86767 -sqlalchemy/orm/properties.py,sha256=Z4NjTQfX8_s-LN8sDASz7hGhRaoM7LfiXbu_a6GTdMs,15217 -sqlalchemy/orm/query.py,sha256=hnHEmZ9h45SbdswE66me2W7hZe8t75LUAvHsyaW0Wcw,129454 -sqlalchemy/orm/relationships.py,sha256=8C42p6sUZITj-jIVWkpQTW5jHmkZr8199EBrv3iSlHo,147850 -sqlalchemy/orm/scoping.py,sha256=LIh2NCqtXUEOdVViqX0flSvN6-vUFthWKpePVrKTiwY,7485 -sqlalchemy/orm/session.py,sha256=q7vTrGW_Ot_Nk1HEtOUAOIdg8wQe5EyBaPEfGaiBSNs,166858 -sqlalchemy/orm/state.py,sha256=pUui99xTI8NaU-Z2IsObeK1lkGpFsKq5wCfwLuRhucw,34549 -sqlalchemy/orm/strategies.py,sha256=ArZpE31V-i7tq_Q92x_HER6eCkZDE0LYxfS3uYeqeSo,111482 -sqlalchemy/orm/strategy_options.py,sha256=dQoG6WxOC-WqysnGMhq33atRxnBCEwvZ9ukkOMaOjfo,69932 -sqlalchemy/orm/sync.py,sha256=HSxLeM8wlCWbCT5rAJ6W3i4lgc4ATc4rF1xQteU8Pkc,5991 -sqlalchemy/orm/unitofwork.py,sha256=36bPYJhQ5p3UydF9WSlMtFw3iwuBAdeq82IPmnk_koA,27874 -sqlalchemy/orm/util.py,sha256=i6gHKPSwcB4L3zfbXhISB1ijk8sBFpqZZHgYm45tUrs,78508 -sqlalchemy/pool/__init__.py,sha256=k8H4QCb62Orw08TqDOFUuOUoCmEfrOsOGp1n2CHFmTA,1659 -sqlalchemy/pool/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/pool/__pycache__/base.cpython-311.pyc,, -sqlalchemy/pool/__pycache__/dbapi_proxy.cpython-311.pyc,, -sqlalchemy/pool/__pycache__/events.cpython-311.pyc,, -sqlalchemy/pool/__pycache__/impl.cpython-311.pyc,, -sqlalchemy/pool/base.py,sha256=JI38Hp4qc6jA70vcMh8aCr6pctX8wOHvpcxVY6fdbTM,41700 -sqlalchemy/pool/dbapi_proxy.py,sha256=9oyQUJrx7PbiW14G4GGUOl04epcQE_vAaenYyNgp-O8,4376 -sqlalchemy/pool/events.py,sha256=NfIiPCVW4s73d2EkHObveZVWjUKklbdue6IhkYU4ckk,11989 -sqlalchemy/pool/impl.py,sha256=FYns7IhXVhRi0r5RpehxFIVBomM3nN5EoDV2MUlpf_4,16297 -sqlalchemy/processors.py,sha256=QcFfdR33gfeDRf7YsUFcwDfcbbCHalOghwBt1Ddu6FY,5921 -sqlalchemy/schema.py,sha256=A0s_F3KJ3-6LAMaVlww4XL_h8N8AvlR9Pc51OV7c7bo,2472 -sqlalchemy/sql/__init__.py,sha256=w0pfXj1o8i3Xi21MELm0cfBi-S5JYRyXU6S4WsLYxtk,4811 -sqlalchemy/sql/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/annotation.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/base.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/coercions.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/compiler.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/crud.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/ddl.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/default_comparator.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/dml.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/elements.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/events.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/expression.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/functions.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/lambdas.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/naming.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/operators.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/roles.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/schema.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/selectable.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/sqltypes.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/traversals.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/type_api.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/util.cpython-311.pyc,, -sqlalchemy/sql/__pycache__/visitors.cpython-311.pyc,, -sqlalchemy/sql/annotation.py,sha256=EtBy0KwyEfxxPBZkGmfj9rKERKg-kR_Uz6DUTdhWyyA,11981 -sqlalchemy/sql/base.py,sha256=rQjKju5gVEX9af59al298mN2ftcaZOtXBF3G3E-m30c,57601 -sqlalchemy/sql/coercions.py,sha256=m2q-w9lHlbmdx4zaE5kius2AVEUCz7CQ6v_u4Joz41w,35817 -sqlalchemy/sql/compiler.py,sha256=mTMTxSaYp_f4u7uIpvJCSE0Q_IeJBCuGDgpTioDmtgg,199422 -sqlalchemy/sql/crud.py,sha256=dxoJTl4WmtTLNFFZEQK28Agj8u-halXj-ycY1_BTldk,37058 -sqlalchemy/sql/ddl.py,sha256=8t2Xy9qb7i_iWYFsmtQOKSb4AruqBllUygCKRvzYpvs,45549 -sqlalchemy/sql/default_comparator.py,sha256=7VJ9hI6ZEaP2LnxnwI4TVZ71sIg4QrkGwXtWop2ZL58,11538 -sqlalchemy/sql/dml.py,sha256=VmQjjyaQW0mACzo0QQa7I5GvUnGAYTBAlsYSihM12wI,56184 -sqlalchemy/sql/elements.py,sha256=cI7C04KnZ72p-Eg_AuXU7m8AWqRDkK9gytsQHH2Gn0E,187909 -sqlalchemy/sql/events.py,sha256=WvDp8G2u8JpRmEr7jC49Ltx8Qmh2MAvi7zfaEJf3w24,13574 -sqlalchemy/sql/expression.py,sha256=nAFhKCaT4tFQMelmEgLt21vuPSbbnx8eToT9UQff0HQ,9106 -sqlalchemy/sql/functions.py,sha256=-uXTji-BlZkaLhHwJKawpoEMPPkjUK0kqKnquSCgfWw,50650 -sqlalchemy/sql/lambdas.py,sha256=b3E9ns7ZIwvsDbMGN-PonxSpG_ye7gd3bucUd0m7mh0,46363 -sqlalchemy/sql/naming.py,sha256=drA3lmYAl81HQlbNCZTq-mmJl8U74hTTE1iavn6Crp4,6996 -sqlalchemy/sql/operators.py,sha256=pmbDfO8e3k88v_0ZqB7cy7jn7fcnXtVVvs5qeEWCv5Q,50233 -sqlalchemy/sql/roles.py,sha256=UaZ7qp_MRxLea6u4lPT-B0N5vQNHfifFLhAvUB5nX3Q,5877 -sqlalchemy/sql/schema.py,sha256=0oqDO_x3BjixN-SmDM96b6nPZFpXZtx23EBj-4jDLt8,201034 -sqlalchemy/sql/selectable.py,sha256=D3GsUmqdlNsm1BoQ35AdscHtJF019rDATUAsjf3lFno,244491 -sqlalchemy/sql/sqltypes.py,sha256=BZXYs7dPf9gUk8ycbeUvTnYLGEEb3rXrV5ECFHKjjI4,117959 -sqlalchemy/sql/traversals.py,sha256=xB_6x8rfWFOCLNwlrCpF8_4AYPyXRM1k-p6iMqakv0E,54512 -sqlalchemy/sql/type_api.py,sha256=aMqTB5qZ-ETcP8rN5qSEOI-85nXu7T2ZLcT7vgjbxbE,73212 -sqlalchemy/sql/util.py,sha256=tKiSPKHP-6g5ZjZRsTzP3377fi2UxoGlOFPUxiA4nSs,37087 -sqlalchemy/sql/visitors.py,sha256=i6Y1T72o4oECC7ZKZHrbQZ9N2pePqqcp1jKgsAy32Q8,28181 -sqlalchemy/testing/__init__.py,sha256=VL67NeF-Qk9yhFJDyx-HukAxvSMevv6x1TE3xJwdbL4,3043 -sqlalchemy/testing/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/assertions.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/assertsql.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/asyncio.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/config.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/engines.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/entities.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/exclusions.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/fixtures.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/mock.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/pickleable.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/profiling.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/provision.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/requirements.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/schema.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/util.cpython-311.pyc,, -sqlalchemy/testing/__pycache__/warnings.cpython-311.pyc,, -sqlalchemy/testing/assertions.py,sha256=Af8g-lYGnr2o5ayQqpvRpHFCTkm8_KMnGmeTRlG6GUQ,29993 -sqlalchemy/testing/assertsql.py,sha256=zYKhMv2Ky-aDATFjxU6R0dBu1iHCGzCuy0ysoJjL_Dg,15421 -sqlalchemy/testing/asyncio.py,sha256=_o36XxwLv_h4pfZGcW14PquhR96jDFGn6YIcxOlgNTw,3799 -sqlalchemy/testing/config.py,sha256=QM2Ox16xaHgcU53PkBmzi1LZn83fAKQQb1f6xsk5mPA,8729 -sqlalchemy/testing/engines.py,sha256=R88LPqz3zeYuH2uIMxEuYgBxSfj4Apg3U0t4aAXmod4,13857 -sqlalchemy/testing/entities.py,sha256=DF3ZrMQWmsM6XxBo8PvQzQanPn5aDyoNodLYSCXMeUQ,3364 -sqlalchemy/testing/exclusions.py,sha256=YCTSLGpO03_n2TzfcBtOdM7uJhdKhdWdSgcXwjC7rO8,13794 -sqlalchemy/testing/fixtures.py,sha256=PQhj1q0x1Lmgmt9M7EZYfvuUNyl9HdKMUgKDFG3tIg4,31902 -sqlalchemy/testing/mock.py,sha256=6ulh6RB6BsohHrQNALEdtKyjjdBW7tc3mtyNw1X01YA,926 -sqlalchemy/testing/pickleable.py,sha256=QUniRTgiT0fpd5Nb63y2wreCVn-3yFl1T_9lqEMyEUM,3037 -sqlalchemy/testing/plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -sqlalchemy/testing/plugin/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-311.pyc,, -sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-311.pyc,, -sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-311.pyc,, -sqlalchemy/testing/plugin/__pycache__/reinvent_fixtures_py2k.cpython-311.pyc,, -sqlalchemy/testing/plugin/bootstrap.py,sha256=Pykdrxtc5QVxY11Z0dyQjtRlpftk9pYeh35xunm-NdU,1755 -sqlalchemy/testing/plugin/plugin_base.py,sha256=YaOdlMUFhy4YcR1MLO0anXv0zZLKKsmj9_BSeC2Fgos,22328 -sqlalchemy/testing/plugin/pytestplugin.py,sha256=mG2b_NSpklZPSxKGRV7xrE4thcnPo6LED0f5RuVq2T8,26953 -sqlalchemy/testing/plugin/reinvent_fixtures_py2k.py,sha256=b9fWp5RXdePykrNviZPXaGDIjOEOfovchez2Ovr4IRQ,3400 -sqlalchemy/testing/profiling.py,sha256=bRTxFPlIDQCGU8lnIgkh0K6MnEydnf_FjClEDcMa12o,10988 -sqlalchemy/testing/provision.py,sha256=KgkqJazFsLEY9CjeniUApQiR90_ghoQHiDBY2nuCErU,12486 -sqlalchemy/testing/requirements.py,sha256=klojCRUs36tUl2J1YsUduGsB9g8oc4ftTtl_CdrCI74,45819 -sqlalchemy/testing/schema.py,sha256=FLGooWz_AOKCz-Gvm1U1v5UiVY0sIkm0S7eTYLaW9xc,6762 -sqlalchemy/testing/suite/__init__.py,sha256=u3lEc0j47s7Dad_2SVWOZ6EU2aOMRWqE_WrQ17HmBsA,489 -sqlalchemy/testing/suite/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_cte.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_deprecations.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_insert.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_results.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_rowcount.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_select.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_types.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_unicode_ddl.cpython-311.pyc,, -sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-311.pyc,, -sqlalchemy/testing/suite/test_cte.py,sha256=shi2WJZpzAzDCdkmzx0IDEu-URsLLBsdyFdzDqsfpyw,6387 -sqlalchemy/testing/suite/test_ddl.py,sha256=BZuYkKaG_tNUkZcFLNd6o1qnfbzU0IYiedCw_NhOJBg,12143 -sqlalchemy/testing/suite/test_deprecations.py,sha256=0LUmXIiK8hHUr6tY8cJJC7VErOj9YNGNN-c324k08Dw,5204 -sqlalchemy/testing/suite/test_dialect.py,sha256=_kghfOkgZ09hiGby9BcR6G2oA72aNSofqgMKFQuIU_M,13334 -sqlalchemy/testing/suite/test_insert.py,sha256=QlMovNeDU-GB5UvTEumce3LLK94PAduKlWe2TqCcLF4,11501 -sqlalchemy/testing/suite/test_reflection.py,sha256=ZKkABYz7bwMSJTtPRpF1z_CVfGcI69dvAVLnOM6CBVM,63305 -sqlalchemy/testing/suite/test_results.py,sha256=wA4SjK46-1TCqqXcOw_a7_9ajMkF3aU4tuLOK9iVWyI,14409 -sqlalchemy/testing/suite/test_rowcount.py,sha256=0kswa4gunxRreCpRkFR7Yxff0k0o-2R8fugMmOGcANg,5042 -sqlalchemy/testing/suite/test_select.py,sha256=fFQg5DuGyNSTeBfW5hmSqiuAvPx0FUyCYKbETaCfeVQ,57247 -sqlalchemy/testing/suite/test_sequence.py,sha256=2KBcs0FtoL3gk37IN2PnRZSnQwt7RKkShAbYQHFTBcw,8713 -sqlalchemy/testing/suite/test_types.py,sha256=ur57ApD3PU_AslebV8f5MECRy68XuGqJFmBcsIwcRz4,49552 -sqlalchemy/testing/suite/test_unicode_ddl.py,sha256=HhAM4_38vpwJFX2d9xh0S_YAIQJQ-79GmfO3ich2Tu4,6943 -sqlalchemy/testing/suite/test_update_delete.py,sha256=5moBXgVXSHmSIJI26CfxT6K51-4z8Ftg2uaVOjeDKBs,1685 -sqlalchemy/testing/util.py,sha256=dLHxjZjh9ZjdI9fzcWhCji01gD5keBi5R1xCOPFs94Q,14526 -sqlalchemy/testing/warnings.py,sha256=wG0YJejPt0s7ECrXkoe4BLndVvPKjSu33hHDych7ImU,2352 -sqlalchemy/types.py,sha256=GhXBo4X64sU2y4jCfZ-o_L-XFDU_-ss0RnGB9WbuykY,3114 -sqlalchemy/util/__init__.py,sha256=EuZh7uinu1w29GS7m-3W_qQA42JqIASaVSmIa2D0Fek,6631 -sqlalchemy/util/__pycache__/__init__.cpython-311.pyc,, -sqlalchemy/util/__pycache__/_collections.cpython-311.pyc,, -sqlalchemy/util/__pycache__/_compat_py3k.cpython-311.pyc,, -sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-311.pyc,, -sqlalchemy/util/__pycache__/_preloaded.cpython-311.pyc,, -sqlalchemy/util/__pycache__/compat.cpython-311.pyc,, -sqlalchemy/util/__pycache__/concurrency.cpython-311.pyc,, -sqlalchemy/util/__pycache__/deprecations.cpython-311.pyc,, -sqlalchemy/util/__pycache__/langhelpers.cpython-311.pyc,, -sqlalchemy/util/__pycache__/queue.cpython-311.pyc,, -sqlalchemy/util/__pycache__/topological.cpython-311.pyc,, -sqlalchemy/util/_collections.py,sha256=kBcFSrLwqTnCRJSbfDmeduPVv0z7C8Cf9LAPrVgXdSc,30228 -sqlalchemy/util/_compat_py3k.py,sha256=9xGrag5qij3Iic8ZaLPXPSqPeIE42x2wsSyMqOpYLes,2262 -sqlalchemy/util/_concurrency_py3k.py,sha256=_Z7rVqWGVw0Vq5_Xd23GGLYJzwPV1OdFfiKseDaKPwY,6792 -sqlalchemy/util/_preloaded.py,sha256=oqsE9vx6ffmtqe0jKC5rQ4kMryORnqGz5I-nRn2pt7M,2464 -sqlalchemy/util/compat.py,sha256=egPzlCIHwhN5u6tNnLyp_wbtBVACiSXj0xn7Gpmi9TM,19020 -sqlalchemy/util/concurrency.py,sha256=pFPMASB4FbfKToUz3jhqlYaWub85r7ij6cNMfKnNnN8,2351 -sqlalchemy/util/deprecations.py,sha256=XGxVfo2pDero6xVNiR3JUlUMLJzr-dYwN9Rp5RwK6ZM,14158 -sqlalchemy/util/langhelpers.py,sha256=xOlrYS-jfTigvfLDv8TWhOGPV_AS6rPk3NQ6A4cdC1Y,58233 -sqlalchemy/util/queue.py,sha256=IMLulUHcTukHN1LycytcRbnJkcUeecAce7nos_Oy_Rw,9584 -sqlalchemy/util/topological.py,sha256=jmqRwWtuX2CVOd18oXa6fFgtTJy39Ka3xT_3oJHbsek,2959 diff --git a/lib/SQLAlchemy-1.4.46.dist-info/REQUESTED b/lib/SQLAlchemy-1.4.46.dist-info/REQUESTED deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/SQLAlchemy-1.4.46.dist-info/WHEEL b/lib/SQLAlchemy-1.4.46.dist-info/WHEEL deleted file mode 100644 index 7d9ab249..00000000 --- a/lib/SQLAlchemy-1.4.46.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.38.4) -Root-Is-Purelib: false -Tag: cp311-cp311-win_amd64 - diff --git a/lib/SQLAlchemy-1.4.46.dist-info/top_level.txt b/lib/SQLAlchemy-1.4.46.dist-info/top_level.txt deleted file mode 100644 index 39fb2bef..00000000 --- a/lib/SQLAlchemy-1.4.46.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -sqlalchemy diff --git a/lib/_distutils_hack/__init__.py b/lib/_distutils_hack/__init__.py deleted file mode 100644 index f987a536..00000000 --- a/lib/_distutils_hack/__init__.py +++ /dev/null @@ -1,222 +0,0 @@ -# don't import any costly modules -import sys -import os - - -is_pypy = '__pypy__' in sys.builtin_module_names - - -def warn_distutils_present(): - if 'distutils' not in sys.modules: - return - if is_pypy and sys.version_info < (3, 7): - # PyPy for 3.6 unconditionally imports distutils, so bypass the warning - # https://foss.heptapod.net/pypy/pypy/-/blob/be829135bc0d758997b3566062999ee8b23872b4/lib-python/3/site.py#L250 - return - import warnings - - warnings.warn( - "Distutils was imported before Setuptools, but importing Setuptools " - "also replaces the `distutils` module in `sys.modules`. This may lead " - "to undesirable behaviors or errors. To avoid these issues, avoid " - "using distutils directly, ensure that setuptools is installed in the " - "traditional way (e.g. not an editable install), and/or make sure " - "that setuptools is always imported before distutils." - ) - - -def clear_distutils(): - if 'distutils' not in sys.modules: - return - import warnings - - warnings.warn("Setuptools is replacing distutils.") - mods = [ - name - for name in sys.modules - if name == "distutils" or name.startswith("distutils.") - ] - for name in mods: - del sys.modules[name] - - -def enabled(): - """ - Allow selection of distutils by environment variable. - """ - which = os.environ.get('SETUPTOOLS_USE_DISTUTILS', 'local') - return which == 'local' - - -def ensure_local_distutils(): - import importlib - - clear_distutils() - - # With the DistutilsMetaFinder in place, - # perform an import to cause distutils to be - # loaded from setuptools._distutils. Ref #2906. - with shim(): - importlib.import_module('distutils') - - # check that submodules load as expected - core = importlib.import_module('distutils.core') - assert '_distutils' in core.__file__, core.__file__ - assert 'setuptools._distutils.log' not in sys.modules - - -def do_override(): - """ - Ensure that the local copy of distutils is preferred over stdlib. - - See https://github.com/pypa/setuptools/issues/417#issuecomment-392298401 - for more motivation. - """ - if enabled(): - warn_distutils_present() - ensure_local_distutils() - - -class _TrivialRe: - def __init__(self, *patterns): - self._patterns = patterns - - def match(self, string): - return all(pat in string for pat in self._patterns) - - -class DistutilsMetaFinder: - def find_spec(self, fullname, path, target=None): - # optimization: only consider top level modules and those - # found in the CPython test suite. - if path is not None and not fullname.startswith('test.'): - return - - method_name = 'spec_for_{fullname}'.format(**locals()) - method = getattr(self, method_name, lambda: None) - return method() - - def spec_for_distutils(self): - if self.is_cpython(): - return - - import importlib - import importlib.abc - import importlib.util - - try: - mod = importlib.import_module('setuptools._distutils') - except Exception: - # There are a couple of cases where setuptools._distutils - # may not be present: - # - An older Setuptools without a local distutils is - # taking precedence. Ref #2957. - # - Path manipulation during sitecustomize removes - # setuptools from the path but only after the hook - # has been loaded. Ref #2980. - # In either case, fall back to stdlib behavior. - return - - class DistutilsLoader(importlib.abc.Loader): - def create_module(self, spec): - mod.__name__ = 'distutils' - return mod - - def exec_module(self, module): - pass - - return importlib.util.spec_from_loader( - 'distutils', DistutilsLoader(), origin=mod.__file__ - ) - - @staticmethod - def is_cpython(): - """ - Suppress supplying distutils for CPython (build and tests). - Ref #2965 and #3007. - """ - return os.path.isfile('pybuilddir.txt') - - def spec_for_pip(self): - """ - Ensure stdlib distutils when running under pip. - See pypa/pip#8761 for rationale. - """ - if self.pip_imported_during_build(): - return - clear_distutils() - self.spec_for_distutils = lambda: None - - @classmethod - def pip_imported_during_build(cls): - """ - Detect if pip is being imported in a build script. Ref #2355. - """ - import traceback - - return any( - cls.frame_file_is_setup(frame) for frame, line in traceback.walk_stack(None) - ) - - @staticmethod - def frame_file_is_setup(frame): - """ - Return True if the indicated frame suggests a setup.py file. - """ - # some frames may not have __file__ (#2940) - return frame.f_globals.get('__file__', '').endswith('setup.py') - - def spec_for_sensitive_tests(self): - """ - Ensure stdlib distutils when running select tests under CPython. - - python/cpython#91169 - """ - clear_distutils() - self.spec_for_distutils = lambda: None - - sensitive_tests = ( - [ - 'test.test_distutils', - 'test.test_peg_generator', - 'test.test_importlib', - ] - if sys.version_info < (3, 10) - else [ - 'test.test_distutils', - ] - ) - - -for name in DistutilsMetaFinder.sensitive_tests: - setattr( - DistutilsMetaFinder, - f'spec_for_{name}', - DistutilsMetaFinder.spec_for_sensitive_tests, - ) - - -DISTUTILS_FINDER = DistutilsMetaFinder() - - -def add_shim(): - DISTUTILS_FINDER in sys.meta_path or insert_shim() - - -class shim: - def __enter__(self): - insert_shim() - - def __exit__(self, exc, value, tb): - remove_shim() - - -def insert_shim(): - sys.meta_path.insert(0, DISTUTILS_FINDER) - - -def remove_shim(): - try: - sys.meta_path.remove(DISTUTILS_FINDER) - except ValueError: - pass diff --git a/lib/_distutils_hack/override.py b/lib/_distutils_hack/override.py deleted file mode 100644 index 2cc433a4..00000000 --- a/lib/_distutils_hack/override.py +++ /dev/null @@ -1 +0,0 @@ -__import__('_distutils_hack').do_override() diff --git a/lib/autocommand-2.2.2.dist-info/INSTALLER b/lib/autocommand-2.2.2.dist-info/INSTALLER deleted file mode 100644 index a1b589e3..00000000 --- a/lib/autocommand-2.2.2.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/lib/autocommand-2.2.2.dist-info/LICENSE b/lib/autocommand-2.2.2.dist-info/LICENSE deleted file mode 100644 index b49c3af0..00000000 --- a/lib/autocommand-2.2.2.dist-info/LICENSE +++ /dev/null @@ -1,166 +0,0 @@ -GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. - diff --git a/lib/autocommand-2.2.2.dist-info/METADATA b/lib/autocommand-2.2.2.dist-info/METADATA deleted file mode 100644 index 32214fb4..00000000 --- a/lib/autocommand-2.2.2.dist-info/METADATA +++ /dev/null @@ -1,420 +0,0 @@ -Metadata-Version: 2.1 -Name: autocommand -Version: 2.2.2 -Summary: A library to create a command-line program from a function -Home-page: https://github.com/Lucretiel/autocommand -Author: Nathan West -License: LGPLv3 -Project-URL: Homepage, https://github.com/Lucretiel/autocommand -Project-URL: Bug Tracker, https://github.com/Lucretiel/autocommand/issues -Platform: any -Classifier: Development Status :: 6 - Mature -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Topic :: Software Development -Classifier: Topic :: Software Development :: Libraries -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Requires-Python: >=3.7 -Description-Content-Type: text/markdown -License-File: LICENSE - -[![PyPI version](https://badge.fury.io/py/autocommand.svg)](https://badge.fury.io/py/autocommand) - -# autocommand - -A library to automatically generate and run simple argparse parsers from function signatures. - -## Installation - -Autocommand is installed via pip: - -``` -$ pip install autocommand -``` - -## Usage - -Autocommand turns a function into a command-line program. It converts the function's parameter signature into command-line arguments, and automatically runs the function if the module was called as `__main__`. In effect, it lets your create a smart main function. - -```python -from autocommand import autocommand - -# This program takes exactly one argument and echos it. -@autocommand(__name__) -def echo(thing): - print(thing) -``` - -``` -$ python echo.py hello -hello -$ python echo.py -h -usage: echo [-h] thing - -positional arguments: - thing - -optional arguments: - -h, --help show this help message and exit -$ python echo.py hello world # too many arguments -usage: echo.py [-h] thing -echo.py: error: unrecognized arguments: world -``` - -As you can see, autocommand converts the signature of the function into an argument spec. When you run the file as a program, autocommand collects the command-line arguments and turns them into function arguments. The function is executed with these arguments, and then the program exits with the return value of the function, via `sys.exit`. Autocommand also automatically creates a usage message, which can be invoked with `-h` or `--help`, and automatically prints an error message when provided with invalid arguments. - -### Types - -You can use a type annotation to give an argument a type. Any type (or in fact any callable) that returns an object when given a string argument can be used, though there are a few special cases that are described later. - -```python -@autocommand(__name__) -def net_client(host, port: int): - ... -``` - -Autocommand will catch `TypeErrors` raised by the type during argument parsing, so you can supply a callable and do some basic argument validation as well. - -### Trailing Arguments - -You can add a `*args` parameter to your function to give it trailing arguments. The command will collect 0 or more trailing arguments and supply them to `args` as a tuple. If a type annotation is supplied, the type is applied to each argument. - -```python -# Write the contents of each file, one by one -@autocommand(__name__) -def cat(*files): - for filename in files: - with open(filename) as file: - for line in file: - print(line.rstrip()) -``` - -``` -$ python cat.py -h -usage: ipython [-h] [file [file ...]] - -positional arguments: - file - -optional arguments: - -h, --help show this help message and exit -``` - -### Options - -To create `--option` switches, just assign a default. Autocommand will automatically create `--long` and `-s`hort switches. - -```python -@autocommand(__name__) -def do_with_config(argument, config='~/foo.conf'): - pass -``` - -``` -$ python example.py -h -usage: example.py [-h] [-c CONFIG] argument - -positional arguments: - argument - -optional arguments: - -h, --help show this help message and exit - -c CONFIG, --config CONFIG -``` - -The option's type is automatically deduced from the default, unless one is explicitly given in an annotation: - -```python -@autocommand(__name__) -def http_connect(host, port=80): - print('{}:{}'.format(host, port)) -``` - -``` -$ python http.py -h -usage: http.py [-h] [-p PORT] host - -positional arguments: - host - -optional arguments: - -h, --help show this help message and exit - -p PORT, --port PORT -$ python http.py localhost -localhost:80 -$ python http.py localhost -p 8080 -localhost:8080 -$ python http.py localhost -p blah -usage: http.py [-h] [-p PORT] host -http.py: error: argument -p/--port: invalid int value: 'blah' -``` - -#### None - -If an option is given a default value of `None`, it reads in a value as normal, but supplies `None` if the option isn't provided. - -#### Switches - -If an argument is given a default value of `True` or `False`, or -given an explicit `bool` type, it becomes an option switch. - -```python - @autocommand(__name__) - def example(verbose=False, quiet=False): - pass -``` - -``` -$ python example.py -h -usage: example.py [-h] [-v] [-q] - -optional arguments: - -h, --help show this help message and exit - -v, --verbose - -q, --quiet -``` - -Autocommand attempts to do the "correct thing" in these cases- if the default is `True`, then supplying the switch makes the argument `False`; if the type is `bool` and the default is some other `True` value, then supplying the switch makes the argument `False`, while not supplying the switch makes the argument the default value. - -Autocommand also supports the creation of switch inverters. Pass `add_nos=True` to `autocommand` to enable this. - -``` - @autocommand(__name__, add_nos=True) - def example(verbose=False): - pass -``` - -``` -$ python example.py -h -usage: ipython [-h] [-v] [--no-verbose] - -optional arguments: - -h, --help show this help message and exit - -v, --verbose - --no-verbose -``` - -Using the `--no-` version of a switch will pass the opposite value in as a function argument. If multiple switches are present, the last one takes precedence. - -#### Files - -If the default value is a file object, such as `sys.stdout`, then autocommand just looks for a string, for a file path. It doesn't do any special checking on the string, though (such as checking if the file exists); it's better to let the client decide how to handle errors in this case. Instead, it provides a special context manager called `smart_open`, which behaves exactly like `open` if a filename or other openable type is provided, but also lets you use already open files: - -```python -from autocommand import autocommand, smart_open -import sys - -# Write the contents of stdin, or a file, to stdout -@autocommand(__name__) -def write_out(infile=sys.stdin): - with smart_open(infile) as f: - for line in f: - print(line.rstrip()) - # If a file was opened, it is closed here. If it was just stdin, it is untouched. -``` - -``` -$ echo "Hello World!" | python write_out.py | tee hello.txt -Hello World! -$ python write_out.py --infile hello.txt -Hello World! -``` - -### Descriptions and docstrings - -The `autocommand` decorator accepts `description` and `epilog` kwargs, corresponding to the `description `_ and `epilog `_ of the `ArgumentParser`. If no description is given, but the decorated function has a docstring, then it is taken as the `description` for the `ArgumentParser`. You can also provide both the description and epilog in the docstring by splitting it into two sections with 4 or more - characters. - -```python -@autocommand(__name__) -def copy(infile=sys.stdin, outfile=sys.stdout): - ''' - Copy an the contents of a file (or stdin) to another file (or stdout) - ---------- - Some extra documentation in the epilog - ''' - with smart_open(infile) as istr: - with smart_open(outfile, 'w') as ostr: - for line in istr: - ostr.write(line) -``` - -``` -$ python copy.py -h -usage: copy.py [-h] [-i INFILE] [-o OUTFILE] - -Copy an the contents of a file (or stdin) to another file (or stdout) - -optional arguments: - -h, --help show this help message and exit - -i INFILE, --infile INFILE - -o OUTFILE, --outfile OUTFILE - -Some extra documentation in the epilog -$ echo "Hello World" | python copy.py --outfile hello.txt -$ python copy.py --infile hello.txt --outfile hello2.txt -$ python copy.py --infile hello2.txt -Hello World -``` - -### Parameter descriptions - -You can also attach description text to individual parameters in the annotation. To attach both a type and a description, supply them both in any order in a tuple - -```python -@autocommand(__name__) -def copy_net( - infile: 'The name of the file to send', - host: 'The host to send the file to', - port: (int, 'The port to connect to')): - - ''' - Copy a file over raw TCP to a remote destination. - ''' - # Left as an exercise to the reader -``` - -### Decorators and wrappers - -Autocommand automatically follows wrapper chains created by `@functools.wraps`. This means that you can apply other wrapping decorators to your main function, and autocommand will still correctly detect the signature. - -```python -from functools import wraps -from autocommand import autocommand - -def print_yielded(func): - ''' - Convert a generator into a function that prints all yielded elements - ''' - @wraps(func) - def wrapper(*args, **kwargs): - for thing in func(*args, **kwargs): - print(thing) - return wrapper - -@autocommand(__name__, - description= 'Print all the values from START to STOP, inclusive, in steps of STEP', - epilog= 'STOP and STEP default to 1') -@print_yielded -def seq(stop, start=1, step=1): - for i in range(start, stop + 1, step): - yield i -``` - -``` -$ seq.py -h -usage: seq.py [-h] [-s START] [-S STEP] stop - -Print all the values from START to STOP, inclusive, in steps of STEP - -positional arguments: - stop - -optional arguments: - -h, --help show this help message and exit - -s START, --start START - -S STEP, --step STEP - -STOP and STEP default to 1 -``` - -Even though autocommand is being applied to the `wrapper` returned by `print_yielded`, it still retreives the signature of the underlying `seq` function to create the argument parsing. - -### Custom Parser - -While autocommand's automatic parser generator is a powerful convenience, it doesn't cover all of the different features that argparse provides. If you need these features, you can provide your own parser as a kwarg to `autocommand`: - -```python -from argparse import ArgumentParser -from autocommand import autocommand - -parser = ArgumentParser() -# autocommand can't do optional positonal parameters -parser.add_argument('arg', nargs='?') -# or mutually exclusive options -group = parser.add_mutually_exclusive_group() -group.add_argument('-v', '--verbose', action='store_true') -group.add_argument('-q', '--quiet', action='store_true') - -@autocommand(__name__, parser=parser) -def main(arg, verbose, quiet): - print(arg, verbose, quiet) -``` - -``` -$ python parser.py -h -usage: write_file.py [-h] [-v | -q] [arg] - -positional arguments: - arg - -optional arguments: - -h, --help show this help message and exit - -v, --verbose - -q, --quiet -$ python parser.py -None False False -$ python parser.py hello -hello False False -$ python parser.py -v -None True False -$ python parser.py -q -None False True -$ python parser.py -vq -usage: parser.py [-h] [-v | -q] [arg] -parser.py: error: argument -q/--quiet: not allowed with argument -v/--verbose -``` - -Any parser should work fine, so long as each of the parser's arguments has a corresponding parameter in the decorated main function. The order of parameters doesn't matter, as long as they are all present. Note that when using a custom parser, autocommand doesn't modify the parser or the retrieved arguments. This means that no description/epilog will be added, and the function's type annotations and defaults (if present) will be ignored. - -## Testing and Library use - -The decorated function is only called and exited from if the first argument to `autocommand` is `'__main__'` or `True`. If it is neither of these values, or no argument is given, then a new main function is created by the decorator. This function has the signature `main(argv=None)`, and is intended to be called with arguments as if via `main(sys.argv[1:])`. The function has the attributes `parser` and `main`, which are the generated `ArgumentParser` and the original main function that was decorated. This is to facilitate testing and library use of your main. Calling the function triggers a `parse_args()` with the supplied arguments, and returns the result of the main function. Note that, while it returns instead of calling `sys.exit`, the `parse_args()` function will raise a `SystemExit` in the event of a parsing error or `-h/--help` argument. - -```python - @autocommand() - def test_prog(arg1, arg2: int, quiet=False, verbose=False): - if not quiet: - print(arg1, arg2) - if verbose: - print("LOUD NOISES") - - return 0 - - print(test_prog(['-v', 'hello', '80'])) -``` - -``` -$ python test_prog.py -hello 80 -LOUD NOISES -0 -``` - -If the function is called with no arguments, `sys.argv[1:]` is used. This is to allow the autocommand function to be used as a setuptools entry point. - -## Exceptions and limitations - -- There are a few possible exceptions that `autocommand` can raise. All of them derive from `autocommand.AutocommandError`. - - - If an invalid annotation is given (that is, it isn't a `type`, `str`, `(type, str)`, or `(str, type)`, an `AnnotationError` is raised. The `type` may be any callable, as described in the `Types`_ section. - - If the function has a `**kwargs` parameter, a `KWargError` is raised. - - If, somehow, the function has a positional-only parameter, a `PositionalArgError` is raised. This means that the argument doesn't have a name, which is currently not possible with a plain `def` or `lambda`, though many built-in functions have this kind of parameter. - -- There are a few argparse features that are not supported by autocommand. - - - It isn't possible to have an optional positional argument (as opposed to a `--option`). POSIX thinks this is bad form anyway. - - It isn't possible to have mutually exclusive arguments or options - - It isn't possible to have subcommands or subparsers, though I'm working on a few solutions involving classes or nested function definitions to allow this. - -## Development - -Autocommand cannot be important from the project root; this is to enforce separation of concerns and prevent accidental importing of `setup.py` or tests. To develop, install the project in editable mode: - -``` -$ python setup.py develop -``` - -This will create a link to the source files in the deployment directory, so that any source changes are reflected when it is imported. diff --git a/lib/autocommand-2.2.2.dist-info/RECORD b/lib/autocommand-2.2.2.dist-info/RECORD deleted file mode 100644 index 8ae60593..00000000 --- a/lib/autocommand-2.2.2.dist-info/RECORD +++ /dev/null @@ -1,18 +0,0 @@ -autocommand-2.2.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -autocommand-2.2.2.dist-info/LICENSE,sha256=reeNBJgtaZctREqOFKlPh6IzTdOFXMgDSOqOJAqg3y0,7634 -autocommand-2.2.2.dist-info/METADATA,sha256=OADZuR3O6iBlpu1ieTgzYul6w4uOVrk0P0BO5TGGAJk,15006 -autocommand-2.2.2.dist-info/RECORD,, -autocommand-2.2.2.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 -autocommand-2.2.2.dist-info/top_level.txt,sha256=AzfhgKKS8EdAwWUTSF8mgeVQbXOY9kokHB6kSqwwqu0,12 -autocommand/__init__.py,sha256=zko5Rnvolvb-UXjCx_2ArPTGBWwUK5QY4LIQIKYR7As,1037 -autocommand/__pycache__/__init__.cpython-311.pyc,, -autocommand/__pycache__/autoasync.cpython-311.pyc,, -autocommand/__pycache__/autocommand.cpython-311.pyc,, -autocommand/__pycache__/automain.cpython-311.pyc,, -autocommand/__pycache__/autoparse.cpython-311.pyc,, -autocommand/__pycache__/errors.cpython-311.pyc,, -autocommand/autoasync.py,sha256=AMdyrxNS4pqWJfP_xuoOcImOHWD-qT7x06wmKN1Vp-U,5680 -autocommand/autocommand.py,sha256=hmkEmQ72HtL55gnURVjDOnsfYlGd5lLXbvT4KG496Qw,2505 -autocommand/automain.py,sha256=A2b8i754Mxc_DjU9WFr6vqYDWlhz0cn8miu8d8EsxV8,2076 -autocommand/autoparse.py,sha256=WVWmZJPcbzUKXP40raQw_0HD8qPJ2V9VG1eFFmmnFxw,11642 -autocommand/errors.py,sha256=7aa3roh9Herd6nIKpQHNWEslWE8oq7GiHYVUuRqORnA,886 diff --git a/lib/autocommand-2.2.2.dist-info/WHEEL b/lib/autocommand-2.2.2.dist-info/WHEEL deleted file mode 100644 index 57e3d840..00000000 --- a/lib/autocommand-2.2.2.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.38.4) -Root-Is-Purelib: true -Tag: py3-none-any - diff --git a/lib/autocommand-2.2.2.dist-info/top_level.txt b/lib/autocommand-2.2.2.dist-info/top_level.txt deleted file mode 100644 index dda5158f..00000000 --- a/lib/autocommand-2.2.2.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -autocommand diff --git a/lib/autocommand/__init__.py b/lib/autocommand/__init__.py deleted file mode 100644 index 73fbfca6..00000000 --- a/lib/autocommand/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2014-2016 Nathan West -# -# This file is part of autocommand. -# -# autocommand is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# autocommand is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with autocommand. If not, see . - -# flake8 flags all these imports as unused, hence the NOQAs everywhere. - -from .automain import automain # NOQA -from .autoparse import autoparse, smart_open # NOQA -from .autocommand import autocommand # NOQA - -try: - from .autoasync import autoasync # NOQA -except ImportError: # pragma: no cover - pass diff --git a/lib/autocommand/autoasync.py b/lib/autocommand/autoasync.py deleted file mode 100644 index 688f7e05..00000000 --- a/lib/autocommand/autoasync.py +++ /dev/null @@ -1,142 +0,0 @@ -# Copyright 2014-2015 Nathan West -# -# This file is part of autocommand. -# -# autocommand is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# autocommand is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with autocommand. If not, see . - -from asyncio import get_event_loop, iscoroutine -from functools import wraps -from inspect import signature - - -async def _run_forever_coro(coro, args, kwargs, loop): - ''' - This helper function launches an async main function that was tagged with - forever=True. There are two possibilities: - - - The function is a normal function, which handles initializing the event - loop, which is then run forever - - The function is a coroutine, which needs to be scheduled in the event - loop, which is then run forever - - There is also the possibility that the function is a normal function - wrapping a coroutine function - - The function is therefore called unconditionally and scheduled in the event - loop if the return value is a coroutine object. - - The reason this is a separate function is to make absolutely sure that all - the objects created are garbage collected after all is said and done; we - do this to ensure that any exceptions raised in the tasks are collected - ASAP. - ''' - - # Personal note: I consider this an antipattern, as it relies on the use of - # unowned resources. The setup function dumps some stuff into the event - # loop where it just whirls in the ether without a well defined owner or - # lifetime. For this reason, there's a good chance I'll remove the - # forever=True feature from autoasync at some point in the future. - thing = coro(*args, **kwargs) - if iscoroutine(thing): - await thing - - -def autoasync(coro=None, *, loop=None, forever=False, pass_loop=False): - ''' - Convert an asyncio coroutine into a function which, when called, is - evaluted in an event loop, and the return value returned. This is intented - to make it easy to write entry points into asyncio coroutines, which - otherwise need to be explictly evaluted with an event loop's - run_until_complete. - - If `loop` is given, it is used as the event loop to run the coro in. If it - is None (the default), the loop is retreived using asyncio.get_event_loop. - This call is defered until the decorated function is called, so that - callers can install custom event loops or event loop policies after - @autoasync is applied. - - If `forever` is True, the loop is run forever after the decorated coroutine - is finished. Use this for servers created with asyncio.start_server and the - like. - - If `pass_loop` is True, the event loop object is passed into the coroutine - as the `loop` kwarg when the wrapper function is called. In this case, the - wrapper function's __signature__ is updated to remove this parameter, so - that autoparse can still be used on it without generating a parameter for - `loop`. - - This coroutine can be called with ( @autoasync(...) ) or without - ( @autoasync ) arguments. - - Examples: - - @autoasync - def get_file(host, port): - reader, writer = yield from asyncio.open_connection(host, port) - data = reader.read() - sys.stdout.write(data.decode()) - - get_file(host, port) - - @autoasync(forever=True, pass_loop=True) - def server(host, port, loop): - yield_from loop.create_server(Proto, host, port) - - server('localhost', 8899) - - ''' - if coro is None: - return lambda c: autoasync( - c, loop=loop, - forever=forever, - pass_loop=pass_loop) - - # The old and new signatures are required to correctly bind the loop - # parameter in 100% of cases, even if it's a positional parameter. - # NOTE: A future release will probably require the loop parameter to be - # a kwonly parameter. - if pass_loop: - old_sig = signature(coro) - new_sig = old_sig.replace(parameters=( - param for name, param in old_sig.parameters.items() - if name != "loop")) - - @wraps(coro) - def autoasync_wrapper(*args, **kwargs): - # Defer the call to get_event_loop so that, if a custom policy is - # installed after the autoasync decorator, it is respected at call time - local_loop = get_event_loop() if loop is None else loop - - # Inject the 'loop' argument. We have to use this signature binding to - # ensure it's injected in the correct place (positional, keyword, etc) - if pass_loop: - bound_args = old_sig.bind_partial() - bound_args.arguments.update( - loop=local_loop, - **new_sig.bind(*args, **kwargs).arguments) - args, kwargs = bound_args.args, bound_args.kwargs - - if forever: - local_loop.create_task(_run_forever_coro( - coro, args, kwargs, local_loop - )) - local_loop.run_forever() - else: - return local_loop.run_until_complete(coro(*args, **kwargs)) - - # Attach the updated signature. This allows 'pass_loop' to be used with - # autoparse - if pass_loop: - autoasync_wrapper.__signature__ = new_sig - - return autoasync_wrapper diff --git a/lib/autocommand/autocommand.py b/lib/autocommand/autocommand.py deleted file mode 100644 index 097e86de..00000000 --- a/lib/autocommand/autocommand.py +++ /dev/null @@ -1,70 +0,0 @@ -# Copyright 2014-2015 Nathan West -# -# This file is part of autocommand. -# -# autocommand is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# autocommand is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with autocommand. If not, see . - -from .autoparse import autoparse -from .automain import automain -try: - from .autoasync import autoasync -except ImportError: # pragma: no cover - pass - - -def autocommand( - module, *, - description=None, - epilog=None, - add_nos=False, - parser=None, - loop=None, - forever=False, - pass_loop=False): - - if callable(module): - raise TypeError('autocommand requires a module name argument') - - def autocommand_decorator(func): - # Step 1: if requested, run it all in an asyncio event loop. autoasync - # patches the __signature__ of the decorated function, so that in the - # event that pass_loop is True, the `loop` parameter of the original - # function will *not* be interpreted as a command-line argument by - # autoparse - if loop is not None or forever or pass_loop: - func = autoasync( - func, - loop=None if loop is True else loop, - pass_loop=pass_loop, - forever=forever) - - # Step 2: create parser. We do this second so that the arguments are - # parsed and passed *before* entering the asyncio event loop, if it - # exists. This simplifies the stack trace and ensures errors are - # reported earlier. It also ensures that errors raised during parsing & - # passing are still raised if `forever` is True. - func = autoparse( - func, - description=description, - epilog=epilog, - add_nos=add_nos, - parser=parser) - - # Step 3: call the function automatically if __name__ == '__main__' (or - # if True was provided) - func = automain(module)(func) - - return func - - return autocommand_decorator diff --git a/lib/autocommand/automain.py b/lib/autocommand/automain.py deleted file mode 100644 index 6cc45db6..00000000 --- a/lib/autocommand/automain.py +++ /dev/null @@ -1,59 +0,0 @@ -# Copyright 2014-2015 Nathan West -# -# This file is part of autocommand. -# -# autocommand is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# autocommand is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with autocommand. If not, see . - -import sys -from .errors import AutocommandError - - -class AutomainRequiresModuleError(AutocommandError, TypeError): - pass - - -def automain(module, *, args=(), kwargs=None): - ''' - This decorator automatically invokes a function if the module is being run - as the "__main__" module. Optionally, provide args or kwargs with which to - call the function. If `module` is "__main__", the function is called, and - the program is `sys.exit`ed with the return value. You can also pass `True` - to cause the function to be called unconditionally. If the function is not - called, it is returned unchanged by the decorator. - - Usage: - - @automain(__name__) # Pass __name__ to check __name__=="__main__" - def main(): - ... - - If __name__ is "__main__" here, the main function is called, and then - sys.exit called with the return value. - ''' - - # Check that @automain(...) was called, rather than @automain - if callable(module): - raise AutomainRequiresModuleError(module) - - if module == '__main__' or module is True: - if kwargs is None: - kwargs = {} - - # Use a function definition instead of a lambda for a neater traceback - def automain_decorator(main): - sys.exit(main(*args, **kwargs)) - - return automain_decorator - else: - return lambda main: main diff --git a/lib/autocommand/autoparse.py b/lib/autocommand/autoparse.py deleted file mode 100644 index 0276a3fa..00000000 --- a/lib/autocommand/autoparse.py +++ /dev/null @@ -1,333 +0,0 @@ -# Copyright 2014-2015 Nathan West -# -# This file is part of autocommand. -# -# autocommand is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# autocommand is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with autocommand. If not, see . - -import sys -from re import compile as compile_regex -from inspect import signature, getdoc, Parameter -from argparse import ArgumentParser -from contextlib import contextmanager -from functools import wraps -from io import IOBase -from autocommand.errors import AutocommandError - - -_empty = Parameter.empty - - -class AnnotationError(AutocommandError): - '''Annotation error: annotation must be a string, type, or tuple of both''' - - -class PositionalArgError(AutocommandError): - ''' - Postional Arg Error: autocommand can't handle postional-only parameters - ''' - - -class KWArgError(AutocommandError): - '''kwarg Error: autocommand can't handle a **kwargs parameter''' - - -class DocstringError(AutocommandError): - '''Docstring error''' - - -class TooManySplitsError(DocstringError): - ''' - The docstring had too many ---- section splits. Currently we only support - using up to a single split, to split the docstring into description and - epilog parts. - ''' - - -def _get_type_description(annotation): - ''' - Given an annotation, return the (type, description) for the parameter. - If you provide an annotation that is somehow both a string and a callable, - the behavior is undefined. - ''' - if annotation is _empty: - return None, None - elif callable(annotation): - return annotation, None - elif isinstance(annotation, str): - return None, annotation - elif isinstance(annotation, tuple): - try: - arg1, arg2 = annotation - except ValueError as e: - raise AnnotationError(annotation) from e - else: - if callable(arg1) and isinstance(arg2, str): - return arg1, arg2 - elif isinstance(arg1, str) and callable(arg2): - return arg2, arg1 - - raise AnnotationError(annotation) - - -def _add_arguments(param, parser, used_char_args, add_nos): - ''' - Add the argument(s) to an ArgumentParser (using add_argument) for a given - parameter. used_char_args is the set of -short options currently already in - use, and is updated (if necessary) by this function. If add_nos is True, - this will also add an inverse switch for all boolean options. For - instance, for the boolean parameter "verbose", this will create --verbose - and --no-verbose. - ''' - - # Impl note: This function is kept separate from make_parser because it's - # already very long and I wanted to separate out as much as possible into - # its own call scope, to prevent even the possibility of suble mutation - # bugs. - if param.kind is param.POSITIONAL_ONLY: - raise PositionalArgError(param) - elif param.kind is param.VAR_KEYWORD: - raise KWArgError(param) - - # These are the kwargs for the add_argument function. - arg_spec = {} - is_option = False - - # Get the type and default from the annotation. - arg_type, description = _get_type_description(param.annotation) - - # Get the default value - default = param.default - - # If there is no explicit type, and the default is present and not None, - # infer the type from the default. - if arg_type is None and default not in {_empty, None}: - arg_type = type(default) - - # Add default. The presence of a default means this is an option, not an - # argument. - if default is not _empty: - arg_spec['default'] = default - is_option = True - - # Add the type - if arg_type is not None: - # Special case for bool: make it just a --switch - if arg_type is bool: - if not default or default is _empty: - arg_spec['action'] = 'store_true' - else: - arg_spec['action'] = 'store_false' - - # Switches are always options - is_option = True - - # Special case for file types: make it a string type, for filename - elif isinstance(default, IOBase): - arg_spec['type'] = str - - # TODO: special case for list type. - # - How to specificy type of list members? - # - param: [int] - # - param: int =[] - # - action='append' vs nargs='*' - - else: - arg_spec['type'] = arg_type - - # nargs: if the signature includes *args, collect them as trailing CLI - # arguments in a list. *args can't have a default value, so it can never be - # an option. - if param.kind is param.VAR_POSITIONAL: - # TODO: consider depluralizing metavar/name here. - arg_spec['nargs'] = '*' - - # Add description. - if description is not None: - arg_spec['help'] = description - - # Get the --flags - flags = [] - name = param.name - - if is_option: - # Add the first letter as a -short option. - for letter in name[0], name[0].swapcase(): - if letter not in used_char_args: - used_char_args.add(letter) - flags.append('-{}'.format(letter)) - break - - # If the parameter is a --long option, or is a -short option that - # somehow failed to get a flag, add it. - if len(name) > 1 or not flags: - flags.append('--{}'.format(name)) - - arg_spec['dest'] = name - else: - flags.append(name) - - parser.add_argument(*flags, **arg_spec) - - # Create the --no- version for boolean switches - if add_nos and arg_type is bool: - parser.add_argument( - '--no-{}'.format(name), - action='store_const', - dest=name, - const=default if default is not _empty else False) - - -def make_parser(func_sig, description, epilog, add_nos): - ''' - Given the signature of a function, create an ArgumentParser - ''' - parser = ArgumentParser(description=description, epilog=epilog) - - used_char_args = {'h'} - - # Arange the params so that single-character arguments are first. This - # esnures they don't have to get --long versions. sorted is stable, so the - # parameters will otherwise still be in relative order. - params = sorted( - func_sig.parameters.values(), - key=lambda param: len(param.name) > 1) - - for param in params: - _add_arguments(param, parser, used_char_args, add_nos) - - return parser - - -_DOCSTRING_SPLIT = compile_regex(r'\n\s*-{4,}\s*\n') - - -def parse_docstring(docstring): - ''' - Given a docstring, parse it into a description and epilog part - ''' - if docstring is None: - return '', '' - - parts = _DOCSTRING_SPLIT.split(docstring) - - if len(parts) == 1: - return docstring, '' - elif len(parts) == 2: - return parts[0], parts[1] - else: - raise TooManySplitsError() - - -def autoparse( - func=None, *, - description=None, - epilog=None, - add_nos=False, - parser=None): - ''' - This decorator converts a function that takes normal arguments into a - function which takes a single optional argument, argv, parses it using an - argparse.ArgumentParser, and calls the underlying function with the parsed - arguments. If it is not given, sys.argv[1:] is used. This is so that the - function can be used as a setuptools entry point, as well as a normal main - function. sys.argv[1:] is not evaluated until the function is called, to - allow injecting different arguments for testing. - - It uses the argument signature of the function to create an - ArgumentParser. Parameters without defaults become positional parameters, - while parameters *with* defaults become --options. Use annotations to set - the type of the parameter. - - The `desctiption` and `epilog` parameters corrospond to the same respective - argparse parameters. If no description is given, it defaults to the - decorated functions's docstring, if present. - - If add_nos is True, every boolean option (that is, every parameter with a - default of True/False or a type of bool) will have a --no- version created - as well, which inverts the option. For instance, the --verbose option will - have a --no-verbose counterpart. These are not mutually exclusive- - whichever one appears last in the argument list will have precedence. - - If a parser is given, it is used instead of one generated from the function - signature. In this case, no parser is created; instead, the given parser is - used to parse the argv argument. The parser's results' argument names must - match up with the parameter names of the decorated function. - - The decorated function is attached to the result as the `func` attribute, - and the parser is attached as the `parser` attribute. - ''' - - # If @autoparse(...) is used instead of @autoparse - if func is None: - return lambda f: autoparse( - f, description=description, - epilog=epilog, - add_nos=add_nos, - parser=parser) - - func_sig = signature(func) - - docstr_description, docstr_epilog = parse_docstring(getdoc(func)) - - if parser is None: - parser = make_parser( - func_sig, - description or docstr_description, - epilog or docstr_epilog, - add_nos) - - @wraps(func) - def autoparse_wrapper(argv=None): - if argv is None: - argv = sys.argv[1:] - - # Get empty argument binding, to fill with parsed arguments. This - # object does all the heavy lifting of turning named arguments into - # into correctly bound *args and **kwargs. - parsed_args = func_sig.bind_partial() - parsed_args.arguments.update(vars(parser.parse_args(argv))) - - return func(*parsed_args.args, **parsed_args.kwargs) - - # TODO: attach an updated __signature__ to autoparse_wrapper, just in case. - - # Attach the wrapped function and parser, and return the wrapper. - autoparse_wrapper.func = func - autoparse_wrapper.parser = parser - return autoparse_wrapper - - -@contextmanager -def smart_open(filename_or_file, *args, **kwargs): - ''' - This context manager allows you to open a filename, if you want to default - some already-existing file object, like sys.stdout, which shouldn't be - closed at the end of the context. If the filename argument is a str, bytes, - or int, the file object is created via a call to open with the given *args - and **kwargs, sent to the context, and closed at the end of the context, - just like "with open(filename) as f:". If it isn't one of the openable - types, the object simply sent to the context unchanged, and left unclosed - at the end of the context. Example: - - def work_with_file(name=sys.stdout): - with smart_open(name) as f: - # Works correctly if name is a str filename or sys.stdout - print("Some stuff", file=f) - # If it was a filename, f is closed at the end here. - ''' - if isinstance(filename_or_file, (str, bytes, int)): - with open(filename_or_file, *args, **kwargs) as file: - yield file - else: - yield filename_or_file diff --git a/lib/autocommand/errors.py b/lib/autocommand/errors.py deleted file mode 100644 index 25706073..00000000 --- a/lib/autocommand/errors.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2014-2016 Nathan West -# -# This file is part of autocommand. -# -# autocommand is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# autocommand is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with autocommand. If not, see . - - -class AutocommandError(Exception): - '''Base class for autocommand exceptions''' - pass - -# Individual modules will define errors specific to that module. diff --git a/lib/bencodepy-0.9.5.dist-info/INSTALLER b/lib/bencodepy-0.9.5.dist-info/INSTALLER deleted file mode 100644 index a1b589e3..00000000 --- a/lib/bencodepy-0.9.5.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/lib/bencodepy-0.9.5.dist-info/METADATA b/lib/bencodepy-0.9.5.dist-info/METADATA deleted file mode 100644 index 88d28de7..00000000 --- a/lib/bencodepy-0.9.5.dist-info/METADATA +++ /dev/null @@ -1,9 +0,0 @@ -Metadata-Version: 2.1 -Name: bencodepy -Version: 0.9.5 -Summary: Bencode encoder/decoder written in Python 3 under the GPLv2. -Home-page: https://github.com/eweast/bencodepy_opti -Author: Eric Weast -Author-email: eweast@hotmail.com -License: GPLv2 - diff --git a/lib/bencodepy-0.9.5.dist-info/RECORD b/lib/bencodepy-0.9.5.dist-info/RECORD deleted file mode 100644 index 72686fdd..00000000 --- a/lib/bencodepy-0.9.5.dist-info/RECORD +++ /dev/null @@ -1,14 +0,0 @@ -bencodepy-0.9.5.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -bencodepy-0.9.5.dist-info/METADATA,sha256=KWhEYshYAo135OOnta9pnT9UId--0-n9rqnc1yZL9bQ,252 -bencodepy-0.9.5.dist-info/RECORD,, -bencodepy-0.9.5.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -bencodepy-0.9.5.dist-info/WHEEL,sha256=2wepM1nk4DS4eFpYrW1TTqPcoGNfHhhO_i5m4cOimbo,92 -bencodepy-0.9.5.dist-info/top_level.txt,sha256=i_Hh7rZWjL8V-SRMG8ogBBGPYj5StAwDzJJq4tfOgM8,10 -bencodepy/__init__.py,sha256=C5sLzPd1lxMKv0M3ex7zxuV3jVZThXMoKFNG6TRfVTM,271 -bencodepy/__pycache__/__init__.cpython-311.pyc,, -bencodepy/__pycache__/decoder.cpython-311.pyc,, -bencodepy/__pycache__/encode.cpython-311.pyc,, -bencodepy/__pycache__/exceptions.cpython-311.pyc,, -bencodepy/decoder.py,sha256=wduDICcrlWosssJLhe3afo0s5m5sND_Y8gU1ysxjTN0,3712 -bencodepy/encode.py,sha256=O-Nc0nycGBV9i-87748DKMKej4F-YkEhcPDXJ_rTGM8,2922 -bencodepy/exceptions.py,sha256=sDLGszKpspOqAfl5sP9Hidla-ne_OS-5I5Qd5zQJU2U,290 diff --git a/lib/bencodepy-0.9.5.dist-info/REQUESTED b/lib/bencodepy-0.9.5.dist-info/REQUESTED deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/bencodepy-0.9.5.dist-info/WHEEL b/lib/bencodepy-0.9.5.dist-info/WHEEL deleted file mode 100644 index 57e3d840..00000000 --- a/lib/bencodepy-0.9.5.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.38.4) -Root-Is-Purelib: true -Tag: py3-none-any - diff --git a/lib/bencodepy-0.9.5.dist-info/top_level.txt b/lib/bencodepy-0.9.5.dist-info/top_level.txt deleted file mode 100644 index 7c196035..00000000 --- a/lib/bencodepy-0.9.5.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -bencodepy diff --git a/lib/bencodepy/__init__.py b/lib/bencodepy/__init__.py deleted file mode 100644 index ea260347..00000000 --- a/lib/bencodepy/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -__author__ = 'Eric Weast' -__copyright__ = "Copyright 2014, Eric Weast" -__license__ = "GPL v2" - -from .exceptions import EncodingError -from .exceptions import DecodingError -from .decoder import decode -from .decoder import decode_from_file -from .encode import encode \ No newline at end of file diff --git a/lib/bencodepy/decoder.py b/lib/bencodepy/decoder.py deleted file mode 100644 index 6a197112..00000000 --- a/lib/bencodepy/decoder.py +++ /dev/null @@ -1,100 +0,0 @@ -from collections import OrderedDict -from collections.abc import Iterable -import bencodepy - - -class Decoder: - def __init__(self, data: bytes): - self.data = data - self.idx = 0 - - def __read(self, i: int) -> bytes: - """Returns a set number (i) of bytes from self.data.""" - b = self.data[self.idx: self.idx + i] - self.idx += i - if len(b) != i: - raise bencodepy.DecodingError( - "Incorrect byte length returned between indexes of {0} and {1}. Possible unexpected End of File." - .format(str(self.idx), str(self.idx - i))) - return b - - def __read_to(self, terminator: bytes) -> bytes: - """Returns bytes from self.data starting at index (self.idx) until terminator character.""" - try: - # noinspection PyTypeChecker - i = self.data.index(terminator, self.idx) - b = self.data[self.idx:i] - self.idx = i + 1 - return b - except ValueError: - raise bencodepy.DecodingError( - 'Unable to locate terminator character "{0}" after index {1}.'.format(str(terminator), str(self.idx))) - - def __parse(self) -> object: - """Selects the appropriate method to decode next bencode element and returns the result.""" - char = self.data[self.idx: self.idx + 1] - if char in [b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'0']: - str_len = int(self.__read_to(b':')) - return self.__read(str_len) - elif char == b'i': - self.idx += 1 - return int(self.__read_to(b'e')) - elif char == b'd': - return self.__parse_dict() - elif char == b'l': - return self.__parse_list() - elif char == b'': - raise bencodepy.DecodingError('Unexpected End of File at index position of {0}.'.format(str(self.idx))) - else: - raise bencodepy.DecodingError( - 'Invalid token character ({0}) at position {1}.'.format(str(char), str(self.idx))) - - def decode(self) -> Iterable: - """Start of decode process. Returns final results.""" - if self.data[0:1] not in (b'd', b'l'): - return self.__wrap_with_tuple() - return self.__parse() - - def __wrap_with_tuple(self) -> tuple: - """Returns a tuple of all nested bencode elements.""" - l = list() - length = len(self.data) - while self.idx < length: - l.append(self.__parse()) - return tuple(l) - - def __parse_dict(self) -> OrderedDict: - """Returns an Ordered Dictionary of nested bencode elements.""" - self.idx += 1 - d = OrderedDict() - key_name = None - while self.data[self.idx: self.idx + 1] != b'e': - if key_name is None: - key_name = self.__parse() - else: - d[key_name] = self.__parse() - key_name = None - self.idx += 1 - return d - - def __parse_list(self) -> list: - """Returns an list of nested bencode elements.""" - self.idx += 1 - l = [] - while self.data[self.idx: self.idx + 1] != b'e': - l.append(self.__parse()) - self.idx += 1 - return l - - -def decode_from_file(path: str) -> Iterable: - """Convenience function. Reads file and calls decode().""" - with open(path, 'rb') as f: - b = f.read() - return decode(b) - - -def decode(data: bytes) -> Iterable: - """Convenience function. Initializes Decoder class, calls decode method, and returns the result.""" - decoder = Decoder(data) - return decoder.decode() diff --git a/lib/bencodepy/encode.py b/lib/bencodepy/encode.py deleted file mode 100644 index 57667634..00000000 --- a/lib/bencodepy/encode.py +++ /dev/null @@ -1,87 +0,0 @@ -from . import EncodingError -from collections import OrderedDict - - -def encode(obj, encoding='utf-8', strict=True): - coded_byte_list = [] - - def __encode_str(s: str) -> None: - """Converts the input string to bytes and passes it the __encode_byte_str function for encoding.""" - b = bytes(s, encoding) - __encode_byte_str(b) - - def __encode_byte_str(b: bytes) -> None: - """Ben-encodes string from bytes.""" - nonlocal coded_byte_list - length = len(b) - coded_byte_list.append(bytes(str(length), encoding) + b':' + b) - - def __encode_int(i: int) -> None: - """Ben-encodes integer from int.""" - nonlocal coded_byte_list - coded_byte_list.append(b'i' + bytes(str(i), 'utf-8') + b'e') - - def __encode_tuple(t: tuple) -> None: - """Converts the input tuple to lists and passes it the __encode_list function for encoding.""" - l = [i for i in t] - __encode_list(l) - - def __encode_list(l: list) -> None: - """Ben-encodes list from list.""" - nonlocal coded_byte_list - coded_byte_list.append(b'l') - for i in l: - __select_encoder(i) - coded_byte_list.append(b'e') - - def __encode_dict(d: dict) -> None: - """Ben-encodes dictionary from dict.""" - nonlocal coded_byte_list - coded_byte_list.append(b'd') - for k in d: - __select_encoder(k) - __select_encoder(d[k]) - coded_byte_list.append(b'e') - - opt = { - bytes: lambda x: __encode_byte_str(x), - OrderedDict: lambda x: __encode_dict(x), - dict: lambda x: __encode_dict(x), - list: lambda x: __encode_list(x), - str: lambda x: __encode_str(x), - int: lambda x: __encode_int(x), - tuple: lambda x: __encode_tuple(x), - } - - def __select_encoder(o: object) -> bytes: - """Calls the appropriate function to encode the passed object (obj).""" - - nonlocal opt - - t = type(o) - if t in opt: - opt[t](o) - else: - if isinstance(o, bytes): - __encode_byte_str(o) - elif isinstance(o, dict): - __encode_dict(o) - elif isinstance(o, list): - __encode_list(o) - elif isinstance(o, str): - __encode_str(o) - elif isinstance(o, int): - __encode_int(o) - elif isinstance(o, tuple): - __encode_tuple(o) - else: - if strict: - nonlocal coded_byte_list - coded_byte_list = [] - raise EncodingError("Unable to encode object: {0}".format(o.__repr__())) - else: - print("Unable to encode object: {0}".format(str(o))) - - __select_encoder(obj) - - return b''.join(coded_byte_list) diff --git a/lib/bencodepy/exceptions.py b/lib/bencodepy/exceptions.py deleted file mode 100644 index 010a129b..00000000 --- a/lib/bencodepy/exceptions.py +++ /dev/null @@ -1,14 +0,0 @@ -class EncodingError(Exception): - def __init__(self, msg): - self.msg = msg - - def __str__(self): - return repr(self.msg) - - -class DecodingError(Exception): - def __init__(self, msg): - self.msg = msg - - def __str__(self): - return repr(self.msg) \ No newline at end of file diff --git a/lib/bin/calc-prorate.exe b/lib/bin/calc-prorate.exe deleted file mode 100644 index ba3bf2d3..00000000 Binary files a/lib/bin/calc-prorate.exe and /dev/null differ diff --git a/lib/bin/cheroot.exe b/lib/bin/cheroot.exe deleted file mode 100644 index 3f706d5b..00000000 Binary files a/lib/bin/cheroot.exe and /dev/null differ diff --git a/lib/bin/cherryd.exe b/lib/bin/cherryd.exe deleted file mode 100644 index 1a0528cc..00000000 Binary files a/lib/bin/cherryd.exe and /dev/null differ diff --git a/lib/bin/docutils.exe b/lib/bin/docutils.exe deleted file mode 100644 index d2e9357e..00000000 Binary files a/lib/bin/docutils.exe and /dev/null differ diff --git a/lib/bin/hachoir-grep.exe b/lib/bin/hachoir-grep.exe deleted file mode 100644 index 2fe1608e..00000000 Binary files a/lib/bin/hachoir-grep.exe and /dev/null differ diff --git a/lib/bin/hachoir-metadata.exe b/lib/bin/hachoir-metadata.exe deleted file mode 100644 index 5e07d730..00000000 Binary files a/lib/bin/hachoir-metadata.exe and /dev/null differ diff --git a/lib/bin/hachoir-strip.exe b/lib/bin/hachoir-strip.exe deleted file mode 100644 index c2916c7c..00000000 Binary files a/lib/bin/hachoir-strip.exe and /dev/null differ diff --git a/lib/bin/hachoir-urwid.exe b/lib/bin/hachoir-urwid.exe deleted file mode 100644 index 568e8d16..00000000 Binary files a/lib/bin/hachoir-urwid.exe and /dev/null differ diff --git a/lib/bin/hachoir-wx.exe b/lib/bin/hachoir-wx.exe deleted file mode 100644 index 31a44f93..00000000 Binary files a/lib/bin/hachoir-wx.exe and /dev/null differ diff --git a/lib/bin/m2r.exe b/lib/bin/m2r.exe deleted file mode 100644 index 792c4509..00000000 Binary files a/lib/bin/m2r.exe and /dev/null differ diff --git a/lib/bin/mako-render.exe b/lib/bin/mako-render.exe deleted file mode 100644 index ac95c813..00000000 Binary files a/lib/bin/mako-render.exe and /dev/null differ diff --git a/lib/bin/normalizer.exe b/lib/bin/normalizer.exe deleted file mode 100644 index ae1bd2d8..00000000 Binary files a/lib/bin/normalizer.exe and /dev/null differ diff --git a/lib/bin/rst2html.py b/lib/bin/rst2html.py deleted file mode 100644 index 91db115f..00000000 --- a/lib/bin/rst2html.py +++ /dev/null @@ -1,23 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# $Id: rst2html.py 8927 2022-01-03 23:50:05Z milde $ -# Author: David Goodger -# Copyright: This module has been placed in the public domain. - -""" -A minimal front end to the Docutils Publisher, producing HTML. -""" - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -from docutils.core import publish_cmdline, default_description - - -description = ('Generates (X)HTML documents from standalone reStructuredText ' - 'sources. ' + default_description) - -publish_cmdline(writer_name='html', description=description) diff --git a/lib/bin/rst2html4.py b/lib/bin/rst2html4.py deleted file mode 100644 index edd40343..00000000 --- a/lib/bin/rst2html4.py +++ /dev/null @@ -1,26 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# $Id: rst2html4.py 8927 2022-01-03 23:50:05Z milde $ -# Author: David Goodger -# Copyright: This module has been placed in the public domain. - -""" -A minimal front end to the Docutils Publisher, producing (X)HTML. - -The output conforms to XHTML 1.0 transitional -and almost to HTML 4.01 transitional (except for closing empty tags). -""" - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -from docutils.core import publish_cmdline, default_description - - -description = ('Generates (X)HTML documents from standalone reStructuredText ' - 'sources. ' + default_description) - -publish_cmdline(writer_name='html4', description=description) diff --git a/lib/bin/rst2html5.py b/lib/bin/rst2html5.py deleted file mode 100644 index 59eea0cd..00000000 --- a/lib/bin/rst2html5.py +++ /dev/null @@ -1,33 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe -# :Copyright: © 2015 Günter Milde. -# :License: Released under the terms of the `2-Clause BSD license`_, in short: -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. -# This file is offered as-is, without any warranty. -# -# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause -# -# Revision: $Revision: 9021 $ -# Date: $Date: 2022-03-04 16:54:22 +0100 (Fr, 04. Mär 2022) $ - -""" -A minimal front end to the Docutils Publisher, producing HTML 5 documents. - -The output is also valid XML. -""" - -try: - import locale # module missing in Jython - locale.setlocale(locale.LC_ALL, '') -except locale.Error: - pass - -from docutils.core import publish_cmdline, default_description - -description = ('Generates HTML5 documents from standalone ' - 'reStructuredText sources.\n' - + default_description) - -publish_cmdline(writer_name='html5', description=description) diff --git a/lib/bin/rst2latex.py b/lib/bin/rst2latex.py deleted file mode 100644 index 1f17548c..00000000 --- a/lib/bin/rst2latex.py +++ /dev/null @@ -1,26 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# $Id: rst2latex.py 8956 2022-01-20 10:11:44Z milde $ -# Author: David Goodger -# Copyright: This module has been placed in the public domain. - -""" -A minimal front end to the Docutils Publisher, producing LaTeX. -""" - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -from docutils.core import publish_cmdline - -description = ('Generates LaTeX documents from standalone reStructuredText ' - 'sources. ' - 'Reads from (default is stdin) and writes to ' - ' (default is stdout). See ' - ' for ' - 'the full reference.') - -publish_cmdline(writer_name='latex', description=description) diff --git a/lib/bin/rst2man.py b/lib/bin/rst2man.py deleted file mode 100644 index 5f0edac0..00000000 --- a/lib/bin/rst2man.py +++ /dev/null @@ -1,27 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# Author: -# Contact: grubert@users.sf.net -# Copyright: This module has been placed in the public domain. - -""" -man.py -====== - -This module provides a simple command line interface that uses the -man page writer to output from ReStructuredText source. -""" - -import locale -try: - locale.setlocale(locale.LC_ALL, '') -except: - pass - -from docutils.core import publish_cmdline, default_description -from docutils.writers import manpage - -description = ("Generates plain unix manual documents. " - + default_description) - -publish_cmdline(writer=manpage.Writer(), description=description) diff --git a/lib/bin/rst2odt.py b/lib/bin/rst2odt.py deleted file mode 100644 index f479e852..00000000 --- a/lib/bin/rst2odt.py +++ /dev/null @@ -1,28 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# $Id: rst2odt.py 8994 2022-01-29 16:28:17Z milde $ -# Author: Dave Kuhlman -# Copyright: This module has been placed in the public domain. - -""" -A front end to the Docutils Publisher, producing OpenOffice documents. -""" - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -from docutils.core import publish_cmdline_to_binary, default_description -from docutils.writers.odf_odt import Writer, Reader - - -description = ('Generates OpenDocument/OpenOffice/ODF documents from ' - 'standalone reStructuredText sources. ' + default_description) - - -writer = Writer() -reader = Reader() -output = publish_cmdline_to_binary(reader=reader, writer=writer, - description=description) diff --git a/lib/bin/rst2odt_prepstyles.py b/lib/bin/rst2odt_prepstyles.py deleted file mode 100644 index 9268bbf0..00000000 --- a/lib/bin/rst2odt_prepstyles.py +++ /dev/null @@ -1,65 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# $Id: rst2odt_prepstyles.py 8932 2022-01-05 14:59:31Z milde $ -# Author: Dave Kuhlman -# Copyright: This module has been placed in the public domain. - -""" -Fix a word-processor-generated styles.odt for odtwriter use: Drop page size -specifications from styles.xml in STYLE_FILE.odt. -""" - -# Author: Michael Schutte - -from lxml import etree -import sys -import zipfile -from tempfile import mkstemp -import shutil -import os - -NAMESPACES = { - "style": "urn:oasis:names:tc:opendocument:xmlns:style:1.0", - "fo": "urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" -} - - -def prepstyle(filename): - - zin = zipfile.ZipFile(filename) - styles = zin.read("styles.xml") - - root = etree.fromstring(styles) - for el in root.xpath("//style:page-layout-properties", - namespaces=NAMESPACES): - for attr in el.attrib: - if attr.startswith("{%s}" % NAMESPACES["fo"]): - del el.attrib[attr] - - tempname = mkstemp() - zout = zipfile.ZipFile(os.fdopen(tempname[0], "w"), "w", - zipfile.ZIP_DEFLATED) - - for item in zin.infolist(): - if item.filename == "styles.xml": - zout.writestr(item, etree.tostring(root)) - else: - zout.writestr(item, zin.read(item.filename)) - - zout.close() - zin.close() - shutil.move(tempname[1], filename) - - -def main(): - args = sys.argv[1:] - if len(args) != 1 or args[0] in ('-h', '--help'): - print(__doc__, file=sys.stderr) - print("Usage: %s STYLE_FILE.odt\n" % sys.argv[0], file=sys.stderr) - sys.exit(1) - filename = args[0] - prepstyle(filename) - - -if __name__ == '__main__': - main() diff --git a/lib/bin/rst2pseudoxml.py b/lib/bin/rst2pseudoxml.py deleted file mode 100644 index b2b3db27..00000000 --- a/lib/bin/rst2pseudoxml.py +++ /dev/null @@ -1,23 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# $Id: rst2pseudoxml.py 8927 2022-01-03 23:50:05Z milde $ -# Author: David Goodger -# Copyright: This module has been placed in the public domain. - -""" -A minimal front end to the Docutils Publisher, producing pseudo-XML. -""" - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -from docutils.core import publish_cmdline, default_description - - -description = ('Generates pseudo-XML from standalone reStructuredText ' - 'sources (for testing purposes). ' + default_description) - -publish_cmdline(description=description) diff --git a/lib/bin/rst2s5.py b/lib/bin/rst2s5.py deleted file mode 100644 index 6703a794..00000000 --- a/lib/bin/rst2s5.py +++ /dev/null @@ -1,24 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# $Id: rst2s5.py 8927 2022-01-03 23:50:05Z milde $ -# Author: Chris Liechti -# Copyright: This module has been placed in the public domain. - -""" -A minimal front end to the Docutils Publisher, producing HTML slides using -the S5 template system. -""" - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -from docutils.core import publish_cmdline, default_description - - -description = ('Generates S5 (X)HTML slideshow documents from standalone ' - 'reStructuredText sources. ' + default_description) - -publish_cmdline(writer_name='s5', description=description) diff --git a/lib/bin/rst2xetex.py b/lib/bin/rst2xetex.py deleted file mode 100644 index 5722c9b8..00000000 --- a/lib/bin/rst2xetex.py +++ /dev/null @@ -1,27 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# $Id: rst2xetex.py 8956 2022-01-20 10:11:44Z milde $ -# Author: Guenter Milde -# Copyright: This module has been placed in the public domain. - -""" -A minimal front end to the Docutils Publisher, producing Lua/XeLaTeX code. -""" - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -from docutils.core import publish_cmdline - -description = ('Generates LaTeX documents from standalone reStructuredText ' - 'sources for compilation with the Unicode-aware TeX variants ' - 'XeLaTeX or LuaLaTeX. ' - 'Reads from (default is stdin) and writes to ' - ' (default is stdout). See ' - ' for ' - 'the full reference.') - -publish_cmdline(writer_name='xetex', description=description) diff --git a/lib/bin/rst2xml.py b/lib/bin/rst2xml.py deleted file mode 100644 index a7e5be7f..00000000 --- a/lib/bin/rst2xml.py +++ /dev/null @@ -1,23 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# $Id: rst2xml.py 8927 2022-01-03 23:50:05Z milde $ -# Author: David Goodger -# Copyright: This module has been placed in the public domain. - -""" -A minimal front end to the Docutils Publisher, producing Docutils XML. -""" - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -from docutils.core import publish_cmdline, default_description - - -description = ('Generates Docutils-native XML from standalone ' - 'reStructuredText sources. ' + default_description) - -publish_cmdline(writer_name='xml', description=description) diff --git a/lib/bin/rstpep2html.py b/lib/bin/rstpep2html.py deleted file mode 100644 index 1d07e7d8..00000000 --- a/lib/bin/rstpep2html.py +++ /dev/null @@ -1,25 +0,0 @@ -#!C:\usr\local\github\barbequesauce\watcher3\.venv\Scripts\python.exe - -# $Id: rstpep2html.py 8927 2022-01-03 23:50:05Z milde $ -# Author: David Goodger -# Copyright: This module has been placed in the public domain. - -""" -A minimal front end to the Docutils Publisher, producing HTML from PEP -(Python Enhancement Proposal) documents. -""" - -try: - import locale - locale.setlocale(locale.LC_ALL, '') -except: - pass - -from docutils.core import publish_cmdline, default_description - - -description = ('Generates (X)HTML from reStructuredText-format PEP files. ' - + default_description) - -publish_cmdline(reader_name='pep', writer_name='pep_html', - description=description) diff --git a/lib/bin/xml2json.exe b/lib/bin/xml2json.exe deleted file mode 100644 index 10c995ed..00000000 Binary files a/lib/bin/xml2json.exe and /dev/null differ diff --git a/lib/certifi-2022.12.7.dist-info/INSTALLER b/lib/certifi-2022.12.7.dist-info/INSTALLER deleted file mode 100644 index a1b589e3..00000000 --- a/lib/certifi-2022.12.7.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/lib/certifi-2022.12.7.dist-info/LICENSE b/lib/certifi-2022.12.7.dist-info/LICENSE deleted file mode 100644 index 0a64774e..00000000 --- a/lib/certifi-2022.12.7.dist-info/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -This package contains a modified version of ca-bundle.crt: - -ca-bundle.crt -- Bundle of CA Root Certificates - -Certificate data from Mozilla as of: Thu Nov 3 19:04:19 2011# -This is a bundle of X.509 certificates of public Certificate Authorities -(CA). These were automatically extracted from Mozilla's root certificates -file (certdata.txt). This file can be found in the mozilla source tree: -https://hg.mozilla.org/mozilla-central/file/tip/security/nss/lib/ckfw/builtins/certdata.txt -It contains the certificates in PEM format and therefore -can be directly used with curl / libcurl / php_curl, or with -an Apache+mod_ssl webserver for SSL client authentication. -Just configure this file as the SSLCACertificateFile.# - -***** BEGIN LICENSE BLOCK ***** -This Source Code Form is subject to the terms of the Mozilla Public License, -v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain -one at http://mozilla.org/MPL/2.0/. - -***** END LICENSE BLOCK ***** -@(#) $RCSfile: certdata.txt,v $ $Revision: 1.80 $ $Date: 2011/11/03 15:11:58 $ diff --git a/lib/certifi-2022.12.7.dist-info/METADATA b/lib/certifi-2022.12.7.dist-info/METADATA deleted file mode 100644 index aeb1991a..00000000 --- a/lib/certifi-2022.12.7.dist-info/METADATA +++ /dev/null @@ -1,83 +0,0 @@ -Metadata-Version: 2.1 -Name: certifi -Version: 2022.12.7 -Summary: Python package for providing Mozilla's CA Bundle. -Home-page: https://github.com/certifi/python-certifi -Author: Kenneth Reitz -Author-email: me@kennethreitz.com -License: MPL-2.0 -Project-URL: Source, https://github.com/certifi/python-certifi -Platform: UNKNOWN -Classifier: Development Status :: 5 - Production/Stable -Classifier: Intended Audience :: Developers -Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0) -Classifier: Natural Language :: English -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Requires-Python: >=3.6 -License-File: LICENSE - -Certifi: Python SSL Certificates -================================ - -Certifi provides Mozilla's carefully curated collection of Root Certificates for -validating the trustworthiness of SSL certificates while verifying the identity -of TLS hosts. It has been extracted from the `Requests`_ project. - -Installation ------------- - -``certifi`` is available on PyPI. Simply install it with ``pip``:: - - $ pip install certifi - -Usage ------ - -To reference the installed certificate authority (CA) bundle, you can use the -built-in function:: - - >>> import certifi - - >>> certifi.where() - '/usr/local/lib/python3.7/site-packages/certifi/cacert.pem' - -Or from the command line:: - - $ python -m certifi - /usr/local/lib/python3.7/site-packages/certifi/cacert.pem - -Enjoy! - -1024-bit Root Certificates -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Browsers and certificate authorities have concluded that 1024-bit keys are -unacceptably weak for certificates, particularly root certificates. For this -reason, Mozilla has removed any weak (i.e. 1024-bit key) certificate from its -bundle, replacing it with an equivalent strong (i.e. 2048-bit or greater key) -certificate from the same CA. Because Mozilla removed these certificates from -its bundle, ``certifi`` removed them as well. - -In previous versions, ``certifi`` provided the ``certifi.old_where()`` function -to intentionally re-add the 1024-bit roots back into your bundle. This was not -recommended in production and therefore was removed at the end of 2018. - -.. _`Requests`: https://requests.readthedocs.io/en/master/ - -Addition/Removal of Certificates --------------------------------- - -Certifi does not support any addition/removal or other modification of the -CA trust store content. This project is intended to provide a reliable and -highly portable root of trust to python deployments. Look to upstream projects -for methods to use alternate trust. - - diff --git a/lib/certifi-2022.12.7.dist-info/RECORD b/lib/certifi-2022.12.7.dist-info/RECORD deleted file mode 100644 index a3a197dc..00000000 --- a/lib/certifi-2022.12.7.dist-info/RECORD +++ /dev/null @@ -1,14 +0,0 @@ -certifi-2022.12.7.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -certifi-2022.12.7.dist-info/LICENSE,sha256=oC9sY4-fuE0G93ZMOrCF2K9-2luTwWbaVDEkeQd8b7A,1052 -certifi-2022.12.7.dist-info/METADATA,sha256=chFpcxKhCPEQ3d8-Vz36zr2Micf1eQhKkFFk7_JvJNo,2911 -certifi-2022.12.7.dist-info/RECORD,, -certifi-2022.12.7.dist-info/WHEEL,sha256=ewwEueio1C2XeHTvT17n8dZUJgOvyCWCt0WVNLClP9o,92 -certifi-2022.12.7.dist-info/top_level.txt,sha256=KMu4vUCfsjLrkPbSNdgdekS-pVJzBAJFO__nI8NF6-U,8 -certifi/__init__.py,sha256=bK_nm9bLJzNvWZc2oZdiTwg2KWD4HSPBWGaM0zUDvMw,94 -certifi/__main__.py,sha256=xBBoj905TUWBLRGANOcf7oi6e-3dMP4cEoG9OyMs11g,243 -certifi/__pycache__/__init__.cpython-311.pyc,, -certifi/__pycache__/__main__.cpython-311.pyc,, -certifi/__pycache__/core.cpython-311.pyc,, -certifi/cacert.pem,sha256=LBHDzgj_xA05AxnHK8ENT5COnGNElNZe0svFUHMf1SQ,275233 -certifi/core.py,sha256=lhewz0zFb2b4ULsQurElmloYwQoecjWzPqY67P8T7iM,4219 -certifi/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/lib/certifi-2022.12.7.dist-info/WHEEL b/lib/certifi-2022.12.7.dist-info/WHEEL deleted file mode 100644 index 5bad85fd..00000000 --- a/lib/certifi-2022.12.7.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.37.0) -Root-Is-Purelib: true -Tag: py3-none-any - diff --git a/lib/certifi-2022.12.7.dist-info/top_level.txt b/lib/certifi-2022.12.7.dist-info/top_level.txt deleted file mode 100644 index 963eac53..00000000 --- a/lib/certifi-2022.12.7.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -certifi diff --git a/lib/certifi/__init__.py b/lib/certifi/__init__.py deleted file mode 100644 index a3546f12..00000000 --- a/lib/certifi/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .core import contents, where - -__all__ = ["contents", "where"] -__version__ = "2022.12.07" diff --git a/lib/certifi/__main__.py b/lib/certifi/__main__.py deleted file mode 100644 index 8945b5da..00000000 --- a/lib/certifi/__main__.py +++ /dev/null @@ -1,12 +0,0 @@ -import argparse - -from certifi import contents, where - -parser = argparse.ArgumentParser() -parser.add_argument("-c", "--contents", action="store_true") -args = parser.parse_args() - -if args.contents: - print(contents()) -else: - print(where()) diff --git a/lib/certifi/cacert.pem b/lib/certifi/cacert.pem deleted file mode 100644 index df9e4e3c..00000000 --- a/lib/certifi/cacert.pem +++ /dev/null @@ -1,4527 +0,0 @@ - -# Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA -# Label: "GlobalSign Root CA" -# Serial: 4835703278459707669005204 -# MD5 Fingerprint: 3e:45:52:15:09:51:92:e1:b7:5d:37:9f:b1:87:29:8a -# SHA1 Fingerprint: b1:bc:96:8b:d4:f4:9d:62:2a:a8:9a:81:f2:15:01:52:a4:1d:82:9c -# SHA256 Fingerprint: eb:d4:10:40:e4:bb:3e:c7:42:c9:e3:81:d3:1e:f2:a4:1a:48:b6:68:5c:96:e7:ce:f3:c1:df:6c:d4:33:1c:99 ------BEGIN CERTIFICATE----- -MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG -A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv -b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw -MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i -YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT -aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ -jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp -xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp -1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG -snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ -U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8 -9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B -AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz -yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE -38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP -AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad -DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME -HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A== ------END CERTIFICATE----- - -# Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Premium 2048 Secure Server CA" -# Serial: 946069240 -# MD5 Fingerprint: ee:29:31:bc:32:7e:9a:e6:e8:b5:f7:51:b4:34:71:90 -# SHA1 Fingerprint: 50:30:06:09:1d:97:d4:f5:ae:39:f7:cb:e7:92:7d:7d:65:2d:34:31 -# SHA256 Fingerprint: 6d:c4:71:72:e0:1c:bc:b0:bf:62:58:0d:89:5f:e2:b8:ac:9a:d4:f8:73:80:1e:0c:10:b9:c8:37:d2:1e:b1:77 ------BEGIN CERTIFICATE----- -MIIEKjCCAxKgAwIBAgIEOGPe+DANBgkqhkiG9w0BAQUFADCBtDEUMBIGA1UEChML -RW50cnVzdC5uZXQxQDA+BgNVBAsUN3d3dy5lbnRydXN0Lm5ldC9DUFNfMjA0OCBp -bmNvcnAuIGJ5IHJlZi4gKGxpbWl0cyBsaWFiLikxJTAjBgNVBAsTHChjKSAxOTk5 -IEVudHJ1c3QubmV0IExpbWl0ZWQxMzAxBgNVBAMTKkVudHJ1c3QubmV0IENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5ICgyMDQ4KTAeFw05OTEyMjQxNzUwNTFaFw0yOTA3 -MjQxNDE1MTJaMIG0MRQwEgYDVQQKEwtFbnRydXN0Lm5ldDFAMD4GA1UECxQ3d3d3 -LmVudHJ1c3QubmV0L0NQU18yMDQ4IGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxp -YWIuKTElMCMGA1UECxMcKGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDEzMDEG -A1UEAxMqRW50cnVzdC5uZXQgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgKDIwNDgp -MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArU1LqRKGsuqjIAcVFmQq -K0vRvwtKTY7tgHalZ7d4QMBzQshowNtTK91euHaYNZOLGp18EzoOH1u3Hs/lJBQe -sYGpjX24zGtLA/ECDNyrpUAkAH90lKGdCCmziAv1h3edVc3kw37XamSrhRSGlVuX -MlBvPci6Zgzj/L24ScF2iUkZ/cCovYmjZy/Gn7xxGWC4LeksyZB2ZnuU4q941mVT -XTzWnLLPKQP5L6RQstRIzgUyVYr9smRMDuSYB3Xbf9+5CFVghTAp+XtIpGmG4zU/ -HoZdenoVve8AjhUiVBcAkCaTvA5JaJG/+EfTnZVCwQ5N328mz8MYIWJmQ3DW1cAH -4QIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQUVeSB0RGAvtiJuQijMfmhJAkWuXAwDQYJKoZIhvcNAQEFBQADggEBADub -j1abMOdTmXx6eadNl9cZlZD7Bh/KM3xGY4+WZiT6QBshJ8rmcnPyT/4xmf3IDExo -U8aAghOY+rat2l098c5u9hURlIIM7j+VrxGrD9cv3h8Dj1csHsm7mhpElesYT6Yf -zX1XEC+bBAlahLVu2B064dae0Wx5XnkcFMXj0EyTO2U87d89vqbllRrDtRnDvV5b -u/8j72gZyxKTJ1wDLW8w0B62GqzeWvfRqqgnpv55gcR5mTNXuhKwqeBCbJPKVt7+ -bYQLCIt+jerXmCHG8+c8eS9enNFMFY3h7CI3zJpDC5fcgJCNs2ebb0gIFVbPv/Er -fF6adulZkMV8gzURZVE= ------END CERTIFICATE----- - -# Issuer: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Subject: CN=Baltimore CyberTrust Root O=Baltimore OU=CyberTrust -# Label: "Baltimore CyberTrust Root" -# Serial: 33554617 -# MD5 Fingerprint: ac:b6:94:a5:9c:17:e0:d7:91:52:9b:b1:97:06:a6:e4 -# SHA1 Fingerprint: d4:de:20:d0:5e:66:fc:53:fe:1a:50:88:2c:78:db:28:52:ca:e4:74 -# SHA256 Fingerprint: 16:af:57:a9:f6:76:b0:ab:12:60:95:aa:5e:ba:de:f2:2a:b3:11:19:d6:44:ac:95:cd:4b:93:db:f3:f2:6a:eb ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIEAgAAuTANBgkqhkiG9w0BAQUFADBaMQswCQYDVQQGEwJJ -RTESMBAGA1UEChMJQmFsdGltb3JlMRMwEQYDVQQLEwpDeWJlclRydXN0MSIwIAYD -VQQDExlCYWx0aW1vcmUgQ3liZXJUcnVzdCBSb290MB4XDTAwMDUxMjE4NDYwMFoX -DTI1MDUxMjIzNTkwMFowWjELMAkGA1UEBhMCSUUxEjAQBgNVBAoTCUJhbHRpbW9y -ZTETMBEGA1UECxMKQ3liZXJUcnVzdDEiMCAGA1UEAxMZQmFsdGltb3JlIEN5YmVy -VHJ1c3QgUm9vdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKMEuyKr -mD1X6CZymrV51Cni4eiVgLGw41uOKymaZN+hXe2wCQVt2yguzmKiYv60iNoS6zjr -IZ3AQSsBUnuId9Mcj8e6uYi1agnnc+gRQKfRzMpijS3ljwumUNKoUMMo6vWrJYeK -mpYcqWe4PwzV9/lSEy/CG9VwcPCPwBLKBsua4dnKM3p31vjsufFoREJIE9LAwqSu -XmD+tqYF/LTdB1kC1FkYmGP1pWPgkAx9XbIGevOF6uvUA65ehD5f/xXtabz5OTZy -dc93Uk3zyZAsuT3lySNTPx8kmCFcB5kpvcY67Oduhjprl3RjM71oGDHweI12v/ye -jl0qhqdNkNwnGjkCAwEAAaNFMEMwHQYDVR0OBBYEFOWdWTCCR1jMrPoIVDaGezq1 -BE3wMBIGA1UdEwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3 -DQEBBQUAA4IBAQCFDF2O5G9RaEIFoN27TyclhAO992T9Ldcw46QQF+vaKSm2eT92 -9hkTI7gQCvlYpNRhcL0EYWoSihfVCr3FvDB81ukMJY2GQE/szKN+OMY3EU/t3Wgx -jkzSswF07r51XgdIGn9w/xZchMB5hbgF/X++ZRGjD8ACtPhSNzkE1akxehi/oCr0 -Epn3o0WC4zxe9Z2etciefC7IpJ5OCBRLbf1wbWsaY71k5h+3zvDyny67G7fyUIhz -ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS -R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. -# Subject: CN=Entrust Root Certification Authority O=Entrust, Inc. OU=www.entrust.net/CPS is incorporated by reference/(c) 2006 Entrust, Inc. -# Label: "Entrust Root Certification Authority" -# Serial: 1164660820 -# MD5 Fingerprint: d6:a5:c3:ed:5d:dd:3e:00:c1:3d:87:92:1f:1d:3f:e4 -# SHA1 Fingerprint: b3:1e:b1:b7:40:e3:6c:84:02:da:dc:37:d4:4d:f5:d4:67:49:52:f9 -# SHA256 Fingerprint: 73:c1:76:43:4f:1b:c6:d5:ad:f4:5b:0e:76:e7:27:28:7c:8d:e5:76:16:c1:e6:e6:14:1a:2b:2c:bc:7d:8e:4c ------BEGIN CERTIFICATE----- -MIIEkTCCA3mgAwIBAgIERWtQVDANBgkqhkiG9w0BAQUFADCBsDELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xOTA3BgNVBAsTMHd3dy5lbnRydXN0 -Lm5ldC9DUFMgaXMgaW5jb3Jwb3JhdGVkIGJ5IHJlZmVyZW5jZTEfMB0GA1UECxMW -KGMpIDIwMDYgRW50cnVzdCwgSW5jLjEtMCsGA1UEAxMkRW50cnVzdCBSb290IENl -cnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA2MTEyNzIwMjM0MloXDTI2MTEyNzIw -NTM0MlowgbAxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMTkw -NwYDVQQLEzB3d3cuZW50cnVzdC5uZXQvQ1BTIGlzIGluY29ycG9yYXRlZCBieSBy -ZWZlcmVuY2UxHzAdBgNVBAsTFihjKSAyMDA2IEVudHJ1c3QsIEluYy4xLTArBgNV -BAMTJEVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJ -KoZIhvcNAQEBBQADggEPADCCAQoCggEBALaVtkNC+sZtKm9I35RMOVcF7sN5EUFo -Nu3s/poBj6E4KPz3EEZmLk0eGrEaTsbRwJWIsMn/MYszA9u3g3s+IIRe7bJWKKf4 -4LlAcTfFy0cOlypowCKVYhXbR9n10Cv/gkvJrT7eTNuQgFA/CYqEAOwwCj0Yzfv9 -KlmaI5UXLEWeH25DeW0MXJj+SKfFI0dcXv1u5x609mhF0YaDW6KKjbHjKYD+JXGI -rb68j6xSlkuqUY3kEzEZ6E5Nn9uss2rVvDlUccp6en+Q3X0dgNmBu1kmwhH+5pPi -94DkZfs0Nw4pgHBNrziGLp5/V6+eF67rHMsoIV+2HNjnogQi+dPa2MsCAwEAAaOB -sDCBrTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zArBgNVHRAEJDAi -gA8yMDA2MTEyNzIwMjM0MlqBDzIwMjYxMTI3MjA1MzQyWjAfBgNVHSMEGDAWgBRo -kORnpKZTgMeGZqTx90tD+4S9bTAdBgNVHQ4EFgQUaJDkZ6SmU4DHhmak8fdLQ/uE -vW0wHQYJKoZIhvZ9B0EABBAwDhsIVjcuMTo0LjADAgSQMA0GCSqGSIb3DQEBBQUA -A4IBAQCT1DCw1wMgKtD5Y+iRDAUgqV8ZyntyTtSx29CW+1RaGSwMCPeyvIWonX9t -O1KzKtvn1ISMY/YPyyYBkVBs9F8U4pN0wBOeMDpQ47RgxRzwIkSNcUesyBrJ6Zua -AGAT/3B+XxFNSRuzFVJ7yVTav52Vr2ua2J7p8eRDjeIRRDq/r72DQnNSi6q7pynP -9WQcCk3RvKqsnyrQ/39/2n3qse0wJcGE2jTSW3iDVuycNsMm4hH2Z0kdkquM++v/ -eu6FSqdQgPCnXEqULl8FmTxSQeDNtGPPAUO6nIPcj2A781q0tHuu2guQOHXvgR1m -0vdXcDazv/wor3ElhVsT/h5/WrQ8 ------END CERTIFICATE----- - -# Issuer: CN=AAA Certificate Services O=Comodo CA Limited -# Subject: CN=AAA Certificate Services O=Comodo CA Limited -# Label: "Comodo AAA Services root" -# Serial: 1 -# MD5 Fingerprint: 49:79:04:b0:eb:87:19:ac:47:b0:bc:11:51:9b:74:d0 -# SHA1 Fingerprint: d1:eb:23:a4:6d:17:d6:8f:d9:25:64:c2:f1:f1:60:17:64:d8:e3:49 -# SHA256 Fingerprint: d7:a7:a0:fb:5d:7e:27:31:d7:71:e9:48:4e:bc:de:f7:1d:5f:0c:3e:0a:29:48:78:2b:c8:3e:e0:ea:69:9e:f4 ------BEGIN CERTIFICATE----- -MIIEMjCCAxqgAwIBAgIBATANBgkqhkiG9w0BAQUFADB7MQswCQYDVQQGEwJHQjEb -MBkGA1UECAwSR3JlYXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHDAdTYWxmb3JkMRow -GAYDVQQKDBFDb21vZG8gQ0EgTGltaXRlZDEhMB8GA1UEAwwYQUFBIENlcnRpZmlj -YXRlIFNlcnZpY2VzMB4XDTA0MDEwMTAwMDAwMFoXDTI4MTIzMTIzNTk1OVowezEL -MAkGA1UEBhMCR0IxGzAZBgNVBAgMEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE -BwwHU2FsZm9yZDEaMBgGA1UECgwRQ29tb2RvIENBIExpbWl0ZWQxITAfBgNVBAMM -GEFBQSBDZXJ0aWZpY2F0ZSBTZXJ2aWNlczCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAL5AnfRu4ep2hxxNRUSOvkbIgwadwSr+GB+O5AL686tdUIoWMQua -BtDFcCLNSS1UY8y2bmhGC1Pqy0wkwLxyTurxFa70VJoSCsN6sjNg4tqJVfMiWPPe -3M/vg4aijJRPn2jymJBGhCfHdr/jzDUsi14HZGWCwEiwqJH5YZ92IFCokcdmtet4 -YgNW8IoaE+oxox6gmf049vYnMlhvB/VruPsUK6+3qszWY19zjNoFmag4qMsXeDZR -rOme9Hg6jc8P2ULimAyrL58OAd7vn5lJ8S3frHRNG5i1R8XlKdH5kBjHYpy+g8cm -ez6KJcfA3Z3mNWgQIJ2P2N7Sw4ScDV7oL8kCAwEAAaOBwDCBvTAdBgNVHQ4EFgQU -oBEKIz6W8Qfs4q8p74Klf9AwpLQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQF -MAMBAf8wewYDVR0fBHQwcjA4oDagNIYyaHR0cDovL2NybC5jb21vZG9jYS5jb20v -QUFBQ2VydGlmaWNhdGVTZXJ2aWNlcy5jcmwwNqA0oDKGMGh0dHA6Ly9jcmwuY29t -b2RvLm5ldC9BQUFDZXJ0aWZpY2F0ZVNlcnZpY2VzLmNybDANBgkqhkiG9w0BAQUF -AAOCAQEACFb8AvCb6P+k+tZ7xkSAzk/ExfYAWMymtrwUSWgEdujm7l3sAg9g1o1Q -GE8mTgHj5rCl7r+8dFRBv/38ErjHT1r0iWAFf2C3BUrz9vHCv8S5dIa2LX1rzNLz -Rt0vxuBqw8M0Ayx9lt1awg6nCpnBBYurDC/zXDrPbDdVCYfeU0BsWO/8tqtlbgT2 -G9w84FoVxp7Z8VlIMCFlA2zs6SFz7JsDoeA3raAVGI/6ugLOpyypEBMs1OUIJqsi -l2D4kF501KKaU73yqWjgom7C12yxow+ev+to51byrvLjKzg6CYG1a4XXvi3tPxq3 -smPi9WIsgtRqAEFQ8TmDn5XpNpaYbg== ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2" -# Serial: 1289 -# MD5 Fingerprint: 5e:39:7b:dd:f8:ba:ec:82:e9:ac:62:ba:0c:54:00:2b -# SHA1 Fingerprint: ca:3a:fb:cf:12:40:36:4b:44:b2:16:20:88:80:48:39:19:93:7c:f7 -# SHA256 Fingerprint: 85:a0:dd:7d:d7:20:ad:b7:ff:05:f8:3d:54:2b:20:9d:c7:ff:45:28:f7:d6:77:b1:83:89:fe:a5:e5:c4:9e:86 ------BEGIN CERTIFICATE----- -MIIFtzCCA5+gAwIBAgICBQkwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMjAeFw0wNjExMjQxODI3MDBaFw0zMTExMjQxODIzMzNaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCa -GMpLlA0ALa8DKYrwD4HIrkwZhR0In6spRIXzL4GtMh6QRr+jhiYaHv5+HBg6XJxg -Fyo6dIMzMH1hVBHL7avg5tKifvVrbxi3Cgst/ek+7wrGsxDp3MJGF/hd/aTa/55J -WpzmM+Yklvc/ulsrHHo1wtZn/qtmUIttKGAr79dgw8eTvI02kfN/+NsRE8Scd3bB -rrcCaoF6qUWD4gXmuVbBlDePSHFjIuwXZQeVikvfj8ZaCuWw419eaxGrDPmF60Tp -+ARz8un+XJiM9XOva7R+zdRcAitMOeGylZUtQofX1bOQQ7dsE/He3fbE+Ik/0XX1 -ksOR1YqI0JDs3G3eicJlcZaLDQP9nL9bFqyS2+r+eXyt66/3FsvbzSUr5R/7mp/i -Ucw6UwxI5g69ybR2BlLmEROFcmMDBOAENisgGQLodKcftslWZvB1JdxnwQ5hYIiz -PtGo/KPaHbDRsSNU30R2be1B2MGyIrZTHN81Hdyhdyox5C315eXbyOD/5YDXC2Og -/zOhD7osFRXql7PSorW+8oyWHhqPHWykYTe5hnMz15eWniN9gqRMgeKh0bpnX5UH -oycR7hYQe7xFSkyyBNKr79X9DFHOUGoIMfmR2gyPZFwDwzqLID9ujWc9Otb+fVuI -yV77zGHcizN300QyNQliBJIWENieJ0f7OyHj+OsdWwIDAQABo4GwMIGtMA8GA1Ud -EwEB/wQFMAMBAf8wCwYDVR0PBAQDAgEGMB0GA1UdDgQWBBQahGK8SEwzJQTU7tD2 -A8QZRtGUazBuBgNVHSMEZzBlgBQahGK8SEwzJQTU7tD2A8QZRtGUa6FJpEcwRTEL -MAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMT -ElF1b1ZhZGlzIFJvb3QgQ0EgMoICBQkwDQYJKoZIhvcNAQEFBQADggIBAD4KFk2f -BluornFdLwUvZ+YTRYPENvbzwCYMDbVHZF34tHLJRqUDGCdViXh9duqWNIAXINzn -g/iN/Ae42l9NLmeyhP3ZRPx3UIHmfLTJDQtyU/h2BwdBR5YM++CCJpNVjP4iH2Bl -fF/nJrP3MpCYUNQ3cVX2kiF495V5+vgtJodmVjB3pjd4M1IQWK4/YY7yarHvGH5K -WWPKjaJW1acvvFYfzznB4vsKqBUsfU16Y8Zsl0Q80m/DShcK+JDSV6IZUaUtl0Ha -B0+pUNqQjZRG4T7wlP0QADj1O+hA4bRuVhogzG9Yje0uRY/W6ZM/57Es3zrWIozc -hLsib9D45MY56QSIPMO661V6bYCZJPVsAfv4l7CUW+v90m/xd2gNNWQjrLhVoQPR -TUIZ3Ph1WVaj+ahJefivDrkRoHy3au000LYmYjgahwz46P0u05B/B5EqHdZ+XIWD -mbA4CD/pXvk1B+TJYm5Xf6dQlfe6yJvmjqIBxdZmv3lh8zwc4bmCXF2gw+nYSL0Z -ohEUGW6yhhtoPkg3Goi3XZZenMfvJ2II4pEZXNLxId26F0KCl3GBUzGpn/Z9Yr9y -4aOTHcyKJloJONDO1w2AFrR4pTqHTI2KpdVGl/IsELm8VCLAAVBpQ570su9t+Oza -8eOx79+Rj1QqCyXBJhnEUhAFZdWCEOrCMc0u ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3" -# Serial: 1478 -# MD5 Fingerprint: 31:85:3c:62:94:97:63:b9:aa:fd:89:4e:af:6f:e0:cf -# SHA1 Fingerprint: 1f:49:14:f7:d8:74:95:1d:dd:ae:02:c0:be:fd:3a:2d:82:75:51:85 -# SHA256 Fingerprint: 18:f1:fc:7f:20:5d:f8:ad:dd:eb:7f:e0:07:dd:57:e3:af:37:5a:9c:4d:8d:73:54:6b:f4:f1:fe:d1:e1:8d:35 ------BEGIN CERTIFICATE----- -MIIGnTCCBIWgAwIBAgICBcYwDQYJKoZIhvcNAQEFBQAwRTELMAkGA1UEBhMCQk0x -GTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxGzAZBgNVBAMTElF1b1ZhZGlzIFJv -b3QgQ0EgMzAeFw0wNjExMjQxOTExMjNaFw0zMTExMjQxOTA2NDRaMEUxCzAJBgNV -BAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMRswGQYDVQQDExJRdW9W -YWRpcyBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDM -V0IWVJzmmNPTTe7+7cefQzlKZbPoFog02w1ZkXTPkrgEQK0CSzGrvI2RaNggDhoB -4hp7Thdd4oq3P5kazethq8Jlph+3t723j/z9cI8LoGe+AaJZz3HmDyl2/7FWeUUr -H556VOijKTVopAFPD6QuN+8bv+OPEKhyq1hX51SGyMnzW9os2l2ObjyjPtr7guXd -8lyyBTNvijbO0BNO/79KDDRMpsMhvVAEVeuxu537RR5kFd5VAYwCdrXLoT9Cabwv -vWhDFlaJKjdhkf2mrk7AyxRllDdLkgbvBNDInIjbC3uBr7E9KsRlOni27tyAsdLT -mZw67mtaa7ONt9XOnMK+pUsvFrGeaDsGb659n/je7Mwpp5ijJUMv7/FfJuGITfhe -btfZFG4ZM2mnO4SJk8RTVROhUXhA+LjJou57ulJCg54U7QVSWllWp5f8nT8KKdjc -T5EOE7zelaTfi5m+rJsziO+1ga8bxiJTyPbH7pcUsMV8eFLI8M5ud2CEpukqdiDt -WAEXMJPpGovgc2PZapKUSU60rUqFxKMiMPwJ7Wgic6aIDFUhWMXhOp8q3crhkODZ -c6tsgLjoC2SToJyMGf+z0gzskSaHirOi4XCPLArlzW1oUevaPwV/izLmE1xr/l9A -4iLItLRkT9a6fUg+qGkM17uGcclzuD87nSVL2v9A6wIDAQABo4IBlTCCAZEwDwYD -VR0TAQH/BAUwAwEB/zCB4QYDVR0gBIHZMIHWMIHTBgkrBgEEAb5YAAMwgcUwgZMG -CCsGAQUFBwICMIGGGoGDQW55IHVzZSBvZiB0aGlzIENlcnRpZmljYXRlIGNvbnN0 -aXR1dGVzIGFjY2VwdGFuY2Ugb2YgdGhlIFF1b1ZhZGlzIFJvb3QgQ0EgMyBDZXJ0 -aWZpY2F0ZSBQb2xpY3kgLyBDZXJ0aWZpY2F0aW9uIFByYWN0aWNlIFN0YXRlbWVu -dC4wLQYIKwYBBQUHAgEWIWh0dHA6Ly93d3cucXVvdmFkaXNnbG9iYWwuY29tL2Nw -czALBgNVHQ8EBAMCAQYwHQYDVR0OBBYEFPLAE+CCQz777i9nMpY1XNu4ywLQMG4G -A1UdIwRnMGWAFPLAE+CCQz777i9nMpY1XNu4ywLQoUmkRzBFMQswCQYDVQQGEwJC -TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDEbMBkGA1UEAxMSUXVvVmFkaXMg -Um9vdCBDQSAzggIFxjANBgkqhkiG9w0BAQUFAAOCAgEAT62gLEz6wPJv92ZVqyM0 -7ucp2sNbtrCD2dDQ4iH782CnO11gUyeim/YIIirnv6By5ZwkajGxkHon24QRiSem -d1o417+shvzuXYO8BsbRd2sPbSQvS3pspweWyuOEn62Iix2rFo1bZhfZFvSLgNLd -+LJ2w/w4E6oM3kJpK27zPOuAJ9v1pkQNn1pVWQvVDVJIxa6f8i+AxeoyUDUSly7B -4f/xI4hROJ/yZlZ25w9Rl6VSDE1JUZU2Pb+iSwwQHYaZTKrzchGT5Or2m9qoXadN -t54CrnMAyNojA+j56hl0YgCUyyIgvpSnWbWCar6ZeXqp8kokUvd0/bpO5qgdAm6x -DYBEwa7TIzdfu4V8K5Iu6H6li92Z4b8nby1dqnuH/grdS/yO9SbkbnBCbjPsMZ57 -k8HkyWkaPcBrTiJt7qtYTcbQQcEr6k8Sh17rRdhs9ZgC06DYVYoGmRmioHfRMJ6s -zHXug/WwYjnPbFfiTNKRCw51KBuav/0aQ/HKd/s7j2G4aSgWQgRecCocIdiP4b0j -Wy10QJLZYxkNc91pvGJHvOB0K7Lrfb5BG7XARsWhIstfTsEokt4YutUqKLsRixeT -mJlglFwjz1onl14LBQaTNx47aTbrqZ5hHY8y2o4M1nQ+ewkk2gF3R8Q7zTSMmfXK -4SVhM7JZG+Ju1zdXtg2pEto= ------END CERTIFICATE----- - -# Issuer: O=SECOM Trust.net OU=Security Communication RootCA1 -# Subject: O=SECOM Trust.net OU=Security Communication RootCA1 -# Label: "Security Communication Root CA" -# Serial: 0 -# MD5 Fingerprint: f1:bc:63:6a:54:e0:b5:27:f5:cd:e7:1a:e3:4d:6e:4a -# SHA1 Fingerprint: 36:b1:2b:49:f9:81:9e:d7:4c:9e:bc:38:0f:c6:56:8f:5d:ac:b2:f7 -# SHA256 Fingerprint: e7:5e:72:ed:9f:56:0e:ec:6e:b4:80:00:73:a4:3f:c3:ad:19:19:5a:39:22:82:01:78:95:97:4a:99:02:6b:6c ------BEGIN CERTIFICATE----- -MIIDWjCCAkKgAwIBAgIBADANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJKUDEY -MBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYDVQQLEx5TZWN1cml0eSBDb21t -dW5pY2F0aW9uIFJvb3RDQTEwHhcNMDMwOTMwMDQyMDQ5WhcNMjMwOTMwMDQyMDQ5 -WjBQMQswCQYDVQQGEwJKUDEYMBYGA1UEChMPU0VDT00gVHJ1c3QubmV0MScwJQYD -VQQLEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQCzs/5/022x7xZ8V6UMbXaKL0u/ZPtM7orw8yl8 -9f/uKuDp6bpbZCKamm8sOiZpUQWZJtzVHGpxxpp9Hp3dfGzGjGdnSj74cbAZJ6kJ -DKaVv0uMDPpVmDvY6CKhS3E4eayXkmmziX7qIWgGmBSWh9JhNrxtJ1aeV+7AwFb9 -Ms+k2Y7CI9eNqPPYJayX5HA49LY6tJ07lyZDo6G8SVlyTCMwhwFY9k6+HGhWZq/N -QV3Is00qVUarH9oe4kA92819uZKAnDfdDJZkndwi92SL32HeFZRSFaB9UslLqCHJ -xrHty8OVYNEP8Ktw+N/LTX7s1vqr2b1/VPKl6Xn62dZ2JChzAgMBAAGjPzA9MB0G -A1UdDgQWBBSgc0mZaNyFW2XjmygvV5+9M7wHSDALBgNVHQ8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAaECpqLvkT115swW1F7NgE+vG -kl3g0dNq/vu+m22/xwVtWSDEHPC32oRYAmP6SBbvT6UL90qY8j+eG61Ha2POCEfr -Uj94nK9NrvjVT8+amCoQQTlSxN3Zmw7vkwGusi7KaEIkQmywszo+zenaSMQVy+n5 -Bw+SUEmK3TGXX8npN6o7WWWXlDLJs58+OmJYxUmtYg5xpTKqL8aJdkNAExNnPaJU -JRDL8Try2frbSVa7pv6nQTXD4IhhyYjH3zYQIphZ6rBK+1YWc26sTfcioU+tHXot -RSflMMFe8toTyyVCUZVHA4xsIcx0Qu1T/zOLjw9XARYvz6buyXAiFL39vmwLAw== ------END CERTIFICATE----- - -# Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com -# Label: "XRamp Global CA Root" -# Serial: 107108908803651509692980124233745014957 -# MD5 Fingerprint: a1:0b:44:b3:ca:10:d8:00:6e:9d:0f:d8:0f:92:0a:d1 -# SHA1 Fingerprint: b8:01:86:d1:eb:9c:86:a5:41:04:cf:30:54:f3:4c:52:b7:e5:58:c6 -# SHA256 Fingerprint: ce:cd:dc:90:50:99:d8:da:df:c5:b1:d2:09:b7:37:cb:e2:c1:8c:fb:2c:10:c0:ff:0b:cf:0d:32:86:fc:1a:a2 ------BEGIN CERTIFICATE----- -MIIEMDCCAxigAwIBAgIQUJRs7Bjq1ZxN1ZfvdY+grTANBgkqhkiG9w0BAQUFADCB -gjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3dy54cmFtcHNlY3VyaXR5LmNvbTEk -MCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2VydmljZXMgSW5jMS0wKwYDVQQDEyRY -UmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQxMTAxMTcx -NDA0WhcNMzUwMTAxMDUzNzE5WjCBgjELMAkGA1UEBhMCVVMxHjAcBgNVBAsTFXd3 -dy54cmFtcHNlY3VyaXR5LmNvbTEkMCIGA1UEChMbWFJhbXAgU2VjdXJpdHkgU2Vy -dmljZXMgSW5jMS0wKwYDVQQDEyRYUmFtcCBHbG9iYWwgQ2VydGlmaWNhdGlvbiBB -dXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCYJB69FbS6 -38eMpSe2OAtp87ZOqCwuIR1cRN8hXX4jdP5efrRKt6atH67gBhbim1vZZ3RrXYCP -KZ2GG9mcDZhtdhAoWORlsH9KmHmf4MMxfoArtYzAQDsRhtDLooY2YKTVMIJt2W7Q -DxIEM5dfT2Fa8OT5kavnHTu86M/0ay00fOJIYRyO82FEzG+gSqmUsE3a56k0enI4 -qEHMPJQRfevIpoy3hsvKMzvZPTeL+3o+hiznc9cKV6xkmxnr9A8ECIqsAxcZZPRa -JSKNNCyy9mgdEm3Tih4U2sSPpuIjhdV6Db1q4Ons7Be7QhtnqiXtRYMh/MHJfNVi -PvryxS3T/dRlAgMBAAGjgZ8wgZwwEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0P -BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMZPoj0GY4QJnM5i5ASs -jVy16bYbMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwueHJhbXBzZWN1cml0 -eS5jb20vWEdDQS5jcmwwEAYJKwYBBAGCNxUBBAMCAQEwDQYJKoZIhvcNAQEFBQAD -ggEBAJEVOQMBG2f7Shz5CmBbodpNl2L5JFMn14JkTpAuw0kbK5rc/Kh4ZzXxHfAR -vbdI4xD2Dd8/0sm2qlWkSLoC295ZLhVbO50WfUfXN+pfTXYSNrsf16GBBEYgoyxt -qZ4Bfj8pzgCT3/3JknOJiWSe5yvkHJEs0rnOfc5vMZnT5r7SHpDwCRR5XCOrTdLa -IR9NmXmd4c8nnxCbHIgNsIpkQTG4DmyQJKSbXHGPurt+HBvbaoAPIbzp26a3QPSy -i6mx5O+aGtA9aZnuqCij4Tyz8LIRnM98QObd50N9otg6tamN8jSZxNQQ4Qb9CYQQ -O+7ETPTsJ3xCwnR8gooJybQDJbw= ------END CERTIFICATE----- - -# Issuer: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Subject: O=The Go Daddy Group, Inc. OU=Go Daddy Class 2 Certification Authority -# Label: "Go Daddy Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 91:de:06:25:ab:da:fd:32:17:0c:bb:25:17:2a:84:67 -# SHA1 Fingerprint: 27:96:ba:e6:3f:18:01:e2:77:26:1b:a0:d7:77:70:02:8f:20:ee:e4 -# SHA256 Fingerprint: c3:84:6b:f2:4b:9e:93:ca:64:27:4c:0e:c6:7c:1e:cc:5e:02:4f:fc:ac:d2:d7:40:19:35:0e:81:fe:54:6a:e4 ------BEGIN CERTIFICATE----- -MIIEADCCAuigAwIBAgIBADANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEh -MB8GA1UEChMYVGhlIEdvIERhZGR5IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBE -YWRkeSBDbGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTA0MDYyOTE3 -MDYyMFoXDTM0MDYyOTE3MDYyMFowYzELMAkGA1UEBhMCVVMxITAfBgNVBAoTGFRo -ZSBHbyBEYWRkeSBHcm91cCwgSW5jLjExMC8GA1UECxMoR28gRGFkZHkgQ2xhc3Mg -MiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTCCASAwDQYJKoZIhvcNAQEBBQADggEN -ADCCAQgCggEBAN6d1+pXGEmhW+vXX0iG6r7d/+TvZxz0ZWizV3GgXne77ZtJ6XCA -PVYYYwhv2vLM0D9/AlQiVBDYsoHUwHU9S3/Hd8M+eKsaA7Ugay9qK7HFiH7Eux6w -wdhFJ2+qN1j3hybX2C32qRe3H3I2TqYXP2WYktsqbl2i/ojgC95/5Y0V4evLOtXi -EqITLdiOr18SPaAIBQi2XKVlOARFmR6jYGB0xUGlcmIbYsUfb18aQr4CUWWoriMY -avx4A6lNf4DD+qta/KFApMoZFv6yyO9ecw3ud72a9nmYvLEHZ6IVDd2gWMZEewo+ -YihfukEHU1jPEX44dMX4/7VpkI+EdOqXG68CAQOjgcAwgb0wHQYDVR0OBBYEFNLE -sNKR1EwRcbNhyz2h/t2oatTjMIGNBgNVHSMEgYUwgYKAFNLEsNKR1EwRcbNhyz2h -/t2oatTjoWekZTBjMQswCQYDVQQGEwJVUzEhMB8GA1UEChMYVGhlIEdvIERhZGR5 -IEdyb3VwLCBJbmMuMTEwLwYDVQQLEyhHbyBEYWRkeSBDbGFzcyAyIENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQAD -ggEBADJL87LKPpH8EsahB4yOd6AzBhRckB4Y9wimPQoZ+YeAEW5p5JYXMP80kWNy -OO7MHAGjHZQopDH2esRU1/blMVgDoszOYtuURXO1v0XJJLXVggKtI3lpjbi2Tc7P -TMozI+gciKqdi0FuFskg5YmezTvacPd+mSYgFFQlq25zheabIZ0KbIIOqPjCDPoQ -HmyW74cNxA9hi63ugyuV+I6ShHI56yDqg+2DzZduCLzrTia2cyvk0/ZM/iZx4mER -dEr/VxqHD3VILs9RaRegAhJhldXRQLIQTO7ErBBDpqWeCtWVYpoNz4iCxTIM5Cuf -ReYNnyicsbkqWletNw+vHX/bvZ8= ------END CERTIFICATE----- - -# Issuer: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Subject: O=Starfield Technologies, Inc. OU=Starfield Class 2 Certification Authority -# Label: "Starfield Class 2 CA" -# Serial: 0 -# MD5 Fingerprint: 32:4a:4b:bb:c8:63:69:9b:be:74:9a:c6:dd:1d:46:24 -# SHA1 Fingerprint: ad:7e:1c:28:b0:64:ef:8f:60:03:40:20:14:c3:d0:e3:37:0e:b5:8a -# SHA256 Fingerprint: 14:65:fa:20:53:97:b8:76:fa:a6:f0:a9:95:8e:55:90:e4:0f:cc:7f:aa:4f:b7:c2:c8:67:75:21:fb:5f:b6:58 ------BEGIN CERTIFICATE----- -MIIEDzCCAvegAwIBAgIBADANBgkqhkiG9w0BAQUFADBoMQswCQYDVQQGEwJVUzEl -MCMGA1UEChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMp -U3RhcmZpZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDQw -NjI5MTczOTE2WhcNMzQwNjI5MTczOTE2WjBoMQswCQYDVQQGEwJVUzElMCMGA1UE -ChMcU3RhcmZpZWxkIFRlY2hub2xvZ2llcywgSW5jLjEyMDAGA1UECxMpU3RhcmZp -ZWxkIENsYXNzIDIgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwggEgMA0GCSqGSIb3 -DQEBAQUAA4IBDQAwggEIAoIBAQC3Msj+6XGmBIWtDBFk385N78gDGIc/oav7PKaf -8MOh2tTYbitTkPskpD6E8J7oX+zlJ0T1KKY/e97gKvDIr1MvnsoFAZMej2YcOadN -+lq2cwQlZut3f+dZxkqZJRRU6ybH838Z1TBwj6+wRir/resp7defqgSHo9T5iaU0 -X9tDkYI22WY8sbi5gv2cOj4QyDvvBmVmepsZGD3/cVE8MC5fvj13c7JdBmzDI1aa -K4UmkhynArPkPw2vCHmCuDY96pzTNbO8acr1zJ3o/WSNF4Azbl5KXZnJHoe0nRrA -1W4TNSNe35tfPe/W93bC6j67eA0cQmdrBNj41tpvi/JEoAGrAgEDo4HFMIHCMB0G -A1UdDgQWBBS/X7fRzt0fhvRbVazc1xDCDqmI5zCBkgYDVR0jBIGKMIGHgBS/X7fR -zt0fhvRbVazc1xDCDqmI56FspGowaDELMAkGA1UEBhMCVVMxJTAjBgNVBAoTHFN0 -YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAsTKVN0YXJmaWVsZCBD -bGFzcyAyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggEAMAwGA1UdEwQFMAMBAf8w -DQYJKoZIhvcNAQEFBQADggEBAAWdP4id0ckaVaGsafPzWdqbAYcaT1epoXkJKtv3 -L7IezMdeatiDh6GX70k1PncGQVhiv45YuApnP+yz3SFmH8lU+nLMPUxA2IGvd56D -eruix/U0F47ZEUD0/CwqTRV/p2JdLiXTAAsgGh1o+Re49L2L7ShZ3U0WixeDyLJl -xy16paq8U4Zt3VekyvggQQto8PT7dL5WXXp59fkdheMtlb71cZBDzI0fmgAKhynp -VSJYACPq4xJDKVtHCN2MQWplBqjlIapBtJUhlbl90TSrE9atvNziPTnNvT51cKEY -WQPJIrSPnNVeKtelttQKbfi3QBFGmh95DmK/D5fs4C8fF5Q= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root CA" -# Serial: 17154717934120587862167794914071425081 -# MD5 Fingerprint: 87:ce:0b:7b:2a:0e:49:00:e1:58:71:9b:37:a8:93:72 -# SHA1 Fingerprint: 05:63:b8:63:0d:62:d7:5a:bb:c8:ab:1e:4b:df:b5:a8:99:b2:4d:43 -# SHA256 Fingerprint: 3e:90:99:b5:01:5e:8f:48:6c:00:bc:ea:9d:11:1e:e7:21:fa:ba:35:5a:89:bc:f1:df:69:56:1e:3d:c6:32:5c ------BEGIN CERTIFICATE----- -MIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0BAQUFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg+XESpa7c -JpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lTXDGEKvYP -mDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5a3/UsDg+ -wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g0I6QNcZ4 -VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1roV9Iq4/ -AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whfGHdPAgMB -AAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYun -pyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3cmbYMuRC -dWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmrEthngYTf -fwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+fT8r87cm -NW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5QZ7dsvfPx -H2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu838fYxAe -+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw8g== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root CA" -# Serial: 10944719598952040374951832963794454346 -# MD5 Fingerprint: 79:e4:a9:84:0d:7d:3a:96:d7:c0:4f:e2:43:4c:89:2e -# SHA1 Fingerprint: a8:98:5d:3a:65:e5:e5:c4:b2:d7:d6:6d:40:c6:dd:2f:b1:9c:54:36 -# SHA256 Fingerprint: 43:48:a0:e9:44:4c:78:cb:26:5e:05:8d:5e:89:44:b4:d8:4f:96:62:bd:26:db:25:7f:89:34:a4:43:c7:01:61 ------BEGIN CERTIFICATE----- -MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD -QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB -CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97 -nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt -43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P -T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4 -gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR -TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw -DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr -hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg -06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF -PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls -YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk -CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert High Assurance EV Root CA O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert High Assurance EV Root CA" -# Serial: 3553400076410547919724730734378100087 -# MD5 Fingerprint: d4:74:de:57:5c:39:b2:d3:9c:85:83:c5:c0:65:49:8a -# SHA1 Fingerprint: 5f:b7:ee:06:33:e2:59:db:ad:0c:4c:9a:e6:d3:8f:1a:61:c7:dc:25 -# SHA256 Fingerprint: 74:31:e5:f4:c3:c1:ce:46:90:77:4f:0b:61:e0:54:40:88:3b:a9:a0:1e:d0:0b:a6:ab:d7:80:6e:d3:b1:18:cf ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIQAqxcJmoLQJuPC3nyrkYldzANBgkqhkiG9w0BAQUFADBs -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j -ZSBFViBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTMxMTExMDAwMDAwMFowbDEL -MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 -LmRpZ2ljZXJ0LmNvbTErMCkGA1UEAxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug -RVYgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMbM5XPm -+9S75S0tMqbf5YE/yc0lSbZxKsPVlDRnogocsF9ppkCxxLeyj9CYpKlBWTrT3JTW -PNt0OKRKzE0lgvdKpVMSOO7zSW1xkX5jtqumX8OkhPhPYlG++MXs2ziS4wblCJEM -xChBVfvLWokVfnHoNb9Ncgk9vjo4UFt3MRuNs8ckRZqnrG0AFFoEt7oT61EKmEFB -Ik5lYYeBQVCmeVyJ3hlKV9Uu5l0cUyx+mM0aBhakaHPQNAQTXKFx01p8VdteZOE3 -hzBWBOURtCmAEvF5OYiiAhF8J2a3iLd48soKqDirCmTCv2ZdlYTBoSUeh10aUAsg -EsxBu24LUTi4S8sCAwEAAaNjMGEwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFLE+w2kD+L9HAdSYJhoIAu9jZCvDMB8GA1UdIwQYMBaA -FLE+w2kD+L9HAdSYJhoIAu9jZCvDMA0GCSqGSIb3DQEBBQUAA4IBAQAcGgaX3Nec -nzyIZgYIVyHbIUf4KmeqvxgydkAQV8GK83rZEWWONfqe/EW1ntlMMUu4kehDLI6z -eM7b41N5cdblIZQB2lWHmiRk9opmzN6cN82oNLFpmyPInngiK3BD41VHMWEZ71jF -hS9OMPagMRYjyOfiZRYzy78aG6A9+MpeizGLYAiJLQwGXFK3xPkKmNEVX58Svnw2 -Yzi9RKR/5CYrCsSXaQ3pjOLAEFe4yHYSkVXySGnYvCoCWw9E1CAx2/S6cCZdkGCe -vEsXCS+0yx5DaMkHJ8HSXPfqIbloEpw8nL+e/IBcm2PN7EeqJSdnoDfzAIJ9VNep -+OkuE6N36B9K ------END CERTIFICATE----- - -# Issuer: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Gold CA - G2 O=SwissSign AG -# Label: "SwissSign Gold CA - G2" -# Serial: 13492815561806991280 -# MD5 Fingerprint: 24:77:d9:a8:91:d1:3b:fa:88:2d:c2:ff:f8:cd:33:93 -# SHA1 Fingerprint: d8:c5:38:8a:b7:30:1b:1b:6e:d4:7a:e6:45:25:3a:6f:9f:1a:27:61 -# SHA256 Fingerprint: 62:dd:0b:e9:b9:f5:0a:16:3e:a0:f8:e7:5c:05:3b:1e:ca:57:ea:55:c8:68:8f:64:7c:68:81:f2:c8:35:7b:95 ------BEGIN CERTIFICATE----- -MIIFujCCA6KgAwIBAgIJALtAHEP1Xk+wMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV -BAYTAkNIMRUwEwYDVQQKEwxTd2lzc1NpZ24gQUcxHzAdBgNVBAMTFlN3aXNzU2ln -biBHb2xkIENBIC0gRzIwHhcNMDYxMDI1MDgzMDM1WhcNMzYxMDI1MDgzMDM1WjBF -MQswCQYDVQQGEwJDSDEVMBMGA1UEChMMU3dpc3NTaWduIEFHMR8wHQYDVQQDExZT -d2lzc1NpZ24gR29sZCBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC -CgKCAgEAr+TufoskDhJuqVAtFkQ7kpJcyrhdhJJCEyq8ZVeCQD5XJM1QiyUqt2/8 -76LQwB8CJEoTlo8jE+YoWACjR8cGp4QjK7u9lit/VcyLwVcfDmJlD909Vopz2q5+ -bbqBHH5CjCA12UNNhPqE21Is8w4ndwtrvxEvcnifLtg+5hg3Wipy+dpikJKVyh+c -6bM8K8vzARO/Ws/BtQpgvd21mWRTuKCWs2/iJneRjOBiEAKfNA+k1ZIzUd6+jbqE -emA8atufK+ze3gE/bk3lUIbLtK/tREDFylqM2tIrfKjuvqblCqoOpd8FUrdVxyJd -MmqXl2MT28nbeTZ7hTpKxVKJ+STnnXepgv9VHKVxaSvRAiTysybUa9oEVeXBCsdt -MDeQKuSeFDNeFhdVxVu1yzSJkvGdJo+hB9TGsnhQ2wwMC3wLjEHXuendjIj3o02y -MszYF9rNt85mndT9Xv+9lz4pded+p2JYryU0pUHHPbwNUMoDAw8IWh+Vc3hiv69y -FGkOpeUDDniOJihC8AcLYiAQZzlG+qkDzAQ4embvIIO1jEpWjpEA/I5cgt6IoMPi -aG59je883WX0XaxR7ySArqpWl2/5rX3aYT+YdzylkbYcjCbaZaIJbcHiVOO5ykxM -gI93e2CaHt+28kgeDrpOVG2Y4OGiGqJ3UM/EY5LsRxmd6+ZrzsECAwEAAaOBrDCB -qTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUWyV7 -lqRlUX64OfPAeGZe6Drn8O4wHwYDVR0jBBgwFoAUWyV7lqRlUX64OfPAeGZe6Drn -8O4wRgYDVR0gBD8wPTA7BglghXQBWQECAQEwLjAsBggrBgEFBQcCARYgaHR0cDov -L3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIBACe6 -45R88a7A3hfm5djV9VSwg/S7zV4Fe0+fdWavPOhWfvxyeDgD2StiGwC5+OlgzczO -UYrHUDFu4Up+GC9pWbY9ZIEr44OE5iKHjn3g7gKZYbge9LgriBIWhMIxkziWMaa5 -O1M/wySTVltpkuzFwbs4AOPsF6m43Md8AYOfMke6UiI0HTJ6CVanfCU2qT1L2sCC -bwq7EsiHSycR+R4tx5M/nttfJmtS2S6K8RTGRI0Vqbe/vd6mGu6uLftIdxf+u+yv -GPUqUfA5hJeVbG4bwyvEdGB5JbAKJ9/fXtI5z0V9QkvfsywexcZdylU6oJxpmo/a -77KwPJ+HbBIrZXAVUjEaJM9vMSNQH4xPjyPDdEFjHFWoFN0+4FFQz/EbMFYOkrCC -hdiDyyJkvC24JdVUorgG6q2SpCSgwYa1ShNqR88uC1aVVMvOmttqtKay20EIhid3 -92qgQmwLOM7XdVAyksLfKzAiSNDVQTglXaTpXZ/GlHXQRf0wl0OPkKsKx4ZzYEpp -Ld6leNcG2mqeSz53OiATIgHQv2ieY2BrNU0LbbqhPcCT4H8js1WtciVORvnSFu+w -ZMEBnunKoGqYDs/YYPIvSbjkQuE4NRb0yG5P94FW6LqjviOvrv1vA+ACOzB2+htt -Qc8Bsem4yWb02ybzOqR08kkkW8mw0FfB+j564ZfJ ------END CERTIFICATE----- - -# Issuer: CN=SwissSign Silver CA - G2 O=SwissSign AG -# Subject: CN=SwissSign Silver CA - G2 O=SwissSign AG -# Label: "SwissSign Silver CA - G2" -# Serial: 5700383053117599563 -# MD5 Fingerprint: e0:06:a1:c9:7d:cf:c9:fc:0d:c0:56:75:96:d8:62:13 -# SHA1 Fingerprint: 9b:aa:e5:9f:56:ee:21:cb:43:5a:be:25:93:df:a7:f0:40:d1:1d:cb -# SHA256 Fingerprint: be:6c:4d:a2:bb:b9:ba:59:b6:f3:93:97:68:37:42:46:c3:c0:05:99:3f:a9:8f:02:0d:1d:ed:be:d4:8a:81:d5 ------BEGIN CERTIFICATE----- -MIIFvTCCA6WgAwIBAgIITxvUL1S7L0swDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UE -BhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMYU3dpc3NTaWdu -IFNpbHZlciBDQSAtIEcyMB4XDTA2MTAyNTA4MzI0NloXDTM2MTAyNTA4MzI0Nlow -RzELMAkGA1UEBhMCQ0gxFTATBgNVBAoTDFN3aXNzU2lnbiBBRzEhMB8GA1UEAxMY -U3dpc3NTaWduIFNpbHZlciBDQSAtIEcyMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A -MIICCgKCAgEAxPGHf9N4Mfc4yfjDmUO8x/e8N+dOcbpLj6VzHVxumK4DV644N0Mv -Fz0fyM5oEMF4rhkDKxD6LHmD9ui5aLlV8gREpzn5/ASLHvGiTSf5YXu6t+WiE7br -YT7QbNHm+/pe7R20nqA1W6GSy/BJkv6FCgU+5tkL4k+73JU3/JHpMjUi0R86TieF -nbAVlDLaYQ1HTWBCrpJH6INaUFjpiou5XaHc3ZlKHzZnu0jkg7Y360g6rw9njxcH -6ATK72oxh9TAtvmUcXtnZLi2kUpCe2UuMGoM9ZDulebyzYLs2aFK7PayS+VFheZt -eJMELpyCbTapxDFkH4aDCyr0NQp4yVXPQbBH6TCfmb5hqAaEuSh6XzjZG6k4sIN/ -c8HDO0gqgg8hm7jMqDXDhBuDsz6+pJVpATqJAHgE2cn0mRmrVn5bi4Y5FZGkECwJ -MoBgs5PAKrYYC51+jUnyEEp/+dVGLxmSo5mnJqy7jDzmDrxHB9xzUfFwZC8I+bRH -HTBsROopN4WSaGa8gzj+ezku01DwH/teYLappvonQfGbGHLy9YR0SslnxFSuSGTf -jNFusB3hB48IHpmccelM2KX3RxIfdNFRnobzwqIjQAtz20um53MGjMGg6cFZrEb6 -5i/4z3GcRm25xBWNOHkDRUjvxF3XCO6HOSKGsg0PWEP3calILv3q1h8CAwEAAaOB -rDCBqTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU -F6DNweRBtjpbO8tFnb0cwpj6hlgwHwYDVR0jBBgwFoAUF6DNweRBtjpbO8tFnb0c -wpj6hlgwRgYDVR0gBD8wPTA7BglghXQBWQEDAQEwLjAsBggrBgEFBQcCARYgaHR0 -cDovL3JlcG9zaXRvcnkuc3dpc3NzaWduLmNvbS8wDQYJKoZIhvcNAQEFBQADggIB -AHPGgeAn0i0P4JUw4ppBf1AsX19iYamGamkYDHRJ1l2E6kFSGG9YrVBWIGrGvShp -WJHckRE1qTodvBqlYJ7YH39FkWnZfrt4csEGDyrOj4VwYaygzQu4OSlWhDJOhrs9 -xCrZ1x9y7v5RoSJBsXECYxqCsGKrXlcSH9/L3XWgwF15kIwb4FDm3jH+mHtwX6WQ -2K34ArZv02DdQEsixT2tOnqfGhpHkXkzuoLcMmkDlm4fS/Bx/uNncqCxv1yL5PqZ -IseEuRuNI5c/7SXgz2W79WEE790eslpBIlqhn10s6FvJbakMDHiqYMZWjwFaDGi8 -aRl5xB9+lwW/xekkUV7U1UtT7dkjWjYDZaPBA61BMPNGG4WQr2W11bHkFlt4dR2X -em1ZqSqPe97Dh4kQmUlzeMg9vVE1dCrV8X5pGyq7O70luJpaPXJhkGaH7gzWTdQR -dAtq/gsD/KNVV4n+SsuuWxcFyPKNIzFTONItaj+CuY0IavdeQXRuwxF+B6wpYJE/ -OMpXEA29MC/HpeZBoNquBYeaoKRlbEwJDIm6uNO5wJOKMPqN5ZprFQFOZ6raYlY+ -hAhm0sQ2fac+EPyI4NSA5QC9qvNOBqN6avlicuMJT+ubDgEj8Z+7fNzcbBGXJbLy -tGMU0gYqZ4yD9c7qB9iaah7s5Aq7KkzrCWA5zspi2C5u ------END CERTIFICATE----- - -# Issuer: CN=SecureTrust CA O=SecureTrust Corporation -# Subject: CN=SecureTrust CA O=SecureTrust Corporation -# Label: "SecureTrust CA" -# Serial: 17199774589125277788362757014266862032 -# MD5 Fingerprint: dc:32:c3:a7:6d:25:57:c7:68:09:9d:ea:2d:a9:a2:d1 -# SHA1 Fingerprint: 87:82:c6:c3:04:35:3b:cf:d2:96:92:d2:59:3e:7d:44:d9:34:ff:11 -# SHA256 Fingerprint: f1:c1:b5:0a:e5:a2:0d:d8:03:0e:c9:f6:bc:24:82:3d:d3:67:b5:25:57:59:b4:e7:1b:61:fc:e9:f7:37:5d:73 ------BEGIN CERTIFICATE----- -MIIDuDCCAqCgAwIBAgIQDPCOXAgWpa1Cf/DrJxhZ0DANBgkqhkiG9w0BAQUFADBI -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -FzAVBgNVBAMTDlNlY3VyZVRydXN0IENBMB4XDTA2MTEwNzE5MzExOFoXDTI5MTIz -MTE5NDA1NVowSDELMAkGA1UEBhMCVVMxIDAeBgNVBAoTF1NlY3VyZVRydXN0IENv -cnBvcmF0aW9uMRcwFQYDVQQDEw5TZWN1cmVUcnVzdCBDQTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKukgeWVzfX2FI7CT8rU4niVWJxB4Q2ZQCQXOZEz -Zum+4YOvYlyJ0fwkW2Gz4BERQRwdbvC4u/jep4G6pkjGnx29vo6pQT64lO0pGtSO -0gMdA+9tDWccV9cGrcrI9f4Or2YlSASWC12juhbDCE/RRvgUXPLIXgGZbf2IzIao -wW8xQmxSPmjL8xk037uHGFaAJsTQ3MBv396gwpEWoGQRS0S8Hvbn+mPeZqx2pHGj -7DaUaHp3pLHnDi+BeuK1cobvomuL8A/b01k/unK8RCSc43Oz969XL0Imnal0ugBS -8kvNU3xHCzaFDmapCJcWNFfBZveA4+1wVMeT4C4oFVmHursCAwEAAaOBnTCBmjAT -BgkrBgEEAYI3FAIEBh4EAEMAQTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQUQjK2FvoE/f5dS3rD/fdMQB1aQ68wNAYDVR0fBC0wKzApoCeg -JYYjaHR0cDovL2NybC5zZWN1cmV0cnVzdC5jb20vU1RDQS5jcmwwEAYJKwYBBAGC -NxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBADDtT0rhWDpSclu1pqNlGKa7UTt3 -6Z3q059c4EVlew3KW+JwULKUBRSuSceNQQcSc5R+DCMh/bwQf2AQWnL1mA6s7Ll/ -3XpvXdMc9P+IBWlCqQVxyLesJugutIxq/3HcuLHfmbx8IVQr5Fiiu1cprp6poxkm -D5kuCLDv/WnPmRoJjeOnnyvJNjR7JLN4TJUXpAYmHrZkUjZfYGfZnMUFdAvnZyPS -CPyI6a6Lf+Ew9Dd+/cYy2i2eRDAwbO4H3tI0/NL/QPZL9GZGBlSm8jIKYyYwa5vR -3ItHuuG51WLQoqD0ZwV4KWMabwTW+MZMo5qxN7SN5ShLHZ4swrhovO0C7jE= ------END CERTIFICATE----- - -# Issuer: CN=Secure Global CA O=SecureTrust Corporation -# Subject: CN=Secure Global CA O=SecureTrust Corporation -# Label: "Secure Global CA" -# Serial: 9751836167731051554232119481456978597 -# MD5 Fingerprint: cf:f4:27:0d:d4:ed:dc:65:16:49:6d:3d:da:bf:6e:de -# SHA1 Fingerprint: 3a:44:73:5a:e5:81:90:1f:24:86:61:46:1e:3b:9c:c4:5f:f5:3a:1b -# SHA256 Fingerprint: 42:00:f5:04:3a:c8:59:0e:bb:52:7d:20:9e:d1:50:30:29:fb:cb:d4:1c:a1:b5:06:ec:27:f1:5a:de:7d:ac:69 ------BEGIN CERTIFICATE----- -MIIDvDCCAqSgAwIBAgIQB1YipOjUiolN9BPI8PjqpTANBgkqhkiG9w0BAQUFADBK -MQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3QgQ29ycG9yYXRpb24x -GTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwHhcNMDYxMTA3MTk0MjI4WhcNMjkx -MjMxMTk1MjA2WjBKMQswCQYDVQQGEwJVUzEgMB4GA1UEChMXU2VjdXJlVHJ1c3Qg -Q29ycG9yYXRpb24xGTAXBgNVBAMTEFNlY3VyZSBHbG9iYWwgQ0EwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvNS7YrGxVaQZx5RNoJLNP2MwhR/jxYDiJ -iQPpvepeRlMJ3Fz1Wuj3RSoC6zFh1ykzTM7HfAo3fg+6MpjhHZevj8fcyTiW89sa -/FHtaMbQbqR8JNGuQsiWUGMu4P51/pinX0kuleM5M2SOHqRfkNJnPLLZ/kG5VacJ -jnIFHovdRIWCQtBJwB1g8NEXLJXr9qXBkqPFwqcIYA1gBBCWeZ4WNOaptvolRTnI -HmX5k/Wq8VLcmZg9pYYaDDUz+kulBAYVHDGA76oYa8J719rO+TMg1fW9ajMtgQT7 -sFzUnKPiXB3jqUJ1XnvUd+85VLrJChgbEplJL4hL/VBi0XPnj3pDAgMBAAGjgZ0w -gZowEwYJKwYBBAGCNxQCBAYeBABDAEEwCwYDVR0PBAQDAgGGMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFK9EBMJBfkiD2045AuzshHrmzsmkMDQGA1UdHwQtMCsw -KaAnoCWGI2h0dHA6Ly9jcmwuc2VjdXJldHJ1c3QuY29tL1NHQ0EuY3JsMBAGCSsG -AQQBgjcVAQQDAgEAMA0GCSqGSIb3DQEBBQUAA4IBAQBjGghAfaReUw132HquHw0L -URYD7xh8yOOvaliTFGCRsoTciE6+OYo68+aCiV0BN7OrJKQVDpI1WkpEXk5X+nXO -H0jOZvQ8QCaSmGwb7iRGDBezUqXbpZGRzzfTb+cnCDpOGR86p1hcF895P4vkp9Mm -I50mD1hp/Ed+stCNi5O/KU9DaXR2Z0vPB4zmAve14bRDtUstFJ/53CYNv6ZHdAbY -iNE6KTCEztI5gGIbqMdXSbxqVVFnFUq+NQfk1XWYN3kwFNspnWzFacxHVaIw98xc -f8LDmBxrThaA63p4ZUWiABqvDA1VZDRIuJK58bRQKfJPIx/abKwfROHdI3hRW8cW ------END CERTIFICATE----- - -# Issuer: CN=COMODO Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO Certification Authority O=COMODO CA Limited -# Label: "COMODO Certification Authority" -# Serial: 104350513648249232941998508985834464573 -# MD5 Fingerprint: 5c:48:dc:f7:42:72:ec:56:94:6d:1c:cc:71:35:80:75 -# SHA1 Fingerprint: 66:31:bf:9e:f7:4f:9e:b6:c9:d5:a6:0c:ba:6a:be:d1:f7:bd:ef:7b -# SHA256 Fingerprint: 0c:2c:d6:3d:f7:80:6f:a3:99:ed:e8:09:11:6b:57:5b:f8:79:89:f0:65:18:f9:80:8c:86:05:03:17:8b:af:66 ------BEGIN CERTIFICATE----- -MIIEHTCCAwWgAwIBAgIQToEtioJl4AsC7j41AkblPTANBgkqhkiG9w0BAQUFADCB -gTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxJzAlBgNV -BAMTHkNPTU9ETyBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEyMDEwMDAw -MDBaFw0yOTEyMzEyMzU5NTlaMIGBMQswCQYDVQQGEwJHQjEbMBkGA1UECBMSR3Jl -YXRlciBNYW5jaGVzdGVyMRAwDgYDVQQHEwdTYWxmb3JkMRowGAYDVQQKExFDT01P -RE8gQ0EgTGltaXRlZDEnMCUGA1UEAxMeQ09NT0RPIENlcnRpZmljYXRpb24gQXV0 -aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0ECLi3LjkRv3 -UcEbVASY06m/weaKXTuH+7uIzg3jLz8GlvCiKVCZrts7oVewdFFxze1CkU1B/qnI -2GqGd0S7WWaXUF601CxwRM/aN5VCaTwwxHGzUvAhTaHYujl8HJ6jJJ3ygxaYqhZ8 -Q5sVW7euNJH+1GImGEaaP+vB+fGQV+useg2L23IwambV4EajcNxo2f8ESIl33rXp -+2dtQem8Ob0y2WIC8bGoPW43nOIv4tOiJovGuFVDiOEjPqXSJDlqR6sA1KGzqSX+ -DT+nHbrTUcELpNqsOO9VUCQFZUaTNE8tja3G1CEZ0o7KBWFxB3NH5YoZEr0ETc5O -nKVIrLsm9wIDAQABo4GOMIGLMB0GA1UdDgQWBBQLWOWLxkwVN6RAqTCpIb5HNlpW -/zAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zBJBgNVHR8EQjBAMD6g -PKA6hjhodHRwOi8vY3JsLmNvbW9kb2NhLmNvbS9DT01PRE9DZXJ0aWZpY2F0aW9u -QXV0aG9yaXR5LmNybDANBgkqhkiG9w0BAQUFAAOCAQEAPpiem/Yb6dc5t3iuHXIY -SdOH5EOC6z/JqvWote9VfCFSZfnVDeFs9D6Mk3ORLgLETgdxb8CPOGEIqB6BCsAv -IC9Bi5HcSEW88cbeunZrM8gALTFGTO3nnc+IlP8zwFboJIYmuNg4ON8qa90SzMc/ -RxdMosIGlgnW2/4/PEZB31jiVg88O8EckzXZOFKs7sjsLjBOlDW0JB9LeGna8gI4 -zJVSk/BwJVmcIGfE7vmLV2H0knZ9P4SNVbfo5azV8fUZVqZa+5Acr5Pr5RzUZ5dd -BA6+C4OmF4O5MBKgxTMVBbkN+8cFduPYSo38NBejxiEovjBFMR7HeL5YYTisO+IB -ZQ== ------END CERTIFICATE----- - -# Issuer: CN=COMODO ECC Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO ECC Certification Authority O=COMODO CA Limited -# Label: "COMODO ECC Certification Authority" -# Serial: 41578283867086692638256921589707938090 -# MD5 Fingerprint: 7c:62:ff:74:9d:31:53:5e:68:4a:d5:78:aa:1e:bf:23 -# SHA1 Fingerprint: 9f:74:4e:9f:2b:4d:ba:ec:0f:31:2c:50:b6:56:3b:8e:2d:93:c3:11 -# SHA256 Fingerprint: 17:93:92:7a:06:14:54:97:89:ad:ce:2f:8f:34:f7:f0:b6:6d:0f:3a:e3:a3:b8:4d:21:ec:15:db:ba:4f:ad:c7 ------BEGIN CERTIFICATE----- -MIICiTCCAg+gAwIBAgIQH0evqmIAcFBUTAGem2OZKjAKBggqhkjOPQQDAzCBhTEL -MAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UE -BxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMT -IkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwMzA2MDAw -MDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdy -ZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09N -T0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBFQ0MgQ2VydGlmaWNhdGlv -biBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQDR3svdcmCFYX7deSR -FtSrYpn1PlILBs5BAH+X4QokPB0BBO490o0JlwzgdeT6+3eKKvUDYEs2ixYjFq0J -cfRK9ChQtP6IHG4/bC8vCVlbpVsLM5niwz2J+Wos77LTBumjQjBAMB0GA1UdDgQW -BBR1cacZSBm8nZ3qQUfflMRId5nTeTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ -BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjEA7wNbeqy3eApyt4jf/7VGFAkK+qDm -fQjGGoe9GKhzvSbKYAydzpmfz1wPMOG+FDHqAjAU9JM8SaczepBGR7NjfRObTrdv -GDeAU/7dIOA1mjbRxwG55tzd8/8dLDoWV9mSOdY= ------END CERTIFICATE----- - -# Issuer: CN=Certigna O=Dhimyotis -# Subject: CN=Certigna O=Dhimyotis -# Label: "Certigna" -# Serial: 18364802974209362175 -# MD5 Fingerprint: ab:57:a6:5b:7d:42:82:19:b5:d8:58:26:28:5e:fd:ff -# SHA1 Fingerprint: b1:2e:13:63:45:86:a4:6f:1a:b2:60:68:37:58:2d:c4:ac:fd:94:97 -# SHA256 Fingerprint: e3:b6:a2:db:2e:d7:ce:48:84:2f:7a:c5:32:41:c7:b7:1d:54:14:4b:fb:40:c1:1f:3f:1d:0b:42:f5:ee:a1:2d ------BEGIN CERTIFICATE----- -MIIDqDCCApCgAwIBAgIJAP7c4wEPyUj/MA0GCSqGSIb3DQEBBQUAMDQxCzAJBgNV -BAYTAkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hMB4X -DTA3MDYyOTE1MTMwNVoXDTI3MDYyOTE1MTMwNVowNDELMAkGA1UEBhMCRlIxEjAQ -BgNVBAoMCURoaW15b3RpczERMA8GA1UEAwwIQ2VydGlnbmEwggEiMA0GCSqGSIb3 -DQEBAQUAA4IBDwAwggEKAoIBAQDIaPHJ1tazNHUmgh7stL7qXOEm7RFHYeGifBZ4 -QCHkYJ5ayGPhxLGWkv8YbWkj4Sti993iNi+RB7lIzw7sebYs5zRLcAglozyHGxny -gQcPOJAZ0xH+hrTy0V4eHpbNgGzOOzGTtvKg0KmVEn2lmsxryIRWijOp5yIVUxbw -zBfsV1/pogqYCd7jX5xv3EjjhQsVWqa6n6xI4wmy9/Qy3l40vhx4XUJbzg4ij02Q -130yGLMLLGq/jj8UEYkgDncUtT2UCIf3JR7VsmAA7G8qKCVuKj4YYxclPz5EIBb2 -JsglrgVKtOdjLPOMFlN+XPsRGgjBRmKfIrjxwo1p3Po6WAbfAgMBAAGjgbwwgbkw -DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUGu3+QTmQtCRZvgHyUtVF9lo53BEw -ZAYDVR0jBF0wW4AUGu3+QTmQtCRZvgHyUtVF9lo53BGhOKQ2MDQxCzAJBgNVBAYT -AkZSMRIwEAYDVQQKDAlEaGlteW90aXMxETAPBgNVBAMMCENlcnRpZ25hggkA/tzj -AQ/JSP8wDgYDVR0PAQH/BAQDAgEGMBEGCWCGSAGG+EIBAQQEAwIABzANBgkqhkiG -9w0BAQUFAAOCAQEAhQMeknH2Qq/ho2Ge6/PAD/Kl1NqV5ta+aDY9fm4fTIrv0Q8h -bV6lUmPOEvjvKtpv6zf+EwLHyzs+ImvaYS5/1HI93TDhHkxAGYwP15zRgzB7mFnc -fca5DClMoTOi62c6ZYTTluLtdkVwj7Ur3vkj1kluPBS1xp81HlDQwY9qcEQCYsuu -HWhBp6pX6FOqB9IG9tUUBguRA3UsbHK1YZWaDYu5Def131TN3ubY1gkIl2PlwS6w -t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw -WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== ------END CERTIFICATE----- - -# Issuer: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Subject: O=Chunghwa Telecom Co., Ltd. OU=ePKI Root Certification Authority -# Label: "ePKI Root Certification Authority" -# Serial: 28956088682735189655030529057352760477 -# MD5 Fingerprint: 1b:2e:00:ca:26:06:90:3d:ad:fe:6f:15:68:d3:6b:b3 -# SHA1 Fingerprint: 67:65:0d:f1:7e:8e:7e:5b:82:40:a4:f4:56:4b:cf:e2:3d:69:c6:f0 -# SHA256 Fingerprint: c0:a6:f4:dc:63:a2:4b:fd:cf:54:ef:2a:6a:08:2a:0a:72:de:35:80:3e:2f:f5:ff:52:7a:e5:d8:72:06:df:d5 ------BEGIN CERTIFICATE----- -MIIFsDCCA5igAwIBAgIQFci9ZUdcr7iXAF7kBtK8nTANBgkqhkiG9w0BAQUFADBe -MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 -ZC4xKjAoBgNVBAsMIWVQS0kgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe -Fw0wNDEyMjAwMjMxMjdaFw0zNDEyMjAwMjMxMjdaMF4xCzAJBgNVBAYTAlRXMSMw -IQYDVQQKDBpDaHVuZ2h3YSBUZWxlY29tIENvLiwgTHRkLjEqMCgGA1UECwwhZVBL -SSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA4SUP7o3biDN1Z82tH306Tm2d0y8U82N0ywEhajfqhFAH -SyZbCUNsIZ5qyNUD9WBpj8zwIuQf5/dqIjG3LBXy4P4AakP/h2XGtRrBp0xtInAh -ijHyl3SJCRImHJ7K2RKilTza6We/CKBk49ZCt0Xvl/T29de1ShUCWH2YWEtgvM3X -DZoTM1PRYfl61dd4s5oz9wCGzh1NlDivqOx4UXCKXBCDUSH3ET00hl7lSM2XgYI1 -TBnsZfZrxQWh7kcT1rMhJ5QQCtkkO7q+RBNGMD+XPNjX12ruOzjjK9SXDrkb5wdJ -fzcq+Xd4z1TtW0ado4AOkUPB1ltfFLqfpo0kR0BZv3I4sjZsN/+Z0V0OWQqraffA -sgRFelQArr5T9rXn4fg8ozHSqf4hUmTFpmfwdQcGlBSBVcYn5AGPF8Fqcde+S/uU -WH1+ETOxQvdibBjWzwloPn9s9h6PYq2lY9sJpx8iQkEeb5mKPtf5P0B6ebClAZLS -nT0IFaUQAS2zMnaolQ2zepr7BxB4EW/hj8e6DyUadCrlHJhBmd8hh+iVBmoKs2pH -dmX2Os+PYhcZewoozRrSgx4hxyy/vv9haLdnG7t4TY3OZ+XkwY63I2binZB1NJip -NiuKmpS5nezMirH4JYlcWrYvjB9teSSnUmjDhDXiZo1jDiVN1Rmy5nk3pyKdVDEC -AwEAAaNqMGgwHQYDVR0OBBYEFB4M97Zn8uGSJglFwFU5Lnc/QkqiMAwGA1UdEwQF -MAMBAf8wOQYEZyoHAAQxMC8wLQIBADAJBgUrDgMCGgUAMAcGBWcqAwAABBRFsMLH -ClZ87lt4DJX5GFPBphzYEDANBgkqhkiG9w0BAQUFAAOCAgEACbODU1kBPpVJufGB -uvl2ICO1J2B01GqZNF5sAFPZn/KmsSQHRGoqxqWOeBLoR9lYGxMqXnmbnwoqZ6Yl -PwZpVnPDimZI+ymBV3QGypzqKOg4ZyYr8dW1P2WT+DZdjo2NQCCHGervJ8A9tDkP -JXtoUHRVnAxZfVo9QZQlUgjgRywVMRnVvwdVxrsStZf0X4OFunHB2WyBEXYKCrC/ -gpf36j36+uwtqSiUO1bd0lEursC9CBWMd1I0ltabrNMdjmEPNXubrjlpC2JgQCA2 -j6/7Nu4tCEoduL+bXPjqpRugc6bY+G7gMwRfaKonh+3ZwZCc7b3jajWvY9+rGNm6 -5ulK6lCKD2GTHuItGeIwlDWSXQ62B68ZgI9HkFFLLk3dheLSClIKF5r8GrBQAuUB -o2M3IUxExJtRmREOc5wGj1QupyheRDmHVi03vYVElOEMSyycw5KFNGHLD7ibSkNS -/jQ6fbjpKdx2qcgw+BRxgMYeNkh0IkFch4LoGHGLQYlE535YW6i4jRPpp2zDR+2z -Gp1iro2C6pSe3VkQw63d4k3jMdXH7OjysP6SHhYKGvzZ8/gntsm+HbRsZJB/9OTE -W9c3rkIO3aQab3yIVMUWbuF6aC74Or8NpDyJO3inTmODBCEIZ43ygknQW/2xzQ+D -hNQ+IIX3Sj0rnP0qCglN6oH4EZw= ------END CERTIFICATE----- - -# Issuer: O=certSIGN OU=certSIGN ROOT CA -# Subject: O=certSIGN OU=certSIGN ROOT CA -# Label: "certSIGN ROOT CA" -# Serial: 35210227249154 -# MD5 Fingerprint: 18:98:c0:d6:e9:3a:fc:f9:b0:f5:0c:f7:4b:01:44:17 -# SHA1 Fingerprint: fa:b7:ee:36:97:26:62:fb:2d:b0:2a:f6:bf:03:fd:e8:7c:4b:2f:9b -# SHA256 Fingerprint: ea:a9:62:c4:fa:4a:6b:af:eb:e4:15:19:6d:35:1c:cd:88:8d:4f:53:f3:fa:8a:e6:d7:c4:66:a9:4e:60:42:bb ------BEGIN CERTIFICATE----- -MIIDODCCAiCgAwIBAgIGIAYFFnACMA0GCSqGSIb3DQEBBQUAMDsxCzAJBgNVBAYT -AlJPMREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBD -QTAeFw0wNjA3MDQxNzIwMDRaFw0zMTA3MDQxNzIwMDRaMDsxCzAJBgNVBAYTAlJP -MREwDwYDVQQKEwhjZXJ0U0lHTjEZMBcGA1UECxMQY2VydFNJR04gUk9PVCBDQTCC -ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALczuX7IJUqOtdu0KBuqV5Do -0SLTZLrTk+jUrIZhQGpgV2hUhE28alQCBf/fm5oqrl0Hj0rDKH/v+yv6efHHrfAQ -UySQi2bJqIirr1qjAOm+ukbuW3N7LBeCgV5iLKECZbO9xSsAfsT8AzNXDe3i+s5d -RdY4zTW2ssHQnIFKquSyAVwdj1+ZxLGt24gh65AIgoDzMKND5pCCrlUoSe1b16kQ -OA7+j0xbm0bqQfWwCHTD0IgztnzXdN/chNFDDnU5oSVAKOp4yw4sLjmdjItuFhwv -JoIQ4uNllAoEwF73XVv4EOLQunpL+943AAAaWyjj0pxzPjKHmKHJUS/X3qwzs08C -AwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAcYwHQYDVR0O -BBYEFOCMm9slSbPxfIbWskKHC9BroNnkMA0GCSqGSIb3DQEBBQUAA4IBAQA+0hyJ -LjX8+HXd5n9liPRyTMks1zJO890ZeUe9jjtbkw9QSSQTaxQGcu8J06Gh40CEyecY -MnQ8SG4Pn0vU9x7Tk4ZkVJdjclDVVc/6IJMCopvDI5NOFlV2oHB5bc0hH88vLbwZ -44gx+FkagQnIl6Z0x2DEW8xXjrJ1/RsCCdtZb3KTafcxQdaIOL+Hsr0Wefmq5L6I -Jd1hJyMctTEHBDa0GpC9oHRxUIltvBTjD4au8as+x6AJzKNI0eDbZOeStc+vckNw -i/nDhDwTqn6Sm1dTk/pwwpEOMfmbZ13pljheX7NzTogVZ96edhBiIL5VaZVDADlN -9u6wWk5JRFRYX0KD ------END CERTIFICATE----- - -# Issuer: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) -# Subject: CN=NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny O=NetLock Kft. OU=Tan\xfas\xedtv\xe1nykiad\xf3k (Certification Services) -# Label: "NetLock Arany (Class Gold) F\u0151tan\xfas\xedtv\xe1ny" -# Serial: 80544274841616 -# MD5 Fingerprint: c5:a1:b7:ff:73:dd:d6:d7:34:32:18:df:fc:3c:ad:88 -# SHA1 Fingerprint: 06:08:3f:59:3f:15:a1:04:a0:69:a4:6b:a9:03:d0:06:b7:97:09:91 -# SHA256 Fingerprint: 6c:61:da:c3:a2:de:f0:31:50:6b:e0:36:d2:a6:fe:40:19:94:fb:d1:3d:f9:c8:d4:66:59:92:74:c4:46:ec:98 ------BEGIN CERTIFICATE----- -MIIEFTCCAv2gAwIBAgIGSUEs5AAQMA0GCSqGSIb3DQEBCwUAMIGnMQswCQYDVQQG -EwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFTATBgNVBAoMDE5ldExvY2sgS2Z0LjE3 -MDUGA1UECwwuVGFuw7pzw610dsOhbnlraWFkw7NrIChDZXJ0aWZpY2F0aW9uIFNl -cnZpY2VzKTE1MDMGA1UEAwwsTmV0TG9jayBBcmFueSAoQ2xhc3MgR29sZCkgRsWR -dGFuw7pzw610dsOhbnkwHhcNMDgxMjExMTUwODIxWhcNMjgxMjA2MTUwODIxWjCB -pzELMAkGA1UEBhMCSFUxETAPBgNVBAcMCEJ1ZGFwZXN0MRUwEwYDVQQKDAxOZXRM -b2NrIEtmdC4xNzA1BgNVBAsMLlRhbsO6c8OtdHbDoW55a2lhZMOzayAoQ2VydGlm -aWNhdGlvbiBTZXJ2aWNlcykxNTAzBgNVBAMMLE5ldExvY2sgQXJhbnkgKENsYXNz -IEdvbGQpIEbFkXRhbsO6c8OtdHbDoW55MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A -MIIBCgKCAQEAxCRec75LbRTDofTjl5Bu0jBFHjzuZ9lk4BqKf8owyoPjIMHj9DrT -lF8afFttvzBPhCf2nx9JvMaZCpDyD/V/Q4Q3Y1GLeqVw/HpYzY6b7cNGbIRwXdrz -AZAj/E4wqX7hJ2Pn7WQ8oLjJM2P+FpD/sLj916jAwJRDC7bVWaaeVtAkH3B5r9s5 -VA1lddkVQZQBr17s9o3x/61k/iCa11zr/qYfCGSji3ZVrR47KGAuhyXoqq8fxmRG -ILdwfzzeSNuWU7c5d+Qa4scWhHaXWy+7GRWF+GmF9ZmnqfI0p6m2pgP8b4Y9VHx2 -BJtr+UBdADTHLpl1neWIA6pN+APSQnbAGwIDAKiLo0UwQzASBgNVHRMBAf8ECDAG -AQH/AgEEMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUzPpnk/C2uNClwB7zU/2M -U9+D15YwDQYJKoZIhvcNAQELBQADggEBAKt/7hwWqZw8UQCgwBEIBaeZ5m8BiFRh -bvG5GK1Krf6BQCOUL/t1fC8oS2IkgYIL9WHxHG64YTjrgfpioTtaYtOUZcTh5m2C -+C8lcLIhJsFyUR+MLMOEkMNaj7rP9KdlpeuY0fsFskZ1FSNqb4VjMIDw1Z4fKRzC -bLBQWV2QWzuoDTDPv31/zvGdg73JRm4gpvlhUbohL3u+pRVjodSVh/GeufOJ8z2F -uLjbvrW5KfnaNwUASZQDhETnv0Mxz3WLJdH0pmT1kvarBes96aULNmLazAZfNou2 -XjG4Kvte9nHfRCaexOYNkbQudZWAUWpLMKawYqGT8ZvYzsRjdT9ZR7E= ------END CERTIFICATE----- - -# Issuer: CN=Hongkong Post Root CA 1 O=Hongkong Post -# Subject: CN=Hongkong Post Root CA 1 O=Hongkong Post -# Label: "Hongkong Post Root CA 1" -# Serial: 1000 -# MD5 Fingerprint: a8:0d:6f:39:78:b9:43:6d:77:42:6d:98:5a:cc:23:ca -# SHA1 Fingerprint: d6:da:a8:20:8d:09:d2:15:4d:24:b5:2f:cb:34:6e:b2:58:b2:8a:58 -# SHA256 Fingerprint: f9:e6:7d:33:6c:51:00:2a:c0:54:c6:32:02:2d:66:dd:a2:e7:e3:ff:f1:0a:d0:61:ed:31:d8:bb:b4:10:cf:b2 ------BEGIN CERTIFICATE----- -MIIDMDCCAhigAwIBAgICA+gwDQYJKoZIhvcNAQEFBQAwRzELMAkGA1UEBhMCSEsx -FjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdrb25nIFBvc3Qg -Um9vdCBDQSAxMB4XDTAzMDUxNTA1MTMxNFoXDTIzMDUxNTA0NTIyOVowRzELMAkG -A1UEBhMCSEsxFjAUBgNVBAoTDUhvbmdrb25nIFBvc3QxIDAeBgNVBAMTF0hvbmdr -b25nIFBvc3QgUm9vdCBDQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEArP84tulmAknjorThkPlAj3n54r15/gK97iSSHSL22oVyaf7XPwnU3ZG1ApzQ -jVrhVcNQhrkpJsLj2aDxaQMoIIBFIi1WpztUlVYiWR8o3x8gPW2iNr4joLFutbEn -PzlTCeqrauh0ssJlXI6/fMN4hM2eFvz1Lk8gKgifd/PFHsSaUmYeSF7jEAaPIpjh -ZY4bXSNmO7ilMlHIhqqhqZ5/dpTCpmy3QfDVyAY45tQM4vM7TG1QjMSDJ8EThFk9 -nnV0ttgCXjqQesBCNnLsak3c78QA3xMYV18meMjWCnl3v/evt3a5pQuEF10Q6m/h -q5URX208o1xNg1vysxmKgIsLhwIDAQABoyYwJDASBgNVHRMBAf8ECDAGAQH/AgED -MA4GA1UdDwEB/wQEAwIBxjANBgkqhkiG9w0BAQUFAAOCAQEADkbVPK7ih9legYsC -mEEIjEy82tvuJxuC52pF7BaLT4Wg87JwvVqWuspube5Gi27nKi6Wsxkz67SfqLI3 -7piol7Yutmcn1KZJ/RyTZXaeQi/cImyaT/JaFTmxcdcrUehtHJjA2Sr0oYJ71clB -oiMBdDhViw+5LmeiIAQ32pwL0xch4I+XeTRvhEgCIDMb5jREn5Fw9IBehEPCKdJs -EhTkYY2sEJCehFC78JZvRZ+K88psT/oROhUVRsPNH4NbLUES7VBnQRM9IauUiqpO -fMGx+6fWtScvl6tu4B3i0RwsH0Ti/L6RoZz71ilTc4afU9hDDl3WY4JxHYB0yvbi -AmvZWg== ------END CERTIFICATE----- - -# Issuer: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. -# Subject: CN=SecureSign RootCA11 O=Japan Certification Services, Inc. -# Label: "SecureSign RootCA11" -# Serial: 1 -# MD5 Fingerprint: b7:52:74:e2:92:b4:80:93:f2:75:e4:cc:d7:f2:ea:26 -# SHA1 Fingerprint: 3b:c4:9f:48:f8:f3:73:a0:9c:1e:bd:f8:5b:b1:c3:65:c7:d8:11:b3 -# SHA256 Fingerprint: bf:0f:ee:fb:9e:3a:58:1a:d5:f9:e9:db:75:89:98:57:43:d2:61:08:5c:4d:31:4f:6f:5d:72:59:aa:42:16:12 ------BEGIN CERTIFICATE----- -MIIDbTCCAlWgAwIBAgIBATANBgkqhkiG9w0BAQUFADBYMQswCQYDVQQGEwJKUDEr -MCkGA1UEChMiSmFwYW4gQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcywgSW5jLjEcMBoG -A1UEAxMTU2VjdXJlU2lnbiBSb290Q0ExMTAeFw0wOTA0MDgwNDU2NDdaFw0yOTA0 -MDgwNDU2NDdaMFgxCzAJBgNVBAYTAkpQMSswKQYDVQQKEyJKYXBhbiBDZXJ0aWZp -Y2F0aW9uIFNlcnZpY2VzLCBJbmMuMRwwGgYDVQQDExNTZWN1cmVTaWduIFJvb3RD -QTExMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA/XeqpRyQBTvLTJsz -i1oURaTnkBbR31fSIRCkF/3frNYfp+TbfPfs37gD2pRY/V1yfIw/XwFndBWW4wI8 -h9uuywGOwvNmxoVF9ALGOrVisq/6nL+k5tSAMJjzDbaTj6nU2DbysPyKyiyhFTOV -MdrAG/LuYpmGYz+/3ZMqg6h2uRMft85OQoWPIucuGvKVCbIFtUROd6EgvanyTgp9 -UK31BQ1FT0Zx/Sg+U/sE2C3XZR1KG/rPO7AxmjVuyIsG0wCR8pQIZUyxNAYAeoni -8McDWc/V1uinMrPmmECGxc0nEovMe863ETxiYAcjPitAbpSACW22s293bzUIUPsC -h8U+iQIDAQABo0IwQDAdBgNVHQ4EFgQUW/hNT7KlhtQ60vFjmqC+CfZXt94wDgYD -VR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEB -AKChOBZmLqdWHyGcBvod7bkixTgm2E5P7KN/ed5GIaGHd48HCJqypMWvDzKYC3xm -KbabfSVSSUOrTC4rbnpwrxYO4wJs+0LmGJ1F2FXI6Dvd5+H0LgscNFxsWEr7jIhQ -X5Ucv+2rIrVls4W6ng+4reV6G4pQOh29Dbx7VFALuUKvVaAYga1lme++5Jy/xIWr -QbJUb9wlze144o4MjQlJ3WN7WmmWAiGovVJZ6X01y8hSyn+B/tlr0/cR7SXf+Of5 -pPpyl4RTDaXQMhhRdlkUbA/r7F+AjHVDg8OFmP9Mni0N5HeDk061lgeLKBObjBmN -QSdJQO7e5iNEOdyhIta6A/I= ------END CERTIFICATE----- - -# Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. -# Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. -# Label: "Microsec e-Szigno Root CA 2009" -# Serial: 14014712776195784473 -# MD5 Fingerprint: f8:49:f4:03:bc:44:2d:83:be:48:69:7d:29:64:fc:b1 -# SHA1 Fingerprint: 89:df:74:fe:5c:f4:0f:4a:80:f9:e3:37:7d:54:da:91:e1:01:31:8e -# SHA256 Fingerprint: 3c:5f:81:fe:a5:fa:b8:2c:64:bf:a2:ea:ec:af:cd:e8:e0:77:fc:86:20:a7:ca:e5:37:16:3d:f3:6e:db:f3:78 ------BEGIN CERTIFICATE----- -MIIECjCCAvKgAwIBAgIJAMJ+QwRORz8ZMA0GCSqGSIb3DQEBCwUAMIGCMQswCQYD -VQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3QxFjAUBgNVBAoMDU1pY3Jvc2VjIEx0 -ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3ppZ25vIFJvb3QgQ0EgMjAwOTEfMB0G -CSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5odTAeFw0wOTA2MTYxMTMwMThaFw0y -OTEyMzAxMTMwMThaMIGCMQswCQYDVQQGEwJIVTERMA8GA1UEBwwIQnVkYXBlc3Qx -FjAUBgNVBAoMDU1pY3Jvc2VjIEx0ZC4xJzAlBgNVBAMMHk1pY3Jvc2VjIGUtU3pp -Z25vIFJvb3QgQ0EgMjAwOTEfMB0GCSqGSIb3DQEJARYQaW5mb0BlLXN6aWduby5o -dTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOn4j/NjrdqG2KfgQvvP -kd6mJviZpWNwrZuuyjNAfW2WbqEORO7hE52UQlKavXWFdCyoDh2Tthi3jCyoz/tc -cbna7P7ofo/kLx2yqHWH2Leh5TvPmUpG0IMZfcChEhyVbUr02MelTTMuhTlAdX4U -fIASmFDHQWe4oIBhVKZsTh/gnQ4H6cm6M+f+wFUoLAKApxn1ntxVUwOXewdI/5n7 -N4okxFnMUBBjjqqpGrCEGob5X7uxUG6k0QrM1XF+H6cbfPVTbiJfyyvm1HxdrtbC -xkzlBQHZ7Vf8wSN5/PrIJIOV87VqUQHQd9bpEqH5GoP7ghu5sJf0dgYzQ0mg/wu1 -+rUCAwEAAaOBgDB+MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G -A1UdDgQWBBTLD8bfQkPMPcu1SCOhGnqmKrs0aDAfBgNVHSMEGDAWgBTLD8bfQkPM -Pcu1SCOhGnqmKrs0aDAbBgNVHREEFDASgRBpbmZvQGUtc3ppZ25vLmh1MA0GCSqG -SIb3DQEBCwUAA4IBAQDJ0Q5eLtXMs3w+y/w9/w0olZMEyL/azXm4Q5DwpL7v8u8h -mLzU1F0G9u5C7DBsoKqpyvGvivo/C3NqPuouQH4frlRheesuCDfXI/OMn74dseGk -ddug4lQUsbocKaQY9hK6ohQU4zE1yED/t+AFdlfBHFny+L/k7SViXITwfn4fs775 -tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c -2Pm2G2JwCz02yULyMtd6YebS2z3PyKnJm9zbWETXbzivf3jTo60adbocwTZ8jx5t -HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 -# Label: "GlobalSign Root CA - R3" -# Serial: 4835703278459759426209954 -# MD5 Fingerprint: c5:df:b8:49:ca:05:13:55:ee:2d:ba:1a:c3:3e:b0:28 -# SHA1 Fingerprint: d6:9b:56:11:48:f0:1c:77:c5:45:78:c1:09:26:df:5b:85:69:76:ad -# SHA256 Fingerprint: cb:b5:22:d7:b7:f1:27:ad:6a:01:13:86:5b:df:1c:d4:10:2e:7d:07:59:af:63:5a:7c:f4:72:0d:c9:63:c5:3b ------BEGIN CERTIFICATE----- -MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G -A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp -Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 -MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG -A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 -RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT -gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm -KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd -QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ -XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw -DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o -LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU -RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp -jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK -6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX -mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs -Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH -WD9f ------END CERTIFICATE----- - -# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" -# Serial: 6047274297262753887 -# MD5 Fingerprint: 73:3a:74:7a:ec:bb:a3:96:a6:c2:e4:e2:c8:9b:c0:c3 -# SHA1 Fingerprint: ae:c5:fb:3f:c8:e1:bf:c4:e5:4f:03:07:5a:9a:e8:00:b7:f7:b6:fa -# SHA256 Fingerprint: 04:04:80:28:bf:1f:28:64:d4:8f:9a:d4:d8:32:94:36:6a:82:88:56:55:3f:3b:14:30:3f:90:14:7f:5d:40:ef ------BEGIN CERTIFICATE----- -MIIGFDCCA/ygAwIBAgIIU+w77vuySF8wDQYJKoZIhvcNAQEFBQAwUTELMAkGA1UE -BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h -cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0wOTA1MjAwODM4MTVaFw0zMDEy -MzEwODM4MTVaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg -Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 -thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM -cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG -L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i -NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h -X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b -m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy -Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja -EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T -KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF -6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh -OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYD -VR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRlzeurNR4APn7VdMActHNHDhpkLzCBpgYD -VR0gBIGeMIGbMIGYBgRVHSAAMIGPMC8GCCsGAQUFBwIBFiNodHRwOi8vd3d3LmZp -cm1hcHJvZmVzaW9uYWwuY29tL2NwczBcBggrBgEFBQcCAjBQHk4AUABhAHMAZQBv -ACAAZABlACAAbABhACAAQgBvAG4AYQBuAG8AdgBhACAANAA3ACAAQgBhAHIAYwBl -AGwAbwBuAGEAIAAwADgAMAAxADcwDQYJKoZIhvcNAQEFBQADggIBABd9oPm03cXF -661LJLWhAqvdpYhKsg9VSytXjDvlMd3+xDLx51tkljYyGOylMnfX40S2wBEqgLk9 -am58m9Ot/MPWo+ZkKXzR4Tgegiv/J2Wv+xYVxC5xhOW1//qkR71kMrv2JYSiJ0L1 -ILDCExARzRAVukKQKtJE4ZYm6zFIEv0q2skGz3QeqUvVhyj5eTSSPi5E6PaPT481 -PyWzOdxjKpBrIF/EUhJOlywqrJ2X3kjyo2bbwtKDlaZmp54lD+kLM5FlClrD2VQS -3a/DTg4fJl4N3LON7NWBcN7STyQF82xO9UxJZo3R/9ILJUFI/lGExkKvgATP0H5k -SeTy36LssUzAKh3ntLFlosS88Zj0qnAHY7S42jtM+kAiMFsRpvAFDsYCA0irhpuF -3dvd6qJ2gHN99ZwExEWN57kci57q13XRcrHedUTnQn3iV2t93Jm8PYMo6oCTjcVM -ZcFwgbg4/EMxsvYDNEeyrPsiBsse3RdHHF9mudMaotoRsaS8I8nkvof/uZS2+F0g -StRf571oe2XyFR7SOqkt6dhrJKyXWERHrVkY8SFlcN7ONGCoQPHzPKTDKCOM/icz -Q0CgFzzr6juwcqajuUpLXhZI9LK8yIySxZ2frHI2vDSANGupi5LAuBft7HZT9SQB -jLMi6Et8Vcad+qMUu2WFbm5PEn4KPJ2V ------END CERTIFICATE----- - -# Issuer: CN=Izenpe.com O=IZENPE S.A. -# Subject: CN=Izenpe.com O=IZENPE S.A. -# Label: "Izenpe.com" -# Serial: 917563065490389241595536686991402621 -# MD5 Fingerprint: a6:b0:cd:85:80:da:5c:50:34:a3:39:90:2f:55:67:73 -# SHA1 Fingerprint: 2f:78:3d:25:52:18:a7:4a:65:39:71:b5:2c:a2:9c:45:15:6f:e9:19 -# SHA256 Fingerprint: 25:30:cc:8e:98:32:15:02:ba:d9:6f:9b:1f:ba:1b:09:9e:2d:29:9e:0f:45:48:bb:91:4f:36:3b:c0:d4:53:1f ------BEGIN CERTIFICATE----- -MIIF8TCCA9mgAwIBAgIQALC3WhZIX7/hy/WL1xnmfTANBgkqhkiG9w0BAQsFADA4 -MQswCQYDVQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6 -ZW5wZS5jb20wHhcNMDcxMjEzMTMwODI4WhcNMzcxMjEzMDgyNzI1WjA4MQswCQYD -VQQGEwJFUzEUMBIGA1UECgwLSVpFTlBFIFMuQS4xEzARBgNVBAMMCkl6ZW5wZS5j -b20wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDJ03rKDx6sp4boFmVq -scIbRTJxldn+EFvMr+eleQGPicPK8lVx93e+d5TzcqQsRNiekpsUOqHnJJAKClaO -xdgmlOHZSOEtPtoKct2jmRXagaKH9HtuJneJWK3W6wyyQXpzbm3benhB6QiIEn6H -LmYRY2xU+zydcsC8Lv/Ct90NduM61/e0aL6i9eOBbsFGb12N4E3GVFWJGjMxCrFX -uaOKmMPsOzTFlUFpfnXCPCDFYbpRR6AgkJOhkEvzTnyFRVSa0QUmQbC1TR0zvsQD -yCV8wXDbO/QJLVQnSKwv4cSsPsjLkkxTOTcj7NMB+eAJRE1NZMDhDVqHIrytG6P+ -JrUV86f8hBnp7KGItERphIPzidF0BqnMC9bC3ieFUCbKF7jJeodWLBoBHmy+E60Q -rLUk9TiRodZL2vG70t5HtfG8gfZZa88ZU+mNFctKy6lvROUbQc/hhqfK0GqfvEyN -BjNaooXlkDWgYlwWTvDjovoDGrQscbNYLN57C9saD+veIR8GdwYDsMnvmfzAuU8L -hij+0rnq49qlw0dpEuDb8PYZi+17cNcC1u2HGCgsBCRMd+RIihrGO5rUD8r6ddIB -QFqNeb+Lz0vPqhbBleStTIo+F5HUsWLlguWABKQDfo2/2n+iD5dPDNMN+9fR5XJ+ -HMh3/1uaD7euBUbl8agW7EekFwIDAQABo4H2MIHzMIGwBgNVHREEgagwgaWBD2lu -Zm9AaXplbnBlLmNvbaSBkTCBjjFHMEUGA1UECgw+SVpFTlBFIFMuQS4gLSBDSUYg -QTAxMzM3MjYwLVJNZXJjLlZpdG9yaWEtR2FzdGVpeiBUMTA1NSBGNjIgUzgxQzBB -BgNVBAkMOkF2ZGEgZGVsIE1lZGl0ZXJyYW5lbyBFdG9yYmlkZWEgMTQgLSAwMTAx -MCBWaXRvcmlhLUdhc3RlaXowDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AQYwHQYDVR0OBBYEFB0cZQ6o8iV7tJHP5LGx5r1VdGwFMA0GCSqGSIb3DQEBCwUA -A4ICAQB4pgwWSp9MiDrAyw6lFn2fuUhfGI8NYjb2zRlrrKvV9pF9rnHzP7MOeIWb -laQnIUdCSnxIOvVFfLMMjlF4rJUT3sb9fbgakEyrkgPH7UIBzg/YsfqikuFgba56 -awmqxinuaElnMIAkejEWOVt+8Rwu3WwJrfIxwYJOubv5vr8qhT/AQKM6WfxZSzwo -JNu0FXWuDYi6LnPAvViH5ULy617uHjAimcs30cQhbIHsvm0m5hzkQiCeR7Csg1lw -LDXWrzY0tM07+DKo7+N4ifuNRSzanLh+QBxh5z6ikixL8s36mLYp//Pye6kfLqCT -VyvehQP5aTfLnnhqBbTFMXiJ7HqnheG5ezzevh55hM6fcA5ZwjUukCox2eRFekGk -LhObNA5me0mrZJfQRsN5nXJQY6aYWwa9SG3YOYNw6DXwBdGqvOPbyALqfP2C2sJb -UjWumDqtujWTI6cfSN01RpiyEGjkpTHCClguGYEQyVB1/OpaFs4R1+7vUIgtYf8/ -QnMFlEPVjjxOAToZpR9GTnfQXeWBIiGH/pR9hNiTrdZoQ0iy2+tzJOeRf1SktoA+ -naM8THLCV8Sg1Mw4J87VBp6iSNnpn86CcDaTmjvfliHjWbcM2pE38P1ZWrOZyGls -QyYBNWNgVYkDOnXYukrZVP/u3oDYLdE41V4tC5h9Pmzb/CaIxw== ------END CERTIFICATE----- - -# Issuer: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. -# Subject: CN=Go Daddy Root Certificate Authority - G2 O=GoDaddy.com, Inc. -# Label: "Go Daddy Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: 80:3a:bc:22:c1:e6:fb:8d:9b:3b:27:4a:32:1b:9a:01 -# SHA1 Fingerprint: 47:be:ab:c9:22:ea:e8:0e:78:78:34:62:a7:9f:45:c2:54:fd:e6:8b -# SHA256 Fingerprint: 45:14:0b:32:47:eb:9c:c8:c5:b4:f0:d7:b5:30:91:f7:32:92:08:9e:6e:5a:63:e2:74:9d:d3:ac:a9:19:8e:da ------BEGIN CERTIFICATE----- -MIIDxTCCAq2gAwIBAgIBADANBgkqhkiG9w0BAQsFADCBgzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAYBgNVBAoT -EUdvRGFkZHkuY29tLCBJbmMuMTEwLwYDVQQDEyhHbyBEYWRkeSBSb290IENlcnRp -ZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAwMFoXDTM3MTIzMTIz -NTk1OVowgYMxCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6b25hMRMwEQYDVQQH -EwpTY290dHNkYWxlMRowGAYDVQQKExFHb0RhZGR5LmNvbSwgSW5jLjExMC8GA1UE -AxMoR28gRGFkZHkgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIw -DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9xYgjx+lk09xvJGKP3gElY6SKD -E6bFIEMBO4Tx5oVJnyfq9oQbTqC023CYxzIBsQU+B07u9PpPL1kwIuerGVZr4oAH -/PMWdYA5UXvl+TW2dE6pjYIT5LY/qQOD+qK+ihVqf94Lw7YZFAXK6sOoBJQ7Rnwy -DfMAZiLIjWltNowRGLfTshxgtDj6AozO091GB94KPutdfMh8+7ArU6SSYmlRJQVh -GkSBjCypQ5Yj36w6gZoOKcUcqeldHraenjAKOc7xiID7S13MMuyFYkMlNAJWJwGR -tDtwKj9useiciAF9n9T521NtYJ2/LOdYq7hfRvzOxBsDPAnrSTFcaUaz4EcCAwEA -AaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE -FDqahQcQZyi27/a9BUFuIMGU2g/eMA0GCSqGSIb3DQEBCwUAA4IBAQCZ21151fmX -WWcDYfF+OwYxdS2hII5PZYe096acvNjpL9DbWu7PdIxztDhC2gV7+AJ1uP2lsdeu -9tfeE8tTEH6KRtGX+rcuKxGrkLAngPnon1rpN5+r5N9ss4UXnT3ZJE95kTXWXwTr -gIOrmgIttRD02JDHBHNA7XIloKmf7J6raBKZV8aPEjoJpL1E/QYVN8Gb5DKj7Tjo -2GTzLH4U/ALqn83/B2gX2yKQOC16jdFU8WnjXzPKej17CuPKf1855eJ1usV2GDPO -LPAvTK33sefOT6jEm0pUBsV/fdUID+Ic/n4XuKxe9tQWskMJDE32p2u0mYRlynqI -4uJEvlz36hz1 ------END CERTIFICATE----- - -# Issuer: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Subject: CN=Starfield Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Label: "Starfield Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: d6:39:81:c6:52:7e:96:69:fc:fc:ca:66:ed:05:f2:96 -# SHA1 Fingerprint: b5:1c:06:7c:ee:2b:0c:3d:f8:55:ab:2d:92:f4:fe:39:d4:e7:0f:0e -# SHA256 Fingerprint: 2c:e1:cb:0b:f9:d2:f9:e1:02:99:3f:be:21:51:52:c3:b2:dd:0c:ab:de:1c:68:e5:31:9b:83:91:54:db:b7:f5 ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjzELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT -HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xMjAwBgNVBAMTKVN0YXJmaWVs -ZCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5MDkwMTAwMDAw -MFoXDTM3MTIzMTIzNTk1OVowgY8xCzAJBgNVBAYTAlVTMRAwDgYDVQQIEwdBcml6 -b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFyZmllbGQgVGVj -aG5vbG9naWVzLCBJbmMuMTIwMAYDVQQDEylTdGFyZmllbGQgUm9vdCBDZXJ0aWZp -Y2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAL3twQP89o/8ArFvW59I2Z154qK3A2FWGMNHttfKPTUuiUP3oWmb3ooa/RMg -nLRJdzIpVv257IzdIvpy3Cdhl+72WoTsbhm5iSzchFvVdPtrX8WJpRBSiUZV9Lh1 -HOZ/5FSuS/hVclcCGfgXcVnrHigHdMWdSL5stPSksPNkN3mSwOxGXn/hbVNMYq/N -Hwtjuzqd+/x5AJhhdM8mgkBj87JyahkNmcrUDnXMN/uLicFZ8WJ/X7NfZTD4p7dN -dloedl40wOiWVpmKs/B/pM293DIxfJHP4F8R+GuqSVzRmZTRouNjWwl2tVZi4Ut0 -HZbUJtQIBFnQmA4O5t78w+wfkPECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFHwMMh+n2TB/xH1oo2Kooc6rB1snMA0G -CSqGSIb3DQEBCwUAA4IBAQARWfolTwNvlJk7mh+ChTnUdgWUXuEok21iXQnCoKjU -sHU48TRqneSfioYmUeYs0cYtbpUgSpIB7LiKZ3sx4mcujJUDJi5DnUox9g61DLu3 -4jd/IroAow57UvtruzvE03lRTs2Q9GcHGcg8RnoNAX3FWOdt5oUwF5okxBDgBPfg -8n/Uqgr/Qh037ZTlZFkSIHc40zI+OIF1lnP6aI+xy84fxez6nH7PfrHxBy22/L/K -pL/QlwVKvOoYKAKQvVR4CSFx09F9HdkWsKlhPdAKACL8x3vLCWRFCztAgfd9fDL1 -mMpYjn0q7pBZc2T5NnReJaH1ZgUufzkVqSr7UIuOhWn0 ------END CERTIFICATE----- - -# Issuer: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Subject: CN=Starfield Services Root Certificate Authority - G2 O=Starfield Technologies, Inc. -# Label: "Starfield Services Root Certificate Authority - G2" -# Serial: 0 -# MD5 Fingerprint: 17:35:74:af:7b:61:1c:eb:f4:f9:3c:e2:ee:40:f9:a2 -# SHA1 Fingerprint: 92:5a:8f:8d:2c:6d:04:e0:66:5f:59:6a:ff:22:d8:63:e8:25:6f:3f -# SHA256 Fingerprint: 56:8d:69:05:a2:c8:87:08:a4:b3:02:51:90:ed:cf:ed:b1:97:4a:60:6a:13:c6:e5:29:0f:cb:2a:e6:3e:da:b5 ------BEGIN CERTIFICATE----- -MIID7zCCAtegAwIBAgIBADANBgkqhkiG9w0BAQsFADCBmDELMAkGA1UEBhMCVVMx -EDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxJTAjBgNVBAoT -HFN0YXJmaWVsZCBUZWNobm9sb2dpZXMsIEluYy4xOzA5BgNVBAMTMlN0YXJmaWVs -ZCBTZXJ2aWNlcyBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAtIEcyMB4XDTA5 -MDkwMTAwMDAwMFoXDTM3MTIzMTIzNTk1OVowgZgxCzAJBgNVBAYTAlVTMRAwDgYD -VQQIEwdBcml6b25hMRMwEQYDVQQHEwpTY290dHNkYWxlMSUwIwYDVQQKExxTdGFy -ZmllbGQgVGVjaG5vbG9naWVzLCBJbmMuMTswOQYDVQQDEzJTdGFyZmllbGQgU2Vy -dmljZXMgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgLSBHMjCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBANUMOsQq+U7i9b4Zl1+OiFOxHz/Lz58gE20p -OsgPfTz3a3Y4Y9k2YKibXlwAgLIvWX/2h/klQ4bnaRtSmpDhcePYLQ1Ob/bISdm2 -8xpWriu2dBTrz/sm4xq6HZYuajtYlIlHVv8loJNwU4PahHQUw2eeBGg6345AWh1K -Ts9DkTvnVtYAcMtS7nt9rjrnvDH5RfbCYM8TWQIrgMw0R9+53pBlbQLPLJGmpufe -hRhJfGZOozptqbXuNC66DQO4M99H67FrjSXZm86B0UVGMpZwh94CDklDhbZsc7tk -6mFBrMnUVN+HL8cisibMn1lUaJ/8viovxFUcdUBgF4UCVTmLfwUCAwEAAaNCMEAw -DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJxfAN+q -AdcwKziIorhtSpzyEZGDMA0GCSqGSIb3DQEBCwUAA4IBAQBLNqaEd2ndOxmfZyMI -bw5hyf2E3F/YNoHN2BtBLZ9g3ccaaNnRbobhiCPPE95Dz+I0swSdHynVv/heyNXB -ve6SbzJ08pGCL72CQnqtKrcgfU28elUSwhXqvfdqlS5sdJ/PHLTyxQGjhdByPq1z -qwubdQxtRbeOlKyWN7Wg0I8VRw7j6IPdj/3vQQF3zCepYoUz8jcI73HPdwbeyBkd -iEDPfUYd/x7H4c7/I9vG+o1VTqkC50cRRj70/b17KSa7qWFiNyi2LSr2EIZkyXCn -0q23KXB56jzaYyWf/Wi3MOxw+3WKt21gZ7IeyLnp2KhvAotnDU0mV3HaIPzBSlCN -sSi6 ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Commercial O=AffirmTrust -# Subject: CN=AffirmTrust Commercial O=AffirmTrust -# Label: "AffirmTrust Commercial" -# Serial: 8608355977964138876 -# MD5 Fingerprint: 82:92:ba:5b:ef:cd:8a:6f:a6:3d:55:f9:84:f6:d6:b7 -# SHA1 Fingerprint: f9:b5:b6:32:45:5f:9c:be:ec:57:5f:80:dc:e9:6e:2c:c7:b2:78:b7 -# SHA256 Fingerprint: 03:76:ab:1d:54:c5:f9:80:3c:e4:b2:e2:01:a0:ee:7e:ef:7b:57:b6:36:e8:a9:3c:9b:8d:48:60:c9:6f:5f:a7 ------BEGIN CERTIFICATE----- -MIIDTDCCAjSgAwIBAgIId3cGJyapsXwwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz -dCBDb21tZXJjaWFsMB4XDTEwMDEyOTE0MDYwNloXDTMwMTIzMTE0MDYwNlowRDEL -MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp -cm1UcnVzdCBDb21tZXJjaWFsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEA9htPZwcroRX1BiLLHwGy43NFBkRJLLtJJRTWzsO3qyxPxkEylFf6EqdbDuKP -Hx6GGaeqtS25Xw2Kwq+FNXkyLbscYjfysVtKPcrNcV/pQr6U6Mje+SJIZMblq8Yr -ba0F8PrVC8+a5fBQpIs7R6UjW3p6+DM/uO+Zl+MgwdYoic+U+7lF7eNAFxHUdPAL -MeIrJmqbTFeurCA+ukV6BfO9m2kVrn1OIGPENXY6BwLJN/3HR+7o8XYdcxXyl6S1 -yHp52UKqK39c/s4mT6NmgTWvRLpUHhwwMmWd5jyTXlBOeuM61G7MGvv50jeuJCqr -VwMiKA1JdX+3KNp1v47j3A55MQIDAQABo0IwQDAdBgNVHQ4EFgQUnZPGU4teyq8/ -nx4P5ZmVvCT2lI8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ -KoZIhvcNAQELBQADggEBAFis9AQOzcAN/wr91LoWXym9e2iZWEnStB03TX8nfUYG -XUPGhi4+c7ImfU+TqbbEKpqrIZcUsd6M06uJFdhrJNTxFq7YpFzUf1GO7RgBsZNj -vbz4YYCanrHOQnDiqX0GJX0nof5v7LMeJNrjS1UaADs1tDvZ110w/YETifLCBivt -Z8SOyUOyXGsViQK8YvxO8rUzqrJv0wqiUOP2O+guRMLbZjipM1ZI8W0bM40NjD9g -N53Tym1+NH4Nn3J2ixufcv1SNUFFApYvHLKac0khsUlHRUe072o0EclNmsxZt9YC -nlpOZbWUrhvfKbAW8b8Angc6F2S1BLUjIZkKlTuXfO8= ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Networking O=AffirmTrust -# Subject: CN=AffirmTrust Networking O=AffirmTrust -# Label: "AffirmTrust Networking" -# Serial: 8957382827206547757 -# MD5 Fingerprint: 42:65:ca:be:01:9a:9a:4c:a9:8c:41:49:cd:c0:d5:7f -# SHA1 Fingerprint: 29:36:21:02:8b:20:ed:02:f5:66:c5:32:d1:d6:ed:90:9f:45:00:2f -# SHA256 Fingerprint: 0a:81:ec:5a:92:97:77:f1:45:90:4a:f3:8d:5d:50:9f:66:b5:e2:c5:8f:cd:b5:31:05:8b:0e:17:f3:f0:b4:1b ------BEGIN CERTIFICATE----- -MIIDTDCCAjSgAwIBAgIIfE8EORzUmS0wDQYJKoZIhvcNAQEFBQAwRDELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZpcm1UcnVz -dCBOZXR3b3JraW5nMB4XDTEwMDEyOTE0MDgyNFoXDTMwMTIzMTE0MDgyNFowRDEL -MAkGA1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MR8wHQYDVQQDDBZBZmZp -cm1UcnVzdCBOZXR3b3JraW5nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC -AQEAtITMMxcua5Rsa2FSoOujz3mUTOWUgJnLVWREZY9nZOIG41w3SfYvm4SEHi3y -YJ0wTsyEheIszx6e/jarM3c1RNg1lho9Nuh6DtjVR6FqaYvZ/Ls6rnla1fTWcbua -kCNrmreIdIcMHl+5ni36q1Mr3Lt2PpNMCAiMHqIjHNRqrSK6mQEubWXLviRmVSRL -QESxG9fhwoXA3hA/Pe24/PHxI1Pcv2WXb9n5QHGNfb2V1M6+oF4nI979ptAmDgAp -6zxG8D1gvz9Q0twmQVGeFDdCBKNwV6gbh+0t+nvujArjqWaJGctB+d1ENmHP4ndG -yH329JKBNv3bNPFyfvMMFr20FQIDAQABo0IwQDAdBgNVHQ4EFgQUBx/S55zawm6i -QLSwelAQUHTEyL0wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwDQYJ -KoZIhvcNAQEFBQADggEBAIlXshZ6qML91tmbmzTCnLQyFE2npN/svqe++EPbkTfO -tDIuUFUaNU52Q3Eg75N3ThVwLofDwR1t3Mu1J9QsVtFSUzpE0nPIxBsFZVpikpzu -QY0x2+c06lkh1QF612S4ZDnNye2v7UsDSKegmQGA3GWjNq5lWUhPgkvIZfFXHeVZ -Lgo/bNjR9eUJtGxUAArgFU2HdW23WJZa3W3SAKD0m0i+wzekujbgfIeFlxoVot4u -olu9rxj5kFDNcFn4J2dHy8egBzp90SxdbBk6ZrV9/ZFvgrG+CJPbFEfxojfHRZ48 -x3evZKiT3/Zpg4Jg8klCNO1aAFSFHBY2kgxc+qatv9s= ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Premium O=AffirmTrust -# Subject: CN=AffirmTrust Premium O=AffirmTrust -# Label: "AffirmTrust Premium" -# Serial: 7893706540734352110 -# MD5 Fingerprint: c4:5d:0e:48:b6:ac:28:30:4e:0a:bc:f9:38:16:87:57 -# SHA1 Fingerprint: d8:a6:33:2c:e0:03:6f:b1:85:f6:63:4f:7d:6a:06:65:26:32:28:27 -# SHA256 Fingerprint: 70:a7:3f:7f:37:6b:60:07:42:48:90:45:34:b1:14:82:d5:bf:0e:69:8e:cc:49:8d:f5:25:77:eb:f2:e9:3b:9a ------BEGIN CERTIFICATE----- -MIIFRjCCAy6gAwIBAgIIbYwURrGmCu4wDQYJKoZIhvcNAQEMBQAwQTELMAkGA1UE -BhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1UcnVz -dCBQcmVtaXVtMB4XDTEwMDEyOTE0MTAzNloXDTQwMTIzMTE0MTAzNlowQTELMAkG -A1UEBhMCVVMxFDASBgNVBAoMC0FmZmlybVRydXN0MRwwGgYDVQQDDBNBZmZpcm1U -cnVzdCBQcmVtaXVtMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBLf -qV/+Qd3d9Z+K4/as4Tx4mrzY8H96oDMq3I0gW64tb+eT2TZwamjPjlGjhVtnBKAQ -JG9dKILBl1fYSCkTtuG+kU3fhQxTGJoeJKJPj/CihQvL9Cl/0qRY7iZNyaqoe5rZ -+jjeRFcV5fiMyNlI4g0WJx0eyIOFJbe6qlVBzAMiSy2RjYvmia9mx+n/K+k8rNrS -s8PhaJyJ+HoAVt70VZVs+7pk3WKL3wt3MutizCaam7uqYoNMtAZ6MMgpv+0GTZe5 -HMQxK9VfvFMSF5yZVylmd2EhMQcuJUmdGPLu8ytxjLW6OQdJd/zvLpKQBY0tL3d7 -70O/Nbua2Plzpyzy0FfuKE4mX4+QaAkvuPjcBukumj5Rp9EixAqnOEhss/n/fauG -V+O61oV4d7pD6kh/9ti+I20ev9E2bFhc8e6kGVQa9QPSdubhjL08s9NIS+LI+H+S -qHZGnEJlPqQewQcDWkYtuJfzt9WyVSHvutxMAJf7FJUnM7/oQ0dG0giZFmA7mn7S -5u046uwBHjxIVkkJx0w3AJ6IDsBz4W9m6XJHMD4Q5QsDyZpCAGzFlH5hxIrff4Ia -C1nEWTJ3s7xgaVY5/bQGeyzWZDbZvUjthB9+pSKPKrhC9IK31FOQeE4tGv2Bb0TX -OwF0lkLgAOIua+rF7nKsu7/+6qqo+Nz2snmKtmcCAwEAAaNCMEAwHQYDVR0OBBYE -FJ3AZ6YMItkm9UWrpmVSESfYRaxjMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ -BAQDAgEGMA0GCSqGSIb3DQEBDAUAA4ICAQCzV00QYk465KzquByvMiPIs0laUZx2 -KI15qldGF9X1Uva3ROgIRL8YhNILgM3FEv0AVQVhh0HctSSePMTYyPtwni94loMg -Nt58D2kTiKV1NpgIpsbfrM7jWNa3Pt668+s0QNiigfV4Py/VpfzZotReBA4Xrf5B -8OWycvpEgjNC6C1Y91aMYj+6QrCcDFx+LmUmXFNPALJ4fqENmS2NuB2OosSw/WDQ -MKSOyARiqcTtNd56l+0OOF6SL5Nwpamcb6d9Ex1+xghIsV5n61EIJenmJWtSKZGc -0jlzCFfemQa0W50QBuHCAKi4HEoCChTQwUHK+4w1IX2COPKpVJEZNZOUbWo6xbLQ -u4mGk+ibyQ86p3q4ofB4Rvr8Ny/lioTz3/4E2aFooC8k4gmVBtWVyuEklut89pMF -u+1z6S3RdTnX5yTb2E5fQ4+e0BQ5v1VwSJlXMbSc7kqYA5YwH2AG7hsj/oFgIxpH -YoWlzBk0gG+zrBrjn/B7SK3VAdlntqlyk+otZrWyuOQ9PLLvTIzq6we/qzWaVYa8 -GKa1qF60g2xraUDTn9zxw2lrueFtCfTxqlB2Cnp9ehehVZZCmTEJ3WARjQUwfuaO -RtGdFNrHF+QFlozEJLUbzxQHskD4o55BhrwE0GuWyCqANP2/7waj3VjFhT0+j/6e -KeC2uAloGRwYQw== ------END CERTIFICATE----- - -# Issuer: CN=AffirmTrust Premium ECC O=AffirmTrust -# Subject: CN=AffirmTrust Premium ECC O=AffirmTrust -# Label: "AffirmTrust Premium ECC" -# Serial: 8401224907861490260 -# MD5 Fingerprint: 64:b0:09:55:cf:b1:d5:99:e2:be:13:ab:a6:5d:ea:4d -# SHA1 Fingerprint: b8:23:6b:00:2f:1d:16:86:53:01:55:6c:11:a4:37:ca:eb:ff:c3:bb -# SHA256 Fingerprint: bd:71:fd:f6:da:97:e4:cf:62:d1:64:7a:dd:25:81:b0:7d:79:ad:f8:39:7e:b4:ec:ba:9c:5e:84:88:82:14:23 ------BEGIN CERTIFICATE----- -MIIB/jCCAYWgAwIBAgIIdJclisc/elQwCgYIKoZIzj0EAwMwRTELMAkGA1UEBhMC -VVMxFDASBgNVBAoMC0FmZmlybVRydXN0MSAwHgYDVQQDDBdBZmZpcm1UcnVzdCBQ -cmVtaXVtIEVDQzAeFw0xMDAxMjkxNDIwMjRaFw00MDEyMzExNDIwMjRaMEUxCzAJ -BgNVBAYTAlVTMRQwEgYDVQQKDAtBZmZpcm1UcnVzdDEgMB4GA1UEAwwXQWZmaXJt -VHJ1c3QgUHJlbWl1bSBFQ0MwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQNMF4bFZ0D -0KF5Nbc6PJJ6yhUczWLznCZcBz3lVPqj1swS6vQUX+iOGasvLkjmrBhDeKzQN8O9 -ss0s5kfiGuZjuD0uL3jET9v0D6RoTFVya5UdThhClXjMNzyR4ptlKymjQjBAMB0G -A1UdDgQWBBSaryl6wBE1NSZRMADDav5A1a7WPDAPBgNVHRMBAf8EBTADAQH/MA4G -A1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNnADBkAjAXCfOHiFBar8jAQr9HX/Vs -aobgxCd05DhT1wV/GzTjxi+zygk8N53X57hG8f2h4nECMEJZh0PUUd+60wkyWs6I -flc9nF9Ca/UHLbXwgpP5WW+uZPpY5Yse42O+tYHNbwKMeQ== ------END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Network CA O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Network CA" -# Serial: 279744 -# MD5 Fingerprint: d5:e9:81:40:c5:18:69:fc:46:2c:89:75:62:0f:aa:78 -# SHA1 Fingerprint: 07:e0:32:e0:20:b7:2c:3f:19:2f:06:28:a2:59:3a:19:a7:0f:06:9e -# SHA256 Fingerprint: 5c:58:46:8d:55:f5:8e:49:7e:74:39:82:d2:b5:00:10:b6:d1:65:37:4a:cf:83:a7:d4:a3:2d:b7:68:c4:40:8e ------BEGIN CERTIFICATE----- -MIIDuzCCAqOgAwIBAgIDBETAMA0GCSqGSIb3DQEBBQUAMH4xCzAJBgNVBAYTAlBM -MSIwIAYDVQQKExlVbml6ZXRvIFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLEx5D -ZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxIjAgBgNVBAMTGUNlcnR1bSBU -cnVzdGVkIE5ldHdvcmsgQ0EwHhcNMDgxMDIyMTIwNzM3WhcNMjkxMjMxMTIwNzM3 -WjB+MQswCQYDVQQGEwJQTDEiMCAGA1UEChMZVW5pemV0byBUZWNobm9sb2dpZXMg -Uy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSIw -IAYDVQQDExlDZXJ0dW0gVHJ1c3RlZCBOZXR3b3JrIENBMIIBIjANBgkqhkiG9w0B -AQEFAAOCAQ8AMIIBCgKCAQEA4/t9o3K6wvDJFIf1awFO4W5AB7ptJ11/91sts1rH -UV+rpDKmYYe2bg+G0jACl/jXaVehGDldamR5xgFZrDwxSjh80gTSSyjoIF87B6LM -TXPb865Px1bVWqeWifrzq2jUI4ZZJ88JJ7ysbnKDHDBy3+Ci6dLhdHUZvSqeexVU -BBvXQzmtVSjF4hq79MDkrjhJM8x2hZ85RdKknvISjFH4fOQtf/WsX+sWn7Et0brM -kUJ3TCXJkDhv2/DM+44el1k+1WBO5gUo7Ul5E0u6SNsv+XLTOcr+H9g0cvW0QM8x -AcPs3hEtF10fuFDRXhmnad4HMyjKUJX5p1TLVIZQRan5SQIDAQABo0IwQDAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBQIds3LB/8k9sXN7buQvOKEN0Z19zAOBgNV -HQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEFBQADggEBAKaorSLOAT2mo/9i0Eidi15y -sHhE49wcrwn9I0j6vSrEuVUEtRCjjSfeC4Jj0O7eDDd5QVsisrCaQVymcODU0HfL -I9MA4GxWL+FpDQ3Zqr8hgVDZBqWo/5U30Kr+4rP1mS1FhIrlQgnXdAIv94nYmem8 -J9RHjboNRhx3zxSkHLmkMcScKHQDNP8zGSal6Q10tz6XxnboJ5ajZt3hrvJBW8qY -VoNzcOSGGtIxQbovvi0TWnZvTuhOgQ4/WwMioBK+ZlgRSssDxLQqKi2WF+A5VLxI -03YnnZotBqbJ7DnSq9ufmgsnAjUpsUCV5/nonFWIGUbWtzT1fs45mtk48VH3Tyw= ------END CERTIFICATE----- - -# Issuer: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA Root Certification Authority O=TAIWAN-CA OU=Root CA -# Label: "TWCA Root Certification Authority" -# Serial: 1 -# MD5 Fingerprint: aa:08:8f:f6:f9:7b:b7:f2:b1:a7:1e:9b:ea:ea:bd:79 -# SHA1 Fingerprint: cf:9e:87:6d:d3:eb:fc:42:26:97:a3:b5:a3:7a:a0:76:a9:06:23:48 -# SHA256 Fingerprint: bf:d8:8f:e1:10:1c:41:ae:3e:80:1b:f8:be:56:35:0e:e9:ba:d1:a6:b9:bd:51:5e:dc:5c:6d:5b:87:11:ac:44 ------BEGIN CERTIFICATE----- -MIIDezCCAmOgAwIBAgIBATANBgkqhkiG9w0BAQUFADBfMQswCQYDVQQGEwJUVzES -MBAGA1UECgwJVEFJV0FOLUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFU -V0NBIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMDgwODI4MDcyNDMz -WhcNMzAxMjMxMTU1OTU5WjBfMQswCQYDVQQGEwJUVzESMBAGA1UECgwJVEFJV0FO -LUNBMRAwDgYDVQQLDAdSb290IENBMSowKAYDVQQDDCFUV0NBIFJvb3QgQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCwfnK4pAOU5qfeCTiRShFAh6d8WWQUe7UREN3+v9XAu1bihSX0NXIP+FPQQeFE -AcK0HMMxQhZHhTMidrIKbw/lJVBPhYa+v5guEGcevhEFhgWQxFnQfHgQsIBct+HH -K3XLfJ+utdGdIzdjp9xCoi2SBBtQwXu4PhvJVgSLL1KbralW6cH/ralYhzC2gfeX -RfwZVzsrb+RH9JlF/h3x+JejiB03HFyP4HYlmlD4oFT/RJB2I9IyxsOrBr/8+7/z -rX2SYgJbKdM1o5OaQ2RgXbL6Mv87BK9NQGr5x+PvI/1ry+UPizgN7gr8/g+YnzAx -3WxSZfmLgb4i4RxYA7qRG4kHAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqOFsmjd6LWvJPelSDGRjjCDWmujANBgkq -hkiG9w0BAQUFAAOCAQEAPNV3PdrfibqHDAhUaiBQkr6wQT25JmSDCi/oQMCXKCeC -MErJk/9q56YAf4lCmtYR5VPOL8zy2gXE/uJQxDqGfczafhAJO5I1KlOy/usrBdls -XebQ79NqZp4VKIV66IIArB6nCWlWQtNoURi+VJq/REG6Sb4gumlc7rh3zc5sH62D -lhh9DrUUOYTxKOkto557HnpyWoOzeW/vtPzQCqVYT0bf+215WfKEIlKuD8z7fDvn -aspHYcN6+NOSBB+4IIThNlQWx0DeO4pz3N/GCUzf7Nr/1FNCocnyYh0igzyXxfkZ -YiesZSLX0zzG5Y6yU8xJzrww/nsOM5D77dIUkR8Hrw== ------END CERTIFICATE----- - -# Issuer: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 -# Subject: O=SECOM Trust Systems CO.,LTD. OU=Security Communication RootCA2 -# Label: "Security Communication RootCA2" -# Serial: 0 -# MD5 Fingerprint: 6c:39:7d:a4:0e:55:59:b2:3f:d6:41:b1:12:50:de:43 -# SHA1 Fingerprint: 5f:3b:8c:f2:f8:10:b3:7d:78:b4:ce:ec:19:19:c3:73:34:b9:c7:74 -# SHA256 Fingerprint: 51:3b:2c:ec:b8:10:d4:cd:e5:dd:85:39:1a:df:c6:c2:dd:60:d8:7b:b7:36:d2:b5:21:48:4a:a4:7a:0e:be:f6 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIBADANBgkqhkiG9w0BAQsFADBdMQswCQYDVQQGEwJKUDEl -MCMGA1UEChMcU0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UECxMe -U2VjdXJpdHkgQ29tbXVuaWNhdGlvbiBSb290Q0EyMB4XDTA5MDUyOTA1MDAzOVoX -DTI5MDUyOTA1MDAzOVowXTELMAkGA1UEBhMCSlAxJTAjBgNVBAoTHFNFQ09NIFRy -dXN0IFN5c3RlbXMgQ08uLExURC4xJzAlBgNVBAsTHlNlY3VyaXR5IENvbW11bmlj -YXRpb24gUm9vdENBMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANAV -OVKxUrO6xVmCxF1SrjpDZYBLx/KWvNs2l9amZIyoXvDjChz335c9S672XewhtUGr -zbl+dp+++T42NKA7wfYxEUV0kz1XgMX5iZnK5atq1LXaQZAQwdbWQonCv/Q4EpVM -VAX3NuRFg3sUZdbcDE3R3n4MqzvEFb46VqZab3ZpUql6ucjrappdUtAtCms1FgkQ -hNBqyjoGADdH5H5XTz+L62e4iKrFvlNVspHEfbmwhRkGeC7bYRr6hfVKkaHnFtWO -ojnflLhwHyg/i/xAXmODPIMqGplrz95Zajv8bxbXH/1KEOtOghY6rCcMU/Gt1SSw -awNQwS08Ft1ENCcadfsCAwEAAaNCMEAwHQYDVR0OBBYEFAqFqXdlBZh8QIH4D5cs -OPEK7DzPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 -DQEBCwUAA4IBAQBMOqNErLlFsceTfsgLCkLfZOoc7llsCLqJX2rKSpWeeo8HxdpF -coJxDjrSzG+ntKEju/Ykn8sX/oymzsLS28yN/HH8AynBbF0zX2S2ZTuJbxh2ePXc -okgfGT+Ok+vx+hfuzU7jBBJV1uXk3fs+BXziHV7Gp7yXT2g69ekuCkO2r1dcYmh8 -t/2jioSgrGK+KwmHNPBqAbubKVY8/gA3zyNs8U6qtnRGEmyR7jTV7JqR50S+kDFy -1UkC9gLl9B/rfNmWVan/7Ir5mUf/NVoCqgTLiluHcSmRvaS0eg29mvVXIwAHIRc/ -SjnRBUkLp7Y3gaVdjKozXoEofKd9J+sAro03 ------END CERTIFICATE----- - -# Issuer: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 -# Subject: CN=Actalis Authentication Root CA O=Actalis S.p.A./03358520967 -# Label: "Actalis Authentication Root CA" -# Serial: 6271844772424770508 -# MD5 Fingerprint: 69:c1:0d:4f:07:a3:1b:c3:fe:56:3d:04:bc:11:f6:a6 -# SHA1 Fingerprint: f3:73:b3:87:06:5a:28:84:8a:f2:f3:4a:ce:19:2b:dd:c7:8e:9c:ac -# SHA256 Fingerprint: 55:92:60:84:ec:96:3a:64:b9:6e:2a:be:01:ce:0b:a8:6a:64:fb:fe:bc:c7:aa:b5:af:c1:55:b3:7f:d7:60:66 ------BEGIN CERTIFICATE----- -MIIFuzCCA6OgAwIBAgIIVwoRl0LE48wwDQYJKoZIhvcNAQELBQAwazELMAkGA1UE -BhMCSVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8w -MzM1ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290 -IENBMB4XDTExMDkyMjExMjIwMloXDTMwMDkyMjExMjIwMlowazELMAkGA1UEBhMC -SVQxDjAMBgNVBAcMBU1pbGFuMSMwIQYDVQQKDBpBY3RhbGlzIFMucC5BLi8wMzM1 -ODUyMDk2NzEnMCUGA1UEAwweQWN0YWxpcyBBdXRoZW50aWNhdGlvbiBSb290IENB -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8bEpSmkLO/lGMWwUKNv -UTufClrJwkg4CsIcoBh/kbWHuUA/3R1oHwiD1S0eiKD4j1aPbZkCkpAW1V8IbInX -4ay8IMKx4INRimlNAJZaby/ARH6jDuSRzVju3PvHHkVH3Se5CAGfpiEd9UEtL0z9 -KK3giq0itFZljoZUj5NDKd45RnijMCO6zfB9E1fAXdKDa0hMxKufgFpbOr3JpyI/ -gCczWw63igxdBzcIy2zSekciRDXFzMwujt0q7bd9Zg1fYVEiVRvjRuPjPdA1Yprb -rxTIW6HMiRvhMCb8oJsfgadHHwTrozmSBp+Z07/T6k9QnBn+locePGX2oxgkg4YQ -51Q+qDp2JE+BIcXjDwL4k5RHILv+1A7TaLndxHqEguNTVHnd25zS8gebLra8Pu2F -be8lEfKXGkJh90qX6IuxEAf6ZYGyojnP9zz/GPvG8VqLWeICrHuS0E4UT1lF9gxe -KF+w6D9Fz8+vm2/7hNN3WpVvrJSEnu68wEqPSpP4RCHiMUVhUE4Q2OM1fEwZtN4F -v6MGn8i1zeQf1xcGDXqVdFUNaBr8EBtiZJ1t4JWgw5QHVw0U5r0F+7if5t+L4sbn -fpb2U8WANFAoWPASUHEXMLrmeGO89LKtmyuy/uE5jF66CyCU3nuDuP/jVo23Eek7 -jPKxwV2dpAtMK9myGPW1n0sCAwEAAaNjMGEwHQYDVR0OBBYEFFLYiDrIn3hm7Ynz -ezhwlMkCAjbQMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUUtiIOsifeGbt -ifN7OHCUyQICNtAwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAL -e3KHwGCmSUyIWOYdiPcUZEim2FgKDk8TNd81HdTtBjHIgT5q1d07GjLukD0R0i70 -jsNjLiNmsGe+b7bAEzlgqqI0JZN1Ut6nna0Oh4lScWoWPBkdg/iaKWW+9D+a2fDz -WochcYBNy+A4mz+7+uAwTc+G02UQGRjRlwKxK3JCaKygvU5a2hi/a5iB0P2avl4V -SM0RFbnAKVy06Ij3Pjaut2L9HmLecHgQHEhb2rykOLpn7VU+Xlff1ANATIGk0k9j -pwlCCRT8AKnCgHNPLsBA2RF7SOp6AsDT6ygBJlh0wcBzIm2Tlf05fbsq4/aC4yyX -X04fkZT6/iyj2HYauE2yOE+b+h1IYHkm4vP9qdCa6HCPSXrW5b0KDtst842/6+Ok -fcvHlXHo2qN8xcL4dJIEG4aspCJTQLas/kx2z/uUMsA1n3Y/buWQbqCmJqK4LL7R -K4X9p2jIugErsWx0Hbhzlefut8cl8ABMALJ+tguLHPPAUJ4lueAI3jZm/zel0btU -ZCzJJ7VLkn5l/9Mt4blOvH+kQSGQQXemOR/qnuOf0GZvBeyqdn6/axag67XH/JJU -LysRJyU3eExRarDzzFhdFPFqSBX/wge2sY0PjlxQRrM9vwGYT7JZVEc+NHt4bVaT -LnPqZih4zR0Uv6CPLy64Lo7yFIrM6bV8+2ydDKXhlg== ------END CERTIFICATE----- - -# Issuer: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 -# Subject: CN=Buypass Class 2 Root CA O=Buypass AS-983163327 -# Label: "Buypass Class 2 Root CA" -# Serial: 2 -# MD5 Fingerprint: 46:a7:d2:fe:45:fb:64:5a:a8:59:90:9b:78:44:9b:29 -# SHA1 Fingerprint: 49:0a:75:74:de:87:0a:47:fe:58:ee:f6:c7:6b:eb:c6:0b:12:40:99 -# SHA256 Fingerprint: 9a:11:40:25:19:7c:5b:b9:5d:94:e6:3d:55:cd:43:79:08:47:b6:46:b2:3c:df:11:ad:a4:a0:0e:ff:15:fb:48 ------BEGIN CERTIFICATE----- -MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg -Q2xhc3MgMiBSb290IENBMB4XDTEwMTAyNjA4MzgwM1oXDTQwMTAyNjA4MzgwM1ow -TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw -HgYDVQQDDBdCdXlwYXNzIENsYXNzIDIgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB -BQADggIPADCCAgoCggIBANfHXvfBB9R3+0Mh9PT1aeTuMgHbo4Yf5FkNuud1g1Lr -6hxhFUi7HQfKjK6w3Jad6sNgkoaCKHOcVgb/S2TwDCo3SbXlzwx87vFKu3MwZfPV -L4O2fuPn9Z6rYPnT8Z2SdIrkHJasW4DptfQxh6NR/Md+oW+OU3fUl8FVM5I+GC91 -1K2GScuVr1QGbNgGE41b/+EmGVnAJLqBcXmQRFBoJJRfuLMR8SlBYaNByyM21cHx -MlAQTn/0hpPshNOOvEu/XAFOBz3cFIqUCqTqc/sLUegTBxj6DvEr0VQVfTzh97QZ -QmdiXnfgolXsttlpF9U6r0TtSsWe5HonfOV116rLJeffawrbD02TTqigzXsu8lkB -arcNuAeBfos4GzjmCleZPe4h6KP1DBbdi+w0jpwqHAAVF41og9JwnxgIzRFo1clr -Us3ERo/ctfPYV3Me6ZQ5BL/T3jjetFPsaRyifsSP5BtwrfKi+fv3FmRmaZ9JUaLi -FRhnBkp/1Wy1TbMz4GHrXb7pmA8y1x1LPC5aAVKRCfLf6o3YBkBjqhHk/sM3nhRS -P/TizPJhk9H9Z2vXUq6/aKtAQ6BXNVN48FP4YUIHZMbXb5tMOA1jrGKvNouicwoN -9SG9dKpN6nIDSdvHXx1iY8f93ZHsM+71bbRuMGjeyNYmsHVee7QHIJihdjK4TWxP -AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFMmAd+BikoL1Rpzz -uvdMw964o605MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAU18h -9bqwOlI5LJKwbADJ784g7wbylp7ppHR/ehb8t/W2+xUbP6umwHJdELFx7rxP462s -A20ucS6vxOOto70MEae0/0qyexAQH6dXQbLArvQsWdZHEIjzIVEpMMpghq9Gqx3t -OluwlN5E40EIosHsHdb9T7bWR9AUC8rmyrV7d35BH16Dx7aMOZawP5aBQW9gkOLo -+fsicdl9sz1Gv7SEr5AcD48Saq/v7h56rgJKihcrdv6sVIkkLE8/trKnToyokZf7 -KcZ7XC25y2a2t6hbElGFtQl+Ynhw/qlqYLYdDnkM/crqJIByw5c/8nerQyIKx+u2 -DISCLIBrQYoIwOula9+ZEsuK1V6ADJHgJgg2SMX6OBE1/yWDLfJ6v9r9jv6ly0Us -H8SIU653DtmadsWOLB2jutXsMq7Aqqz30XpN69QH4kj3Io6wpJ9qzo6ysmD0oyLQ -I+uUWnpp3Q+/QFesa1lQ2aOZ4W7+jQF5JyMV3pKdewlNWudLSDBaGOYKbeaP4NK7 -5t98biGCwWg5TbSYWGZizEqQXsP6JwSxeRV0mcy+rSDeJmAc61ZRpqPq5KM/p/9h -3PFaTWwyI0PurKju7koSCTxdccK+efrCh2gdC/1cacwG0Jp9VJkqyTkaGa9LKkPz -Y11aWOIv4x3kqdbQCtCev9eBCfHJxyYNrJgWVqA= ------END CERTIFICATE----- - -# Issuer: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 -# Subject: CN=Buypass Class 3 Root CA O=Buypass AS-983163327 -# Label: "Buypass Class 3 Root CA" -# Serial: 2 -# MD5 Fingerprint: 3d:3b:18:9e:2c:64:5a:e8:d5:88:ce:0e:f9:37:c2:ec -# SHA1 Fingerprint: da:fa:f7:fa:66:84:ec:06:8f:14:50:bd:c7:c2:81:a5:bc:a9:64:57 -# SHA256 Fingerprint: ed:f7:eb:bc:a2:7a:2a:38:4d:38:7b:7d:40:10:c6:66:e2:ed:b4:84:3e:4c:29:b4:ae:1d:5b:93:32:e6:b2:4d ------BEGIN CERTIFICATE----- -MIIFWTCCA0GgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBOMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxIDAeBgNVBAMMF0J1eXBhc3Mg -Q2xhc3MgMyBSb290IENBMB4XDTEwMTAyNjA4Mjg1OFoXDTQwMTAyNjA4Mjg1OFow -TjELMAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MSAw -HgYDVQQDDBdCdXlwYXNzIENsYXNzIDMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEB -BQADggIPADCCAgoCggIBAKXaCpUWUOOV8l6ddjEGMnqb8RB2uACatVI2zSRHsJ8Y -ZLya9vrVediQYkwiL944PdbgqOkcLNt4EemOaFEVcsfzM4fkoF0LXOBXByow9c3E -N3coTRiR5r/VUv1xLXA+58bEiuPwKAv0dpihi4dVsjoT/Lc+JzeOIuOoTyrvYLs9 -tznDDgFHmV0ST9tD+leh7fmdvhFHJlsTmKtdFoqwNxxXnUX/iJY2v7vKB3tvh2PX -0DJq1l1sDPGzbjniazEuOQAnFN44wOwZZoYS6J1yFhNkUsepNxz9gjDthBgd9K5c -/3ATAOux9TN6S9ZV+AWNS2mw9bMoNlwUxFFzTWsL8TQH2xc519woe2v1n/MuwU8X -KhDzzMro6/1rqy6any2CbgTUUgGTLT2G/H783+9CHaZr77kgxve9oKeV/afmiSTY -zIw0bOIjL9kSGiG5VZFvC5F5GQytQIgLcOJ60g7YaEi7ghM5EFjp2CoHxhLbWNvS -O1UQRwUVZ2J+GGOmRj8JDlQyXr8NYnon74Do29lLBlo3WiXQCBJ31G8JUJc9yB3D -34xFMFbG02SrZvPAXpacw8Tvw3xrizp5f7NJzz3iiZ+gMEuFuZyUJHmPfWupRWgP -K9Dx2hzLabjKSWJtyNBjYt1gD1iqj6G8BaVmos8bdrKEZLFMOVLAMLrwjEsCsLa3 -AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFEe4zf/lb+74suwv -Tg75JbCOPGvDMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAACAj -QTUEkMJAYmDv4jVM1z+s4jSQuKFvdvoWFqRINyzpkMLyPPgKn9iB5btb2iUspKdV -cSQy9sgL8rxq+JOssgfCX5/bzMiKqr5qb+FJEMwx14C7u8jYog5kV+qi9cKpMRXS -IGrs/CIBKM+GuIAeqcwRpTzyFrNHnfzSgCHEy9BHcEGhyoMZCCxt8l13nIoUE9Q2 -HJLw5QY33KbmkJs4j1xrG0aGQ0JfPgEHU1RdZX33inOhmlRaHylDFCfChQ+1iHsa -O5S3HWCntZznKWlXWpuTekMwGwPXYshApqr8ZORK15FTAaggiG6cX0S5y2CBNOxv -033aSF/rtJC8LakcC6wc1aJoIIAE1vyxjy+7SjENSoYc6+I2KSb12tjE8nVhz36u -dmNKekBlk4f4HoCMhuWG1o8O/FMsYOgWYRqiPkN7zTlgVGr18okmAWiDSKIz6MkE -kbIRNBE+6tBDGR8Dk5AM/1E9V/RBbuHLoL7ryWPNbczk+DaqaJ3tvV2XcEQNtg41 -3OEMXbugUZTLfhbrES+jkkXITHHZvMmZUldGL1DPvTVp9D0VzgalLA8+9oG6lLvD -u79leNKGef9JOxqDDPDeeOzI8k1MGt6CKfjBWtrt7uYnXuhF0J0cUahoq0Tj0Itq -4/g7u9xN12TyUb7mqqta6THuBrxzvxNiCp/HuZc= ------END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 3 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 3" -# Serial: 1 -# MD5 Fingerprint: ca:fb:40:a8:4e:39:92:8a:1d:fe:8e:2f:c4:27:ea:ef -# SHA1 Fingerprint: 55:a6:72:3e:cb:f2:ec:cd:c3:23:74:70:19:9d:2a:be:11:e3:81:d1 -# SHA256 Fingerprint: fd:73:da:d3:1c:64:4f:f1:b4:3b:ef:0c:cd:da:96:71:0b:9c:d9:87:5e:ca:7e:31:70:7a:f3:e9:6d:52:2b:bd ------BEGIN CERTIFICATE----- -MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx -KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd -BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl -YyBHbG9iYWxSb290IENsYXNzIDMwHhcNMDgxMDAxMTAyOTU2WhcNMzMxMDAxMjM1 -OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy -aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 -ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDMwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC9dZPwYiJvJK7genasfb3ZJNW4t/zN -8ELg63iIVl6bmlQdTQyK9tPPcPRStdiTBONGhnFBSivwKixVA9ZIw+A5OO3yXDw/ -RLyTPWGrTs0NvvAgJ1gORH8EGoel15YUNpDQSXuhdfsaa3Ox+M6pCSzyU9XDFES4 -hqX2iys52qMzVNn6chr3IhUciJFrf2blw2qAsCTz34ZFiP0Zf3WHHx+xGwpzJFu5 -ZeAsVMhg02YXP+HMVDNzkQI6pn97djmiH5a2OK61yJN0HZ65tOVgnS9W0eDrXltM -EnAMbEQgqxHY9Bn20pxSN+f6tsIxO0rUFJmtxxr1XV/6B7h8DR/Wgx6zAgMBAAGj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS1 -A/d2O2GCahKqGFPrAyGUv/7OyjANBgkqhkiG9w0BAQsFAAOCAQEAVj3vlNW92nOy -WL6ukK2YJ5f+AbGwUgC4TeQbIXQbfsDuXmkqJa9c1h3a0nnJ85cp4IaH3gRZD/FZ -1GSFS5mvJQQeyUapl96Cshtwn5z2r3Ex3XsFpSzTucpH9sry9uetuUg/vBa3wW30 -6gmv7PO15wWeph6KU1HWk4HMdJP2udqmJQV0eVp+QD6CSyYRMG7hP0HHRwA11fXT -91Q+gT3aSWqas+8QPebrb9HIIkfLzM8BMZLZGOMivgkeGj5asuRrDFR6fUNOuIml -e9eiPZaGzPImNC1qkp2aGtAw4l1OBLBfiyB+d8E9lYLRRpo7PHi4b6HQDWSieB4p -TpPDpFQUWw== ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH -# Subject: CN=D-TRUST Root Class 3 CA 2 2009 O=D-Trust GmbH -# Label: "D-TRUST Root Class 3 CA 2 2009" -# Serial: 623603 -# MD5 Fingerprint: cd:e0:25:69:8d:47:ac:9c:89:35:90:f7:fd:51:3d:2f -# SHA1 Fingerprint: 58:e8:ab:b0:36:15:33:fb:80:f7:9b:1b:6d:29:d3:ff:8d:5f:00:f0 -# SHA256 Fingerprint: 49:e7:a4:42:ac:f0:ea:62:87:05:00:54:b5:25:64:b6:50:e4:f4:9e:42:e3:48:d6:aa:38:e0:39:e9:57:b1:c1 ------BEGIN CERTIFICATE----- -MIIEMzCCAxugAwIBAgIDCYPzMA0GCSqGSIb3DQEBCwUAME0xCzAJBgNVBAYTAkRF -MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMMHkQtVFJVU1QgUm9vdCBD -bGFzcyAzIENBIDIgMjAwOTAeFw0wOTExMDUwODM1NThaFw0yOTExMDUwODM1NTha -ME0xCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxJzAlBgNVBAMM -HkQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgMjAwOTCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBANOySs96R+91myP6Oi/WUEWJNTrGa9v+2wBoqOADER03 -UAifTUpolDWzU9GUY6cgVq/eUXjsKj3zSEhQPgrfRlWLJ23DEE0NkVJD2IfgXU42 -tSHKXzlABF9bfsyjxiupQB7ZNoTWSPOSHjRGICTBpFGOShrvUD9pXRl/RcPHAY9R -ySPocq60vFYJfxLLHLGvKZAKyVXMD9O0Gu1HNVpK7ZxzBCHQqr0ME7UAyiZsxGsM -lFqVlNpQmvH/pStmMaTJOKDfHR+4CS7zp+hnUquVH+BGPtikw8paxTGA6Eian5Rp -/hnd2HN8gcqW3o7tszIFZYQ05ub9VxC1X3a/L7AQDcUCAwEAAaOCARowggEWMA8G -A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFP3aFMSfMN4hvR5COfyrYyNJ4PGEMA4G -A1UdDwEB/wQEAwIBBjCB0wYDVR0fBIHLMIHIMIGAoH6gfIZ6bGRhcDovL2RpcmVj -dG9yeS5kLXRydXN0Lm5ldC9DTj1ELVRSVVNUJTIwUm9vdCUyMENsYXNzJTIwMyUy -MENBJTIwMiUyMDIwMDksTz1ELVRydXN0JTIwR21iSCxDPURFP2NlcnRpZmljYXRl -cmV2b2NhdGlvbmxpc3QwQ6BBoD+GPWh0dHA6Ly93d3cuZC10cnVzdC5uZXQvY3Js -L2QtdHJ1c3Rfcm9vdF9jbGFzc18zX2NhXzJfMjAwOS5jcmwwDQYJKoZIhvcNAQEL -BQADggEBAH+X2zDI36ScfSF6gHDOFBJpiBSVYEQBrLLpME+bUMJm2H6NMLVwMeni -acfzcNsgFYbQDfC+rAF1hM5+n02/t2A7nPPKHeJeaNijnZflQGDSNiH+0LS4F9p0 -o3/U37CYAqxva2ssJSRyoWXuJVrl5jLn8t+rSfrzkGkj2wTZ51xY/GXUl77M/C4K -zCUqNQT4YJEVdT1B/yMfGchs64JTBKbkTCJNjYy6zltz7GRUUG3RnFX7acM2w4y8 -PIWmawomDeCTmGCufsYkl4phX5GOZpIJhzbNi5stPvZR1FDUWSi9g/LMKHtThm3Y -Johw1+qRzT65ysCQblrGXnRl11z+o+I= ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH -# Subject: CN=D-TRUST Root Class 3 CA 2 EV 2009 O=D-Trust GmbH -# Label: "D-TRUST Root Class 3 CA 2 EV 2009" -# Serial: 623604 -# MD5 Fingerprint: aa:c6:43:2c:5e:2d:cd:c4:34:c0:50:4f:11:02:4f:b6 -# SHA1 Fingerprint: 96:c9:1b:0b:95:b4:10:98:42:fa:d0:d8:22:79:fe:60:fa:b9:16:83 -# SHA256 Fingerprint: ee:c5:49:6b:98:8c:e9:86:25:b9:34:09:2e:ec:29:08:be:d0:b0:f3:16:c2:d4:73:0c:84:ea:f1:f3:d3:48:81 ------BEGIN CERTIFICATE----- -MIIEQzCCAyugAwIBAgIDCYP0MA0GCSqGSIb3DQEBCwUAMFAxCzAJBgNVBAYTAkRF -MRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNVBAMMIUQtVFJVU1QgUm9vdCBD -bGFzcyAzIENBIDIgRVYgMjAwOTAeFw0wOTExMDUwODUwNDZaFw0yOTExMDUwODUw -NDZaMFAxCzAJBgNVBAYTAkRFMRUwEwYDVQQKDAxELVRydXN0IEdtYkgxKjAoBgNV -BAMMIUQtVFJVU1QgUm9vdCBDbGFzcyAzIENBIDIgRVYgMjAwOTCCASIwDQYJKoZI -hvcNAQEBBQADggEPADCCAQoCggEBAJnxhDRwui+3MKCOvXwEz75ivJn9gpfSegpn -ljgJ9hBOlSJzmY3aFS3nBfwZcyK3jpgAvDw9rKFs+9Z5JUut8Mxk2og+KbgPCdM0 -3TP1YtHhzRnp7hhPTFiu4h7WDFsVWtg6uMQYZB7jM7K1iXdODL/ZlGsTl28So/6Z -qQTMFexgaDbtCHu39b+T7WYxg4zGcTSHThfqr4uRjRxWQa4iN1438h3Z0S0NL2lR -p75mpoo6Kr3HGrHhFPC+Oh25z1uxav60sUYgovseO3Dvk5h9jHOW8sXvhXCtKSb8 -HgQ+HKDYD8tSg2J87otTlZCpV6LqYQXY+U3EJ/pure3511H3a6UCAwEAAaOCASQw -ggEgMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNOUikxiEyoZLsyvcop9Ntea -HNxnMA4GA1UdDwEB/wQEAwIBBjCB3QYDVR0fBIHVMIHSMIGHoIGEoIGBhn9sZGFw -Oi8vZGlyZWN0b3J5LmQtdHJ1c3QubmV0L0NOPUQtVFJVU1QlMjBSb290JTIwQ2xh -c3MlMjAzJTIwQ0ElMjAyJTIwRVYlMjAyMDA5LE89RC1UcnVzdCUyMEdtYkgsQz1E -RT9jZXJ0aWZpY2F0ZXJldm9jYXRpb25saXN0MEagRKBChkBodHRwOi8vd3d3LmQt -dHJ1c3QubmV0L2NybC9kLXRydXN0X3Jvb3RfY2xhc3NfM19jYV8yX2V2XzIwMDku -Y3JsMA0GCSqGSIb3DQEBCwUAA4IBAQA07XtaPKSUiO8aEXUHL7P+PPoeUSbrh/Yp -3uDx1MYkCenBz1UbtDDZzhr+BlGmFaQt77JLvyAoJUnRpjZ3NOhk31KxEcdzes05 -nsKtjHEh8lprr988TlWvsoRlFIm5d8sqMb7Po23Pb0iUMkZv53GMoKaEGTcH8gNF -CSuGdXzfX2lXANtu2KZyIktQ1HWYVt+3GP9DQ1CuekR78HlR10M9p9OB0/DJT7na -xpeG0ILD5EJt/rDiZE4OJudANCa1CInXCGNjOCd1HjPqbqjdn5lPdE2BiYBL3ZqX -KVwvvoFBuYz/6n1gBp7N1z3TLqMVvKjmJuVvw9y4AyHqnxbxLFS1 ------END CERTIFICATE----- - -# Issuer: CN=CA Disig Root R2 O=Disig a.s. -# Subject: CN=CA Disig Root R2 O=Disig a.s. -# Label: "CA Disig Root R2" -# Serial: 10572350602393338211 -# MD5 Fingerprint: 26:01:fb:d8:27:a7:17:9a:45:54:38:1a:43:01:3b:03 -# SHA1 Fingerprint: b5:61:eb:ea:a4:de:e4:25:4b:69:1a:98:a5:57:47:c2:34:c7:d9:71 -# SHA256 Fingerprint: e2:3d:4a:03:6d:7b:70:e9:f5:95:b1:42:20:79:d2:b9:1e:df:bb:1f:b6:51:a0:63:3e:aa:8a:9d:c5:f8:07:03 ------BEGIN CERTIFICATE----- -MIIFaTCCA1GgAwIBAgIJAJK4iNuwisFjMA0GCSqGSIb3DQEBCwUAMFIxCzAJBgNV -BAYTAlNLMRMwEQYDVQQHEwpCcmF0aXNsYXZhMRMwEQYDVQQKEwpEaXNpZyBhLnMu -MRkwFwYDVQQDExBDQSBEaXNpZyBSb290IFIyMB4XDTEyMDcxOTA5MTUzMFoXDTQy -MDcxOTA5MTUzMFowUjELMAkGA1UEBhMCU0sxEzARBgNVBAcTCkJyYXRpc2xhdmEx -EzARBgNVBAoTCkRpc2lnIGEucy4xGTAXBgNVBAMTEENBIERpc2lnIFJvb3QgUjIw -ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCio8QACdaFXS1tFPbCw3Oe -NcJxVX6B+6tGUODBfEl45qt5WDza/3wcn9iXAng+a0EE6UG9vgMsRfYvZNSrXaNH -PWSb6WiaxswbP7q+sos0Ai6YVRn8jG+qX9pMzk0DIaPY0jSTVpbLTAwAFjxfGs3I -x2ymrdMxp7zo5eFm1tL7A7RBZckQrg4FY8aAamkw/dLukO8NJ9+flXP04SXabBbe -QTg06ov80egEFGEtQX6sx3dOy1FU+16SGBsEWmjGycT6txOgmLcRK7fWV8x8nhfR -yyX+hk4kLlYMeE2eARKmK6cBZW58Yh2EhN/qwGu1pSqVg8NTEQxzHQuyRpDRQjrO -QG6Vrf/GlK1ul4SOfW+eioANSW1z4nuSHsPzwfPrLgVv2RvPN3YEyLRa5Beny912 -H9AZdugsBbPWnDTYltxhh5EF5EQIM8HauQhl1K6yNg3ruji6DOWbnuuNZt2Zz9aJ -QfYEkoopKW1rOhzndX0CcQ7zwOe9yxndnWCywmZgtrEE7snmhrmaZkCo5xHtgUUD -i/ZnWejBBhG93c+AAk9lQHhcR1DIm+YfgXvkRKhbhZri3lrVx/k6RGZL5DJUfORs -nLMOPReisjQS1n6yqEm70XooQL6iFh/f5DcfEXP7kAplQ6INfPgGAVUzfbANuPT1 -rqVCV3w2EYx7XsQDnYx5nQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1Ud -DwEB/wQEAwIBBjAdBgNVHQ4EFgQUtZn4r7CU9eMg1gqtzk5WpC5uQu0wDQYJKoZI -hvcNAQELBQADggIBACYGXnDnZTPIgm7ZnBc6G3pmsgH2eDtpXi/q/075KMOYKmFM -tCQSin1tERT3nLXK5ryeJ45MGcipvXrA1zYObYVybqjGom32+nNjf7xueQgcnYqf -GopTpti72TVVsRHFqQOzVju5hJMiXn7B9hJSi+osZ7z+Nkz1uM/Rs0mSO9MpDpkb -lvdhuDvEK7Z4bLQjb/D907JedR+Zlais9trhxTF7+9FGs9K8Z7RiVLoJ92Owk6Ka -+elSLotgEqv89WBW7xBci8QaQtyDW2QOy7W81k/BfDxujRNt+3vrMNDcTa/F1bal -TFtxyegxvug4BkihGuLq0t4SOVga/4AOgnXmt8kHbA7v/zjxmHHEt38OFdAlab0i -nSvtBfZGR6ztwPDUO+Ls7pZbkBNOHlY667DvlruWIxG68kOGdGSVyCh13x01utI3 -gzhTODY7z2zp+WsO0PsE6E9312UBeIYMej4hYvF/Y3EMyZ9E26gnonW+boE+18Dr -G5gPcFw0sorMwIUY6256s/daoQe/qUKS82Ail+QUoQebTnbAjn39pCXHR+3/H3Os -zMOl6W8KjptlwlCFtaOgUxLMVYdh84GuEEZhvUQhuMI9dM9+JDX6HAcOmz0iyu8x -L4ysEr3vQCj8KWefshNPZiTEUxnpHikV7+ZtsH8tZ/3zbBt1RqPlShfppNcL ------END CERTIFICATE----- - -# Issuer: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV -# Subject: CN=ACCVRAIZ1 O=ACCV OU=PKIACCV -# Label: "ACCVRAIZ1" -# Serial: 6828503384748696800 -# MD5 Fingerprint: d0:a0:5a:ee:05:b6:09:94:21:a1:7d:f1:b2:29:82:02 -# SHA1 Fingerprint: 93:05:7a:88:15:c6:4f:ce:88:2f:fa:91:16:52:28:78:bc:53:64:17 -# SHA256 Fingerprint: 9a:6e:c0:12:e1:a7:da:9d:be:34:19:4d:47:8a:d7:c0:db:18:22:fb:07:1d:f1:29:81:49:6e:d1:04:38:41:13 ------BEGIN CERTIFICATE----- -MIIH0zCCBbugAwIBAgIIXsO3pkN/pOAwDQYJKoZIhvcNAQEFBQAwQjESMBAGA1UE -AwwJQUNDVlJBSVoxMRAwDgYDVQQLDAdQS0lBQ0NWMQ0wCwYDVQQKDARBQ0NWMQsw -CQYDVQQGEwJFUzAeFw0xMTA1MDUwOTM3MzdaFw0zMDEyMzEwOTM3MzdaMEIxEjAQ -BgNVBAMMCUFDQ1ZSQUlaMTEQMA4GA1UECwwHUEtJQUNDVjENMAsGA1UECgwEQUND -VjELMAkGA1UEBhMCRVMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCb -qau/YUqXry+XZpp0X9DZlv3P4uRm7x8fRzPCRKPfmt4ftVTdFXxpNRFvu8gMjmoY -HtiP2Ra8EEg2XPBjs5BaXCQ316PWywlxufEBcoSwfdtNgM3802/J+Nq2DoLSRYWo -G2ioPej0RGy9ocLLA76MPhMAhN9KSMDjIgro6TenGEyxCQ0jVn8ETdkXhBilyNpA -lHPrzg5XPAOBOp0KoVdDaaxXbXmQeOW1tDvYvEyNKKGno6e6Ak4l0Squ7a4DIrhr -IA8wKFSVf+DuzgpmndFALW4ir50awQUZ0m/A8p/4e7MCQvtQqR0tkw8jq8bBD5L/ -0KIV9VMJcRz/RROE5iZe+OCIHAr8Fraocwa48GOEAqDGWuzndN9wrqODJerWx5eH -k6fGioozl2A3ED6XPm4pFdahD9GILBKfb6qkxkLrQaLjlUPTAYVtjrs78yM2x/47 -4KElB0iryYl0/wiPgL/AlmXz7uxLaL2diMMxs0Dx6M/2OLuc5NF/1OVYm3z61PMO -m3WR5LpSLhl+0fXNWhn8ugb2+1KoS5kE3fj5tItQo05iifCHJPqDQsGH+tUtKSpa -cXpkatcnYGMN285J9Y0fkIkyF/hzQ7jSWpOGYdbhdQrqeWZ2iE9x6wQl1gpaepPl -uUsXQA+xtrn13k/c4LOsOxFwYIRKQ26ZIMApcQrAZQIDAQABo4ICyzCCAscwfQYI -KwYBBQUHAQEEcTBvMEwGCCsGAQUFBzAChkBodHRwOi8vd3d3LmFjY3YuZXMvZmls -ZWFkbWluL0FyY2hpdm9zL2NlcnRpZmljYWRvcy9yYWl6YWNjdjEuY3J0MB8GCCsG -AQUFBzABhhNodHRwOi8vb2NzcC5hY2N2LmVzMB0GA1UdDgQWBBTSh7Tj3zcnk1X2 -VuqB5TbMjB4/vTAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNKHtOPfNyeT -VfZW6oHlNsyMHj+9MIIBcwYDVR0gBIIBajCCAWYwggFiBgRVHSAAMIIBWDCCASIG -CCsGAQUFBwICMIIBFB6CARAAQQB1AHQAbwByAGkAZABhAGQAIABkAGUAIABDAGUA -cgB0AGkAZgBpAGMAYQBjAGkA8wBuACAAUgBhAO0AegAgAGQAZQAgAGwAYQAgAEEA -QwBDAFYAIAAoAEEAZwBlAG4AYwBpAGEAIABkAGUAIABUAGUAYwBuAG8AbABvAGcA -7QBhACAAeQAgAEMAZQByAHQAaQBmAGkAYwBhAGMAaQDzAG4AIABFAGwAZQBjAHQA -cgDzAG4AaQBjAGEALAAgAEMASQBGACAAUQA0ADYAMAAxADEANQA2AEUAKQAuACAA -QwBQAFMAIABlAG4AIABoAHQAdABwADoALwAvAHcAdwB3AC4AYQBjAGMAdgAuAGUA -czAwBggrBgEFBQcCARYkaHR0cDovL3d3dy5hY2N2LmVzL2xlZ2lzbGFjaW9uX2Mu -aHRtMFUGA1UdHwROMEwwSqBIoEaGRGh0dHA6Ly93d3cuYWNjdi5lcy9maWxlYWRt -aW4vQXJjaGl2b3MvY2VydGlmaWNhZG9zL3JhaXphY2N2MV9kZXIuY3JsMA4GA1Ud -DwEB/wQEAwIBBjAXBgNVHREEEDAOgQxhY2N2QGFjY3YuZXMwDQYJKoZIhvcNAQEF -BQADggIBAJcxAp/n/UNnSEQU5CmH7UwoZtCPNdpNYbdKl02125DgBS4OxnnQ8pdp -D70ER9m+27Up2pvZrqmZ1dM8MJP1jaGo/AaNRPTKFpV8M9xii6g3+CfYCS0b78gU -JyCpZET/LtZ1qmxNYEAZSUNUY9rizLpm5U9EelvZaoErQNV/+QEnWCzI7UiRfD+m -AM/EKXMRNt6GGT6d7hmKG9Ww7Y49nCrADdg9ZuM8Db3VlFzi4qc1GwQA9j9ajepD -vV+JHanBsMyZ4k0ACtrJJ1vnE5Bc5PUzolVt3OAJTS+xJlsndQAJxGJ3KQhfnlms -tn6tn1QwIgPBHnFk/vk4CpYY3QIUrCPLBhwepH2NDd4nQeit2hW3sCPdK6jT2iWH -7ehVRE2I9DZ+hJp4rPcOVkkO1jMl1oRQQmwgEh0q1b688nCBpHBgvgW1m54ERL5h -I6zppSSMEYCUWqKiuUnSwdzRp+0xESyeGabu4VXhwOrPDYTkF7eifKXeVSUG7szA -h1xA2syVP1XgNce4hL60Xc16gwFy7ofmXx2utYXGJt/mwZrpHgJHnyqobalbz+xF -d3+YJ5oyXSrjhO7FmGYvliAd3djDJ9ew+f7Zfc3Qn48LFFhRny+Lwzgt3uiP1o2H -pPVWQxaZLPSkVrQ0uGE3ycJYgBugl6H8WY3pEfbRD0tVNEYqi4Y7 ------END CERTIFICATE----- - -# Issuer: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA -# Subject: CN=TWCA Global Root CA O=TAIWAN-CA OU=Root CA -# Label: "TWCA Global Root CA" -# Serial: 3262 -# MD5 Fingerprint: f9:03:7e:cf:e6:9e:3c:73:7a:2a:90:07:69:ff:2b:96 -# SHA1 Fingerprint: 9c:bb:48:53:f6:a4:f6:d3:52:a4:e8:32:52:55:60:13:f5:ad:af:65 -# SHA256 Fingerprint: 59:76:90:07:f7:68:5d:0f:cd:50:87:2f:9f:95:d5:75:5a:5b:2b:45:7d:81:f3:69:2b:61:0a:98:67:2f:0e:1b ------BEGIN CERTIFICATE----- -MIIFQTCCAymgAwIBAgICDL4wDQYJKoZIhvcNAQELBQAwUTELMAkGA1UEBhMCVFcx -EjAQBgNVBAoTCVRBSVdBTi1DQTEQMA4GA1UECxMHUm9vdCBDQTEcMBoGA1UEAxMT -VFdDQSBHbG9iYWwgUm9vdCBDQTAeFw0xMjA2MjcwNjI4MzNaFw0zMDEyMzExNTU5 -NTlaMFExCzAJBgNVBAYTAlRXMRIwEAYDVQQKEwlUQUlXQU4tQ0ExEDAOBgNVBAsT -B1Jvb3QgQ0ExHDAaBgNVBAMTE1RXQ0EgR2xvYmFsIFJvb3QgQ0EwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCwBdvI64zEbooh745NnHEKH1Jw7W2CnJfF -10xORUnLQEK1EjRsGcJ0pDFfhQKX7EMzClPSnIyOt7h52yvVavKOZsTuKwEHktSz -0ALfUPZVr2YOy+BHYC8rMjk1Ujoog/h7FsYYuGLWRyWRzvAZEk2tY/XTP3VfKfCh -MBwqoJimFb3u/Rk28OKRQ4/6ytYQJ0lM793B8YVwm8rqqFpD/G2Gb3PpN0Wp8DbH -zIh1HrtsBv+baz4X7GGqcXzGHaL3SekVtTzWoWH1EfcFbx39Eb7QMAfCKbAJTibc -46KokWofwpFFiFzlmLhxpRUZyXx1EcxwdE8tmx2RRP1WKKD+u4ZqyPpcC1jcxkt2 -yKsi2XMPpfRaAok/T54igu6idFMqPVMnaR1sjjIsZAAmY2E2TqNGtz99sy2sbZCi -laLOz9qC5wc0GZbpuCGqKX6mOL6OKUohZnkfs8O1CWfe1tQHRvMq2uYiN2DLgbYP -oA/pyJV/v1WRBXrPPRXAb94JlAGD1zQbzECl8LibZ9WYkTunhHiVJqRaCPgrdLQA -BDzfuBSO6N+pjWxnkjMdwLfS7JLIvgm/LCkFbwJrnu+8vyq8W8BQj0FwcYeyTbcE -qYSjMq+u7msXi7Kx/mzhkIyIqJdIzshNy/MGz19qCkKxHh53L46g5pIOBvwFItIm -4TFRfTLcDwIDAQABoyMwITAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB -/zANBgkqhkiG9w0BAQsFAAOCAgEAXzSBdu+WHdXltdkCY4QWwa6gcFGn90xHNcgL -1yg9iXHZqjNB6hQbbCEAwGxCGX6faVsgQt+i0trEfJdLjbDorMjupWkEmQqSpqsn -LhpNgb+E1HAerUf+/UqdM+DyucRFCCEK2mlpc3INvjT+lIutwx4116KD7+U4x6WF -H6vPNOw/KP4M8VeGTslV9xzU2KV9Bnpv1d8Q34FOIWWxtuEXeZVFBs5fzNxGiWNo -RI2T9GRwoD2dKAXDOXC4Ynsg/eTb6QihuJ49CcdP+yz4k3ZB3lLg4VfSnQO8d57+ -nile98FRYB/e2guyLXW3Q0iT5/Z5xoRdgFlglPx4mI88k1HtQJAH32RjJMtOcQWh -15QaiDLxInQirqWm2BJpTGCjAu4r7NRjkgtevi92a6O2JryPA9gK8kxkRr05YuWW -6zRjESjMlfGt7+/cgFhI6Uu46mWs6fyAtbXIRfmswZ/ZuepiiI7E8UuDEq3mi4TW -nsLrgxifarsbJGAzcMzs9zLzXNl5fe+epP7JI8Mk7hWSsT2RTyaGvWZzJBPqpK5j -wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz -aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy -KwbQBM0= ------END CERTIFICATE----- - -# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Label: "TeliaSonera Root CA v1" -# Serial: 199041966741090107964904287217786801558 -# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c -# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 -# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 ------BEGIN CERTIFICATE----- -MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw -NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv -b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD -VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F -VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 -7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X -Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ -/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs -81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm -dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe -Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu -sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 -pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs -slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ -arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD -VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG -9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl -dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx -0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj -TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed -Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 -Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI -OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 -vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW -t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn -HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx -SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= ------END CERTIFICATE----- - -# Issuer: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi -# Subject: CN=E-Tugra Certification Authority O=E-Tu\u011fra EBG Bili\u015fim Teknolojileri ve Hizmetleri A.\u015e. OU=E-Tugra Sertifikasyon Merkezi -# Label: "E-Tugra Certification Authority" -# Serial: 7667447206703254355 -# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 -# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 -# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c ------BEGIN CERTIFICATE----- -MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV -BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC -aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV -BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 -Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz -MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ -BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp -em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN -ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY -B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH -D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF -Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo -q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D -k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH -fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut -dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM -ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 -zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn -rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX -U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 -Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 -XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF -Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR -HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY -GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c -77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 -+GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK -vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 -FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl -yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P -AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD -y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d -NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== ------END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 2" -# Serial: 1 -# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a -# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 -# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 ------BEGIN CERTIFICATE----- -MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx -KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd -BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl -YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 -OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy -aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 -ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd -AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC -FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi -1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq -jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ -wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ -WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy -NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC -uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw -IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 -g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN -9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP -BSeOE6Fuwg== ------END CERTIFICATE----- - -# Issuer: CN=Atos TrustedRoot 2011 O=Atos -# Subject: CN=Atos TrustedRoot 2011 O=Atos -# Label: "Atos TrustedRoot 2011" -# Serial: 6643877497813316402 -# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 -# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 -# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE -AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG -EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM -FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC -REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp -Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM -VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ -SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ -4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L -cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi -eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV -HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG -A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 -DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j -vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP -DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc -maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D -lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv -KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 1 G3" -# Serial: 687049649626669250736271037606554624078720034195 -# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab -# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 -# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 -MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV -wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe -rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 -68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh -4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp -UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o -abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc -3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G -KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt -hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO -Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt -zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD -ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC -MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 -cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN -qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 -YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv -b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 -8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k -NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj -ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp -q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt -nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2 G3" -# Serial: 390156079458959257446133169266079962026824725800 -# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 -# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 -# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 -MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf -qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW -n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym -c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ -O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 -o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j -IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq -IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz -8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh -vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l -7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG -cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD -ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 -AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC -roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga -W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n -lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE -+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV -csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd -dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg -KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM -HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 -WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3 G3" -# Serial: 268090761170461462463995952157327242137089239581 -# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 -# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d -# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 -MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR -/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu -FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR -U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c -ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR -FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k -A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw -eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl -sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp -VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q -A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ -ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD -ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px -KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI -FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv -oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg -u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP -0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf -3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl -8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ -DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN -PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ -ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root G2" -# Serial: 15385348160840213938643033620894905419 -# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d -# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f -# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 ------BEGIN CERTIFICATE----- -MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA -n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc -biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp -EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA -bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu -YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB -AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW -BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI -QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I -0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni -lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 -B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv -ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo -IhNzbM8m9Yop5w== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root G3" -# Serial: 15459312981008553731928384953135426796 -# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb -# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 -# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 ------BEGIN CERTIFICATE----- -MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw -CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu -ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg -RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV -UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu -Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq -hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf -Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q -RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ -BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD -AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY -JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv -6pZjamVFkpUBtA== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G2" -# Serial: 4293743540046975378534879503202253541 -# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 -# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 -# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f ------BEGIN CERTIFICATE----- -MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH -MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI -2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx -1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ -q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz -tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ -vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP -BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV -5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY -1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 -NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG -Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 -8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe -pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl -MrY= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G3" -# Serial: 7089244469030293291760083333884364146 -# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca -# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e -# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 ------BEGIN CERTIFICATE----- -MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw -CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu -ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe -Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw -EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x -IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF -K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG -fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO -Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd -BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx -AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ -oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 -sycX ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Trusted Root G4" -# Serial: 7451500558977370777930084869016614236 -# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 -# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 -# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 ------BEGIN CERTIFICATE----- -MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg -RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV -UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu -Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y -ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If -xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV -ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO -DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ -jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ -CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi -EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM -fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY -uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK -chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t -9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD -ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 -SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd -+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc -fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa -sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N -cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N -0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie -4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI -r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 -/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm -gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ ------END CERTIFICATE----- - -# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited -# Label: "COMODO RSA Certification Authority" -# Serial: 101909084537582093308941363524873193117 -# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 -# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 -# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 ------BEGIN CERTIFICATE----- -MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB -hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV -BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 -MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT -EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR -Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR -6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X -pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC -9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV -/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf -Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z -+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w -qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah -SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC -u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf -Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq -crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E -FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB -/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl -wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM -4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV -2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna -FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ -CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK -boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke -jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL -S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb -QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl -0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB -NVOFBkpdn627G190 ------END CERTIFICATE----- - -# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Label: "USERTrust RSA Certification Authority" -# Serial: 2645093764781058787591871645665788717 -# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 -# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e -# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 ------BEGIN CERTIFICATE----- -MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB -iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl -cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV -BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw -MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV -BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU -aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B -3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY -tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ -Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 -VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT -79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 -c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT -Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l -c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee -UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE -Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd -BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF -Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO -VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 -ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs -8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR -iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze -Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ -XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ -qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB -VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB -L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG -jjxDah2nGN59PRbxYvnKkKj9 ------END CERTIFICATE----- - -# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network -# Label: "USERTrust ECC Certification Authority" -# Serial: 123013823720199481456569720443997572134 -# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 -# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 -# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a ------BEGIN CERTIFICATE----- -MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL -MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl -eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT -JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx -MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT -Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg -VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo -I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng -o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G -A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB -zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW -RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 -# Label: "GlobalSign ECC Root CA - R5" -# Serial: 32785792099990507226680698011560947931244 -# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 -# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa -# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 ------BEGIN CERTIFICATE----- -MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk -MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH -bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX -DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD -QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu -MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc -8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke -hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI -KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg -515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO -xwy8p2Fp8fc74SrL+SvzZpA3 ------END CERTIFICATE----- - -# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust -# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust -# Label: "IdenTrust Commercial Root CA 1" -# Serial: 13298821034946342390520003877796839426 -# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 -# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 -# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK -MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu -VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw -MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw -JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT -3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU -+ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp -S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 -bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi -T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL -vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK -Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK -dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT -c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv -l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N -iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB -/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD -ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH -6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt -LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 -nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 -+wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK -W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT -AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq -l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG -4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ -mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A -7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H ------END CERTIFICATE----- - -# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust -# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust -# Label: "IdenTrust Public Sector Root CA 1" -# Serial: 13298821034946342390521976156843933698 -# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba -# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd -# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f ------BEGIN CERTIFICATE----- -MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN -MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu -VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN -MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 -MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 -ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy -RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS -bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF -/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R -3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw -EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy -9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V -GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ -2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV -WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD -W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ -BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN -AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj -t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV -DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 -TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G -lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW -mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df -WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 -+bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ -tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA -GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv -8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - G2" -# Serial: 1246989352 -# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 -# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 -# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 ------BEGIN CERTIFICATE----- -MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC -VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 -cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs -IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz -dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy -NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu -dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt -dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 -aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj -YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK -AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T -RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN -cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW -wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 -U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 -jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP -BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN -BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ -jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ -Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v -1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R -nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH -VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - EC1" -# Serial: 51543124481930649114116133369 -# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc -# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 -# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 ------BEGIN CERTIFICATE----- -MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG -A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 -d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu -dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq -RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy -MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD -VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 -L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g -Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD -ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi -A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt -ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH -Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O -BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC -R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX -hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G ------END CERTIFICATE----- - -# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority -# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority -# Label: "CFCA EV ROOT" -# Serial: 407555286 -# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 -# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 -# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd ------BEGIN CERTIFICATE----- -MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD -TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y -aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx -MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j -aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP -T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 -sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL -TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 -/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp -7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz -EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt -hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP -a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot -aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg -TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV -PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv -cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL -tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd -BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB -ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT -ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL -jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS -ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy -P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 -xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d -Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN -5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe -/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z -AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ -5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su ------END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GB CA O=WISeKey OU=OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GB CA" -# Serial: 157768595616588414422159278966750757568 -# MD5 Fingerprint: a4:eb:b9:61:28:2e:b7:2f:98:b0:35:26:90:99:51:1d -# SHA1 Fingerprint: 0f:f9:40:76:18:d3:d7:6a:4b:98:f0:a8:35:9e:0c:fd:27:ac:cc:ed -# SHA256 Fingerprint: 6b:9c:08:e8:6e:b0:f7:67:cf:ad:65:cd:98:b6:21:49:e5:49:4a:67:f5:84:5e:7b:d1:ed:01:9f:27:b8:6b:d6 ------BEGIN CERTIFICATE----- -MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt -MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg -Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i -YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x -CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG -b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh -bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 -HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx -WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX -1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk -u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P -99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r -M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB -BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh -cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 -gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO -ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf -aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic -Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= ------END CERTIFICATE----- - -# Issuer: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. -# Subject: CN=SZAFIR ROOT CA2 O=Krajowa Izba Rozliczeniowa S.A. -# Label: "SZAFIR ROOT CA2" -# Serial: 357043034767186914217277344587386743377558296292 -# MD5 Fingerprint: 11:64:c1:89:b0:24:b1:8c:b1:07:7e:89:9e:51:9e:99 -# SHA1 Fingerprint: e2:52:fa:95:3f:ed:db:24:60:bd:6e:28:f3:9c:cc:cf:5e:b3:3f:de -# SHA256 Fingerprint: a1:33:9d:33:28:1a:0b:56:e5:57:d3:d3:2b:1c:e7:f9:36:7e:b0:94:bd:5f:a7:2a:7e:50:04:c8:de:d7:ca:fe ------BEGIN CERTIFICATE----- -MIIDcjCCAlqgAwIBAgIUPopdB+xV0jLVt+O2XwHrLdzk1uQwDQYJKoZIhvcNAQEL -BQAwUTELMAkGA1UEBhMCUEwxKDAmBgNVBAoMH0tyYWpvd2EgSXpiYSBSb3psaWN6 -ZW5pb3dhIFMuQS4xGDAWBgNVBAMMD1NaQUZJUiBST09UIENBMjAeFw0xNTEwMTkw -NzQzMzBaFw0zNTEwMTkwNzQzMzBaMFExCzAJBgNVBAYTAlBMMSgwJgYDVQQKDB9L -cmFqb3dhIEl6YmEgUm96bGljemVuaW93YSBTLkEuMRgwFgYDVQQDDA9TWkFGSVIg -Uk9PVCBDQTIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC3vD5QqEvN -QLXOYeeWyrSh2gwisPq1e3YAd4wLz32ohswmUeQgPYUM1ljj5/QqGJ3a0a4m7utT -3PSQ1hNKDJA8w/Ta0o4NkjrcsbH/ON7Dui1fgLkCvUqdGw+0w8LBZwPd3BucPbOw -3gAeqDRHu5rr/gsUvTaE2g0gv/pby6kWIK05YO4vdbbnl5z5Pv1+TW9NL++IDWr6 -3fE9biCloBK0TXC5ztdyO4mTp4CEHCdJckm1/zuVnsHMyAHs6A6KCpbns6aH5db5 -BSsNl0BwPLqsdVqc1U2dAgrSS5tmS0YHF2Wtn2yIANwiieDhZNRnvDF5YTy7ykHN -XGoAyDw4jlivAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD -AgEGMB0GA1UdDgQWBBQuFqlKGLXLzPVvUPMjX/hd56zwyDANBgkqhkiG9w0BAQsF -AAOCAQEAtXP4A9xZWx126aMqe5Aosk3AM0+qmrHUuOQn/6mWmc5G4G18TKI4pAZw -8PRBEew/R40/cof5O/2kbytTAOD/OblqBw7rHRz2onKQy4I9EYKL0rufKq8h5mOG -nXkZ7/e7DDWQw4rtTw/1zBLZpD67oPwglV9PJi8RI4NOdQcPv5vRtB3pEAT+ymCP -oky4rc/hkA/NrgrHXXu3UNLUYfrVFdvXn4dRVOul4+vJhaAlIDf7js4MNIThPIGy -d05DpYhfhmehPea0XGG2Ptv+tyjFogeutcrKjSoS75ftwjCkySp6+/NNIxuZMzSg -LvWpCz/UXeHPhJ/iGcJfitYgHuNztw== ------END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Network CA 2 O=Unizeto Technologies S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Network CA 2" -# Serial: 44979900017204383099463764357512596969 -# MD5 Fingerprint: 6d:46:9e:d9:25:6d:08:23:5b:5e:74:7d:1e:27:db:f2 -# SHA1 Fingerprint: d3:dd:48:3e:2b:bf:4c:05:e8:af:10:f5:fa:76:26:cf:d3:dc:30:92 -# SHA256 Fingerprint: b6:76:f2:ed:da:e8:77:5c:d3:6c:b0:f6:3c:d1:d4:60:39:61:f4:9e:62:65:ba:01:3a:2f:03:07:b6:d0:b8:04 ------BEGIN CERTIFICATE----- -MIIF0jCCA7qgAwIBAgIQIdbQSk8lD8kyN/yqXhKN6TANBgkqhkiG9w0BAQ0FADCB -gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu -QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG -A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMCIYDzIwMTExMDA2MDgz -OTU2WhgPMjA0NjEwMDYwODM5NTZaMIGAMQswCQYDVQQGEwJQTDEiMCAGA1UEChMZ -VW5pemV0byBUZWNobm9sb2dpZXMgUy5BLjEnMCUGA1UECxMeQ2VydHVtIENlcnRp -ZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtDZXJ0dW0gVHJ1c3RlZCBOZXR3 -b3JrIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9+Xj45tWA -DGSdhhuWZGc/IjoedQF97/tcZ4zJzFxrqZHmuULlIEub2pt7uZld2ZuAS9eEQCsn -0+i6MLs+CRqnSZXvK0AkwpfHp+6bJe+oCgCXhVqqndwpyeI1B+twTUrWwbNWuKFB -OJvR+zF/j+Bf4bE/D44WSWDXBo0Y+aomEKsq09DRZ40bRr5HMNUuctHFY9rnY3lE -fktjJImGLjQ/KUxSiyqnwOKRKIm5wFv5HdnnJ63/mgKXwcZQkpsCLL2puTRZCr+E -Sv/f/rOf69me4Jgj7KZrdxYq28ytOxykh9xGc14ZYmhFV+SQgkK7QtbwYeDBoz1m -o130GO6IyY0XRSmZMnUCMe4pJshrAua1YkV/NxVaI2iJ1D7eTiew8EAMvE0Xy02i -sx7QBlrd9pPPV3WZ9fqGGmd4s7+W/jTcvedSVuWz5XV710GRBdxdaeOVDUO5/IOW -OZV7bIBaTxNyxtd9KXpEulKkKtVBRgkg/iKgtlswjbyJDNXXcPiHUv3a76xRLgez -Tv7QCdpw75j6VuZt27VXS9zlLCUVyJ4ueE742pyehizKV/Ma5ciSixqClnrDvFAS -adgOWkaLOusm+iPJtrCBvkIApPjW/jAux9JG9uWOdf3yzLnQh1vMBhBgu4M1t15n -3kfsmUjxpKEV/q2MYo45VU85FrmxY53/twIDAQABo0IwQDAPBgNVHRMBAf8EBTAD -AQH/MB0GA1UdDgQWBBS2oVQ5AsOgP46KvPrU+Bym0ToO/TAOBgNVHQ8BAf8EBAMC -AQYwDQYJKoZIhvcNAQENBQADggIBAHGlDs7k6b8/ONWJWsQCYftMxRQXLYtPU2sQ -F/xlhMcQSZDe28cmk4gmb3DWAl45oPePq5a1pRNcgRRtDoGCERuKTsZPpd1iHkTf -CVn0W3cLN+mLIMb4Ck4uWBzrM9DPhmDJ2vuAL55MYIR4PSFk1vtBHxgP58l1cb29 -XN40hz5BsA72udY/CROWFC/emh1auVbONTqwX3BNXuMp8SMoclm2q8KMZiYcdywm -djWLKKdpoPk79SPdhRB0yZADVpHnr7pH1BKXESLjokmUbOe3lEu6LaTaM4tMpkT/ -WjzGHWTYtTHkpjx6qFcL2+1hGsvxznN3Y6SHb0xRONbkX8eftoEq5IVIeVheO/jb -AoJnwTnbw3RLPTYe+SmTiGhbqEQZIfCn6IENLOiTNrQ3ssqwGyZ6miUfmpqAnksq -P/ujmv5zMnHCnsZy4YpoJ/HkD7TETKVhk/iXEAcqMCWpuchxuO9ozC1+9eB+D4Ko -b7a6bINDd82Kkhehnlt4Fj1F4jNy3eFmypnTycUm/Q1oBEauttmbjL4ZvrHG8hnj -XALKLNhvSgfZyTXaQHXyxKcZb55CEJh15pWLYLztxRLXis7VmFxWlgPF7ncGNf/P -5O4/E2Hu29othfDNrp2yGAlFw5Khchf8R7agCyzxxN5DaAhqXzvwdmP7zAYspsbi -DrW5viSP ------END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions RootCA 2015" -# Serial: 0 -# MD5 Fingerprint: ca:ff:e2:db:03:d9:cb:4b:e9:0f:ad:84:fd:7b:18:ce -# SHA1 Fingerprint: 01:0c:06:95:a6:98:19:14:ff:bf:5f:c6:b0:b6:95:ea:29:e9:12:a6 -# SHA256 Fingerprint: a0:40:92:9a:02:ce:53:b4:ac:f4:f2:ff:c6:98:1c:e4:49:6f:75:5e:6d:45:fe:0b:2a:69:2b:cd:52:52:3f:36 ------BEGIN CERTIFICATE----- -MIIGCzCCA/OgAwIBAgIBADANBgkqhkiG9w0BAQsFADCBpjELMAkGA1UEBhMCR1Ix -DzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5k -IFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxQDA+BgNVBAMT -N0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgUm9v -dENBIDIwMTUwHhcNMTUwNzA3MTAxMTIxWhcNNDAwNjMwMTAxMTIxWjCBpjELMAkG -A1UEBhMCR1IxDzANBgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNh -ZGVtaWMgYW5kIFJlc2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkx -QDA+BgNVBAMTN0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 -dGlvbnMgUm9vdENBIDIwMTUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC -AQDC+Kk/G4n8PDwEXT2QNrCROnk8ZlrvbTkBSRq0t89/TSNTt5AA4xMqKKYx8ZEA -4yjsriFBzh/a/X0SWwGDD7mwX5nh8hKDgE0GPt+sr+ehiGsxr/CL0BgzuNtFajT0 -AoAkKAoCFZVedioNmToUW/bLy1O8E00BiDeUJRtCvCLYjqOWXjrZMts+6PAQZe10 -4S+nfK8nNLspfZu2zwnI5dMK/IhlZXQK3HMcXM1AsRzUtoSMTFDPaI6oWa7CJ06C -ojXdFPQf/7J31Ycvqm59JCfnxssm5uX+Zwdj2EUN3TpZZTlYepKZcj2chF6IIbjV -9Cz82XBST3i4vTwri5WY9bPRaM8gFH5MXF/ni+X1NYEZN9cRCLdmvtNKzoNXADrD -gfgXy5I2XdGj2HUb4Ysn6npIQf1FGQatJ5lOwXBH3bWfgVMS5bGMSF0xQxfjjMZ6 -Y5ZLKTBOhE5iGV48zpeQpX8B653g+IuJ3SWYPZK2fu/Z8VFRfS0myGlZYeCsargq -NhEEelC9MoS+L9xy1dcdFkfkR2YgP/SWxa+OAXqlD3pk9Q0Yh9muiNX6hME6wGko -LfINaFGq46V3xqSQDqE3izEjR8EJCOtu93ib14L8hCCZSRm2Ekax+0VVFqmjZayc -Bw/qa9wfLgZy7IaIEuQt218FL+TwA9MmM+eAws1CoRc0CwIDAQABo0IwQDAPBgNV -HRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUcRVnyMjJvXVd -ctA4GGqd83EkVAswDQYJKoZIhvcNAQELBQADggIBAHW7bVRLqhBYRjTyYtcWNl0I -XtVsyIe9tC5G8jH4fOpCtZMWVdyhDBKg2mF+D1hYc2Ryx+hFjtyp8iY/xnmMsVMI -M4GwVhO+5lFc2JsKT0ucVlMC6U/2DWDqTUJV6HwbISHTGzrMd/K4kPFox/la/vot -9L/J9UUbzjgQKjeKeaO04wlshYaT/4mWJ3iBj2fjRnRUjtkNaeJK9E10A/+yd+2V -Z5fkscWrv2oj6NSU4kQoYsRL4vDY4ilrGnB+JGGTe08DMiUNRSQrlrRGar9KC/ea -j8GsGsVn82800vpzY4zvFrCopEYq+OsS7HK07/grfoxSwIuEVPkvPuNVqNxmsdnh -X9izjFk0WaSrT2y7HxjbdavYy5LNlDhhDgcGH0tGEPEVvo2FXDtKK4F5D7Rpn0lQ -l033DlZdwJVqwjbDG2jJ9SrcR5q+ss7FJej6A7na+RZukYT1HCjI/CbM1xyQVqdf -bzoEvM14iQuODy+jqk+iGxI9FghAD/FGTNeqewjBCvVtJ94Cj8rDtSvK6evIIVM4 -pcw72Hc3MKJP2W/R8kCtQXoXxdZKNYm3QdV8hn9VTYNKpXMgwDqvkPGaJI7ZjnHK -e7iG2rKPmT4dEw0SEe7Uq/DpFXYC5ODfqiAeW2GFZECpkJcNrVPSWh2HagCXZWK0 -vm9qp/UsQu0yrbYhnr68 ------END CERTIFICATE----- - -# Issuer: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Subject: CN=Hellenic Academic and Research Institutions ECC RootCA 2015 O=Hellenic Academic and Research Institutions Cert. Authority -# Label: "Hellenic Academic and Research Institutions ECC RootCA 2015" -# Serial: 0 -# MD5 Fingerprint: 81:e5:b4:17:eb:c2:f5:e1:4b:0d:41:7b:49:92:fe:ef -# SHA1 Fingerprint: 9f:f1:71:8d:92:d5:9a:f3:7d:74:97:b4:bc:6f:84:68:0b:ba:b6:66 -# SHA256 Fingerprint: 44:b5:45:aa:8a:25:e6:5a:73:ca:15:dc:27:fc:36:d2:4c:1c:b9:95:3a:06:65:39:b1:15:82:dc:48:7b:48:33 ------BEGIN CERTIFICATE----- -MIICwzCCAkqgAwIBAgIBADAKBggqhkjOPQQDAjCBqjELMAkGA1UEBhMCR1IxDzAN -BgNVBAcTBkF0aGVuczFEMEIGA1UEChM7SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl -c2VhcmNoIEluc3RpdHV0aW9ucyBDZXJ0LiBBdXRob3JpdHkxRDBCBgNVBAMTO0hl -bGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgRUNDIFJv -b3RDQSAyMDE1MB4XDTE1MDcwNzEwMzcxMloXDTQwMDYzMDEwMzcxMlowgaoxCzAJ -BgNVBAYTAkdSMQ8wDQYDVQQHEwZBdGhlbnMxRDBCBgNVBAoTO0hlbGxlbmljIEFj -YWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1dGlvbnMgQ2VydC4gQXV0aG9yaXR5 -MUQwQgYDVQQDEztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0 -dXRpb25zIEVDQyBSb290Q0EgMjAxNTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJKg -QehLgoRc4vgxEZmGZE4JJS+dQS8KrjVPdJWyUWRrjWvmP3CV8AVER6ZyOFB2lQJa -jq4onvktTpnvLEhvTCUp6NFxW98dwXU3tNf6e3pCnGoKVlp8aQuqgAkkbH7BRqNC -MEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFLQi -C4KZJAEOnLvkDv2/+5cgk5kqMAoGCCqGSM49BAMCA2cAMGQCMGfOFmI4oqxiRaep -lSTAGiecMjvAwNW6qef4BENThe5SId6d9SWDPp5YSy/XZxMOIQIwBeF1Ad5o7Sof -TUwJCA3sS61kFyjndc5FZXIhF8siQQ6ME5g4mlRtm8rifOoCWCKR ------END CERTIFICATE----- - -# Issuer: CN=ISRG Root X1 O=Internet Security Research Group -# Subject: CN=ISRG Root X1 O=Internet Security Research Group -# Label: "ISRG Root X1" -# Serial: 172886928669790476064670243504169061120 -# MD5 Fingerprint: 0c:d2:f9:e0:da:17:73:e9:ed:86:4d:a5:e3:70:e7:4e -# SHA1 Fingerprint: ca:bd:2a:79:a1:07:6a:31:f2:1d:25:36:35:cb:03:9d:43:29:a5:e8 -# SHA256 Fingerprint: 96:bc:ec:06:26:49:76:f3:74:60:77:9a:cf:28:c5:a7:cf:e8:a3:c0:aa:e1:1a:8f:fc:ee:05:c0:bd:df:08:c6 ------BEGIN CERTIFICATE----- -MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw -TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh -cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 -WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu -ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc -h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ -0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U -A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW -T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH -B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC -B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv -KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn -OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn -jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw -qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI -rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV -HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq -hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL -ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ -3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK -NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 -ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur -TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC -jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc -oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq -4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA -mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d -emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= ------END CERTIFICATE----- - -# Issuer: O=FNMT-RCM OU=AC RAIZ FNMT-RCM -# Subject: O=FNMT-RCM OU=AC RAIZ FNMT-RCM -# Label: "AC RAIZ FNMT-RCM" -# Serial: 485876308206448804701554682760554759 -# MD5 Fingerprint: e2:09:04:b4:d3:bd:d1:a0:14:fd:1a:d2:47:c4:57:1d -# SHA1 Fingerprint: ec:50:35:07:b2:15:c4:95:62:19:e2:a8:9a:5b:42:99:2c:4c:2c:20 -# SHA256 Fingerprint: eb:c5:57:0c:29:01:8c:4d:67:b1:aa:12:7b:af:12:f7:03:b4:61:1e:bc:17:b7:da:b5:57:38:94:17:9b:93:fa ------BEGIN CERTIFICATE----- -MIIFgzCCA2ugAwIBAgIPXZONMGc2yAYdGsdUhGkHMA0GCSqGSIb3DQEBCwUAMDsx -CzAJBgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJ -WiBGTk1ULVJDTTAeFw0wODEwMjkxNTU5NTZaFw0zMDAxMDEwMDAwMDBaMDsxCzAJ -BgNVBAYTAkVTMREwDwYDVQQKDAhGTk1ULVJDTTEZMBcGA1UECwwQQUMgUkFJWiBG -Tk1ULVJDTTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALpxgHpMhm5/ -yBNtwMZ9HACXjywMI7sQmkCpGreHiPibVmr75nuOi5KOpyVdWRHbNi63URcfqQgf -BBckWKo3Shjf5TnUV/3XwSyRAZHiItQDwFj8d0fsjz50Q7qsNI1NOHZnjrDIbzAz -WHFctPVrbtQBULgTfmxKo0nRIBnuvMApGGWn3v7v3QqQIecaZ5JCEJhfTzC8PhxF -tBDXaEAUwED653cXeuYLj2VbPNmaUtu1vZ5Gzz3rkQUCwJaydkxNEJY7kvqcfw+Z -374jNUUeAlz+taibmSXaXvMiwzn15Cou08YfxGyqxRxqAQVKL9LFwag0Jl1mpdIC -IfkYtwb1TplvqKtMUejPUBjFd8g5CSxJkjKZqLsXF3mwWsXmo8RZZUc1g16p6DUL -mbvkzSDGm0oGObVo/CK67lWMK07q87Hj/LaZmtVC+nFNCM+HHmpxffnTtOmlcYF7 -wk5HlqX2doWjKI/pgG6BU6VtX7hI+cL5NqYuSf+4lsKMB7ObiFj86xsc3i1w4peS -MKGJ47xVqCfWS+2QrYv6YyVZLag13cqXM7zlzced0ezvXg5KkAYmY6252TUtB7p2 -ZSysV4999AeU14ECll2jB0nVetBX+RvnU0Z1qrB5QstocQjpYL05ac70r8NWQMet -UqIJ5G+GR4of6ygnXYMgrwTJbFaai0b1AgMBAAGjgYMwgYAwDwYDVR0TAQH/BAUw -AwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFPd9xf3E6Jobd2Sn9R2gzL+H -YJptMD4GA1UdIAQ3MDUwMwYEVR0gADArMCkGCCsGAQUFBwIBFh1odHRwOi8vd3d3 -LmNlcnQuZm5tdC5lcy9kcGNzLzANBgkqhkiG9w0BAQsFAAOCAgEAB5BK3/MjTvDD -nFFlm5wioooMhfNzKWtN/gHiqQxjAb8EZ6WdmF/9ARP67Jpi6Yb+tmLSbkyU+8B1 -RXxlDPiyN8+sD8+Nb/kZ94/sHvJwnvDKuO+3/3Y3dlv2bojzr2IyIpMNOmqOFGYM -LVN0V2Ue1bLdI4E7pWYjJ2cJj+F3qkPNZVEI7VFY/uY5+ctHhKQV8Xa7pO6kO8Rf -77IzlhEYt8llvhjho6Tc+hj507wTmzl6NLrTQfv6MooqtyuGC2mDOL7Nii4LcK2N -JpLuHvUBKwrZ1pebbuCoGRw6IYsMHkCtA+fdZn71uSANA+iW+YJF1DngoABd15jm -fZ5nc8OaKveri6E6FO80vFIOiZiaBECEHX5FaZNXzuvO+FB8TxxuBEOb+dY7Ixjp -6o7RTUaN8Tvkasq6+yO3m/qZASlaWFot4/nUbQ4mrcFuNLwy+AwF+mWj2zs3gyLp -1txyM/1d8iC9djwj2ij3+RvrWWTV3F9yfiD8zYm1kGdNYno/Tq0dwzn+evQoFt9B -9kiABdcPUXmsEKvU7ANm5mqwujGSQkBqvjrTcuFqN1W8rB2Vt2lh8kORdOag0wok -RqEIr9baRRmW1FMdW4R58MD3R++Lj8UGrp1MYp3/RgT408m2ECVAdf4WqslKYIYv -uu8wd+RU4riEmViAqhOLUTpPSPaLtrM= ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 1 O=Amazon -# Subject: CN=Amazon Root CA 1 O=Amazon -# Label: "Amazon Root CA 1" -# Serial: 143266978916655856878034712317230054538369994 -# MD5 Fingerprint: 43:c6:bf:ae:ec:fe:ad:2f:18:c6:88:68:30:fc:c8:e6 -# SHA1 Fingerprint: 8d:a7:f9:65:ec:5e:fc:37:91:0f:1c:6e:59:fd:c1:cc:6a:6e:de:16 -# SHA256 Fingerprint: 8e:cd:e6:88:4f:3d:87:b1:12:5b:a3:1a:c3:fc:b1:3d:70:16:de:7f:57:cc:90:4f:e1:cb:97:c6:ae:98:19:6e ------BEGIN CERTIFICATE----- -MIIDQTCCAimgAwIBAgITBmyfz5m/jAo54vB4ikPmljZbyjANBgkqhkiG9w0BAQsF -ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 -b24gUm9vdCBDQSAxMB4XDTE1MDUyNjAwMDAwMFoXDTM4MDExNzAwMDAwMFowOTEL -MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv -b3QgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALJ4gHHKeNXj -ca9HgFB0fW7Y14h29Jlo91ghYPl0hAEvrAIthtOgQ3pOsqTQNroBvo3bSMgHFzZM -9O6II8c+6zf1tRn4SWiw3te5djgdYZ6k/oI2peVKVuRF4fn9tBb6dNqcmzU5L/qw -IFAGbHrQgLKm+a/sRxmPUDgH3KKHOVj4utWp+UhnMJbulHheb4mjUcAwhmahRWa6 -VOujw5H5SNz/0egwLX0tdHA114gk957EWW67c4cX8jJGKLhD+rcdqsq08p8kDi1L -93FcXmn/6pUCyziKrlA4b9v7LWIbxcceVOF34GfID5yHI9Y/QCB/IIDEgEw+OyQm -jgSubJrIqg0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AYYwHQYDVR0OBBYEFIQYzIU07LwMlJQuCFmcx7IQTgoIMA0GCSqGSIb3DQEBCwUA -A4IBAQCY8jdaQZChGsV2USggNiMOruYou6r4lK5IpDB/G/wkjUu0yKGX9rbxenDI -U5PMCCjjmCXPI6T53iHTfIUJrU6adTrCC2qJeHZERxhlbI1Bjjt/msv0tadQ1wUs -N+gDS63pYaACbvXy8MWy7Vu33PqUXHeeE6V/Uq2V8viTO96LXFvKWlJbYK8U90vv -o/ufQJVtMVT8QtPHRh8jrdkPSHCa2XV4cdFyQzR1bldZwgJcJmApzyMZFo6IQ6XU -5MsI+yMRQ+hDKXJioaldXgjUkK642M4UwtBV8ob2xJNDd2ZhwLnoQdeXeGADbkpy -rqXRfboQnoZsG4q5WTP468SQvvG5 ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 2 O=Amazon -# Subject: CN=Amazon Root CA 2 O=Amazon -# Label: "Amazon Root CA 2" -# Serial: 143266982885963551818349160658925006970653239 -# MD5 Fingerprint: c8:e5:8d:ce:a8:42:e2:7a:c0:2a:5c:7c:9e:26:bf:66 -# SHA1 Fingerprint: 5a:8c:ef:45:d7:a6:98:59:76:7a:8c:8b:44:96:b5:78:cf:47:4b:1a -# SHA256 Fingerprint: 1b:a5:b2:aa:8c:65:40:1a:82:96:01:18:f8:0b:ec:4f:62:30:4d:83:ce:c4:71:3a:19:c3:9c:01:1e:a4:6d:b4 ------BEGIN CERTIFICATE----- -MIIFQTCCAymgAwIBAgITBmyf0pY1hp8KD+WGePhbJruKNzANBgkqhkiG9w0BAQwF -ADA5MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6 -b24gUm9vdCBDQSAyMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTEL -MAkGA1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJv -b3QgQ0EgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Wny2cSkxK -gXlRmeyKy2tgURO8TW0G/LAIjd0ZEGrHJgw12MBvIITplLGbhQPDW9tK6Mj4kHbZ -W0/jTOgGNk3Mmqw9DJArktQGGWCsN0R5hYGCrVo34A3MnaZMUnbqQ523BNFQ9lXg -1dKmSYXpN+nKfq5clU1Imj+uIFptiJXZNLhSGkOQsL9sBbm2eLfq0OQ6PBJTYv9K -8nu+NQWpEjTj82R0Yiw9AElaKP4yRLuH3WUnAnE72kr3H9rN9yFVkE8P7K6C4Z9r -2UXTu/Bfh+08LDmG2j/e7HJV63mjrdvdfLC6HM783k81ds8P+HgfajZRRidhW+me -z/CiVX18JYpvL7TFz4QuK/0NURBs+18bvBt+xa47mAExkv8LV/SasrlX6avvDXbR -8O70zoan4G7ptGmh32n2M8ZpLpcTnqWHsFcQgTfJU7O7f/aS0ZzQGPSSbtqDT6Zj -mUyl+17vIWR6IF9sZIUVyzfpYgwLKhbcAS4y2j5L9Z469hdAlO+ekQiG+r5jqFoz -7Mt0Q5X5bGlSNscpb/xVA1wf+5+9R+vnSUeVC06JIglJ4PVhHvG/LopyboBZ/1c6 -+XUyo05f7O0oYtlNc/LMgRdg7c3r3NunysV+Ar3yVAhU/bQtCSwXVEqY0VThUWcI -0u1ufm8/0i2BWSlmy5A5lREedCf+3euvAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB -Af8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSwDPBMMPQFWAJI/TPlUq9LhONm -UjANBgkqhkiG9w0BAQwFAAOCAgEAqqiAjw54o+Ci1M3m9Zh6O+oAA7CXDpO8Wqj2 -LIxyh6mx/H9z/WNxeKWHWc8w4Q0QshNabYL1auaAn6AFC2jkR2vHat+2/XcycuUY -+gn0oJMsXdKMdYV2ZZAMA3m3MSNjrXiDCYZohMr/+c8mmpJ5581LxedhpxfL86kS -k5Nrp+gvU5LEYFiwzAJRGFuFjWJZY7attN6a+yb3ACfAXVU3dJnJUH/jWS5E4ywl -7uxMMne0nxrpS10gxdr9HIcWxkPo1LsmmkVwXqkLN1PiRnsn/eBG8om3zEK2yygm -btmlyTrIQRNg91CMFa6ybRoVGld45pIq2WWQgj9sAq+uEjonljYE1x2igGOpm/Hl -urR8FLBOybEfdF849lHqm/osohHUqS0nGkWxr7JOcQ3AWEbWaQbLU8uz/mtBzUF+ -fUwPfHJ5elnNXkoOrJupmHN5fLT0zLm4BwyydFy4x2+IoZCn9Kr5v2c69BoVYh63 -n749sSmvZ6ES8lgQGVMDMBu4Gon2nL2XA46jCfMdiyHxtN/kHNGfZQIG6lzWE7OE -76KlXIx3KadowGuuQNKotOrN8I1LOJwZmhsoVLiJkO/KdYE+HvJkJMcYr07/R54H -9jVlpNMKVv/1F2Rs76giJUmTtt8AF9pYfl3uxRuw0dFfIRDH+fO6AgonB8Xx1sfT -4PsJYGw= ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 3 O=Amazon -# Subject: CN=Amazon Root CA 3 O=Amazon -# Label: "Amazon Root CA 3" -# Serial: 143266986699090766294700635381230934788665930 -# MD5 Fingerprint: a0:d4:ef:0b:f7:b5:d8:49:95:2a:ec:f5:c4:fc:81:87 -# SHA1 Fingerprint: 0d:44:dd:8c:3c:8c:1a:1a:58:75:64:81:e9:0f:2e:2a:ff:b3:d2:6e -# SHA256 Fingerprint: 18:ce:6c:fe:7b:f1:4e:60:b2:e3:47:b8:df:e8:68:cb:31:d0:2e:bb:3a:da:27:15:69:f5:03:43:b4:6d:b3:a4 ------BEGIN CERTIFICATE----- -MIIBtjCCAVugAwIBAgITBmyf1XSXNmY/Owua2eiedgPySjAKBggqhkjOPQQDAjA5 -MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g -Um9vdCBDQSAzMB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG -A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg -Q0EgMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABCmXp8ZBf8ANm+gBG1bG8lKl -ui2yEujSLtf6ycXYqm0fc4E7O5hrOXwzpcVOho6AF2hiRVd9RFgdszflZwjrZt6j -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBSr -ttvXBp43rDCGB5Fwx5zEGbF4wDAKBggqhkjOPQQDAgNJADBGAiEA4IWSoxe3jfkr -BqWTrBqYaGFy+uGh0PsceGCmQ5nFuMQCIQCcAu/xlJyzlvnrxir4tiz+OpAUFteM -YyRIHN8wfdVoOw== ------END CERTIFICATE----- - -# Issuer: CN=Amazon Root CA 4 O=Amazon -# Subject: CN=Amazon Root CA 4 O=Amazon -# Label: "Amazon Root CA 4" -# Serial: 143266989758080763974105200630763877849284878 -# MD5 Fingerprint: 89:bc:27:d5:eb:17:8d:06:6a:69:d5:fd:89:47:b4:cd -# SHA1 Fingerprint: f6:10:84:07:d6:f8:bb:67:98:0c:c2:e2:44:c2:eb:ae:1c:ef:63:be -# SHA256 Fingerprint: e3:5d:28:41:9e:d0:20:25:cf:a6:90:38:cd:62:39:62:45:8d:a5:c6:95:fb:de:a3:c2:2b:0b:fb:25:89:70:92 ------BEGIN CERTIFICATE----- -MIIB8jCCAXigAwIBAgITBmyf18G7EEwpQ+Vxe3ssyBrBDjAKBggqhkjOPQQDAzA5 -MQswCQYDVQQGEwJVUzEPMA0GA1UEChMGQW1hem9uMRkwFwYDVQQDExBBbWF6b24g -Um9vdCBDQSA0MB4XDTE1MDUyNjAwMDAwMFoXDTQwMDUyNjAwMDAwMFowOTELMAkG -A1UEBhMCVVMxDzANBgNVBAoTBkFtYXpvbjEZMBcGA1UEAxMQQW1hem9uIFJvb3Qg -Q0EgNDB2MBAGByqGSM49AgEGBSuBBAAiA2IABNKrijdPo1MN/sGKe0uoe0ZLY7Bi -9i0b2whxIdIA6GO9mif78DluXeo9pcmBqqNbIJhFXRbb/egQbeOc4OO9X4Ri83Bk -M6DLJC9wuoihKqB1+IGuYgbEgds5bimwHvouXKNCMEAwDwYDVR0TAQH/BAUwAwEB -/zAOBgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFNPsxzplbszh2naaVvuc84ZtV+WB -MAoGCCqGSM49BAMDA2gAMGUCMDqLIfG9fhGt0O9Yli/W651+kI0rz2ZVwyzjKKlw -CkcO8DdZEv8tmZQoTipPNU0zWgIxAOp1AE47xDqUEpHJWEadIRNyp4iciuRMStuW -1KyLa2tJElMzrdfkviT8tQp21KW8EA== ------END CERTIFICATE----- - -# Issuer: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM -# Subject: CN=TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1 O=Turkiye Bilimsel ve Teknolojik Arastirma Kurumu - TUBITAK OU=Kamu Sertifikasyon Merkezi - Kamu SM -# Label: "TUBITAK Kamu SM SSL Kok Sertifikasi - Surum 1" -# Serial: 1 -# MD5 Fingerprint: dc:00:81:dc:69:2f:3e:2f:b0:3b:f6:3d:5a:91:8e:49 -# SHA1 Fingerprint: 31:43:64:9b:ec:ce:27:ec:ed:3a:3f:0b:8f:0d:e4:e8:91:dd:ee:ca -# SHA256 Fingerprint: 46:ed:c3:68:90:46:d5:3a:45:3f:b3:10:4a:b8:0d:ca:ec:65:8b:26:60:ea:16:29:dd:7e:86:79:90:64:87:16 ------BEGIN CERTIFICATE----- -MIIEYzCCA0ugAwIBAgIBATANBgkqhkiG9w0BAQsFADCB0jELMAkGA1UEBhMCVFIx -GDAWBgNVBAcTD0dlYnplIC0gS29jYWVsaTFCMEAGA1UEChM5VHVya2l5ZSBCaWxp -bXNlbCB2ZSBUZWtub2xvamlrIEFyYXN0aXJtYSBLdXJ1bXUgLSBUVUJJVEFLMS0w -KwYDVQQLEyRLYW11IFNlcnRpZmlrYXN5b24gTWVya2V6aSAtIEthbXUgU00xNjA0 -BgNVBAMTLVRVQklUQUsgS2FtdSBTTSBTU0wgS29rIFNlcnRpZmlrYXNpIC0gU3Vy -dW0gMTAeFw0xMzExMjUwODI1NTVaFw00MzEwMjUwODI1NTVaMIHSMQswCQYDVQQG -EwJUUjEYMBYGA1UEBxMPR2ViemUgLSBLb2NhZWxpMUIwQAYDVQQKEzlUdXJraXll -IEJpbGltc2VsIHZlIFRla25vbG9qaWsgQXJhc3Rpcm1hIEt1cnVtdSAtIFRVQklU -QUsxLTArBgNVBAsTJEthbXUgU2VydGlmaWthc3lvbiBNZXJrZXppIC0gS2FtdSBT -TTE2MDQGA1UEAxMtVFVCSVRBSyBLYW11IFNNIFNTTCBLb2sgU2VydGlmaWthc2kg -LSBTdXJ1bSAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr3UwM6q7 -a9OZLBI3hNmNe5eA027n/5tQlT6QlVZC1xl8JoSNkvoBHToP4mQ4t4y86Ij5iySr -LqP1N+RAjhgleYN1Hzv/bKjFxlb4tO2KRKOrbEz8HdDc72i9z+SqzvBV96I01INr -N3wcwv61A+xXzry0tcXtAA9TNypN9E8Mg/uGz8v+jE69h/mniyFXnHrfA2eJLJ2X -YacQuFWQfw4tJzh03+f92k4S400VIgLI4OD8D62K18lUUMw7D8oWgITQUVbDjlZ/ -iSIzL+aFCr2lqBs23tPcLG07xxO9WSMs5uWk99gL7eqQQESolbuT1dCANLZGeA4f -AJNG4e7p+exPFwIDAQABo0IwQDAdBgNVHQ4EFgQUZT/HiobGPN08VFw1+DrtUgxH -V8gwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL -BQADggEBACo/4fEyjq7hmFxLXs9rHmoJ0iKpEsdeV31zVmSAhHqT5Am5EM2fKifh -AHe+SMg1qIGf5LgsyX8OsNJLN13qudULXjS99HMpw+0mFZx+CFOKWI3QSyjfwbPf -IPP54+M638yclNhOT8NrF7f3cuitZjO1JVOr4PhMqZ398g26rrnZqsZr+ZO7rqu4 -lzwDGrpDxpa5RXI4s6ehlj2Re37AIVNMh+3yC1SVUZPVIqUNivGTDj5UDrDYyU7c -8jEyVupk+eq1nRZmQnLzf9OxMUP8pI4X8W0jq5Rm+K37DwhuJi1/FwcJsoz7UMCf -lo3Ptv0AnVoUmr8CRPXBwp8iXqIPoeM= ------END CERTIFICATE----- - -# Issuer: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. -# Subject: CN=GDCA TrustAUTH R5 ROOT O=GUANG DONG CERTIFICATE AUTHORITY CO.,LTD. -# Label: "GDCA TrustAUTH R5 ROOT" -# Serial: 9009899650740120186 -# MD5 Fingerprint: 63:cc:d9:3d:34:35:5c:6f:53:a3:e2:08:70:48:1f:b4 -# SHA1 Fingerprint: 0f:36:38:5b:81:1a:25:c3:9b:31:4e:83:ca:e9:34:66:70:cc:74:b4 -# SHA256 Fingerprint: bf:ff:8f:d0:44:33:48:7d:6a:8a:a6:0c:1a:29:76:7a:9f:c2:bb:b0:5e:42:0f:71:3a:13:b9:92:89:1d:38:93 ------BEGIN CERTIFICATE----- -MIIFiDCCA3CgAwIBAgIIfQmX/vBH6nowDQYJKoZIhvcNAQELBQAwYjELMAkGA1UE -BhMCQ04xMjAwBgNVBAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZ -IENPLixMVEQuMR8wHQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMB4XDTE0 -MTEyNjA1MTMxNVoXDTQwMTIzMTE1NTk1OVowYjELMAkGA1UEBhMCQ04xMjAwBgNV -BAoMKUdVQU5HIERPTkcgQ0VSVElGSUNBVEUgQVVUSE9SSVRZIENPLixMVEQuMR8w -HQYDVQQDDBZHRENBIFRydXN0QVVUSCBSNSBST09UMIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA2aMW8Mh0dHeb7zMNOwZ+Vfy1YI92hhJCfVZmPoiC7XJj -Dp6L3TQsAlFRwxn9WVSEyfFrs0yw6ehGXTjGoqcuEVe6ghWinI9tsJlKCvLriXBj -TnnEt1u9ol2x8kECK62pOqPseQrsXzrj/e+APK00mxqriCZ7VqKChh/rNYmDf1+u -KU49tm7srsHwJ5uu4/Ts765/94Y9cnrrpftZTqfrlYwiOXnhLQiPzLyRuEH3FMEj -qcOtmkVEs7LXLM3GKeJQEK5cy4KOFxg2fZfmiJqwTTQJ9Cy5WmYqsBebnh52nUpm -MUHfP/vFBu8btn4aRjb3ZGM74zkYI+dndRTVdVeSN72+ahsmUPI2JgaQxXABZG12 -ZuGR224HwGGALrIuL4xwp9E7PLOR5G62xDtw8mySlwnNR30YwPO7ng/Wi64HtloP -zgsMR6flPri9fcebNaBhlzpBdRfMK5Z3KpIhHtmVdiBnaM8Nvd/WHwlqmuLMc3Gk -L30SgLdTMEZeS1SZD2fJpcjyIMGC7J0R38IC+xo70e0gmu9lZJIQDSri3nDxGGeC -jGHeuLzRL5z7D9Ar7Rt2ueQ5Vfj4oR24qoAATILnsn8JuLwwoC8N9VKejveSswoA -HQBUlwbgsQfZxw9cZX08bVlX5O2ljelAU58VS6Bx9hoh49pwBiFYFIeFd3mqgnkC -AwEAAaNCMEAwHQYDVR0OBBYEFOLJQJ9NzuiaoXzPDj9lxSmIahlRMA8GA1UdEwEB -/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQDRSVfg -p8xoWLoBDysZzY2wYUWsEe1jUGn4H3++Fo/9nesLqjJHdtJnJO29fDMylyrHBYZm -DRd9FBUb1Ov9H5r2XpdptxolpAqzkT9fNqyL7FeoPueBihhXOYV0GkLH6VsTX4/5 -COmSdI31R9KrO9b7eGZONn356ZLpBN79SWP8bfsUcZNnL0dKt7n/HipzcEYwv1ry -L3ml4Y0M2fmyYzeMN2WFcGpcWwlyua1jPLHd+PwyvzeG5LuOmCd+uh8W4XAR8gPf -JWIyJyYYMoSf/wA6E7qaTfRPuBRwIrHKK5DOKcFw9C+df/KQHtZa37dG/OaG+svg -IHZ6uqbL9XzeYqWxi+7egmaKTjowHz+Ay60nugxe19CxVsp3cbK1daFQqUBDF8Io -2c9Si1vIY9RCPqAzekYu9wogRlR+ak8x8YF+QnQ4ZXMn7sZ8uI7XpTrXmKGcjBBV -09tL7ECQ8s1uV9JiDnxXk7Gnbc2dg7sq5+W2O3FYrf3RRbxake5TFW/TRQl1brqQ -XR4EzzffHqhmsYzmIGrv/EhOdJhCrylvLmrH+33RZjEizIYAfmaDDEL0vTSSwxrq -T8p+ck0LcIymSLumoRT2+1hEmRSuqguTaaApJUqlyyvdimYHFngVV3Eb7PVHhPOe -MTd61X8kreS8/f3MboPoDKi3QWwH3b08hpcv0g== ------END CERTIFICATE----- - -# Issuer: CN=SSL.com Root Certification Authority RSA O=SSL Corporation -# Subject: CN=SSL.com Root Certification Authority RSA O=SSL Corporation -# Label: "SSL.com Root Certification Authority RSA" -# Serial: 8875640296558310041 -# MD5 Fingerprint: 86:69:12:c0:70:f1:ec:ac:ac:c2:d5:bc:a5:5b:a1:29 -# SHA1 Fingerprint: b7:ab:33:08:d1:ea:44:77:ba:14:80:12:5a:6f:bd:a9:36:49:0c:bb -# SHA256 Fingerprint: 85:66:6a:56:2e:e0:be:5c:e9:25:c1:d8:89:0a:6f:76:a8:7e:c1:6d:4d:7d:5f:29:ea:74:19:cf:20:12:3b:69 ------BEGIN CERTIFICATE----- -MIIF3TCCA8WgAwIBAgIIeyyb0xaAMpkwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE -BhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQK -DA9TU0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eSBSU0EwHhcNMTYwMjEyMTczOTM5WhcNNDEwMjEyMTcz -OTM5WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv -dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNv -bSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTCCAiIwDQYJKoZIhvcN -AQEBBQADggIPADCCAgoCggIBAPkP3aMrfcvQKv7sZ4Wm5y4bunfh4/WvpOz6Sl2R -xFdHaxh3a3by/ZPkPQ/CFp4LZsNWlJ4Xg4XOVu/yFv0AYvUiCVToZRdOQbngT0aX -qhvIuG5iXmmxX9sqAn78bMrzQdjt0Oj8P2FI7bADFB0QDksZ4LtO7IZl/zbzXmcC -C52GVWH9ejjt/uIZALdvoVBidXQ8oPrIJZK0bnoix/geoeOy3ZExqysdBP+lSgQ3 -6YWkMyv94tZVNHwZpEpox7Ko07fKoZOI68GXvIz5HdkihCR0xwQ9aqkpk8zruFvh -/l8lqjRYyMEjVJ0bmBHDOJx+PYZspQ9AhnwC9FwCTyjLrnGfDzrIM/4RJTXq/LrF -YD3ZfBjVsqnTdXgDciLKOsMf7yzlLqn6niy2UUb9rwPW6mBo6oUWNmuF6R7As93E -JNyAKoFBbZQ+yODJgUEAnl6/f8UImKIYLEJAs/lvOCdLToD0PYFH4Ih86hzOtXVc -US4cK38acijnALXRdMbX5J+tB5O2UzU1/Dfkw/ZdFr4hc96SCvigY2q8lpJqPvi8 -ZVWb3vUNiSYE/CUapiVpy8JtynziWV+XrOvvLsi81xtZPCvM8hnIk2snYxnP/Okm -+Mpxm3+T/jRnhE6Z6/yzeAkzcLpmpnbtG3PrGqUNxCITIJRWCk4sbE6x/c+cCbqi -M+2HAgMBAAGjYzBhMB0GA1UdDgQWBBTdBAkHovV6fVJTEpKV7jiAJQ2mWTAPBgNV -HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFN0ECQei9Xp9UlMSkpXuOIAlDaZZMA4G -A1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAIBgRlCn7Jp0cHh5wYfGV -cpNxJK1ok1iOMq8bs3AD/CUrdIWQPXhq9LmLpZc7tRiRux6n+UBbkflVma8eEdBc -Hadm47GUBwwyOabqG7B52B2ccETjit3E+ZUfijhDPwGFpUenPUayvOUiaPd7nNgs -PgohyC0zrL/FgZkxdMF1ccW+sfAjRfSda/wZY52jvATGGAslu1OJD7OAUN5F7kR/ -q5R4ZJjT9ijdh9hwZXT7DrkT66cPYakylszeu+1jTBi7qUD3oFRuIIhxdRjqerQ0 -cuAjJ3dctpDqhiVAq+8zD8ufgr6iIPv2tS0a5sKFsXQP+8hlAqRSAUfdSSLBv9jr -a6x+3uxjMxW3IwiPxg+NQVrdjsW5j+VFP3jbutIbQLH+cU0/4IGiul607BXgk90I -H37hVZkLId6Tngr75qNJvTYw/ud3sqB1l7UtgYgXZSD32pAAn8lSzDLKNXz1PQ/Y -K9f1JmzJBjSWFupwWRoyeXkLtoh/D1JIPb9s2KJELtFOt3JY04kTlf5Eq/jXixtu -nLwsoFvVagCvXzfh1foQC5ichucmj87w7G6KVwuA406ywKBjYZC6VWg3dGq2ktuf -oYYitmUnDuy2n0Jg5GfCtdpBC8TTi2EbvPofkSvXRAdeuims2cXp71NIWuuA8ShY -Ic2wBlX7Jz9TkHCpBB5XJ7k= ------END CERTIFICATE----- - -# Issuer: CN=SSL.com Root Certification Authority ECC O=SSL Corporation -# Subject: CN=SSL.com Root Certification Authority ECC O=SSL Corporation -# Label: "SSL.com Root Certification Authority ECC" -# Serial: 8495723813297216424 -# MD5 Fingerprint: 2e:da:e4:39:7f:9c:8f:37:d1:70:9f:26:17:51:3a:8e -# SHA1 Fingerprint: c3:19:7c:39:24:e6:54:af:1b:c4:ab:20:95:7a:e2:c3:0e:13:02:6a -# SHA256 Fingerprint: 34:17:bb:06:cc:60:07:da:1b:96:1c:92:0b:8a:b4:ce:3f:ad:82:0e:4a:a3:0b:9a:cb:c4:a7:4e:bd:ce:bc:65 ------BEGIN CERTIFICATE----- -MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC -VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T -U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0 -aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz -WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0 -b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS -b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB -BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI -7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg -CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud -EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD -VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T -kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+ -gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl ------END CERTIFICATE----- - -# Issuer: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation -# Subject: CN=SSL.com EV Root Certification Authority RSA R2 O=SSL Corporation -# Label: "SSL.com EV Root Certification Authority RSA R2" -# Serial: 6248227494352943350 -# MD5 Fingerprint: e1:1e:31:58:1a:ae:54:53:02:f6:17:6a:11:7b:4d:95 -# SHA1 Fingerprint: 74:3a:f0:52:9b:d0:32:a0:f4:4a:83:cd:d4:ba:a9:7b:7c:2e:c4:9a -# SHA256 Fingerprint: 2e:7b:f1:6c:c2:24:85:a7:bb:e2:aa:86:96:75:07:61:b0:ae:39:be:3b:2f:e9:d0:cc:6d:4e:f7:34:91:42:5c ------BEGIN CERTIFICATE----- -MIIF6zCCA9OgAwIBAgIIVrYpzTS8ePYwDQYJKoZIhvcNAQELBQAwgYIxCzAJBgNV -BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjEYMBYGA1UE -CgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQDDC5TU0wuY29tIEVWIFJvb3QgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIyMB4XDTE3MDUzMTE4MTQzN1oXDTQy -MDUzMDE4MTQzN1owgYIxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4G -A1UEBwwHSG91c3RvbjEYMBYGA1UECgwPU1NMIENvcnBvcmF0aW9uMTcwNQYDVQQD -DC5TU0wuY29tIEVWIFJvb3QgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgUlNBIFIy -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAjzZlQOHWTcDXtOlG2mvq -M0fNTPl9fb69LT3w23jhhqXZuglXaO1XPqDQCEGD5yhBJB/jchXQARr7XnAjssuf -OePPxU7Gkm0mxnu7s9onnQqG6YE3Bf7wcXHswxzpY6IXFJ3vG2fThVUCAtZJycxa -4bH3bzKfydQ7iEGonL3Lq9ttewkfokxykNorCPzPPFTOZw+oz12WGQvE43LrrdF9 -HSfvkusQv1vrO6/PgN3B0pYEW3p+pKk8OHakYo6gOV7qd89dAFmPZiw+B6KjBSYR -aZfqhbcPlgtLyEDhULouisv3D5oi53+aNxPN8k0TayHRwMwi8qFG9kRpnMphNQcA -b9ZhCBHqurj26bNg5U257J8UZslXWNvNh2n4ioYSA0e/ZhN2rHd9NCSFg83XqpyQ -Gp8hLH94t2S42Oim9HizVcuE0jLEeK6jj2HdzghTreyI/BXkmg3mnxp3zkyPuBQV -PWKchjgGAGYS5Fl2WlPAApiiECtoRHuOec4zSnaqW4EWG7WK2NAAe15itAnWhmMO -pgWVSbooi4iTsjQc2KRVbrcc0N6ZVTsj9CLg+SlmJuwgUHfbSguPvuUCYHBBXtSu -UDkiFCbLsjtzdFVHB3mBOagwE0TlBIqulhMlQg+5U8Sb/M3kHN48+qvWBkofZ6aY -MBzdLNvcGJVXZsb/XItW9XcCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAfBgNV -HSMEGDAWgBT5YLvU49U09rj1BoAlp3PbRmmonjAdBgNVHQ4EFgQU+WC71OPVNPa4 -9QaAJadz20ZpqJ4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQBW -s47LCp1Jjr+kxJG7ZhcFUZh1++VQLHqe8RT6q9OKPv+RKY9ji9i0qVQBDb6Thi/5 -Sm3HXvVX+cpVHBK+Rw82xd9qt9t1wkclf7nxY/hoLVUE0fKNsKTPvDxeH3jnpaAg -cLAExbf3cqfeIg29MyVGjGSSJuM+LmOW2puMPfgYCdcDzH2GguDKBAdRUNf/ktUM -79qGn5nX67evaOI5JpS6aLe/g9Pqemc9YmeuJeVy6OLk7K4S9ksrPJ/psEDzOFSz -/bdoyNrGj1E8svuR3Bznm53htw1yj+KkxKl4+esUrMZDBcJlOSgYAsOCsp0FvmXt -ll9ldDz7CTUue5wT/RsPXcdtgTpWD8w74a8CLyKsRspGPKAcTNZEtF4uXBVmCeEm -Kf7GUmG6sXP/wwyc5WxqlD8UykAWlYTzWamsX0xhk23RO8yilQwipmdnRC652dKK -QbNmC1r7fSOl8hqw/96bg5Qu0T/fkreRrwU7ZcegbLHNYhLDkBvjJc40vG93drEQ -w/cFGsDWr3RiSBd3kmmQYRzelYB0VI8YHMPzA9C/pEN1hlMYegouCRw2n5H9gooi -S9EOUCXdywMMF8mDAAhONU2Ki+3wApRmLER/y5UnlhetCTCstnEXbosX9hwJ1C07 -mKVx01QT2WDz9UtmT/rx7iASjbSsV7FFY6GsdqnC+w== ------END CERTIFICATE----- - -# Issuer: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation -# Subject: CN=SSL.com EV Root Certification Authority ECC O=SSL Corporation -# Label: "SSL.com EV Root Certification Authority ECC" -# Serial: 3182246526754555285 -# MD5 Fingerprint: 59:53:22:65:83:42:01:54:c0:ce:42:b9:5a:7c:f2:90 -# SHA1 Fingerprint: 4c:dd:51:a3:d1:f5:20:32:14:b0:c6:c5:32:23:03:91:c7:46:42:6d -# SHA256 Fingerprint: 22:a2:c1:f7:bd:ed:70:4c:c1:e7:01:b5:f4:08:c3:10:88:0f:e9:56:b5:de:2a:4a:44:f9:9c:87:3a:25:a7:c8 ------BEGIN CERTIFICATE----- -MIIClDCCAhqgAwIBAgIILCmcWxbtBZUwCgYIKoZIzj0EAwIwfzELMAkGA1UEBhMC -VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T -U0wgQ29ycG9yYXRpb24xNDAyBgNVBAMMK1NTTC5jb20gRVYgUm9vdCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNTIzWhcNNDEwMjEyMTgx -NTIzWjB/MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hv -dXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjE0MDIGA1UEAwwrU1NMLmNv -bSBFViBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49 -AgEGBSuBBAAiA2IABKoSR5CYG/vvw0AHgyBO8TCCogbR8pKGYfL2IWjKAMTH6kMA -VIbc/R/fALhBYlzccBYy3h+Z1MzFB8gIH2EWB1E9fVwHU+M1OIzfzZ/ZLg1Kthku -WnBaBu2+8KGwytAJKaNjMGEwHQYDVR0OBBYEFFvKXuXe0oGqzagtZFG22XKbl+ZP -MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUW8pe5d7SgarNqC1kUbbZcpuX -5k8wDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2gAMGUCMQCK5kCJN+vp1RPZ -ytRrJPOwPYdGWBrssd9v+1a6cGvHOMzosYxPD/fxZ3YOg9AeUY8CMD32IygmTMZg -h5Mmm7I1HrrW9zzRHM76JTymGoEVW/MSD2zuZYrJh6j5B+BimoxcSg== ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R6 -# Label: "GlobalSign Root CA - R6" -# Serial: 1417766617973444989252670301619537 -# MD5 Fingerprint: 4f:dd:07:e4:d4:22:64:39:1e:0c:37:42:ea:d1:c6:ae -# SHA1 Fingerprint: 80:94:64:0e:b5:a7:a1:ca:11:9c:1f:dd:d5:9f:81:02:63:a7:fb:d1 -# SHA256 Fingerprint: 2c:ab:ea:fe:37:d0:6c:a2:2a:ba:73:91:c0:03:3d:25:98:29:52:c4:53:64:73:49:76:3a:3a:b5:ad:6c:cf:69 ------BEGIN CERTIFICATE----- -MIIFgzCCA2ugAwIBAgIORea7A4Mzw4VlSOb/RVEwDQYJKoZIhvcNAQEMBQAwTDEg -MB4GA1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjYxEzARBgNVBAoTCkdsb2Jh -bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTQxMjEwMDAwMDAwWhcNMzQx -MjEwMDAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSNjET -MBEGA1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCAiIwDQYJ -KoZIhvcNAQEBBQADggIPADCCAgoCggIBAJUH6HPKZvnsFMp7PPcNCPG0RQssgrRI -xutbPK6DuEGSMxSkb3/pKszGsIhrxbaJ0cay/xTOURQh7ErdG1rG1ofuTToVBu1k -ZguSgMpE3nOUTvOniX9PeGMIyBJQbUJmL025eShNUhqKGoC3GYEOfsSKvGRMIRxD -aNc9PIrFsmbVkJq3MQbFvuJtMgamHvm566qjuL++gmNQ0PAYid/kD3n16qIfKtJw -LnvnvJO7bVPiSHyMEAc4/2ayd2F+4OqMPKq0pPbzlUoSB239jLKJz9CgYXfIWHSw -1CM69106yqLbnQneXUQtkPGBzVeS+n68UARjNN9rkxi+azayOeSsJDa38O+2HBNX -k7besvjihbdzorg1qkXy4J02oW9UivFyVm4uiMVRQkQVlO6jxTiWm05OWgtH8wY2 -SXcwvHE35absIQh1/OZhFj931dmRl4QKbNQCTXTAFO39OfuD8l4UoQSwC+n+7o/h -bguyCLNhZglqsQY6ZZZZwPA1/cnaKI0aEYdwgQqomnUdnjqGBQCe24DWJfncBZ4n -WUx2OVvq+aWh2IMP0f/fMBH5hc8zSPXKbWQULHpYT9NLCEnFlWQaYw55PfWzjMpY -rZxCRXluDocZXFSxZba/jJvcE+kNb7gu3GduyYsRtYQUigAZcIN5kZeR1Bonvzce -MgfYFGM8KEyvAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTAD -AQH/MB0GA1UdDgQWBBSubAWjkxPioufi1xzWx/B/yGdToDAfBgNVHSMEGDAWgBSu -bAWjkxPioufi1xzWx/B/yGdToDANBgkqhkiG9w0BAQwFAAOCAgEAgyXt6NH9lVLN -nsAEoJFp5lzQhN7craJP6Ed41mWYqVuoPId8AorRbrcWc+ZfwFSY1XS+wc3iEZGt -Ixg93eFyRJa0lV7Ae46ZeBZDE1ZXs6KzO7V33EByrKPrmzU+sQghoefEQzd5Mr61 -55wsTLxDKZmOMNOsIeDjHfrYBzN2VAAiKrlNIC5waNrlU/yDXNOd8v9EDERm8tLj -vUYAGm0CuiVdjaExUd1URhxN25mW7xocBFymFe944Hn+Xds+qkxV/ZoVqW/hpvvf -cDDpw+5CRu3CkwWJ+n1jez/QcYF8AOiYrg54NMMl+68KnyBr3TsTjxKM4kEaSHpz -oHdpx7Zcf4LIHv5YGygrqGytXm3ABdJ7t+uA/iU3/gKbaKxCXcPu9czc8FB10jZp -nOZ7BN9uBmm23goJSFmH63sUYHpkqmlD75HHTOwY3WzvUy2MmeFe8nI+z1TIvWfs -pA9MRf/TuTAjB0yPEL+GltmZWrSZVxykzLsViVO6LAUP5MSeGbEYNNVMnbrt9x+v -JJUEeKgDu+6B5dpffItKoZB0JaezPkvILFa9x8jvOOJckvB595yEunQtYQEgfn7R -8k8HWV+LLUNS60YMlOH1Zkd5d9VUWx+tJDfLRVpOoERIyNiwmcUVhAn21klJwGW4 -5hpxbqCo8YLoRT5s1gLXCmeDBVrJpBA= ------END CERTIFICATE----- - -# Issuer: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed -# Subject: CN=OISTE WISeKey Global Root GC CA O=WISeKey OU=OISTE Foundation Endorsed -# Label: "OISTE WISeKey Global Root GC CA" -# Serial: 44084345621038548146064804565436152554 -# MD5 Fingerprint: a9:d6:b9:2d:2f:93:64:f8:a5:69:ca:91:e9:68:07:23 -# SHA1 Fingerprint: e0:11:84:5e:34:de:be:88:81:b9:9c:f6:16:26:d1:96:1f:c3:b9:31 -# SHA256 Fingerprint: 85:60:f9:1c:36:24:da:ba:95:70:b5:fe:a0:db:e3:6f:f1:1a:83:23:be:94:86:85:4f:b3:f3:4a:55:71:19:8d ------BEGIN CERTIFICATE----- -MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw -CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 -bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg -Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ -BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu -ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS -b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni -eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W -p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E -BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T -rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV -57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg -Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 ------END CERTIFICATE----- - -# Issuer: CN=UCA Global G2 Root O=UniTrust -# Subject: CN=UCA Global G2 Root O=UniTrust -# Label: "UCA Global G2 Root" -# Serial: 124779693093741543919145257850076631279 -# MD5 Fingerprint: 80:fe:f0:c4:4a:f0:5c:62:32:9f:1c:ba:78:a9:50:f8 -# SHA1 Fingerprint: 28:f9:78:16:19:7a:ff:18:25:18:aa:44:fe:c1:a0:ce:5c:b6:4c:8a -# SHA256 Fingerprint: 9b:ea:11:c9:76:fe:01:47:64:c1:be:56:a6:f9:14:b5:a5:60:31:7a:bd:99:88:39:33:82:e5:16:1a:a0:49:3c ------BEGIN CERTIFICATE----- -MIIFRjCCAy6gAwIBAgIQXd+x2lqj7V2+WmUgZQOQ7zANBgkqhkiG9w0BAQsFADA9 -MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxGzAZBgNVBAMMElVDQSBH -bG9iYWwgRzIgUm9vdDAeFw0xNjAzMTEwMDAwMDBaFw00MDEyMzEwMDAwMDBaMD0x -CzAJBgNVBAYTAkNOMREwDwYDVQQKDAhVbmlUcnVzdDEbMBkGA1UEAwwSVUNBIEds -b2JhbCBHMiBSb290MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxeYr -b3zvJgUno4Ek2m/LAfmZmqkywiKHYUGRO8vDaBsGxUypK8FnFyIdK+35KYmToni9 -kmugow2ifsqTs6bRjDXVdfkX9s9FxeV67HeToI8jrg4aA3++1NDtLnurRiNb/yzm -VHqUwCoV8MmNsHo7JOHXaOIxPAYzRrZUEaalLyJUKlgNAQLx+hVRZ2zA+te2G3/R -VogvGjqNO7uCEeBHANBSh6v7hn4PJGtAnTRnvI3HLYZveT6OqTwXS3+wmeOwcWDc -C/Vkw85DvG1xudLeJ1uK6NjGruFZfc8oLTW4lVYa8bJYS7cSN8h8s+1LgOGN+jIj -tm+3SJUIsUROhYw6AlQgL9+/V087OpAh18EmNVQg7Mc/R+zvWr9LesGtOxdQXGLY -D0tK3Cv6brxzks3sx1DoQZbXqX5t2Okdj4q1uViSukqSKwxW/YDrCPBeKW4bHAyv -j5OJrdu9o54hyokZ7N+1wxrrFv54NkzWbtA+FxyQF2smuvt6L78RHBgOLXMDj6Dl -NaBa4kx1HXHhOThTeEDMg5PXCp6dW4+K5OXgSORIskfNTip1KnvyIvbJvgmRlld6 -iIis7nCs+dwp4wwcOxJORNanTrAmyPPZGpeRaOrvjUYG0lZFWJo8DA+DuAUlwznP -O6Q0ibd5Ei9Hxeepl2n8pndntd978XplFeRhVmUCAwEAAaNCMEAwDgYDVR0PAQH/ -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIHEjMz15DD/pQwIX4wV -ZyF0Ad/fMA0GCSqGSIb3DQEBCwUAA4ICAQATZSL1jiutROTL/7lo5sOASD0Ee/oj -L3rtNtqyzm325p7lX1iPyzcyochltq44PTUbPrw7tgTQvPlJ9Zv3hcU2tsu8+Mg5 -1eRfB70VVJd0ysrtT7q6ZHafgbiERUlMjW+i67HM0cOU2kTC5uLqGOiiHycFutfl -1qnN3e92mI0ADs0b+gO3joBYDic/UvuUospeZcnWhNq5NXHzJsBPd+aBJ9J3O5oU -b3n09tDh05S60FdRvScFDcH9yBIw7m+NESsIndTUv4BFFJqIRNow6rSn4+7vW4LV -PtateJLbXDzz2K36uGt/xDYotgIVilQsnLAXc47QN6MUPJiVAAwpBVueSUmxX8fj -y88nZY41F7dXyDDZQVu5FLbowg+UMaeUmMxq67XhJ/UQqAHojhJi6IjMtX9Gl8Cb -EGY4GjZGXyJoPd/JxhMnq1MGrKI8hgZlb7F+sSlEmqO6SWkoaY/X5V+tBIZkbxqg -DMUIYs6Ao9Dz7GjevjPHF1t/gMRMTLGmhIrDO7gJzRSBuhjjVFc2/tsvfEehOjPI -+Vg7RE+xygKJBJYoaMVLuCaJu9YzL1DV/pqJuhgyklTGW+Cd+V7lDSKb9triyCGy -YiGqhkCyLmTTX8jjfhFnRR8F/uOi77Oos/N9j/gMHyIfLXC0uAE0djAA5SN4p1bX -UB+K+wb1whnw0A== ------END CERTIFICATE----- - -# Issuer: CN=UCA Extended Validation Root O=UniTrust -# Subject: CN=UCA Extended Validation Root O=UniTrust -# Label: "UCA Extended Validation Root" -# Serial: 106100277556486529736699587978573607008 -# MD5 Fingerprint: a1:f3:5f:43:c6:34:9b:da:bf:8c:7e:05:53:ad:96:e2 -# SHA1 Fingerprint: a3:a1:b0:6f:24:61:23:4a:e3:36:a5:c2:37:fc:a6:ff:dd:f0:d7:3a -# SHA256 Fingerprint: d4:3a:f9:b3:54:73:75:5c:96:84:fc:06:d7:d8:cb:70:ee:5c:28:e7:73:fb:29:4e:b4:1e:e7:17:22:92:4d:24 ------BEGIN CERTIFICATE----- -MIIFWjCCA0KgAwIBAgIQT9Irj/VkyDOeTzRYZiNwYDANBgkqhkiG9w0BAQsFADBH -MQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNVBAMMHFVDQSBF -eHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwHhcNMTUwMzEzMDAwMDAwWhcNMzgxMjMx -MDAwMDAwWjBHMQswCQYDVQQGEwJDTjERMA8GA1UECgwIVW5pVHJ1c3QxJTAjBgNV -BAMMHFVDQSBFeHRlbmRlZCBWYWxpZGF0aW9uIFJvb3QwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQCpCQcoEwKwmeBkqh5DFnpzsZGgdT6o+uM4AHrsiWog -D4vFsJszA1qGxliG1cGFu0/GnEBNyr7uaZa4rYEwmnySBesFK5pI0Lh2PpbIILvS -sPGP2KxFRv+qZ2C0d35qHzwaUnoEPQc8hQ2E0B92CvdqFN9y4zR8V05WAT558aop -O2z6+I9tTcg1367r3CTueUWnhbYFiN6IXSV8l2RnCdm/WhUFhvMJHuxYMjMR83dk -sHYf5BA1FxvyDrFspCqjc/wJHx4yGVMR59mzLC52LqGj3n5qiAno8geK+LLNEOfi -c0CTuwjRP+H8C5SzJe98ptfRr5//lpr1kXuYC3fUfugH0mK1lTnj8/FtDw5lhIpj -VMWAtuCeS31HJqcBCF3RiJ7XwzJE+oJKCmhUfzhTA8ykADNkUVkLo4KRel7sFsLz -KuZi2irbWWIQJUoqgQtHB0MGcIfS+pMRKXpITeuUx3BNr2fVUbGAIAEBtHoIppB/ -TuDvB0GHr2qlXov7z1CymlSvw4m6WC31MJixNnI5fkkE/SmnTHnkBVfblLkWU41G -sx2VYVdWf6/wFlthWG82UBEL2KwrlRYaDh8IzTY0ZRBiZtWAXxQgXy0MoHgKaNYs -1+lvK9JKBZP8nm9rZ/+I8U6laUpSNwXqxhaN0sSZ0YIrO7o1dfdRUVjzyAfd5LQD -fwIDAQABo0IwQDAdBgNVHQ4EFgQU2XQ65DA9DfcS3H5aBZ8eNJr34RQwDwYDVR0T -AQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQADggIBADaN -l8xCFWQpN5smLNb7rhVpLGsaGvdftvkHTFnq88nIua7Mui563MD1sC3AO6+fcAUR -ap8lTwEpcOPlDOHqWnzcSbvBHiqB9RZLcpHIojG5qtr8nR/zXUACE/xOHAbKsxSQ -VBcZEhrxH9cMaVr2cXj0lH2RC47skFSOvG+hTKv8dGT9cZr4QQehzZHkPJrgmzI5 -c6sq1WnIeJEmMX3ixzDx/BR4dxIOE/TdFpS/S2d7cFOFyrC78zhNLJA5wA3CXWvp -4uXViI3WLL+rG761KIcSF3Ru/H38j9CHJrAb+7lsq+KePRXBOy5nAliRn+/4Qh8s -t2j1da3Ptfb/EX3C8CSlrdP6oDyp+l3cpaDvRKS+1ujl5BOWF3sGPjLtx7dCvHaj -2GU4Kzg1USEODm8uNBNA4StnDG1KQTAYI1oyVZnJF+A83vbsea0rWBmirSwiGpWO -vpaQXUJXxPkUAzUrHC1RVwinOt4/5Mi0A3PCwSaAuwtCH60NryZy2sy+s6ODWA2C -xR9GUeOcGMyNm43sSet1UNWMKFnKdDTajAshqx7qG+XH/RU+wBeq+yNuJkbL+vmx -cmtpzyKEC2IPrNkZAJSidjzULZrtBJ4tBmIQN1IchXIbJ+XMxjHsN+xjWZsLHXbM -fjKaiJUINlK73nZfdklJrX+9ZSCyycErdhh2n1ax ------END CERTIFICATE----- - -# Issuer: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 -# Subject: CN=Certigna Root CA O=Dhimyotis OU=0002 48146308100036 -# Label: "Certigna Root CA" -# Serial: 269714418870597844693661054334862075617 -# MD5 Fingerprint: 0e:5c:30:62:27:eb:5b:bc:d7:ae:62:ba:e9:d5:df:77 -# SHA1 Fingerprint: 2d:0d:52:14:ff:9e:ad:99:24:01:74:20:47:6e:6c:85:27:27:f5:43 -# SHA256 Fingerprint: d4:8d:3d:23:ee:db:50:a4:59:e5:51:97:60:1c:27:77:4b:9d:7b:18:c9:4d:5a:05:95:11:a1:02:50:b9:31:68 ------BEGIN CERTIFICATE----- -MIIGWzCCBEOgAwIBAgIRAMrpG4nxVQMNo+ZBbcTjpuEwDQYJKoZIhvcNAQELBQAw -WjELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCURoaW15b3RpczEcMBoGA1UECwwTMDAw -MiA0ODE0NjMwODEwMDAzNjEZMBcGA1UEAwwQQ2VydGlnbmEgUm9vdCBDQTAeFw0x -MzEwMDEwODMyMjdaFw0zMzEwMDEwODMyMjdaMFoxCzAJBgNVBAYTAkZSMRIwEAYD -VQQKDAlEaGlteW90aXMxHDAaBgNVBAsMEzAwMDIgNDgxNDYzMDgxMDAwMzYxGTAX -BgNVBAMMEENlcnRpZ25hIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw -ggIKAoICAQDNGDllGlmx6mQWDoyUJJV8g9PFOSbcDO8WV43X2KyjQn+Cyu3NW9sO -ty3tRQgXstmzy9YXUnIo245Onoq2C/mehJpNdt4iKVzSs9IGPjA5qXSjklYcoW9M -CiBtnyN6tMbaLOQdLNyzKNAT8kxOAkmhVECe5uUFoC2EyP+YbNDrihqECB63aCPu -I9Vwzm1RaRDuoXrC0SIxwoKF0vJVdlB8JXrJhFwLrN1CTivngqIkicuQstDuI7pm -TLtipPlTWmR7fJj6o0ieD5Wupxj0auwuA0Wv8HT4Ks16XdG+RCYyKfHx9WzMfgIh -C59vpD++nVPiz32pLHxYGpfhPTc3GGYo0kDFUYqMwy3OU4gkWGQwFsWq4NYKpkDf -ePb1BHxpE4S80dGnBs8B92jAqFe7OmGtBIyT46388NtEbVncSVmurJqZNjBBe3Yz -IoejwpKGbvlw7q6Hh5UbxHq9MfPU0uWZ/75I7HX1eBYdpnDBfzwboZL7z8g81sWT -Co/1VTp2lc5ZmIoJlXcymoO6LAQ6l73UL77XbJuiyn1tJslV1c/DeVIICZkHJC1k -JWumIWmbat10TWuXekG9qxf5kBdIjzb5LdXF2+6qhUVB+s06RbFo5jZMm5BX7CO5 -hwjCxAnxl4YqKE3idMDaxIzb3+KhF1nOJFl0Mdp//TBt2dzhauH8XwIDAQABo4IB -GjCCARYwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYE -FBiHVuBud+4kNTxOc5of1uHieX4rMB8GA1UdIwQYMBaAFBiHVuBud+4kNTxOc5of -1uHieX4rMEQGA1UdIAQ9MDswOQYEVR0gADAxMC8GCCsGAQUFBwIBFiNodHRwczov -L3d3d3cuY2VydGlnbmEuZnIvYXV0b3JpdGVzLzBtBgNVHR8EZjBkMC+gLaArhilo -dHRwOi8vY3JsLmNlcnRpZ25hLmZyL2NlcnRpZ25hcm9vdGNhLmNybDAxoC+gLYYr -aHR0cDovL2NybC5kaGlteW90aXMuY29tL2NlcnRpZ25hcm9vdGNhLmNybDANBgkq -hkiG9w0BAQsFAAOCAgEAlLieT/DjlQgi581oQfccVdV8AOItOoldaDgvUSILSo3L -6btdPrtcPbEo/uRTVRPPoZAbAh1fZkYJMyjhDSSXcNMQH+pkV5a7XdrnxIxPTGRG -HVyH41neQtGbqH6mid2PHMkwgu07nM3A6RngatgCdTer9zQoKJHyBApPNeNgJgH6 -0BGM+RFq7q89w1DTj18zeTyGqHNFkIwgtnJzFyO+B2XleJINugHA64wcZr+shncB -lA2c5uk5jR+mUYyZDDl34bSb+hxnV29qao6pK0xXeXpXIs/NX2NGjVxZOob4Mkdi -o2cNGJHc+6Zr9UhhcyNZjgKnvETq9Emd8VRY+WCv2hikLyhF3HqgiIZd8zvn/yk1 -gPxkQ5Tm4xxvvq0OKmOZK8l+hfZx6AYDlf7ej0gcWtSS6Cvu5zHbugRqh5jnxV/v -faci9wHYTfmJ0A6aBVmknpjZbyvKcL5kwlWj9Omvw5Ip3IgWJJk8jSaYtlu3zM63 -Nwf9JtmYhST/WSMDmu2dnajkXjjO11INb9I/bbEFa0nOipFGc/T2L/Coc3cOZayh -jWZSaX5LaAzHHjcng6WMxwLkFM1JAbBzs/3GkDpv0mztO+7skb6iQ12LAEpmJURw -3kAP+HwV96LOPNdeE4yBFxgX0b3xdxA61GU5wSesVywlVP+i2k+KYTlerj1KjL0= ------END CERTIFICATE----- - -# Issuer: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI -# Subject: CN=emSign Root CA - G1 O=eMudhra Technologies Limited OU=emSign PKI -# Label: "emSign Root CA - G1" -# Serial: 235931866688319308814040 -# MD5 Fingerprint: 9c:42:84:57:dd:cb:0b:a7:2e:95:ad:b6:f3:da:bc:ac -# SHA1 Fingerprint: 8a:c7:ad:8f:73:ac:4e:c1:b5:75:4d:a5:40:f4:fc:cf:7c:b5:8e:8c -# SHA256 Fingerprint: 40:f6:af:03:46:a9:9a:a1:cd:1d:55:5a:4e:9c:ce:62:c7:f9:63:46:03:ee:40:66:15:83:3d:c8:c8:d0:03:67 ------BEGIN CERTIFICATE----- -MIIDlDCCAnygAwIBAgIKMfXkYgxsWO3W2DANBgkqhkiG9w0BAQsFADBnMQswCQYD -VQQGEwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBU -ZWNobm9sb2dpZXMgTGltaXRlZDEcMBoGA1UEAxMTZW1TaWduIFJvb3QgQ0EgLSBH -MTAeFw0xODAyMTgxODMwMDBaFw00MzAyMTgxODMwMDBaMGcxCzAJBgNVBAYTAklO -MRMwEQYDVQQLEwplbVNpZ24gUEtJMSUwIwYDVQQKExxlTXVkaHJhIFRlY2hub2xv -Z2llcyBMaW1pdGVkMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEcxMIIBIjAN -BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAk0u76WaK7p1b1TST0Bsew+eeuGQz -f2N4aLTNLnF115sgxk0pvLZoYIr3IZpWNVrzdr3YzZr/k1ZLpVkGoZM0Kd0WNHVO -8oG0x5ZOrRkVUkr+PHB1cM2vK6sVmjM8qrOLqs1D/fXqcP/tzxE7lM5OMhbTI0Aq -d7OvPAEsbO2ZLIvZTmmYsvePQbAyeGHWDV/D+qJAkh1cF+ZwPjXnorfCYuKrpDhM -tTk1b+oDafo6VGiFbdbyL0NVHpENDtjVaqSW0RM8LHhQ6DqS0hdW5TUaQBw+jSzt -Od9C4INBdN+jzcKGYEho42kLVACL5HZpIQ15TjQIXhTCzLG3rdd8cIrHhQIDAQAB -o0IwQDAdBgNVHQ4EFgQU++8Nhp6w492pufEhF38+/PB3KxowDgYDVR0PAQH/BAQD -AgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAFn/8oz1h31x -PaOfG1vR2vjTnGs2vZupYeveFix0PZ7mddrXuqe8QhfnPZHr5X3dPpzxz5KsbEjM -wiI/aTvFthUvozXGaCocV685743QNcMYDHsAVhzNixl03r4PEuDQqqE/AjSxcM6d -GNYIAwlG7mDgfrbESQRRfXBgvKqy/3lyeqYdPV8q+Mri/Tm3R7nrft8EI6/6nAYH -6ftjk4BAtcZsCjEozgyfz7MjNYBBjWzEN3uBL4ChQEKF6dk4jeihU80Bv2noWgby -RQuQ+q7hv53yrlc8pa6yVvSLZUDp/TGBLPQ5Cdjua6e0ph0VpZj3AYHYhX3zUVxx -iN66zB+Afko= ------END CERTIFICATE----- - -# Issuer: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI -# Subject: CN=emSign ECC Root CA - G3 O=eMudhra Technologies Limited OU=emSign PKI -# Label: "emSign ECC Root CA - G3" -# Serial: 287880440101571086945156 -# MD5 Fingerprint: ce:0b:72:d1:9f:88:8e:d0:50:03:e8:e3:b8:8b:67:40 -# SHA1 Fingerprint: 30:43:fa:4f:f2:57:dc:a0:c3:80:ee:2e:58:ea:78:b2:3f:e6:bb:c1 -# SHA256 Fingerprint: 86:a1:ec:ba:08:9c:4a:8d:3b:be:27:34:c6:12:ba:34:1d:81:3e:04:3c:f9:e8:a8:62:cd:5c:57:a3:6b:be:6b ------BEGIN CERTIFICATE----- -MIICTjCCAdOgAwIBAgIKPPYHqWhwDtqLhDAKBggqhkjOPQQDAzBrMQswCQYDVQQG -EwJJTjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNo -bm9sb2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0g -RzMwHhcNMTgwMjE4MTgzMDAwWhcNNDMwMjE4MTgzMDAwWjBrMQswCQYDVQQGEwJJ -TjETMBEGA1UECxMKZW1TaWduIFBLSTElMCMGA1UEChMcZU11ZGhyYSBUZWNobm9s -b2dpZXMgTGltaXRlZDEgMB4GA1UEAxMXZW1TaWduIEVDQyBSb290IENBIC0gRzMw -djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQjpQy4LRL1KPOxst3iAhKAnjlfSU2fySU0 -WXTsuwYc58Byr+iuL+FBVIcUqEqy6HyC5ltqtdyzdc6LBtCGI79G1Y4PPwT01xyS -fvalY8L1X44uT6EYGQIrMgqCZH0Wk9GjQjBAMB0GA1UdDgQWBBR8XQKEE9TMipuB -zhccLikenEhjQjAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggq -hkjOPQQDAwNpADBmAjEAvvNhzwIQHWSVB7gYboiFBS+DCBeQyh+KTOgNG3qxrdWB -CUfvO6wIBHxcmbHtRwfSAjEAnbpV/KlK6O3t5nYBQnvI+GDZjVGLVTv7jHvrZQnD -+JbNR6iC8hZVdyR+EhCVBCyj ------END CERTIFICATE----- - -# Issuer: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI -# Subject: CN=emSign Root CA - C1 O=eMudhra Inc OU=emSign PKI -# Label: "emSign Root CA - C1" -# Serial: 825510296613316004955058 -# MD5 Fingerprint: d8:e3:5d:01:21:fa:78:5a:b0:df:ba:d2:ee:2a:5f:68 -# SHA1 Fingerprint: e7:2e:f1:df:fc:b2:09:28:cf:5d:d4:d5:67:37:b1:51:cb:86:4f:01 -# SHA256 Fingerprint: 12:56:09:aa:30:1d:a0:a2:49:b9:7a:82:39:cb:6a:34:21:6f:44:dc:ac:9f:39:54:b1:42:92:f2:e8:c8:60:8f ------BEGIN CERTIFICATE----- -MIIDczCCAlugAwIBAgILAK7PALrEzzL4Q7IwDQYJKoZIhvcNAQELBQAwVjELMAkG -A1UEBhMCVVMxEzARBgNVBAsTCmVtU2lnbiBQS0kxFDASBgNVBAoTC2VNdWRocmEg -SW5jMRwwGgYDVQQDExNlbVNpZ24gUm9vdCBDQSAtIEMxMB4XDTE4MDIxODE4MzAw -MFoXDTQzMDIxODE4MzAwMFowVjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln -biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMRwwGgYDVQQDExNlbVNpZ24gUm9v -dCBDQSAtIEMxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz+upufGZ -BczYKCFK83M0UYRWEPWgTywS4/oTmifQz/l5GnRfHXk5/Fv4cI7gklL35CX5VIPZ -HdPIWoU/Xse2B+4+wM6ar6xWQio5JXDWv7V7Nq2s9nPczdcdioOl+yuQFTdrHCZH -3DspVpNqs8FqOp099cGXOFgFixwR4+S0uF2FHYP+eF8LRWgYSKVGczQ7/g/IdrvH -GPMF0Ybzhe3nudkyrVWIzqa2kbBPrH4VI5b2P/AgNBbeCsbEBEV5f6f9vtKppa+c -xSMq9zwhbL2vj07FOrLzNBL834AaSaTUqZX3noleoomslMuoaJuvimUnzYnu3Yy1 -aylwQ6BpC+S5DwIDAQABo0IwQDAdBgNVHQ4EFgQU/qHgcB4qAzlSWkK+XJGFehiq -TbUwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL -BQADggEBAMJKVvoVIXsoounlHfv4LcQ5lkFMOycsxGwYFYDGrK9HWS8mC+M2sO87 -/kOXSTKZEhVb3xEp/6tT+LvBeA+snFOvV71ojD1pM/CjoCNjO2RnIkSt1XHLVip4 -kqNPEjE2NuLe/gDEo2APJ62gsIq1NnpSob0n9CAnYuhNlCQT5AoE6TyrLshDCUrG -YQTlSTR+08TI9Q/Aqum6VF7zYytPT1DU/rl7mYw9wC68AivTxEDkigcxHpvOJpkT -+xHqmiIMERnHXhuBUDDIlhJu58tBf5E7oke3VIAb3ADMmpDqw8NQBmIMMMAVSKeo -WXzhriKi4gp6D/piq1JM4fHfyr6DDUI= ------END CERTIFICATE----- - -# Issuer: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI -# Subject: CN=emSign ECC Root CA - C3 O=eMudhra Inc OU=emSign PKI -# Label: "emSign ECC Root CA - C3" -# Serial: 582948710642506000014504 -# MD5 Fingerprint: 3e:53:b3:a3:81:ee:d7:10:f8:d3:b0:1d:17:92:f5:d5 -# SHA1 Fingerprint: b6:af:43:c2:9b:81:53:7d:f6:ef:6b:c3:1f:1f:60:15:0c:ee:48:66 -# SHA256 Fingerprint: bc:4d:80:9b:15:18:9d:78:db:3e:1d:8c:f4:f9:72:6a:79:5d:a1:64:3c:a5:f1:35:8e:1d:db:0e:dc:0d:7e:b3 ------BEGIN CERTIFICATE----- -MIICKzCCAbGgAwIBAgIKe3G2gla4EnycqDAKBggqhkjOPQQDAzBaMQswCQYDVQQG -EwJVUzETMBEGA1UECxMKZW1TaWduIFBLSTEUMBIGA1UEChMLZU11ZGhyYSBJbmMx -IDAeBgNVBAMTF2VtU2lnbiBFQ0MgUm9vdCBDQSAtIEMzMB4XDTE4MDIxODE4MzAw -MFoXDTQzMDIxODE4MzAwMFowWjELMAkGA1UEBhMCVVMxEzARBgNVBAsTCmVtU2ln -biBQS0kxFDASBgNVBAoTC2VNdWRocmEgSW5jMSAwHgYDVQQDExdlbVNpZ24gRUND -IFJvb3QgQ0EgLSBDMzB2MBAGByqGSM49AgEGBSuBBAAiA2IABP2lYa57JhAd6bci -MK4G9IGzsUJxlTm801Ljr6/58pc1kjZGDoeVjbk5Wum739D+yAdBPLtVb4Ojavti -sIGJAnB9SMVK4+kiVCJNk7tCDK93nCOmfddhEc5lx/h//vXyqaNCMEAwHQYDVR0O -BBYEFPtaSNCAIEDyqOkAB2kZd6fmw/TPMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB -Af8EBTADAQH/MAoGCCqGSM49BAMDA2gAMGUCMQC02C8Cif22TGK6Q04ThHK1rt0c -3ta13FaPWEBaLd4gTCKDypOofu4SQMfWh0/434UCMBwUZOR8loMRnLDRWmFLpg9J -0wD8ofzkpf9/rdcw0Md3f76BB1UwUCAU9Vc4CqgxUQ== ------END CERTIFICATE----- - -# Issuer: CN=Hongkong Post Root CA 3 O=Hongkong Post -# Subject: CN=Hongkong Post Root CA 3 O=Hongkong Post -# Label: "Hongkong Post Root CA 3" -# Serial: 46170865288971385588281144162979347873371282084 -# MD5 Fingerprint: 11:fc:9f:bd:73:30:02:8a:fd:3f:f3:58:b9:cb:20:f0 -# SHA1 Fingerprint: 58:a2:d0:ec:20:52:81:5b:c1:f3:f8:64:02:24:4e:c2:8e:02:4b:02 -# SHA256 Fingerprint: 5a:2f:c0:3f:0c:83:b0:90:bb:fa:40:60:4b:09:88:44:6c:76:36:18:3d:f9:84:6e:17:10:1a:44:7f:b8:ef:d6 ------BEGIN CERTIFICATE----- -MIIFzzCCA7egAwIBAgIUCBZfikyl7ADJk0DfxMauI7gcWqQwDQYJKoZIhvcNAQEL -BQAwbzELMAkGA1UEBhMCSEsxEjAQBgNVBAgTCUhvbmcgS29uZzESMBAGA1UEBxMJ -SG9uZyBLb25nMRYwFAYDVQQKEw1Ib25na29uZyBQb3N0MSAwHgYDVQQDExdIb25n -a29uZyBQb3N0IFJvb3QgQ0EgMzAeFw0xNzA2MDMwMjI5NDZaFw00MjA2MDMwMjI5 -NDZaMG8xCzAJBgNVBAYTAkhLMRIwEAYDVQQIEwlIb25nIEtvbmcxEjAQBgNVBAcT -CUhvbmcgS29uZzEWMBQGA1UEChMNSG9uZ2tvbmcgUG9zdDEgMB4GA1UEAxMXSG9u -Z2tvbmcgUG9zdCBSb290IENBIDMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCziNfqzg8gTr7m1gNt7ln8wlffKWihgw4+aMdoWJwcYEuJQwy51BWy7sFO -dem1p+/l6TWZ5Mwc50tfjTMwIDNT2aa71T4Tjukfh0mtUC1Qyhi+AViiE3CWu4mI -VoBc+L0sPOFMV4i707mV78vH9toxdCim5lSJ9UExyuUmGs2C4HDaOym71QP1mbpV -9WTRYA6ziUm4ii8F0oRFKHyPaFASePwLtVPLwpgchKOesL4jpNrcyCse2m5FHomY -2vkALgbpDDtw1VAliJnLzXNg99X/NWfFobxeq81KuEXryGgeDQ0URhLj0mRiikKY -vLTGCAj4/ahMZJx2Ab0vqWwzD9g/KLg8aQFChn5pwckGyuV6RmXpwtZQQS4/t+Tt -bNe/JgERohYpSms0BpDsE9K2+2p20jzt8NYt3eEV7KObLyzJPivkaTv/ciWxNoZb -x39ri1UbSsUgYT2uy1DhCDq+sI9jQVMwCFk8mB13umOResoQUGC/8Ne8lYePl8X+ -l2oBlKN8W4UdKjk60FSh0Tlxnf0h+bV78OLgAo9uliQlLKAeLKjEiafv7ZkGL7YK -TE/bosw3Gq9HhS2KX8Q0NEwA/RiTZxPRN+ZItIsGxVd7GYYKecsAyVKvQv83j+Gj -Hno9UKtjBucVtT+2RTeUN7F+8kjDf8V1/peNRY8apxpyKBpADwIDAQABo2MwYTAP -BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBQXnc0e -i9Y5K3DTXNSguB+wAPzFYTAdBgNVHQ4EFgQUF53NHovWOStw01zUoLgfsAD8xWEw -DQYJKoZIhvcNAQELBQADggIBAFbVe27mIgHSQpsY1Q7XZiNc4/6gx5LS6ZStS6LG -7BJ8dNVI0lkUmcDrudHr9EgwW62nV3OZqdPlt9EuWSRY3GguLmLYauRwCy0gUCCk -MpXRAJi70/33MvJJrsZ64Ee+bs7Lo3I6LWldy8joRTnU+kLBEUx3XZL7av9YROXr -gZ6voJmtvqkBZss4HTzfQx/0TW60uhdG/H39h4F5ag0zD/ov+BS5gLNdTaqX4fnk -GMX41TiMJjz98iji7lpJiCzfeT2OnpA8vUFKOt1b9pq0zj8lMH8yfaIDlNDceqFS -3m6TjRgm/VWsvY+b0s+v54Ysyx8Jb6NvqYTUc79NoXQbTiNg8swOqn+knEwlqLJm -Ozj/2ZQw9nKEvmhVEA/GcywWaZMH/rFF7buiVWqw2rVKAiUnhde3t4ZEFolsgCs+ -l6mc1X5VTMbeRRAc6uk7nwNT7u56AQIWeNTowr5GdogTPyK7SBIdUgC0An4hGh6c -JfTzPV4e0hz5sy229zdcxsshTrD3mUcYhcErulWuBurQB7Lcq9CClnXO0lD+mefP -L5/ndtFhKvshuzHQqp9HpLIiyhY6UFfEW0NnxWViA0kB60PZ2Pierc+xYw5F9KBa -LJstxabArahH9CdMOA0uG0k7UvToiIMrVCjU8jVStDKDYmlkDJGcn5fqdBb9HxEG -mpv0 ------END CERTIFICATE----- - -# Issuer: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only -# Subject: CN=Entrust Root Certification Authority - G4 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2015 Entrust, Inc. - for authorized use only -# Label: "Entrust Root Certification Authority - G4" -# Serial: 289383649854506086828220374796556676440 -# MD5 Fingerprint: 89:53:f1:83:23:b7:7c:8e:05:f1:8c:71:38:4e:1f:88 -# SHA1 Fingerprint: 14:88:4e:86:26:37:b0:26:af:59:62:5c:40:77:ec:35:29:ba:96:01 -# SHA256 Fingerprint: db:35:17:d1:f6:73:2a:2d:5a:b9:7c:53:3e:c7:07:79:ee:32:70:a6:2f:b4:ac:42:38:37:24:60:e6:f0:1e:88 ------BEGIN CERTIFICATE----- -MIIGSzCCBDOgAwIBAgIRANm1Q3+vqTkPAAAAAFVlrVgwDQYJKoZIhvcNAQELBQAw -gb4xCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQL -Ex9TZWUgd3d3LmVudHJ1c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykg -MjAxNSBFbnRydXN0LCBJbmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAw -BgNVBAMTKUVudHJ1c3QgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0 -MB4XDTE1MDUyNzExMTExNloXDTM3MTIyNzExNDExNlowgb4xCzAJBgNVBAYTAlVT -MRYwFAYDVQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1 -c3QubmV0L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxNSBFbnRydXN0LCBJ -bmMuIC0gZm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMjAwBgNVBAMTKUVudHJ1c3Qg -Um9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEc0MIICIjANBgkqhkiG9w0B -AQEFAAOCAg8AMIICCgKCAgEAsewsQu7i0TD/pZJH4i3DumSXbcr3DbVZwbPLqGgZ -2K+EbTBwXX7zLtJTmeH+H17ZSK9dE43b/2MzTdMAArzE+NEGCJR5WIoV3imz/f3E -T+iq4qA7ec2/a0My3dl0ELn39GjUu9CH1apLiipvKgS1sqbHoHrmSKvS0VnM1n4j -5pds8ELl3FFLFUHtSUrJ3hCX1nbB76W1NhSXNdh4IjVS70O92yfbYVaCNNzLiGAM -C1rlLAHGVK/XqsEQe9IFWrhAnoanw5CGAlZSCXqc0ieCU0plUmr1POeo8pyvi73T -DtTUXm6Hnmo9RR3RXRv06QqsYJn7ibT/mCzPfB3pAqoEmh643IhuJbNsZvc8kPNX -wbMv9W3y+8qh+CmdRouzavbmZwe+LGcKKh9asj5XxNMhIWNlUpEbsZmOeX7m640A -2Vqq6nPopIICR5b+W45UYaPrL0swsIsjdXJ8ITzI9vF01Bx7owVV7rtNOzK+mndm -nqxpkCIHH2E6lr7lmk/MBTwoWdPBDFSoWWG9yHJM6Nyfh3+9nEg2XpWjDrk4JFX8 -dWbrAuMINClKxuMrLzOg2qOGpRKX/YAr2hRC45K9PvJdXmd0LhyIRyk0X+IyqJwl -N4y6mACXi0mWHv0liqzc2thddG5msP9E36EYxr5ILzeUePiVSj9/E15dWf10hkNj -c0kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD -VR0OBBYEFJ84xFYjwznooHFs6FRM5Og6sb9nMA0GCSqGSIb3DQEBCwUAA4ICAQAS -5UKme4sPDORGpbZgQIeMJX6tuGguW8ZAdjwD+MlZ9POrYs4QjbRaZIxowLByQzTS -Gwv2LFPSypBLhmb8qoMi9IsabyZIrHZ3CL/FmFz0Jomee8O5ZDIBf9PD3Vht7LGr -hFV0d4QEJ1JrhkzO3bll/9bGXp+aEJlLdWr+aumXIOTkdnrG0CSqkM0gkLpHZPt/ -B7NTeLUKYvJzQ85BK4FqLoUWlFPUa19yIqtRLULVAJyZv967lDtX/Zr1hstWO1uI -AeV8KEsD+UmDfLJ/fOPtjqF/YFOOVZ1QNBIPt5d7bIdKROf1beyAN/BYGW5KaHbw -H5Lk6rWS02FREAutp9lfx1/cH6NcjKF+m7ee01ZvZl4HliDtC3T7Zk6LERXpgUl+ -b7DUUH8i119lAg2m9IUe2K4GS0qn0jFmwvjO5QimpAKWRGhXxNUzzxkvFMSUHHuk -2fCfDrGA4tGeEWSpiBE6doLlYsKA2KSD7ZPvfC+QsDJMlhVoSFLUmQjAJOgc47Ol -IQ6SwJAfzyBfyjs4x7dtOvPmRLgOMWuIjnDrnBdSqEGULoe256YSxXXfW8AKbnuk -5F6G+TaU33fD6Q3AOfF5u0aOq0NZJ7cguyPpVkAh7DE9ZapD8j3fcEThuk0mEDuY -n/PIjhs4ViFqUZPTkcpG2om3PVODLAgfi49T3f+sHw== ------END CERTIFICATE----- - -# Issuer: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation -# Subject: CN=Microsoft ECC Root Certificate Authority 2017 O=Microsoft Corporation -# Label: "Microsoft ECC Root Certificate Authority 2017" -# Serial: 136839042543790627607696632466672567020 -# MD5 Fingerprint: dd:a1:03:e6:4a:93:10:d1:bf:f0:19:42:cb:fe:ed:67 -# SHA1 Fingerprint: 99:9a:64:c3:7f:f4:7d:9f:ab:95:f1:47:69:89:14:60:ee:c4:c3:c5 -# SHA256 Fingerprint: 35:8d:f3:9d:76:4a:f9:e1:b7:66:e9:c9:72:df:35:2e:e1:5c:fa:c2:27:af:6a:d1:d7:0e:8e:4a:6e:dc:ba:02 ------BEGIN CERTIFICATE----- -MIICWTCCAd+gAwIBAgIQZvI9r4fei7FK6gxXMQHC7DAKBggqhkjOPQQDAzBlMQsw -CQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYD -VQQDEy1NaWNyb3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIw -MTcwHhcNMTkxMjE4MjMwNjQ1WhcNNDIwNzE4MjMxNjA0WjBlMQswCQYDVQQGEwJV -UzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1NaWNy -b3NvZnQgRUNDIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwdjAQBgcq -hkjOPQIBBgUrgQQAIgNiAATUvD0CQnVBEyPNgASGAlEvaqiBYgtlzPbKnR5vSmZR -ogPZnZH6thaxjG7efM3beaYvzrvOcS/lpaso7GMEZpn4+vKTEAXhgShC48Zo9OYb -hGBKia/teQ87zvH2RPUBeMCjVDBSMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8E -BTADAQH/MB0GA1UdDgQWBBTIy5lycFIM+Oa+sgRXKSrPQhDtNTAQBgkrBgEEAYI3 -FQEEAwIBADAKBggqhkjOPQQDAwNoADBlAjBY8k3qDPlfXu5gKcs68tvWMoQZP3zV -L8KxzJOuULsJMsbG7X7JNpQS5GiFBqIb0C8CMQCZ6Ra0DvpWSNSkMBaReNtUjGUB -iudQZsIxtzm6uBoiB078a1QWIP8rtedMDE2mT3M= ------END CERTIFICATE----- - -# Issuer: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation -# Subject: CN=Microsoft RSA Root Certificate Authority 2017 O=Microsoft Corporation -# Label: "Microsoft RSA Root Certificate Authority 2017" -# Serial: 40975477897264996090493496164228220339 -# MD5 Fingerprint: 10:ff:00:ff:cf:c9:f8:c7:7a:c0:ee:35:8e:c9:0f:47 -# SHA1 Fingerprint: 73:a5:e6:4a:3b:ff:83:16:ff:0e:dc:cc:61:8a:90:6e:4e:ae:4d:74 -# SHA256 Fingerprint: c7:41:f7:0f:4b:2a:8d:88:bf:2e:71:c1:41:22:ef:53:ef:10:eb:a0:cf:a5:e6:4c:fa:20:f4:18:85:30:73:e0 ------BEGIN CERTIFICATE----- -MIIFqDCCA5CgAwIBAgIQHtOXCV/YtLNHcB6qvn9FszANBgkqhkiG9w0BAQwFADBl -MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYw -NAYDVQQDEy1NaWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 -IDIwMTcwHhcNMTkxMjE4MjI1MTIyWhcNNDIwNzE4MjMwMDIzWjBlMQswCQYDVQQG -EwJVUzEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTYwNAYDVQQDEy1N -aWNyb3NvZnQgUlNBIFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTcwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKW76UM4wplZEWCpW9R2LBifOZ -Nt9GkMml7Xhqb0eRaPgnZ1AzHaGm++DlQ6OEAlcBXZxIQIJTELy/xztokLaCLeX0 -ZdDMbRnMlfl7rEqUrQ7eS0MdhweSE5CAg2Q1OQT85elss7YfUJQ4ZVBcF0a5toW1 -HLUX6NZFndiyJrDKxHBKrmCk3bPZ7Pw71VdyvD/IybLeS2v4I2wDwAW9lcfNcztm -gGTjGqwu+UcF8ga2m3P1eDNbx6H7JyqhtJqRjJHTOoI+dkC0zVJhUXAoP8XFWvLJ -jEm7FFtNyP9nTUwSlq31/niol4fX/V4ggNyhSyL71Imtus5Hl0dVe49FyGcohJUc -aDDv70ngNXtk55iwlNpNhTs+VcQor1fznhPbRiefHqJeRIOkpcrVE7NLP8TjwuaG -YaRSMLl6IE9vDzhTyzMMEyuP1pq9KsgtsRx9S1HKR9FIJ3Jdh+vVReZIZZ2vUpC6 -W6IYZVcSn2i51BVrlMRpIpj0M+Dt+VGOQVDJNE92kKz8OMHY4Xu54+OU4UZpyw4K -UGsTuqwPN1q3ErWQgR5WrlcihtnJ0tHXUeOrO8ZV/R4O03QK0dqq6mm4lyiPSMQH -+FJDOvTKVTUssKZqwJz58oHhEmrARdlns87/I6KJClTUFLkqqNfs+avNJVgyeY+Q -W5g5xAgGwax/Dj0ApQIDAQABo1QwUjAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/ -BAUwAwEB/zAdBgNVHQ4EFgQUCctZf4aycI8awznjwNnpv7tNsiMwEAYJKwYBBAGC -NxUBBAMCAQAwDQYJKoZIhvcNAQEMBQADggIBAKyvPl3CEZaJjqPnktaXFbgToqZC -LgLNFgVZJ8og6Lq46BrsTaiXVq5lQ7GPAJtSzVXNUzltYkyLDVt8LkS/gxCP81OC -gMNPOsduET/m4xaRhPtthH80dK2Jp86519efhGSSvpWhrQlTM93uCupKUY5vVau6 -tZRGrox/2KJQJWVggEbbMwSubLWYdFQl3JPk+ONVFT24bcMKpBLBaYVu32TxU5nh -SnUgnZUP5NbcA/FZGOhHibJXWpS2qdgXKxdJ5XbLwVaZOjex/2kskZGT4d9Mozd2 -TaGf+G0eHdP67Pv0RR0Tbc/3WeUiJ3IrhvNXuzDtJE3cfVa7o7P4NHmJweDyAmH3 -pvwPuxwXC65B2Xy9J6P9LjrRk5Sxcx0ki69bIImtt2dmefU6xqaWM/5TkshGsRGR -xpl/j8nWZjEgQRCHLQzWwa80mMpkg/sTV9HB8Dx6jKXB/ZUhoHHBk2dxEuqPiApp -GWSZI1b7rCoucL5mxAyE7+WL85MB+GqQk2dLsmijtWKP6T+MejteD+eMuMZ87zf9 -dOLITzNy4ZQ5bb0Sr74MTnB8G2+NszKTc0QWbej09+CVgI+WXTik9KveCjCHk9hN -AHFiRSdLOkKEW39lt2c0Ui2cFmuqqNh7o0JMcccMyj6D5KbvtwEwXlGjefVwaaZB -RA+GsCyRxj3qrg+E ------END CERTIFICATE----- - -# Issuer: CN=e-Szigno Root CA 2017 O=Microsec Ltd. -# Subject: CN=e-Szigno Root CA 2017 O=Microsec Ltd. -# Label: "e-Szigno Root CA 2017" -# Serial: 411379200276854331539784714 -# MD5 Fingerprint: de:1f:f6:9e:84:ae:a7:b4:21:ce:1e:58:7d:d1:84:98 -# SHA1 Fingerprint: 89:d4:83:03:4f:9e:9a:48:80:5f:72:37:d4:a9:a6:ef:cb:7c:1f:d1 -# SHA256 Fingerprint: be:b0:0b:30:83:9b:9b:c3:2c:32:e4:44:79:05:95:06:41:f2:64:21:b1:5e:d0:89:19:8b:51:8a:e2:ea:1b:99 ------BEGIN CERTIFICATE----- -MIICQDCCAeWgAwIBAgIMAVRI7yH9l1kN9QQKMAoGCCqGSM49BAMCMHExCzAJBgNV -BAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMgTHRk -LjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25vIFJv -b3QgQ0EgMjAxNzAeFw0xNzA4MjIxMjA3MDZaFw00MjA4MjIxMjA3MDZaMHExCzAJ -BgNVBAYTAkhVMREwDwYDVQQHDAhCdWRhcGVzdDEWMBQGA1UECgwNTWljcm9zZWMg -THRkLjEXMBUGA1UEYQwOVkFUSFUtMjM1ODQ0OTcxHjAcBgNVBAMMFWUtU3ppZ25v -IFJvb3QgQ0EgMjAxNzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJbcPYrYsHtv -xie+RJCxs1YVe45DJH0ahFnuY2iyxl6H0BVIHqiQrb1TotreOpCmYF9oMrWGQd+H -Wyx7xf58etqjYzBhMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0G -A1UdDgQWBBSHERUI0arBeAyxr87GyZDvvzAEwDAfBgNVHSMEGDAWgBSHERUI0arB -eAyxr87GyZDvvzAEwDAKBggqhkjOPQQDAgNJADBGAiEAtVfd14pVCzbhhkT61Nlo -jbjcI4qKDdQvfepz7L9NbKgCIQDLpbQS+ue16M9+k/zzNY9vTlp8tLxOsvxyqltZ -+efcMQ== ------END CERTIFICATE----- - -# Issuer: O=CERTSIGN SA OU=certSIGN ROOT CA G2 -# Subject: O=CERTSIGN SA OU=certSIGN ROOT CA G2 -# Label: "certSIGN Root CA G2" -# Serial: 313609486401300475190 -# MD5 Fingerprint: 8c:f1:75:8a:c6:19:cf:94:b7:f7:65:20:87:c3:97:c7 -# SHA1 Fingerprint: 26:f9:93:b4:ed:3d:28:27:b0:b9:4b:a7:e9:15:1d:a3:8d:92:e5:32 -# SHA256 Fingerprint: 65:7c:fe:2f:a7:3f:aa:38:46:25:71:f3:32:a2:36:3a:46:fc:e7:02:09:51:71:07:02:cd:fb:b6:ee:da:33:05 ------BEGIN CERTIFICATE----- -MIIFRzCCAy+gAwIBAgIJEQA0tk7GNi02MA0GCSqGSIb3DQEBCwUAMEExCzAJBgNV -BAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJR04g -Uk9PVCBDQSBHMjAeFw0xNzAyMDYwOTI3MzVaFw00MjAyMDYwOTI3MzVaMEExCzAJ -BgNVBAYTAlJPMRQwEgYDVQQKEwtDRVJUU0lHTiBTQTEcMBoGA1UECxMTY2VydFNJ -R04gUk9PVCBDQSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMDF -dRmRfUR0dIf+DjuW3NgBFszuY5HnC2/OOwppGnzC46+CjobXXo9X69MhWf05N0Iw -vlDqtg+piNguLWkh59E3GE59kdUWX2tbAMI5Qw02hVK5U2UPHULlj88F0+7cDBrZ -uIt4ImfkabBoxTzkbFpG583H+u/E7Eu9aqSs/cwoUe+StCmrqzWaTOTECMYmzPhp -n+Sc8CnTXPnGFiWeI8MgwT0PPzhAsP6CRDiqWhqKa2NYOLQV07YRaXseVO6MGiKs -cpc/I1mbySKEwQdPzH/iV8oScLumZfNpdWO9lfsbl83kqK/20U6o2YpxJM02PbyW -xPFsqa7lzw1uKA2wDrXKUXt4FMMgL3/7FFXhEZn91QqhngLjYl/rNUssuHLoPj1P -rCy7Lobio3aP5ZMqz6WryFyNSwb/EkaseMsUBzXgqd+L6a8VTxaJW732jcZZroiF -DsGJ6x9nxUWO/203Nit4ZoORUSs9/1F3dmKh7Gc+PoGD4FapUB8fepmrY7+EF3fx -DTvf95xhszWYijqy7DwaNz9+j5LP2RIUZNoQAhVB/0/E6xyjyfqZ90bp4RjZsbgy -LcsUDFDYg2WD7rlcz8sFWkz6GZdr1l0T08JcVLwyc6B49fFtHsufpaafItzRUZ6C -eWRgKRM+o/1Pcmqr4tTluCRVLERLiohEnMqE0yo7AgMBAAGjQjBAMA8GA1UdEwEB -/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBSCIS1mxteg4BXrzkwJ -d8RgnlRuAzANBgkqhkiG9w0BAQsFAAOCAgEAYN4auOfyYILVAzOBywaK8SJJ6ejq -kX/GM15oGQOGO0MBzwdw5AgeZYWR5hEit/UCI46uuR59H35s5r0l1ZUa8gWmr4UC -b6741jH/JclKyMeKqdmfS0mbEVeZkkMR3rYzpMzXjWR91M08KCy0mpbqTfXERMQl -qiCA2ClV9+BB/AYm/7k29UMUA2Z44RGx2iBfRgB4ACGlHgAoYXhvqAEBj500mv/0 -OJD7uNGzcgbJceaBxXntC6Z58hMLnPddDnskk7RI24Zf3lCGeOdA5jGokHZwYa+c -NywRtYK3qq4kNFtyDGkNzVmf9nGvnAvRCjj5BiKDUyUM/FHE5r7iOZULJK2v0ZXk -ltd0ZGtxTgI8qoXzIKNDOXZbbFD+mpwUHmUUihW9o4JFWklWatKcsWMy5WHgUyIO -pwpJ6st+H6jiYoD2EEVSmAYY3qXNL3+q1Ok+CHLsIwMCPKaq2LxndD0UF/tUSxfj -03k9bWtJySgOLnRQvwzZRjoQhsmnP+mg7H/rpXdYaXHmgwo38oZJar55CJD2AhZk -PuXaTH4MNMn5X7azKFGnpyuqSfqNZSlO42sTp5SjLVFteAxEy9/eCG/Oo2Sr05WE -1LlSVHJ7liXMvGnjSG4N0MedJ5qq+BOS3R7fY581qRY27Iy4g/Q9iY/NtBde17MX -QRBdJ3NghVdJIgc= ------END CERTIFICATE----- - -# Issuer: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global Certification Authority" -# Serial: 1846098327275375458322922162 -# MD5 Fingerprint: f8:1c:18:2d:2f:ba:5f:6d:a1:6c:bc:c7:ab:91:c7:0e -# SHA1 Fingerprint: 2f:8f:36:4f:e1:58:97:44:21:59:87:a5:2a:9a:d0:69:95:26:7f:b5 -# SHA256 Fingerprint: 97:55:20:15:f5:dd:fc:3c:87:88:c0:06:94:45:55:40:88:94:45:00:84:f1:00:86:70:86:bc:1a:2b:b5:8d:c8 ------BEGIN CERTIFICATE----- -MIIF2jCCA8KgAwIBAgIMBfcOhtpJ80Y1LrqyMA0GCSqGSIb3DQEBCwUAMIGIMQsw -CQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28x -ITAfBgNVBAoMGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1 -c3R3YXZlIEdsb2JhbCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0xNzA4MjMx -OTM0MTJaFw00MjA4MjMxOTM0MTJaMIGIMQswCQYDVQQGEwJVUzERMA8GA1UECAwI -SWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xITAfBgNVBAoMGFRydXN0d2F2ZSBI -b2xkaW5ncywgSW5jLjExMC8GA1UEAwwoVHJ1c3R3YXZlIEdsb2JhbCBDZXJ0aWZp -Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB -ALldUShLPDeS0YLOvR29zd24q88KPuFd5dyqCblXAj7mY2Hf8g+CY66j96xz0Xzn -swuvCAAJWX/NKSqIk4cXGIDtiLK0thAfLdZfVaITXdHG6wZWiYj+rDKd/VzDBcdu -7oaJuogDnXIhhpCujwOl3J+IKMujkkkP7NAP4m1ET4BqstTnoApTAbqOl5F2brz8 -1Ws25kCI1nsvXwXoLG0R8+eyvpJETNKXpP7ScoFDB5zpET71ixpZfR9oWN0EACyW -80OzfpgZdNmcc9kYvkHHNHnZ9GLCQ7mzJ7Aiy/k9UscwR7PJPrhq4ufogXBeQotP -JqX+OsIgbrv4Fo7NDKm0G2x2EOFYeUY+VM6AqFcJNykbmROPDMjWLBz7BegIlT1l -RtzuzWniTY+HKE40Cz7PFNm73bZQmq131BnW2hqIyE4bJ3XYsgjxroMwuREOzYfw -hI0Vcnyh78zyiGG69Gm7DIwLdVcEuE4qFC49DxweMqZiNu5m4iK4BUBjECLzMx10 -coos9TkpoNPnG4CELcU9402x/RpvumUHO1jsQkUm+9jaJXLE9gCxInm943xZYkqc -BW89zubWR2OZxiRvchLIrH+QtAuRcOi35hYQcRfO3gZPSEF9NUqjifLJS3tBEW1n -twiYTOURGa5CgNz7kAXU+FDKvuStx8KU1xad5hePrzb7AgMBAAGjQjBAMA8GA1Ud -EwEB/wQFMAMBAf8wHQYDVR0OBBYEFJngGWcNYtt2s9o9uFvo/ULSMQ6HMA4GA1Ud -DwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAmHNw4rDT7TnsTGDZqRKGFx6W -0OhUKDtkLSGm+J1WE2pIPU/HPinbbViDVD2HfSMF1OQc3Og4ZYbFdada2zUFvXfe -uyk3QAUHw5RSn8pk3fEbK9xGChACMf1KaA0HZJDmHvUqoai7PF35owgLEQzxPy0Q -lG/+4jSHg9bP5Rs1bdID4bANqKCqRieCNqcVtgimQlRXtpla4gt5kNdXElE1GYhB -aCXUNxeEFfsBctyV3lImIJgm4nb1J2/6ADtKYdkNy1GTKv0WBpanI5ojSP5RvbbE -sLFUzt5sQa0WZ37b/TjNuThOssFgy50X31ieemKyJo90lZvkWx3SD92YHJtZuSPT -MaCm/zjdzyBP6VhWOmfD0faZmZ26NraAL4hHT4a/RDqA5Dccprrql5gR0IRiR2Qe -qu5AvzSxnI9O4fKSTx+O856X3vOmeWqJcU9LJxdI/uz0UA9PSX3MReO9ekDFQdxh -VicGaeVyQYHTtgGJoC86cnn+OjC/QezHYj6RS8fZMXZC+fc8Y+wmjHMMfRod6qh8 -h6jCJ3zhM0EPz8/8AKAigJ5Kp28AsEFFtyLKaEjFQqKu3R3y4G5OBVixwJAWKqQ9 -EEC+j2Jjg6mcgn0tAumDMHzLJ8n9HmYAsC7TIS+OMxZsmO0QqAfWzJPP29FpHOTK -yeC2nOnOcXHebD8WpHk= ------END CERTIFICATE----- - -# Issuer: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global ECC P256 Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global ECC P256 Certification Authority" -# Serial: 4151900041497450638097112925 -# MD5 Fingerprint: 5b:44:e3:8d:5d:36:86:26:e8:0d:05:d2:59:a7:83:54 -# SHA1 Fingerprint: b4:90:82:dd:45:0c:be:8b:5b:b1:66:d3:e2:a4:08:26:cd:ed:42:cf -# SHA256 Fingerprint: 94:5b:bc:82:5e:a5:54:f4:89:d1:fd:51:a7:3d:df:2e:a6:24:ac:70:19:a0:52:05:22:5c:22:a7:8c:cf:a8:b4 ------BEGIN CERTIFICATE----- -MIICYDCCAgegAwIBAgIMDWpfCD8oXD5Rld9dMAoGCCqGSM49BAMCMIGRMQswCQYD -VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf -BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 -YXZlIEdsb2JhbCBFQ0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x -NzA4MjMxOTM1MTBaFw00MjA4MjMxOTM1MTBaMIGRMQswCQYDVQQGEwJVUzERMA8G -A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 -d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF -Q0MgUDI1NiBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTBZMBMGByqGSM49AgEGCCqG -SM49AwEHA0IABH77bOYj43MyCMpg5lOcunSNGLB4kFKA3TjASh3RqMyTpJcGOMoN -FWLGjgEqZZ2q3zSRLoHB5DOSMcT9CTqmP62jQzBBMA8GA1UdEwEB/wQFMAMBAf8w -DwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUo0EGrJBt0UrrdaVKEJmzsaGLSvcw -CgYIKoZIzj0EAwIDRwAwRAIgB+ZU2g6gWrKuEZ+Hxbb/ad4lvvigtwjzRM4q3wgh -DDcCIC0mA6AFvWvR9lz4ZcyGbbOcNEhjhAnFjXca4syc4XR7 ------END CERTIFICATE----- - -# Issuer: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. -# Subject: CN=Trustwave Global ECC P384 Certification Authority O=Trustwave Holdings, Inc. -# Label: "Trustwave Global ECC P384 Certification Authority" -# Serial: 2704997926503831671788816187 -# MD5 Fingerprint: ea:cf:60:c4:3b:b9:15:29:40:a1:97:ed:78:27:93:d6 -# SHA1 Fingerprint: e7:f3:a3:c8:cf:6f:c3:04:2e:6d:0e:67:32:c5:9e:68:95:0d:5e:d2 -# SHA256 Fingerprint: 55:90:38:59:c8:c0:c3:eb:b8:75:9e:ce:4e:25:57:22:5f:f5:75:8b:bd:38:eb:d4:82:76:60:1e:1b:d5:80:97 ------BEGIN CERTIFICATE----- -MIICnTCCAiSgAwIBAgIMCL2Fl2yZJ6SAaEc7MAoGCCqGSM49BAMDMIGRMQswCQYD -VQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAf -BgNVBAoTGFRydXN0d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3 -YXZlIEdsb2JhbCBFQ0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0x -NzA4MjMxOTM2NDNaFw00MjA4MjMxOTM2NDNaMIGRMQswCQYDVQQGEwJVUzERMA8G -A1UECBMISWxsaW5vaXMxEDAOBgNVBAcTB0NoaWNhZ28xITAfBgNVBAoTGFRydXN0 -d2F2ZSBIb2xkaW5ncywgSW5jLjE6MDgGA1UEAxMxVHJ1c3R3YXZlIEdsb2JhbCBF -Q0MgUDM4NCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTB2MBAGByqGSM49AgEGBSuB -BAAiA2IABGvaDXU1CDFHBa5FmVXxERMuSvgQMSOjfoPTfygIOiYaOs+Xgh+AtycJ -j9GOMMQKmw6sWASr9zZ9lCOkmwqKi6vr/TklZvFe/oyujUF5nQlgziip04pt89ZF -1PKYhDhloKNDMEEwDwYDVR0TAQH/BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwYAMB0G -A1UdDgQWBBRVqYSJ0sEyvRjLbKYHTsjnnb6CkDAKBggqhkjOPQQDAwNnADBkAjA3 -AZKXRRJ+oPM+rRk6ct30UJMDEr5E0k9BpIycnR+j9sKS50gU/k6bpZFXrsY3crsC -MGclCrEMXu6pY5Jv5ZAL/mYiykf9ijH3g/56vxC+GCsej/YpHpRZ744hN8tRmKVu -Sw== ------END CERTIFICATE----- - -# Issuer: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. -# Subject: CN=NAVER Global Root Certification Authority O=NAVER BUSINESS PLATFORM Corp. -# Label: "NAVER Global Root Certification Authority" -# Serial: 9013692873798656336226253319739695165984492813 -# MD5 Fingerprint: c8:7e:41:f6:25:3b:f5:09:b3:17:e8:46:3d:bf:d0:9b -# SHA1 Fingerprint: 8f:6b:f2:a9:27:4a:da:14:a0:c4:f4:8e:61:27:f9:c0:1e:78:5d:d1 -# SHA256 Fingerprint: 88:f4:38:dc:f8:ff:d1:fa:8f:42:91:15:ff:e5:f8:2a:e1:e0:6e:0c:70:c3:75:fa:ad:71:7b:34:a4:9e:72:65 ------BEGIN CERTIFICATE----- -MIIFojCCA4qgAwIBAgIUAZQwHqIL3fXFMyqxQ0Rx+NZQTQ0wDQYJKoZIhvcNAQEM -BQAwaTELMAkGA1UEBhMCS1IxJjAkBgNVBAoMHU5BVkVSIEJVU0lORVNTIFBMQVRG -T1JNIENvcnAuMTIwMAYDVQQDDClOQVZFUiBHbG9iYWwgUm9vdCBDZXJ0aWZpY2F0 -aW9uIEF1dGhvcml0eTAeFw0xNzA4MTgwODU4NDJaFw0zNzA4MTgyMzU5NTlaMGkx -CzAJBgNVBAYTAktSMSYwJAYDVQQKDB1OQVZFUiBCVVNJTkVTUyBQTEFURk9STSBD -b3JwLjEyMDAGA1UEAwwpTkFWRVIgR2xvYmFsIFJvb3QgQ2VydGlmaWNhdGlvbiBB -dXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC21PGTXLVA -iQqrDZBbUGOukJR0F0Vy1ntlWilLp1agS7gvQnXp2XskWjFlqxcX0TM62RHcQDaH -38dq6SZeWYp34+hInDEW+j6RscrJo+KfziFTowI2MMtSAuXaMl3Dxeb57hHHi8lE -HoSTGEq0n+USZGnQJoViAbbJAh2+g1G7XNr4rRVqmfeSVPc0W+m/6imBEtRTkZaz -kVrd/pBzKPswRrXKCAfHcXLJZtM0l/aM9BhK4dA9WkW2aacp+yPOiNgSnABIqKYP -szuSjXEOdMWLyEz59JuOuDxp7W87UC9Y7cSw0BwbagzivESq2M0UXZR4Yb8Obtoq -vC8MC3GmsxY/nOb5zJ9TNeIDoKAYv7vxvvTWjIcNQvcGufFt7QSUqP620wbGQGHf -nZ3zVHbOUzoBppJB7ASjjw2i1QnK1sua8e9DXcCrpUHPXFNwcMmIpi3Ua2FzUCaG -YQ5fG8Ir4ozVu53BA0K6lNpfqbDKzE0K70dpAy8i+/Eozr9dUGWokG2zdLAIx6yo -0es+nPxdGoMuK8u180SdOqcXYZaicdNwlhVNt0xz7hlcxVs+Qf6sdWA7G2POAN3a -CJBitOUt7kinaxeZVL6HSuOpXgRM6xBtVNbv8ejyYhbLgGvtPe31HzClrkvJE+2K -AQHJuFFYwGY6sWZLxNUxAmLpdIQM201GLQIDAQABo0IwQDAdBgNVHQ4EFgQU0p+I -36HNLL3s9TsBAZMzJ7LrYEswDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMB -Af8wDQYJKoZIhvcNAQEMBQADggIBADLKgLOdPVQG3dLSLvCkASELZ0jKbY7gyKoN -qo0hV4/GPnrK21HUUrPUloSlWGB/5QuOH/XcChWB5Tu2tyIvCZwTFrFsDDUIbatj -cu3cvuzHV+YwIHHW1xDBE1UBjCpD5EHxzzp6U5LOogMFDTjfArsQLtk70pt6wKGm -+LUx5vR1yblTmXVHIloUFcd4G7ad6Qz4G3bxhYTeodoS76TiEJd6eN4MUZeoIUCL -hr0N8F5OSza7OyAfikJW4Qsav3vQIkMsRIz75Sq0bBwcupTgE34h5prCy8VCZLQe -lHsIJchxzIdFV4XTnyliIoNRlwAYl3dqmJLJfGBs32x9SuRwTMKeuB330DTHD8z7 -p/8Dvq1wkNoL3chtl1+afwkyQf3NosxabUzyqkn+Zvjp2DXrDige7kgvOtB5CTh8 -piKCk5XQA76+AqAF3SAi428diDRgxuYKuQl1C/AH6GmWNcf7I4GOODm4RStDeKLR -LBT/DShycpWbXgnbiUSYqqFJu3FS8r/2/yehNq+4tneI3TqkbZs0kNwUXTC/t+sX -5Ie3cdCh13cV1ELX8vMxmV2b3RZtP+oGI/hGoiLtk/bdmuYqh7GYVPEi92tF4+KO -dh2ajcQGjTa3FPOdVGm3jjzVpG2Tgbet9r1ke8LJaDmgkpzNNIaRkPpkUZ3+/uul -9XXeifdy ------END CERTIFICATE----- - -# Issuer: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres -# Subject: CN=AC RAIZ FNMT-RCM SERVIDORES SEGUROS O=FNMT-RCM OU=Ceres -# Label: "AC RAIZ FNMT-RCM SERVIDORES SEGUROS" -# Serial: 131542671362353147877283741781055151509 -# MD5 Fingerprint: 19:36:9c:52:03:2f:d2:d1:bb:23:cc:dd:1e:12:55:bb -# SHA1 Fingerprint: 62:ff:d9:9e:c0:65:0d:03:ce:75:93:d2:ed:3f:2d:32:c9:e3:e5:4a -# SHA256 Fingerprint: 55:41:53:b1:3d:2c:f9:dd:b7:53:bf:be:1a:4e:0a:e0:8d:0a:a4:18:70:58:fe:60:a2:b8:62:b2:e4:b8:7b:cb ------BEGIN CERTIFICATE----- -MIICbjCCAfOgAwIBAgIQYvYybOXE42hcG2LdnC6dlTAKBggqhkjOPQQDAzB4MQsw -CQYDVQQGEwJFUzERMA8GA1UECgwIRk5NVC1SQ00xDjAMBgNVBAsMBUNlcmVzMRgw -FgYDVQRhDA9WQVRFUy1RMjgyNjAwNEoxLDAqBgNVBAMMI0FDIFJBSVogRk5NVC1S -Q00gU0VSVklET1JFUyBTRUdVUk9TMB4XDTE4MTIyMDA5MzczM1oXDTQzMTIyMDA5 -MzczM1oweDELMAkGA1UEBhMCRVMxETAPBgNVBAoMCEZOTVQtUkNNMQ4wDAYDVQQL -DAVDZXJlczEYMBYGA1UEYQwPVkFURVMtUTI4MjYwMDRKMSwwKgYDVQQDDCNBQyBS -QUlaIEZOTVQtUkNNIFNFUlZJRE9SRVMgU0VHVVJPUzB2MBAGByqGSM49AgEGBSuB -BAAiA2IABPa6V1PIyqvfNkpSIeSX0oNnnvBlUdBeh8dHsVnyV0ebAAKTRBdp20LH -sbI6GA60XYyzZl2hNPk2LEnb80b8s0RpRBNm/dfF/a82Tc4DTQdxz69qBdKiQ1oK -Um8BA06Oi6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD -VR0OBBYEFAG5L++/EYZg8k/QQW6rcx/n0m5JMAoGCCqGSM49BAMDA2kAMGYCMQCu -SuMrQMN0EfKVrRYj3k4MGuZdpSRea0R7/DjiT8ucRRcRTBQnJlU5dUoDzBOQn5IC -MQD6SmxgiHPz7riYYqnOK8LZiqZwMR2vsJRM60/G49HzYqc8/5MuB1xJAWdpEgJy -v+c= ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign Root R46 O=GlobalSign nv-sa -# Subject: CN=GlobalSign Root R46 O=GlobalSign nv-sa -# Label: "GlobalSign Root R46" -# Serial: 1552617688466950547958867513931858518042577 -# MD5 Fingerprint: c4:14:30:e4:fa:66:43:94:2a:6a:1b:24:5f:19:d0:ef -# SHA1 Fingerprint: 53:a2:b0:4b:ca:6b:d6:45:e6:39:8a:8e:c4:0d:d2:bf:77:c3:a2:90 -# SHA256 Fingerprint: 4f:a3:12:6d:8d:3a:11:d1:c4:85:5a:4f:80:7c:ba:d6:cf:91:9d:3a:5a:88:b0:3b:ea:2c:63:72:d9:3c:40:c9 ------BEGIN CERTIFICATE----- -MIIFWjCCA0KgAwIBAgISEdK7udcjGJ5AXwqdLdDfJWfRMA0GCSqGSIb3DQEBDAUA -MEYxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYD -VQQDExNHbG9iYWxTaWduIFJvb3QgUjQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMy -MDAwMDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYt -c2ExHDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBSNDYwggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQCsrHQy6LNl5brtQyYdpokNRbopiLKkHWPd08EsCVeJ -OaFV6Wc0dwxu5FUdUiXSE2te4R2pt32JMl8Nnp8semNgQB+msLZ4j5lUlghYruQG -vGIFAha/r6gjA7aUD7xubMLL1aa7DOn2wQL7Id5m3RerdELv8HQvJfTqa1VbkNud -316HCkD7rRlr+/fKYIje2sGP1q7Vf9Q8g+7XFkyDRTNrJ9CG0Bwta/OrffGFqfUo -0q3v84RLHIf8E6M6cqJaESvWJ3En7YEtbWaBkoe0G1h6zD8K+kZPTXhc+CtI4wSE -y132tGqzZfxCnlEmIyDLPRT5ge1lFgBPGmSXZgjPjHvjK8Cd+RTyG/FWaha/LIWF -zXg4mutCagI0GIMXTpRW+LaCtfOW3T3zvn8gdz57GSNrLNRyc0NXfeD412lPFzYE -+cCQYDdF3uYM2HSNrpyibXRdQr4G9dlkbgIQrImwTDsHTUB+JMWKmIJ5jqSngiCN -I/onccnfxkF0oE32kRbcRoxfKWMxWXEM2G/CtjJ9++ZdU6Z+Ffy7dXxd7Pj2Fxzs -x2sZy/N78CsHpdlseVR2bJ0cpm4O6XkMqCNqo98bMDGfsVR7/mrLZqrcZdCinkqa -ByFrgY/bxFn63iLABJzjqls2k+g9vXqhnQt2sQvHnf3PmKgGwvgqo6GDoLclcqUC -4wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQUA1yrc4GHqMywptWU4jaWSf8FmSwwDQYJKoZIhvcNAQEMBQADggIBAHx4 -7PYCLLtbfpIrXTncvtgdokIzTfnvpCo7RGkerNlFo048p9gkUbJUHJNOxO97k4Vg -JuoJSOD1u8fpaNK7ajFxzHmuEajwmf3lH7wvqMxX63bEIaZHU1VNaL8FpO7XJqti -2kM3S+LGteWygxk6x9PbTZ4IevPuzz5i+6zoYMzRx6Fcg0XERczzF2sUyQQCPtIk -pnnpHs6i58FZFZ8d4kuaPp92CC1r2LpXFNqD6v6MVenQTqnMdzGxRBF6XLE+0xRF -FRhiJBPSy03OXIPBNvIQtQ6IbbjhVp+J3pZmOUdkLG5NrmJ7v2B0GbhWrJKsFjLt -rWhV/pi60zTe9Mlhww6G9kuEYO4Ne7UyWHmRVSyBQ7N0H3qqJZ4d16GLuc1CLgSk -ZoNNiTW2bKg2SnkheCLQQrzRQDGQob4Ez8pn7fXwgNNgyYMqIgXQBztSvwyeqiv5 -u+YfjyW6hY0XHgL+XVAEV8/+LbzvXMAaq7afJMbfc2hIkCwU9D9SGuTSyxTDYWnP -4vkYxboznxSjBF25cfe1lNj2M8FawTSLfJvdkzrnE6JwYZ+vj+vYxXX4M2bUdGc6 -N3ec592kD3ZDZopD8p/7DEJ4Y9HiD2971KE9dJeFt0g5QdYg/NA6s/rob8SKunE3 -vouXsXgxT7PntgMTzlSdriVZzH81Xwj3QEUxeCp6 ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign Root E46 O=GlobalSign nv-sa -# Subject: CN=GlobalSign Root E46 O=GlobalSign nv-sa -# Label: "GlobalSign Root E46" -# Serial: 1552617690338932563915843282459653771421763 -# MD5 Fingerprint: b5:b8:66:ed:de:08:83:e3:c9:e2:01:34:06:ac:51:6f -# SHA1 Fingerprint: 39:b4:6c:d5:fe:80:06:eb:e2:2f:4a:bb:08:33:a0:af:db:b9:dd:84 -# SHA256 Fingerprint: cb:b9:c4:4d:84:b8:04:3e:10:50:ea:31:a6:9f:51:49:55:d7:bf:d2:e2:c6:b4:93:01:01:9a:d6:1d:9f:50:58 ------BEGIN CERTIFICATE----- -MIICCzCCAZGgAwIBAgISEdK7ujNu1LzmJGjFDYQdmOhDMAoGCCqGSM49BAMDMEYx -CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMRwwGgYDVQQD -ExNHbG9iYWxTaWduIFJvb3QgRTQ2MB4XDTE5MDMyMDAwMDAwMFoXDTQ2MDMyMDAw -MDAwMFowRjELMAkGA1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2Ex -HDAaBgNVBAMTE0dsb2JhbFNpZ24gUm9vdCBFNDYwdjAQBgcqhkjOPQIBBgUrgQQA -IgNiAAScDrHPt+ieUnd1NPqlRqetMhkytAepJ8qUuwzSChDH2omwlwxwEwkBjtjq -R+q+soArzfwoDdusvKSGN+1wCAB16pMLey5SnCNoIwZD7JIvU4Tb+0cUB+hflGdd -yXqBPCCjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud -DgQWBBQxCpCPtsad0kRLgLWi5h+xEk8blTAKBggqhkjOPQQDAwNoADBlAjEA31SQ -7Zvvi5QCkxeCmb6zniz2C5GMn0oUsfZkvLtoURMMA/cVi4RguYv/Uo7njLwcAjA8 -+RHUjE7AwWHCFUyqqx0LMV87HOIAl0Qx5v5zli/altP+CAezNIm8BZ/3Hobui3A= ------END CERTIFICATE----- - -# Issuer: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH -# Subject: CN=GLOBALTRUST 2020 O=e-commerce monitoring GmbH -# Label: "GLOBALTRUST 2020" -# Serial: 109160994242082918454945253 -# MD5 Fingerprint: 8a:c7:6f:cb:6d:e3:cc:a2:f1:7c:83:fa:0e:78:d7:e8 -# SHA1 Fingerprint: d0:67:c1:13:51:01:0c:aa:d0:c7:6a:65:37:31:16:26:4f:53:71:a2 -# SHA256 Fingerprint: 9a:29:6a:51:82:d1:d4:51:a2:e3:7f:43:9b:74:da:af:a2:67:52:33:29:f9:0f:9a:0d:20:07:c3:34:e2:3c:9a ------BEGIN CERTIFICATE----- -MIIFgjCCA2qgAwIBAgILWku9WvtPilv6ZeUwDQYJKoZIhvcNAQELBQAwTTELMAkG -A1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9uaXRvcmluZyBHbWJIMRkw -FwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMB4XDTIwMDIxMDAwMDAwMFoXDTQwMDYx -MDAwMDAwMFowTTELMAkGA1UEBhMCQVQxIzAhBgNVBAoTGmUtY29tbWVyY2UgbW9u -aXRvcmluZyBHbWJIMRkwFwYDVQQDExBHTE9CQUxUUlVTVCAyMDIwMIICIjANBgkq -hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAri5WrRsc7/aVj6B3GyvTY4+ETUWiD59b -RatZe1E0+eyLinjF3WuvvcTfk0Uev5E4C64OFudBc/jbu9G4UeDLgztzOG53ig9Z -YybNpyrOVPu44sB8R85gfD+yc/LAGbaKkoc1DZAoouQVBGM+uq/ufF7MpotQsjj3 -QWPKzv9pj2gOlTblzLmMCcpL3TGQlsjMH/1WljTbjhzqLL6FLmPdqqmV0/0plRPw -yJiT2S0WR5ARg6I6IqIoV6Lr/sCMKKCmfecqQjuCgGOlYx8ZzHyyZqjC0203b+J+ -BlHZRYQfEs4kUmSFC0iAToexIiIwquuuvuAC4EDosEKAA1GqtH6qRNdDYfOiaxaJ -SaSjpCuKAsR49GiKweR6NrFvG5Ybd0mN1MkGco/PU+PcF4UgStyYJ9ORJitHHmkH -r96i5OTUawuzXnzUJIBHKWk7buis/UDr2O1xcSvy6Fgd60GXIsUf1DnQJ4+H4xj0 -4KlGDfV0OoIu0G4skaMxXDtG6nsEEFZegB31pWXogvziB4xiRfUg3kZwhqG8k9Me -dKZssCz3AwyIDMvUclOGvGBG85hqwvG/Q/lwIHfKN0F5VVJjjVsSn8VoxIidrPIw -q7ejMZdnrY8XD2zHc+0klGvIg5rQmjdJBKuxFshsSUktq6HQjJLyQUp5ISXbY9e2 -nKd+Qmn7OmMCAwEAAaNjMGEwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC -AQYwHQYDVR0OBBYEFNwuH9FhN3nkq9XVsxJxaD1qaJwiMB8GA1UdIwQYMBaAFNwu -H9FhN3nkq9XVsxJxaD1qaJwiMA0GCSqGSIb3DQEBCwUAA4ICAQCR8EICaEDuw2jA -VC/f7GLDw56KoDEoqoOOpFaWEhCGVrqXctJUMHytGdUdaG/7FELYjQ7ztdGl4wJC -XtzoRlgHNQIw4Lx0SsFDKv/bGtCwr2zD/cuz9X9tAy5ZVp0tLTWMstZDFyySCstd -6IwPS3BD0IL/qMy/pJTAvoe9iuOTe8aPmxadJ2W8esVCgmxcB9CpwYhgROmYhRZf -+I/KARDOJcP5YBugxZfD0yyIMaK9MOzQ0MAS8cE54+X1+NZK3TTN+2/BT+MAi1bi -kvcoskJ3ciNnxz8RFbLEAwW+uxF7Cr+obuf/WEPPm2eggAe2HcqtbepBEX4tdJP7 -wry+UUTF72glJ4DjyKDUEuzZpTcdN3y0kcra1LGWge9oXHYQSa9+pTeAsRxSvTOB -TI/53WXZFM2KJVj04sWDpQmQ1GwUY7VA3+vA/MRYfg0UFodUJ25W5HCEuGwyEn6C -MUO+1918oa2u1qsgEu8KwxCMSZY13At1XrFP1U80DhEgB3VDRemjEdqso5nCtnkn -4rnvyOL2NSl6dPrFf4IFYqYK6miyeUcGbvJXqBUzxvd4Sj1Ce2t+/vdG6tHrju+I -aFvowdlxfv1k7/9nR4hYJS8+hge9+6jlgqispdNpQ80xiEmEU5LAsTkbOYMBMMTy -qfrQA71yN2BWHzZ8vTmR9W0Nv3vXkg== ------END CERTIFICATE----- - -# Issuer: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz -# Subject: CN=ANF Secure Server Root CA O=ANF Autoridad de Certificacion OU=ANF CA Raiz -# Label: "ANF Secure Server Root CA" -# Serial: 996390341000653745 -# MD5 Fingerprint: 26:a6:44:5a:d9:af:4e:2f:b2:1d:b6:65:b0:4e:e8:96 -# SHA1 Fingerprint: 5b:6e:68:d0:cc:15:b6:a0:5f:1e:c1:5f:ae:02:fc:6b:2f:5d:6f:74 -# SHA256 Fingerprint: fb:8f:ec:75:91:69:b9:10:6b:1e:51:16:44:c6:18:c5:13:04:37:3f:6c:06:43:08:8d:8b:ef:fd:1b:99:75:99 ------BEGIN CERTIFICATE----- -MIIF7zCCA9egAwIBAgIIDdPjvGz5a7EwDQYJKoZIhvcNAQELBQAwgYQxEjAQBgNV -BAUTCUc2MzI4NzUxMDELMAkGA1UEBhMCRVMxJzAlBgNVBAoTHkFORiBBdXRvcmlk -YWQgZGUgQ2VydGlmaWNhY2lvbjEUMBIGA1UECxMLQU5GIENBIFJhaXoxIjAgBgNV -BAMTGUFORiBTZWN1cmUgU2VydmVyIFJvb3QgQ0EwHhcNMTkwOTA0MTAwMDM4WhcN -MzkwODMwMTAwMDM4WjCBhDESMBAGA1UEBRMJRzYzMjg3NTEwMQswCQYDVQQGEwJF -UzEnMCUGA1UEChMeQU5GIEF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uMRQwEgYD -VQQLEwtBTkYgQ0EgUmFpejEiMCAGA1UEAxMZQU5GIFNlY3VyZSBTZXJ2ZXIgUm9v -dCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANvrayvmZFSVgpCj -cqQZAZ2cC4Ffc0m6p6zzBE57lgvsEeBbphzOG9INgxwruJ4dfkUyYA8H6XdYfp9q -yGFOtibBTI3/TO80sh9l2Ll49a2pcbnvT1gdpd50IJeh7WhM3pIXS7yr/2WanvtH -2Vdy8wmhrnZEE26cLUQ5vPnHO6RYPUG9tMJJo8gN0pcvB2VSAKduyK9o7PQUlrZX -H1bDOZ8rbeTzPvY1ZNoMHKGESy9LS+IsJJ1tk0DrtSOOMspvRdOoiXsezx76W0OL -zc2oD2rKDF65nkeP8Nm2CgtYZRczuSPkdxl9y0oukntPLxB3sY0vaJxizOBQ+OyR -p1RMVwnVdmPF6GUe7m1qzwmd+nxPrWAI/VaZDxUse6mAq4xhj0oHdkLePfTdsiQz -W7i1o0TJrH93PB0j7IKppuLIBkwC/qxcmZkLLxCKpvR/1Yd0DVlJRfbwcVw5Kda/ -SiOL9V8BY9KHcyi1Swr1+KuCLH5zJTIdC2MKF4EA/7Z2Xue0sUDKIbvVgFHlSFJn -LNJhiQcND85Cd8BEc5xEUKDbEAotlRyBr+Qc5RQe8TZBAQIvfXOn3kLMTOmJDVb3 -n5HUA8ZsyY/b2BzgQJhdZpmYgG4t/wHFzstGH6wCxkPmrqKEPMVOHj1tyRRM4y5B -u8o5vzY8KhmqQYdOpc5LMnndkEl/AgMBAAGjYzBhMB8GA1UdIwQYMBaAFJxf0Gxj -o1+TypOYCK2Mh6UsXME3MB0GA1UdDgQWBBScX9BsY6Nfk8qTmAitjIelLFzBNzAO -BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOC -AgEATh65isagmD9uw2nAalxJUqzLK114OMHVVISfk/CHGT0sZonrDUL8zPB1hT+L -9IBdeeUXZ701guLyPI59WzbLWoAAKfLOKyzxj6ptBZNscsdW699QIyjlRRA96Gej -rw5VD5AJYu9LWaL2U/HANeQvwSS9eS9OICI7/RogsKQOLHDtdD+4E5UGUcjohybK -pFtqFiGS3XNgnhAY3jyB6ugYw3yJ8otQPr0R4hUDqDZ9MwFsSBXXiJCZBMXM5gf0 -vPSQ7RPi6ovDj6MzD8EpTBNO2hVWcXNyglD2mjN8orGoGjR0ZVzO0eurU+AagNjq -OknkJjCb5RyKqKkVMoaZkgoQI1YS4PbOTOK7vtuNknMBZi9iPrJyJ0U27U1W45eZ -/zo1PqVUSlJZS2Db7v54EX9K3BR5YLZrZAPbFYPhor72I5dQ8AkzNqdxliXzuUJ9 -2zg/LFis6ELhDtjTO0wugumDLmsx2d1Hhk9tl5EuT+IocTUW0fJz/iUrB0ckYyfI -+PbZa/wSMVYIwFNCr5zQM378BvAxRAMU8Vjq8moNqRGyg77FGr8H6lnco4g175x2 -MjxNBiLOFeXdntiP2t7SxDnlF4HPOEfrf4htWRvfn0IUrn7PqLBmZdo3r5+qPeoo -tt7VMVgWglvquxl1AnMaykgaIZOQCo6ThKd9OyMYkomgjaw= ------END CERTIFICATE----- - -# Issuer: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority -# Subject: CN=Certum EC-384 CA O=Asseco Data Systems S.A. OU=Certum Certification Authority -# Label: "Certum EC-384 CA" -# Serial: 160250656287871593594747141429395092468 -# MD5 Fingerprint: b6:65:b3:96:60:97:12:a1:ec:4e:e1:3d:a3:c6:c9:f1 -# SHA1 Fingerprint: f3:3e:78:3c:ac:df:f4:a2:cc:ac:67:55:69:56:d7:e5:16:3c:e1:ed -# SHA256 Fingerprint: 6b:32:80:85:62:53:18:aa:50:d1:73:c9:8d:8b:da:09:d5:7e:27:41:3d:11:4c:f7:87:a0:f5:d0:6c:03:0c:f6 ------BEGIN CERTIFICATE----- -MIICZTCCAeugAwIBAgIQeI8nXIESUiClBNAt3bpz9DAKBggqhkjOPQQDAzB0MQsw -CQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEuMScw -JQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAXBgNVBAMT -EENlcnR1bSBFQy0zODQgQ0EwHhcNMTgwMzI2MDcyNDU0WhcNNDMwMzI2MDcyNDU0 -WjB0MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBT -LkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxGTAX -BgNVBAMTEENlcnR1bSBFQy0zODQgQ0EwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATE -KI6rGFtqvm5kN2PkzeyrOvfMobgOgknXhimfoZTy42B4mIF4Bk3y7JoOV2CDn7Tm -Fy8as10CW4kjPMIRBSqniBMY81CE1700LCeJVf/OTOffph8oxPBUw7l8t1Ot68Kj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI0GZnQkdjrzife81r1HfS+8 -EF9LMA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNoADBlAjADVS2m5hjEfO/J -UG7BJw+ch69u1RsIGL2SKcHvlJF40jocVYli5RsJHrpka/F2tNQCMQC0QoSZ/6vn -nvuRlydd3LBbMHHOXjgaatkl5+r3YZJW+OraNsKHZZYuciUvf9/DE8k= ------END CERTIFICATE----- - -# Issuer: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority -# Subject: CN=Certum Trusted Root CA O=Asseco Data Systems S.A. OU=Certum Certification Authority -# Label: "Certum Trusted Root CA" -# Serial: 40870380103424195783807378461123655149 -# MD5 Fingerprint: 51:e1:c2:e7:fe:4c:84:af:59:0e:2f:f4:54:6f:ea:29 -# SHA1 Fingerprint: c8:83:44:c0:18:ae:9f:cc:f1:87:b7:8f:22:d1:c5:d7:45:84:ba:e5 -# SHA256 Fingerprint: fe:76:96:57:38:55:77:3e:37:a9:5e:7a:d4:d9:cc:96:c3:01:57:c1:5d:31:76:5b:a9:b1:57:04:e1:ae:78:fd ------BEGIN CERTIFICATE----- -MIIFwDCCA6igAwIBAgIQHr9ZULjJgDdMBvfrVU+17TANBgkqhkiG9w0BAQ0FADB6 -MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEgU3lzdGVtcyBTLkEu -MScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxHzAdBgNV -BAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwHhcNMTgwMzE2MTIxMDEzWhcNNDMw -MzE2MTIxMDEzWjB6MQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg -U3lzdGVtcyBTLkEuMScwJQYDVQQLEx5DZXJ0dW0gQ2VydGlmaWNhdGlvbiBBdXRo -b3JpdHkxHzAdBgNVBAMTFkNlcnR1bSBUcnVzdGVkIFJvb3QgQ0EwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQDRLY67tzbqbTeRn06TpwXkKQMlzhyC93yZ -n0EGze2jusDbCSzBfN8pfktlL5On1AFrAygYo9idBcEq2EXxkd7fO9CAAozPOA/q -p1x4EaTByIVcJdPTsuclzxFUl6s1wB52HO8AU5853BSlLCIls3Jy/I2z5T4IHhQq -NwuIPMqw9MjCoa68wb4pZ1Xi/K1ZXP69VyywkI3C7Te2fJmItdUDmj0VDT06qKhF -8JVOJVkdzZhpu9PMMsmN74H+rX2Ju7pgE8pllWeg8xn2A1bUatMn4qGtg/BKEiJ3 -HAVz4hlxQsDsdUaakFjgao4rpUYwBI4Zshfjvqm6f1bxJAPXsiEodg42MEx51UGa -mqi4NboMOvJEGyCI98Ul1z3G4z5D3Yf+xOr1Uz5MZf87Sst4WmsXXw3Hw09Omiqi -7VdNIuJGmj8PkTQkfVXjjJU30xrwCSss0smNtA0Aq2cpKNgB9RkEth2+dv5yXMSF -ytKAQd8FqKPVhJBPC/PgP5sZ0jeJP/J7UhyM9uH3PAeXjA6iWYEMspA90+NZRu0P -qafegGtaqge2Gcu8V/OXIXoMsSt0Puvap2ctTMSYnjYJdmZm/Bo/6khUHL4wvYBQ -v3y1zgD2DGHZ5yQD4OMBgQ692IU0iL2yNqh7XAjlRICMb/gv1SHKHRzQ+8S1h9E6 -Tsd2tTVItQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSM+xx1 -vALTn04uSNn5YFSqxLNP+jAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQENBQAD -ggIBAEii1QALLtA/vBzVtVRJHlpr9OTy4EA34MwUe7nJ+jW1dReTagVphZzNTxl4 -WxmB82M+w85bj/UvXgF2Ez8sALnNllI5SW0ETsXpD4YN4fqzX4IS8TrOZgYkNCvo -zMrnadyHncI013nR03e4qllY/p0m+jiGPp2Kh2RX5Rc64vmNueMzeMGQ2Ljdt4NR -5MTMI9UGfOZR0800McD2RrsLrfw9EAUqO0qRJe6M1ISHgCq8CYyqOhNf6DR5UMEQ -GfnTKB7U0VEwKbOukGfWHwpjscWpxkIxYxeU72nLL/qMFH3EQxiJ2fAyQOaA4kZf -5ePBAFmo+eggvIksDkc0C+pXwlM2/KfUrzHN/gLldfq5Jwn58/U7yn2fqSLLiMmq -0Uc9NneoWWRrJ8/vJ8HjJLWG965+Mk2weWjROeiQWMODvA8s1pfrzgzhIMfatz7D -P78v3DSk+yshzWePS/Tj6tQ/50+6uaWTRRxmHyH6ZF5v4HaUMst19W7l9o/HuKTM -qJZ9ZPskWkoDbGs4xugDQ5r3V7mzKWmTOPQD8rv7gmsHINFSH5pkAnuYZttcTVoP -0ISVoDwUQwbKytu4QTbaakRnh6+v40URFWkIsr4WOZckbxJF0WddCajJFdr60qZf -E2Efv4WstK2tBZQIgx51F9NxO5NQI1mg7TyRVJ12AMXDuDjb ------END CERTIFICATE----- - -# Issuer: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique -# Subject: CN=TunTrust Root CA O=Agence Nationale de Certification Electronique -# Label: "TunTrust Root CA" -# Serial: 108534058042236574382096126452369648152337120275 -# MD5 Fingerprint: 85:13:b9:90:5b:36:5c:b6:5e:b8:5a:f8:e0:31:57:b4 -# SHA1 Fingerprint: cf:e9:70:84:0f:e0:73:0f:9d:f6:0c:7f:2c:4b:ee:20:46:34:9c:bb -# SHA256 Fingerprint: 2e:44:10:2a:b5:8c:b8:54:19:45:1c:8e:19:d9:ac:f3:66:2c:af:bc:61:4b:6a:53:96:0a:30:f7:d0:e2:eb:41 ------BEGIN CERTIFICATE----- -MIIFszCCA5ugAwIBAgIUEwLV4kBMkkaGFmddtLu7sms+/BMwDQYJKoZIhvcNAQEL -BQAwYTELMAkGA1UEBhMCVE4xNzA1BgNVBAoMLkFnZW5jZSBOYXRpb25hbGUgZGUg -Q2VydGlmaWNhdGlvbiBFbGVjdHJvbmlxdWUxGTAXBgNVBAMMEFR1blRydXN0IFJv -b3QgQ0EwHhcNMTkwNDI2MDg1NzU2WhcNNDQwNDI2MDg1NzU2WjBhMQswCQYDVQQG -EwJUTjE3MDUGA1UECgwuQWdlbmNlIE5hdGlvbmFsZSBkZSBDZXJ0aWZpY2F0aW9u -IEVsZWN0cm9uaXF1ZTEZMBcGA1UEAwwQVHVuVHJ1c3QgUm9vdCBDQTCCAiIwDQYJ -KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMPN0/y9BFPdDCA61YguBUtB9YOCfvdZ -n56eY+hz2vYGqU8ftPkLHzmMmiDQfgbU7DTZhrx1W4eI8NLZ1KMKsmwb60ksPqxd -2JQDoOw05TDENX37Jk0bbjBU2PWARZw5rZzJJQRNmpA+TkBuimvNKWfGzC3gdOgF -VwpIUPp6Q9p+7FuaDmJ2/uqdHYVy7BG7NegfJ7/Boce7SBbdVtfMTqDhuazb1YMZ -GoXRlJfXyqNlC/M4+QKu3fZnz8k/9YosRxqZbwUN/dAdgjH8KcwAWJeRTIAAHDOF -li/LQcKLEITDCSSJH7UP2dl3RxiSlGBcx5kDPP73lad9UKGAwqmDrViWVSHbhlnU -r8a83YFuB9tgYv7sEG7aaAH0gxupPqJbI9dkxt/con3YS7qC0lH4Zr8GRuR5KiY2 -eY8fTpkdso8MDhz/yV3A/ZAQprE38806JG60hZC/gLkMjNWb1sjxVj8agIl6qeIb -MlEsPvLfe/ZdeikZjuXIvTZxi11Mwh0/rViizz1wTaZQmCXcI/m4WEEIcb9PuISg -jwBUFfyRbVinljvrS5YnzWuioYasDXxU5mZMZl+QviGaAkYt5IPCgLnPSz7ofzwB -7I9ezX/SKEIBlYrilz0QIX32nRzFNKHsLA4KUiwSVXAkPcvCFDVDXSdOvsC9qnyW -5/yeYa1E0wCXAgMBAAGjYzBhMB0GA1UdDgQWBBQGmpsfU33x9aTI04Y+oXNZtPdE -ITAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFAaamx9TffH1pMjThj6hc1m0 -90QhMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAqgVutt0Vyb+z -xiD2BkewhpMl0425yAA/l/VSJ4hxyXT968pk21vvHl26v9Hr7lxpuhbI87mP0zYu -QEkHDVneixCwSQXi/5E/S7fdAo74gShczNxtr18UnH1YeA32gAm56Q6XKRm4t+v4 -FstVEuTGfbvE7Pi1HE4+Z7/FXxttbUcoqgRYYdZ2vyJ/0Adqp2RT8JeNnYA/u8EH -22Wv5psymsNUk8QcCMNE+3tjEUPRahphanltkE8pjkcFwRJpadbGNjHh/PqAulxP -xOu3Mqz4dWEX1xAZufHSCe96Qp1bWgvUxpVOKs7/B9dPfhgGiPEZtdmYu65xxBzn -dFlY7wyJz4sfdZMaBBSSSFCp61cpABbjNhzI+L/wM9VBD8TMPN3pM0MBkRArHtG5 -Xc0yGYuPjCB31yLEQtyEFpslbei0VXF/sHyz03FJuc9SpAQ/3D2gu68zngowYI7b -nV2UqL1g52KAdoGDDIzMMEZJ4gzSqK/rYXHv5yJiqfdcZGyfFoxnNidF9Ql7v/YQ -CvGwjVRDjAS6oz/v4jXH+XTgbzRB0L9zZVcg+ZtnemZoJE6AZb0QmQZZ8mWvuMZH -u/2QeItBcy6vVR/cO5JyboTT0GFMDcx2V+IthSIVNg3rAZ3r2OvEhJn7wAzMMujj -d9qDRIueVSjAi1jTkD5OGwDxFa2DK5o= ------END CERTIFICATE----- - -# Issuer: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA -# Subject: CN=HARICA TLS RSA Root CA 2021 O=Hellenic Academic and Research Institutions CA -# Label: "HARICA TLS RSA Root CA 2021" -# Serial: 76817823531813593706434026085292783742 -# MD5 Fingerprint: 65:47:9b:58:86:dd:2c:f0:fc:a2:84:1f:1e:96:c4:91 -# SHA1 Fingerprint: 02:2d:05:82:fa:88:ce:14:0c:06:79:de:7f:14:10:e9:45:d7:a5:6d -# SHA256 Fingerprint: d9:5d:0e:8e:da:79:52:5b:f9:be:b1:1b:14:d2:10:0d:32:94:98:5f:0c:62:d9:fa:bd:9c:d9:99:ec:cb:7b:1d ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIQOcqTHO9D88aOk8f0ZIk4fjANBgkqhkiG9w0BAQsFADBs -MQswCQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl -c2VhcmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBSU0Eg -Um9vdCBDQSAyMDIxMB4XDTIxMDIxOTEwNTUzOFoXDTQ1MDIxMzEwNTUzN1owbDEL -MAkGA1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNl -YXJjaCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgUlNBIFJv -b3QgQ0EgMjAyMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAIvC569l -mwVnlskNJLnQDmT8zuIkGCyEf3dRywQRNrhe7Wlxp57kJQmXZ8FHws+RFjZiPTgE -4VGC/6zStGndLuwRo0Xua2s7TL+MjaQenRG56Tj5eg4MmOIjHdFOY9TnuEFE+2uv -a9of08WRiFukiZLRgeaMOVig1mlDqa2YUlhu2wr7a89o+uOkXjpFc5gH6l8Cct4M -pbOfrqkdtx2z/IpZ525yZa31MJQjB/OCFks1mJxTuy/K5FrZx40d/JiZ+yykgmvw -Kh+OC19xXFyuQnspiYHLA6OZyoieC0AJQTPb5lh6/a6ZcMBaD9YThnEvdmn8kN3b -LW7R8pv1GmuebxWMevBLKKAiOIAkbDakO/IwkfN4E8/BPzWr8R0RI7VDIp4BkrcY -AuUR0YLbFQDMYTfBKnya4dC6s1BG7oKsnTH4+yPiAwBIcKMJJnkVU2DzOFytOOqB -AGMUuTNe3QvboEUHGjMJ+E20pwKmafTCWQWIZYVWrkvL4N48fS0ayOn7H6NhStYq -E613TBoYm5EPWNgGVMWX+Ko/IIqmhaZ39qb8HOLubpQzKoNQhArlT4b4UEV4AIHr -W2jjJo3Me1xR9BQsQL4aYB16cmEdH2MtiKrOokWQCPxrvrNQKlr9qEgYRtaQQJKQ -CoReaDH46+0N0x3GfZkYVVYnZS6NRcUk7M7jAgMBAAGjQjBAMA8GA1UdEwEB/wQF -MAMBAf8wHQYDVR0OBBYEFApII6ZgpJIKM+qTW8VX6iVNvRLuMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQsFAAOCAgEAPpBIqm5iFSVmewzVjIuJndftTgfvnNAU -X15QvWiWkKQUEapobQk1OUAJ2vQJLDSle1mESSmXdMgHHkdt8s4cUCbjnj1AUz/3 -f5Z2EMVGpdAgS1D0NTsY9FVqQRtHBmg8uwkIYtlfVUKqrFOFrJVWNlar5AWMxaja -H6NpvVMPxP/cyuN+8kyIhkdGGvMA9YCRotxDQpSbIPDRzbLrLFPCU3hKTwSUQZqP -JzLB5UkZv/HywouoCjkxKLR9YjYsTewfM7Z+d21+UPCfDtcRj88YxeMn/ibvBZ3P -zzfF0HvaO7AWhAw6k9a+F9sPPg4ZeAnHqQJyIkv3N3a6dcSFA1pj1bF1BcK5vZSt -jBWZp5N99sXzqnTPBIWUmAD04vnKJGW/4GKvyMX6ssmeVkjaef2WdhW+o45WxLM0 -/L5H9MG0qPzVMIho7suuyWPEdr6sOBjhXlzPrjoiUevRi7PzKzMHVIf6tLITe7pT -BGIBnfHAT+7hOtSLIBD6Alfm78ELt5BGnBkpjNxvoEppaZS3JGWg/6w/zgH7IS79 -aPib8qXPMThcFarmlwDB31qlpzmq6YR/PFGoOtmUW4y/Twhx5duoXNTSpv4Ao8YW -xw/ogM4cKGR0GQjTQuPOAF1/sdwTsOEFy9EgqoZ0njnnkf3/W9b3raYvAwtt41dU -63ZTGI0RmLo= ------END CERTIFICATE----- - -# Issuer: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA -# Subject: CN=HARICA TLS ECC Root CA 2021 O=Hellenic Academic and Research Institutions CA -# Label: "HARICA TLS ECC Root CA 2021" -# Serial: 137515985548005187474074462014555733966 -# MD5 Fingerprint: ae:f7:4c:e5:66:35:d1:b7:9b:8c:22:93:74:d3:4b:b0 -# SHA1 Fingerprint: bc:b0:c1:9d:e9:98:92:70:19:38:57:e9:8d:a7:b4:5d:6e:ee:01:48 -# SHA256 Fingerprint: 3f:99:cc:47:4a:cf:ce:4d:fe:d5:87:94:66:5e:47:8d:15:47:73:9f:2e:78:0f:1b:b4:ca:9b:13:30:97:d4:01 ------BEGIN CERTIFICATE----- -MIICVDCCAdugAwIBAgIQZ3SdjXfYO2rbIvT/WeK/zjAKBggqhkjOPQQDAzBsMQsw -CQYDVQQGEwJHUjE3MDUGA1UECgwuSGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJlc2Vh -cmNoIEluc3RpdHV0aW9ucyBDQTEkMCIGA1UEAwwbSEFSSUNBIFRMUyBFQ0MgUm9v -dCBDQSAyMDIxMB4XDTIxMDIxOTExMDExMFoXDTQ1MDIxMzExMDEwOVowbDELMAkG -A1UEBhMCR1IxNzA1BgNVBAoMLkhlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJj -aCBJbnN0aXR1dGlvbnMgQ0ExJDAiBgNVBAMMG0hBUklDQSBUTFMgRUNDIFJvb3Qg -Q0EgMjAyMTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDgI/rGgltJ6rK9JOtDA4MM7 -KKrxcm1lAEeIhPyaJmuqS7psBAqIXhfyVYf8MLA04jRYVxqEU+kw2anylnTDUR9Y -STHMmE5gEYd103KUkE+bECUqqHgtvpBBWJAVcqeht6NCMEAwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUyRtTgRL+BNUW0aq8mm+3oJUZbsowDgYDVR0PAQH/BAQD -AgGGMAoGCCqGSM49BAMDA2cAMGQCMBHervjcToiwqfAircJRQO9gcS3ujwLEXQNw -SaSS6sUUiHCm0w2wqsosQJz76YJumgIwK0eaB8bRwoF8yguWGEEbo/QwCZ61IygN -nxS2PFOiTAZpffpskcYqSUXm7LcT4Tps ------END CERTIFICATE----- - -# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Label: "Autoridad de Certificacion Firmaprofesional CIF A62634068" -# Serial: 1977337328857672817 -# MD5 Fingerprint: 4e:6e:9b:54:4c:ca:b7:fa:48:e4:90:b1:15:4b:1c:a3 -# SHA1 Fingerprint: 0b:be:c2:27:22:49:cb:39:aa:db:35:5c:53:e3:8c:ae:78:ff:b6:fe -# SHA256 Fingerprint: 57:de:05:83:ef:d2:b2:6e:03:61:da:99:da:9d:f4:64:8d:ef:7e:e8:44:1c:3b:72:8a:fa:9b:cd:e0:f9:b2:6a ------BEGIN CERTIFICATE----- -MIIGFDCCA/ygAwIBAgIIG3Dp0v+ubHEwDQYJKoZIhvcNAQELBQAwUTELMAkGA1UE -BhMCRVMxQjBABgNVBAMMOUF1dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1h -cHJvZmVzaW9uYWwgQ0lGIEE2MjYzNDA2ODAeFw0xNDA5MjMxNTIyMDdaFw0zNjA1 -MDUxNTIyMDdaMFExCzAJBgNVBAYTAkVTMUIwQAYDVQQDDDlBdXRvcmlkYWQgZGUg -Q2VydGlmaWNhY2lvbiBGaXJtYXByb2Zlc2lvbmFsIENJRiBBNjI2MzQwNjgwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDKlmuO6vj78aI14H9M2uDDUtd9 -thDIAl6zQyrET2qyyhxdKJp4ERppWVevtSBC5IsP5t9bpgOSL/UR5GLXMnE42QQM -cas9UX4PB99jBVzpv5RvwSmCwLTaUbDBPLutN0pcyvFLNg4kq7/DhHf9qFD0sefG -L9ItWY16Ck6WaVICqjaY7Pz6FIMMNx/Jkjd/14Et5cS54D40/mf0PmbR0/RAz15i -NA9wBj4gGFrO93IbJWyTdBSTo3OxDqqHECNZXyAFGUftaI6SEspd/NYrspI8IM/h -X68gvqB2f3bl7BqGYTM+53u0P6APjqK5am+5hyZvQWyIplD9amML9ZMWGxmPsu2b -m8mQ9QEM3xk9Dz44I8kvjwzRAv4bVdZO0I08r0+k8/6vKtMFnXkIoctXMbScyJCy -Z/QYFpM6/EfY0XiWMR+6KwxfXZmtY4laJCB22N/9q06mIqqdXuYnin1oKaPnirja -EbsXLZmdEyRG98Xi2J+Of8ePdG1asuhy9azuJBCtLxTa/y2aRnFHvkLfuwHb9H/T -KI8xWVvTyQKmtFLKbpf7Q8UIJm+K9Lv9nyiqDdVF8xM6HdjAeI9BZzwelGSuewvF -6NkBiDkal4ZkQdU7hwxu+g/GvUgUvzlN1J5Bto+WHWOWk9mVBngxaJ43BjuAiUVh -OSPHG0SjFeUc+JIwuwIDAQABo4HvMIHsMB0GA1UdDgQWBBRlzeurNR4APn7VdMAc -tHNHDhpkLzASBgNVHRMBAf8ECDAGAQH/AgEBMIGmBgNVHSAEgZ4wgZswgZgGBFUd -IAAwgY8wLwYIKwYBBQUHAgEWI2h0dHA6Ly93d3cuZmlybWFwcm9mZXNpb25hbC5j -b20vY3BzMFwGCCsGAQUFBwICMFAeTgBQAGEAcwBlAG8AIABkAGUAIABsAGEAIABC -AG8AbgBhAG4AbwB2AGEAIAA0ADcAIABCAGEAcgBjAGUAbABvAG4AYQAgADAAOAAw -ADEANzAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQELBQADggIBAHSHKAIrdx9m -iWTtj3QuRhy7qPj4Cx2Dtjqn6EWKB7fgPiDL4QjbEwj4KKE1soCzC1HA01aajTNF -Sa9J8OA9B3pFE1r/yJfY0xgsfZb43aJlQ3CTkBW6kN/oGbDbLIpgD7dvlAceHabJ -hfa9NPhAeGIQcDq+fUs5gakQ1JZBu/hfHAsdCPKxsIl68veg4MSPi3i1O1ilI45P -Vf42O+AMt8oqMEEgtIDNrvx2ZnOorm7hfNoD6JQg5iKj0B+QXSBTFCZX2lSX3xZE -EAEeiGaPcjiT3SC3NL7X8e5jjkd5KAb881lFJWAiMxujX6i6KtoaPc1A6ozuBRWV -1aUsIC+nmCjuRfzxuIgALI9C2lHVnOUTaHFFQ4ueCyE8S1wF3BqfmI7avSKecs2t -CsvMo2ebKHTEm9caPARYpoKdrcd7b/+Alun4jWq9GJAd/0kakFI3ky88Al2CdgtR -5xbHV/g4+afNmyJU72OwFW1TZQNKXkqgsqeOSQBZONXH9IBk9W6VULgRfhVwOEqw -f9DEMnDAGf/JOC0ULGb0QkTmVXYbgBVX/8Cnp6o5qtjTcNAuuuuUavpfNIbnYrX9 -ivAwhZTJryQCL2/W3Wf+47BVTwSYT6RBVuKT0Gro1vP7ZeDOdcQxWQzugsgMYDNK -GbqEZycPvEJdvSRUDewdcAZfpLz6IHxV ------END CERTIFICATE----- - -# Issuer: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. -# Subject: CN=vTrus ECC Root CA O=iTrusChina Co.,Ltd. -# Label: "vTrus ECC Root CA" -# Serial: 630369271402956006249506845124680065938238527194 -# MD5 Fingerprint: de:4b:c1:f5:52:8c:9b:43:e1:3e:8f:55:54:17:8d:85 -# SHA1 Fingerprint: f6:9c:db:b0:fc:f6:02:13:b6:52:32:a6:a3:91:3f:16:70:da:c3:e1 -# SHA256 Fingerprint: 30:fb:ba:2c:32:23:8e:2a:98:54:7a:f9:79:31:e5:50:42:8b:9b:3f:1c:8e:eb:66:33:dc:fa:86:c5:b2:7d:d3 ------BEGIN CERTIFICATE----- -MIICDzCCAZWgAwIBAgIUbmq8WapTvpg5Z6LSa6Q75m0c1towCgYIKoZIzj0EAwMw -RzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4xGjAY -BgNVBAMTEXZUcnVzIEVDQyBSb290IENBMB4XDTE4MDczMTA3MjY0NFoXDTQzMDcz -MTA3MjY0NFowRzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28u -LEx0ZC4xGjAYBgNVBAMTEXZUcnVzIEVDQyBSb290IENBMHYwEAYHKoZIzj0CAQYF -K4EEACIDYgAEZVBKrox5lkqqHAjDo6LN/llWQXf9JpRCux3NCNtzslt188+cToL0 -v/hhJoVs1oVbcnDS/dtitN9Ti72xRFhiQgnH+n9bEOf+QP3A2MMrMudwpremIFUd -e4BdS49nTPEQo0IwQDAdBgNVHQ4EFgQUmDnNvtiyjPeyq+GtJK97fKHbH88wDwYD -VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwCgYIKoZIzj0EAwMDaAAwZQIw -V53dVvHH4+m4SVBrm2nDb+zDfSXkV5UTQJtS0zvzQBm8JsctBp61ezaf9SXUY2sA -AjEA6dPGnlaaKsyh2j/IZivTWJwghfqrkYpwcBE4YGQLYgmRWAD5Tfs0aNoJrSEG -GJTO ------END CERTIFICATE----- - -# Issuer: CN=vTrus Root CA O=iTrusChina Co.,Ltd. -# Subject: CN=vTrus Root CA O=iTrusChina Co.,Ltd. -# Label: "vTrus Root CA" -# Serial: 387574501246983434957692974888460947164905180485 -# MD5 Fingerprint: b8:c9:37:df:fa:6b:31:84:64:c5:ea:11:6a:1b:75:fc -# SHA1 Fingerprint: 84:1a:69:fb:f5:cd:1a:25:34:13:3d:e3:f8:fc:b8:99:d0:c9:14:b7 -# SHA256 Fingerprint: 8a:71:de:65:59:33:6f:42:6c:26:e5:38:80:d0:0d:88:a1:8d:a4:c6:a9:1f:0d:cb:61:94:e2:06:c5:c9:63:87 ------BEGIN CERTIFICATE----- -MIIFVjCCAz6gAwIBAgIUQ+NxE9izWRRdt86M/TX9b7wFjUUwDQYJKoZIhvcNAQEL -BQAwQzELMAkGA1UEBhMCQ04xHDAaBgNVBAoTE2lUcnVzQ2hpbmEgQ28uLEx0ZC4x -FjAUBgNVBAMTDXZUcnVzIFJvb3QgQ0EwHhcNMTgwNzMxMDcyNDA1WhcNNDMwNzMx -MDcyNDA1WjBDMQswCQYDVQQGEwJDTjEcMBoGA1UEChMTaVRydXNDaGluYSBDby4s -THRkLjEWMBQGA1UEAxMNdlRydXMgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD -ggIPADCCAgoCggIBAL1VfGHTuB0EYgWgrmy3cLRB6ksDXhA/kFocizuwZotsSKYc -IrrVQJLuM7IjWcmOvFjai57QGfIvWcaMY1q6n6MLsLOaXLoRuBLpDLvPbmyAhykU -AyyNJJrIZIO1aqwTLDPxn9wsYTwaP3BVm60AUn/PBLn+NvqcwBauYv6WTEN+VRS+ -GrPSbcKvdmaVayqwlHeFXgQPYh1jdfdr58tbmnDsPmcF8P4HCIDPKNsFxhQnL4Z9 -8Cfe/+Z+M0jnCx5Y0ScrUw5XSmXX+6KAYPxMvDVTAWqXcoKv8R1w6Jz1717CbMdH -flqUhSZNO7rrTOiwCcJlwp2dCZtOtZcFrPUGoPc2BX70kLJrxLT5ZOrpGgrIDajt -J8nU57O5q4IikCc9Kuh8kO+8T/3iCiSn3mUkpF3qwHYw03dQ+A0Em5Q2AXPKBlim -0zvc+gRGE1WKyURHuFE5Gi7oNOJ5y1lKCn+8pu8fA2dqWSslYpPZUxlmPCdiKYZN -pGvu/9ROutW04o5IWgAZCfEF2c6Rsffr6TlP9m8EQ5pV9T4FFL2/s1m02I4zhKOQ -UqqzApVg+QxMaPnu1RcN+HFXtSXkKe5lXa/R7jwXC1pDxaWG6iSe4gUH3DRCEpHW -OXSuTEGC2/KmSNGzm/MzqvOmwMVO9fSddmPmAsYiS8GVP1BkLFTltvA8Kc9XAgMB -AAGjQjBAMB0GA1UdDgQWBBRUYnBj8XWEQ1iO0RYgscasGrz2iTAPBgNVHRMBAf8E -BTADAQH/MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAgEAKbqSSaet -8PFww+SX8J+pJdVrnjT+5hpk9jprUrIQeBqfTNqK2uwcN1LgQkv7bHbKJAs5EhWd -nxEt/Hlk3ODg9d3gV8mlsnZwUKT+twpw1aA08XXXTUm6EdGz2OyC/+sOxL9kLX1j -bhd47F18iMjrjld22VkE+rxSH0Ws8HqA7Oxvdq6R2xCOBNyS36D25q5J08FsEhvM -Kar5CKXiNxTKsbhm7xqC5PD48acWabfbqWE8n/Uxy+QARsIvdLGx14HuqCaVvIiv -TDUHKgLKeBRtRytAVunLKmChZwOgzoy8sHJnxDHO2zTlJQNgJXtxmOTAGytfdELS -S8VZCAeHvsXDf+eW2eHcKJfWjwXj9ZtOyh1QRwVTsMo554WgicEFOwE30z9J4nfr -I8iIZjs9OXYhRvHsXyO466JmdXTBQPfYaJqT4i2pLr0cox7IdMakLXogqzu4sEb9 -b91fUlV1YvCXoHzXOP0l382gmxDPi7g4Xl7FtKYCNqEeXxzP4padKar9mK5S4fNB -UvupLnKWnyfjqnN9+BojZns7q2WwMgFLFT49ok8MKzWixtlnEjUwzXYuFrOZnk1P -Ti07NEPhmg4NpGaXutIcSkwsKouLgU9xGqndXHt7CMUADTdA43x7VF8vhV929ven -sBxXVsFy6K2ir40zSbofitzmdHxghm+Hl3s= ------END CERTIFICATE----- - -# Issuer: CN=ISRG Root X2 O=Internet Security Research Group -# Subject: CN=ISRG Root X2 O=Internet Security Research Group -# Label: "ISRG Root X2" -# Serial: 87493402998870891108772069816698636114 -# MD5 Fingerprint: d3:9e:c4:1e:23:3c:a6:df:cf:a3:7e:6d:e0:14:e6:e5 -# SHA1 Fingerprint: bd:b1:b9:3c:d5:97:8d:45:c6:26:14:55:f8:db:95:c7:5a:d1:53:af -# SHA256 Fingerprint: 69:72:9b:8e:15:a8:6e:fc:17:7a:57:af:b7:17:1d:fc:64:ad:d2:8c:2f:ca:8c:f1:50:7e:34:45:3c:cb:14:70 ------BEGIN CERTIFICATE----- -MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw -CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg -R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 -MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT -ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw -EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW -+1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 -ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T -AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI -zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW -tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 -/q4AaOeMSQ+2b1tbFfLn ------END CERTIFICATE----- - -# Issuer: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. -# Subject: CN=HiPKI Root CA - G1 O=Chunghwa Telecom Co., Ltd. -# Label: "HiPKI Root CA - G1" -# Serial: 60966262342023497858655262305426234976 -# MD5 Fingerprint: 69:45:df:16:65:4b:e8:68:9a:8f:76:5f:ff:80:9e:d3 -# SHA1 Fingerprint: 6a:92:e4:a8:ee:1b:ec:96:45:37:e3:29:57:49:cd:96:e3:e5:d2:60 -# SHA256 Fingerprint: f0:15:ce:3c:c2:39:bf:ef:06:4b:e9:f1:d2:c4:17:e1:a0:26:4a:0a:94:be:1f:0c:8d:12:18:64:eb:69:49:cc ------BEGIN CERTIFICATE----- -MIIFajCCA1KgAwIBAgIQLd2szmKXlKFD6LDNdmpeYDANBgkqhkiG9w0BAQsFADBP -MQswCQYDVQQGEwJUVzEjMCEGA1UECgwaQ2h1bmdod2EgVGVsZWNvbSBDby4sIEx0 -ZC4xGzAZBgNVBAMMEkhpUEtJIFJvb3QgQ0EgLSBHMTAeFw0xOTAyMjIwOTQ2MDRa -Fw0zNzEyMzExNTU5NTlaME8xCzAJBgNVBAYTAlRXMSMwIQYDVQQKDBpDaHVuZ2h3 -YSBUZWxlY29tIENvLiwgTHRkLjEbMBkGA1UEAwwSSGlQS0kgUm9vdCBDQSAtIEcx -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA9B5/UnMyDHPkvRN0o9Qw -qNCuS9i233VHZvR85zkEHmpwINJaR3JnVfSl6J3VHiGh8Ge6zCFovkRTv4354twv -Vcg3Px+kwJyz5HdcoEb+d/oaoDjq7Zpy3iu9lFc6uux55199QmQ5eiY29yTw1S+6 -lZgRZq2XNdZ1AYDgr/SEYYwNHl98h5ZeQa/rh+r4XfEuiAU+TCK72h8q3VJGZDnz -Qs7ZngyzsHeXZJzA9KMuH5UHsBffMNsAGJZMoYFL3QRtU6M9/Aes1MU3guvklQgZ -KILSQjqj2FPseYlgSGDIcpJQ3AOPgz+yQlda22rpEZfdhSi8MEyr48KxRURHH+CK -FgeW0iEPU8DtqX7UTuybCeyvQqww1r/REEXgphaypcXTT3OUM3ECoWqj1jOXTyFj -HluP2cFeRXF3D4FdXyGarYPM+l7WjSNfGz1BryB1ZlpK9p/7qxj3ccC2HTHsOyDr -y+K49a6SsvfhhEvyovKTmiKe0xRvNlS9H15ZFblzqMF8b3ti6RZsR1pl8w4Rm0bZ -/W3c1pzAtH2lsN0/Vm+h+fbkEkj9Bn8SV7apI09bA8PgcSojt/ewsTu8mL3WmKgM -a/aOEmem8rJY5AIJEzypuxC00jBF8ez3ABHfZfjcK0NVvxaXxA/VLGGEqnKG/uY6 -fsI/fe78LxQ+5oXdUG+3Se0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNV -HQ4EFgQU8ncX+l6o/vY9cdVouslGDDjYr7AwDgYDVR0PAQH/BAQDAgGGMA0GCSqG -SIb3DQEBCwUAA4ICAQBQUfB13HAE4/+qddRxosuej6ip0691x1TPOhwEmSKsxBHi -7zNKpiMdDg1H2DfHb680f0+BazVP6XKlMeJ45/dOlBhbQH3PayFUhuaVevvGyuqc -SE5XCV0vrPSltJczWNWseanMX/mF+lLFjfiRFOs6DRfQUsJ748JzjkZ4Bjgs6Fza -ZsT0pPBWGTMpWmWSBUdGSquEwx4noR8RkpkndZMPvDY7l1ePJlsMu5wP1G4wB9Tc -XzZoZjmDlicmisjEOf6aIW/Vcobpf2Lll07QJNBAsNB1CI69aO4I1258EHBGG3zg -iLKecoaZAeO/n0kZtCW+VmWuF2PlHt/o/0elv+EmBYTksMCv5wiZqAxeJoBF1Pho -L5aPruJKHJwWDBNvOIf2u8g0X5IDUXlwpt/L9ZlNec1OvFefQ05rLisY+GpzjLrF -Ne85akEez3GoorKGB1s6yeHvP2UEgEcyRHCVTjFnanRbEEV16rCf0OY1/k6fi8wr -kkVbbiVghUbN0aqwdmaTd5a+g744tiROJgvM7XpWGuDpWsZkrUx6AEhEL7lAuxM+ -vhV4nYWBSipX3tUZQ9rbyltHhoMLP7YNdnhzeSJesYAfz77RP1YQmCuVh6EfnWQU -YDksswBVLuT1sw5XxJFBAJw/6KXf6vb/yPCtbVKoF6ubYfwSUTXkJf2vqmqGOQ== ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 -# Label: "GlobalSign ECC Root CA - R4" -# Serial: 159662223612894884239637590694 -# MD5 Fingerprint: 26:29:f8:6d:e1:88:bf:a2:65:7f:aa:c4:cd:0f:7f:fc -# SHA1 Fingerprint: 6b:a0:b0:98:e1:71:ef:5a:ad:fe:48:15:80:77:10:f4:bd:6f:0b:28 -# SHA256 Fingerprint: b0:85:d7:0b:96:4f:19:1a:73:e4:af:0d:54:ae:7a:0e:07:aa:fd:af:9b:71:dd:08:62:13:8a:b7:32:5a:24:a2 ------BEGIN CERTIFICATE----- -MIIB3DCCAYOgAwIBAgINAgPlfvU/k/2lCSGypjAKBggqhkjOPQQDAjBQMSQwIgYD -VQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0gUjQxEzARBgNVBAoTCkdsb2Jh -bFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMTIxMTEzMDAwMDAwWhcNMzgw -MTE5MDMxNDA3WjBQMSQwIgYDVQQLExtHbG9iYWxTaWduIEVDQyBSb290IENBIC0g -UjQxEzARBgNVBAoTCkdsb2JhbFNpZ24xEzARBgNVBAMTCkdsb2JhbFNpZ24wWTAT -BgcqhkjOPQIBBggqhkjOPQMBBwNCAAS4xnnTj2wlDp8uORkcA6SumuU5BwkWymOx -uYb4ilfBV85C+nOh92VC/x7BALJucw7/xyHlGKSq2XE/qNS5zowdo0IwQDAOBgNV -HQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUVLB7rUW44kB/ -+wpu+74zyTyjhNUwCgYIKoZIzj0EAwIDRwAwRAIgIk90crlgr/HmnKAWBVBfw147 -bmF0774BxL4YSFlhgjICICadVGNA3jdgUM/I2O2dgq43mLyjj0xMqTQrbO/7lZsm ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R1 O=Google Trust Services LLC -# Subject: CN=GTS Root R1 O=Google Trust Services LLC -# Label: "GTS Root R1" -# Serial: 159662320309726417404178440727 -# MD5 Fingerprint: 05:fe:d0:bf:71:a8:a3:76:63:da:01:e0:d8:52:dc:40 -# SHA1 Fingerprint: e5:8c:1c:c4:91:3b:38:63:4b:e9:10:6e:e3:ad:8e:6b:9d:d9:81:4a -# SHA256 Fingerprint: d9:47:43:2a:bd:e7:b7:fa:90:fc:2e:6b:59:10:1b:12:80:e0:e1:c7:e4:e4:0f:a3:c6:88:7f:ff:57:a7:f4:cf ------BEGIN CERTIFICATE----- -MIIFVzCCAz+gAwIBAgINAgPlk28xsBNJiGuiFzANBgkqhkiG9w0BAQwFADBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw -MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQC2EQKLHuOhd5s73L+UPreVp0A8of2C+X0yBoJx9vaMf/vo -27xqLpeXo4xL+Sv2sfnOhB2x+cWX3u+58qPpvBKJXqeqUqv4IyfLpLGcY9vXmX7w -Cl7raKb0xlpHDU0QM+NOsROjyBhsS+z8CZDfnWQpJSMHobTSPS5g4M/SCYe7zUjw -TcLCeoiKu7rPWRnWr4+wB7CeMfGCwcDfLqZtbBkOtdh+JhpFAz2weaSUKK0Pfybl -qAj+lug8aJRT7oM6iCsVlgmy4HqMLnXWnOunVmSPlk9orj2XwoSPwLxAwAtcvfaH -szVsrBhQf4TgTM2S0yDpM7xSma8ytSmzJSq0SPly4cpk9+aCEI3oncKKiPo4Zor8 -Y/kB+Xj9e1x3+naH+uzfsQ55lVe0vSbv1gHR6xYKu44LtcXFilWr06zqkUspzBmk -MiVOKvFlRNACzqrOSbTqn3yDsEB750Orp2yjj32JgfpMpf/VjsPOS+C12LOORc92 -wO1AK/1TD7Cn1TsNsYqiA94xrcx36m97PtbfkSIS5r762DL8EGMUUXLeXdYWk70p -aDPvOmbsB4om3xPXV2V4J95eSRQAogB/mqghtqmxlbCluQ0WEdrHbEg8QOB+DVrN -VjzRlwW5y0vtOUucxD/SVRNuJLDWcfr0wbrM7Rv1/oFB2ACYPTrIrnqYNxgFlQID -AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E -FgQU5K8rJnEaK0gnhS9SZizv8IkTcT4wDQYJKoZIhvcNAQEMBQADggIBAJ+qQibb -C5u+/x6Wki4+omVKapi6Ist9wTrYggoGxval3sBOh2Z5ofmmWJyq+bXmYOfg6LEe -QkEzCzc9zolwFcq1JKjPa7XSQCGYzyI0zzvFIoTgxQ6KfF2I5DUkzps+GlQebtuy -h6f88/qBVRRiClmpIgUxPoLW7ttXNLwzldMXG+gnoot7TiYaelpkttGsN/H9oPM4 -7HLwEXWdyzRSjeZ2axfG34arJ45JK3VmgRAhpuo+9K4l/3wV3s6MJT/KYnAK9y8J -ZgfIPxz88NtFMN9iiMG1D53Dn0reWVlHxYciNuaCp+0KueIHoI17eko8cdLiA6Ef -MgfdG+RCzgwARWGAtQsgWSl4vflVy2PFPEz0tv/bal8xa5meLMFrUKTX5hgUvYU/ -Z6tGn6D/Qqc6f1zLXbBwHSs09dR2CQzreExZBfMzQsNhFRAbd03OIozUhfJFfbdT -6u9AWpQKXCBfTkBdYiJ23//OYb2MI3jSNwLgjt7RETeJ9r/tSQdirpLsQBqvFAnZ -0E6yove+7u7Y/9waLd64NnHi/Hm3lCXRSHNboTXns5lndcEZOitHTtNCjv0xyBZm -2tIMPNuzjsmhDYAPexZ3FL//2wmUspO8IFgV6dtxQ/PeEMMA3KgqlbbC1j+Qa3bb -bP6MvPJwNQzcmRk13NfIRmPVNnGuV/u3gm3c ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R2 O=Google Trust Services LLC -# Subject: CN=GTS Root R2 O=Google Trust Services LLC -# Label: "GTS Root R2" -# Serial: 159662449406622349769042896298 -# MD5 Fingerprint: 1e:39:c0:53:e6:1e:29:82:0b:ca:52:55:36:5d:57:dc -# SHA1 Fingerprint: 9a:44:49:76:32:db:de:fa:d0:bc:fb:5a:7b:17:bd:9e:56:09:24:94 -# SHA256 Fingerprint: 8d:25:cd:97:22:9d:bf:70:35:6b:da:4e:b3:cc:73:40:31:e2:4c:f0:0f:af:cf:d3:2d:c7:6e:b5:84:1c:7e:a8 ------BEGIN CERTIFICATE----- -MIIFVzCCAz+gAwIBAgINAgPlrsWNBCUaqxElqjANBgkqhkiG9w0BAQwFADBHMQsw -CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU -MBIGA1UEAxMLR1RTIFJvb3QgUjIwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAw -MDAwWjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZp -Y2VzIExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjIwggIiMA0GCSqGSIb3DQEBAQUA -A4ICDwAwggIKAoICAQDO3v2m++zsFDQ8BwZabFn3GTXd98GdVarTzTukk3LvCvpt -nfbwhYBboUhSnznFt+4orO/LdmgUud+tAWyZH8QiHZ/+cnfgLFuv5AS/T3KgGjSY -6Dlo7JUle3ah5mm5hRm9iYz+re026nO8/4Piy33B0s5Ks40FnotJk9/BW9BuXvAu -MC6C/Pq8tBcKSOWIm8Wba96wyrQD8Nr0kLhlZPdcTK3ofmZemde4wj7I0BOdre7k -RXuJVfeKH2JShBKzwkCX44ofR5GmdFrS+LFjKBC4swm4VndAoiaYecb+3yXuPuWg -f9RhD1FLPD+M2uFwdNjCaKH5wQzpoeJ/u1U8dgbuak7MkogwTZq9TwtImoS1mKPV -+3PBV2HdKFZ1E66HjucMUQkQdYhMvI35ezzUIkgfKtzra7tEscszcTJGr61K8Yzo -dDqs5xoic4DSMPclQsciOzsSrZYuxsN2B6ogtzVJV+mSSeh2FnIxZyuWfoqjx5RW -Ir9qS34BIbIjMt/kmkRtWVtd9QCgHJvGeJeNkP+byKq0rxFROV7Z+2et1VsRnTKa -G73VululycslaVNVJ1zgyjbLiGH7HrfQy+4W+9OmTN6SpdTi3/UGVN4unUu0kzCq -gc7dGtxRcw1PcOnlthYhGXmy5okLdWTK1au8CcEYof/UVKGFPP0UJAOyh9OktwID -AQABo0IwQDAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4E -FgQUu//KjiOfT5nK2+JopqUVJxce2Q4wDQYJKoZIhvcNAQEMBQADggIBAB/Kzt3H -vqGf2SdMC9wXmBFqiN495nFWcrKeGk6c1SuYJF2ba3uwM4IJvd8lRuqYnrYb/oM8 -0mJhwQTtzuDFycgTE1XnqGOtjHsB/ncw4c5omwX4Eu55MaBBRTUoCnGkJE+M3DyC -B19m3H0Q/gxhswWV7uGugQ+o+MePTagjAiZrHYNSVc61LwDKgEDg4XSsYPWHgJ2u -NmSRXbBoGOqKYcl3qJfEycel/FVL8/B/uWU9J2jQzGv6U53hkRrJXRqWbTKH7QMg -yALOWr7Z6v2yTcQvG99fevX4i8buMTolUVVnjWQye+mew4K6Ki3pHrTgSAai/Gev -HyICc/sgCq+dVEuhzf9gR7A/Xe8bVr2XIZYtCtFenTgCR2y59PYjJbigapordwj6 -xLEokCZYCDzifqrXPW+6MYgKBesntaFJ7qBFVHvmJ2WZICGoo7z7GJa7Um8M7YNR -TOlZ4iBgxcJlkoKM8xAfDoqXvneCbT+PHV28SSe9zE8P4c52hgQjxcCMElv924Sg -JPFI/2R80L5cFtHvma3AH/vLrrw4IgYmZNralw4/KBVEqE8AyvCazM90arQ+POuV -7LXTWtiBmelDGDfrs7vRWGJB82bSj6p4lVQgw1oudCvV0b4YacCs1aTPObpRhANl -6WLAYv7YTVWW4tAR+kg0Eeye7QUd5MjWHYbL ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R3 O=Google Trust Services LLC -# Subject: CN=GTS Root R3 O=Google Trust Services LLC -# Label: "GTS Root R3" -# Serial: 159662495401136852707857743206 -# MD5 Fingerprint: 3e:e7:9d:58:02:94:46:51:94:e5:e0:22:4a:8b:e7:73 -# SHA1 Fingerprint: ed:e5:71:80:2b:c8:92:b9:5b:83:3c:d2:32:68:3f:09:cd:a0:1e:46 -# SHA256 Fingerprint: 34:d8:a7:3e:e2:08:d9:bc:db:0d:95:65:20:93:4b:4e:40:e6:94:82:59:6e:8b:6f:73:c8:42:6b:01:0a:6f:48 ------BEGIN CERTIFICATE----- -MIICCTCCAY6gAwIBAgINAgPluILrIPglJ209ZjAKBggqhkjOPQQDAzBHMQswCQYD -VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG -A1UEAxMLR1RTIFJvb3QgUjMwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw -WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz -IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNi -AAQfTzOHMymKoYTey8chWEGJ6ladK0uFxh1MJ7x/JlFyb+Kf1qPKzEUURout736G -jOyxfi//qXGdGIRFBEFVbivqJn+7kAHjSxm65FSWRQmx1WyRRK2EE46ajA2ADDL2 -4CejQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBTB8Sa6oC2uhYHP0/EqEr24Cmf9vDAKBggqhkjOPQQDAwNpADBmAjEA9uEglRR7 -VKOQFhG/hMjqb2sXnh5GmCCbn9MN2azTL818+FsuVbu/3ZL3pAzcMeGiAjEA/Jdm -ZuVDFhOD3cffL74UOO0BzrEXGhF16b0DjyZ+hOXJYKaV11RZt+cRLInUue4X ------END CERTIFICATE----- - -# Issuer: CN=GTS Root R4 O=Google Trust Services LLC -# Subject: CN=GTS Root R4 O=Google Trust Services LLC -# Label: "GTS Root R4" -# Serial: 159662532700760215368942768210 -# MD5 Fingerprint: 43:96:83:77:19:4d:76:b3:9d:65:52:e4:1d:22:a5:e8 -# SHA1 Fingerprint: 77:d3:03:67:b5:e0:0c:15:f6:0c:38:61:df:7c:e1:3b:92:46:4d:47 -# SHA256 Fingerprint: 34:9d:fa:40:58:c5:e2:63:12:3b:39:8a:e7:95:57:3c:4e:13:13:c8:3f:e6:8f:93:55:6c:d5:e8:03:1b:3c:7d ------BEGIN CERTIFICATE----- -MIICCTCCAY6gAwIBAgINAgPlwGjvYxqccpBQUjAKBggqhkjOPQQDAzBHMQswCQYD -VQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEUMBIG -A1UEAxMLR1RTIFJvb3QgUjQwHhcNMTYwNjIyMDAwMDAwWhcNMzYwNjIyMDAwMDAw -WjBHMQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2Vz -IExMQzEUMBIGA1UEAxMLR1RTIFJvb3QgUjQwdjAQBgcqhkjOPQIBBgUrgQQAIgNi -AATzdHOnaItgrkO4NcWBMHtLSZ37wWHO5t5GvWvVYRg1rkDdc/eJkTBa6zzuhXyi -QHY7qca4R9gq55KRanPpsXI5nymfopjTX15YhmUPoYRlBtHci8nHc8iMai/lxKvR -HYqjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQW -BBSATNbrdP9JNqPV2Py1PsVq8JQdjDAKBggqhkjOPQQDAwNpADBmAjEA6ED/g94D -9J+uHXqnLrmvT/aDHQ4thQEd0dlq7A/Cr8deVl5c1RxYIigL9zC2L7F8AjEA8GE8 -p/SgguMh1YQdc4acLa/KNJvxn7kjNuK8YAOdgLOaVsjh4rsUecrNIdSUtUlD ------END CERTIFICATE----- - -# Issuer: CN=Telia Root CA v2 O=Telia Finland Oyj -# Subject: CN=Telia Root CA v2 O=Telia Finland Oyj -# Label: "Telia Root CA v2" -# Serial: 7288924052977061235122729490515358 -# MD5 Fingerprint: 0e:8f:ac:aa:82:df:85:b1:f4:dc:10:1c:fc:99:d9:48 -# SHA1 Fingerprint: b9:99:cd:d1:73:50:8a:c4:47:05:08:9c:8c:88:fb:be:a0:2b:40:cd -# SHA256 Fingerprint: 24:2b:69:74:2f:cb:1e:5b:2a:bf:98:89:8b:94:57:21:87:54:4e:5b:4d:99:11:78:65:73:62:1f:6a:74:b8:2c ------BEGIN CERTIFICATE----- -MIIFdDCCA1ygAwIBAgIPAWdfJ9b+euPkrL4JWwWeMA0GCSqGSIb3DQEBCwUAMEQx -CzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZMBcGA1UE -AwwQVGVsaWEgUm9vdCBDQSB2MjAeFw0xODExMjkxMTU1NTRaFw00MzExMjkxMTU1 -NTRaMEQxCzAJBgNVBAYTAkZJMRowGAYDVQQKDBFUZWxpYSBGaW5sYW5kIE95ajEZ -MBcGA1UEAwwQVGVsaWEgUm9vdCBDQSB2MjCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBALLQPwe84nvQa5n44ndp586dpAO8gm2h/oFlH0wnrI4AuhZ76zBq -AMCzdGh+sq/H1WKzej9Qyow2RCRj0jbpDIX2Q3bVTKFgcmfiKDOlyzG4OiIjNLh9 -vVYiQJ3q9HsDrWj8soFPmNB06o3lfc1jw6P23pLCWBnglrvFxKk9pXSW/q/5iaq9 -lRdU2HhE8Qx3FZLgmEKnpNaqIJLNwaCzlrI6hEKNfdWV5Nbb6WLEWLN5xYzTNTOD -n3WhUidhOPFZPY5Q4L15POdslv5e2QJltI5c0BE0312/UqeBAMN/mUWZFdUXyApT -7GPzmX3MaRKGwhfwAZ6/hLzRUssbkmbOpFPlob/E2wnW5olWK8jjfN7j/4nlNW4o -6GwLI1GpJQXrSPjdscr6bAhR77cYbETKJuFzxokGgeWKrLDiKca5JLNrRBH0pUPC -TEPlcDaMtjNXepUugqD0XBCzYYP2AgWGLnwtbNwDRm41k9V6lS/eINhbfpSQBGq6 -WT0EBXWdN6IOLj3rwaRSg/7Qa9RmjtzG6RJOHSpXqhC8fF6CfaamyfItufUXJ63R -DolUK5X6wK0dmBR4M0KGCqlztft0DbcbMBnEWg4cJ7faGND/isgFuvGqHKI3t+ZI -pEYslOqodmJHixBTB0hXbOKSTbauBcvcwUpej6w9GU7C7WB1K9vBykLVAgMBAAGj -YzBhMB8GA1UdIwQYMBaAFHKs5DN5qkWH9v2sHZ7Wxy+G2CQ5MB0GA1UdDgQWBBRy -rOQzeapFh/b9rB2e1scvhtgkOTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUw -AwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAoDtZpwmUPjaE0n4vOaWWl/oRrfxn83EJ -8rKJhGdEr7nv7ZbsnGTbMjBvZ5qsfl+yqwE2foH65IRe0qw24GtixX1LDoJt0nZi -0f6X+J8wfBj5tFJ3gh1229MdqfDBmgC9bXXYfef6xzijnHDoRnkDry5023X4blMM -A8iZGok1GTzTyVR8qPAs5m4HeW9q4ebqkYJpCh3DflminmtGFZhb069GHWLIzoBS -SRE/yQQSwxN8PzuKlts8oB4KtItUsiRnDe+Cy748fdHif64W1lZYudogsYMVoe+K -TTJvQS8TUoKU1xrBeKJR3Stwbbca+few4GeXVtt8YVMJAygCQMez2P2ccGrGKMOF -6eLtGpOg3kuYooQ+BXcBlj37tCAPnHICehIv1aO6UXivKitEZU61/Qrowc15h2Er -3oBXRb9n8ZuRXqWk7FlIEA04x7D6w0RtBPV4UBySllva9bguulvP5fBqnUsvWHMt -Ty3EHD70sz+rFQ47GUGKpMFXEmZxTPpT41frYpUJnlTd0cI8Vzy9OK2YZLe4A5pT -VmBds9hCG1xLEooc6+t9xnppxyd/pPiL8uSUZodL6ZQHCRJ5irLrdATczvREWeAW -ysUsWNc8e89ihmpQfTU2Zqf7N+cox9jQraVplI/owd8k+BsHMYeB2F326CjYSlKA -rBPuUBQemMc= ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH -# Subject: CN=D-TRUST BR Root CA 1 2020 O=D-Trust GmbH -# Label: "D-TRUST BR Root CA 1 2020" -# Serial: 165870826978392376648679885835942448534 -# MD5 Fingerprint: b5:aa:4b:d5:ed:f7:e3:55:2e:8f:72:0a:f3:75:b8:ed -# SHA1 Fingerprint: 1f:5b:98:f0:e3:b5:f7:74:3c:ed:e6:b0:36:7d:32:cd:f4:09:41:67 -# SHA256 Fingerprint: e5:9a:aa:81:60:09:c2:2b:ff:5b:25:ba:d3:7d:f3:06:f0:49:79:7c:1f:81:d8:5a:b0:89:e6:57:bd:8f:00:44 ------BEGIN CERTIFICATE----- -MIIC2zCCAmCgAwIBAgIQfMmPK4TX3+oPyWWa00tNljAKBggqhkjOPQQDAzBIMQsw -CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS -VVNUIEJSIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTA5NDUwMFoXDTM1MDIxMTA5 -NDQ1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG -A1UEAxMZRC1UUlVTVCBCUiBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB -BAAiA2IABMbLxyjR+4T1mu9CFCDhQ2tuda38KwOE1HaTJddZO0Flax7mNCq7dPYS -zuht56vkPE4/RAiLzRZxy7+SmfSk1zxQVFKQhYN4lGdnoxwJGT11NIXe7WB9xwy0 -QVK5buXuQqOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHOREKv/ -VbNafAkl1bK6CKBrqx9tMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g -PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2JyX3Jvb3Rf -Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l -dC9DTj1ELVRSVVNUJTIwQlIlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 -c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO -PQQDAwNpADBmAjEAlJAtE/rhY/hhY+ithXhUkZy4kzg+GkHaQBZTQgjKL47xPoFW -wKrY7RjEsK70PvomAjEA8yjixtsrmfu3Ubgko6SUeho/5jbiA1czijDLgsfWFBHV -dWNbFJWcHwHP2NVypw87 ------END CERTIFICATE----- - -# Issuer: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH -# Subject: CN=D-TRUST EV Root CA 1 2020 O=D-Trust GmbH -# Label: "D-TRUST EV Root CA 1 2020" -# Serial: 126288379621884218666039612629459926992 -# MD5 Fingerprint: 8c:2d:9d:70:9f:48:99:11:06:11:fb:e9:cb:30:c0:6e -# SHA1 Fingerprint: 61:db:8c:21:59:69:03:90:d8:7c:9c:12:86:54:cf:9d:3d:f4:dd:07 -# SHA256 Fingerprint: 08:17:0d:1a:a3:64:53:90:1a:2f:95:92:45:e3:47:db:0c:8d:37:ab:aa:bc:56:b8:1a:a1:00:dc:95:89:70:db ------BEGIN CERTIFICATE----- -MIIC2zCCAmCgAwIBAgIQXwJB13qHfEwDo6yWjfv/0DAKBggqhkjOPQQDAzBIMQsw -CQYDVQQGEwJERTEVMBMGA1UEChMMRC1UcnVzdCBHbWJIMSIwIAYDVQQDExlELVRS -VVNUIEVWIFJvb3QgQ0EgMSAyMDIwMB4XDTIwMDIxMTEwMDAwMFoXDTM1MDIxMTA5 -NTk1OVowSDELMAkGA1UEBhMCREUxFTATBgNVBAoTDEQtVHJ1c3QgR21iSDEiMCAG -A1UEAxMZRC1UUlVTVCBFViBSb290IENBIDEgMjAyMDB2MBAGByqGSM49AgEGBSuB -BAAiA2IABPEL3YZDIBnfl4XoIkqbz52Yv7QFJsnL46bSj8WeeHsxiamJrSc8ZRCC -/N/DnU7wMyPE0jL1HLDfMxddxfCxivnvubcUyilKwg+pf3VlSSowZ/Rk99Yad9rD -wpdhQntJraOCAQ0wggEJMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFH8QARY3 -OqQo5FD4pPfsazK2/umLMA4GA1UdDwEB/wQEAwIBBjCBxgYDVR0fBIG+MIG7MD6g -PKA6hjhodHRwOi8vY3JsLmQtdHJ1c3QubmV0L2NybC9kLXRydXN0X2V2X3Jvb3Rf -Y2FfMV8yMDIwLmNybDB5oHegdYZzbGRhcDovL2RpcmVjdG9yeS5kLXRydXN0Lm5l -dC9DTj1ELVRSVVNUJTIwRVYlMjBSb290JTIwQ0ElMjAxJTIwMjAyMCxPPUQtVHJ1 -c3QlMjBHbWJILEM9REU/Y2VydGlmaWNhdGVyZXZvY2F0aW9ubGlzdDAKBggqhkjO -PQQDAwNpADBmAjEAyjzGKnXCXnViOTYAYFqLwZOZzNnbQTs7h5kXO9XMT8oi96CA -y/m0sRtW9XLS/BnRAjEAkfcwkz8QRitxpNA7RJvAKQIFskF3UfN5Wp6OFKBOQtJb -gfM0agPnIjhQW+0ZT0MW ------END CERTIFICATE----- - -# Issuer: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. -# Subject: CN=DigiCert TLS ECC P384 Root G5 O=DigiCert, Inc. -# Label: "DigiCert TLS ECC P384 Root G5" -# Serial: 13129116028163249804115411775095713523 -# MD5 Fingerprint: d3:71:04:6a:43:1c:db:a6:59:e1:a8:a3:aa:c5:71:ed -# SHA1 Fingerprint: 17:f3:de:5e:9f:0f:19:e9:8e:f6:1f:32:26:6e:20:c4:07:ae:30:ee -# SHA256 Fingerprint: 01:8e:13:f0:77:25:32:cf:80:9b:d1:b1:72:81:86:72:83:fc:48:c6:e1:3b:e9:c6:98:12:85:4a:49:0c:1b:05 ------BEGIN CERTIFICATE----- -MIICGTCCAZ+gAwIBAgIQCeCTZaz32ci5PhwLBCou8zAKBggqhkjOPQQDAzBOMQsw -CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJjAkBgNVBAMTHURp -Z2lDZXJ0IFRMUyBFQ0MgUDM4NCBSb290IEc1MB4XDTIxMDExNTAwMDAwMFoXDTQ2 -MDExNDIzNTk1OVowTjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ -bmMuMSYwJAYDVQQDEx1EaWdpQ2VydCBUTFMgRUNDIFAzODQgUm9vdCBHNTB2MBAG -ByqGSM49AgEGBSuBBAAiA2IABMFEoc8Rl1Ca3iOCNQfN0MsYndLxf3c1TzvdlHJS -7cI7+Oz6e2tYIOyZrsn8aLN1udsJ7MgT9U7GCh1mMEy7H0cKPGEQQil8pQgO4CLp -0zVozptjn4S1mU1YoI71VOeVyaNCMEAwHQYDVR0OBBYEFMFRRVBZqz7nLFr6ICIS -B4CIfBFqMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MAoGCCqGSM49 -BAMDA2gAMGUCMQCJao1H5+z8blUD2WdsJk6Dxv3J+ysTvLd6jLRl0mlpYxNjOyZQ -LgGheQaRnUi/wr4CMEfDFXuxoJGZSZOoPHzoRgaLLPIxAJSdYsiJvRmEFOml+wG4 -DXZDjC5Ty3zfDBeWUA== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. -# Subject: CN=DigiCert TLS RSA4096 Root G5 O=DigiCert, Inc. -# Label: "DigiCert TLS RSA4096 Root G5" -# Serial: 11930366277458970227240571539258396554 -# MD5 Fingerprint: ac:fe:f7:34:96:a9:f2:b3:b4:12:4b:e4:27:41:6f:e1 -# SHA1 Fingerprint: a7:88:49:dc:5d:7c:75:8c:8c:de:39:98:56:b3:aa:d0:b2:a5:71:35 -# SHA256 Fingerprint: 37:1a:00:dc:05:33:b3:72:1a:7e:eb:40:e8:41:9e:70:79:9d:2b:0a:0f:2c:1d:80:69:31:65:f7:ce:c4:ad:75 ------BEGIN CERTIFICATE----- -MIIFZjCCA06gAwIBAgIQCPm0eKj6ftpqMzeJ3nzPijANBgkqhkiG9w0BAQwFADBN -MQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xJTAjBgNVBAMT -HERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwHhcNMjEwMTE1MDAwMDAwWhcN -NDYwMTE0MjM1OTU5WjBNMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs -IEluYy4xJTAjBgNVBAMTHERpZ2lDZXJ0IFRMUyBSU0E0MDk2IFJvb3QgRzUwggIi -MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCz0PTJeRGd/fxmgefM1eS87IE+ -ajWOLrfn3q/5B03PMJ3qCQuZvWxX2hhKuHisOjmopkisLnLlvevxGs3npAOpPxG0 -2C+JFvuUAT27L/gTBaF4HI4o4EXgg/RZG5Wzrn4DReW+wkL+7vI8toUTmDKdFqgp -wgscONyfMXdcvyej/Cestyu9dJsXLfKB2l2w4SMXPohKEiPQ6s+d3gMXsUJKoBZM -pG2T6T867jp8nVid9E6P/DsjyG244gXazOvswzH016cpVIDPRFtMbzCe88zdH5RD -nU1/cHAN1DrRN/BsnZvAFJNY781BOHW8EwOVfH/jXOnVDdXifBBiqmvwPXbzP6Po -sMH976pXTayGpxi0KcEsDr9kvimM2AItzVwv8n/vFfQMFawKsPHTDU9qTXeXAaDx -Zre3zu/O7Oyldcqs4+Fj97ihBMi8ez9dLRYiVu1ISf6nL3kwJZu6ay0/nTvEF+cd -Lvvyz6b84xQslpghjLSR6Rlgg/IwKwZzUNWYOwbpx4oMYIwo+FKbbuH2TbsGJJvX -KyY//SovcfXWJL5/MZ4PbeiPT02jP/816t9JXkGPhvnxd3lLG7SjXi/7RgLQZhNe -XoVPzthwiHvOAbWWl9fNff2C+MIkwcoBOU+NosEUQB+cZtUMCUbW8tDRSHZWOkPL -tgoRObqME2wGtZ7P6wIDAQABo0IwQDAdBgNVHQ4EFgQUUTMc7TZArxfTJc1paPKv -TiM+s0EwDgYDVR0PAQH/BAQDAgGGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcN -AQEMBQADggIBAGCmr1tfV9qJ20tQqcQjNSH/0GEwhJG3PxDPJY7Jv0Y02cEhJhxw -GXIeo8mH/qlDZJY6yFMECrZBu8RHANmfGBg7sg7zNOok992vIGCukihfNudd5N7H -PNtQOa27PShNlnx2xlv0wdsUpasZYgcYQF+Xkdycx6u1UQ3maVNVzDl92sURVXLF -O4uJ+DQtpBflF+aZfTCIITfNMBc9uPK8qHWgQ9w+iUuQrm0D4ByjoJYJu32jtyoQ -REtGBzRj7TG5BO6jm5qu5jF49OokYTurWGT/u4cnYiWB39yhL/btp/96j1EuMPik -AdKFOV8BmZZvWltwGUb+hmA+rYAQCd05JS9Yf7vSdPD3Rh9GOUrYU9DzLjtxpdRv -/PNn5AeP3SYZ4Y1b+qOTEZvpyDrDVWiakuFSdjjo4bq9+0/V77PnSIMx8IIh47a+ -p6tv75/fTM8BuGJqIz3nCU2AG3swpMPdB380vqQmsvZB6Akd4yCYqjdP//fx4ilw -MUc/dNAUFvohigLVigmUdy7yWSiLfFCSCmZ4OIN1xLVaqBHG5cGdZlXPU8Sv13WF -qUITVuwhd4GTWgzqltlJyqEI8pc7bZsEGCREjnwB8twl2F6GmrE52/WRMmrRpnCK -ovfepEWFJqgejF0pW8hL2JpqA15w8oVPbEtoL8pU9ozaMv7Da4M/OMZ+ ------END CERTIFICATE----- - -# Issuer: CN=Certainly Root R1 O=Certainly -# Subject: CN=Certainly Root R1 O=Certainly -# Label: "Certainly Root R1" -# Serial: 188833316161142517227353805653483829216 -# MD5 Fingerprint: 07:70:d4:3e:82:87:a0:fa:33:36:13:f4:fa:33:e7:12 -# SHA1 Fingerprint: a0:50:ee:0f:28:71:f4:27:b2:12:6d:6f:50:96:25:ba:cc:86:42:af -# SHA256 Fingerprint: 77:b8:2c:d8:64:4c:43:05:f7:ac:c5:cb:15:6b:45:67:50:04:03:3d:51:c6:0c:62:02:a8:e0:c3:34:67:d3:a0 ------BEGIN CERTIFICATE----- -MIIFRzCCAy+gAwIBAgIRAI4P+UuQcWhlM1T01EQ5t+AwDQYJKoZIhvcNAQELBQAw -PTELMAkGA1UEBhMCVVMxEjAQBgNVBAoTCUNlcnRhaW5seTEaMBgGA1UEAxMRQ2Vy -dGFpbmx5IFJvb3QgUjEwHhcNMjEwNDAxMDAwMDAwWhcNNDYwNDAxMDAwMDAwWjA9 -MQswCQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0 -YWlubHkgUm9vdCBSMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANA2 -1B/q3avk0bbm+yLA3RMNansiExyXPGhjZjKcA7WNpIGD2ngwEc/csiu+kr+O5MQT -vqRoTNoCaBZ0vrLdBORrKt03H2As2/X3oXyVtwxwhi7xOu9S98zTm/mLvg7fMbed -aFySpvXl8wo0tf97ouSHocavFwDvA5HtqRxOcT3Si2yJ9HiG5mpJoM610rCrm/b0 -1C7jcvk2xusVtyWMOvwlDbMicyF0yEqWYZL1LwsYpfSt4u5BvQF5+paMjRcCMLT5 -r3gajLQ2EBAHBXDQ9DGQilHFhiZ5shGIXsXwClTNSaa/ApzSRKft43jvRl5tcdF5 -cBxGX1HpyTfcX35pe0HfNEXgO4T0oYoKNp43zGJS4YkNKPl6I7ENPT2a/Z2B7yyQ -wHtETrtJ4A5KVpK8y7XdeReJkd5hiXSSqOMyhb5OhaRLWcsrxXiOcVTQAjeZjOVJ -6uBUcqQRBi8LjMFbvrWhsFNunLhgkR9Za/kt9JQKl7XsxXYDVBtlUrpMklZRNaBA -2CnbrlJ2Oy0wQJuK0EJWtLeIAaSHO1OWzaMWj/Nmqhexx2DgwUMFDO6bW2BvBlyH -Wyf5QBGenDPBt+U1VwV/J84XIIwc/PH72jEpSe31C4SnT8H2TsIonPru4K8H+zMR -eiFPCyEQtkA6qyI6BJyLm4SGcprSp6XEtHWRqSsjAgMBAAGjQjBAMA4GA1UdDwEB -/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTgqj8ljZ9EXME66C6u -d0yEPmcM9DANBgkqhkiG9w0BAQsFAAOCAgEAuVevuBLaV4OPaAszHQNTVfSVcOQr -PbA56/qJYv331hgELyE03fFo8NWWWt7CgKPBjcZq91l3rhVkz1t5BXdm6ozTaw3d -8VkswTOlMIAVRQdFGjEitpIAq5lNOo93r6kiyi9jyhXWx8bwPWz8HA2YEGGeEaIi -1wrykXprOQ4vMMM2SZ/g6Q8CRFA3lFV96p/2O7qUpUzpvD5RtOjKkjZUbVwlKNrd -rRT90+7iIgXr0PK3aBLXWopBGsaSpVo7Y0VPv+E6dyIvXL9G+VoDhRNCX8reU9di -taY1BMJH/5n9hN9czulegChB8n3nHpDYT3Y+gjwN/KUD+nsa2UUeYNrEjvn8K8l7 -lcUq/6qJ34IxD3L/DCfXCh5WAFAeDJDBlrXYFIW7pw0WwfgHJBu6haEaBQmAupVj -yTrsJZ9/nbqkRxWbRHDxakvWOF5D8xh+UG7pWijmZeZ3Gzr9Hb4DJqPb1OG7fpYn -Kx3upPvaJVQTA945xsMfTZDsjxtK0hzthZU4UHlG1sGQUDGpXJpuHfUzVounmdLy -yCwzk5Iwx06MZTMQZBf9JBeW0Y3COmor6xOLRPIh80oat3df1+2IpHLlOR+Vnb5n -wXARPbv0+Em34yaXOp/SX3z7wJl8OSngex2/DaeP0ik0biQVy96QXr8axGbqwua6 -OV+KmalBWQewLK8= ------END CERTIFICATE----- - -# Issuer: CN=Certainly Root E1 O=Certainly -# Subject: CN=Certainly Root E1 O=Certainly -# Label: "Certainly Root E1" -# Serial: 8168531406727139161245376702891150584 -# MD5 Fingerprint: 0a:9e:ca:cd:3e:52:50:c6:36:f3:4b:a3:ed:a7:53:e9 -# SHA1 Fingerprint: f9:e1:6d:dc:01:89:cf:d5:82:45:63:3e:c5:37:7d:c2:eb:93:6f:2b -# SHA256 Fingerprint: b4:58:5f:22:e4:ac:75:6a:4e:86:12:a1:36:1c:5d:9d:03:1a:93:fd:84:fe:bb:77:8f:a3:06:8b:0f:c4:2d:c2 ------BEGIN CERTIFICATE----- -MIIB9zCCAX2gAwIBAgIQBiUzsUcDMydc+Y2aub/M+DAKBggqhkjOPQQDAzA9MQsw -CQYDVQQGEwJVUzESMBAGA1UEChMJQ2VydGFpbmx5MRowGAYDVQQDExFDZXJ0YWlu -bHkgUm9vdCBFMTAeFw0yMTA0MDEwMDAwMDBaFw00NjA0MDEwMDAwMDBaMD0xCzAJ -BgNVBAYTAlVTMRIwEAYDVQQKEwlDZXJ0YWlubHkxGjAYBgNVBAMTEUNlcnRhaW5s -eSBSb290IEUxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE3m/4fxzf7flHh4axpMCK -+IKXgOqPyEpeKn2IaKcBYhSRJHpcnqMXfYqGITQYUBsQ3tA3SybHGWCA6TS9YBk2 -QNYphwk8kXr2vBMj3VlOBF7PyAIcGFPBMdjaIOlEjeR2o0IwQDAOBgNVHQ8BAf8E -BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU8ygYy2R17ikq6+2uI1g4 -hevIIgcwCgYIKoZIzj0EAwMDaAAwZQIxALGOWiDDshliTd6wT99u0nCK8Z9+aozm -ut6Dacpps6kFtZaSF4fC0urQe87YQVt8rgIwRt7qy12a7DLCZRawTDBcMPPaTnOG -BtjOiQRINzf43TNRnXCve1XYAS59BWQOhriR ------END CERTIFICATE----- - -# Issuer: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center -# Subject: CN=E-Tugra Global Root CA RSA v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center -# Label: "E-Tugra Global Root CA RSA v3" -# Serial: 75951268308633135324246244059508261641472512052 -# MD5 Fingerprint: 22:be:10:f6:c2:f8:03:88:73:5f:33:29:47:28:47:a4 -# SHA1 Fingerprint: e9:a8:5d:22:14:52:1c:5b:aa:0a:b4:be:24:6a:23:8a:c9:ba:e2:a9 -# SHA256 Fingerprint: ef:66:b0:b1:0a:3c:db:9f:2e:36:48:c7:6b:d2:af:18:ea:d2:bf:e6:f1:17:65:5e:28:c4:06:0d:a1:a3:f4:c2 ------BEGIN CERTIFICATE----- -MIIF8zCCA9ugAwIBAgIUDU3FzRYilZYIfrgLfxUGNPt5EDQwDQYJKoZIhvcNAQEL -BQAwgYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUt -VHVncmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYw -JAYDVQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIFJTQSB2MzAeFw0yMDAzMTgw -OTA3MTdaFw00NTAzMTIwOTA3MTdaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMG -QW5rYXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1 -Z3JhIFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBD -QSBSU0EgdjMwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCiZvCJt3J7 -7gnJY9LTQ91ew6aEOErxjYG7FL1H6EAX8z3DeEVypi6Q3po61CBxyryfHUuXCscx -uj7X/iWpKo429NEvx7epXTPcMHD4QGxLsqYxYdE0PD0xesevxKenhOGXpOhL9hd8 -7jwH7eKKV9y2+/hDJVDqJ4GohryPUkqWOmAalrv9c/SF/YP9f4RtNGx/ardLAQO/ -rWm31zLZ9Vdq6YaCPqVmMbMWPcLzJmAy01IesGykNz709a/r4d+ABs8qQedmCeFL -l+d3vSFtKbZnwy1+7dZ5ZdHPOrbRsV5WYVB6Ws5OUDGAA5hH5+QYfERaxqSzO8bG -wzrwbMOLyKSRBfP12baqBqG3q+Sx6iEUXIOk/P+2UNOMEiaZdnDpwA+mdPy70Bt4 -znKS4iicvObpCdg604nmvi533wEKb5b25Y08TVJ2Glbhc34XrD2tbKNSEhhw5oBO -M/J+JjKsBY04pOZ2PJ8QaQ5tndLBeSBrW88zjdGUdjXnXVXHt6woq0bM5zshtQoK -5EpZ3IE1S0SVEgpnpaH/WwAH0sDM+T/8nzPyAPiMbIedBi3x7+PmBvrFZhNb/FAH -nnGGstpvdDDPk1Po3CLW3iAfYY2jLqN4MpBs3KwytQXk9TwzDdbgh3cXTJ2w2Amo -DVf3RIXwyAS+XF1a4xeOVGNpf0l0ZAWMowIDAQABo2MwYTAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFLK0ruYt9ybVqnUtdkvAG1Mh0EjvMB0GA1UdDgQWBBSy -tK7mLfcm1ap1LXZLwBtTIdBI7zAOBgNVHQ8BAf8EBAMCAQYwDQYJKoZIhvcNAQEL -BQADggIBAImocn+M684uGMQQgC0QDP/7FM0E4BQ8Tpr7nym/Ip5XuYJzEmMmtcyQ -6dIqKe6cLcwsmb5FJ+Sxce3kOJUxQfJ9emN438o2Fi+CiJ+8EUdPdk3ILY7r3y18 -Tjvarvbj2l0Upq7ohUSdBm6O++96SmotKygY/r+QLHUWnw/qln0F7psTpURs+APQ -3SPh/QMSEgj0GDSz4DcLdxEBSL9htLX4GdnLTeqjjO/98Aa1bZL0SmFQhO3sSdPk -vmjmLuMxC1QLGpLWgti2omU8ZgT5Vdps+9u1FGZNlIM7zR6mK7L+d0CGq+ffCsn9 -9t2HVhjYsCxVYJb6CH5SkPVLpi6HfMsg2wY+oF0Dd32iPBMbKaITVaA9FCKvb7jQ -mhty3QUBjYZgv6Rn7rWlDdF/5horYmbDB7rnoEgcOMPpRfunf/ztAmgayncSd6YA -VSgU7NbHEqIbZULpkejLPoeJVF3Zr52XnGnnCv8PWniLYypMfUeUP95L6VPQMPHF -9p5J3zugkaOj/s1YzOrfr28oO6Bpm4/srK4rVJ2bBLFHIK+WEj5jlB0E5y67hscM -moi/dkfv97ALl2bSRM9gUgfh1SxKOidhd8rXj+eHDjD/DLsE4mHDosiXYY60MGo8 -bcIHX0pzLz/5FooBZu+6kcpSV3uu1OYP3Qt6f4ueJiDPO++BcYNZ ------END CERTIFICATE----- - -# Issuer: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center -# Subject: CN=E-Tugra Global Root CA ECC v3 O=E-Tugra EBG A.S. OU=E-Tugra Trust Center -# Label: "E-Tugra Global Root CA ECC v3" -# Serial: 218504919822255052842371958738296604628416471745 -# MD5 Fingerprint: 46:bc:81:bb:f1:b5:1e:f7:4b:96:bc:14:e2:e7:27:64 -# SHA1 Fingerprint: 8a:2f:af:57:53:b1:b0:e6:a1:04:ec:5b:6a:69:71:6d:f6:1c:e2:84 -# SHA256 Fingerprint: 87:3f:46:85:fa:7f:56:36:25:25:2e:6d:36:bc:d7:f1:6f:c2:49:51:f2:64:e4:7e:1b:95:4f:49:08:cd:ca:13 ------BEGIN CERTIFICATE----- -MIICpTCCAiqgAwIBAgIUJkYZdzHhT28oNt45UYbm1JeIIsEwCgYIKoZIzj0EAwMw -gYAxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHEwZBbmthcmExGTAXBgNVBAoTEEUtVHVn -cmEgRUJHIEEuUy4xHTAbBgNVBAsTFEUtVHVncmEgVHJ1c3QgQ2VudGVyMSYwJAYD -VQQDEx1FLVR1Z3JhIEdsb2JhbCBSb290IENBIEVDQyB2MzAeFw0yMDAzMTgwOTQ2 -NThaFw00NTAzMTIwOTQ2NThaMIGAMQswCQYDVQQGEwJUUjEPMA0GA1UEBxMGQW5r -YXJhMRkwFwYDVQQKExBFLVR1Z3JhIEVCRyBBLlMuMR0wGwYDVQQLExRFLVR1Z3Jh -IFRydXN0IENlbnRlcjEmMCQGA1UEAxMdRS1UdWdyYSBHbG9iYWwgUm9vdCBDQSBF -Q0MgdjMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASOmCm/xxAeJ9urA8woLNheSBkQ -KczLWYHMjLiSF4mDKpL2w6QdTGLVn9agRtwcvHbB40fQWxPa56WzZkjnIZpKT4YK -fWzqTTKACrJ6CZtpS5iB4i7sAnCWH/31Rs7K3IKjYzBhMA8GA1UdEwEB/wQFMAMB -Af8wHwYDVR0jBBgwFoAU/4Ixcj75xGZsrTie0bBRiKWQzPUwHQYDVR0OBBYEFP+C -MXI++cRmbK04ntGwUYilkMz1MA4GA1UdDwEB/wQEAwIBBjAKBggqhkjOPQQDAwNp -ADBmAjEA5gVYaWHlLcoNy/EZCL3W/VGSGn5jVASQkZo1kTmZ+gepZpO6yGjUij/6 -7W4WAie3AjEA3VoXK3YdZUKWpqxdinlW2Iob35reX8dQj7FbcQwm32pAAOwzkSFx -vmjkI6TZraE3 ------END CERTIFICATE----- - -# Issuer: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD. -# Subject: CN=Security Communication RootCA3 O=SECOM Trust Systems CO.,LTD. -# Label: "Security Communication RootCA3" -# Serial: 16247922307909811815 -# MD5 Fingerprint: 1c:9a:16:ff:9e:5c:e0:4d:8a:14:01:f4:35:5d:29:26 -# SHA1 Fingerprint: c3:03:c8:22:74:92:e5:61:a2:9c:5f:79:91:2b:1e:44:13:91:30:3a -# SHA256 Fingerprint: 24:a5:5c:2a:b0:51:44:2d:06:17:76:65:41:23:9a:4a:d0:32:d7:c5:51:75:aa:34:ff:de:2f:bc:4f:5c:52:94 ------BEGIN CERTIFICATE----- -MIIFfzCCA2egAwIBAgIJAOF8N0D9G/5nMA0GCSqGSIb3DQEBDAUAMF0xCzAJBgNV -BAYTAkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMScw -JQYDVQQDEx5TZWN1cml0eSBDb21tdW5pY2F0aW9uIFJvb3RDQTMwHhcNMTYwNjE2 -MDYxNzE2WhcNMzgwMTE4MDYxNzE2WjBdMQswCQYDVQQGEwJKUDElMCMGA1UEChMc -U0VDT00gVHJ1c3QgU3lzdGVtcyBDTy4sTFRELjEnMCUGA1UEAxMeU2VjdXJpdHkg -Q29tbXVuaWNhdGlvbiBSb290Q0EzMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC -CgKCAgEA48lySfcw3gl8qUCBWNO0Ot26YQ+TUG5pPDXC7ltzkBtnTCHsXzW7OT4r -CmDvu20rhvtxosis5FaU+cmvsXLUIKx00rgVrVH+hXShuRD+BYD5UpOzQD11EKzA -lrenfna84xtSGc4RHwsENPXY9Wk8d/Nk9A2qhd7gCVAEF5aEt8iKvE1y/By7z/MG -TfmfZPd+pmaGNXHIEYBMwXFAWB6+oHP2/D5Q4eAvJj1+XCO1eXDe+uDRpdYMQXF7 -9+qMHIjH7Iv10S9VlkZ8WjtYO/u62C21Jdp6Ts9EriGmnpjKIG58u4iFW/vAEGK7 -8vknR+/RiTlDxN/e4UG/VHMgly1s2vPUB6PmudhvrvyMGS7TZ2crldtYXLVqAvO4 -g160a75BflcJdURQVc1aEWEhCmHCqYj9E7wtiS/NYeCVvsq1e+F7NGcLH7YMx3we -GVPKp7FKFSBWFHA9K4IsD50VHUeAR/94mQ4xr28+j+2GaR57GIgUssL8gjMunEst -+3A7caoreyYn8xrC3PsXuKHqy6C0rtOUfnrQq8PsOC0RLoi/1D+tEjtCrI8Cbn3M -0V9hvqG8OmpI6iZVIhZdXw3/JzOfGAN0iltSIEdrRU0id4xVJ/CvHozJgyJUt5rQ -T9nO/NkuHJYosQLTA70lUhw0Zk8jq/R3gpYd0VcwCBEF/VfR2ccCAwEAAaNCMEAw -HQYDVR0OBBYEFGQUfPxYchamCik0FW8qy7z8r6irMA4GA1UdDwEB/wQEAwIBBjAP -BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDAUAA4ICAQDcAiMI4u8hOscNtybS -YpOnpSNyByCCYN8Y11StaSWSntkUz5m5UoHPrmyKO1o5yGwBQ8IibQLwYs1OY0PA -FNr0Y/Dq9HHuTofjcan0yVflLl8cebsjqodEV+m9NU1Bu0soo5iyG9kLFwfl9+qd -9XbXv8S2gVj/yP9kaWJ5rW4OH3/uHWnlt3Jxs/6lATWUVCvAUm2PVcTJ0rjLyjQI -UYWg9by0F1jqClx6vWPGOi//lkkZhOpn2ASxYfQAW0q3nHE3GYV5v4GwxxMOdnE+ -OoAGrgYWp421wsTL/0ClXI2lyTrtcoHKXJg80jQDdwj98ClZXSEIx2C/pHF7uNke -gr4Jr2VvKKu/S7XuPghHJ6APbw+LP6yVGPO5DtxnVW5inkYO0QR4ynKudtml+LLf -iAlhi+8kTtFZP1rUPcmTPCtk9YENFpb3ksP+MW/oKjJ0DvRMmEoYDjBU1cXrvMUV -nuiZIesnKwkK2/HmcBhWuwzkvvnoEKQTkrgc4NtnHVMDpCKn3F2SEDzq//wbEBrD -2NCcnWXL0CsnMQMeNuE9dnUM/0Umud1RvCPHX9jYhxBAEg09ODfnRDwYwFMJZI// -1ZqmfHAuc1Uh6N//g7kdPjIe1qZ9LPFm6Vwdp6POXiUyK+OVrCoHzrQoeIY8Laad -TdJ0MN1kURXbg4NR16/9M51NZg== ------END CERTIFICATE----- - -# Issuer: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. -# Subject: CN=Security Communication ECC RootCA1 O=SECOM Trust Systems CO.,LTD. -# Label: "Security Communication ECC RootCA1" -# Serial: 15446673492073852651 -# MD5 Fingerprint: 7e:43:b0:92:68:ec:05:43:4c:98:ab:5d:35:2e:7e:86 -# SHA1 Fingerprint: b8:0e:26:a9:bf:d2:b2:3b:c0:ef:46:c9:ba:c7:bb:f6:1d:0d:41:41 -# SHA256 Fingerprint: e7:4f:bd:a5:5b:d5:64:c4:73:a3:6b:44:1a:a7:99:c8:a6:8e:07:74:40:e8:28:8b:9f:a1:e5:0e:4b:ba:ca:11 ------BEGIN CERTIFICATE----- -MIICODCCAb6gAwIBAgIJANZdm7N4gS7rMAoGCCqGSM49BAMDMGExCzAJBgNVBAYT -AkpQMSUwIwYDVQQKExxTRUNPTSBUcnVzdCBTeXN0ZW1zIENPLixMVEQuMSswKQYD -VQQDEyJTZWN1cml0eSBDb21tdW5pY2F0aW9uIEVDQyBSb290Q0ExMB4XDTE2MDYx -NjA1MTUyOFoXDTM4MDExODA1MTUyOFowYTELMAkGA1UEBhMCSlAxJTAjBgNVBAoT -HFNFQ09NIFRydXN0IFN5c3RlbXMgQ08uLExURC4xKzApBgNVBAMTIlNlY3VyaXR5 -IENvbW11bmljYXRpb24gRUNDIFJvb3RDQTEwdjAQBgcqhkjOPQIBBgUrgQQAIgNi -AASkpW9gAwPDvTH00xecK4R1rOX9PVdu12O/5gSJko6BnOPpR27KkBLIE+Cnnfdl -dB9sELLo5OnvbYUymUSxXv3MdhDYW72ixvnWQuRXdtyQwjWpS4g8EkdtXP9JTxpK -ULGjQjBAMB0GA1UdDgQWBBSGHOf+LaVKiwj+KBH6vqNm+GBZLzAOBgNVHQ8BAf8E -BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjAVXUI9/Lbu -9zuxNuie9sRGKEkz0FhDKmMpzE2xtHqiuQ04pV1IKv3LsnNdo4gIxwwCMQDAqy0O -be0YottT6SXbVQjgUMzfRGEWgqtJsLKB7HOHeLRMsmIbEvoWTSVLY70eN9k= ------END CERTIFICATE----- diff --git a/lib/certifi/core.py b/lib/certifi/core.py deleted file mode 100644 index de028981..00000000 --- a/lib/certifi/core.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -certifi.py -~~~~~~~~~~ - -This module returns the installation location of cacert.pem or its contents. -""" -import sys - - -if sys.version_info >= (3, 11): - - from importlib.resources import as_file, files - - _CACERT_CTX = None - _CACERT_PATH = None - - def where() -> str: - # This is slightly terrible, but we want to delay extracting the file - # in cases where we're inside of a zipimport situation until someone - # actually calls where(), but we don't want to re-extract the file - # on every call of where(), so we'll do it once then store it in a - # global variable. - global _CACERT_CTX - global _CACERT_PATH - if _CACERT_PATH is None: - # This is slightly janky, the importlib.resources API wants you to - # manage the cleanup of this file, so it doesn't actually return a - # path, it returns a context manager that will give you the path - # when you enter it and will do any cleanup when you leave it. In - # the common case of not needing a temporary file, it will just - # return the file system location and the __exit__() is a no-op. - # - # We also have to hold onto the actual context manager, because - # it will do the cleanup whenever it gets garbage collected, so - # we will also store that at the global level as well. - _CACERT_CTX = as_file(files("certifi").joinpath("cacert.pem")) - _CACERT_PATH = str(_CACERT_CTX.__enter__()) - - return _CACERT_PATH - - def contents() -> str: - return files("certifi").joinpath("cacert.pem").read_text(encoding="ascii") - -elif sys.version_info >= (3, 7): - - from importlib.resources import path as get_path, read_text - - _CACERT_CTX = None - _CACERT_PATH = None - - def where() -> str: - # This is slightly terrible, but we want to delay extracting the - # file in cases where we're inside of a zipimport situation until - # someone actually calls where(), but we don't want to re-extract - # the file on every call of where(), so we'll do it once then store - # it in a global variable. - global _CACERT_CTX - global _CACERT_PATH - if _CACERT_PATH is None: - # This is slightly janky, the importlib.resources API wants you - # to manage the cleanup of this file, so it doesn't actually - # return a path, it returns a context manager that will give - # you the path when you enter it and will do any cleanup when - # you leave it. In the common case of not needing a temporary - # file, it will just return the file system location and the - # __exit__() is a no-op. - # - # We also have to hold onto the actual context manager, because - # it will do the cleanup whenever it gets garbage collected, so - # we will also store that at the global level as well. - _CACERT_CTX = get_path("certifi", "cacert.pem") - _CACERT_PATH = str(_CACERT_CTX.__enter__()) - - return _CACERT_PATH - - def contents() -> str: - return read_text("certifi", "cacert.pem", encoding="ascii") - -else: - import os - import types - from typing import Union - - Package = Union[types.ModuleType, str] - Resource = Union[str, "os.PathLike"] - - # This fallback will work for Python versions prior to 3.7 that lack the - # importlib.resources module but relies on the existing `where` function - # so won't address issues with environments like PyOxidizer that don't set - # __file__ on modules. - def read_text( - package: Package, - resource: Resource, - encoding: str = 'utf-8', - errors: str = 'strict' - ) -> str: - with open(where(), encoding=encoding) as data: - return data.read() - - # If we don't have importlib.resources, then we will just do the old logic - # of assuming we're on the filesystem and munge the path directly. - def where() -> str: - f = os.path.dirname(__file__) - - return os.path.join(f, "cacert.pem") - - def contents() -> str: - return read_text("certifi", "cacert.pem", encoding="ascii") diff --git a/lib/certifi/py.typed b/lib/certifi/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/charset_normalizer-2.1.1.dist-info/INSTALLER b/lib/charset_normalizer-2.1.1.dist-info/INSTALLER deleted file mode 100644 index a1b589e3..00000000 --- a/lib/charset_normalizer-2.1.1.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/lib/charset_normalizer-2.1.1.dist-info/LICENSE b/lib/charset_normalizer-2.1.1.dist-info/LICENSE deleted file mode 100644 index ad82355b..00000000 --- a/lib/charset_normalizer-2.1.1.dist-info/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2019 TAHRI Ahmed R. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/lib/charset_normalizer-2.1.1.dist-info/METADATA b/lib/charset_normalizer-2.1.1.dist-info/METADATA deleted file mode 100644 index c6ebd7d4..00000000 --- a/lib/charset_normalizer-2.1.1.dist-info/METADATA +++ /dev/null @@ -1,269 +0,0 @@ -Metadata-Version: 2.1 -Name: charset-normalizer -Version: 2.1.1 -Summary: The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet. -Home-page: https://github.com/ousret/charset_normalizer -Author: Ahmed TAHRI @Ousret -Author-email: ahmed.tahri@cloudnursery.dev -License: MIT -Project-URL: Bug Reports, https://github.com/Ousret/charset_normalizer/issues -Project-URL: Documentation, https://charset-normalizer.readthedocs.io/en/latest -Keywords: encoding,i18n,txt,text,charset,charset-detector,normalization,unicode,chardet -Classifier: Development Status :: 5 - Production/Stable -Classifier: License :: OSI Approved :: MIT License -Classifier: Intended Audience :: Developers -Classifier: Topic :: Software Development :: Libraries :: Python Modules -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Topic :: Text Processing :: Linguistic -Classifier: Topic :: Utilities -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Typing :: Typed -Requires-Python: >=3.6.0 -Description-Content-Type: text/markdown -License-File: LICENSE -Provides-Extra: unicode_backport -Requires-Dist: unicodedata2 ; extra == 'unicode_backport' - - -

Charset Detection, for Everyone 👋

- -

- The Real First Universal Charset Detector
- - - - - - - - Download Count Total - -

- -> A library that helps you read text from an unknown charset encoding.
Motivated by `chardet`, -> I'm trying to resolve the issue by taking a new approach. -> All IANA character set names for which the Python core library provides codecs are supported. - -

- >>>>> 👉 Try Me Online Now, Then Adopt Me 👈 <<<<< -

- -This project offers you an alternative to **Universal Charset Encoding Detector**, also known as **Chardet**. - -| Feature | [Chardet](https://github.com/chardet/chardet) | Charset Normalizer | [cChardet](https://github.com/PyYoshi/cChardet) | -| ------------- | :-------------: | :------------------: | :------------------: | -| `Fast` | ❌
| ✅
| ✅
| -| `Universal**` | ❌ | ✅ | ❌ | -| `Reliable` **without** distinguishable standards | ❌ | ✅ | ✅ | -| `Reliable` **with** distinguishable standards | ✅ | ✅ | ✅ | -| `License` | LGPL-2.1
_restrictive_ | MIT | MPL-1.1
_restrictive_ | -| `Native Python` | ✅ | ✅ | ❌ | -| `Detect spoken language` | ❌ | ✅ | N/A | -| `UnicodeDecodeError Safety` | ❌ | ✅ | ❌ | -| `Whl Size` | 193.6 kB | 39.5 kB | ~200 kB | -| `Supported Encoding` | 33 | :tada: [93](https://charset-normalizer.readthedocs.io/en/latest/user/support.html#supported-encodings) | 40 - -

-Reading Normalized TextCat Reading Text - -*\*\* : They are clearly using specific code for a specific encoding even if covering most of used one*
-Did you got there because of the logs? See [https://charset-normalizer.readthedocs.io/en/latest/user/miscellaneous.html](https://charset-normalizer.readthedocs.io/en/latest/user/miscellaneous.html) - -## ⭐ Your support - -*Fork, test-it, star-it, submit your ideas! We do listen.* - -## ⚡ Performance - -This package offer better performance than its counterpart Chardet. Here are some numbers. - -| Package | Accuracy | Mean per file (ms) | File per sec (est) | -| ------------- | :-------------: | :------------------: | :------------------: | -| [chardet](https://github.com/chardet/chardet) | 86 % | 200 ms | 5 file/sec | -| charset-normalizer | **98 %** | **39 ms** | 26 file/sec | - -| Package | 99th percentile | 95th percentile | 50th percentile | -| ------------- | :-------------: | :------------------: | :------------------: | -| [chardet](https://github.com/chardet/chardet) | 1200 ms | 287 ms | 23 ms | -| charset-normalizer | 400 ms | 200 ms | 15 ms | - -Chardet's performance on larger file (1MB+) are very poor. Expect huge difference on large payload. - -> Stats are generated using 400+ files using default parameters. More details on used files, see GHA workflows. -> And yes, these results might change at any time. The dataset can be updated to include more files. -> The actual delays heavily depends on your CPU capabilities. The factors should remain the same. -> Keep in mind that the stats are generous and that Chardet accuracy vs our is measured using Chardet initial capability -> (eg. Supported Encoding) Challenge-them if you want. - -[cchardet](https://github.com/PyYoshi/cChardet) is a non-native (cpp binding) and unmaintained faster alternative with -a better accuracy than chardet but lower than this package. If speed is the most important factor, you should try it. - -## ✨ Installation - -Using PyPi for latest stable -```sh -pip install charset-normalizer -U -``` - -If you want a more up-to-date `unicodedata` than the one available in your Python setup. -```sh -pip install charset-normalizer[unicode_backport] -U -``` - -## 🚀 Basic Usage - -### CLI -This package comes with a CLI. - -``` -usage: normalizer [-h] [-v] [-a] [-n] [-m] [-r] [-f] [-t THRESHOLD] - file [file ...] - -The Real First Universal Charset Detector. Discover originating encoding used -on text file. Normalize text to unicode. - -positional arguments: - files File(s) to be analysed - -optional arguments: - -h, --help show this help message and exit - -v, --verbose Display complementary information about file if any. - Stdout will contain logs about the detection process. - -a, --with-alternative - Output complementary possibilities if any. Top-level - JSON WILL be a list. - -n, --normalize Permit to normalize input file. If not set, program - does not write anything. - -m, --minimal Only output the charset detected to STDOUT. Disabling - JSON output. - -r, --replace Replace file when trying to normalize it instead of - creating a new one. - -f, --force Replace file without asking if you are sure, use this - flag with caution. - -t THRESHOLD, --threshold THRESHOLD - Define a custom maximum amount of chaos allowed in - decoded content. 0. <= chaos <= 1. - --version Show version information and exit. -``` - -```bash -normalizer ./data/sample.1.fr.srt -``` - -:tada: Since version 1.4.0 the CLI produce easily usable stdout result in JSON format. - -```json -{ - "path": "/home/default/projects/charset_normalizer/data/sample.1.fr.srt", - "encoding": "cp1252", - "encoding_aliases": [ - "1252", - "windows_1252" - ], - "alternative_encodings": [ - "cp1254", - "cp1256", - "cp1258", - "iso8859_14", - "iso8859_15", - "iso8859_16", - "iso8859_3", - "iso8859_9", - "latin_1", - "mbcs" - ], - "language": "French", - "alphabets": [ - "Basic Latin", - "Latin-1 Supplement" - ], - "has_sig_or_bom": false, - "chaos": 0.149, - "coherence": 97.152, - "unicode_path": null, - "is_preferred": true -} -``` - -### Python -*Just print out normalized text* -```python -from charset_normalizer import from_path - -results = from_path('./my_subtitle.srt') - -print(str(results.best())) -``` - -*Normalize any text file* -```python -from charset_normalizer import normalize -try: - normalize('./my_subtitle.srt') # should write to disk my_subtitle-***.srt -except IOError as e: - print('Sadly, we are unable to perform charset normalization.', str(e)) -``` - -*Upgrade your code without effort* -```python -from charset_normalizer import detect -``` - -The above code will behave the same as **chardet**. We ensure that we offer the best (reasonable) BC result possible. - -See the docs for advanced usage : [readthedocs.io](https://charset-normalizer.readthedocs.io/en/latest/) - -## 😇 Why - -When I started using Chardet, I noticed that it was not suited to my expectations, and I wanted to propose a -reliable alternative using a completely different method. Also! I never back down on a good challenge! - -I **don't care** about the **originating charset** encoding, because **two different tables** can -produce **two identical rendered string.** -What I want is to get readable text, the best I can. - -In a way, **I'm brute forcing text decoding.** How cool is that ? 😎 - -Don't confuse package **ftfy** with charset-normalizer or chardet. ftfy goal is to repair unicode string whereas charset-normalizer to convert raw file in unknown encoding to unicode. - -## 🍰 How - - - Discard all charset encoding table that could not fit the binary content. - - Measure chaos, or the mess once opened (by chunks) with a corresponding charset encoding. - - Extract matches with the lowest mess detected. - - Additionally, we measure coherence / probe for a language. - -**Wait a minute**, what is chaos/mess and coherence according to **YOU ?** - -*Chaos :* I opened hundred of text files, **written by humans**, with the wrong encoding table. **I observed**, then -**I established** some ground rules about **what is obvious** when **it seems like** a mess. - I know that my interpretation of what is chaotic is very subjective, feel free to contribute in order to - improve or rewrite it. - -*Coherence :* For each language there is on earth, we have computed ranked letter appearance occurrences (the best we can). So I thought -that intel is worth something here. So I use those records against decoded text to check if I can detect intelligent design. - -## ⚡ Known limitations - - - Language detection is unreliable when text contains two or more languages sharing identical letters. (eg. HTML (english tags) + Turkish content (Sharing Latin characters)) - - Every charset detector heavily depends on sufficient content. In common cases, do not bother run detection on very tiny content. - -## 👤 Contributing - -Contributions, issues and feature requests are very much welcome.
-Feel free to check [issues page](https://github.com/ousret/charset_normalizer/issues) if you want to contribute. - -## 📝 License - -Copyright © 2019 [Ahmed TAHRI @Ousret](https://github.com/Ousret).
-This project is [MIT](https://github.com/Ousret/charset_normalizer/blob/master/LICENSE) licensed. - -Characters frequencies used in this project © 2012 [Denny Vrandečić](http://simia.net/letters/) diff --git a/lib/charset_normalizer-2.1.1.dist-info/RECORD b/lib/charset_normalizer-2.1.1.dist-info/RECORD deleted file mode 100644 index 798f095c..00000000 --- a/lib/charset_normalizer-2.1.1.dist-info/RECORD +++ /dev/null @@ -1,33 +0,0 @@ -../../bin/normalizer.exe,sha256=RkhMVjRGK1fEk5XgxY_2_wgGJr8OCruhhOZXJLJDSM4,108449 -charset_normalizer-2.1.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -charset_normalizer-2.1.1.dist-info/LICENSE,sha256=6zGgxaT7Cbik4yBV0lweX5w1iidS_vPNcgIT0cz-4kE,1070 -charset_normalizer-2.1.1.dist-info/METADATA,sha256=C99l12g4d1E9_UiW-mqPCWx7v2M_lYGWxy1GTOjXSsA,11942 -charset_normalizer-2.1.1.dist-info/RECORD,, -charset_normalizer-2.1.1.dist-info/WHEEL,sha256=G16H4A3IeoQmnOrYV4ueZGKSjhipXx8zc8nu9FGlvMA,92 -charset_normalizer-2.1.1.dist-info/entry_points.txt,sha256=uYo8aIGLWv8YgWfSna5HnfY_En4pkF1w4bgawNAXzP0,76 -charset_normalizer-2.1.1.dist-info/top_level.txt,sha256=7ASyzePr8_xuZWJsnqJjIBtyV8vhEo0wBCv1MPRRi3Q,19 -charset_normalizer/__init__.py,sha256=jGhhf1IcOgCpZsr593E9fPvjWKnflVqHe_LwkOJjInU,1790 -charset_normalizer/__pycache__/__init__.cpython-311.pyc,, -charset_normalizer/__pycache__/api.cpython-311.pyc,, -charset_normalizer/__pycache__/cd.cpython-311.pyc,, -charset_normalizer/__pycache__/constant.cpython-311.pyc,, -charset_normalizer/__pycache__/legacy.cpython-311.pyc,, -charset_normalizer/__pycache__/md.cpython-311.pyc,, -charset_normalizer/__pycache__/models.cpython-311.pyc,, -charset_normalizer/__pycache__/utils.cpython-311.pyc,, -charset_normalizer/__pycache__/version.cpython-311.pyc,, -charset_normalizer/api.py,sha256=euVPmjAMbjpqhEHPjfKtyy1mK52U0TOUBUQgM_Qy6eE,19191 -charset_normalizer/assets/__init__.py,sha256=r7aakPaRIc2FFG2mw2V8NOTvkl25_euKZ3wPf5SAVa4,15222 -charset_normalizer/assets/__pycache__/__init__.cpython-311.pyc,, -charset_normalizer/cd.py,sha256=Pxdkbn4cy0iZF42KTb1FiWIqqKobuz_fDjGwc6JMNBc,10811 -charset_normalizer/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -charset_normalizer/cli/__pycache__/__init__.cpython-311.pyc,, -charset_normalizer/cli/__pycache__/normalizer.cpython-311.pyc,, -charset_normalizer/cli/normalizer.py,sha256=FmD1RXeMpRBg_mjR0MaJhNUpM2qZ8wz2neAE7AayBeg,9521 -charset_normalizer/constant.py,sha256=NgU-pY8JH2a9lkVT8oKwAFmIUYNKOuSBwZgF9MrlNCM,19157 -charset_normalizer/legacy.py,sha256=XKeZOts_HdYQU_Jb3C9ZfOjY2CiUL132k9_nXer8gig,3384 -charset_normalizer/md.py,sha256=pZP8IVpSC82D8INA9Tf_y0ijJSRI-UIncZvLdfTWEd4,17642 -charset_normalizer/models.py,sha256=i68YdlSLTEI3EEBVXq8TLNAbyyjrLC2OWszc-OBAk9I,13167 -charset_normalizer/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -charset_normalizer/utils.py,sha256=ykOznhcAeH-ODLBWJuI7t1nbwa1SAfN_bDYTCJGyh4U,11771 -charset_normalizer/version.py,sha256=_eh2MA3qS__IajlePQxKBmlw6zaBDvPYlLdEgxgIojw,79 diff --git a/lib/charset_normalizer-2.1.1.dist-info/WHEEL b/lib/charset_normalizer-2.1.1.dist-info/WHEEL deleted file mode 100644 index becc9a66..00000000 --- a/lib/charset_normalizer-2.1.1.dist-info/WHEEL +++ /dev/null @@ -1,5 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.37.1) -Root-Is-Purelib: true -Tag: py3-none-any - diff --git a/lib/charset_normalizer-2.1.1.dist-info/entry_points.txt b/lib/charset_normalizer-2.1.1.dist-info/entry_points.txt deleted file mode 100644 index a06d3600..00000000 --- a/lib/charset_normalizer-2.1.1.dist-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -normalizer = charset_normalizer.cli.normalizer:cli_detect diff --git a/lib/charset_normalizer-2.1.1.dist-info/top_level.txt b/lib/charset_normalizer-2.1.1.dist-info/top_level.txt deleted file mode 100644 index 66958f0a..00000000 --- a/lib/charset_normalizer-2.1.1.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -charset_normalizer diff --git a/lib/charset_normalizer/__init__.py b/lib/charset_normalizer/__init__.py deleted file mode 100644 index 2dcaf56f..00000000 --- a/lib/charset_normalizer/__init__.py +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Charset-Normalizer -~~~~~~~~~~~~~~ -The Real First Universal Charset Detector. -A library that helps you read text from an unknown charset encoding. -Motivated by chardet, This package is trying to resolve the issue by taking a new approach. -All IANA character set names for which the Python core library provides codecs are supported. - -Basic usage: - >>> from charset_normalizer import from_bytes - >>> results = from_bytes('Bсеки човек има право на образование. Oбразованието!'.encode('utf_8')) - >>> best_guess = results.best() - >>> str(best_guess) - 'Bсеки човек има право на образование. Oбразованието!' - -Others methods and usages are available - see the full documentation -at . -:copyright: (c) 2021 by Ahmed TAHRI -:license: MIT, see LICENSE for more details. -""" -import logging - -from .api import from_bytes, from_fp, from_path, normalize -from .legacy import ( - CharsetDetector, - CharsetDoctor, - CharsetNormalizerMatch, - CharsetNormalizerMatches, - detect, -) -from .models import CharsetMatch, CharsetMatches -from .utils import set_logging_handler -from .version import VERSION, __version__ - -__all__ = ( - "from_fp", - "from_path", - "from_bytes", - "normalize", - "detect", - "CharsetMatch", - "CharsetMatches", - "CharsetNormalizerMatch", - "CharsetNormalizerMatches", - "CharsetDetector", - "CharsetDoctor", - "__version__", - "VERSION", - "set_logging_handler", -) - -# Attach a NullHandler to the top level logger by default -# https://docs.python.org/3.3/howto/logging.html#configuring-logging-for-a-library - -logging.getLogger("charset_normalizer").addHandler(logging.NullHandler()) diff --git a/lib/charset_normalizer/api.py b/lib/charset_normalizer/api.py deleted file mode 100644 index 72907f94..00000000 --- a/lib/charset_normalizer/api.py +++ /dev/null @@ -1,584 +0,0 @@ -import logging -import warnings -from os import PathLike -from os.path import basename, splitext -from typing import Any, BinaryIO, List, Optional, Set - -from .cd import ( - coherence_ratio, - encoding_languages, - mb_encoding_languages, - merge_coherence_ratios, -) -from .constant import IANA_SUPPORTED, TOO_BIG_SEQUENCE, TOO_SMALL_SEQUENCE, TRACE -from .md import mess_ratio -from .models import CharsetMatch, CharsetMatches -from .utils import ( - any_specified_encoding, - cut_sequence_chunks, - iana_name, - identify_sig_or_bom, - is_cp_similar, - is_multi_byte_encoding, - should_strip_sig_or_bom, -) - -# Will most likely be controversial -# logging.addLevelName(TRACE, "TRACE") -logger = logging.getLogger("charset_normalizer") -explain_handler = logging.StreamHandler() -explain_handler.setFormatter( - logging.Formatter("%(asctime)s | %(levelname)s | %(message)s") -) - - -def from_bytes( - sequences: bytes, - steps: int = 5, - chunk_size: int = 512, - threshold: float = 0.2, - cp_isolation: Optional[List[str]] = None, - cp_exclusion: Optional[List[str]] = None, - preemptive_behaviour: bool = True, - explain: bool = False, -) -> CharsetMatches: - """ - Given a raw bytes sequence, return the best possibles charset usable to render str objects. - If there is no results, it is a strong indicator that the source is binary/not text. - By default, the process will extract 5 blocs of 512o each to assess the mess and coherence of a given sequence. - And will give up a particular code page after 20% of measured mess. Those criteria are customizable at will. - - The preemptive behavior DOES NOT replace the traditional detection workflow, it prioritize a particular code page - but never take it for granted. Can improve the performance. - - You may want to focus your attention to some code page or/and not others, use cp_isolation and cp_exclusion for that - purpose. - - This function will strip the SIG in the payload/sequence every time except on UTF-16, UTF-32. - By default the library does not setup any handler other than the NullHandler, if you choose to set the 'explain' - toggle to True it will alter the logger configuration to add a StreamHandler that is suitable for debugging. - Custom logging format and handler can be set manually. - """ - - if not isinstance(sequences, (bytearray, bytes)): - raise TypeError( - "Expected object of type bytes or bytearray, got: {0}".format( - type(sequences) - ) - ) - - if explain: - previous_logger_level: int = logger.level - logger.addHandler(explain_handler) - logger.setLevel(TRACE) - - length: int = len(sequences) - - if length == 0: - logger.debug("Encoding detection on empty bytes, assuming utf_8 intention.") - if explain: - logger.removeHandler(explain_handler) - logger.setLevel(previous_logger_level or logging.WARNING) - return CharsetMatches([CharsetMatch(sequences, "utf_8", 0.0, False, [], "")]) - - if cp_isolation is not None: - logger.log( - TRACE, - "cp_isolation is set. use this flag for debugging purpose. " - "limited list of encoding allowed : %s.", - ", ".join(cp_isolation), - ) - cp_isolation = [iana_name(cp, False) for cp in cp_isolation] - else: - cp_isolation = [] - - if cp_exclusion is not None: - logger.log( - TRACE, - "cp_exclusion is set. use this flag for debugging purpose. " - "limited list of encoding excluded : %s.", - ", ".join(cp_exclusion), - ) - cp_exclusion = [iana_name(cp, False) for cp in cp_exclusion] - else: - cp_exclusion = [] - - if length <= (chunk_size * steps): - logger.log( - TRACE, - "override steps (%i) and chunk_size (%i) as content does not fit (%i byte(s) given) parameters.", - steps, - chunk_size, - length, - ) - steps = 1 - chunk_size = length - - if steps > 1 and length / steps < chunk_size: - chunk_size = int(length / steps) - - is_too_small_sequence: bool = len(sequences) < TOO_SMALL_SEQUENCE - is_too_large_sequence: bool = len(sequences) >= TOO_BIG_SEQUENCE - - if is_too_small_sequence: - logger.log( - TRACE, - "Trying to detect encoding from a tiny portion of ({}) byte(s).".format( - length - ), - ) - elif is_too_large_sequence: - logger.log( - TRACE, - "Using lazy str decoding because the payload is quite large, ({}) byte(s).".format( - length - ), - ) - - prioritized_encodings: List[str] = [] - - specified_encoding: Optional[str] = ( - any_specified_encoding(sequences) if preemptive_behaviour else None - ) - - if specified_encoding is not None: - prioritized_encodings.append(specified_encoding) - logger.log( - TRACE, - "Detected declarative mark in sequence. Priority +1 given for %s.", - specified_encoding, - ) - - tested: Set[str] = set() - tested_but_hard_failure: List[str] = [] - tested_but_soft_failure: List[str] = [] - - fallback_ascii: Optional[CharsetMatch] = None - fallback_u8: Optional[CharsetMatch] = None - fallback_specified: Optional[CharsetMatch] = None - - results: CharsetMatches = CharsetMatches() - - sig_encoding, sig_payload = identify_sig_or_bom(sequences) - - if sig_encoding is not None: - prioritized_encodings.append(sig_encoding) - logger.log( - TRACE, - "Detected a SIG or BOM mark on first %i byte(s). Priority +1 given for %s.", - len(sig_payload), - sig_encoding, - ) - - prioritized_encodings.append("ascii") - - if "utf_8" not in prioritized_encodings: - prioritized_encodings.append("utf_8") - - for encoding_iana in prioritized_encodings + IANA_SUPPORTED: - - if cp_isolation and encoding_iana not in cp_isolation: - continue - - if cp_exclusion and encoding_iana in cp_exclusion: - continue - - if encoding_iana in tested: - continue - - tested.add(encoding_iana) - - decoded_payload: Optional[str] = None - bom_or_sig_available: bool = sig_encoding == encoding_iana - strip_sig_or_bom: bool = bom_or_sig_available and should_strip_sig_or_bom( - encoding_iana - ) - - if encoding_iana in {"utf_16", "utf_32"} and not bom_or_sig_available: - logger.log( - TRACE, - "Encoding %s wont be tested as-is because it require a BOM. Will try some sub-encoder LE/BE.", - encoding_iana, - ) - continue - - try: - is_multi_byte_decoder: bool = is_multi_byte_encoding(encoding_iana) - except (ModuleNotFoundError, ImportError): - logger.log( - TRACE, - "Encoding %s does not provide an IncrementalDecoder", - encoding_iana, - ) - continue - - try: - if is_too_large_sequence and is_multi_byte_decoder is False: - str( - sequences[: int(50e4)] - if strip_sig_or_bom is False - else sequences[len(sig_payload) : int(50e4)], - encoding=encoding_iana, - ) - else: - decoded_payload = str( - sequences - if strip_sig_or_bom is False - else sequences[len(sig_payload) :], - encoding=encoding_iana, - ) - except (UnicodeDecodeError, LookupError) as e: - if not isinstance(e, LookupError): - logger.log( - TRACE, - "Code page %s does not fit given bytes sequence at ALL. %s", - encoding_iana, - str(e), - ) - tested_but_hard_failure.append(encoding_iana) - continue - - similar_soft_failure_test: bool = False - - for encoding_soft_failed in tested_but_soft_failure: - if is_cp_similar(encoding_iana, encoding_soft_failed): - similar_soft_failure_test = True - break - - if similar_soft_failure_test: - logger.log( - TRACE, - "%s is deemed too similar to code page %s and was consider unsuited already. Continuing!", - encoding_iana, - encoding_soft_failed, - ) - continue - - r_ = range( - 0 if not bom_or_sig_available else len(sig_payload), - length, - int(length / steps), - ) - - multi_byte_bonus: bool = ( - is_multi_byte_decoder - and decoded_payload is not None - and len(decoded_payload) < length - ) - - if multi_byte_bonus: - logger.log( - TRACE, - "Code page %s is a multi byte encoding table and it appear that at least one character " - "was encoded using n-bytes.", - encoding_iana, - ) - - max_chunk_gave_up: int = int(len(r_) / 4) - - max_chunk_gave_up = max(max_chunk_gave_up, 2) - early_stop_count: int = 0 - lazy_str_hard_failure = False - - md_chunks: List[str] = [] - md_ratios = [] - - try: - for chunk in cut_sequence_chunks( - sequences, - encoding_iana, - r_, - chunk_size, - bom_or_sig_available, - strip_sig_or_bom, - sig_payload, - is_multi_byte_decoder, - decoded_payload, - ): - md_chunks.append(chunk) - - md_ratios.append(mess_ratio(chunk, threshold)) - - if md_ratios[-1] >= threshold: - early_stop_count += 1 - - if (early_stop_count >= max_chunk_gave_up) or ( - bom_or_sig_available and strip_sig_or_bom is False - ): - break - except UnicodeDecodeError as e: # Lazy str loading may have missed something there - logger.log( - TRACE, - "LazyStr Loading: After MD chunk decode, code page %s does not fit given bytes sequence at ALL. %s", - encoding_iana, - str(e), - ) - early_stop_count = max_chunk_gave_up - lazy_str_hard_failure = True - - # We might want to check the sequence again with the whole content - # Only if initial MD tests passes - if ( - not lazy_str_hard_failure - and is_too_large_sequence - and not is_multi_byte_decoder - ): - try: - sequences[int(50e3) :].decode(encoding_iana, errors="strict") - except UnicodeDecodeError as e: - logger.log( - TRACE, - "LazyStr Loading: After final lookup, code page %s does not fit given bytes sequence at ALL. %s", - encoding_iana, - str(e), - ) - tested_but_hard_failure.append(encoding_iana) - continue - - mean_mess_ratio: float = sum(md_ratios) / len(md_ratios) if md_ratios else 0.0 - if mean_mess_ratio >= threshold or early_stop_count >= max_chunk_gave_up: - tested_but_soft_failure.append(encoding_iana) - logger.log( - TRACE, - "%s was excluded because of initial chaos probing. Gave up %i time(s). " - "Computed mean chaos is %f %%.", - encoding_iana, - early_stop_count, - round(mean_mess_ratio * 100, ndigits=3), - ) - # Preparing those fallbacks in case we got nothing. - if ( - encoding_iana in ["ascii", "utf_8", specified_encoding] - and not lazy_str_hard_failure - ): - fallback_entry = CharsetMatch( - sequences, encoding_iana, threshold, False, [], decoded_payload - ) - if encoding_iana == specified_encoding: - fallback_specified = fallback_entry - elif encoding_iana == "ascii": - fallback_ascii = fallback_entry - else: - fallback_u8 = fallback_entry - continue - - logger.log( - TRACE, - "%s passed initial chaos probing. Mean measured chaos is %f %%", - encoding_iana, - round(mean_mess_ratio * 100, ndigits=3), - ) - - if not is_multi_byte_decoder: - target_languages: List[str] = encoding_languages(encoding_iana) - else: - target_languages = mb_encoding_languages(encoding_iana) - - if target_languages: - logger.log( - TRACE, - "{} should target any language(s) of {}".format( - encoding_iana, str(target_languages) - ), - ) - - cd_ratios = [] - - # We shall skip the CD when its about ASCII - # Most of the time its not relevant to run "language-detection" on it. - if encoding_iana != "ascii": - for chunk in md_chunks: - chunk_languages = coherence_ratio( - chunk, 0.1, ",".join(target_languages) if target_languages else None - ) - - cd_ratios.append(chunk_languages) - - cd_ratios_merged = merge_coherence_ratios(cd_ratios) - - if cd_ratios_merged: - logger.log( - TRACE, - "We detected language {} using {}".format( - cd_ratios_merged, encoding_iana - ), - ) - - results.append( - CharsetMatch( - sequences, - encoding_iana, - mean_mess_ratio, - bom_or_sig_available, - cd_ratios_merged, - decoded_payload, - ) - ) - - if ( - encoding_iana in [specified_encoding, "ascii", "utf_8"] - and mean_mess_ratio < 0.1 - ): - logger.debug( - "Encoding detection: %s is most likely the one.", encoding_iana - ) - if explain: - logger.removeHandler(explain_handler) - logger.setLevel(previous_logger_level) - return CharsetMatches([results[encoding_iana]]) - - if encoding_iana == sig_encoding: - logger.debug( - "Encoding detection: %s is most likely the one as we detected a BOM or SIG within " - "the beginning of the sequence.", - encoding_iana, - ) - if explain: - logger.removeHandler(explain_handler) - logger.setLevel(previous_logger_level) - return CharsetMatches([results[encoding_iana]]) - - if len(results) == 0: - if fallback_u8 or fallback_ascii or fallback_specified: - logger.log( - TRACE, - "Nothing got out of the detection process. Using ASCII/UTF-8/Specified fallback.", - ) - - if fallback_specified: - logger.debug( - "Encoding detection: %s will be used as a fallback match", - fallback_specified.encoding, - ) - results.append(fallback_specified) - elif ( - (fallback_u8 and fallback_ascii is None) - or ( - fallback_u8 - and fallback_ascii - and fallback_u8.fingerprint != fallback_ascii.fingerprint - ) - or (fallback_u8 is not None) - ): - logger.debug("Encoding detection: utf_8 will be used as a fallback match") - results.append(fallback_u8) - elif fallback_ascii: - logger.debug("Encoding detection: ascii will be used as a fallback match") - results.append(fallback_ascii) - - if results: - logger.debug( - "Encoding detection: Found %s as plausible (best-candidate) for content. With %i alternatives.", - results.best().encoding, # type: ignore - len(results) - 1, - ) - else: - logger.debug("Encoding detection: Unable to determine any suitable charset.") - - if explain: - logger.removeHandler(explain_handler) - logger.setLevel(previous_logger_level) - - return results - - -def from_fp( - fp: BinaryIO, - steps: int = 5, - chunk_size: int = 512, - threshold: float = 0.20, - cp_isolation: Optional[List[str]] = None, - cp_exclusion: Optional[List[str]] = None, - preemptive_behaviour: bool = True, - explain: bool = False, -) -> CharsetMatches: - """ - Same thing than the function from_bytes but using a file pointer that is already ready. - Will not close the file pointer. - """ - return from_bytes( - fp.read(), - steps, - chunk_size, - threshold, - cp_isolation, - cp_exclusion, - preemptive_behaviour, - explain, - ) - - -def from_path( - path: "PathLike[Any]", - steps: int = 5, - chunk_size: int = 512, - threshold: float = 0.20, - cp_isolation: Optional[List[str]] = None, - cp_exclusion: Optional[List[str]] = None, - preemptive_behaviour: bool = True, - explain: bool = False, -) -> CharsetMatches: - """ - Same thing than the function from_bytes but with one extra step. Opening and reading given file path in binary mode. - Can raise IOError. - """ - with open(path, "rb") as fp: - return from_fp( - fp, - steps, - chunk_size, - threshold, - cp_isolation, - cp_exclusion, - preemptive_behaviour, - explain, - ) - - -def normalize( - path: "PathLike[Any]", - steps: int = 5, - chunk_size: int = 512, - threshold: float = 0.20, - cp_isolation: Optional[List[str]] = None, - cp_exclusion: Optional[List[str]] = None, - preemptive_behaviour: bool = True, -) -> CharsetMatch: - """ - Take a (text-based) file path and try to create another file next to it, this time using UTF-8. - """ - warnings.warn( - "normalize is deprecated and will be removed in 3.0", - DeprecationWarning, - ) - - results = from_path( - path, - steps, - chunk_size, - threshold, - cp_isolation, - cp_exclusion, - preemptive_behaviour, - ) - - filename = basename(path) - target_extensions = list(splitext(filename)) - - if len(results) == 0: - raise IOError( - 'Unable to normalize "{}", no encoding charset seems to fit.'.format( - filename - ) - ) - - result = results.best() - - target_extensions[0] += "-" + result.encoding # type: ignore - - with open( - "{}".format(str(path).replace(filename, "".join(target_extensions))), "wb" - ) as fp: - fp.write(result.output()) # type: ignore - - return result # type: ignore diff --git a/lib/charset_normalizer/assets/__init__.py b/lib/charset_normalizer/assets/__init__.py deleted file mode 100644 index 3c33ba30..00000000 --- a/lib/charset_normalizer/assets/__init__.py +++ /dev/null @@ -1,1122 +0,0 @@ -# -*- coding: utf-8 -*- -from typing import Dict, List - -FREQUENCIES: Dict[str, List[str]] = { - "English": [ - "e", - "a", - "t", - "i", - "o", - "n", - "s", - "r", - "h", - "l", - "d", - "c", - "u", - "m", - "f", - "p", - "g", - "w", - "y", - "b", - "v", - "k", - "x", - "j", - "z", - "q", - ], - "German": [ - "e", - "n", - "i", - "r", - "s", - "t", - "a", - "d", - "h", - "u", - "l", - "g", - "o", - "c", - "m", - "b", - "f", - "k", - "w", - "z", - "p", - "v", - "ü", - "ä", - "ö", - "j", - ], - "French": [ - "e", - "a", - "s", - "n", - "i", - "t", - "r", - "l", - "u", - "o", - "d", - "c", - "p", - "m", - "é", - "v", - "g", - "f", - "b", - "h", - "q", - "à", - "x", - "è", - "y", - "j", - ], - "Dutch": [ - "e", - "n", - "a", - "i", - "r", - "t", - "o", - "d", - "s", - "l", - "g", - "h", - "v", - "m", - "u", - "k", - "c", - "p", - "b", - "w", - "j", - "z", - "f", - "y", - "x", - "ë", - ], - "Italian": [ - "e", - "i", - "a", - "o", - "n", - "l", - "t", - "r", - "s", - "c", - "d", - "u", - "p", - "m", - "g", - "v", - "f", - "b", - "z", - "h", - "q", - "è", - "à", - "k", - "y", - "ò", - ], - "Polish": [ - "a", - "i", - "o", - "e", - "n", - "r", - "z", - "w", - "s", - "c", - "t", - "k", - "y", - "d", - "p", - "m", - "u", - "l", - "j", - "ł", - "g", - "b", - "h", - "ą", - "ę", - "ó", - ], - "Spanish": [ - "e", - "a", - "o", - "n", - "s", - "r", - "i", - "l", - "d", - "t", - "c", - "u", - "m", - "p", - "b", - "g", - "v", - "f", - "y", - "ó", - "h", - "q", - "í", - "j", - "z", - "á", - ], - "Russian": [ - "о", - "а", - "е", - "и", - "н", - "с", - "т", - "р", - "в", - "л", - "к", - "м", - "д", - "п", - "у", - "г", - "я", - "ы", - "з", - "б", - "й", - "ь", - "ч", - "х", - "ж", - "ц", - ], - "Japanese": [ - "の", - "に", - "る", - "た", - "は", - "ー", - "と", - "し", - "を", - "で", - "て", - "が", - "い", - "ン", - "れ", - "な", - "年", - "ス", - "っ", - "ル", - "か", - "ら", - "あ", - "さ", - "も", - "り", - ], - "Portuguese": [ - "a", - "e", - "o", - "s", - "i", - "r", - "d", - "n", - "t", - "m", - "u", - "c", - "l", - "p", - "g", - "v", - "b", - "f", - "h", - "ã", - "q", - "é", - "ç", - "á", - "z", - "í", - ], - "Swedish": [ - "e", - "a", - "n", - "r", - "t", - "s", - "i", - "l", - "d", - "o", - "m", - "k", - "g", - "v", - "h", - "f", - "u", - "p", - "ä", - "c", - "b", - "ö", - "å", - "y", - "j", - "x", - ], - "Chinese": [ - "的", - "一", - "是", - "不", - "了", - "在", - "人", - "有", - "我", - "他", - "这", - "个", - "们", - "中", - "来", - "上", - "大", - "为", - "和", - "国", - "地", - "到", - "以", - "说", - "时", - "要", - "就", - "出", - "会", - ], - "Ukrainian": [ - "о", - "а", - "н", - "і", - "и", - "р", - "в", - "т", - "е", - "с", - "к", - "л", - "у", - "д", - "м", - "п", - "з", - "я", - "ь", - "б", - "г", - "й", - "ч", - "х", - "ц", - "ї", - ], - "Norwegian": [ - "e", - "r", - "n", - "t", - "a", - "s", - "i", - "o", - "l", - "d", - "g", - "k", - "m", - "v", - "f", - "p", - "u", - "b", - "h", - "å", - "y", - "j", - "ø", - "c", - "æ", - "w", - ], - "Finnish": [ - "a", - "i", - "n", - "t", - "e", - "s", - "l", - "o", - "u", - "k", - "ä", - "m", - "r", - "v", - "j", - "h", - "p", - "y", - "d", - "ö", - "g", - "c", - "b", - "f", - "w", - "z", - ], - "Vietnamese": [ - "n", - "h", - "t", - "i", - "c", - "g", - "a", - "o", - "u", - "m", - "l", - "r", - "à", - "đ", - "s", - "e", - "v", - "p", - "b", - "y", - "ư", - "d", - "á", - "k", - "ộ", - "ế", - ], - "Czech": [ - "o", - "e", - "a", - "n", - "t", - "s", - "i", - "l", - "v", - "r", - "k", - "d", - "u", - "m", - "p", - "í", - "c", - "h", - "z", - "á", - "y", - "j", - "b", - "ě", - "é", - "ř", - ], - "Hungarian": [ - "e", - "a", - "t", - "l", - "s", - "n", - "k", - "r", - "i", - "o", - "z", - "á", - "é", - "g", - "m", - "b", - "y", - "v", - "d", - "h", - "u", - "p", - "j", - "ö", - "f", - "c", - ], - "Korean": [ - "이", - "다", - "에", - "의", - "는", - "로", - "하", - "을", - "가", - "고", - "지", - "서", - "한", - "은", - "기", - "으", - "년", - "대", - "사", - "시", - "를", - "리", - "도", - "인", - "스", - "일", - ], - "Indonesian": [ - "a", - "n", - "e", - "i", - "r", - "t", - "u", - "s", - "d", - "k", - "m", - "l", - "g", - "p", - "b", - "o", - "h", - "y", - "j", - "c", - "w", - "f", - "v", - "z", - "x", - "q", - ], - "Turkish": [ - "a", - "e", - "i", - "n", - "r", - "l", - "ı", - "k", - "d", - "t", - "s", - "m", - "y", - "u", - "o", - "b", - "ü", - "ş", - "v", - "g", - "z", - "h", - "c", - "p", - "ç", - "ğ", - ], - "Romanian": [ - "e", - "i", - "a", - "r", - "n", - "t", - "u", - "l", - "o", - "c", - "s", - "d", - "p", - "m", - "ă", - "f", - "v", - "î", - "g", - "b", - "ș", - "ț", - "z", - "h", - "â", - "j", - ], - "Farsi": [ - "ا", - "ی", - "ر", - "د", - "ن", - "ه", - "و", - "م", - "ت", - "ب", - "س", - "ل", - "ک", - "ش", - "ز", - "ف", - "گ", - "ع", - "خ", - "ق", - "ج", - "آ", - "پ", - "ح", - "ط", - "ص", - ], - "Arabic": [ - "ا", - "ل", - "ي", - "م", - "و", - "ن", - "ر", - "ت", - "ب", - "ة", - "ع", - "د", - "س", - "ف", - "ه", - "ك", - "ق", - "أ", - "ح", - "ج", - "ش", - "ط", - "ص", - "ى", - "خ", - "إ", - ], - "Danish": [ - "e", - "r", - "n", - "t", - "a", - "i", - "s", - "d", - "l", - "o", - "g", - "m", - "k", - "f", - "v", - "u", - "b", - "h", - "p", - "å", - "y", - "ø", - "æ", - "c", - "j", - "w", - ], - "Serbian": [ - "а", - "и", - "о", - "е", - "н", - "р", - "с", - "у", - "т", - "к", - "ј", - "в", - "д", - "м", - "п", - "л", - "г", - "з", - "б", - "a", - "i", - "e", - "o", - "n", - "ц", - "ш", - ], - "Lithuanian": [ - "i", - "a", - "s", - "o", - "r", - "e", - "t", - "n", - "u", - "k", - "m", - "l", - "p", - "v", - "d", - "j", - "g", - "ė", - "b", - "y", - "ų", - "š", - "ž", - "c", - "ą", - "į", - ], - "Slovene": [ - "e", - "a", - "i", - "o", - "n", - "r", - "s", - "l", - "t", - "j", - "v", - "k", - "d", - "p", - "m", - "u", - "z", - "b", - "g", - "h", - "č", - "c", - "š", - "ž", - "f", - "y", - ], - "Slovak": [ - "o", - "a", - "e", - "n", - "i", - "r", - "v", - "t", - "s", - "l", - "k", - "d", - "m", - "p", - "u", - "c", - "h", - "j", - "b", - "z", - "á", - "y", - "ý", - "í", - "č", - "é", - ], - "Hebrew": [ - "י", - "ו", - "ה", - "ל", - "ר", - "ב", - "ת", - "מ", - "א", - "ש", - "נ", - "ע", - "ם", - "ד", - "ק", - "ח", - "פ", - "ס", - "כ", - "ג", - "ט", - "צ", - "ן", - "ז", - "ך", - ], - "Bulgarian": [ - "а", - "и", - "о", - "е", - "н", - "т", - "р", - "с", - "в", - "л", - "к", - "д", - "п", - "м", - "з", - "г", - "я", - "ъ", - "у", - "б", - "ч", - "ц", - "й", - "ж", - "щ", - "х", - ], - "Croatian": [ - "a", - "i", - "o", - "e", - "n", - "r", - "j", - "s", - "t", - "u", - "k", - "l", - "v", - "d", - "m", - "p", - "g", - "z", - "b", - "c", - "č", - "h", - "š", - "ž", - "ć", - "f", - ], - "Hindi": [ - "क", - "र", - "स", - "न", - "त", - "म", - "ह", - "प", - "य", - "ल", - "व", - "ज", - "द", - "ग", - "ब", - "श", - "ट", - "अ", - "ए", - "थ", - "भ", - "ड", - "च", - "ध", - "ष", - "इ", - ], - "Estonian": [ - "a", - "i", - "e", - "s", - "t", - "l", - "u", - "n", - "o", - "k", - "r", - "d", - "m", - "v", - "g", - "p", - "j", - "h", - "ä", - "b", - "õ", - "ü", - "f", - "c", - "ö", - "y", - ], - "Simple English": [ - "e", - "a", - "t", - "i", - "o", - "n", - "s", - "r", - "h", - "l", - "d", - "c", - "m", - "u", - "f", - "p", - "g", - "w", - "b", - "y", - "v", - "k", - "j", - "x", - "z", - "q", - ], - "Thai": [ - "า", - "น", - "ร", - "อ", - "ก", - "เ", - "ง", - "ม", - "ย", - "ล", - "ว", - "ด", - "ท", - "ส", - "ต", - "ะ", - "ป", - "บ", - "ค", - "ห", - "แ", - "จ", - "พ", - "ช", - "ข", - "ใ", - ], - "Greek": [ - "α", - "τ", - "ο", - "ι", - "ε", - "ν", - "ρ", - "σ", - "κ", - "η", - "π", - "ς", - "υ", - "μ", - "λ", - "ί", - "ό", - "ά", - "γ", - "έ", - "δ", - "ή", - "ω", - "χ", - "θ", - "ύ", - ], - "Tamil": [ - "க", - "த", - "ப", - "ட", - "ர", - "ம", - "ல", - "ன", - "வ", - "ற", - "ய", - "ள", - "ச", - "ந", - "இ", - "ண", - "அ", - "ஆ", - "ழ", - "ங", - "எ", - "உ", - "ஒ", - "ஸ", - ], - "Classical Chinese": [ - "之", - "年", - "為", - "也", - "以", - "一", - "人", - "其", - "者", - "國", - "有", - "二", - "十", - "於", - "曰", - "三", - "不", - "大", - "而", - "子", - "中", - "五", - "四", - ], - "Kazakh": [ - "а", - "ы", - "е", - "н", - "т", - "р", - "л", - "і", - "д", - "с", - "м", - "қ", - "к", - "о", - "б", - "и", - "у", - "ғ", - "ж", - "ң", - "з", - "ш", - "й", - "п", - "г", - "ө", - ], -} diff --git a/lib/charset_normalizer/cd.py b/lib/charset_normalizer/cd.py deleted file mode 100644 index ee4b7424..00000000 --- a/lib/charset_normalizer/cd.py +++ /dev/null @@ -1,339 +0,0 @@ -import importlib -from codecs import IncrementalDecoder -from collections import Counter -from functools import lru_cache -from typing import Counter as TypeCounter, Dict, List, Optional, Tuple - -from .assets import FREQUENCIES -from .constant import KO_NAMES, LANGUAGE_SUPPORTED_COUNT, TOO_SMALL_SEQUENCE, ZH_NAMES -from .md import is_suspiciously_successive_range -from .models import CoherenceMatches -from .utils import ( - is_accentuated, - is_latin, - is_multi_byte_encoding, - is_unicode_range_secondary, - unicode_range, -) - - -def encoding_unicode_range(iana_name: str) -> List[str]: - """ - Return associated unicode ranges in a single byte code page. - """ - if is_multi_byte_encoding(iana_name): - raise IOError("Function not supported on multi-byte code page") - - decoder = importlib.import_module( - "encodings.{}".format(iana_name) - ).IncrementalDecoder - - p: IncrementalDecoder = decoder(errors="ignore") - seen_ranges: Dict[str, int] = {} - character_count: int = 0 - - for i in range(0x40, 0xFF): - chunk: str = p.decode(bytes([i])) - - if chunk: - character_range: Optional[str] = unicode_range(chunk) - - if character_range is None: - continue - - if is_unicode_range_secondary(character_range) is False: - if character_range not in seen_ranges: - seen_ranges[character_range] = 0 - seen_ranges[character_range] += 1 - character_count += 1 - - return sorted( - [ - character_range - for character_range in seen_ranges - if seen_ranges[character_range] / character_count >= 0.15 - ] - ) - - -def unicode_range_languages(primary_range: str) -> List[str]: - """ - Return inferred languages used with a unicode range. - """ - languages: List[str] = [] - - for language, characters in FREQUENCIES.items(): - for character in characters: - if unicode_range(character) == primary_range: - languages.append(language) - break - - return languages - - -@lru_cache() -def encoding_languages(iana_name: str) -> List[str]: - """ - Single-byte encoding language association. Some code page are heavily linked to particular language(s). - This function does the correspondence. - """ - unicode_ranges: List[str] = encoding_unicode_range(iana_name) - primary_range: Optional[str] = None - - for specified_range in unicode_ranges: - if "Latin" not in specified_range: - primary_range = specified_range - break - - if primary_range is None: - return ["Latin Based"] - - return unicode_range_languages(primary_range) - - -@lru_cache() -def mb_encoding_languages(iana_name: str) -> List[str]: - """ - Multi-byte encoding language association. Some code page are heavily linked to particular language(s). - This function does the correspondence. - """ - if ( - iana_name.startswith("shift_") - or iana_name.startswith("iso2022_jp") - or iana_name.startswith("euc_j") - or iana_name == "cp932" - ): - return ["Japanese"] - if iana_name.startswith("gb") or iana_name in ZH_NAMES: - return ["Chinese", "Classical Chinese"] - if iana_name.startswith("iso2022_kr") or iana_name in KO_NAMES: - return ["Korean"] - - return [] - - -@lru_cache(maxsize=LANGUAGE_SUPPORTED_COUNT) -def get_target_features(language: str) -> Tuple[bool, bool]: - """ - Determine main aspects from a supported language if it contains accents and if is pure Latin. - """ - target_have_accents: bool = False - target_pure_latin: bool = True - - for character in FREQUENCIES[language]: - if not target_have_accents and is_accentuated(character): - target_have_accents = True - if target_pure_latin and is_latin(character) is False: - target_pure_latin = False - - return target_have_accents, target_pure_latin - - -def alphabet_languages( - characters: List[str], ignore_non_latin: bool = False -) -> List[str]: - """ - Return associated languages associated to given characters. - """ - languages: List[Tuple[str, float]] = [] - - source_have_accents = any(is_accentuated(character) for character in characters) - - for language, language_characters in FREQUENCIES.items(): - - target_have_accents, target_pure_latin = get_target_features(language) - - if ignore_non_latin and target_pure_latin is False: - continue - - if target_have_accents is False and source_have_accents: - continue - - character_count: int = len(language_characters) - - character_match_count: int = len( - [c for c in language_characters if c in characters] - ) - - ratio: float = character_match_count / character_count - - if ratio >= 0.2: - languages.append((language, ratio)) - - languages = sorted(languages, key=lambda x: x[1], reverse=True) - - return [compatible_language[0] for compatible_language in languages] - - -def characters_popularity_compare( - language: str, ordered_characters: List[str] -) -> float: - """ - Determine if a ordered characters list (by occurrence from most appearance to rarest) match a particular language. - The result is a ratio between 0. (absolutely no correspondence) and 1. (near perfect fit). - Beware that is function is not strict on the match in order to ease the detection. (Meaning close match is 1.) - """ - if language not in FREQUENCIES: - raise ValueError("{} not available".format(language)) - - character_approved_count: int = 0 - FREQUENCIES_language_set = set(FREQUENCIES[language]) - - for character in ordered_characters: - if character not in FREQUENCIES_language_set: - continue - - characters_before_source: List[str] = FREQUENCIES[language][ - 0 : FREQUENCIES[language].index(character) - ] - characters_after_source: List[str] = FREQUENCIES[language][ - FREQUENCIES[language].index(character) : - ] - characters_before: List[str] = ordered_characters[ - 0 : ordered_characters.index(character) - ] - characters_after: List[str] = ordered_characters[ - ordered_characters.index(character) : - ] - - before_match_count: int = len( - set(characters_before) & set(characters_before_source) - ) - - after_match_count: int = len( - set(characters_after) & set(characters_after_source) - ) - - if len(characters_before_source) == 0 and before_match_count <= 4: - character_approved_count += 1 - continue - - if len(characters_after_source) == 0 and after_match_count <= 4: - character_approved_count += 1 - continue - - if ( - before_match_count / len(characters_before_source) >= 0.4 - or after_match_count / len(characters_after_source) >= 0.4 - ): - character_approved_count += 1 - continue - - return character_approved_count / len(ordered_characters) - - -def alpha_unicode_split(decoded_sequence: str) -> List[str]: - """ - Given a decoded text sequence, return a list of str. Unicode range / alphabet separation. - Ex. a text containing English/Latin with a bit a Hebrew will return two items in the resulting list; - One containing the latin letters and the other hebrew. - """ - layers: Dict[str, str] = {} - - for character in decoded_sequence: - if character.isalpha() is False: - continue - - character_range: Optional[str] = unicode_range(character) - - if character_range is None: - continue - - layer_target_range: Optional[str] = None - - for discovered_range in layers: - if ( - is_suspiciously_successive_range(discovered_range, character_range) - is False - ): - layer_target_range = discovered_range - break - - if layer_target_range is None: - layer_target_range = character_range - - if layer_target_range not in layers: - layers[layer_target_range] = character.lower() - continue - - layers[layer_target_range] += character.lower() - - return list(layers.values()) - - -def merge_coherence_ratios(results: List[CoherenceMatches]) -> CoherenceMatches: - """ - This function merge results previously given by the function coherence_ratio. - The return type is the same as coherence_ratio. - """ - per_language_ratios: Dict[str, List[float]] = {} - for result in results: - for sub_result in result: - language, ratio = sub_result - if language not in per_language_ratios: - per_language_ratios[language] = [ratio] - continue - per_language_ratios[language].append(ratio) - - merge = [ - ( - language, - round( - sum(per_language_ratios[language]) / len(per_language_ratios[language]), - 4, - ), - ) - for language in per_language_ratios - ] - - return sorted(merge, key=lambda x: x[1], reverse=True) - - -@lru_cache(maxsize=2048) -def coherence_ratio( - decoded_sequence: str, threshold: float = 0.1, lg_inclusion: Optional[str] = None -) -> CoherenceMatches: - """ - Detect ANY language that can be identified in given sequence. The sequence will be analysed by layers. - A layer = Character extraction by alphabets/ranges. - """ - - results: List[Tuple[str, float]] = [] - ignore_non_latin: bool = False - - sufficient_match_count: int = 0 - - lg_inclusion_list = lg_inclusion.split(",") if lg_inclusion is not None else [] - if "Latin Based" in lg_inclusion_list: - ignore_non_latin = True - lg_inclusion_list.remove("Latin Based") - - for layer in alpha_unicode_split(decoded_sequence): - sequence_frequencies: TypeCounter[str] = Counter(layer) - most_common = sequence_frequencies.most_common() - - character_count: int = sum(o for c, o in most_common) - - if character_count <= TOO_SMALL_SEQUENCE: - continue - - popular_character_ordered: List[str] = [c for c, o in most_common] - - for language in lg_inclusion_list or alphabet_languages( - popular_character_ordered, ignore_non_latin - ): - ratio: float = characters_popularity_compare( - language, popular_character_ordered - ) - - if ratio < threshold: - continue - elif ratio >= 0.8: - sufficient_match_count += 1 - - results.append((language, round(ratio, 4))) - - if sufficient_match_count >= 3: - break - - return sorted(results, key=lambda x: x[1], reverse=True) diff --git a/lib/charset_normalizer/cli/__init__.py b/lib/charset_normalizer/cli/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/charset_normalizer/cli/normalizer.py b/lib/charset_normalizer/cli/normalizer.py deleted file mode 100644 index b8b652a5..00000000 --- a/lib/charset_normalizer/cli/normalizer.py +++ /dev/null @@ -1,295 +0,0 @@ -import argparse -import sys -from json import dumps -from os.path import abspath -from platform import python_version -from typing import List, Optional - -try: - from unicodedata2 import unidata_version -except ImportError: - from unicodedata import unidata_version - -from charset_normalizer import from_fp -from charset_normalizer.models import CliDetectionResult -from charset_normalizer.version import __version__ - - -def query_yes_no(question: str, default: str = "yes") -> bool: - """Ask a yes/no question via input() and return their answer. - - "question" is a string that is presented to the user. - "default" is the presumed answer if the user just hits . - It must be "yes" (the default), "no" or None (meaning - an answer is required of the user). - - The "answer" return value is True for "yes" or False for "no". - - Credit goes to (c) https://stackoverflow.com/questions/3041986/apt-command-line-interface-like-yes-no-input - """ - valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} - if default is None: - prompt = " [y/n] " - elif default == "yes": - prompt = " [Y/n] " - elif default == "no": - prompt = " [y/N] " - else: - raise ValueError("invalid default answer: '%s'" % default) - - while True: - sys.stdout.write(question + prompt) - choice = input().lower() - if default is not None and choice == "": - return valid[default] - elif choice in valid: - return valid[choice] - else: - sys.stdout.write("Please respond with 'yes' or 'no' " "(or 'y' or 'n').\n") - - -def cli_detect(argv: Optional[List[str]] = None) -> int: - """ - CLI assistant using ARGV and ArgumentParser - :param argv: - :return: 0 if everything is fine, anything else equal trouble - """ - parser = argparse.ArgumentParser( - description="The Real First Universal Charset Detector. " - "Discover originating encoding used on text file. " - "Normalize text to unicode." - ) - - parser.add_argument( - "files", type=argparse.FileType("rb"), nargs="+", help="File(s) to be analysed" - ) - parser.add_argument( - "-v", - "--verbose", - action="store_true", - default=False, - dest="verbose", - help="Display complementary information about file if any. " - "Stdout will contain logs about the detection process.", - ) - parser.add_argument( - "-a", - "--with-alternative", - action="store_true", - default=False, - dest="alternatives", - help="Output complementary possibilities if any. Top-level JSON WILL be a list.", - ) - parser.add_argument( - "-n", - "--normalize", - action="store_true", - default=False, - dest="normalize", - help="Permit to normalize input file. If not set, program does not write anything.", - ) - parser.add_argument( - "-m", - "--minimal", - action="store_true", - default=False, - dest="minimal", - help="Only output the charset detected to STDOUT. Disabling JSON output.", - ) - parser.add_argument( - "-r", - "--replace", - action="store_true", - default=False, - dest="replace", - help="Replace file when trying to normalize it instead of creating a new one.", - ) - parser.add_argument( - "-f", - "--force", - action="store_true", - default=False, - dest="force", - help="Replace file without asking if you are sure, use this flag with caution.", - ) - parser.add_argument( - "-t", - "--threshold", - action="store", - default=0.2, - type=float, - dest="threshold", - help="Define a custom maximum amount of chaos allowed in decoded content. 0. <= chaos <= 1.", - ) - parser.add_argument( - "--version", - action="version", - version="Charset-Normalizer {} - Python {} - Unicode {}".format( - __version__, python_version(), unidata_version - ), - help="Show version information and exit.", - ) - - args = parser.parse_args(argv) - - if args.replace is True and args.normalize is False: - print("Use --replace in addition of --normalize only.", file=sys.stderr) - return 1 - - if args.force is True and args.replace is False: - print("Use --force in addition of --replace only.", file=sys.stderr) - return 1 - - if args.threshold < 0.0 or args.threshold > 1.0: - print("--threshold VALUE should be between 0. AND 1.", file=sys.stderr) - return 1 - - x_ = [] - - for my_file in args.files: - - matches = from_fp(my_file, threshold=args.threshold, explain=args.verbose) - - best_guess = matches.best() - - if best_guess is None: - print( - 'Unable to identify originating encoding for "{}". {}'.format( - my_file.name, - "Maybe try increasing maximum amount of chaos." - if args.threshold < 1.0 - else "", - ), - file=sys.stderr, - ) - x_.append( - CliDetectionResult( - abspath(my_file.name), - None, - [], - [], - "Unknown", - [], - False, - 1.0, - 0.0, - None, - True, - ) - ) - else: - x_.append( - CliDetectionResult( - abspath(my_file.name), - best_guess.encoding, - best_guess.encoding_aliases, - [ - cp - for cp in best_guess.could_be_from_charset - if cp != best_guess.encoding - ], - best_guess.language, - best_guess.alphabets, - best_guess.bom, - best_guess.percent_chaos, - best_guess.percent_coherence, - None, - True, - ) - ) - - if len(matches) > 1 and args.alternatives: - for el in matches: - if el != best_guess: - x_.append( - CliDetectionResult( - abspath(my_file.name), - el.encoding, - el.encoding_aliases, - [ - cp - for cp in el.could_be_from_charset - if cp != el.encoding - ], - el.language, - el.alphabets, - el.bom, - el.percent_chaos, - el.percent_coherence, - None, - False, - ) - ) - - if args.normalize is True: - - if best_guess.encoding.startswith("utf") is True: - print( - '"{}" file does not need to be normalized, as it already came from unicode.'.format( - my_file.name - ), - file=sys.stderr, - ) - if my_file.closed is False: - my_file.close() - continue - - o_: List[str] = my_file.name.split(".") - - if args.replace is False: - o_.insert(-1, best_guess.encoding) - if my_file.closed is False: - my_file.close() - elif ( - args.force is False - and query_yes_no( - 'Are you sure to normalize "{}" by replacing it ?'.format( - my_file.name - ), - "no", - ) - is False - ): - if my_file.closed is False: - my_file.close() - continue - - try: - x_[0].unicode_path = abspath("./{}".format(".".join(o_))) - - with open(x_[0].unicode_path, "w", encoding="utf-8") as fp: - fp.write(str(best_guess)) - except IOError as e: - print(str(e), file=sys.stderr) - if my_file.closed is False: - my_file.close() - return 2 - - if my_file.closed is False: - my_file.close() - - if args.minimal is False: - print( - dumps( - [el.__dict__ for el in x_] if len(x_) > 1 else x_[0].__dict__, - ensure_ascii=True, - indent=4, - ) - ) - else: - for my_file in args.files: - print( - ", ".join( - [ - el.encoding or "undefined" - for el in x_ - if el.path == abspath(my_file.name) - ] - ) - ) - - return 0 - - -if __name__ == "__main__": - cli_detect() diff --git a/lib/charset_normalizer/constant.py b/lib/charset_normalizer/constant.py deleted file mode 100644 index ac840c46..00000000 --- a/lib/charset_normalizer/constant.py +++ /dev/null @@ -1,497 +0,0 @@ -from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE -from encodings.aliases import aliases -from re import IGNORECASE, compile as re_compile -from typing import Dict, List, Set, Union - -from .assets import FREQUENCIES - -# Contain for each eligible encoding a list of/item bytes SIG/BOM -ENCODING_MARKS: Dict[str, Union[bytes, List[bytes]]] = { - "utf_8": BOM_UTF8, - "utf_7": [ - b"\x2b\x2f\x76\x38", - b"\x2b\x2f\x76\x39", - b"\x2b\x2f\x76\x2b", - b"\x2b\x2f\x76\x2f", - b"\x2b\x2f\x76\x38\x2d", - ], - "gb18030": b"\x84\x31\x95\x33", - "utf_32": [BOM_UTF32_BE, BOM_UTF32_LE], - "utf_16": [BOM_UTF16_BE, BOM_UTF16_LE], -} - -TOO_SMALL_SEQUENCE: int = 32 -TOO_BIG_SEQUENCE: int = int(10e6) - -UTF8_MAXIMAL_ALLOCATION: int = 1112064 - -UNICODE_RANGES_COMBINED: Dict[str, range] = { - "Control character": range(31 + 1), - "Basic Latin": range(32, 127 + 1), - "Latin-1 Supplement": range(128, 255 + 1), - "Latin Extended-A": range(256, 383 + 1), - "Latin Extended-B": range(384, 591 + 1), - "IPA Extensions": range(592, 687 + 1), - "Spacing Modifier Letters": range(688, 767 + 1), - "Combining Diacritical Marks": range(768, 879 + 1), - "Greek and Coptic": range(880, 1023 + 1), - "Cyrillic": range(1024, 1279 + 1), - "Cyrillic Supplement": range(1280, 1327 + 1), - "Armenian": range(1328, 1423 + 1), - "Hebrew": range(1424, 1535 + 1), - "Arabic": range(1536, 1791 + 1), - "Syriac": range(1792, 1871 + 1), - "Arabic Supplement": range(1872, 1919 + 1), - "Thaana": range(1920, 1983 + 1), - "NKo": range(1984, 2047 + 1), - "Samaritan": range(2048, 2111 + 1), - "Mandaic": range(2112, 2143 + 1), - "Syriac Supplement": range(2144, 2159 + 1), - "Arabic Extended-A": range(2208, 2303 + 1), - "Devanagari": range(2304, 2431 + 1), - "Bengali": range(2432, 2559 + 1), - "Gurmukhi": range(2560, 2687 + 1), - "Gujarati": range(2688, 2815 + 1), - "Oriya": range(2816, 2943 + 1), - "Tamil": range(2944, 3071 + 1), - "Telugu": range(3072, 3199 + 1), - "Kannada": range(3200, 3327 + 1), - "Malayalam": range(3328, 3455 + 1), - "Sinhala": range(3456, 3583 + 1), - "Thai": range(3584, 3711 + 1), - "Lao": range(3712, 3839 + 1), - "Tibetan": range(3840, 4095 + 1), - "Myanmar": range(4096, 4255 + 1), - "Georgian": range(4256, 4351 + 1), - "Hangul Jamo": range(4352, 4607 + 1), - "Ethiopic": range(4608, 4991 + 1), - "Ethiopic Supplement": range(4992, 5023 + 1), - "Cherokee": range(5024, 5119 + 1), - "Unified Canadian Aboriginal Syllabics": range(5120, 5759 + 1), - "Ogham": range(5760, 5791 + 1), - "Runic": range(5792, 5887 + 1), - "Tagalog": range(5888, 5919 + 1), - "Hanunoo": range(5920, 5951 + 1), - "Buhid": range(5952, 5983 + 1), - "Tagbanwa": range(5984, 6015 + 1), - "Khmer": range(6016, 6143 + 1), - "Mongolian": range(6144, 6319 + 1), - "Unified Canadian Aboriginal Syllabics Extended": range(6320, 6399 + 1), - "Limbu": range(6400, 6479 + 1), - "Tai Le": range(6480, 6527 + 1), - "New Tai Lue": range(6528, 6623 + 1), - "Khmer Symbols": range(6624, 6655 + 1), - "Buginese": range(6656, 6687 + 1), - "Tai Tham": range(6688, 6831 + 1), - "Combining Diacritical Marks Extended": range(6832, 6911 + 1), - "Balinese": range(6912, 7039 + 1), - "Sundanese": range(7040, 7103 + 1), - "Batak": range(7104, 7167 + 1), - "Lepcha": range(7168, 7247 + 1), - "Ol Chiki": range(7248, 7295 + 1), - "Cyrillic Extended C": range(7296, 7311 + 1), - "Sundanese Supplement": range(7360, 7375 + 1), - "Vedic Extensions": range(7376, 7423 + 1), - "Phonetic Extensions": range(7424, 7551 + 1), - "Phonetic Extensions Supplement": range(7552, 7615 + 1), - "Combining Diacritical Marks Supplement": range(7616, 7679 + 1), - "Latin Extended Additional": range(7680, 7935 + 1), - "Greek Extended": range(7936, 8191 + 1), - "General Punctuation": range(8192, 8303 + 1), - "Superscripts and Subscripts": range(8304, 8351 + 1), - "Currency Symbols": range(8352, 8399 + 1), - "Combining Diacritical Marks for Symbols": range(8400, 8447 + 1), - "Letterlike Symbols": range(8448, 8527 + 1), - "Number Forms": range(8528, 8591 + 1), - "Arrows": range(8592, 8703 + 1), - "Mathematical Operators": range(8704, 8959 + 1), - "Miscellaneous Technical": range(8960, 9215 + 1), - "Control Pictures": range(9216, 9279 + 1), - "Optical Character Recognition": range(9280, 9311 + 1), - "Enclosed Alphanumerics": range(9312, 9471 + 1), - "Box Drawing": range(9472, 9599 + 1), - "Block Elements": range(9600, 9631 + 1), - "Geometric Shapes": range(9632, 9727 + 1), - "Miscellaneous Symbols": range(9728, 9983 + 1), - "Dingbats": range(9984, 10175 + 1), - "Miscellaneous Mathematical Symbols-A": range(10176, 10223 + 1), - "Supplemental Arrows-A": range(10224, 10239 + 1), - "Braille Patterns": range(10240, 10495 + 1), - "Supplemental Arrows-B": range(10496, 10623 + 1), - "Miscellaneous Mathematical Symbols-B": range(10624, 10751 + 1), - "Supplemental Mathematical Operators": range(10752, 11007 + 1), - "Miscellaneous Symbols and Arrows": range(11008, 11263 + 1), - "Glagolitic": range(11264, 11359 + 1), - "Latin Extended-C": range(11360, 11391 + 1), - "Coptic": range(11392, 11519 + 1), - "Georgian Supplement": range(11520, 11567 + 1), - "Tifinagh": range(11568, 11647 + 1), - "Ethiopic Extended": range(11648, 11743 + 1), - "Cyrillic Extended-A": range(11744, 11775 + 1), - "Supplemental Punctuation": range(11776, 11903 + 1), - "CJK Radicals Supplement": range(11904, 12031 + 1), - "Kangxi Radicals": range(12032, 12255 + 1), - "Ideographic Description Characters": range(12272, 12287 + 1), - "CJK Symbols and Punctuation": range(12288, 12351 + 1), - "Hiragana": range(12352, 12447 + 1), - "Katakana": range(12448, 12543 + 1), - "Bopomofo": range(12544, 12591 + 1), - "Hangul Compatibility Jamo": range(12592, 12687 + 1), - "Kanbun": range(12688, 12703 + 1), - "Bopomofo Extended": range(12704, 12735 + 1), - "CJK Strokes": range(12736, 12783 + 1), - "Katakana Phonetic Extensions": range(12784, 12799 + 1), - "Enclosed CJK Letters and Months": range(12800, 13055 + 1), - "CJK Compatibility": range(13056, 13311 + 1), - "CJK Unified Ideographs Extension A": range(13312, 19903 + 1), - "Yijing Hexagram Symbols": range(19904, 19967 + 1), - "CJK Unified Ideographs": range(19968, 40959 + 1), - "Yi Syllables": range(40960, 42127 + 1), - "Yi Radicals": range(42128, 42191 + 1), - "Lisu": range(42192, 42239 + 1), - "Vai": range(42240, 42559 + 1), - "Cyrillic Extended-B": range(42560, 42655 + 1), - "Bamum": range(42656, 42751 + 1), - "Modifier Tone Letters": range(42752, 42783 + 1), - "Latin Extended-D": range(42784, 43007 + 1), - "Syloti Nagri": range(43008, 43055 + 1), - "Common Indic Number Forms": range(43056, 43071 + 1), - "Phags-pa": range(43072, 43135 + 1), - "Saurashtra": range(43136, 43231 + 1), - "Devanagari Extended": range(43232, 43263 + 1), - "Kayah Li": range(43264, 43311 + 1), - "Rejang": range(43312, 43359 + 1), - "Hangul Jamo Extended-A": range(43360, 43391 + 1), - "Javanese": range(43392, 43487 + 1), - "Myanmar Extended-B": range(43488, 43519 + 1), - "Cham": range(43520, 43615 + 1), - "Myanmar Extended-A": range(43616, 43647 + 1), - "Tai Viet": range(43648, 43743 + 1), - "Meetei Mayek Extensions": range(43744, 43775 + 1), - "Ethiopic Extended-A": range(43776, 43823 + 1), - "Latin Extended-E": range(43824, 43887 + 1), - "Cherokee Supplement": range(43888, 43967 + 1), - "Meetei Mayek": range(43968, 44031 + 1), - "Hangul Syllables": range(44032, 55215 + 1), - "Hangul Jamo Extended-B": range(55216, 55295 + 1), - "High Surrogates": range(55296, 56191 + 1), - "High Private Use Surrogates": range(56192, 56319 + 1), - "Low Surrogates": range(56320, 57343 + 1), - "Private Use Area": range(57344, 63743 + 1), - "CJK Compatibility Ideographs": range(63744, 64255 + 1), - "Alphabetic Presentation Forms": range(64256, 64335 + 1), - "Arabic Presentation Forms-A": range(64336, 65023 + 1), - "Variation Selectors": range(65024, 65039 + 1), - "Vertical Forms": range(65040, 65055 + 1), - "Combining Half Marks": range(65056, 65071 + 1), - "CJK Compatibility Forms": range(65072, 65103 + 1), - "Small Form Variants": range(65104, 65135 + 1), - "Arabic Presentation Forms-B": range(65136, 65279 + 1), - "Halfwidth and Fullwidth Forms": range(65280, 65519 + 1), - "Specials": range(65520, 65535 + 1), - "Linear B Syllabary": range(65536, 65663 + 1), - "Linear B Ideograms": range(65664, 65791 + 1), - "Aegean Numbers": range(65792, 65855 + 1), - "Ancient Greek Numbers": range(65856, 65935 + 1), - "Ancient Symbols": range(65936, 65999 + 1), - "Phaistos Disc": range(66000, 66047 + 1), - "Lycian": range(66176, 66207 + 1), - "Carian": range(66208, 66271 + 1), - "Coptic Epact Numbers": range(66272, 66303 + 1), - "Old Italic": range(66304, 66351 + 1), - "Gothic": range(66352, 66383 + 1), - "Old Permic": range(66384, 66431 + 1), - "Ugaritic": range(66432, 66463 + 1), - "Old Persian": range(66464, 66527 + 1), - "Deseret": range(66560, 66639 + 1), - "Shavian": range(66640, 66687 + 1), - "Osmanya": range(66688, 66735 + 1), - "Osage": range(66736, 66815 + 1), - "Elbasan": range(66816, 66863 + 1), - "Caucasian Albanian": range(66864, 66927 + 1), - "Linear A": range(67072, 67455 + 1), - "Cypriot Syllabary": range(67584, 67647 + 1), - "Imperial Aramaic": range(67648, 67679 + 1), - "Palmyrene": range(67680, 67711 + 1), - "Nabataean": range(67712, 67759 + 1), - "Hatran": range(67808, 67839 + 1), - "Phoenician": range(67840, 67871 + 1), - "Lydian": range(67872, 67903 + 1), - "Meroitic Hieroglyphs": range(67968, 67999 + 1), - "Meroitic Cursive": range(68000, 68095 + 1), - "Kharoshthi": range(68096, 68191 + 1), - "Old South Arabian": range(68192, 68223 + 1), - "Old North Arabian": range(68224, 68255 + 1), - "Manichaean": range(68288, 68351 + 1), - "Avestan": range(68352, 68415 + 1), - "Inscriptional Parthian": range(68416, 68447 + 1), - "Inscriptional Pahlavi": range(68448, 68479 + 1), - "Psalter Pahlavi": range(68480, 68527 + 1), - "Old Turkic": range(68608, 68687 + 1), - "Old Hungarian": range(68736, 68863 + 1), - "Rumi Numeral Symbols": range(69216, 69247 + 1), - "Brahmi": range(69632, 69759 + 1), - "Kaithi": range(69760, 69839 + 1), - "Sora Sompeng": range(69840, 69887 + 1), - "Chakma": range(69888, 69967 + 1), - "Mahajani": range(69968, 70015 + 1), - "Sharada": range(70016, 70111 + 1), - "Sinhala Archaic Numbers": range(70112, 70143 + 1), - "Khojki": range(70144, 70223 + 1), - "Multani": range(70272, 70319 + 1), - "Khudawadi": range(70320, 70399 + 1), - "Grantha": range(70400, 70527 + 1), - "Newa": range(70656, 70783 + 1), - "Tirhuta": range(70784, 70879 + 1), - "Siddham": range(71040, 71167 + 1), - "Modi": range(71168, 71263 + 1), - "Mongolian Supplement": range(71264, 71295 + 1), - "Takri": range(71296, 71375 + 1), - "Ahom": range(71424, 71487 + 1), - "Warang Citi": range(71840, 71935 + 1), - "Zanabazar Square": range(72192, 72271 + 1), - "Soyombo": range(72272, 72367 + 1), - "Pau Cin Hau": range(72384, 72447 + 1), - "Bhaiksuki": range(72704, 72815 + 1), - "Marchen": range(72816, 72895 + 1), - "Masaram Gondi": range(72960, 73055 + 1), - "Cuneiform": range(73728, 74751 + 1), - "Cuneiform Numbers and Punctuation": range(74752, 74879 + 1), - "Early Dynastic Cuneiform": range(74880, 75087 + 1), - "Egyptian Hieroglyphs": range(77824, 78895 + 1), - "Anatolian Hieroglyphs": range(82944, 83583 + 1), - "Bamum Supplement": range(92160, 92735 + 1), - "Mro": range(92736, 92783 + 1), - "Bassa Vah": range(92880, 92927 + 1), - "Pahawh Hmong": range(92928, 93071 + 1), - "Miao": range(93952, 94111 + 1), - "Ideographic Symbols and Punctuation": range(94176, 94207 + 1), - "Tangut": range(94208, 100351 + 1), - "Tangut Components": range(100352, 101119 + 1), - "Kana Supplement": range(110592, 110847 + 1), - "Kana Extended-A": range(110848, 110895 + 1), - "Nushu": range(110960, 111359 + 1), - "Duployan": range(113664, 113823 + 1), - "Shorthand Format Controls": range(113824, 113839 + 1), - "Byzantine Musical Symbols": range(118784, 119039 + 1), - "Musical Symbols": range(119040, 119295 + 1), - "Ancient Greek Musical Notation": range(119296, 119375 + 1), - "Tai Xuan Jing Symbols": range(119552, 119647 + 1), - "Counting Rod Numerals": range(119648, 119679 + 1), - "Mathematical Alphanumeric Symbols": range(119808, 120831 + 1), - "Sutton SignWriting": range(120832, 121519 + 1), - "Glagolitic Supplement": range(122880, 122927 + 1), - "Mende Kikakui": range(124928, 125151 + 1), - "Adlam": range(125184, 125279 + 1), - "Arabic Mathematical Alphabetic Symbols": range(126464, 126719 + 1), - "Mahjong Tiles": range(126976, 127023 + 1), - "Domino Tiles": range(127024, 127135 + 1), - "Playing Cards": range(127136, 127231 + 1), - "Enclosed Alphanumeric Supplement": range(127232, 127487 + 1), - "Enclosed Ideographic Supplement": range(127488, 127743 + 1), - "Miscellaneous Symbols and Pictographs": range(127744, 128511 + 1), - "Emoticons range(Emoji)": range(128512, 128591 + 1), - "Ornamental Dingbats": range(128592, 128639 + 1), - "Transport and Map Symbols": range(128640, 128767 + 1), - "Alchemical Symbols": range(128768, 128895 + 1), - "Geometric Shapes Extended": range(128896, 129023 + 1), - "Supplemental Arrows-C": range(129024, 129279 + 1), - "Supplemental Symbols and Pictographs": range(129280, 129535 + 1), - "CJK Unified Ideographs Extension B": range(131072, 173791 + 1), - "CJK Unified Ideographs Extension C": range(173824, 177983 + 1), - "CJK Unified Ideographs Extension D": range(177984, 178207 + 1), - "CJK Unified Ideographs Extension E": range(178208, 183983 + 1), - "CJK Unified Ideographs Extension F": range(183984, 191471 + 1), - "CJK Compatibility Ideographs Supplement": range(194560, 195103 + 1), - "Tags": range(917504, 917631 + 1), - "Variation Selectors Supplement": range(917760, 917999 + 1), -} - - -UNICODE_SECONDARY_RANGE_KEYWORD: List[str] = [ - "Supplement", - "Extended", - "Extensions", - "Modifier", - "Marks", - "Punctuation", - "Symbols", - "Forms", - "Operators", - "Miscellaneous", - "Drawing", - "Block", - "Shapes", - "Supplemental", - "Tags", -] - -RE_POSSIBLE_ENCODING_INDICATION = re_compile( - r"(?:(?:encoding)|(?:charset)|(?:coding))(?:[\:= ]{1,10})(?:[\"\']?)([a-zA-Z0-9\-_]+)(?:[\"\']?)", - IGNORECASE, -) - -IANA_SUPPORTED: List[str] = sorted( - filter( - lambda x: x.endswith("_codec") is False - and x not in {"rot_13", "tactis", "mbcs"}, - list(set(aliases.values())), - ) -) - -IANA_SUPPORTED_COUNT: int = len(IANA_SUPPORTED) - -# pre-computed code page that are similar using the function cp_similarity. -IANA_SUPPORTED_SIMILAR: Dict[str, List[str]] = { - "cp037": ["cp1026", "cp1140", "cp273", "cp500"], - "cp1026": ["cp037", "cp1140", "cp273", "cp500"], - "cp1125": ["cp866"], - "cp1140": ["cp037", "cp1026", "cp273", "cp500"], - "cp1250": ["iso8859_2"], - "cp1251": ["kz1048", "ptcp154"], - "cp1252": ["iso8859_15", "iso8859_9", "latin_1"], - "cp1253": ["iso8859_7"], - "cp1254": ["iso8859_15", "iso8859_9", "latin_1"], - "cp1257": ["iso8859_13"], - "cp273": ["cp037", "cp1026", "cp1140", "cp500"], - "cp437": ["cp850", "cp858", "cp860", "cp861", "cp862", "cp863", "cp865"], - "cp500": ["cp037", "cp1026", "cp1140", "cp273"], - "cp850": ["cp437", "cp857", "cp858", "cp865"], - "cp857": ["cp850", "cp858", "cp865"], - "cp858": ["cp437", "cp850", "cp857", "cp865"], - "cp860": ["cp437", "cp861", "cp862", "cp863", "cp865"], - "cp861": ["cp437", "cp860", "cp862", "cp863", "cp865"], - "cp862": ["cp437", "cp860", "cp861", "cp863", "cp865"], - "cp863": ["cp437", "cp860", "cp861", "cp862", "cp865"], - "cp865": ["cp437", "cp850", "cp857", "cp858", "cp860", "cp861", "cp862", "cp863"], - "cp866": ["cp1125"], - "iso8859_10": ["iso8859_14", "iso8859_15", "iso8859_4", "iso8859_9", "latin_1"], - "iso8859_11": ["tis_620"], - "iso8859_13": ["cp1257"], - "iso8859_14": [ - "iso8859_10", - "iso8859_15", - "iso8859_16", - "iso8859_3", - "iso8859_9", - "latin_1", - ], - "iso8859_15": [ - "cp1252", - "cp1254", - "iso8859_10", - "iso8859_14", - "iso8859_16", - "iso8859_3", - "iso8859_9", - "latin_1", - ], - "iso8859_16": [ - "iso8859_14", - "iso8859_15", - "iso8859_2", - "iso8859_3", - "iso8859_9", - "latin_1", - ], - "iso8859_2": ["cp1250", "iso8859_16", "iso8859_4"], - "iso8859_3": ["iso8859_14", "iso8859_15", "iso8859_16", "iso8859_9", "latin_1"], - "iso8859_4": ["iso8859_10", "iso8859_2", "iso8859_9", "latin_1"], - "iso8859_7": ["cp1253"], - "iso8859_9": [ - "cp1252", - "cp1254", - "cp1258", - "iso8859_10", - "iso8859_14", - "iso8859_15", - "iso8859_16", - "iso8859_3", - "iso8859_4", - "latin_1", - ], - "kz1048": ["cp1251", "ptcp154"], - "latin_1": [ - "cp1252", - "cp1254", - "cp1258", - "iso8859_10", - "iso8859_14", - "iso8859_15", - "iso8859_16", - "iso8859_3", - "iso8859_4", - "iso8859_9", - ], - "mac_iceland": ["mac_roman", "mac_turkish"], - "mac_roman": ["mac_iceland", "mac_turkish"], - "mac_turkish": ["mac_iceland", "mac_roman"], - "ptcp154": ["cp1251", "kz1048"], - "tis_620": ["iso8859_11"], -} - - -CHARDET_CORRESPONDENCE: Dict[str, str] = { - "iso2022_kr": "ISO-2022-KR", - "iso2022_jp": "ISO-2022-JP", - "euc_kr": "EUC-KR", - "tis_620": "TIS-620", - "utf_32": "UTF-32", - "euc_jp": "EUC-JP", - "koi8_r": "KOI8-R", - "iso8859_1": "ISO-8859-1", - "iso8859_2": "ISO-8859-2", - "iso8859_5": "ISO-8859-5", - "iso8859_6": "ISO-8859-6", - "iso8859_7": "ISO-8859-7", - "iso8859_8": "ISO-8859-8", - "utf_16": "UTF-16", - "cp855": "IBM855", - "mac_cyrillic": "MacCyrillic", - "gb2312": "GB2312", - "gb18030": "GB18030", - "cp932": "CP932", - "cp866": "IBM866", - "utf_8": "utf-8", - "utf_8_sig": "UTF-8-SIG", - "shift_jis": "SHIFT_JIS", - "big5": "Big5", - "cp1250": "windows-1250", - "cp1251": "windows-1251", - "cp1252": "Windows-1252", - "cp1253": "windows-1253", - "cp1255": "windows-1255", - "cp1256": "windows-1256", - "cp1254": "Windows-1254", - "cp949": "CP949", -} - - -COMMON_SAFE_ASCII_CHARACTERS: Set[str] = { - "<", - ">", - "=", - ":", - "/", - "&", - ";", - "{", - "}", - "[", - "]", - ",", - "|", - '"', - "-", -} - - -KO_NAMES: Set[str] = {"johab", "cp949", "euc_kr"} -ZH_NAMES: Set[str] = {"big5", "cp950", "big5hkscs", "hz"} - -NOT_PRINTABLE_PATTERN = re_compile(r"[0-9\W\n\r\t]+") - -LANGUAGE_SUPPORTED_COUNT: int = len(FREQUENCIES) - -# Logging LEVEL bellow DEBUG -TRACE: int = 5 diff --git a/lib/charset_normalizer/legacy.py b/lib/charset_normalizer/legacy.py deleted file mode 100644 index cdebe2b8..00000000 --- a/lib/charset_normalizer/legacy.py +++ /dev/null @@ -1,95 +0,0 @@ -import warnings -from typing import Dict, Optional, Union - -from .api import from_bytes, from_fp, from_path, normalize -from .constant import CHARDET_CORRESPONDENCE -from .models import CharsetMatch, CharsetMatches - - -def detect(byte_str: bytes) -> Dict[str, Optional[Union[str, float]]]: - """ - chardet legacy method - Detect the encoding of the given byte string. It should be mostly backward-compatible. - Encoding name will match Chardet own writing whenever possible. (Not on encoding name unsupported by it) - This function is deprecated and should be used to migrate your project easily, consult the documentation for - further information. Not planned for removal. - - :param byte_str: The byte sequence to examine. - """ - if not isinstance(byte_str, (bytearray, bytes)): - raise TypeError( # pragma: nocover - "Expected object of type bytes or bytearray, got: " - "{0}".format(type(byte_str)) - ) - - if isinstance(byte_str, bytearray): - byte_str = bytes(byte_str) - - r = from_bytes(byte_str).best() - - encoding = r.encoding if r is not None else None - language = r.language if r is not None and r.language != "Unknown" else "" - confidence = 1.0 - r.chaos if r is not None else None - - # Note: CharsetNormalizer does not return 'UTF-8-SIG' as the sig get stripped in the detection/normalization process - # but chardet does return 'utf-8-sig' and it is a valid codec name. - if r is not None and encoding == "utf_8" and r.bom: - encoding += "_sig" - - return { - "encoding": encoding - if encoding not in CHARDET_CORRESPONDENCE - else CHARDET_CORRESPONDENCE[encoding], - "language": language, - "confidence": confidence, - } - - -class CharsetNormalizerMatch(CharsetMatch): - pass - - -class CharsetNormalizerMatches(CharsetMatches): - @staticmethod - def from_fp(*args, **kwargs): # type: ignore - warnings.warn( # pragma: nocover - "staticmethod from_fp, from_bytes, from_path and normalize are deprecated " - "and scheduled to be removed in 3.0", - DeprecationWarning, - ) - return from_fp(*args, **kwargs) # pragma: nocover - - @staticmethod - def from_bytes(*args, **kwargs): # type: ignore - warnings.warn( # pragma: nocover - "staticmethod from_fp, from_bytes, from_path and normalize are deprecated " - "and scheduled to be removed in 3.0", - DeprecationWarning, - ) - return from_bytes(*args, **kwargs) # pragma: nocover - - @staticmethod - def from_path(*args, **kwargs): # type: ignore - warnings.warn( # pragma: nocover - "staticmethod from_fp, from_bytes, from_path and normalize are deprecated " - "and scheduled to be removed in 3.0", - DeprecationWarning, - ) - return from_path(*args, **kwargs) # pragma: nocover - - @staticmethod - def normalize(*args, **kwargs): # type: ignore - warnings.warn( # pragma: nocover - "staticmethod from_fp, from_bytes, from_path and normalize are deprecated " - "and scheduled to be removed in 3.0", - DeprecationWarning, - ) - return normalize(*args, **kwargs) # pragma: nocover - - -class CharsetDetector(CharsetNormalizerMatches): - pass - - -class CharsetDoctor(CharsetNormalizerMatches): - pass diff --git a/lib/charset_normalizer/md.py b/lib/charset_normalizer/md.py deleted file mode 100644 index 31808af8..00000000 --- a/lib/charset_normalizer/md.py +++ /dev/null @@ -1,553 +0,0 @@ -from functools import lru_cache -from typing import List, Optional - -from .constant import COMMON_SAFE_ASCII_CHARACTERS, UNICODE_SECONDARY_RANGE_KEYWORD -from .utils import ( - is_accentuated, - is_ascii, - is_case_variable, - is_cjk, - is_emoticon, - is_hangul, - is_hiragana, - is_katakana, - is_latin, - is_punctuation, - is_separator, - is_symbol, - is_thai, - is_unprintable, - remove_accent, - unicode_range, -) - - -class MessDetectorPlugin: - """ - Base abstract class used for mess detection plugins. - All detectors MUST extend and implement given methods. - """ - - def eligible(self, character: str) -> bool: - """ - Determine if given character should be fed in. - """ - raise NotImplementedError # pragma: nocover - - def feed(self, character: str) -> None: - """ - The main routine to be executed upon character. - Insert the logic in witch the text would be considered chaotic. - """ - raise NotImplementedError # pragma: nocover - - def reset(self) -> None: # pragma: no cover - """ - Permit to reset the plugin to the initial state. - """ - raise NotImplementedError - - @property - def ratio(self) -> float: - """ - Compute the chaos ratio based on what your feed() has seen. - Must NOT be lower than 0.; No restriction gt 0. - """ - raise NotImplementedError # pragma: nocover - - -class TooManySymbolOrPunctuationPlugin(MessDetectorPlugin): - def __init__(self) -> None: - self._punctuation_count: int = 0 - self._symbol_count: int = 0 - self._character_count: int = 0 - - self._last_printable_char: Optional[str] = None - self._frenzy_symbol_in_word: bool = False - - def eligible(self, character: str) -> bool: - return character.isprintable() - - def feed(self, character: str) -> None: - self._character_count += 1 - - if ( - character != self._last_printable_char - and character not in COMMON_SAFE_ASCII_CHARACTERS - ): - if is_punctuation(character): - self._punctuation_count += 1 - elif ( - character.isdigit() is False - and is_symbol(character) - and is_emoticon(character) is False - ): - self._symbol_count += 2 - - self._last_printable_char = character - - def reset(self) -> None: # pragma: no cover - self._punctuation_count = 0 - self._character_count = 0 - self._symbol_count = 0 - - @property - def ratio(self) -> float: - if self._character_count == 0: - return 0.0 - - ratio_of_punctuation: float = ( - self._punctuation_count + self._symbol_count - ) / self._character_count - - return ratio_of_punctuation if ratio_of_punctuation >= 0.3 else 0.0 - - -class TooManyAccentuatedPlugin(MessDetectorPlugin): - def __init__(self) -> None: - self._character_count: int = 0 - self._accentuated_count: int = 0 - - def eligible(self, character: str) -> bool: - return character.isalpha() - - def feed(self, character: str) -> None: - self._character_count += 1 - - if is_accentuated(character): - self._accentuated_count += 1 - - def reset(self) -> None: # pragma: no cover - self._character_count = 0 - self._accentuated_count = 0 - - @property - def ratio(self) -> float: - if self._character_count == 0: - return 0.0 - ratio_of_accentuation: float = self._accentuated_count / self._character_count - return ratio_of_accentuation if ratio_of_accentuation >= 0.35 else 0.0 - - -class UnprintablePlugin(MessDetectorPlugin): - def __init__(self) -> None: - self._unprintable_count: int = 0 - self._character_count: int = 0 - - def eligible(self, character: str) -> bool: - return True - - def feed(self, character: str) -> None: - if is_unprintable(character): - self._unprintable_count += 1 - self._character_count += 1 - - def reset(self) -> None: # pragma: no cover - self._unprintable_count = 0 - - @property - def ratio(self) -> float: - if self._character_count == 0: - return 0.0 - - return (self._unprintable_count * 8) / self._character_count - - -class SuspiciousDuplicateAccentPlugin(MessDetectorPlugin): - def __init__(self) -> None: - self._successive_count: int = 0 - self._character_count: int = 0 - - self._last_latin_character: Optional[str] = None - - def eligible(self, character: str) -> bool: - return character.isalpha() and is_latin(character) - - def feed(self, character: str) -> None: - self._character_count += 1 - if ( - self._last_latin_character is not None - and is_accentuated(character) - and is_accentuated(self._last_latin_character) - ): - if character.isupper() and self._last_latin_character.isupper(): - self._successive_count += 1 - # Worse if its the same char duplicated with different accent. - if remove_accent(character) == remove_accent(self._last_latin_character): - self._successive_count += 1 - self._last_latin_character = character - - def reset(self) -> None: # pragma: no cover - self._successive_count = 0 - self._character_count = 0 - self._last_latin_character = None - - @property - def ratio(self) -> float: - if self._character_count == 0: - return 0.0 - - return (self._successive_count * 2) / self._character_count - - -class SuspiciousRange(MessDetectorPlugin): - def __init__(self) -> None: - self._suspicious_successive_range_count: int = 0 - self._character_count: int = 0 - self._last_printable_seen: Optional[str] = None - - def eligible(self, character: str) -> bool: - return character.isprintable() - - def feed(self, character: str) -> None: - self._character_count += 1 - - if ( - character.isspace() - or is_punctuation(character) - or character in COMMON_SAFE_ASCII_CHARACTERS - ): - self._last_printable_seen = None - return - - if self._last_printable_seen is None: - self._last_printable_seen = character - return - - unicode_range_a: Optional[str] = unicode_range(self._last_printable_seen) - unicode_range_b: Optional[str] = unicode_range(character) - - if is_suspiciously_successive_range(unicode_range_a, unicode_range_b): - self._suspicious_successive_range_count += 1 - - self._last_printable_seen = character - - def reset(self) -> None: # pragma: no cover - self._character_count = 0 - self._suspicious_successive_range_count = 0 - self._last_printable_seen = None - - @property - def ratio(self) -> float: - if self._character_count == 0: - return 0.0 - - ratio_of_suspicious_range_usage: float = ( - self._suspicious_successive_range_count * 2 - ) / self._character_count - - if ratio_of_suspicious_range_usage < 0.1: - return 0.0 - - return ratio_of_suspicious_range_usage - - -class SuperWeirdWordPlugin(MessDetectorPlugin): - def __init__(self) -> None: - self._word_count: int = 0 - self._bad_word_count: int = 0 - self._foreign_long_count: int = 0 - - self._is_current_word_bad: bool = False - self._foreign_long_watch: bool = False - - self._character_count: int = 0 - self._bad_character_count: int = 0 - - self._buffer: str = "" - self._buffer_accent_count: int = 0 - - def eligible(self, character: str) -> bool: - return True - - def feed(self, character: str) -> None: - if character.isalpha(): - self._buffer += character - if is_accentuated(character): - self._buffer_accent_count += 1 - if ( - self._foreign_long_watch is False - and (is_latin(character) is False or is_accentuated(character)) - and is_cjk(character) is False - and is_hangul(character) is False - and is_katakana(character) is False - and is_hiragana(character) is False - and is_thai(character) is False - ): - self._foreign_long_watch = True - return - if not self._buffer: - return - if ( - character.isspace() or is_punctuation(character) or is_separator(character) - ) and self._buffer: - self._word_count += 1 - buffer_length: int = len(self._buffer) - - self._character_count += buffer_length - - if buffer_length >= 4: - if self._buffer_accent_count / buffer_length > 0.34: - self._is_current_word_bad = True - # Word/Buffer ending with a upper case accentuated letter are so rare, - # that we will consider them all as suspicious. Same weight as foreign_long suspicious. - if is_accentuated(self._buffer[-1]) and self._buffer[-1].isupper(): - self._foreign_long_count += 1 - self._is_current_word_bad = True - if buffer_length >= 24 and self._foreign_long_watch: - self._foreign_long_count += 1 - self._is_current_word_bad = True - - if self._is_current_word_bad: - self._bad_word_count += 1 - self._bad_character_count += len(self._buffer) - self._is_current_word_bad = False - - self._foreign_long_watch = False - self._buffer = "" - self._buffer_accent_count = 0 - elif ( - character not in {"<", ">", "-", "=", "~", "|", "_"} - and character.isdigit() is False - and is_symbol(character) - ): - self._is_current_word_bad = True - self._buffer += character - - def reset(self) -> None: # pragma: no cover - self._buffer = "" - self._is_current_word_bad = False - self._foreign_long_watch = False - self._bad_word_count = 0 - self._word_count = 0 - self._character_count = 0 - self._bad_character_count = 0 - self._foreign_long_count = 0 - - @property - def ratio(self) -> float: - if self._word_count <= 10 and self._foreign_long_count == 0: - return 0.0 - - return self._bad_character_count / self._character_count - - -class CjkInvalidStopPlugin(MessDetectorPlugin): - """ - GB(Chinese) based encoding often render the stop incorrectly when the content does not fit and - can be easily detected. Searching for the overuse of '丅' and '丄'. - """ - - def __init__(self) -> None: - self._wrong_stop_count: int = 0 - self._cjk_character_count: int = 0 - - def eligible(self, character: str) -> bool: - return True - - def feed(self, character: str) -> None: - if character in {"丅", "丄"}: - self._wrong_stop_count += 1 - return - if is_cjk(character): - self._cjk_character_count += 1 - - def reset(self) -> None: # pragma: no cover - self._wrong_stop_count = 0 - self._cjk_character_count = 0 - - @property - def ratio(self) -> float: - if self._cjk_character_count < 16: - return 0.0 - return self._wrong_stop_count / self._cjk_character_count - - -class ArchaicUpperLowerPlugin(MessDetectorPlugin): - def __init__(self) -> None: - self._buf: bool = False - - self._character_count_since_last_sep: int = 0 - - self._successive_upper_lower_count: int = 0 - self._successive_upper_lower_count_final: int = 0 - - self._character_count: int = 0 - - self._last_alpha_seen: Optional[str] = None - self._current_ascii_only: bool = True - - def eligible(self, character: str) -> bool: - return True - - def feed(self, character: str) -> None: - is_concerned = character.isalpha() and is_case_variable(character) - chunk_sep = is_concerned is False - - if chunk_sep and self._character_count_since_last_sep > 0: - if ( - self._character_count_since_last_sep <= 64 - and character.isdigit() is False - and self._current_ascii_only is False - ): - self._successive_upper_lower_count_final += ( - self._successive_upper_lower_count - ) - - self._successive_upper_lower_count = 0 - self._character_count_since_last_sep = 0 - self._last_alpha_seen = None - self._buf = False - self._character_count += 1 - self._current_ascii_only = True - - return - - if self._current_ascii_only is True and is_ascii(character) is False: - self._current_ascii_only = False - - if self._last_alpha_seen is not None: - if (character.isupper() and self._last_alpha_seen.islower()) or ( - character.islower() and self._last_alpha_seen.isupper() - ): - if self._buf is True: - self._successive_upper_lower_count += 2 - self._buf = False - else: - self._buf = True - else: - self._buf = False - - self._character_count += 1 - self._character_count_since_last_sep += 1 - self._last_alpha_seen = character - - def reset(self) -> None: # pragma: no cover - self._character_count = 0 - self._character_count_since_last_sep = 0 - self._successive_upper_lower_count = 0 - self._successive_upper_lower_count_final = 0 - self._last_alpha_seen = None - self._buf = False - self._current_ascii_only = True - - @property - def ratio(self) -> float: - if self._character_count == 0: - return 0.0 - - return self._successive_upper_lower_count_final / self._character_count - - -@lru_cache(maxsize=1024) -def is_suspiciously_successive_range( - unicode_range_a: Optional[str], unicode_range_b: Optional[str] -) -> bool: - """ - Determine if two Unicode range seen next to each other can be considered as suspicious. - """ - if unicode_range_a is None or unicode_range_b is None: - return True - - if unicode_range_a == unicode_range_b: - return False - - if "Latin" in unicode_range_a and "Latin" in unicode_range_b: - return False - - if "Emoticons" in unicode_range_a or "Emoticons" in unicode_range_b: - return False - - # Latin characters can be accompanied with a combining diacritical mark - # eg. Vietnamese. - if ("Latin" in unicode_range_a or "Latin" in unicode_range_b) and ( - "Combining" in unicode_range_a or "Combining" in unicode_range_b - ): - return False - - keywords_range_a, keywords_range_b = unicode_range_a.split( - " " - ), unicode_range_b.split(" ") - - for el in keywords_range_a: - if el in UNICODE_SECONDARY_RANGE_KEYWORD: - continue - if el in keywords_range_b: - return False - - # Japanese Exception - range_a_jp_chars, range_b_jp_chars = ( - unicode_range_a - in ( - "Hiragana", - "Katakana", - ), - unicode_range_b in ("Hiragana", "Katakana"), - ) - if (range_a_jp_chars or range_b_jp_chars) and ( - "CJK" in unicode_range_a or "CJK" in unicode_range_b - ): - return False - if range_a_jp_chars and range_b_jp_chars: - return False - - if "Hangul" in unicode_range_a or "Hangul" in unicode_range_b: - if "CJK" in unicode_range_a or "CJK" in unicode_range_b: - return False - if unicode_range_a == "Basic Latin" or unicode_range_b == "Basic Latin": - return False - - # Chinese/Japanese use dedicated range for punctuation and/or separators. - if ("CJK" in unicode_range_a or "CJK" in unicode_range_b) or ( - unicode_range_a in ["Katakana", "Hiragana"] - and unicode_range_b in ["Katakana", "Hiragana"] - ): - if "Punctuation" in unicode_range_a or "Punctuation" in unicode_range_b: - return False - if "Forms" in unicode_range_a or "Forms" in unicode_range_b: - return False - - return True - - -@lru_cache(maxsize=2048) -def mess_ratio( - decoded_sequence: str, maximum_threshold: float = 0.2, debug: bool = False -) -> float: - """ - Compute a mess ratio given a decoded bytes sequence. The maximum threshold does stop the computation earlier. - """ - - detectors: List[MessDetectorPlugin] = [ - md_class() for md_class in MessDetectorPlugin.__subclasses__() - ] - - length: int = len(decoded_sequence) + 1 - - mean_mess_ratio: float = 0.0 - - if length < 512: - intermediary_mean_mess_ratio_calc: int = 32 - elif length <= 1024: - intermediary_mean_mess_ratio_calc = 64 - else: - intermediary_mean_mess_ratio_calc = 128 - - for character, index in zip(decoded_sequence + "\n", range(length)): - for detector in detectors: - if detector.eligible(character): - detector.feed(character) - - if ( - index > 0 and index % intermediary_mean_mess_ratio_calc == 0 - ) or index == length - 1: - mean_mess_ratio = sum(dt.ratio for dt in detectors) - - if mean_mess_ratio >= maximum_threshold: - break - - if debug: - for dt in detectors: # pragma: nocover - print(dt.__class__, dt.ratio) - - return round(mean_mess_ratio, 3) diff --git a/lib/charset_normalizer/models.py b/lib/charset_normalizer/models.py deleted file mode 100644 index ccb0d475..00000000 --- a/lib/charset_normalizer/models.py +++ /dev/null @@ -1,401 +0,0 @@ -import warnings -from collections import Counter -from encodings.aliases import aliases -from hashlib import sha256 -from json import dumps -from re import sub -from typing import ( - Any, - Counter as TypeCounter, - Dict, - Iterator, - List, - Optional, - Tuple, - Union, -) - -from .constant import NOT_PRINTABLE_PATTERN, TOO_BIG_SEQUENCE -from .md import mess_ratio -from .utils import iana_name, is_multi_byte_encoding, unicode_range - - -class CharsetMatch: - def __init__( - self, - payload: bytes, - guessed_encoding: str, - mean_mess_ratio: float, - has_sig_or_bom: bool, - languages: "CoherenceMatches", - decoded_payload: Optional[str] = None, - ): - self._payload: bytes = payload - - self._encoding: str = guessed_encoding - self._mean_mess_ratio: float = mean_mess_ratio - self._languages: CoherenceMatches = languages - self._has_sig_or_bom: bool = has_sig_or_bom - self._unicode_ranges: Optional[List[str]] = None - - self._leaves: List[CharsetMatch] = [] - self._mean_coherence_ratio: float = 0.0 - - self._output_payload: Optional[bytes] = None - self._output_encoding: Optional[str] = None - - self._string: Optional[str] = decoded_payload - - def __eq__(self, other: object) -> bool: - if not isinstance(other, CharsetMatch): - raise TypeError( - "__eq__ cannot be invoked on {} and {}.".format( - str(other.__class__), str(self.__class__) - ) - ) - return self.encoding == other.encoding and self.fingerprint == other.fingerprint - - def __lt__(self, other: object) -> bool: - """ - Implemented to make sorted available upon CharsetMatches items. - """ - if not isinstance(other, CharsetMatch): - raise ValueError - - chaos_difference: float = abs(self.chaos - other.chaos) - coherence_difference: float = abs(self.coherence - other.coherence) - - # Bellow 1% difference --> Use Coherence - if chaos_difference < 0.01 and coherence_difference > 0.02: - # When having a tough decision, use the result that decoded as many multi-byte as possible. - if chaos_difference == 0.0 and self.coherence == other.coherence: - return self.multi_byte_usage > other.multi_byte_usage - return self.coherence > other.coherence - - return self.chaos < other.chaos - - @property - def multi_byte_usage(self) -> float: - return 1.0 - len(str(self)) / len(self.raw) - - @property - def chaos_secondary_pass(self) -> float: - """ - Check once again chaos in decoded text, except this time, with full content. - Use with caution, this can be very slow. - Notice: Will be removed in 3.0 - """ - warnings.warn( - "chaos_secondary_pass is deprecated and will be removed in 3.0", - DeprecationWarning, - ) - return mess_ratio(str(self), 1.0) - - @property - def coherence_non_latin(self) -> float: - """ - Coherence ratio on the first non-latin language detected if ANY. - Notice: Will be removed in 3.0 - """ - warnings.warn( - "coherence_non_latin is deprecated and will be removed in 3.0", - DeprecationWarning, - ) - return 0.0 - - @property - def w_counter(self) -> TypeCounter[str]: - """ - Word counter instance on decoded text. - Notice: Will be removed in 3.0 - """ - warnings.warn( - "w_counter is deprecated and will be removed in 3.0", DeprecationWarning - ) - - string_printable_only = sub(NOT_PRINTABLE_PATTERN, " ", str(self).lower()) - - return Counter(string_printable_only.split()) - - def __str__(self) -> str: - # Lazy Str Loading - if self._string is None: - self._string = str(self._payload, self._encoding, "strict") - return self._string - - def __repr__(self) -> str: - return "".format(self.encoding, self.fingerprint) - - def add_submatch(self, other: "CharsetMatch") -> None: - if not isinstance(other, CharsetMatch) or other == self: - raise ValueError( - "Unable to add instance <{}> as a submatch of a CharsetMatch".format( - other.__class__ - ) - ) - - other._string = None # Unload RAM usage; dirty trick. - self._leaves.append(other) - - @property - def encoding(self) -> str: - return self._encoding - - @property - def encoding_aliases(self) -> List[str]: - """ - Encoding name are known by many name, using this could help when searching for IBM855 when it's listed as CP855. - """ - also_known_as: List[str] = [] - for u, p in aliases.items(): - if self.encoding == u: - also_known_as.append(p) - elif self.encoding == p: - also_known_as.append(u) - return also_known_as - - @property - def bom(self) -> bool: - return self._has_sig_or_bom - - @property - def byte_order_mark(self) -> bool: - return self._has_sig_or_bom - - @property - def languages(self) -> List[str]: - """ - Return the complete list of possible languages found in decoded sequence. - Usually not really useful. Returned list may be empty even if 'language' property return something != 'Unknown'. - """ - return [e[0] for e in self._languages] - - @property - def language(self) -> str: - """ - Most probable language found in decoded sequence. If none were detected or inferred, the property will return - "Unknown". - """ - if not self._languages: - # Trying to infer the language based on the given encoding - # Its either English or we should not pronounce ourselves in certain cases. - if "ascii" in self.could_be_from_charset: - return "English" - - # doing it there to avoid circular import - from charset_normalizer.cd import encoding_languages, mb_encoding_languages - - languages = ( - mb_encoding_languages(self.encoding) - if is_multi_byte_encoding(self.encoding) - else encoding_languages(self.encoding) - ) - - if len(languages) == 0 or "Latin Based" in languages: - return "Unknown" - - return languages[0] - - return self._languages[0][0] - - @property - def chaos(self) -> float: - return self._mean_mess_ratio - - @property - def coherence(self) -> float: - if not self._languages: - return 0.0 - return self._languages[0][1] - - @property - def percent_chaos(self) -> float: - return round(self.chaos * 100, ndigits=3) - - @property - def percent_coherence(self) -> float: - return round(self.coherence * 100, ndigits=3) - - @property - def raw(self) -> bytes: - """ - Original untouched bytes. - """ - return self._payload - - @property - def submatch(self) -> List["CharsetMatch"]: - return self._leaves - - @property - def has_submatch(self) -> bool: - return len(self._leaves) > 0 - - @property - def alphabets(self) -> List[str]: - if self._unicode_ranges is not None: - return self._unicode_ranges - # list detected ranges - detected_ranges: List[Optional[str]] = [ - unicode_range(char) for char in str(self) - ] - # filter and sort - self._unicode_ranges = sorted(list({r for r in detected_ranges if r})) - return self._unicode_ranges - - @property - def could_be_from_charset(self) -> List[str]: - """ - The complete list of encoding that output the exact SAME str result and therefore could be the originating - encoding. - This list does include the encoding available in property 'encoding'. - """ - return [self._encoding] + [m.encoding for m in self._leaves] - - def first(self) -> "CharsetMatch": - """ - Kept for BC reasons. Will be removed in 3.0. - """ - return self - - def best(self) -> "CharsetMatch": - """ - Kept for BC reasons. Will be removed in 3.0. - """ - return self - - def output(self, encoding: str = "utf_8") -> bytes: - """ - Method to get re-encoded bytes payload using given target encoding. Default to UTF-8. - Any errors will be simply ignored by the encoder NOT replaced. - """ - if self._output_encoding is None or self._output_encoding != encoding: - self._output_encoding = encoding - self._output_payload = str(self).encode(encoding, "replace") - - return self._output_payload # type: ignore - - @property - def fingerprint(self) -> str: - """ - Retrieve the unique SHA256 computed using the transformed (re-encoded) payload. Not the original one. - """ - return sha256(self.output()).hexdigest() - - -class CharsetMatches: - """ - Container with every CharsetMatch items ordered by default from most probable to the less one. - Act like a list(iterable) but does not implements all related methods. - """ - - def __init__(self, results: Optional[List[CharsetMatch]] = None): - self._results: List[CharsetMatch] = sorted(results) if results else [] - - def __iter__(self) -> Iterator[CharsetMatch]: - yield from self._results - - def __getitem__(self, item: Union[int, str]) -> CharsetMatch: - """ - Retrieve a single item either by its position or encoding name (alias may be used here). - Raise KeyError upon invalid index or encoding not present in results. - """ - if isinstance(item, int): - return self._results[item] - if isinstance(item, str): - item = iana_name(item, False) - for result in self._results: - if item in result.could_be_from_charset: - return result - raise KeyError - - def __len__(self) -> int: - return len(self._results) - - def __bool__(self) -> bool: - return len(self._results) > 0 - - def append(self, item: CharsetMatch) -> None: - """ - Insert a single match. Will be inserted accordingly to preserve sort. - Can be inserted as a submatch. - """ - if not isinstance(item, CharsetMatch): - raise ValueError( - "Cannot append instance '{}' to CharsetMatches".format( - str(item.__class__) - ) - ) - # We should disable the submatch factoring when the input file is too heavy (conserve RAM usage) - if len(item.raw) <= TOO_BIG_SEQUENCE: - for match in self._results: - if match.fingerprint == item.fingerprint and match.chaos == item.chaos: - match.add_submatch(item) - return - self._results.append(item) - self._results = sorted(self._results) - - def best(self) -> Optional["CharsetMatch"]: - """ - Simply return the first match. Strict equivalent to matches[0]. - """ - if not self._results: - return None - return self._results[0] - - def first(self) -> Optional["CharsetMatch"]: - """ - Redundant method, call the method best(). Kept for BC reasons. - """ - return self.best() - - -CoherenceMatch = Tuple[str, float] -CoherenceMatches = List[CoherenceMatch] - - -class CliDetectionResult: - def __init__( - self, - path: str, - encoding: Optional[str], - encoding_aliases: List[str], - alternative_encodings: List[str], - language: str, - alphabets: List[str], - has_sig_or_bom: bool, - chaos: float, - coherence: float, - unicode_path: Optional[str], - is_preferred: bool, - ): - self.path: str = path - self.unicode_path: Optional[str] = unicode_path - self.encoding: Optional[str] = encoding - self.encoding_aliases: List[str] = encoding_aliases - self.alternative_encodings: List[str] = alternative_encodings - self.language: str = language - self.alphabets: List[str] = alphabets - self.has_sig_or_bom: bool = has_sig_or_bom - self.chaos: float = chaos - self.coherence: float = coherence - self.is_preferred: bool = is_preferred - - @property - def __dict__(self) -> Dict[str, Any]: # type: ignore - return { - "path": self.path, - "encoding": self.encoding, - "encoding_aliases": self.encoding_aliases, - "alternative_encodings": self.alternative_encodings, - "language": self.language, - "alphabets": self.alphabets, - "has_sig_or_bom": self.has_sig_or_bom, - "chaos": self.chaos, - "coherence": self.coherence, - "unicode_path": self.unicode_path, - "is_preferred": self.is_preferred, - } - - def to_json(self) -> str: - return dumps(self.__dict__, ensure_ascii=True, indent=4) diff --git a/lib/charset_normalizer/py.typed b/lib/charset_normalizer/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/charset_normalizer/utils.py b/lib/charset_normalizer/utils.py deleted file mode 100644 index 859f212b..00000000 --- a/lib/charset_normalizer/utils.py +++ /dev/null @@ -1,424 +0,0 @@ -try: - # WARNING: unicodedata2 support is going to be removed in 3.0 - # Python is quickly catching up. - import unicodedata2 as unicodedata -except ImportError: - import unicodedata # type: ignore[no-redef] - -import importlib -import logging -from codecs import IncrementalDecoder -from encodings.aliases import aliases -from functools import lru_cache -from re import findall -from typing import Generator, List, Optional, Set, Tuple, Union - -from _multibytecodec import MultibyteIncrementalDecoder - -from .constant import ( - ENCODING_MARKS, - IANA_SUPPORTED_SIMILAR, - RE_POSSIBLE_ENCODING_INDICATION, - UNICODE_RANGES_COMBINED, - UNICODE_SECONDARY_RANGE_KEYWORD, - UTF8_MAXIMAL_ALLOCATION, -) - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_accentuated(character: str) -> bool: - try: - description: str = unicodedata.name(character) - except ValueError: - return False - return ( - "WITH GRAVE" in description - or "WITH ACUTE" in description - or "WITH CEDILLA" in description - or "WITH DIAERESIS" in description - or "WITH CIRCUMFLEX" in description - or "WITH TILDE" in description - ) - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def remove_accent(character: str) -> str: - decomposed: str = unicodedata.decomposition(character) - if not decomposed: - return character - - codes: List[str] = decomposed.split(" ") - - return chr(int(codes[0], 16)) - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def unicode_range(character: str) -> Optional[str]: - """ - Retrieve the Unicode range official name from a single character. - """ - character_ord: int = ord(character) - - for range_name, ord_range in UNICODE_RANGES_COMBINED.items(): - if character_ord in ord_range: - return range_name - - return None - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_latin(character: str) -> bool: - try: - description: str = unicodedata.name(character) - except ValueError: - return False - return "LATIN" in description - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_ascii(character: str) -> bool: - try: - character.encode("ascii") - except UnicodeEncodeError: - return False - return True - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_punctuation(character: str) -> bool: - character_category: str = unicodedata.category(character) - - if "P" in character_category: - return True - - character_range: Optional[str] = unicode_range(character) - - if character_range is None: - return False - - return "Punctuation" in character_range - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_symbol(character: str) -> bool: - character_category: str = unicodedata.category(character) - - if "S" in character_category or "N" in character_category: - return True - - character_range: Optional[str] = unicode_range(character) - - if character_range is None: - return False - - return "Forms" in character_range - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_emoticon(character: str) -> bool: - character_range: Optional[str] = unicode_range(character) - - if character_range is None: - return False - - return "Emoticons" in character_range - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_separator(character: str) -> bool: - if character.isspace() or character in {"|", "+", ",", ";", "<", ">"}: - return True - - character_category: str = unicodedata.category(character) - - return "Z" in character_category - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_case_variable(character: str) -> bool: - return character.islower() != character.isupper() - - -def is_private_use_only(character: str) -> bool: - character_category: str = unicodedata.category(character) - - return character_category == "Co" - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_cjk(character: str) -> bool: - try: - character_name = unicodedata.name(character) - except ValueError: - return False - - return "CJK" in character_name - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_hiragana(character: str) -> bool: - try: - character_name = unicodedata.name(character) - except ValueError: - return False - - return "HIRAGANA" in character_name - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_katakana(character: str) -> bool: - try: - character_name = unicodedata.name(character) - except ValueError: - return False - - return "KATAKANA" in character_name - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_hangul(character: str) -> bool: - try: - character_name = unicodedata.name(character) - except ValueError: - return False - - return "HANGUL" in character_name - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_thai(character: str) -> bool: - try: - character_name = unicodedata.name(character) - except ValueError: - return False - - return "THAI" in character_name - - -@lru_cache(maxsize=len(UNICODE_RANGES_COMBINED)) -def is_unicode_range_secondary(range_name: str) -> bool: - return any(keyword in range_name for keyword in UNICODE_SECONDARY_RANGE_KEYWORD) - - -@lru_cache(maxsize=UTF8_MAXIMAL_ALLOCATION) -def is_unprintable(character: str) -> bool: - return ( - character.isspace() is False # includes \n \t \r \v - and character.isprintable() is False - and character != "\x1A" # Why? Its the ASCII substitute character. - and character != "\ufeff" # bug discovered in Python, - # Zero Width No-Break Space located in Arabic Presentation Forms-B, Unicode 1.1 not acknowledged as space. - ) - - -def any_specified_encoding(sequence: bytes, search_zone: int = 4096) -> Optional[str]: - """ - Extract using ASCII-only decoder any specified encoding in the first n-bytes. - """ - if not isinstance(sequence, bytes): - raise TypeError - - seq_len: int = len(sequence) - - results: List[str] = findall( - RE_POSSIBLE_ENCODING_INDICATION, - sequence[: min(seq_len, search_zone)].decode("ascii", errors="ignore"), - ) - - if len(results) == 0: - return None - - for specified_encoding in results: - specified_encoding = specified_encoding.lower().replace("-", "_") - - encoding_alias: str - encoding_iana: str - - for encoding_alias, encoding_iana in aliases.items(): - if encoding_alias == specified_encoding: - return encoding_iana - if encoding_iana == specified_encoding: - return encoding_iana - - return None - - -@lru_cache(maxsize=128) -def is_multi_byte_encoding(name: str) -> bool: - """ - Verify is a specific encoding is a multi byte one based on it IANA name - """ - return name in { - "utf_8", - "utf_8_sig", - "utf_16", - "utf_16_be", - "utf_16_le", - "utf_32", - "utf_32_le", - "utf_32_be", - "utf_7", - } or issubclass( - importlib.import_module("encodings.{}".format(name)).IncrementalDecoder, - MultibyteIncrementalDecoder, - ) - - -def identify_sig_or_bom(sequence: bytes) -> Tuple[Optional[str], bytes]: - """ - Identify and extract SIG/BOM in given sequence. - """ - - for iana_encoding in ENCODING_MARKS: - marks: Union[bytes, List[bytes]] = ENCODING_MARKS[iana_encoding] - - if isinstance(marks, bytes): - marks = [marks] - - for mark in marks: - if sequence.startswith(mark): - return iana_encoding, mark - - return None, b"" - - -def should_strip_sig_or_bom(iana_encoding: str) -> bool: - return iana_encoding not in {"utf_16", "utf_32"} - - -def iana_name(cp_name: str, strict: bool = True) -> str: - cp_name = cp_name.lower().replace("-", "_") - - encoding_alias: str - encoding_iana: str - - for encoding_alias, encoding_iana in aliases.items(): - if cp_name in [encoding_alias, encoding_iana]: - return encoding_iana - - if strict: - raise ValueError("Unable to retrieve IANA for '{}'".format(cp_name)) - - return cp_name - - -def range_scan(decoded_sequence: str) -> List[str]: - ranges: Set[str] = set() - - for character in decoded_sequence: - character_range: Optional[str] = unicode_range(character) - - if character_range is None: - continue - - ranges.add(character_range) - - return list(ranges) - - -def cp_similarity(iana_name_a: str, iana_name_b: str) -> float: - - if is_multi_byte_encoding(iana_name_a) or is_multi_byte_encoding(iana_name_b): - return 0.0 - - decoder_a = importlib.import_module( - "encodings.{}".format(iana_name_a) - ).IncrementalDecoder - decoder_b = importlib.import_module( - "encodings.{}".format(iana_name_b) - ).IncrementalDecoder - - id_a: IncrementalDecoder = decoder_a(errors="ignore") - id_b: IncrementalDecoder = decoder_b(errors="ignore") - - character_match_count: int = 0 - - for i in range(255): - to_be_decoded: bytes = bytes([i]) - if id_a.decode(to_be_decoded) == id_b.decode(to_be_decoded): - character_match_count += 1 - - return character_match_count / 254 - - -def is_cp_similar(iana_name_a: str, iana_name_b: str) -> bool: - """ - Determine if two code page are at least 80% similar. IANA_SUPPORTED_SIMILAR dict was generated using - the function cp_similarity. - """ - return ( - iana_name_a in IANA_SUPPORTED_SIMILAR - and iana_name_b in IANA_SUPPORTED_SIMILAR[iana_name_a] - ) - - -def set_logging_handler( - name: str = "charset_normalizer", - level: int = logging.INFO, - format_string: str = "%(asctime)s | %(levelname)s | %(message)s", -) -> None: - - logger = logging.getLogger(name) - logger.setLevel(level) - - handler = logging.StreamHandler() - handler.setFormatter(logging.Formatter(format_string)) - logger.addHandler(handler) - - -def cut_sequence_chunks( - sequences: bytes, - encoding_iana: str, - offsets: range, - chunk_size: int, - bom_or_sig_available: bool, - strip_sig_or_bom: bool, - sig_payload: bytes, - is_multi_byte_decoder: bool, - decoded_payload: Optional[str] = None, -) -> Generator[str, None, None]: - - if decoded_payload and is_multi_byte_decoder is False: - for i in offsets: - chunk = decoded_payload[i : i + chunk_size] - if not chunk: - break - yield chunk - else: - for i in offsets: - chunk_end = i + chunk_size - if chunk_end > len(sequences) + 8: - continue - - cut_sequence = sequences[i : i + chunk_size] - - if bom_or_sig_available and strip_sig_or_bom is False: - cut_sequence = sig_payload + cut_sequence - - chunk = cut_sequence.decode( - encoding_iana, - errors="ignore" if is_multi_byte_decoder else "strict", - ) - - # multi-byte bad cutting detector and adjustment - # not the cleanest way to perform that fix but clever enough for now. - if is_multi_byte_decoder and i > 0 and sequences[i] >= 0x80: - - chunk_partial_size_chk: int = min(chunk_size, 16) - - if ( - decoded_payload - and chunk[:chunk_partial_size_chk] not in decoded_payload - ): - for j in range(i, i - 4, -1): - cut_sequence = sequences[j:chunk_end] - - if bom_or_sig_available and strip_sig_or_bom is False: - cut_sequence = sig_payload + cut_sequence - - chunk = cut_sequence.decode(encoding_iana, errors="ignore") - - if chunk[:chunk_partial_size_chk] in decoded_payload: - break - - yield chunk diff --git a/lib/charset_normalizer/version.py b/lib/charset_normalizer/version.py deleted file mode 100644 index 64c0dbde..00000000 --- a/lib/charset_normalizer/version.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Expose version -""" - -__version__ = "2.1.1" -VERSION = __version__.split(".") diff --git a/lib/cheroot-9.0.0.dist-info/INSTALLER b/lib/cheroot-9.0.0.dist-info/INSTALLER deleted file mode 100644 index a1b589e3..00000000 --- a/lib/cheroot-9.0.0.dist-info/INSTALLER +++ /dev/null @@ -1 +0,0 @@ -pip diff --git a/lib/cheroot-9.0.0.dist-info/LICENSE.md b/lib/cheroot-9.0.0.dist-info/LICENSE.md deleted file mode 100644 index 92164090..00000000 --- a/lib/cheroot-9.0.0.dist-info/LICENSE.md +++ /dev/null @@ -1,30 +0,0 @@ -Copyright © 2004-2020, CherryPy Team (team@cherrypy.dev) - -All rights reserved. - -* * * - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -* Neither the name of CherryPy nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/cheroot-9.0.0.dist-info/METADATA b/lib/cheroot-9.0.0.dist-info/METADATA deleted file mode 100644 index bef5eb21..00000000 --- a/lib/cheroot-9.0.0.dist-info/METADATA +++ /dev/null @@ -1,150 +0,0 @@ -Metadata-Version: 2.1 -Name: cheroot -Version: 9.0.0 -Summary: Highly-optimized, pure-python HTTP server -Home-page: https://cheroot.cherrypy.dev -Author: CherryPy Team -Author-email: team@cherrypy.dev -Project-URL: CI: GitHub, https://github.com/cherrypy/cheroot/actions -Project-URL: Docs: RTD, https://cheroot.cherrypy.dev -Project-URL: GitHub: issues, https://github.com/cherrypy/cheroot/issues -Project-URL: GitHub: repo, https://github.com/cherrypy/cheroot -Project-URL: Tidelift: funding, https://tidelift.com/subscription/pkg/pypi-cheroot?utm_source=pypi-cheroot&utm_medium=referral&utm_campaign=pypi -Keywords: http,server,ssl,wsgi -Classifier: Development Status :: 5 - Production/Stable -Classifier: Environment :: Web Environment -Classifier: Intended Audience :: Developers -Classifier: Operating System :: OS Independent -Classifier: Framework :: CherryPy -Classifier: License :: OSI Approved :: BSD License -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 3 -Classifier: Programming Language :: Python :: 3 :: Only -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: 3.8 -Classifier: Programming Language :: Python :: 3.9 -Classifier: Programming Language :: Python :: 3.10 -Classifier: Programming Language :: Python :: 3.11 -Classifier: Programming Language :: Python :: Implementation -Classifier: Programming Language :: Python :: Implementation :: CPython -Classifier: Programming Language :: Python :: Implementation :: Jython -Classifier: Programming Language :: Python :: Implementation :: PyPy -Classifier: Topic :: Internet :: WWW/HTTP -Classifier: Topic :: Internet :: WWW/HTTP :: HTTP Servers -Classifier: Topic :: Internet :: WWW/HTTP :: WSGI -Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Server -Classifier: Typing :: Typed -Requires-Python: >=3.6 -Description-Content-Type: text/x-rst -License-File: LICENSE.md -Requires-Dist: six (>=1.11.0) -Requires-Dist: jaraco.functools -Requires-Dist: backports.functools-lru-cache ; python_version < "3.3" -Requires-Dist: selectors2 ; python_version < "3.4" -Requires-Dist: more-itertools (<8.11.0,>=2.6) ; python_version < "3.6" -Requires-Dist: importlib-metadata ; python_version < "3.8" -Requires-Dist: more-itertools (>=2.6) ; python_version >= "3.6" -Provides-Extra: docs -Requires-Dist: sphinx (>=1.8.2) ; extra == 'docs' -Requires-Dist: jaraco.packaging (>=3.2) ; extra == 'docs' -Requires-Dist: sphinx-tabs (>=1.1.0) ; extra == 'docs' -Requires-Dist: furo ; extra == 'docs' -Requires-Dist: python-dateutil ; extra == 'docs' -Requires-Dist: sphinxcontrib-apidoc (>=0.3.0) ; extra == 'docs' - -.. image:: https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct.svg - :target: https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md - :alt: SWUbanner - -.. image:: https://img.shields.io/pypi/v/cheroot.svg - :target: https://pypi.org/project/cheroot - -.. image:: https://tidelift.com/badges/package/pypi/cheroot - :target: https://tidelift.com/subscription/pkg/pypi-cheroot?utm_source=pypi-cheroot&utm_medium=readme - :alt: Cheroot is available as part of the Tidelift Subscription - -.. image:: https://github.com/cherrypy/cheroot/actions/workflows/ci-cd.yml/badge.svg - :target: https://github.com/cherrypy/cheroot/actions/workflows/ci-cd.yml - :alt: GitHub Actions CI/CD Workflow - -.. image:: https://img.shields.io/badge/license-BSD-blue.svg?maxAge=3600 - :target: https://pypi.org/project/cheroot - -.. image:: https://img.shields.io/pypi/pyversions/cheroot.svg - :target: https://pypi.org/project/cheroot - -.. image:: https://codecov.io/gh/cherrypy/cheroot/branch/master/graph/badge.svg - :target: https://codecov.io/gh/cherrypy/cheroot - :alt: codecov - -.. image:: https://readthedocs.org/projects/cheroot/badge/?version=latest - :target: https://cheroot.cherrypy.dev/en/latest/?badge=latest - -.. image:: https://img.shields.io/badge/StackOverflow-Cheroot-blue.svg - :target: https://stackoverflow.com/questions/tagged/cheroot+or+cherrypy - -.. image:: https://img.shields.io/gitter/room/cherrypy/cherrypy.svg - :target: https://gitter.im/cherrypy/cherrypy - -.. image:: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square - :target: http://makeapullrequest.com/ - -.. image:: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fcherrypy%2Fcheroot.svg?type=shield - :target: https://app.fossa.io/projects/git%2Bgithub.com%2Fcherrypy%2Fcheroot?ref=badge_shield - :alt: FOSSA Status - -Cheroot is the high-performance, pure-Python HTTP server used by CherryPy. - -Status -====== - -The test suite currently relies on pytest. It's being run via GitHub -Actions CI/CD workflows. - -For Enterprise -============== - -.. list-table:: - :widths: 10 100 - - * - |tideliftlogo| - - Professional support for Cheroot is available as part of the - `Tidelift Subscription`_. The CherryPy maintainers and the - maintainers of thousands of other packages are working with - Tidelift to deliver one enterprise subscription that covers all - of the open source you use. - - Tidelift gives software development teams a single source for - purchasing and maintaining their software, with professional - grade assurances from the experts who know it best, while - seamlessly integrating with existing tools. - - `Learn more `_. - -.. _Tidelift Subscription: https://tidelift.com/subscription/pkg/pypi-cheroot?utm_source=pypi-cheroot&utm_medium=referral&utm_campaign=readme - -.. |tideliftlogo| image:: https://cdn2.hubspot.net/hubfs/4008838/website/logos/logos_for_download/Tidelift_primary-shorthand-logo.png - :target: https://tidelift.com/subscription/pkg/pypi-cheroot?utm_source=pypi-cheroot&utm_medium=readme - :width: 75 - :alt: Tidelift - -Contribute Cheroot -================== -**Want to add something to upstream?** Feel free to submit a PR or file an issue -if unsure. Please follow `CherryPy's common contribution guidelines -`_. -Note that PR is more likely to be accepted if it includes tests and detailed -description helping maintainers to understand it better 🎉 - -Oh, and be pythonic, please 🐍 - -**Don't know how?** Check out `How to Contribute to Open Source -`_ article by GitHub 🚀 - - -License -======= -.. image:: https://app.fossa.io/api/projects/git%2Bgithub.com%2Fcherrypy%2Fcheroot.svg?type=large - :target: https://app.fossa.io/projects/git%2Bgithub.com%2Fcherrypy%2Fcheroot?ref=badge_large - :alt: FOSSA Status diff --git a/lib/cheroot-9.0.0.dist-info/RECORD b/lib/cheroot-9.0.0.dist-info/RECORD deleted file mode 100644 index 6a69cbba..00000000 --- a/lib/cheroot-9.0.0.dist-info/RECORD +++ /dev/null @@ -1,83 +0,0 @@ -../../bin/cheroot.exe,sha256=4bvuX9K4kuFx8bi_Wi1zvLPAipSV03rij8ucyQOZx0I,108415 -cheroot-9.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 -cheroot-9.0.0.dist-info/LICENSE.md,sha256=4g_utJGn6YCE8VcZNJ6YV6rUHEUDxeR5-IFbBj2_dWQ,1511 -cheroot-9.0.0.dist-info/METADATA,sha256=PNUPxbXjn0mSSS_Seryb0QoCPLMJ_MQeyaK4n5OuilY,6633 -cheroot-9.0.0.dist-info/RECORD,, -cheroot-9.0.0.dist-info/WHEEL,sha256=bb2Ot9scclHKMOLDEHY6B2sicWOgugjFKaJsT7vwMQo,110 -cheroot-9.0.0.dist-info/entry_points.txt,sha256=K_I24wVCr42Pygr3RAy4-dSVdNiQPCXDHcBvJcBvk9U,45 -cheroot-9.0.0.dist-info/top_level.txt,sha256=P8VZfrem5gTRS34X6Thu7jyEoj_zSaPNI_3P0fShbAI,8 -cheroot/__init__.py,sha256=ZZgcItIBeyj6d3xlBw_o4uFxtBwL86Y9MwzFSfmd1_c,284 -cheroot/__init__.pyi,sha256=Y25n44pyE3vp92MiABKrcK3IWRyQ1JG1rZ4Ufqy2nC0,17 -cheroot/__main__.py,sha256=jLOqwD221LYbg1ksPWhjOKYu4M4yV7y3mteM6CJi-Oc,109 -cheroot/__pycache__/__init__.cpython-311.pyc,, -cheroot/__pycache__/__main__.cpython-311.pyc,, -cheroot/__pycache__/_compat.cpython-311.pyc,, -cheroot/__pycache__/cli.cpython-311.pyc,, -cheroot/__pycache__/connections.cpython-311.pyc,, -cheroot/__pycache__/errors.cpython-311.pyc,, -cheroot/__pycache__/makefile.cpython-311.pyc,, -cheroot/__pycache__/server.cpython-311.pyc,, -cheroot/__pycache__/testing.cpython-311.pyc,, -cheroot/__pycache__/wsgi.cpython-311.pyc,, -cheroot/_compat.py,sha256=Yz5NpDh4RMFkULE0xbHyKczYT9jRt_unNjS_SgZPWoI,2046 -cheroot/_compat.pyi,sha256=ir6o-IrUyCJ6qO_gxIcs5qod9AlYiVLNF3kdle3N5Fc,588 -cheroot/cli.py,sha256=5qxJ0tK7nUPwTI2Q97VyjlPNdVwznoj-gTT7DJ_y_S8,6987 -cheroot/cli.pyi,sha256=LIKNaRFyZVRRl2n3Jm_VTJjYaNsDSJQ7IcEr5qcjZR0,828 -cheroot/connections.py,sha256=erXfV-v9rUhFnAW5UcJDtq2yIEa26jRfO2XP1x72J9U,14461 -cheroot/connections.pyi,sha256=r-I9Mkn-PHcjqQvekmMmQNbh8a5-sJyfLp6d5sP7T48,714 -cheroot/errors.py,sha256=BUbMk0cu3LkmvKSdw5yl_rIalOiXDMo31msjMhIchls,2854 -cheroot/errors.pyi,sha256=WPJaht4vEcELxTStNPMYuEmztK52aLbOcYE8MwGTmSU,425 -cheroot/makefile.py,sha256=XVLIM1ngS_XbLzZryUPgs5SSnV8CHyyRGzU3J4QpVPE,2306 -cheroot/makefile.pyi,sha256=39bXRM-9N1jOAEgfudimuqoOkLFAShg-naldaQDKnGM,542 -cheroot/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -cheroot/server.py,sha256=PTpaV5nuKlGt3XNb23Rvi3y82fX4Z7Sz4lydrYykwHY,75996 -cheroot/server.pyi,sha256=fwWhE98nNp1BtWDlkmWO8rUiNO7Eo7Goxh2_yKIHthY,4910 -cheroot/ssl/__init__.py,sha256=U2aplZGnvXibKBSGN3Y9cje_zFS-mA_bJz5jO9h7JaY,1416 -cheroot/ssl/__init__.pyi,sha256=diz9LWOaOnci4cHstYnA6_kz_muPetXmlkxOV4qRy34,555 -cheroot/ssl/__pycache__/__init__.cpython-311.pyc,, -cheroot/ssl/__pycache__/builtin.cpython-311.pyc,, -cheroot/ssl/__pycache__/pyopenssl.cpython-311.pyc,, -cheroot/ssl/builtin.py,sha256=2apU5DsSFCTVysj53Ek-zKb3YH5V01j5MCWwTd3GK7M,17906 -cheroot/ssl/builtin.pyi,sha256=nriJS-4eEkyw04oOZW8dslIXLfr_Kzkb7izy6TavEIw,561 -cheroot/ssl/pyopenssl.py,sha256=3aVW3aQYW6YOHoQyPYaJ46Ts1EQ5_yZCM5g9SnvFQzs,13231 -cheroot/ssl/pyopenssl.pyi,sha256=YlpXT8Rpj_mvBtOBiFuBep758p618JSegch9x2OCP38,1086 -cheroot/test/__init__.py,sha256=_hgyWgeLHsLLScMhGwMOsevD3Wg3pssK535YfNJFoeU,26 -cheroot/test/__pycache__/__init__.cpython-311.pyc,, -cheroot/test/__pycache__/_pytest_plugin.cpython-311.pyc,, -cheroot/test/__pycache__/conftest.cpython-311.pyc,, -cheroot/test/__pycache__/helper.cpython-311.pyc,, -cheroot/test/__pycache__/test__compat.cpython-311.pyc,, -cheroot/test/__pycache__/test_cli.cpython-311.pyc,, -cheroot/test/__pycache__/test_conn.cpython-311.pyc,, -cheroot/test/__pycache__/test_core.cpython-311.pyc,, -cheroot/test/__pycache__/test_dispatch.cpython-311.pyc,, -cheroot/test/__pycache__/test_errors.cpython-311.pyc,, -cheroot/test/__pycache__/test_makefile.cpython-311.pyc,, -cheroot/test/__pycache__/test_server.cpython-311.pyc,, -cheroot/test/__pycache__/test_ssl.cpython-311.pyc,, -cheroot/test/__pycache__/test_wsgi.cpython-311.pyc,, -cheroot/test/__pycache__/webtest.cpython-311.pyc,, -cheroot/test/_pytest_plugin.py,sha256=Vk-MCqCLTBYIpYdASigAHO6h0Uxe1eD-MvZOkWhkpTM,2365 -cheroot/test/conftest.py,sha256=pyMICEAvtvWw1Q-sqhr-VVcal3NbWlDD3rKe9_ROsGM,2051 -cheroot/test/helper.py,sha256=A5HlkxxyswxYRT_0IjHja_TWvd2VxDY44Elzni0uke0,4755 -cheroot/test/test__compat.py,sha256=uC4QuuYSDDMyErGij-vHfGV1fKTA_ylnZEmWXMLXUvc,1661 -cheroot/test/test_cli.py,sha256=7bmQNozJ8cakCFw-ogUzAZq7yGUaSJYwzgXQx8tQfLg,2664 -cheroot/test/test_conn.py,sha256=naE9l4sugRzDkdD7YC_G8PULl_UUa_CplJ6k5DpKr-0,43866 -cheroot/test/test_core.py,sha256=AyCUQXIkqwjH4giyeEEwn0ZxlRJx0gDJeciSE7ymc5k,14897 -cheroot/test/test_dispatch.py,sha256=NJtth_qaHlKa7V83j-ekrf_8G2-7HI8EQiZdKTUcCx4,1210 -cheroot/test/test_errors.py,sha256=xknJ_rJsI_L6FuAUoqqHh99ywTkOdILSU9akT7jeDwA,868 -cheroot/test/test_makefile.py,sha256=j3ZVMMDJI_JPTun-agdaJSKa8z3kH5HhXv_95Lg9T2I,1191 -cheroot/test/test_server.py,sha256=JscNkBKk82LBu5nwKIVTXerdUsruSfiTb3A44OvHTFM,12949 -cheroot/test/test_ssl.py,sha256=2Su5Y9k0ESVfYcu9j4IxzEc4qO_nIOWp7_5gw7P7XjY,22508 -cheroot/test/test_wsgi.py,sha256=vdyH1JN2K8qbfx4olnda-VHOxErtemaVVEUjVlNC5mo,2823 -cheroot/test/webtest.py,sha256=JTUafaFcN6z0RjbLtQ2fntYvILAAMJC1YxWUoVAAbU0,18509 -cheroot/testing.py,sha256=CfkNasT22Ew_sQv1BEWGchrY8LwUxSXJiKWZdmCQJI0,4104 -cheroot/testing.pyi,sha256=HlbbwoROyn5DSAuCQxcUw58v2cCKu1MG7ySzwbH0ZXk,448 -cheroot/workers/__init__.py,sha256=-ziyw7iPWHs2dN4R_Q7AQZ7r0dQPTes1nVCzAg2LOc8,25 -cheroot/workers/__init__.pyi,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 -cheroot/workers/__pycache__/__init__.cpython-311.pyc,, -cheroot/workers/__pycache__/threadpool.cpython-311.pyc,, -cheroot/workers/threadpool.py,sha256=V6EcbGiaJozsoxHoQAoGE6NghjbxPzdSBYg8ueVfBT8,10594 -cheroot/workers/threadpool.pyi,sha256=9xF6s4LAwnURJbdG1K8f97zGk2YVb2kEULJMaxsUXiI,925 -cheroot/wsgi.py,sha256=i0Y8KfnPfqoGYg3Dv50I-6zQJpKX10mQmEiYF369zys,14178 -cheroot/wsgi.pyi,sha256=E_xW_HRQ1dInwMfnCogXD4GM2RMbs77ZkhH4UBq7HaM,1492 diff --git a/lib/cheroot-9.0.0.dist-info/WHEEL b/lib/cheroot-9.0.0.dist-info/WHEEL deleted file mode 100644 index 9d8f872b..00000000 --- a/lib/cheroot-9.0.0.dist-info/WHEEL +++ /dev/null @@ -1,6 +0,0 @@ -Wheel-Version: 1.0 -Generator: bdist_wheel (0.38.4) -Root-Is-Purelib: true -Tag: py2-none-any -Tag: py3-none-any - diff --git a/lib/cheroot-9.0.0.dist-info/entry_points.txt b/lib/cheroot-9.0.0.dist-info/entry_points.txt deleted file mode 100644 index 757165c0..00000000 --- a/lib/cheroot-9.0.0.dist-info/entry_points.txt +++ /dev/null @@ -1,2 +0,0 @@ -[console_scripts] -cheroot = cheroot.cli:main diff --git a/lib/cheroot-9.0.0.dist-info/top_level.txt b/lib/cheroot-9.0.0.dist-info/top_level.txt deleted file mode 100644 index a610cf88..00000000 --- a/lib/cheroot-9.0.0.dist-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -cheroot diff --git a/lib/cheroot/__init__.py b/lib/cheroot/__init__.py deleted file mode 100644 index aac9cd98..00000000 --- a/lib/cheroot/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -"""High-performance, pure-Python HTTP server used by CherryPy.""" - -try: - from importlib import metadata -except ImportError: - import importlib_metadata as metadata # noqa: WPS440 - - -try: - __version__ = metadata.version('cheroot') -except Exception: - __version__ = 'unknown' diff --git a/lib/cheroot/__init__.pyi b/lib/cheroot/__init__.pyi deleted file mode 100644 index bda5b5a7..00000000 --- a/lib/cheroot/__init__.pyi +++ /dev/null @@ -1 +0,0 @@ -__version__: str diff --git a/lib/cheroot/__main__.py b/lib/cheroot/__main__.py deleted file mode 100644 index d2e27c10..00000000 --- a/lib/cheroot/__main__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Stub for accessing the Cheroot CLI tool.""" - -from .cli import main - -if __name__ == '__main__': - main() diff --git a/lib/cheroot/_compat.py b/lib/cheroot/_compat.py deleted file mode 100644 index 20c993de..00000000 --- a/lib/cheroot/_compat.py +++ /dev/null @@ -1,83 +0,0 @@ -# pylint: disable=unused-import -"""Compatibility code for using Cheroot with various versions of Python.""" - -import os -import platform - - -try: - import ssl - IS_ABOVE_OPENSSL10 = ssl.OPENSSL_VERSION_INFO >= (1, 1) - del ssl -except ImportError: - IS_ABOVE_OPENSSL10 = None - - -IS_CI = bool(os.getenv('CI')) -IS_GITHUB_ACTIONS_WORKFLOW = bool(os.getenv('GITHUB_WORKFLOW')) - - -IS_PYPY = platform.python_implementation() == 'PyPy' - - -SYS_PLATFORM = platform.system() -IS_WINDOWS = SYS_PLATFORM == 'Windows' -IS_LINUX = SYS_PLATFORM == 'Linux' -IS_MACOS = SYS_PLATFORM == 'Darwin' - -PLATFORM_ARCH = platform.machine() -IS_PPC = PLATFORM_ARCH.startswith('ppc') - - -def ntob(n, encoding='ISO-8859-1'): - """Return the native string as bytes in the given encoding.""" - assert_native(n) - # In Python 3, the native string type is unicode - return n.encode(encoding) - - -def ntou(n, encoding='ISO-8859-1'): - """Return the native string as Unicode with the given encoding.""" - assert_native(n) - # In Python 3, the native string type is unicode - return n - - -def bton(b, encoding='ISO-8859-1'): - """Return the byte string as native string in the given encoding.""" - return b.decode(encoding) - - -def assert_native(n): - """Check whether the input is of native :py:class:`str` type. - - Raises: - TypeError: in case of failed check - - """ - if not isinstance(n, str): - raise TypeError('n must be a native str (got %s)' % type(n).__name__) - - -def extract_bytes(mv): - r"""Retrieve bytes out of the given input buffer. - - :param mv: input :py:func:`buffer` - :type mv: memoryview or bytes - - :return: unwrapped bytes - :rtype: bytes - - :raises ValueError: if the input is not one of \ - :py:class:`memoryview`/:py:func:`buffer` \ - or :py:class:`bytes` - """ - if isinstance(mv, memoryview): - return mv.tobytes() - - if isinstance(mv, bytes): - return mv - - raise ValueError( - 'extract_bytes() only accepts bytes and memoryview/buffer', - ) diff --git a/lib/cheroot/_compat.pyi b/lib/cheroot/_compat.pyi deleted file mode 100644 index 023bad8c..00000000 --- a/lib/cheroot/_compat.pyi +++ /dev/null @@ -1,21 +0,0 @@ -from typing import Any, ContextManager, Optional, Type, Union - -def suppress(*exceptions: Type[BaseException]) -> ContextManager[None]: ... - -IS_ABOVE_OPENSSL10: Optional[bool] -IS_CI: bool -IS_GITHUB_ACTIONS_WORKFLOW: bool -IS_PYPY: bool -SYS_PLATFORM: str -IS_WINDOWS: bool -IS_LINUX: bool -IS_MACOS: bool -PLATFORM_ARCH: str -IS_PPC: bool - -def ntob(n: str, encoding: str = ...) -> bytes: ... -def ntou(n: str, encoding: str = ...) -> str: ... -def bton(b: bytes, encoding: str = ...) -> str: ... -def assert_native(n: str) -> None: ... - -def extract_bytes(mv: Union[memoryview, bytes]) -> bytes: ... diff --git a/lib/cheroot/cli.py b/lib/cheroot/cli.py deleted file mode 100644 index cd168e91..00000000 --- a/lib/cheroot/cli.py +++ /dev/null @@ -1,243 +0,0 @@ -"""Command line tool for starting a Cheroot WSGI/HTTP server instance. - -Basic usage: - -.. code-block:: shell-session - - $ # Start a server on 127.0.0.1:8000 with the default settings - $ # for the WSGI app myapp/wsgi.py:application() - $ cheroot myapp.wsgi - - $ # Start a server on 0.0.0.0:9000 with 8 threads - $ # for the WSGI app myapp/wsgi.py:main_app() - $ cheroot myapp.wsgi:main_app --bind 0.0.0.0:9000 --threads 8 - - $ # Start a server for the cheroot.server.Gateway subclass - $ # myapp/gateway.py:HTTPGateway - $ cheroot myapp.gateway:HTTPGateway - - $ # Start a server on the UNIX socket /var/spool/myapp.sock - $ cheroot myapp.wsgi --bind /var/spool/myapp.sock - - $ # Start a server on the abstract UNIX socket CherootServer - $ cheroot myapp.wsgi --bind @CherootServer - -.. spelling:: - - cli -""" - -import argparse -import os -import sys -import urllib.parse # noqa: WPS301 -from importlib import import_module -from contextlib import suppress - -from . import server -from . import wsgi - - -class BindLocation: - """A class for storing the bind location for a Cheroot instance.""" - - -class TCPSocket(BindLocation): - """TCPSocket.""" - - def __init__(self, address, port): - """Initialize. - - Args: - address (str): Host name or IP address - port (int): TCP port number - - """ - self.bind_addr = address, port - - -class UnixSocket(BindLocation): - """UnixSocket.""" - - def __init__(self, path): - """Initialize.""" - self.bind_addr = path - - -class AbstractSocket(BindLocation): - """AbstractSocket.""" - - def __init__(self, abstract_socket): - """Initialize.""" - self.bind_addr = '\x00{sock_path}'.format(sock_path=abstract_socket) - - -class Application: - """Application.""" - - @classmethod - def resolve(cls, full_path): - """Read WSGI app/Gateway path string and import application module.""" - mod_path, _, app_path = full_path.partition(':') - app = getattr(import_module(mod_path), app_path or 'application') - # suppress the `TypeError` exception, just in case `app` is not a class - with suppress(TypeError): - if issubclass(app, server.Gateway): - return GatewayYo(app) - - return cls(app) - - def __init__(self, wsgi_app): - """Initialize.""" - if not callable(wsgi_app): - raise TypeError( - 'Application must be a callable object or ' - 'cheroot.server.Gateway subclass', - ) - self.wsgi_app = wsgi_app - - def server_args(self, parsed_args): - """Return keyword args for Server class.""" - args = { - arg: value - for arg, value in vars(parsed_args).items() - if not arg.startswith('_') and value is not None - } - args.update(vars(self)) - return args - - def server(self, parsed_args): - """Server.""" - return wsgi.Server(**self.server_args(parsed_args)) - - -class GatewayYo: - """Gateway.""" - - def __init__(self, gateway): - """Init.""" - self.gateway = gateway - - def server(self, parsed_args): - """Server.""" - server_args = vars(self) - server_args['bind_addr'] = parsed_args['bind_addr'] - if parsed_args.max is not None: - server_args['maxthreads'] = parsed_args.max - if parsed_args.numthreads is not None: - server_args['minthreads'] = parsed_args.numthreads - return server.HTTPServer(**server_args) - - -def parse_wsgi_bind_location(bind_addr_string): - """Convert bind address string to a BindLocation.""" - # if the string begins with an @ symbol, use an abstract socket, - # this is the first condition to verify, otherwise the urlparse - # validation would detect //@ as a valid url with a hostname - # with value: "" and port: None - if bind_addr_string.startswith('@'): - return AbstractSocket(bind_addr_string[1:]) - - # try and match for an IP/hostname and port - match = urllib.parse.urlparse( - '//{addr}'.format(addr=bind_addr_string), - ) - try: - addr = match.hostname - port = match.port - if addr is not None or port is not None: - return TCPSocket(addr, port) - except ValueError: - pass - - # else, assume a UNIX socket path - return UnixSocket(path=bind_addr_string) - - -def parse_wsgi_bind_addr(bind_addr_string): - """Convert bind address string to bind address parameter.""" - return parse_wsgi_bind_location(bind_addr_string).bind_addr - - -_arg_spec = { - '_wsgi_app': { - 'metavar': 'APP_MODULE', - 'type': Application.resolve, - 'help': 'WSGI application callable or cheroot.server.Gateway subclass', - }, - '--bind': { - 'metavar': 'ADDRESS', - 'dest': 'bind_addr', - 'type': parse_wsgi_bind_addr, - 'default': '[::1]:8000', - 'help': 'Network interface to listen on (default: [::1]:8000)', - }, - '--chdir': { - 'metavar': 'PATH', - 'type': os.chdir, - 'help': 'Set the working directory', - }, - '--server-name': { - 'dest': 'server_name', - 'type': str, - 'help': 'Web server name to be advertised via Server HTTP header', - }, - '--threads': { - 'metavar': 'INT', - 'dest': 'numthreads', - 'type': int, - 'help': 'Minimum number of worker threads', - }, - '--max-threads': { - 'metavar': 'INT', - 'dest': 'max', - 'type': int, - 'help': 'Maximum number of worker threads', - }, - '--timeout': { - 'metavar': 'INT', - 'dest': 'timeout', - 'type': int, - 'help': 'Timeout in seconds for accepted connections', - }, - '--shutdown-timeout': { - 'metavar': 'INT', - 'dest': 'shutdown_timeout', - 'type': int, - 'help': 'Time in seconds to wait for worker threads to cleanly exit', - }, - '--request-queue-size': { - 'metavar': 'INT', - 'dest': 'request_queue_size', - 'type': int, - 'help': 'Maximum number of queued connections', - }, - '--accepted-queue-size': { - 'metavar': 'INT', - 'dest': 'accepted_queue_size', - 'type': int, - 'help': 'Maximum number of active requests in queue', - }, - '--accepted-queue-timeout': { - 'metavar': 'INT', - 'dest': 'accepted_queue_timeout', - 'type': int, - 'help': 'Timeout in seconds for putting requests into queue', - }, -} - - -def main(): - """Create a new Cheroot instance with arguments from the command line.""" - parser = argparse.ArgumentParser( - description='Start an instance of the Cheroot WSGI/HTTP server.', - ) - for arg, spec in _arg_spec.items(): - parser.add_argument(arg, **spec) - raw_args = parser.parse_args() - - # ensure cwd in sys.path - '' in sys.path or sys.path.insert(0, '') - - # create a server based on the arguments provided - raw_args._wsgi_app.server(raw_args).safe_start() diff --git a/lib/cheroot/cli.pyi b/lib/cheroot/cli.pyi deleted file mode 100644 index b9803b3e..00000000 --- a/lib/cheroot/cli.pyi +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Any - -class BindLocation: ... - -class TCPSocket(BindLocation): - bind_addr: Any - def __init__(self, address, port) -> None: ... - -class UnixSocket(BindLocation): - bind_addr: Any - def __init__(self, path) -> None: ... - -class AbstractSocket(BindLocation): - bind_addr: Any - def __init__(self, abstract_socket) -> None: ... - -class Application: - @classmethod - def resolve(cls, full_path): ... - wsgi_app: Any - def __init__(self, wsgi_app) -> None: ... - def server_args(self, parsed_args): ... - def server(self, parsed_args): ... - -class GatewayYo: - gateway: Any - def __init__(self, gateway) -> None: ... - def server(self, parsed_args): ... - -def parse_wsgi_bind_location(bind_addr_string: str): ... -def parse_wsgi_bind_addr(bind_addr_string: str): ... -def main() -> None: ... diff --git a/lib/cheroot/connections.py b/lib/cheroot/connections.py deleted file mode 100644 index 9b6366e5..00000000 --- a/lib/cheroot/connections.py +++ /dev/null @@ -1,388 +0,0 @@ -"""Utilities to manage open connections.""" - -import io -import os -import socket -import threading -import time -import selectors -from contextlib import suppress - -from . import errors -from ._compat import IS_WINDOWS -from .makefile import MakeFile - -try: - import fcntl -except ImportError: - try: - from ctypes import windll, WinError - import ctypes.wintypes - _SetHandleInformation = windll.kernel32.SetHandleInformation - _SetHandleInformation.argtypes = [ - ctypes.wintypes.HANDLE, - ctypes.wintypes.DWORD, - ctypes.wintypes.DWORD, - ] - _SetHandleInformation.restype = ctypes.wintypes.BOOL - except ImportError: - def prevent_socket_inheritance(sock): - """Stub inheritance prevention. - - Dummy function, since neither fcntl nor ctypes are available. - """ - pass - else: - def prevent_socket_inheritance(sock): - """Mark the given socket fd as non-inheritable (Windows).""" - if not _SetHandleInformation(sock.fileno(), 1, 0): - raise WinError() -else: - def prevent_socket_inheritance(sock): - """Mark the given socket fd as non-inheritable (POSIX).""" - fd = sock.fileno() - old_flags = fcntl.fcntl(fd, fcntl.F_GETFD) - fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC) - - -class _ThreadsafeSelector: - """Thread-safe wrapper around a DefaultSelector. - - There are 2 thread contexts in which it may be accessed: - * the selector thread - * one of the worker threads in workers/threadpool.py - - The expected read/write patterns are: - * :py:func:`~iter`: selector thread - * :py:meth:`register`: selector thread and threadpool, - via :py:meth:`~cheroot.workers.threadpool.ThreadPool.put` - * :py:meth:`unregister`: selector thread only - - Notably, this means :py:class:`_ThreadsafeSelector` never needs to worry - that connections will be removed behind its back. - - The lock is held when iterating or modifying the selector but is not - required when :py:meth:`select()ing ` on it. - """ - - def __init__(self): - self._selector = selectors.DefaultSelector() - self._lock = threading.Lock() - - def __len__(self): - with self._lock: - return len(self._selector.get_map() or {}) - - @property - def connections(self): - """Retrieve connections registered with the selector.""" - with self._lock: - mapping = self._selector.get_map() or {} - for _, (_, sock_fd, _, conn) in mapping.items(): - yield (sock_fd, conn) - - def register(self, fileobj, events, data=None): - """Register ``fileobj`` with the selector.""" - with self._lock: - return self._selector.register(fileobj, events, data) - - def unregister(self, fileobj): - """Unregister ``fileobj`` from the selector.""" - with self._lock: - return self._selector.unregister(fileobj) - - def select(self, timeout=None): - """Return socket fd and data pairs from selectors.select call. - - Returns entries ready to read in the form: - (socket_file_descriptor, connection) - """ - return ( - (key.fd, key.data) - for key, _ in self._selector.select(timeout=timeout) - ) - - def close(self): - """Close the selector.""" - with self._lock: - self._selector.close() - - -class ConnectionManager: - """Class which manages HTTPConnection objects. - - This is for connections which are being kept-alive for follow-up requests. - """ - - def __init__(self, server): - """Initialize ConnectionManager object. - - Args: - server (cheroot.server.HTTPServer): web server object - that uses this ConnectionManager instance. - """ - self._serving = False - self._stop_requested = False - - self.server = server - self._selector = _ThreadsafeSelector() - - self._selector.register( - server.socket.fileno(), - selectors.EVENT_READ, data=server, - ) - - def put(self, conn): - """Put idle connection into the ConnectionManager to be managed. - - :param conn: HTTP connection to be managed - :type conn: cheroot.server.HTTPConnection - """ - conn.last_used = time.time() - # if this conn doesn't have any more data waiting to be read, - # register it with the selector. - if conn.rfile.has_data(): - self.server.process_conn(conn) - else: - self._selector.register( - conn.socket.fileno(), selectors.EVENT_READ, data=conn, - ) - - def _expire(self, threshold): - r"""Expire least recently used connections. - - :param threshold: Connections that have not been used within this \ - duration (in seconds), are considered expired and \ - are closed and removed. - :type threshold: float - - This should be called periodically. - """ - # find any connections still registered with the selector - # that have not been active recently enough. - timed_out_connections = [ - (sock_fd, conn) - for (sock_fd, conn) in self._selector.connections - if conn != self.server and conn.last_used < threshold - ] - for sock_fd, conn in timed_out_connections: - self._selector.unregister(sock_fd) - conn.close() - - def stop(self): - """Stop the selector loop in run() synchronously. - - May take up to half a second. - """ - self._stop_requested = True - while self._serving: - time.sleep(0.01) - - def run(self, expiration_interval): - """Run the connections selector indefinitely. - - Args: - expiration_interval (float): Interval, in seconds, at which - connections will be checked for expiration. - - Connections that are ready to process are submitted via - self.server.process_conn() - - Connections submitted for processing must be `put()` - back if they should be examined again for another request. - - Can be shut down by calling `stop()`. - """ - self._serving = True - try: - self._run(expiration_interval) - finally: - self._serving = False - - def _run(self, expiration_interval): - r"""Run connection handler loop until stop was requested. - - :param expiration_interval: Interval, in seconds, at which \ - connections will be checked for \ - expiration. - :type expiration_interval: float - - Use ``expiration_interval`` as ``select()`` timeout - to assure expired connections are closed in time. - - On Windows cap the timeout to 0.05 seconds - as ``select()`` does not return when a socket is ready. - """ - last_expiration_check = time.time() - if IS_WINDOWS: - # 0.05 seconds are used as an empirically obtained balance between - # max connection delay and idle system load. Benchmarks show a - # mean processing time per connection of ~0.03 seconds on Linux - # and with 0.01 seconds timeout on Windows: - # https://github.com/cherrypy/cheroot/pull/352 - # While this highly depends on system and hardware, 0.05 seconds - # max delay should hence usually not significantly increase the - # mean time/delay per connection, but significantly reduce idle - # system load by reducing socket loops to 1/5 with 0.01 seconds. - select_timeout = min(expiration_interval, 0.05) - else: - select_timeout = expiration_interval - - while not self._stop_requested: - try: - active_list = self._selector.select(timeout=select_timeout) - except OSError: - self._remove_invalid_sockets() - continue - - for (sock_fd, conn) in active_list: - if conn is self.server: - # New connection - new_conn = self._from_server_socket(self.server.socket) - if new_conn is not None: - self.server.process_conn(new_conn) - else: - # unregister connection from the selector until the server - # has read from it and returned it via put() - self._selector.unregister(sock_fd) - self.server.process_conn(conn) - - now = time.time() - if (now - last_expiration_check) > expiration_interval: - self._expire(threshold=now - self.server.timeout) - last_expiration_check = now - - def _remove_invalid_sockets(self): - """Clean up the resources of any broken connections. - - This method attempts to detect any connections in an invalid state, - unregisters them from the selector and closes the file descriptors of - the corresponding network sockets where possible. - """ - invalid_conns = [] - for sock_fd, conn in self._selector.connections: - if conn is self.server: - continue - - try: - os.fstat(sock_fd) - except OSError: - invalid_conns.append((sock_fd, conn)) - - for sock_fd, conn in invalid_conns: - self._selector.unregister(sock_fd) - # One of the reason on why a socket could cause an error - # is that the socket is already closed, ignore the - # socket error if we try to close it at this point. - # This is equivalent to OSError in Py3 - with suppress(socket.error): - conn.close() - - def _from_server_socket(self, server_socket): # noqa: C901 # FIXME - try: - s, addr = server_socket.accept() - if self.server.stats['Enabled']: - self.server.stats['Accepts'] += 1 - prevent_socket_inheritance(s) - if hasattr(s, 'settimeout'): - s.settimeout(self.server.timeout) - - mf = MakeFile - ssl_env = {} - # if ssl cert and key are set, we try to be a secure HTTP server - if self.server.ssl_adapter is not None: - try: - s, ssl_env = self.server.ssl_adapter.wrap(s) - except errors.NoSSLError: - msg = ( - 'The client sent a plain HTTP request, but ' - 'this server only speaks HTTPS on this port.' - ) - buf = [ - '%s 400 Bad Request\r\n' % self.server.protocol, - 'Content-Length: %s\r\n' % len(msg), - 'Content-Type: text/plain\r\n\r\n', - msg, - ] - - wfile = mf(s, 'wb', io.DEFAULT_BUFFER_SIZE) - try: - wfile.write(''.join(buf).encode('ISO-8859-1')) - except socket.error as ex: - if ex.args[0] not in errors.socket_errors_to_ignore: - raise - return - if not s: - return - mf = self.server.ssl_adapter.makefile - # Re-apply our timeout since we may have a new socket object - if hasattr(s, 'settimeout'): - s.settimeout(self.server.timeout) - - conn = self.server.ConnectionClass(self.server, s, mf) - - if not isinstance(self.server.bind_addr, (str, bytes)): - # optional values - # Until we do DNS lookups, omit REMOTE_HOST - if addr is None: # sometimes this can happen - # figure out if AF_INET or AF_INET6. - if len(s.getsockname()) == 2: - # AF_INET - addr = ('0.0.0.0', 0) - else: - # AF_INET6 - addr = ('::', 0) - conn.remote_addr = addr[0] - conn.remote_port = addr[1] - - conn.ssl_env = ssl_env - return conn - - except socket.timeout: - # The only reason for the timeout in start() is so we can - # notice keyboard interrupts on Win32, which don't interrupt - # accept() by default - return - except socket.error as ex: - if self.server.stats['Enabled']: - self.server.stats['Socket Errors'] += 1 - if ex.args[0] in errors.socket_error_eintr: - # I *think* this is right. EINTR should occur when a signal - # is received during the accept() call; all docs say retry - # the call, and I *think* I'm reading it right that Python - # will then go ahead and poll for and handle the signal - # elsewhere. See - # https://github.com/cherrypy/cherrypy/issues/707. - return - if ex.args[0] in errors.socket_errors_nonblocking: - # Just try again. See - # https://github.com/cherrypy/cherrypy/issues/479. - return - if ex.args[0] in errors.socket_errors_to_ignore: - # Our socket was closed. - # See https://github.com/cherrypy/cherrypy/issues/686. - return - raise - - def close(self): - """Close all monitored connections.""" - for (_, conn) in self._selector.connections: - if conn is not self.server: # server closes its own socket - conn.close() - self._selector.close() - - @property - def _num_connections(self): - """Return the current number of connections. - - Includes all connections registered with the selector, - minus one for the server socket, which is always registered - with the selector. - """ - return len(self._selector) - 1 - - @property - def can_add_keepalive_connection(self): - """Flag whether it is allowed to add a new keep-alive connection.""" - ka_limit = self.server.keep_alive_conn_limit - return ka_limit is None or self._num_connections < ka_limit diff --git a/lib/cheroot/connections.pyi b/lib/cheroot/connections.pyi deleted file mode 100644 index 528ad765..00000000 --- a/lib/cheroot/connections.pyi +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Any - -def prevent_socket_inheritance(sock) -> None: ... - -class _ThreadsafeSelector: - def __init__(self) -> None: ... - def __len__(self): ... - @property - def connections(self) -> None: ... - def register(self, fileobj, events, data: Any | None = ...): ... - def unregister(self, fileobj): ... - def select(self, timeout: Any | None = ...): ... - def close(self) -> None: ... - -class ConnectionManager: - server: Any - def __init__(self, server) -> None: ... - def put(self, conn) -> None: ... - def stop(self) -> None: ... - def run(self, expiration_interval) -> None: ... - def close(self) -> None: ... - @property - def can_add_keepalive_connection(self): ... diff --git a/lib/cheroot/errors.py b/lib/cheroot/errors.py deleted file mode 100644 index 046263ad..00000000 --- a/lib/cheroot/errors.py +++ /dev/null @@ -1,85 +0,0 @@ -# -*- coding: utf-8 -*- -"""Collection of exceptions raised and/or processed by Cheroot.""" - -import errno -import sys - - -class MaxSizeExceeded(Exception): - """Exception raised when a client sends more data then allowed under limit. - - Depends on ``request.body.maxbytes`` config option if used within CherryPy. - """ - - -class NoSSLError(Exception): - """Exception raised when a client speaks HTTP to an HTTPS socket.""" - - -class FatalSSLAlert(Exception): - """Exception raised when the SSL implementation signals a fatal alert.""" - - -def plat_specific_errors(*errnames): - """Return error numbers for all errors in ``errnames`` on this platform. - - The :py:mod:`errno` module contains different global constants - depending on the specific platform (OS). This function will return - the list of numeric values for a given list of potential names. - """ - missing_attr = {None} - unique_nums = {getattr(errno, k, None) for k in errnames} - return list(unique_nums - missing_attr) - - -socket_error_eintr = plat_specific_errors('EINTR', 'WSAEINTR') - -socket_errors_to_ignore = plat_specific_errors( - 'EPIPE', - 'EBADF', 'WSAEBADF', - 'ENOTSOCK', 'WSAENOTSOCK', - 'ETIMEDOUT', 'WSAETIMEDOUT', - 'ECONNREFUSED', 'WSAECONNREFUSED', - 'ECONNRESET', 'WSAECONNRESET', - 'ECONNABORTED', 'WSAECONNABORTED', - 'ENETRESET', 'WSAENETRESET', - 'EHOSTDOWN', 'EHOSTUNREACH', -) -socket_errors_to_ignore.append('timed out') -socket_errors_to_ignore.append('The read operation timed out') -socket_errors_nonblocking = plat_specific_errors( - 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK', -) - -if sys.platform == 'darwin': - socket_errors_to_ignore.extend(plat_specific_errors('EPROTOTYPE')) - socket_errors_nonblocking.extend(plat_specific_errors('EPROTOTYPE')) - - -acceptable_sock_shutdown_error_codes = { - errno.ENOTCONN, - errno.EPIPE, errno.ESHUTDOWN, # corresponds to BrokenPipeError in Python 3 - errno.ECONNRESET, # corresponds to ConnectionResetError in Python 3 -} -"""Errors that may happen during the connection close sequence. - -* ENOTCONN — client is no longer connected -* EPIPE — write on a pipe while the other end has been closed -* ESHUTDOWN — write on a socket which has been shutdown for writing -* ECONNRESET — connection is reset by the peer, we received a TCP RST packet - -Refs: -* https://github.com/cherrypy/cheroot/issues/341#issuecomment-735884889 -* https://bugs.python.org/issue30319 -* https://bugs.python.org/issue30329 -* https://github.com/python/cpython/commit/83a2c28 -* https://github.com/python/cpython/blob/c39b52f/Lib/poplib.py#L297-L302 -* https://docs.microsoft.com/windows/win32/api/winsock/nf-winsock-shutdown -""" - -try: # py3 - acceptable_sock_shutdown_exceptions = ( - BrokenPipeError, ConnectionResetError, - ) -except NameError: # py2 - acceptable_sock_shutdown_exceptions = () diff --git a/lib/cheroot/errors.pyi b/lib/cheroot/errors.pyi deleted file mode 100644 index 18669568..00000000 --- a/lib/cheroot/errors.pyi +++ /dev/null @@ -1,13 +0,0 @@ -from typing import List, Set, Tuple, Type - -class MaxSizeExceeded(Exception): ... -class NoSSLError(Exception): ... -class FatalSSLAlert(Exception): ... - -def plat_specific_errors(*errnames: str) -> List[int]: ... - -socket_error_eintr: List[int] -socket_errors_to_ignore: List[int] -socket_errors_nonblocking: List[int] -acceptable_sock_shutdown_error_codes: Set[int] -acceptable_sock_shutdown_exceptions: Tuple[Type[Exception], ...] diff --git a/lib/cheroot/makefile.py b/lib/cheroot/makefile.py deleted file mode 100644 index 77878c13..00000000 --- a/lib/cheroot/makefile.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Socket file object.""" - -import socket - -# prefer slower Python-based io module -import _pyio as io - - -# Write only 16K at a time to sockets -SOCK_WRITE_BLOCKSIZE = 16384 - - -class BufferedWriter(io.BufferedWriter): - """Faux file object attached to a socket object.""" - - def write(self, b): - """Write bytes to buffer.""" - self._checkClosed() - if isinstance(b, str): - raise TypeError("can't write str to binary stream") - - with self._write_lock: - self._write_buf.extend(b) - self._flush_unlocked() - return len(b) - - def _flush_unlocked(self): - self._checkClosed('flush of closed file') - while self._write_buf: - try: - # ssl sockets only except 'bytes', not bytearrays - # so perhaps we should conditionally wrap this for perf? - n = self.raw.write(bytes(self._write_buf)) - except io.BlockingIOError as e: - n = e.characters_written - del self._write_buf[:n] - - -class StreamReader(io.BufferedReader): - """Socket stream reader.""" - - def __init__(self, sock, mode='r', bufsize=io.DEFAULT_BUFFER_SIZE): - """Initialize socket stream reader.""" - super().__init__(socket.SocketIO(sock, mode), bufsize) - self.bytes_read = 0 - - def read(self, *args, **kwargs): - """Capture bytes read.""" - val = super().read(*args, **kwargs) - self.bytes_read += len(val) - return val - - def has_data(self): - """Return true if there is buffered data to read.""" - return len(self._read_buf) > self._read_pos - - -class StreamWriter(BufferedWriter): - """Socket stream writer.""" - - def __init__(self, sock, mode='w', bufsize=io.DEFAULT_BUFFER_SIZE): - """Initialize socket stream writer.""" - super().__init__(socket.SocketIO(sock, mode), bufsize) - self.bytes_written = 0 - - def write(self, val, *args, **kwargs): - """Capture bytes written.""" - res = super().write(val, *args, **kwargs) - self.bytes_written += len(val) - return res - - -def MakeFile(sock, mode='r', bufsize=io.DEFAULT_BUFFER_SIZE): - """File object attached to a socket object.""" - cls = StreamReader if 'r' in mode else StreamWriter - return cls(sock, mode, bufsize) diff --git a/lib/cheroot/makefile.pyi b/lib/cheroot/makefile.pyi deleted file mode 100644 index 3f5ea275..00000000 --- a/lib/cheroot/makefile.pyi +++ /dev/null @@ -1,19 +0,0 @@ -import io - -SOCK_WRITE_BLOCKSIZE: int - -class BufferedWriter(io.BufferedWriter): - def write(self, b): ... - -class StreamReader(io.BufferedReader): - bytes_read: int - def __init__(self, sock, mode: str = ..., bufsize=...) -> None: ... - def read(self, *args, **kwargs): ... - def has_data(self): ... - -class StreamWriter(BufferedWriter): - bytes_written: int - def __init__(self, sock, mode: str = ..., bufsize=...) -> None: ... - def write(self, val, *args, **kwargs): ... - -def MakeFile(sock, mode: str = ..., bufsize=...): ... diff --git a/lib/cheroot/py.typed b/lib/cheroot/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/cheroot/server.py b/lib/cheroot/server.py deleted file mode 100644 index 6b8e37a9..00000000 --- a/lib/cheroot/server.py +++ /dev/null @@ -1,2182 +0,0 @@ -""" -A high-speed, production ready, thread pooled, generic HTTP server. - -For those of you wanting to understand internals of this module, here's the -basic call flow. The server's listening thread runs a very tight loop, -sticking incoming connections onto a Queue:: - - server = HTTPServer(...) - server.start() - -> serve() - while ready: - _connections.run() - while not stop_requested: - child = socket.accept() # blocks until a request comes in - conn = HTTPConnection(child, ...) - server.process_conn(conn) # adds conn to threadpool - -Worker threads are kept in a pool and poll the Queue, popping off and then -handling each connection in turn. Each connection can consist of an arbitrary -number of requests and their responses, so we run a nested loop:: - - while True: - conn = server.requests.get() - conn.communicate() - -> while True: - req = HTTPRequest(...) - req.parse_request() - -> # Read the Request-Line, e.g. "GET /page HTTP/1.1" - req.rfile.readline() - read_headers(req.rfile, req.inheaders) - req.respond() - -> response = app(...) - try: - for chunk in response: - if chunk: - req.write(chunk) - finally: - if hasattr(response, "close"): - response.close() - if req.close_connection: - return - -For running a server you can invoke :func:`start() ` (it -will run the server forever) or use invoking :func:`prepare() -` and :func:`serve() ` like this:: - - server = HTTPServer(...) - server.prepare() - try: - threading.Thread(target=server.serve).start() - - # waiting/detecting some appropriate stop condition here - ... - - finally: - server.stop() - -And now for a trivial doctest to exercise the test suite - -.. testsetup:: - - from cheroot.server import HTTPServer - ->>> 'HTTPServer' in globals() -True -""" - -import os -import io -import re -import email.utils -import socket -import sys -import time -import traceback as traceback_ -import logging -import platform -import queue -import contextlib -import threading -import urllib.parse -from functools import lru_cache - -from . import connections, errors, __version__ -from ._compat import bton -from ._compat import IS_PPC -from .workers import threadpool -from .makefile import MakeFile, StreamWriter - - -__all__ = ( - 'HTTPRequest', 'HTTPConnection', 'HTTPServer', - 'HeaderReader', 'DropUnderscoreHeaderReader', - 'SizeCheckWrapper', 'KnownLengthRFile', 'ChunkedRFile', - 'Gateway', 'get_ssl_adapter_class', -) - - -IS_WINDOWS = platform.system() == 'Windows' -"""Flag indicating whether the app is running under Windows.""" - - -IS_GAE = os.getenv('SERVER_SOFTWARE', '').startswith('Google App Engine/') -"""Flag indicating whether the app is running in GAE env. - -Ref: -https://cloud.google.com/appengine/docs/standard/python/tools -/using-local-server#detecting_application_runtime_environment -""" - - -IS_UID_GID_RESOLVABLE = not IS_WINDOWS and not IS_GAE -"""Indicates whether UID/GID resolution's available under current platform.""" - - -if IS_UID_GID_RESOLVABLE: - try: - import grp - import pwd - except ImportError: - """Unavailable in the current env. - - This shouldn't be happening normally. - All of the known cases are excluded via the if clause. - """ - IS_UID_GID_RESOLVABLE = False - grp, pwd = None, None - import struct - - -if IS_WINDOWS and hasattr(socket, 'AF_INET6'): - if not hasattr(socket, 'IPPROTO_IPV6'): - socket.IPPROTO_IPV6 = 41 - if not hasattr(socket, 'IPV6_V6ONLY'): - socket.IPV6_V6ONLY = 27 - - -if not hasattr(socket, 'SO_PEERCRED'): - """ - NOTE: the value for SO_PEERCRED can be architecture specific, in - which case the getsockopt() will hopefully fail. The arch - specific value could be derived from platform.processor() - """ - socket.SO_PEERCRED = 21 if IS_PPC else 17 - - -LF = b'\n' -CRLF = b'\r\n' -TAB = b'\t' -SPACE = b' ' -COLON = b':' -SEMICOLON = b';' -EMPTY = b'' -ASTERISK = b'*' -FORWARD_SLASH = b'/' -QUOTED_SLASH = b'%2F' -QUOTED_SLASH_REGEX = re.compile(b''.join((b'(?i)', QUOTED_SLASH))) - - -_STOPPING_FOR_INTERRUPT = object() # sentinel used during shutdown - - -comma_separated_headers = [ - b'Accept', b'Accept-Charset', b'Accept-Encoding', - b'Accept-Language', b'Accept-Ranges', b'Allow', b'Cache-Control', - b'Connection', b'Content-Encoding', b'Content-Language', b'Expect', - b'If-Match', b'If-None-Match', b'Pragma', b'Proxy-Authenticate', b'TE', - b'Trailer', b'Transfer-Encoding', b'Upgrade', b'Vary', b'Via', b'Warning', - b'WWW-Authenticate', -] - - -if not hasattr(logging, 'statistics'): - logging.statistics = {} - - -class HeaderReader: - """Object for reading headers from an HTTP request. - - Interface and default implementation. - """ - - def __call__(self, rfile, hdict=None): # noqa: C901 # FIXME - """ - Read headers from the given stream into the given header dict. - - If hdict is None, a new header dict is created. Returns the populated - header dict. - - Headers which are repeated are folded together using a comma if their - specification so dictates. - - This function raises ValueError when the read bytes violate the HTTP - spec. - You should probably return "400 Bad Request" if this happens. - """ - if hdict is None: - hdict = {} - - while True: - line = rfile.readline() - if not line: - # No more data--illegal end of headers - raise ValueError('Illegal end of headers.') - - if line == CRLF: - # Normal end of headers - break - if not line.endswith(CRLF): - raise ValueError('HTTP requires CRLF terminators') - - if line[0] in (SPACE, TAB): - # It's a continuation line. - v = line.strip() - else: - try: - k, v = line.split(COLON, 1) - except ValueError: - raise ValueError('Illegal header line.') - v = v.strip() - k = self._transform_key(k) - hname = k - - if not self._allow_header(k): - continue - - if k in comma_separated_headers: - existing = hdict.get(hname) - if existing: - v = b', '.join((existing, v)) - hdict[hname] = v - - return hdict - - def _allow_header(self, key_name): - return True - - def _transform_key(self, key_name): - # TODO: what about TE and WWW-Authenticate? - return key_name.strip().title() - - -class DropUnderscoreHeaderReader(HeaderReader): - """Custom HeaderReader to exclude any headers with underscores in them.""" - - def _allow_header(self, key_name): - orig = super(DropUnderscoreHeaderReader, self)._allow_header(key_name) - return orig and '_' not in key_name - - -class SizeCheckWrapper: - """Wraps a file-like object, raising MaxSizeExceeded if too large. - - :param rfile: ``file`` of a limited size - :param int maxlen: maximum length of the file being read - """ - - def __init__(self, rfile, maxlen): - """Initialize SizeCheckWrapper instance.""" - self.rfile = rfile - self.maxlen = maxlen - self.bytes_read = 0 - - def _check_length(self): - if self.maxlen and self.bytes_read > self.maxlen: - raise errors.MaxSizeExceeded() - - def read(self, size=None): - """Read a chunk from ``rfile`` buffer and return it. - - :param size: amount of data to read - :type size: int - - :returns: chunk from ``rfile``, limited by size if specified - :rtype: bytes - """ - data = self.rfile.read(size) - self.bytes_read += len(data) - self._check_length() - return data - - def readline(self, size=None): - """Read a single line from ``rfile`` buffer and return it. - - :param size: minimum amount of data to read - :type size: int - - :returns: one line from ``rfile`` - :rtype: bytes - """ - if size is not None: - data = self.rfile.readline(size) - self.bytes_read += len(data) - self._check_length() - return data - - # User didn't specify a size ... - # We read the line in chunks to make sure it's not a 100MB line ! - res = [] - while True: - data = self.rfile.readline(256) - self.bytes_read += len(data) - self._check_length() - res.append(data) - # See https://github.com/cherrypy/cherrypy/issues/421 - if len(data) < 256 or data[-1:] == LF: - return EMPTY.join(res) - - def readlines(self, sizehint=0): - """Read all lines from ``rfile`` buffer and return them. - - :param sizehint: hint of minimum amount of data to read - :type sizehint: int - - :returns: lines of bytes read from ``rfile`` - :rtype: list[bytes] - """ - # Shamelessly stolen from StringIO - total = 0 - lines = [] - line = self.readline(sizehint) - while line: - lines.append(line) - total += len(line) - if 0 < sizehint <= total: - break - line = self.readline(sizehint) - return lines - - def close(self): - """Release resources allocated for ``rfile``.""" - self.rfile.close() - - def __iter__(self): - """Return file iterator.""" - return self - - def __next__(self): - """Generate next file chunk.""" - data = next(self.rfile) - self.bytes_read += len(data) - self._check_length() - return data - - next = __next__ - - -class KnownLengthRFile: - """Wraps a file-like object, returning an empty string when exhausted. - - :param rfile: ``file`` of a known size - :param int content_length: length of the file being read - """ - - def __init__(self, rfile, content_length): - """Initialize KnownLengthRFile instance.""" - self.rfile = rfile - self.remaining = content_length - - def read(self, size=None): - """Read a chunk from ``rfile`` buffer and return it. - - :param size: amount of data to read - :type size: int - - :rtype: bytes - :returns: chunk from ``rfile``, limited by size if specified - """ - if self.remaining == 0: - return b'' - if size is None: - size = self.remaining - else: - size = min(size, self.remaining) - - data = self.rfile.read(size) - self.remaining -= len(data) - return data - - def readline(self, size=None): - """Read a single line from ``rfile`` buffer and return it. - - :param size: minimum amount of data to read - :type size: int - - :returns: one line from ``rfile`` - :rtype: bytes - """ - if self.remaining == 0: - return b'' - if size is None: - size = self.remaining - else: - size = min(size, self.remaining) - - data = self.rfile.readline(size) - self.remaining -= len(data) - return data - - def readlines(self, sizehint=0): - """Read all lines from ``rfile`` buffer and return them. - - :param sizehint: hint of minimum amount of data to read - :type sizehint: int - - :returns: lines of bytes read from ``rfile`` - :rtype: list[bytes] - """ - # Shamelessly stolen from StringIO - total = 0 - lines = [] - line = self.readline(sizehint) - while line: - lines.append(line) - total += len(line) - if 0 < sizehint <= total: - break - line = self.readline(sizehint) - return lines - - def close(self): - """Release resources allocated for ``rfile``.""" - self.rfile.close() - - def __iter__(self): - """Return file iterator.""" - return self - - def __next__(self): - """Generate next file chunk.""" - data = next(self.rfile) - self.remaining -= len(data) - return data - - next = __next__ - - -class ChunkedRFile: - """Wraps a file-like object, returning an empty string when exhausted. - - This class is intended to provide a conforming wsgi.input value for - request entities that have been encoded with the 'chunked' transfer - encoding. - - :param rfile: file encoded with the 'chunked' transfer encoding - :param int maxlen: maximum length of the file being read - :param int bufsize: size of the buffer used to read the file - """ - - def __init__(self, rfile, maxlen, bufsize=8192): - """Initialize ChunkedRFile instance.""" - self.rfile = rfile - self.maxlen = maxlen - self.bytes_read = 0 - self.buffer = EMPTY - self.bufsize = bufsize - self.closed = False - - def _fetch(self): - if self.closed: - return - - line = self.rfile.readline() - self.bytes_read += len(line) - - if self.maxlen and self.bytes_read > self.maxlen: - raise errors.MaxSizeExceeded( - 'Request Entity Too Large', self.maxlen, - ) - - line = line.strip().split(SEMICOLON, 1) - - try: - chunk_size = line.pop(0) - chunk_size = int(chunk_size, 16) - except ValueError: - raise ValueError( - 'Bad chunked transfer size: {chunk_size!r}'. - format(chunk_size=chunk_size), - ) - - if chunk_size <= 0: - self.closed = True - return - -# if line: chunk_extension = line[0] - - if self.maxlen and self.bytes_read + chunk_size > self.maxlen: - raise IOError('Request Entity Too Large') - - chunk = self.rfile.read(chunk_size) - self.bytes_read += len(chunk) - self.buffer += chunk - - crlf = self.rfile.read(2) - if crlf != CRLF: - raise ValueError( - "Bad chunked transfer coding (expected '\\r\\n', " - 'got ' + repr(crlf) + ')', - ) - - def read(self, size=None): - """Read a chunk from ``rfile`` buffer and return it. - - :param size: amount of data to read - :type size: int - - :returns: chunk from ``rfile``, limited by size if specified - :rtype: bytes - """ - data = EMPTY - - if size == 0: - return data - - while True: - if size and len(data) >= size: - return data - - if not self.buffer: - self._fetch() - if not self.buffer: - # EOF - return data - - if size: - remaining = size - len(data) - data += self.buffer[:remaining] - self.buffer = self.buffer[remaining:] - else: - data += self.buffer - self.buffer = EMPTY - - def readline(self, size=None): - """Read a single line from ``rfile`` buffer and return it. - - :param size: minimum amount of data to read - :type size: int - - :returns: one line from ``rfile`` - :rtype: bytes - """ - data = EMPTY - - if size == 0: - return data - - while True: - if size and len(data) >= size: - return data - - if not self.buffer: - self._fetch() - if not self.buffer: - # EOF - return data - - newline_pos = self.buffer.find(LF) - if size: - if newline_pos == -1: - remaining = size - len(data) - data += self.buffer[:remaining] - self.buffer = self.buffer[remaining:] - else: - remaining = min(size - len(data), newline_pos) - data += self.buffer[:remaining] - self.buffer = self.buffer[remaining:] - else: - if newline_pos == -1: - data += self.buffer - self.buffer = EMPTY - else: - data += self.buffer[:newline_pos] - self.buffer = self.buffer[newline_pos:] - - def readlines(self, sizehint=0): - """Read all lines from ``rfile`` buffer and return them. - - :param sizehint: hint of minimum amount of data to read - :type sizehint: int - - :returns: lines of bytes read from ``rfile`` - :rtype: list[bytes] - """ - # Shamelessly stolen from StringIO - total = 0 - lines = [] - line = self.readline(sizehint) - while line: - lines.append(line) - total += len(line) - if 0 < sizehint <= total: - break - line = self.readline(sizehint) - return lines - - def read_trailer_lines(self): - """Read HTTP headers and yield them. - - :yields: CRLF separated lines - :ytype: bytes - - """ - if not self.closed: - raise ValueError( - 'Cannot read trailers until the request body has been read.', - ) - - while True: - line = self.rfile.readline() - if not line: - # No more data--illegal end of headers - raise ValueError('Illegal end of headers.') - - self.bytes_read += len(line) - if self.maxlen and self.bytes_read > self.maxlen: - raise IOError('Request Entity Too Large') - - if line == CRLF: - # Normal end of headers - break - if not line.endswith(CRLF): - raise ValueError('HTTP requires CRLF terminators') - - yield line - - def close(self): - """Release resources allocated for ``rfile``.""" - self.rfile.close() - - -class HTTPRequest: - """An HTTP Request (and response). - - A single HTTP connection may consist of multiple request/response pairs. - """ - - server = None - """The HTTPServer object which is receiving this request.""" - - conn = None - """The HTTPConnection object on which this request connected.""" - - inheaders = {} - """A dict of request headers.""" - - outheaders = [] - """A list of header tuples to write in the response.""" - - ready = False - """When True, the request has been parsed and is ready to begin generating - the response. When False, signals the calling Connection that the response - should not be generated and the connection should close.""" - - close_connection = False - """Signals the calling Connection that the request should close. This does - not imply an error! The client and/or server may each request that the - connection be closed.""" - - chunked_write = False - """If True, output will be encoded with the "chunked" transfer-coding. - - This value is set automatically inside send_headers.""" - - header_reader = HeaderReader() - """ - A HeaderReader instance or compatible reader. - """ - - def __init__(self, server, conn, proxy_mode=False, strict_mode=True): - """Initialize HTTP request container instance. - - Args: - server (HTTPServer): web server object receiving this request - conn (HTTPConnection): HTTP connection object for this request - proxy_mode (bool): whether this HTTPServer should behave as a PROXY - server for certain requests - strict_mode (bool): whether we should return a 400 Bad Request when - we encounter a request that a HTTP compliant client should not be - making - """ - self.server = server - self.conn = conn - - self.ready = False - self.started_request = False - self.scheme = b'http' - if self.server.ssl_adapter is not None: - self.scheme = b'https' - # Use the lowest-common protocol in case read_request_line errors. - self.response_protocol = 'HTTP/1.0' - self.inheaders = {} - - self.status = '' - self.outheaders = [] - self.sent_headers = False - self.close_connection = self.__class__.close_connection - self.chunked_read = False - self.chunked_write = self.__class__.chunked_write - self.proxy_mode = proxy_mode - self.strict_mode = strict_mode - - def parse_request(self): - """Parse the next HTTP request start-line and message-headers.""" - self.rfile = SizeCheckWrapper( - self.conn.rfile, - self.server.max_request_header_size, - ) - try: - success = self.read_request_line() - except errors.MaxSizeExceeded: - self.simple_response( - '414 Request-URI Too Long', - 'The Request-URI sent with the request exceeds the maximum ' - 'allowed bytes.', - ) - return - else: - if not success: - return - - try: - success = self.read_request_headers() - except errors.MaxSizeExceeded: - self.simple_response( - '413 Request Entity Too Large', - 'The headers sent with the request exceed the maximum ' - 'allowed bytes.', - ) - return - else: - if not success: - return - - self.ready = True - - def read_request_line(self): # noqa: C901 # FIXME - """Read and parse first line of the HTTP request. - - Returns: - bool: True if the request line is valid or False if it's malformed. - - """ - # HTTP/1.1 connections are persistent by default. If a client - # requests a page, then idles (leaves the connection open), - # then rfile.readline() will raise socket.error("timed out"). - # Note that it does this based on the value given to settimeout(), - # and doesn't need the client to request or acknowledge the close - # (although your TCP stack might suffer for it: cf Apache's history - # with FIN_WAIT_2). - request_line = self.rfile.readline() - - # Set started_request to True so communicate() knows to send 408 - # from here on out. - self.started_request = True - if not request_line: - return False - - if request_line == CRLF: - # RFC 2616 sec 4.1: "...if the server is reading the protocol - # stream at the beginning of a message and receives a CRLF - # first, it should ignore the CRLF." - # But only ignore one leading line! else we enable a DoS. - request_line = self.rfile.readline() - if not request_line: - return False - - if not request_line.endswith(CRLF): - self.simple_response( - '400 Bad Request', 'HTTP requires CRLF terminators', - ) - return False - - try: - method, uri, req_protocol = request_line.strip().split(SPACE, 2) - if not req_protocol.startswith(b'HTTP/'): - self.simple_response( - '400 Bad Request', 'Malformed Request-Line: bad protocol', - ) - return False - rp = req_protocol[5:].split(b'.', 1) - if len(rp) != 2: - self.simple_response( - '400 Bad Request', 'Malformed Request-Line: bad version', - ) - return False - rp = tuple(map(int, rp)) # Minor.Major must be threat as integers - if rp > (1, 1): - self.simple_response( - '505 HTTP Version Not Supported', 'Cannot fulfill request', - ) - return False - except (ValueError, IndexError): - self.simple_response('400 Bad Request', 'Malformed Request-Line') - return False - - self.uri = uri - self.method = method.upper() - - if self.strict_mode and method != self.method: - resp = ( - 'Malformed method name: According to RFC 2616 ' - '(section 5.1.1) and its successors ' - 'RFC 7230 (section 3.1.1) and RFC 7231 (section 4.1) ' - 'method names are case-sensitive and uppercase.' - ) - self.simple_response('400 Bad Request', resp) - return False - - try: - scheme, authority, path, qs, fragment = urllib.parse.urlsplit(uri) - except UnicodeError: - self.simple_response('400 Bad Request', 'Malformed Request-URI') - return False - - uri_is_absolute_form = (scheme or authority) - - if self.method == b'OPTIONS': - # TODO: cover this branch with tests - path = ( - uri - # https://tools.ietf.org/html/rfc7230#section-5.3.4 - if (self.proxy_mode and uri_is_absolute_form) - else path - ) - elif self.method == b'CONNECT': - # TODO: cover this branch with tests - if not self.proxy_mode: - self.simple_response('405 Method Not Allowed') - return False - - # `urlsplit()` above parses "example.com:3128" as path part of URI. - # this is a workaround, which makes it detect netloc correctly - uri_split = urllib.parse.urlsplit(b''.join((b'//', uri))) - _scheme, _authority, _path, _qs, _fragment = uri_split - _port = EMPTY - try: - _port = uri_split.port - except ValueError: - pass - - # FIXME: use third-party validation to make checks against RFC - # the validation doesn't take into account, that urllib parses - # invalid URIs without raising errors - # https://tools.ietf.org/html/rfc7230#section-5.3.3 - invalid_path = ( - _authority != uri - or not _port - or any((_scheme, _path, _qs, _fragment)) - ) - if invalid_path: - self.simple_response( - '400 Bad Request', - 'Invalid path in Request-URI: request-' - 'target must match authority-form.', - ) - return False - - authority = path = _authority - scheme = qs = fragment = EMPTY - else: - disallowed_absolute = ( - self.strict_mode - and not self.proxy_mode - and uri_is_absolute_form - ) - if disallowed_absolute: - # https://tools.ietf.org/html/rfc7230#section-5.3.2 - # (absolute form) - """Absolute URI is only allowed within proxies.""" - self.simple_response( - '400 Bad Request', - 'Absolute URI not allowed if server is not a proxy.', - ) - return False - - invalid_path = ( - self.strict_mode - and not uri.startswith(FORWARD_SLASH) - and not uri_is_absolute_form - ) - if invalid_path: - # https://tools.ietf.org/html/rfc7230#section-5.3.1 - # (origin_form) and - """Path should start with a forward slash.""" - resp = ( - 'Invalid path in Request-URI: request-target must contain ' - 'origin-form which starts with absolute-path (URI ' - 'starting with a slash "/").' - ) - self.simple_response('400 Bad Request', resp) - return False - - if fragment: - self.simple_response( - '400 Bad Request', - 'Illegal #fragment in Request-URI.', - ) - return False - - if path is None: - # FIXME: It looks like this case cannot happen - self.simple_response( - '400 Bad Request', - 'Invalid path in Request-URI.', - ) - return False - - # Unquote the path+params (e.g. "/this%20path" -> "/this path"). - # https://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 - # - # But note that "...a URI must be separated into its components - # before the escaped characters within those components can be - # safely decoded." https://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 - # Therefore, "/this%2Fpath" becomes "/this%2Fpath", not - # "/this/path". - try: - # TODO: Figure out whether exception can really happen here. - # It looks like it's caught on urlsplit() call above. - atoms = [ - urllib.parse.unquote_to_bytes(x) - for x in QUOTED_SLASH_REGEX.split(path) - ] - except ValueError as ex: - self.simple_response('400 Bad Request', ex.args[0]) - return False - path = QUOTED_SLASH.join(atoms) - - if not path.startswith(FORWARD_SLASH): - path = FORWARD_SLASH + path - - if scheme is not EMPTY: - self.scheme = scheme - self.authority = authority - self.path = path - - # Note that, like wsgiref and most other HTTP servers, - # we "% HEX HEX"-unquote the path but not the query string. - self.qs = qs - - # Compare request and server HTTP protocol versions, in case our - # server does not support the requested protocol. Limit our output - # to min(req, server). We want the following output: - # request server actual written supported response - # protocol protocol response protocol feature set - # a 1.0 1.0 1.0 1.0 - # b 1.0 1.1 1.1 1.0 - # c 1.1 1.0 1.0 1.0 - # d 1.1 1.1 1.1 1.1 - # Notice that, in (b), the response will be "HTTP/1.1" even though - # the client only understands 1.0. RFC 2616 10.5.6 says we should - # only return 505 if the _major_ version is different. - sp = int(self.server.protocol[5]), int(self.server.protocol[7]) - - if sp[0] != rp[0]: - self.simple_response('505 HTTP Version Not Supported') - return False - - self.request_protocol = req_protocol - self.response_protocol = 'HTTP/%s.%s' % min(rp, sp) - - return True - - def read_request_headers(self): # noqa: C901 # FIXME - """Read ``self.rfile`` into ``self.inheaders``. - - Ref: :py:attr:`self.inheaders `. - - :returns: success status - :rtype: bool - """ - # then all the http headers - try: - self.header_reader(self.rfile, self.inheaders) - except ValueError as ex: - self.simple_response('400 Bad Request', ex.args[0]) - return False - - mrbs = self.server.max_request_body_size - - try: - cl = int(self.inheaders.get(b'Content-Length', 0)) - except ValueError: - self.simple_response( - '400 Bad Request', - 'Malformed Content-Length Header.', - ) - return False - - if mrbs and cl > mrbs: - self.simple_response( - '413 Request Entity Too Large', - 'The entity sent with the request exceeds the maximum ' - 'allowed bytes.', - ) - return False - - # Persistent connection support - if self.response_protocol == 'HTTP/1.1': - # Both server and client are HTTP/1.1 - if self.inheaders.get(b'Connection', b'') == b'close': - self.close_connection = True - else: - # Either the server or client (or both) are HTTP/1.0 - if self.inheaders.get(b'Connection', b'') != b'Keep-Alive': - self.close_connection = True - - # Transfer-Encoding support - te = None - if self.response_protocol == 'HTTP/1.1': - te = self.inheaders.get(b'Transfer-Encoding') - if te: - te = [x.strip().lower() for x in te.split(b',') if x.strip()] - - self.chunked_read = False - - if te: - for enc in te: - if enc == b'chunked': - self.chunked_read = True - else: - # Note that, even if we see "chunked", we must reject - # if there is an extension we don't recognize. - self.simple_response('501 Unimplemented') - self.close_connection = True - return False - - # From PEP 333: - # "Servers and gateways that implement HTTP 1.1 must provide - # transparent support for HTTP 1.1's "expect/continue" mechanism. - # This may be done in any of several ways: - # 1. Respond to requests containing an Expect: 100-continue request - # with an immediate "100 Continue" response, and proceed normally. - # 2. Proceed with the request normally, but provide the application - # with a wsgi.input stream that will send the "100 Continue" - # response if/when the application first attempts to read from - # the input stream. The read request must then remain blocked - # until the client responds. - # 3. Wait until the client decides that the server does not support - # expect/continue, and sends the request body on its own. - # (This is suboptimal, and is not recommended.) - # - # We used to do 3, but are now doing 1. Maybe we'll do 2 someday, - # but it seems like it would be a big slowdown for such a rare case. - if self.inheaders.get(b'Expect', b'') == b'100-continue': - # Don't use simple_response here, because it emits headers - # we don't want. See - # https://github.com/cherrypy/cherrypy/issues/951 - msg = b''.join(( - self.server.protocol.encode('ascii'), SPACE, b'100 Continue', - CRLF, CRLF, - )) - try: - self.conn.wfile.write(msg) - except socket.error as ex: - if ex.args[0] not in errors.socket_errors_to_ignore: - raise - return True - - def respond(self): - """Call the gateway and write its iterable output.""" - mrbs = self.server.max_request_body_size - if self.chunked_read: - self.rfile = ChunkedRFile(self.conn.rfile, mrbs) - else: - cl = int(self.inheaders.get(b'Content-Length', 0)) - if mrbs and mrbs < cl: - if not self.sent_headers: - self.simple_response( - '413 Request Entity Too Large', - 'The entity sent with the request exceeds the ' - 'maximum allowed bytes.', - ) - return - self.rfile = KnownLengthRFile(self.conn.rfile, cl) - - self.server.gateway(self).respond() - self.ready and self.ensure_headers_sent() - - if self.chunked_write: - self.conn.wfile.write(b'0\r\n\r\n') - - def simple_response(self, status, msg=''): - """Write a simple response back to the client.""" - status = str(status) - proto_status = '%s %s\r\n' % (self.server.protocol, status) - content_length = 'Content-Length: %s\r\n' % len(msg) - content_type = 'Content-Type: text/plain\r\n' - buf = [ - proto_status.encode('ISO-8859-1'), - content_length.encode('ISO-8859-1'), - content_type.encode('ISO-8859-1'), - ] - - if status[:3] in ('413', '414'): - # Request Entity Too Large / Request-URI Too Long - self.close_connection = True - if self.response_protocol == 'HTTP/1.1': - # This will not be true for 414, since read_request_line - # usually raises 414 before reading the whole line, and we - # therefore cannot know the proper response_protocol. - buf.append(b'Connection: close\r\n') - else: - # HTTP/1.0 had no 413/414 status nor Connection header. - # Emit 400 instead and trust the message body is enough. - status = '400 Bad Request' - - buf.append(CRLF) - if msg: - if isinstance(msg, str): - msg = msg.encode('ISO-8859-1') - buf.append(msg) - - try: - self.conn.wfile.write(EMPTY.join(buf)) - except socket.error as ex: - if ex.args[0] not in errors.socket_errors_to_ignore: - raise - - def ensure_headers_sent(self): - """Ensure headers are sent to the client if not already sent.""" - if not self.sent_headers: - self.sent_headers = True - self.send_headers() - - def write(self, chunk): - """Write unbuffered data to the client.""" - if self.chunked_write and chunk: - chunk_size_hex = hex(len(chunk))[2:].encode('ascii') - buf = [chunk_size_hex, CRLF, chunk, CRLF] - self.conn.wfile.write(EMPTY.join(buf)) - else: - self.conn.wfile.write(chunk) - - def send_headers(self): # noqa: C901 # FIXME - """Assert, process, and send the HTTP response message-headers. - - You must set ``self.status``, and :py:attr:`self.outheaders - ` before calling this. - """ - hkeys = [key.lower() for key, value in self.outheaders] - status = int(self.status[:3]) - - if status == 413: - # Request Entity Too Large. Close conn to avoid garbage. - self.close_connection = True - elif b'content-length' not in hkeys: - # "All 1xx (informational), 204 (no content), - # and 304 (not modified) responses MUST NOT - # include a message-body." So no point chunking. - if status < 200 or status in (204, 205, 304): - pass - else: - needs_chunked = ( - self.response_protocol == 'HTTP/1.1' - and self.method != b'HEAD' - ) - if needs_chunked: - # Use the chunked transfer-coding - self.chunked_write = True - self.outheaders.append((b'Transfer-Encoding', b'chunked')) - else: - # Closing the conn is the only way to determine len. - self.close_connection = True - - # Override the decision to not close the connection if the connection - # manager doesn't have space for it. - if not self.close_connection: - can_keep = self.server.can_add_keepalive_connection - self.close_connection = not can_keep - - if b'connection' not in hkeys: - if self.response_protocol == 'HTTP/1.1': - # Both server and client are HTTP/1.1 or better - if self.close_connection: - self.outheaders.append((b'Connection', b'close')) - else: - # Server and/or client are HTTP/1.0 - if not self.close_connection: - self.outheaders.append((b'Connection', b'Keep-Alive')) - - if (b'Connection', b'Keep-Alive') in self.outheaders: - self.outheaders.append(( - b'Keep-Alive', - u'timeout={connection_timeout}'. - format(connection_timeout=self.server.timeout). - encode('ISO-8859-1'), - )) - - if (not self.close_connection) and (not self.chunked_read): - # Read any remaining request body data on the socket. - # "If an origin server receives a request that does not include an - # Expect request-header field with the "100-continue" expectation, - # the request includes a request body, and the server responds - # with a final status code before reading the entire request body - # from the transport connection, then the server SHOULD NOT close - # the transport connection until it has read the entire request, - # or until the client closes the connection. Otherwise, the client - # might not reliably receive the response message. However, this - # requirement is not be construed as preventing a server from - # defending itself against denial-of-service attacks, or from - # badly broken client implementations." - remaining = getattr(self.rfile, 'remaining', 0) - if remaining > 0: - self.rfile.read(remaining) - - if b'date' not in hkeys: - self.outheaders.append(( - b'Date', - email.utils.formatdate(usegmt=True).encode('ISO-8859-1'), - )) - - if b'server' not in hkeys: - self.outheaders.append(( - b'Server', - self.server.server_name.encode('ISO-8859-1'), - )) - - proto = self.server.protocol.encode('ascii') - buf = [proto + SPACE + self.status + CRLF] - for k, v in self.outheaders: - buf.append(k + COLON + SPACE + v + CRLF) - buf.append(CRLF) - self.conn.wfile.write(EMPTY.join(buf)) - - -class HTTPConnection: - """An HTTP connection (active socket).""" - - remote_addr = None - remote_port = None - ssl_env = None - rbufsize = io.DEFAULT_BUFFER_SIZE - wbufsize = io.DEFAULT_BUFFER_SIZE - RequestHandlerClass = HTTPRequest - peercreds_enabled = False - peercreds_resolve_enabled = False - - # Fields set by ConnectionManager. - last_used = None - - def __init__(self, server, sock, makefile=MakeFile): - """Initialize HTTPConnection instance. - - Args: - server (HTTPServer): web server object receiving this request - sock (socket._socketobject): the raw socket object (usually - TCP) for this connection - makefile (file): a fileobject class for reading from the socket - """ - self.server = server - self.socket = sock - self.rfile = makefile(sock, 'rb', self.rbufsize) - self.wfile = makefile(sock, 'wb', self.wbufsize) - self.requests_seen = 0 - - self.peercreds_enabled = self.server.peercreds_enabled - self.peercreds_resolve_enabled = self.server.peercreds_resolve_enabled - - # LRU cached methods: - # Ref: https://stackoverflow.com/a/14946506/595220 - self.resolve_peer_creds = ( - lru_cache(maxsize=1)(self.resolve_peer_creds) - ) - self.get_peer_creds = ( - lru_cache(maxsize=1)(self.get_peer_creds) - ) - - def communicate(self): # noqa: C901 # FIXME - """Read each request and respond appropriately. - - Returns true if the connection should be kept open. - """ - request_seen = False - try: - req = self.RequestHandlerClass(self.server, self) - req.parse_request() - if self.server.stats['Enabled']: - self.requests_seen += 1 - if not req.ready: - # Something went wrong in the parsing (and the server has - # probably already made a simple_response). Return and - # let the conn close. - return False - - request_seen = True - req.respond() - if not req.close_connection: - return True - except socket.error as ex: - errnum = ex.args[0] - # sadly SSL sockets return a different (longer) time out string - timeout_errs = 'timed out', 'The read operation timed out' - if errnum in timeout_errs: - # Don't error if we're between requests; only error - # if 1) no request has been started at all, or 2) we're - # in the middle of a request. - # See https://github.com/cherrypy/cherrypy/issues/853 - if (not request_seen) or (req and req.started_request): - self._conditional_error(req, '408 Request Timeout') - elif errnum not in errors.socket_errors_to_ignore: - self.server.error_log( - 'socket.error %s' % repr(errnum), - level=logging.WARNING, traceback=True, - ) - self._conditional_error(req, '500 Internal Server Error') - except (KeyboardInterrupt, SystemExit): - raise - except errors.FatalSSLAlert: - pass - except errors.NoSSLError: - self._handle_no_ssl(req) - except Exception as ex: - self.server.error_log( - repr(ex), level=logging.ERROR, traceback=True, - ) - self._conditional_error(req, '500 Internal Server Error') - return False - - linger = False - - def _handle_no_ssl(self, req): - if not req or req.sent_headers: - return - # Unwrap wfile - try: - resp_sock = self.socket._sock - except AttributeError: - # self.socket is of OpenSSL.SSL.Connection type - resp_sock = self.socket._socket - self.wfile = StreamWriter(resp_sock, 'wb', self.wbufsize) - msg = ( - 'The client sent a plain HTTP request, but ' - 'this server only speaks HTTPS on this port.' - ) - req.simple_response('400 Bad Request', msg) - self.linger = True - - def _conditional_error(self, req, response): - """Respond with an error. - - Don't bother writing if a response - has already started being written. - """ - if not req or req.sent_headers: - return - - try: - req.simple_response(response) - except errors.FatalSSLAlert: - pass - except errors.NoSSLError: - self._handle_no_ssl(req) - - def close(self): - """Close the socket underlying this connection.""" - self.rfile.close() - - if not self.linger: - self._close_kernel_socket() - # close the socket file descriptor - # (will be closed in the OS if there is no - # other reference to the underlying socket) - self.socket.close() - else: - # On the other hand, sometimes we want to hang around for a bit - # to make sure the client has a chance to read our entire - # response. Skipping the close() calls here delays the FIN - # packet until the socket object is garbage-collected later. - # Someday, perhaps, we'll do the full lingering_close that - # Apache does, but not today. - pass - - def get_peer_creds(self): # LRU cached on per-instance basis, see __init__ - """Return the PID/UID/GID tuple of the peer socket for UNIX sockets. - - This function uses SO_PEERCRED to query the UNIX PID, UID, GID - of the peer, which is only available if the bind address is - a UNIX domain socket. - - Raises: - NotImplementedError: in case of unsupported socket type - RuntimeError: in case of SO_PEERCRED lookup unsupported or disabled - - """ - PEERCRED_STRUCT_DEF = '3i' - - if IS_WINDOWS or self.socket.family != socket.AF_UNIX: - raise NotImplementedError( - 'SO_PEERCRED is only supported in Linux kernel and WSL', - ) - elif not self.peercreds_enabled: - raise RuntimeError( - 'Peer creds lookup is disabled within this server', - ) - - try: - peer_creds = self.socket.getsockopt( - # FIXME: Use LOCAL_CREDS for BSD-like OSs - # Ref: https://gist.github.com/LucaFilipozzi/e4f1e118202aff27af6aadebda1b5d91 # noqa - socket.SOL_SOCKET, socket.SO_PEERCRED, - struct.calcsize(PEERCRED_STRUCT_DEF), - ) - except socket.error as socket_err: - """Non-Linux kernels don't support SO_PEERCRED. - - Refs: - http://welz.org.za/notes/on-peer-cred.html - https://github.com/daveti/tcpSockHack - msdn.microsoft.com/en-us/commandline/wsl/release_notes#build-15025 - """ - raise RuntimeError from socket_err - else: - pid, uid, gid = struct.unpack(PEERCRED_STRUCT_DEF, peer_creds) - return pid, uid, gid - - @property - def peer_pid(self): - """Return the id of the connected peer process.""" - pid, _, _ = self.get_peer_creds() - return pid - - @property - def peer_uid(self): - """Return the user id of the connected peer process.""" - _, uid, _ = self.get_peer_creds() - return uid - - @property - def peer_gid(self): - """Return the group id of the connected peer process.""" - _, _, gid = self.get_peer_creds() - return gid - - def resolve_peer_creds(self): # LRU cached on per-instance basis - """Look up the username and group tuple of the ``PEERCREDS``. - - :returns: the username and group tuple of the ``PEERCREDS`` - - :raises NotImplementedError: if the OS is unsupported - :raises RuntimeError: if UID/GID lookup is unsupported or disabled - """ - if not IS_UID_GID_RESOLVABLE: - raise NotImplementedError( - 'UID/GID lookup is unavailable under current platform. ' - 'It can only be done under UNIX-like OS ' - 'but not under the Google App Engine', - ) - elif not self.peercreds_resolve_enabled: - raise RuntimeError( - 'UID/GID lookup is disabled within this server', - ) - - user = pwd.getpwuid(self.peer_uid).pw_name # [0] - group = grp.getgrgid(self.peer_gid).gr_name # [0] - - return user, group - - @property - def peer_user(self): - """Return the username of the connected peer process.""" - user, _ = self.resolve_peer_creds() - return user - - @property - def peer_group(self): - """Return the group of the connected peer process.""" - _, group = self.resolve_peer_creds() - return group - - def _close_kernel_socket(self): - """Terminate the connection at the transport level.""" - # Honor ``sock_shutdown`` for PyOpenSSL connections. - shutdown = getattr( - self.socket, 'sock_shutdown', - self.socket.shutdown, - ) - - try: - shutdown(socket.SHUT_RDWR) # actually send a TCP FIN - except errors.acceptable_sock_shutdown_exceptions: - pass - except socket.error as e: - if e.errno not in errors.acceptable_sock_shutdown_error_codes: - raise - - -class HTTPServer: - """An HTTP server.""" - - _bind_addr = '127.0.0.1' - _interrupt = None - - gateway = None - """A Gateway instance.""" - - minthreads = None - """The minimum number of worker threads to create (default 10).""" - - maxthreads = None - """The maximum number of worker threads to create. - - (default -1 = no limit)""" - - server_name = None - """The name of the server; defaults to ``self.version``.""" - - protocol = 'HTTP/1.1' - """The version string to write in the Status-Line of all HTTP responses. - - For example, "HTTP/1.1" is the default. This also limits the supported - features used in the response.""" - - request_queue_size = 5 - """The 'backlog' arg to socket.listen(); max queued connections. - - (default 5).""" - - shutdown_timeout = 5 - """The total time to wait for worker threads to cleanly exit. - - Specified in seconds.""" - - timeout = 10 - """The timeout in seconds for accepted connections (default 10).""" - - expiration_interval = 0.5 - """The interval, in seconds, at which the server checks for - expired connections (default 0.5). - """ - - version = 'Cheroot/{version!s}'.format(version=__version__) - """A version string for the HTTPServer.""" - - software = None - """The value to set for the SERVER_SOFTWARE entry in the WSGI environ. - - If None, this defaults to ``'%s Server' % self.version``. - """ - - ready = False - """Internal flag which indicating the socket is accepting connections.""" - - max_request_header_size = 0 - """The maximum size, in bytes, for request headers, or 0 for no limit.""" - - max_request_body_size = 0 - """The maximum size, in bytes, for request bodies, or 0 for no limit.""" - - nodelay = True - """If True (the default since 3.1), sets the TCP_NODELAY socket option.""" - - ConnectionClass = HTTPConnection - """The class to use for handling HTTP connections.""" - - ssl_adapter = None - """An instance of ``ssl.Adapter`` (or a subclass). - - Ref: :py:class:`ssl.Adapter `. - - You must have the corresponding TLS driver library installed. - """ - - peercreds_enabled = False - """ - If :py:data:`True`, peer creds will be looked up via UNIX domain socket. - """ - - peercreds_resolve_enabled = False - """ - If :py:data:`True`, username/group will be looked up in the OS from - ``PEERCREDS``-provided IDs. - """ - - keep_alive_conn_limit = 10 - """Maximum number of waiting keep-alive connections that will be kept open. - - Default is 10. Set to None to have unlimited connections.""" - - def __init__( - self, bind_addr, gateway, - minthreads=10, maxthreads=-1, server_name=None, - peercreds_enabled=False, peercreds_resolve_enabled=False, - ): - """Initialize HTTPServer instance. - - Args: - bind_addr (tuple): network interface to listen to - gateway (Gateway): gateway for processing HTTP requests - minthreads (int): minimum number of threads for HTTP thread pool - maxthreads (int): maximum number of threads for HTTP thread pool - server_name (str): web server name to be advertised via Server - HTTP header - """ - self.bind_addr = bind_addr - self.gateway = gateway - - self.requests = threadpool.ThreadPool( - self, min=minthreads or 1, max=maxthreads, - ) - - if not server_name: - server_name = self.version - self.server_name = server_name - self.peercreds_enabled = peercreds_enabled - self.peercreds_resolve_enabled = ( - peercreds_resolve_enabled and peercreds_enabled - ) - self.clear_stats() - - def clear_stats(self): - """Reset server stat counters..""" - self._start_time = None - self._run_time = 0 - self.stats = { - 'Enabled': False, - 'Bind Address': lambda s: repr(self.bind_addr), - 'Run time': lambda s: (not s['Enabled']) and -1 or self.runtime(), - 'Accepts': 0, - 'Accepts/sec': lambda s: s['Accepts'] / self.runtime(), - 'Queue': lambda s: getattr(self.requests, 'qsize', None), - 'Threads': lambda s: len(getattr(self.requests, '_threads', [])), - 'Threads Idle': lambda s: getattr(self.requests, 'idle', None), - 'Socket Errors': 0, - 'Requests': lambda s: (not s['Enabled']) and -1 or sum( - (w['Requests'](w) for w in s['Worker Threads'].values()), 0, - ), - 'Bytes Read': lambda s: (not s['Enabled']) and -1 or sum( - (w['Bytes Read'](w) for w in s['Worker Threads'].values()), 0, - ), - 'Bytes Written': lambda s: (not s['Enabled']) and -1 or sum( - (w['Bytes Written'](w) for w in s['Worker Threads'].values()), - 0, - ), - 'Work Time': lambda s: (not s['Enabled']) and -1 or sum( - (w['Work Time'](w) for w in s['Worker Threads'].values()), 0, - ), - 'Read Throughput': lambda s: (not s['Enabled']) and -1 or sum( - ( - w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6) - for w in s['Worker Threads'].values() - ), 0, - ), - 'Write Throughput': lambda s: (not s['Enabled']) and -1 or sum( - ( - w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6) - for w in s['Worker Threads'].values() - ), 0, - ), - 'Worker Threads': {}, - } - logging.statistics['Cheroot HTTPServer %d' % id(self)] = self.stats - - def runtime(self): - """Return server uptime.""" - if self._start_time is None: - return self._run_time - else: - return self._run_time + (time.time() - self._start_time) - - def __str__(self): - """Render Server instance representing bind address.""" - return '%s.%s(%r)' % ( - self.__module__, self.__class__.__name__, - self.bind_addr, - ) - - @property - def bind_addr(self): - """Return the interface on which to listen for connections. - - For TCP sockets, a (host, port) tuple. Host values may be any - :term:`IPv4` or :term:`IPv6` address, or any valid hostname. - The string 'localhost' is a synonym for '127.0.0.1' (or '::1', - if your hosts file prefers :term:`IPv6`). - The string '0.0.0.0' is a special :term:`IPv4` entry meaning - "any active interface" (INADDR_ANY), and '::' is the similar - IN6ADDR_ANY for :term:`IPv6`. - The empty string or :py:data:`None` are not allowed. - - For UNIX sockets, supply the file name as a string. - - Systemd socket activation is automatic and doesn't require tempering - with this variable. - - .. glossary:: - - :abbr:`IPv4 (Internet Protocol version 4)` - Internet Protocol version 4 - - :abbr:`IPv6 (Internet Protocol version 6)` - Internet Protocol version 6 - """ - return self._bind_addr - - @bind_addr.setter - def bind_addr(self, value): - """Set the interface on which to listen for connections.""" - if isinstance(value, tuple) and value[0] in ('', None): - # Despite the socket module docs, using '' does not - # allow AI_PASSIVE to work. Passing None instead - # returns '0.0.0.0' like we want. In other words: - # host AI_PASSIVE result - # '' Y 192.168.x.y - # '' N 192.168.x.y - # None Y 0.0.0.0 - # None N 127.0.0.1 - # But since you can get the same effect with an explicit - # '0.0.0.0', we deny both the empty string and None as values. - raise ValueError( - "Host values of '' or None are not allowed. " - "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead " - 'to listen on all active interfaces.', - ) - self._bind_addr = value - - def safe_start(self): - """Run the server forever, and stop it cleanly on exit.""" - try: - self.start() - except (KeyboardInterrupt, IOError): - # The time.sleep call might raise - # "IOError: [Errno 4] Interrupted function call" on KBInt. - self.error_log('Keyboard Interrupt: shutting down') - self.stop() - raise - except SystemExit: - self.error_log('SystemExit raised: shutting down') - self.stop() - raise - - def prepare(self): # noqa: C901 # FIXME - """Prepare server to serving requests. - - It binds a socket's port, setups the socket to ``listen()`` and does - other preparing things. - """ - self._interrupt = None - - if self.software is None: - self.software = '%s Server' % self.version - - # Select the appropriate socket - self.socket = None - msg = 'No socket could be created' - if os.getenv('LISTEN_PID', None): - # systemd socket activation - self.socket = socket.fromfd(3, socket.AF_INET, socket.SOCK_STREAM) - elif isinstance(self.bind_addr, (str, bytes)): - # AF_UNIX socket - try: - self.bind_unix_socket(self.bind_addr) - except socket.error as serr: - msg = '%s -- (%s: %s)' % (msg, self.bind_addr, serr) - raise socket.error(msg) from serr - else: - # AF_INET or AF_INET6 socket - # Get the correct address family for our host (allows IPv6 - # addresses) - host, port = self.bind_addr - try: - info = socket.getaddrinfo( - host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM, 0, socket.AI_PASSIVE, - ) - except socket.gaierror: - sock_type = socket.AF_INET - bind_addr = self.bind_addr - - if ':' in host: - sock_type = socket.AF_INET6 - bind_addr = bind_addr + (0, 0) - - info = [(sock_type, socket.SOCK_STREAM, 0, '', bind_addr)] - - for res in info: - af, socktype, proto, _canonname, sa = res - try: - self.bind(af, socktype, proto) - break - except socket.error as serr: - msg = '%s -- (%s: %s)' % (msg, sa, serr) - if self.socket: - self.socket.close() - self.socket = None - - if not self.socket: - raise socket.error(msg) - - # Timeout so KeyboardInterrupt can be caught on Win32 - self.socket.settimeout(1) - self.socket.listen(self.request_queue_size) - - # must not be accessed once stop() has been called - self._connections = connections.ConnectionManager(self) - - # Create worker threads - self.requests.start() - - self.ready = True - self._start_time = time.time() - - def serve(self): - """Serve requests, after invoking :func:`prepare()`.""" - while self.ready and not self.interrupt: - try: - self._connections.run(self.expiration_interval) - except (KeyboardInterrupt, SystemExit): - raise - except Exception: - self.error_log( - 'Error in HTTPServer.serve', level=logging.ERROR, - traceback=True, - ) - - # raise exceptions reported by any worker threads, - # such that the exception is raised from the serve() thread. - if self.interrupt: - while self._stopping_for_interrupt: - time.sleep(0.1) - if self.interrupt: - raise self.interrupt - - def start(self): - """Run the server forever. - - It is shortcut for invoking :func:`prepare()` then :func:`serve()`. - """ - # We don't have to trap KeyboardInterrupt or SystemExit here, - # because cherrypy.server already does so, calling self.stop() for us. - # If you're using this server with another framework, you should - # trap those exceptions in whatever code block calls start(). - self.prepare() - self.serve() - - @contextlib.contextmanager - def _run_in_thread(self): - """Context manager for running this server in a thread.""" - self.prepare() - thread = threading.Thread(target=self.serve) - thread.daemon = True - thread.start() - try: - yield thread - finally: - self.stop() - - @property - def can_add_keepalive_connection(self): - """Flag whether it is allowed to add a new keep-alive connection.""" - return self.ready and self._connections.can_add_keepalive_connection - - def put_conn(self, conn): - """Put an idle connection back into the ConnectionManager.""" - if self.ready: - self._connections.put(conn) - else: - # server is shutting down, just close it - conn.close() - - def error_log(self, msg='', level=20, traceback=False): - """Write error message to log. - - Args: - msg (str): error message - level (int): logging level - traceback (bool): add traceback to output or not - """ - # Override this in subclasses as desired - sys.stderr.write('{msg!s}\n'.format(msg=msg)) - sys.stderr.flush() - if traceback: - tblines = traceback_.format_exc() - sys.stderr.write(tblines) - sys.stderr.flush() - - def bind(self, family, type, proto=0): - """Create (or recreate) the actual socket object.""" - sock = self.prepare_socket( - self.bind_addr, - family, type, proto, - self.nodelay, self.ssl_adapter, - ) - sock = self.socket = self.bind_socket(sock, self.bind_addr) - self.bind_addr = self.resolve_real_bind_addr(sock) - return sock - - def bind_unix_socket(self, bind_addr): # noqa: C901 # FIXME - """Create (or recreate) a UNIX socket object.""" - if IS_WINDOWS: - """ - Trying to access socket.AF_UNIX under Windows - causes an AttributeError. - """ - raise ValueError( # or RuntimeError? - 'AF_UNIX sockets are not supported under Windows.', - ) - - fs_permissions = 0o777 # TODO: allow changing mode - - try: - # Make possible reusing the socket... - os.unlink(self.bind_addr) - except OSError: - """ - File does not exist, which is the primary goal anyway. - """ - except TypeError as typ_err: - err_msg = str(typ_err) - if ( - 'remove() argument 1 must be encoded ' - 'string without null bytes, not unicode' - not in err_msg - and 'embedded NUL character' not in err_msg # py34 - and 'argument must be a ' - 'string without NUL characters' not in err_msg # pypy2 - ): - raise - except ValueError as val_err: - err_msg = str(val_err) - if ( - 'unlink: embedded null ' - 'character in path' not in err_msg - and 'embedded null byte' not in err_msg - and 'argument must be a ' - 'string without NUL characters' not in err_msg # pypy3 - ): - raise - - sock = self.prepare_socket( - bind_addr=bind_addr, - family=socket.AF_UNIX, type=socket.SOCK_STREAM, proto=0, - nodelay=self.nodelay, ssl_adapter=self.ssl_adapter, - ) - - try: - """Linux way of pre-populating fs mode permissions.""" - # Allow everyone access the socket... - os.fchmod(sock.fileno(), fs_permissions) - FS_PERMS_SET = True - except OSError: - FS_PERMS_SET = False - - try: - sock = self.bind_socket(sock, bind_addr) - except socket.error: - sock.close() - raise - - bind_addr = self.resolve_real_bind_addr(sock) - - try: - """FreeBSD/macOS pre-populating fs mode permissions.""" - if not FS_PERMS_SET: - try: - os.lchmod(bind_addr, fs_permissions) - except AttributeError: - os.chmod(bind_addr, fs_permissions, follow_symlinks=False) - FS_PERMS_SET = True - except OSError: - pass - - if not FS_PERMS_SET: - self.error_log( - 'Failed to set socket fs mode permissions', - level=logging.WARNING, - ) - - self.bind_addr = bind_addr - self.socket = sock - return sock - - @staticmethod - def prepare_socket(bind_addr, family, type, proto, nodelay, ssl_adapter): - """Create and prepare the socket object.""" - sock = socket.socket(family, type, proto) - connections.prevent_socket_inheritance(sock) - - host, port = bind_addr[:2] - IS_EPHEMERAL_PORT = port == 0 - - if not (IS_WINDOWS or IS_EPHEMERAL_PORT): - """Enable SO_REUSEADDR for the current socket. - - Skip for Windows (has different semantics) - or ephemeral ports (can steal ports from others). - - Refs: - * https://msdn.microsoft.com/en-us/library/ms740621(v=vs.85).aspx - * https://github.com/cherrypy/cheroot/issues/114 - * https://gavv.github.io/blog/ephemeral-port-reuse/ - """ - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - if nodelay and not isinstance(bind_addr, (str, bytes)): - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - - if ssl_adapter is not None: - sock = ssl_adapter.bind(sock) - - # If listening on the IPV6 any address ('::' = IN6ADDR_ANY), - # activate dual-stack. See - # https://github.com/cherrypy/cherrypy/issues/871. - listening_ipv6 = ( - hasattr(socket, 'AF_INET6') - and family == socket.AF_INET6 - and host in ('::', '::0', '::0.0.0.0') - ) - if listening_ipv6: - try: - sock.setsockopt( - socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0, - ) - except (AttributeError, socket.error): - # Apparently, the socket option is not available in - # this machine's TCP stack - pass - - return sock - - @staticmethod - def bind_socket(socket_, bind_addr): - """Bind the socket to given interface.""" - socket_.bind(bind_addr) - return socket_ - - @staticmethod - def resolve_real_bind_addr(socket_): - """Retrieve actual bind address from bound socket.""" - # FIXME: keep requested bind_addr separate real bound_addr (port - # is different in case of ephemeral port 0) - bind_addr = socket_.getsockname() - if socket_.family in ( - # Windows doesn't have socket.AF_UNIX, so not using it in check - socket.AF_INET, - socket.AF_INET6, - ): - """UNIX domain sockets are strings or bytes. - - In case of bytes with a leading null-byte it's an abstract socket. - """ - return bind_addr[:2] - - if isinstance(bind_addr, bytes): - bind_addr = bton(bind_addr) - - return bind_addr - - def process_conn(self, conn): - """Process an incoming HTTPConnection.""" - try: - self.requests.put(conn) - except queue.Full: - # Just drop the conn. TODO: write 503 back? - conn.close() - - @property - def interrupt(self): - """Flag interrupt of the server.""" - return self._interrupt - - @property - def _stopping_for_interrupt(self): - """Return whether the server is responding to an interrupt.""" - return self._interrupt is _STOPPING_FOR_INTERRUPT - - @interrupt.setter - def interrupt(self, interrupt): - """Perform the shutdown of this server and save the exception. - - Typically invoked by a worker thread in - :py:mod:`~cheroot.workers.threadpool`, the exception is raised - from the thread running :py:meth:`serve` once :py:meth:`stop` - has completed. - """ - self._interrupt = _STOPPING_FOR_INTERRUPT - self.stop() - self._interrupt = interrupt - - def stop(self): # noqa: C901 # FIXME - """Gracefully shutdown a server that is serving forever.""" - if not self.ready: - return # already stopped - - self.ready = False - if self._start_time is not None: - self._run_time += (time.time() - self._start_time) - self._start_time = None - - self._connections.stop() - - sock = getattr(self, 'socket', None) - if sock: - if not isinstance(self.bind_addr, (str, bytes)): - # Touch our own socket to make accept() return immediately. - try: - host, port = sock.getsockname()[:2] - except socket.error as ex: - if ex.args[0] not in errors.socket_errors_to_ignore: - # Changed to use error code and not message - # See - # https://github.com/cherrypy/cherrypy/issues/860. - raise - else: - # Note that we're explicitly NOT using AI_PASSIVE, - # here, because we want an actual IP to touch. - # localhost won't work if we've bound to a public IP, - # but it will if we bound to '0.0.0.0' (INADDR_ANY). - for res in socket.getaddrinfo( - host, port, socket.AF_UNSPEC, - socket.SOCK_STREAM, - ): - af, socktype, proto, _canonname, _sa = res - s = None - try: - s = socket.socket(af, socktype, proto) - # See - # https://groups.google.com/group/cherrypy-users/ - # browse_frm/thread/bbfe5eb39c904fe0 - s.settimeout(1.0) - s.connect((host, port)) - s.close() - except socket.error: - if s: - s.close() - if hasattr(sock, 'close'): - sock.close() - self.socket = None - - self._connections.close() - self.requests.stop(self.shutdown_timeout) - - -class Gateway: - """Base class to interface HTTPServer with other systems, such as WSGI.""" - - def __init__(self, req): - """Initialize Gateway instance with request. - - Args: - req (HTTPRequest): current HTTP request - """ - self.req = req - - def respond(self): - """Process the current request. Must be overridden in a subclass.""" - raise NotImplementedError # pragma: no cover - - -# These may either be ssl.Adapter subclasses or the string names -# of such classes (in which case they will be lazily loaded). -ssl_adapters = { - 'builtin': 'cheroot.ssl.builtin.BuiltinSSLAdapter', - 'pyopenssl': 'cheroot.ssl.pyopenssl.pyOpenSSLAdapter', -} - - -def get_ssl_adapter_class(name='builtin'): - """Return an SSL adapter class for the given name.""" - adapter = ssl_adapters[name.lower()] - if isinstance(adapter, str): - last_dot = adapter.rfind('.') - attr_name = adapter[last_dot + 1:] - mod_path = adapter[:last_dot] - - try: - mod = sys.modules[mod_path] - if mod is None: - raise KeyError() - except KeyError: - # The last [''] is important. - mod = __import__(mod_path, globals(), locals(), ['']) - - # Let an AttributeError propagate outward. - try: - adapter = getattr(mod, attr_name) - except AttributeError: - raise AttributeError( - "'%s' object has no attribute '%s'" - % (mod_path, attr_name), - ) - - return adapter diff --git a/lib/cheroot/server.pyi b/lib/cheroot/server.pyi deleted file mode 100644 index 864adff4..00000000 --- a/lib/cheroot/server.pyi +++ /dev/null @@ -1,172 +0,0 @@ -from typing import Any - -class HeaderReader: - def __call__(self, rfile, hdict: Any | None = ...): ... - -class DropUnderscoreHeaderReader(HeaderReader): ... - -class SizeCheckWrapper: - rfile: Any - maxlen: Any - bytes_read: int - def __init__(self, rfile, maxlen) -> None: ... - def read(self, size: Any | None = ...): ... - def readline(self, size: Any | None = ...): ... - def readlines(self, sizehint: int = ...): ... - def close(self) -> None: ... - def __iter__(self): ... - def __next__(self): ... - next: Any - -class KnownLengthRFile: - rfile: Any - remaining: Any - def __init__(self, rfile, content_length) -> None: ... - def read(self, size: Any | None = ...): ... - def readline(self, size: Any | None = ...): ... - def readlines(self, sizehint: int = ...): ... - def close(self) -> None: ... - def __iter__(self): ... - def __next__(self): ... - next: Any - -class ChunkedRFile: - rfile: Any - maxlen: Any - bytes_read: int - buffer: Any - bufsize: Any - closed: bool - def __init__(self, rfile, maxlen, bufsize: int = ...) -> None: ... - def read(self, size: Any | None = ...): ... - def readline(self, size: Any | None = ...): ... - def readlines(self, sizehint: int = ...): ... - def read_trailer_lines(self) -> None: ... - def close(self) -> None: ... - -class HTTPRequest: - server: Any - conn: Any - inheaders: Any - outheaders: Any - ready: bool - close_connection: bool - chunked_write: bool - header_reader: Any - started_request: bool - scheme: bytes - response_protocol: str - status: str - sent_headers: bool - chunked_read: bool - proxy_mode: Any - strict_mode: Any - def __init__(self, server, conn, proxy_mode: bool = ..., strict_mode: bool = ...) -> None: ... - rfile: Any - def parse_request(self) -> None: ... - uri: Any - method: Any - authority: Any - path: Any - qs: Any - request_protocol: Any - def read_request_line(self): ... - def read_request_headers(self): ... - def respond(self) -> None: ... - def simple_response(self, status, msg: str = ...) -> None: ... - def ensure_headers_sent(self) -> None: ... - def write(self, chunk) -> None: ... - def send_headers(self) -> None: ... - -class HTTPConnection: - remote_addr: Any - remote_port: Any - ssl_env: Any - rbufsize: Any - wbufsize: Any - RequestHandlerClass: Any - peercreds_enabled: bool - peercreds_resolve_enabled: bool - last_used: Any - server: Any - socket: Any - rfile: Any - wfile: Any - requests_seen: int - def __init__(self, server, sock, makefile=...) -> None: ... - def communicate(self): ... - linger: bool - def close(self) -> None: ... - def get_peer_creds(self): ... - @property - def peer_pid(self): ... - @property - def peer_uid(self): ... - @property - def peer_gid(self): ... - def resolve_peer_creds(self): ... - @property - def peer_user(self): ... - @property - def peer_group(self): ... - -class HTTPServer: - gateway: Any - minthreads: Any - maxthreads: Any - server_name: Any - protocol: str - request_queue_size: int - shutdown_timeout: int - timeout: int - expiration_interval: float - version: Any - software: Any - ready: bool - max_request_header_size: int - max_request_body_size: int - nodelay: bool - ConnectionClass: Any - ssl_adapter: Any - peercreds_enabled: bool - peercreds_resolve_enabled: bool - keep_alive_conn_limit: int - requests: Any - def __init__(self, bind_addr, gateway, minthreads: int = ..., maxthreads: int = ..., server_name: Any | None = ..., peercreds_enabled: bool = ..., peercreds_resolve_enabled: bool = ...) -> None: ... - stats: Any - def clear_stats(self): ... - def runtime(self): ... - @property - def bind_addr(self): ... - @bind_addr.setter - def bind_addr(self, value) -> None: ... - def safe_start(self) -> None: ... - socket: Any - def prepare(self) -> None: ... - def serve(self) -> None: ... - def start(self) -> None: ... - @property - def can_add_keepalive_connection(self): ... - def put_conn(self, conn) -> None: ... - def error_log(self, msg: str = ..., level: int = ..., traceback: bool = ...) -> None: ... - def bind(self, family, type, proto: int = ...): ... - def bind_unix_socket(self, bind_addr): ... - @staticmethod - def prepare_socket(bind_addr, family, type, proto, nodelay, ssl_adapter): ... - @staticmethod - def bind_socket(socket_, bind_addr): ... - @staticmethod - def resolve_real_bind_addr(socket_): ... - def process_conn(self, conn) -> None: ... - @property - def interrupt(self): ... - @interrupt.setter - def interrupt(self, interrupt) -> None: ... - def stop(self) -> None: ... - -class Gateway: - req: Any - def __init__(self, req) -> None: ... - def respond(self) -> None: ... - -def get_ssl_adapter_class(name: str = ...): ... diff --git a/lib/cheroot/ssl/__init__.py b/lib/cheroot/ssl/__init__.py deleted file mode 100644 index 19b587d0..00000000 --- a/lib/cheroot/ssl/__init__.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Implementation of the SSL adapter base interface.""" - -from abc import ABCMeta, abstractmethod - - -class Adapter(metaclass=ABCMeta): - """Base class for SSL driver library adapters. - - Required methods: - - * ``wrap(sock) -> (wrapped socket, ssl environ dict)`` - * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> - socket file object`` - """ - - @abstractmethod - def __init__( - self, certificate, private_key, certificate_chain=None, - ciphers=None, - ): - """Set up certificates, private key ciphers and reset context.""" - self.certificate = certificate - self.private_key = private_key - self.certificate_chain = certificate_chain - self.ciphers = ciphers - self.context = None - - @abstractmethod - def bind(self, sock): - """Wrap and return the given socket.""" - return sock - - @abstractmethod - def wrap(self, sock): - """Wrap and return the given socket, plus WSGI environ entries.""" - raise NotImplementedError # pragma: no cover - - @abstractmethod - def get_environ(self): - """Return WSGI environ entries to be merged into each request.""" - raise NotImplementedError # pragma: no cover - - @abstractmethod - def makefile(self, sock, mode='r', bufsize=-1): - """Return socket file object.""" - raise NotImplementedError # pragma: no cover diff --git a/lib/cheroot/ssl/__init__.pyi b/lib/cheroot/ssl/__init__.pyi deleted file mode 100644 index a9807660..00000000 --- a/lib/cheroot/ssl/__init__.pyi +++ /dev/null @@ -1,19 +0,0 @@ -from abc import abstractmethod -from typing import Any - -class Adapter(): - certificate: Any - private_key: Any - certificate_chain: Any - ciphers: Any - context: Any - @abstractmethod - def __init__(self, certificate, private_key, certificate_chain: Any | None = ..., ciphers: Any | None = ...): ... - @abstractmethod - def bind(self, sock): ... - @abstractmethod - def wrap(self, sock): ... - @abstractmethod - def get_environ(self): ... - @abstractmethod - def makefile(self, sock, mode: str = ..., bufsize: int = ...): ... diff --git a/lib/cheroot/ssl/builtin.py b/lib/cheroot/ssl/builtin.py deleted file mode 100644 index b22d4ae6..00000000 --- a/lib/cheroot/ssl/builtin.py +++ /dev/null @@ -1,478 +0,0 @@ -""" -A library for integrating Python's builtin :py:mod:`ssl` library with Cheroot. - -The :py:mod:`ssl` module must be importable for SSL functionality. - -To use this module, set ``HTTPServer.ssl_adapter`` to an instance of -``BuiltinSSLAdapter``. -""" - -import socket -import sys -import threading -from contextlib import suppress - -try: - import ssl -except ImportError: - ssl = None - -try: - from _pyio import DEFAULT_BUFFER_SIZE -except ImportError: - try: - from io import DEFAULT_BUFFER_SIZE - except ImportError: - DEFAULT_BUFFER_SIZE = -1 - -from . import Adapter -from .. import errors -from .._compat import IS_ABOVE_OPENSSL10 -from ..makefile import StreamReader, StreamWriter -from ..server import HTTPServer - -generic_socket_error = OSError - - -def _assert_ssl_exc_contains(exc, *msgs): - """Check whether SSL exception contains either of messages provided.""" - if len(msgs) < 1: - raise TypeError( - '_assert_ssl_exc_contains() requires ' - 'at least one message to be passed.', - ) - err_msg_lower = str(exc).lower() - return any(m.lower() in err_msg_lower for m in msgs) - - -def _loopback_for_cert_thread(context, server): - """Wrap a socket in ssl and perform the server-side handshake.""" - # As we only care about parsing the certificate, the failure of - # which will cause an exception in ``_loopback_for_cert``, - # we can safely ignore connection and ssl related exceptions. Ref: - # https://github.com/cherrypy/cheroot/issues/302#issuecomment-662592030 - with suppress(ssl.SSLError, OSError): - with context.wrap_socket( - server, do_handshake_on_connect=True, server_side=True, - ) as ssl_sock: - # in TLS 1.3 (Python 3.7+, OpenSSL 1.1.1+), the server - # sends the client session tickets that can be used to - # resume the TLS session on a new connection without - # performing the full handshake again. session tickets are - # sent as a post-handshake message at some _unspecified_ - # time and thus a successful connection may be closed - # without the client having received the tickets. - # Unfortunately, on Windows (Python 3.8+), this is treated - # as an incomplete handshake on the server side and a - # ``ConnectionAbortedError`` is raised. - # TLS 1.3 support is still incomplete in Python 3.8; - # there is no way for the client to wait for tickets. - # While not necessary for retrieving the parsed certificate, - # we send a tiny bit of data over the connection in an - # attempt to give the server a chance to send the session - # tickets and close the connection cleanly. - # Note that, as this is essentially a race condition, - # the error may still occur ocasionally. - ssl_sock.send(b'0000') - - -def _loopback_for_cert(certificate, private_key, certificate_chain): - """Create a loopback connection to parse a cert with a private key.""" - context = ssl.create_default_context(cafile=certificate_chain) - context.load_cert_chain(certificate, private_key) - context.check_hostname = False - context.verify_mode = ssl.CERT_NONE - - # Python 3+ Unix, Python 3.5+ Windows - client, server = socket.socketpair() - try: - # `wrap_socket` will block until the ssl handshake is complete. - # it must be called on both ends at the same time -> thread - # openssl will cache the peer's cert during a successful handshake - # and return it via `getpeercert` even after the socket is closed. - # when `close` is called, the SSL shutdown notice will be sent - # and then python will wait to receive the corollary shutdown. - thread = threading.Thread( - target=_loopback_for_cert_thread, args=(context, server), - ) - try: - thread.start() - with context.wrap_socket( - client, do_handshake_on_connect=True, - server_side=False, - ) as ssl_sock: - ssl_sock.recv(4) - return ssl_sock.getpeercert() - finally: - thread.join() - finally: - client.close() - server.close() - - -def _parse_cert(certificate, private_key, certificate_chain): - """Parse a certificate.""" - # loopback_for_cert uses socket.socketpair which was only - # introduced in Python 3.0 for *nix and 3.5 for Windows - # and requires OS support (AttributeError, OSError) - # it also requires a private key either in its own file - # or combined with the cert (SSLError) - with suppress(AttributeError, ssl.SSLError, OSError): - return _loopback_for_cert(certificate, private_key, certificate_chain) - - # KLUDGE: using an undocumented, private, test method to parse a cert - # unfortunately, it is the only built-in way without a connection - # as a private, undocumented method, it may change at any time - # so be tolerant of *any* possible errors it may raise - with suppress(Exception): - return ssl._ssl._test_decode_cert(certificate) - - return {} - - -def _sni_callback(sock, sni, context): - """Handle the SNI callback to tag the socket with the SNI.""" - sock.sni = sni - # return None to allow the TLS negotiation to continue - - -class BuiltinSSLAdapter(Adapter): - """Wrapper for integrating Python's builtin :py:mod:`ssl` with Cheroot.""" - - certificate = None - """The file name of the server SSL certificate.""" - - private_key = None - """The file name of the server's private key file.""" - - certificate_chain = None - """The file name of the certificate chain file.""" - - ciphers = None - """The ciphers list of SSL.""" - - # from mod_ssl/pkg.sslmod/ssl_engine_vars.c ssl_var_lookup_ssl_cert - CERT_KEY_TO_ENV = { - 'version': 'M_VERSION', - 'serialNumber': 'M_SERIAL', - 'notBefore': 'V_START', - 'notAfter': 'V_END', - 'subject': 'S_DN', - 'issuer': 'I_DN', - 'subjectAltName': 'SAN', - # not parsed by the Python standard library - # - A_SIG - # - A_KEY - # not provided by mod_ssl - # - OCSP - # - caIssuers - # - crlDistributionPoints - } - - # from mod_ssl/pkg.sslmod/ssl_engine_vars.c ssl_var_lookup_ssl_cert_dn_rec - CERT_KEY_TO_LDAP_CODE = { - 'countryName': 'C', - 'stateOrProvinceName': 'ST', - # NOTE: mod_ssl also provides 'stateOrProvinceName' as 'SP' - # for compatibility with SSLeay - 'localityName': 'L', - 'organizationName': 'O', - 'organizationalUnitName': 'OU', - 'commonName': 'CN', - 'title': 'T', - 'initials': 'I', - 'givenName': 'G', - 'surname': 'S', - 'description': 'D', - 'userid': 'UID', - 'emailAddress': 'Email', - # not provided by mod_ssl - # - dnQualifier: DNQ - # - domainComponent: DC - # - postalCode: PC - # - streetAddress: STREET - # - serialNumber - # - generationQualifier - # - pseudonym - # - jurisdictionCountryName - # - jurisdictionLocalityName - # - jurisdictionStateOrProvince - # - businessCategory - } - - def __init__( - self, certificate, private_key, certificate_chain=None, - ciphers=None, - ): - """Set up context in addition to base class properties if available.""" - if ssl is None: - raise ImportError('You must install the ssl module to use HTTPS.') - - super(BuiltinSSLAdapter, self).__init__( - certificate, private_key, certificate_chain, ciphers, - ) - - self.context = ssl.create_default_context( - purpose=ssl.Purpose.CLIENT_AUTH, - cafile=certificate_chain, - ) - self.context.load_cert_chain(certificate, private_key) - if self.ciphers is not None: - self.context.set_ciphers(ciphers) - - self._server_env = self._make_env_cert_dict( - 'SSL_SERVER', - _parse_cert(certificate, private_key, self.certificate_chain), - ) - if not self._server_env: - return - cert = None - with open(certificate, mode='rt') as f: - cert = f.read() - - # strip off any keys by only taking the first certificate - cert_start = cert.find(ssl.PEM_HEADER) - if cert_start == -1: - return - cert_end = cert.find(ssl.PEM_FOOTER, cert_start) - if cert_end == -1: - return - cert_end += len(ssl.PEM_FOOTER) - self._server_env['SSL_SERVER_CERT'] = cert[cert_start:cert_end] - - @property - def context(self): - """:py:class:`~ssl.SSLContext` that will be used to wrap sockets.""" - return self._context - - @context.setter - def context(self, context): - """Set the ssl ``context`` to use.""" - self._context = context - # Python 3.7+ - # if a context is provided via `cherrypy.config.update` then - # `self.context` will be set after `__init__` - # use a property to intercept it to add an SNI callback - # but don't override the user's callback - # TODO: chain callbacks - with suppress(AttributeError): - if ssl.HAS_SNI and context.sni_callback is None: - context.sni_callback = _sni_callback - - def bind(self, sock): - """Wrap and return the given socket.""" - return super(BuiltinSSLAdapter, self).bind(sock) - - def wrap(self, sock): - """Wrap and return the given socket, plus WSGI environ entries.""" - EMPTY_RESULT = None, {} - try: - s = self.context.wrap_socket( - sock, do_handshake_on_connect=True, server_side=True, - ) - except ssl.SSLError as ex: - if ex.errno == ssl.SSL_ERROR_EOF: - # This is almost certainly due to the cherrypy engine - # 'pinging' the socket to assert it's connectable; - # the 'ping' isn't SSL. - return EMPTY_RESULT - elif ex.errno == ssl.SSL_ERROR_SSL: - if _assert_ssl_exc_contains(ex, 'http request'): - # The client is speaking HTTP to an HTTPS server. - raise errors.NoSSLError - - # Check if it's one of the known errors - # Errors that are caught by PyOpenSSL, but thrown by - # built-in ssl - _block_errors = ( - 'unknown protocol', 'unknown ca', 'unknown_ca', - 'unknown error', - 'https proxy request', 'inappropriate fallback', - 'wrong version number', - 'no shared cipher', 'certificate unknown', - 'ccs received early', - 'certificate verify failed', # client cert w/o trusted CA - 'version too low', # caused by SSL3 connections - 'unsupported protocol', # caused by TLS1 connections - ) - if _assert_ssl_exc_contains(ex, *_block_errors): - # Accepted error, let's pass - return EMPTY_RESULT - elif _assert_ssl_exc_contains(ex, 'handshake operation timed out'): - # This error is thrown by builtin SSL after a timeout - # when client is speaking HTTP to an HTTPS server. - # The connection can safely be dropped. - return EMPTY_RESULT - raise - except generic_socket_error as exc: - """It is unclear why exactly this happens. - - It's reproducible only with openssl>1.0 and stdlib - :py:mod:`ssl` wrapper. - In CherryPy it's triggered by Checker plugin, which connects - to the app listening to the socket port in TLS mode via plain - HTTP during startup (from the same process). - - - Ref: https://github.com/cherrypy/cherrypy/issues/1618 - """ - is_error0 = exc.args == (0, 'Error') - - if is_error0 and IS_ABOVE_OPENSSL10: - return EMPTY_RESULT - raise - return s, self.get_environ(s) - - def get_environ(self, sock): - """Create WSGI environ entries to be merged into each request.""" - cipher = sock.cipher() - ssl_environ = { - 'wsgi.url_scheme': 'https', - 'HTTPS': 'on', - 'SSL_PROTOCOL': cipher[1], - 'SSL_CIPHER': cipher[0], - 'SSL_CIPHER_EXPORT': '', - 'SSL_CIPHER_USEKEYSIZE': cipher[2], - 'SSL_VERSION_INTERFACE': '%s Python/%s' % ( - HTTPServer.version, sys.version, - ), - 'SSL_VERSION_LIBRARY': ssl.OPENSSL_VERSION, - 'SSL_CLIENT_VERIFY': 'NONE', - # 'NONE' - client did not provide a cert (overriden below) - } - - # Python 3.3+ - with suppress(AttributeError): - compression = sock.compression() - if compression is not None: - ssl_environ['SSL_COMPRESS_METHOD'] = compression - - # Python 3.6+ - with suppress(AttributeError): - ssl_environ['SSL_SESSION_ID'] = sock.session.id.hex() - with suppress(AttributeError): - target_cipher = cipher[:2] - for cip in sock.context.get_ciphers(): - if target_cipher == (cip['name'], cip['protocol']): - ssl_environ['SSL_CIPHER_ALGKEYSIZE'] = cip['alg_bits'] - break - - # Python 3.7+ sni_callback - with suppress(AttributeError): - ssl_environ['SSL_TLS_SNI'] = sock.sni - - if self.context and self.context.verify_mode != ssl.CERT_NONE: - client_cert = sock.getpeercert() - if client_cert: - # builtin ssl **ALWAYS** validates client certificates - # and terminates the connection on failure - ssl_environ['SSL_CLIENT_VERIFY'] = 'SUCCESS' - ssl_environ.update( - self._make_env_cert_dict('SSL_CLIENT', client_cert), - ) - ssl_environ['SSL_CLIENT_CERT'] = ssl.DER_cert_to_PEM_cert( - sock.getpeercert(binary_form=True), - ).strip() - - ssl_environ.update(self._server_env) - - # not supplied by the Python standard library (as of 3.8) - # - SSL_SESSION_RESUMED - # - SSL_SECURE_RENEG - # - SSL_CLIENT_CERT_CHAIN_n - # - SRP_USER - # - SRP_USERINFO - - return ssl_environ - - def _make_env_cert_dict(self, env_prefix, parsed_cert): - """Return a dict of WSGI environment variables for a certificate. - - E.g. SSL_CLIENT_M_VERSION, SSL_CLIENT_M_SERIAL, etc. - See https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#envvars. - """ - if not parsed_cert: - return {} - - env = {} - for cert_key, env_var in self.CERT_KEY_TO_ENV.items(): - key = '%s_%s' % (env_prefix, env_var) - value = parsed_cert.get(cert_key) - if env_var == 'SAN': - env.update(self._make_env_san_dict(key, value)) - elif env_var.endswith('_DN'): - env.update(self._make_env_dn_dict(key, value)) - else: - env[key] = str(value) - - # mod_ssl 2.1+; Python 3.2+ - # number of days until the certificate expires - if 'notBefore' in parsed_cert: - remain = ssl.cert_time_to_seconds(parsed_cert['notAfter']) - remain -= ssl.cert_time_to_seconds(parsed_cert['notBefore']) - remain /= 60 * 60 * 24 - env['%s_V_REMAIN' % (env_prefix,)] = str(int(remain)) - - return env - - def _make_env_san_dict(self, env_prefix, cert_value): - """Return a dict of WSGI environment variables for a certificate DN. - - E.g. SSL_CLIENT_SAN_Email_0, SSL_CLIENT_SAN_DNS_0, etc. - See SSL_CLIENT_SAN_* at - https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#envvars. - """ - if not cert_value: - return {} - - env = {} - dns_count = 0 - email_count = 0 - for attr_name, val in cert_value: - if attr_name == 'DNS': - env['%s_DNS_%i' % (env_prefix, dns_count)] = val - dns_count += 1 - elif attr_name == 'Email': - env['%s_Email_%i' % (env_prefix, email_count)] = val - email_count += 1 - - # other mod_ssl SAN vars: - # - SAN_OTHER_msUPN_n - return env - - def _make_env_dn_dict(self, env_prefix, cert_value): - """Return a dict of WSGI environment variables for a certificate DN. - - E.g. SSL_CLIENT_S_DN_CN, SSL_CLIENT_S_DN_C, etc. - See SSL_CLIENT_S_DN_x509 at - https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#envvars. - """ - if not cert_value: - return {} - - dn = [] - dn_attrs = {} - for rdn in cert_value: - for attr_name, val in rdn: - attr_code = self.CERT_KEY_TO_LDAP_CODE.get(attr_name) - dn.append('%s=%s' % (attr_code or attr_name, val)) - if not attr_code: - continue - dn_attrs.setdefault(attr_code, []) - dn_attrs[attr_code].append(val) - - env = { - env_prefix: ','.join(dn), - } - for attr_code, values in dn_attrs.items(): - env['%s_%s' % (env_prefix, attr_code)] = ','.join(values) - if len(values) == 1: - continue - for i, val in enumerate(values): - env['%s_%s_%i' % (env_prefix, attr_code, i)] = val - return env - - def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE): - """Return socket file object.""" - cls = StreamReader if 'r' in mode else StreamWriter - return cls(sock, mode, bufsize) diff --git a/lib/cheroot/ssl/builtin.pyi b/lib/cheroot/ssl/builtin.pyi deleted file mode 100644 index 72e45001..00000000 --- a/lib/cheroot/ssl/builtin.pyi +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Any -from . import Adapter - -DEFAULT_BUFFER_SIZE: int - -class BuiltinSSLAdapter(Adapter): - CERT_KEY_TO_ENV: Any - CERT_KEY_TO_LDAP_CODE: Any - def __init__(self, certificate, private_key, certificate_chain: Any | None = ..., ciphers: Any | None = ...) -> None: ... - @property - def context(self): ... - @context.setter - def context(self, context) -> None: ... - def bind(self, sock): ... - def wrap(self, sock): ... - def get_environ(self, sock): ... - def makefile(self, sock, mode: str = ..., bufsize: int = ...): ... diff --git a/lib/cheroot/ssl/pyopenssl.py b/lib/cheroot/ssl/pyopenssl.py deleted file mode 100644 index 548200f7..00000000 --- a/lib/cheroot/ssl/pyopenssl.py +++ /dev/null @@ -1,376 +0,0 @@ -""" -A library for integrating :doc:`pyOpenSSL ` with Cheroot. - -The :py:mod:`OpenSSL ` module must be importable -for SSL/TLS/HTTPS functionality. -You can obtain it from `here `_. - -To use this module, set :py:attr:`HTTPServer.ssl_adapter -` to an instance of -:py:class:`ssl.Adapter `. -There are two ways to use :abbr:`TLS (Transport-Level Security)`: - -Method One ----------- - - * :py:attr:`ssl_adapter.context - `: an instance of - :py:class:`SSL.Context `. - -If this is not None, it is assumed to be an :py:class:`SSL.Context -` instance, and will be passed to -:py:class:`SSL.Connection ` on bind(). -The developer is responsible for forming a valid :py:class:`Context -` object. This -approach is to be preferred for more flexibility, e.g. if the cert and -key are streams instead of files, or need decryption, or -:py:data:`SSL.SSLv3_METHOD ` -is desired instead of the default :py:data:`SSL.SSLv23_METHOD -`, etc. Consult -the :doc:`pyOpenSSL ` documentation for -complete options. - -Method Two (shortcut) ---------------------- - - * :py:attr:`ssl_adapter.certificate - `: the file name - of the server's TLS certificate. - * :py:attr:`ssl_adapter.private_key - `: the file name - of the server's private key file. - -Both are :py:data:`None` by default. If :py:attr:`ssl_adapter.context -` is :py:data:`None`, -but ``.private_key`` and ``.certificate`` are both given and valid, they -will be read, and the context will be automatically created from them. - -.. spelling:: - - pyopenssl -""" - -import socket -import sys -import threading -import time - -try: - import OpenSSL.version - from OpenSSL import SSL - from OpenSSL import crypto - - try: - ssl_conn_type = SSL.Connection - except AttributeError: - ssl_conn_type = SSL.ConnectionType -except ImportError: - SSL = None - -from . import Adapter -from .. import errors, server as cheroot_server -from ..makefile import StreamReader, StreamWriter - - -class SSLFileobjectMixin: - """Base mixin for a TLS socket stream.""" - - ssl_timeout = 3 - ssl_retry = .01 - - # FIXME: - def _safe_call(self, is_reader, call, *args, **kwargs): # noqa: C901 - """Wrap the given call with TLS error-trapping. - - is_reader: if False EOF errors will be raised. If True, EOF errors - will return "" (to emulate normal sockets). - """ - start = time.time() - while True: - try: - return call(*args, **kwargs) - except SSL.WantReadError: - # Sleep and try again. This is dangerous, because it means - # the rest of the stack has no way of differentiating - # between a "new handshake" error and "client dropped". - # Note this isn't an endless loop: there's a timeout below. - # Ref: https://stackoverflow.com/a/5133568/595220 - time.sleep(self.ssl_retry) - except SSL.WantWriteError: - time.sleep(self.ssl_retry) - except SSL.SysCallError as e: - if is_reader and e.args == (-1, 'Unexpected EOF'): - return b'' - - errnum = e.args[0] - if is_reader and errnum in errors.socket_errors_to_ignore: - return b'' - raise socket.error(errnum) - except SSL.Error as e: - if is_reader and e.args == (-1, 'Unexpected EOF'): - return b'' - - thirdarg = None - try: - thirdarg = e.args[0][0][2] - except IndexError: - pass - - if thirdarg == 'http request': - # The client is talking HTTP to an HTTPS server. - raise errors.NoSSLError() - - raise errors.FatalSSLAlert(*e.args) - - if time.time() - start > self.ssl_timeout: - raise socket.timeout('timed out') - - def recv(self, size): - """Receive message of a size from the socket.""" - return self._safe_call( - True, - super(SSLFileobjectMixin, self).recv, - size, - ) - - def readline(self, size=-1): - """Receive message of a size from the socket. - - Matches the following interface: - https://docs.python.org/3/library/io.html#io.IOBase.readline - """ - return self._safe_call( - True, - super(SSLFileobjectMixin, self).readline, - size, - ) - - def sendall(self, *args, **kwargs): - """Send whole message to the socket.""" - return self._safe_call( - False, - super(SSLFileobjectMixin, self).sendall, - *args, **kwargs - ) - - def send(self, *args, **kwargs): - """Send some part of message to the socket.""" - return self._safe_call( - False, - super(SSLFileobjectMixin, self).send, - *args, **kwargs - ) - - -class SSLFileobjectStreamReader(SSLFileobjectMixin, StreamReader): - """SSL file object attached to a socket object.""" - - -class SSLFileobjectStreamWriter(SSLFileobjectMixin, StreamWriter): - """SSL file object attached to a socket object.""" - - -class SSLConnectionProxyMeta: - """Metaclass for generating a bunch of proxy methods.""" - - def __new__(mcl, name, bases, nmspc): - """Attach a list of proxy methods to a new class.""" - proxy_methods = ( - 'get_context', 'pending', 'send', 'write', 'recv', 'read', - 'renegotiate', 'bind', 'listen', 'connect', 'accept', - 'setblocking', 'fileno', 'close', 'get_cipher_list', - 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', - 'makefile', 'get_app_data', 'set_app_data', 'state_string', - 'sock_shutdown', 'get_peer_certificate', 'want_read', - 'want_write', 'set_connect_state', 'set_accept_state', - 'connect_ex', 'sendall', 'settimeout', 'gettimeout', - 'shutdown', - ) - proxy_methods_no_args = ( - 'shutdown', - ) - - proxy_props = ( - 'family', - ) - - def lock_decorator(method): - """Create a proxy method for a new class.""" - def proxy_wrapper(self, *args): - self._lock.acquire() - try: - new_args = ( - args[:] if method not in proxy_methods_no_args else [] - ) - return getattr(self._ssl_conn, method)(*new_args) - finally: - self._lock.release() - return proxy_wrapper - for m in proxy_methods: - nmspc[m] = lock_decorator(m) - nmspc[m].__name__ = m - - def make_property(property_): - """Create a proxy method for a new class.""" - def proxy_prop_wrapper(self): - return getattr(self._ssl_conn, property_) - proxy_prop_wrapper.__name__ = property_ - return property(proxy_prop_wrapper) - for p in proxy_props: - nmspc[p] = make_property(p) - - # Doesn't work via super() for some reason. - # Falling back to type() instead: - return type(name, bases, nmspc) - - -class SSLConnection(metaclass=SSLConnectionProxyMeta): - r"""A thread-safe wrapper for an ``SSL.Connection``. - - :param tuple args: the arguments to create the wrapped \ - :py:class:`SSL.Connection(*args) \ - ` - """ - - def __init__(self, *args): - """Initialize SSLConnection instance.""" - self._ssl_conn = SSL.Connection(*args) - self._lock = threading.RLock() - - -class pyOpenSSLAdapter(Adapter): - """A wrapper for integrating pyOpenSSL with Cheroot.""" - - certificate = None - """The file name of the server's TLS certificate.""" - - private_key = None - """The file name of the server's private key file.""" - - certificate_chain = None - """Optional. The file name of CA's intermediate certificate bundle. - - This is needed for cheaper "chained root" TLS certificates, - and should be left as :py:data:`None` if not required.""" - - context = None - """ - An instance of :py:class:`SSL.Context `. - """ - - ciphers = None - """The ciphers list of TLS.""" - - def __init__( - self, certificate, private_key, certificate_chain=None, - ciphers=None, - ): - """Initialize OpenSSL Adapter instance.""" - if SSL is None: - raise ImportError('You must install pyOpenSSL to use HTTPS.') - - super(pyOpenSSLAdapter, self).__init__( - certificate, private_key, certificate_chain, ciphers, - ) - - self._environ = None - - def bind(self, sock): - """Wrap and return the given socket.""" - if self.context is None: - self.context = self.get_context() - conn = SSLConnection(self.context, sock) - self._environ = self.get_environ() - return conn - - def wrap(self, sock): - """Wrap and return the given socket, plus WSGI environ entries.""" - # pyOpenSSL doesn't perform the handshake until the first read/write - # forcing the handshake to complete tends to result in the connection - # closing so we can't reliably access protocol/client cert for the env - return sock, self._environ.copy() - - def get_context(self): - """Return an ``SSL.Context`` from self attributes. - - Ref: :py:class:`SSL.Context ` - """ - # See https://code.activestate.com/recipes/442473/ - c = SSL.Context(SSL.SSLv23_METHOD) - c.use_privatekey_file(self.private_key) - if self.certificate_chain: - c.load_verify_locations(self.certificate_chain) - c.use_certificate_file(self.certificate) - return c - - def get_environ(self): - """Return WSGI environ entries to be merged into each request.""" - ssl_environ = { - 'wsgi.url_scheme': 'https', - 'HTTPS': 'on', - 'SSL_VERSION_INTERFACE': '%s %s/%s Python/%s' % ( - cheroot_server.HTTPServer.version, - OpenSSL.version.__title__, OpenSSL.version.__version__, - sys.version, - ), - 'SSL_VERSION_LIBRARY': SSL.SSLeay_version( - SSL.SSLEAY_VERSION, - ).decode(), - } - - if self.certificate: - # Server certificate attributes - with open(self.certificate, 'rb') as cert_file: - cert = crypto.load_certificate( - crypto.FILETYPE_PEM, cert_file.read(), - ) - - ssl_environ.update({ - 'SSL_SERVER_M_VERSION': cert.get_version(), - 'SSL_SERVER_M_SERIAL': cert.get_serial_number(), - # 'SSL_SERVER_V_START': - # Validity of server's certificate (start time), - # 'SSL_SERVER_V_END': - # Validity of server's certificate (end time), - }) - - for prefix, dn in [ - ('I', cert.get_issuer()), - ('S', cert.get_subject()), - ]: - # X509Name objects don't seem to have a way to get the - # complete DN string. Use str() and slice it instead, - # because str(dn) == "" - dnstr = str(dn)[18:-2] - - wsgikey = 'SSL_SERVER_%s_DN' % prefix - ssl_environ[wsgikey] = dnstr - - # The DN should be of the form: /k1=v1/k2=v2, but we must allow - # for any value to contain slashes itself (in a URL). - while dnstr: - pos = dnstr.rfind('=') - dnstr, value = dnstr[:pos], dnstr[pos + 1:] - pos = dnstr.rfind('/') - dnstr, key = dnstr[:pos], dnstr[pos + 1:] - if key and value: - wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key) - ssl_environ[wsgikey] = value - - return ssl_environ - - def makefile(self, sock, mode='r', bufsize=-1): - """Return socket file object.""" - cls = ( - SSLFileobjectStreamReader - if 'r' in mode else - SSLFileobjectStreamWriter - ) - if SSL and isinstance(sock, ssl_conn_type): - wrapped_socket = cls(sock, mode, bufsize) - wrapped_socket.ssl_timeout = sock.gettimeout() - return wrapped_socket - # This is from past: - # TODO: figure out what it's meant for - else: - return cheroot_server.CP_fileobject(sock, mode, bufsize) diff --git a/lib/cheroot/ssl/pyopenssl.pyi b/lib/cheroot/ssl/pyopenssl.pyi deleted file mode 100644 index 107675c9..00000000 --- a/lib/cheroot/ssl/pyopenssl.pyi +++ /dev/null @@ -1,31 +0,0 @@ -from . import Adapter -from ..makefile import StreamReader, StreamWriter -from OpenSSL import SSL -from typing import Any, Type - -ssl_conn_type: Type[SSL.Connection] - -class SSLFileobjectMixin: - ssl_timeout: int - ssl_retry: float - def recv(self, size): ... - def readline(self, size: int = ...): ... - def sendall(self, *args, **kwargs): ... - def send(self, *args, **kwargs): ... - -class SSLFileobjectStreamReader(SSLFileobjectMixin, StreamReader): ... # type:ignore[misc] -class SSLFileobjectStreamWriter(SSLFileobjectMixin, StreamWriter): ... # type:ignore[misc] - -class SSLConnectionProxyMeta: - def __new__(mcl, name, bases, nmspc): ... - -class SSLConnection: - def __init__(self, *args) -> None: ... - -class pyOpenSSLAdapter(Adapter): - def __init__(self, certificate, private_key, certificate_chain: Any | None = ..., ciphers: Any | None = ...) -> None: ... - def bind(self, sock): ... - def wrap(self, sock): ... - def get_environ(self): ... - def makefile(self, sock, mode: str = ..., bufsize: int = ...): ... - def get_context(self) -> SSL.Context: ... diff --git a/lib/cheroot/test/__init__.py b/lib/cheroot/test/__init__.py deleted file mode 100644 index e2a7b348..00000000 --- a/lib/cheroot/test/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Cheroot test suite.""" diff --git a/lib/cheroot/test/_pytest_plugin.py b/lib/cheroot/test/_pytest_plugin.py deleted file mode 100644 index 8ff3b02c..00000000 --- a/lib/cheroot/test/_pytest_plugin.py +++ /dev/null @@ -1,60 +0,0 @@ -"""Local pytest plugin. - -Contains hooks, which are tightly bound to the Cheroot framework -itself, useless for end-users' app testing. -""" - -from __future__ import absolute_import, division, print_function -__metaclass__ = type - -import pytest -import six - - -pytest_version = tuple(map(int, pytest.__version__.split('.'))) - - -def pytest_load_initial_conftests(early_config, parser, args): - """Drop unfilterable warning ignores.""" - if pytest_version < (6, 2, 0): - return - - # pytest>=6.2.0 under Python 3.8: - # Refs: - # * https://docs.pytest.org/en/stable/usage.html#unraisable - # * https://github.com/pytest-dev/pytest/issues/5299 - early_config._inicache['filterwarnings'].extend(( - 'ignore:Exception in thread CP Server Thread-:' - 'pytest.PytestUnhandledThreadExceptionWarning:_pytest.threadexception', - 'ignore:Exception in thread Thread-:' - 'pytest.PytestUnhandledThreadExceptionWarning:_pytest.threadexception', - 'ignore:Exception ignored in. ' - ' dt2: - diff = dt1 - dt2 - else: - diff = dt2 - dt1 - if not diff < datetime.timedelta(seconds=seconds): - raise AssertionError( - '%r and %r are not within %r seconds.' % - (dt1, dt2, seconds), - ) - - -class Request: - """HTTP request container.""" - - def __init__(self, environ): - """Initialize HTTP request.""" - self.environ = environ - - -class Response: - """HTTP response container.""" - - def __init__(self): - """Initialize HTTP response.""" - self.status = '200 OK' - self.headers = {'Content-Type': 'text/html'} - self.body = None - - def output(self): - """Generate iterable response body object.""" - if self.body is None: - return [] - elif isinstance(self.body, str): - return [self.body.encode('iso-8859-1')] - elif isinstance(self.body, bytes): - return [self.body] - else: - return [x.encode('iso-8859-1') for x in self.body] - - -class Controller: - """WSGI app for tests.""" - - def __call__(self, environ, start_response): - """WSGI request handler.""" - req, resp = Request(environ), Response() - try: - # Python 3 supports unicode attribute names - # Python 2 encodes them - handler = self.handlers[environ['PATH_INFO']] - except KeyError: - resp.status = '404 Not Found' - else: - output = handler(req, resp) - if ( - output is not None - and not any( - resp.status.startswith(status_code) - for status_code in ('204', '304') - ) - ): - resp.body = output - try: - resp.headers.setdefault('Content-Length', str(len(output))) - except TypeError: - if not isinstance(output, types.GeneratorType): - raise - start_response(resp.status, resp.headers.items()) - return resp.output() diff --git a/lib/cheroot/test/test__compat.py b/lib/cheroot/test/test__compat.py deleted file mode 100644 index 8a0f3304..00000000 --- a/lib/cheroot/test/test__compat.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Test suite for cross-python compatibility helpers.""" - -import pytest - -from cheroot._compat import extract_bytes, ntob, ntou, bton - - -@pytest.mark.parametrize( - ('func', 'inp', 'out'), - ( - (ntob, 'bar', b'bar'), - (ntou, 'bar', u'bar'), - (bton, b'bar', 'bar'), - ), -) -def test_compat_functions_positive(func, inp, out): - """Check that compatibility functions work with correct input.""" - assert func(inp, encoding='utf-8') == out - - -@pytest.mark.parametrize( - 'func', - ( - ntob, - ntou, - ), -) -def test_compat_functions_negative_nonnative(func): - """Check that compatibility functions fail loudly for incorrect input.""" - non_native_test_str = b'bar' - with pytest.raises(TypeError): - func(non_native_test_str, encoding='utf-8') - - -def test_ntou_escape(): - """Check that ``ntou`` supports escape-encoding under Python 2.""" - expected = u'hišřії' - actual = ntou('hi\u0161\u0159\u0456\u0457', encoding='escape') - assert actual == expected - - -@pytest.mark.parametrize( - ('input_argument', 'expected_result'), - ( - (b'qwerty', b'qwerty'), - (memoryview(b'asdfgh'), b'asdfgh'), - ), -) -def test_extract_bytes(input_argument, expected_result): - """Check that legitimate inputs produce bytes.""" - assert extract_bytes(input_argument) == expected_result - - -def test_extract_bytes_invalid(): - """Ensure that invalid input causes exception to be raised.""" - with pytest.raises( - ValueError, - match=r'^extract_bytes\(\) only accepts bytes ' - 'and memoryview/buffer$', - ): - extract_bytes(u'some юнікод їїї') diff --git a/lib/cheroot/test/test_cli.py b/lib/cheroot/test/test_cli.py deleted file mode 100644 index a50ba98b..00000000 --- a/lib/cheroot/test/test_cli.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Tests to verify the command line interface. - -.. spelling:: - - cli -""" -import sys - -import pytest - -from cheroot.cli import ( - Application, - parse_wsgi_bind_addr, -) - - -@pytest.mark.parametrize( - ('raw_bind_addr', 'expected_bind_addr'), - ( - # tcp/ip - ('192.168.1.1:80', ('192.168.1.1', 80)), - # ipv6 ips has to be enclosed in brakets when specified in url form - ('[::1]:8000', ('::1', 8000)), - ('localhost:5000', ('localhost', 5000)), - # this is a valid input, but foo gets discarted - ('foo@bar:5000', ('bar', 5000)), - ('foo', ('foo', None)), - ('123456789', ('123456789', None)), - # unix sockets - ('/tmp/cheroot.sock', '/tmp/cheroot.sock'), - ('/tmp/some-random-file-name', '/tmp/some-random-file-name'), - # abstract sockets - ('@cheroot', '\x00cheroot'), - ), -) -def test_parse_wsgi_bind_addr(raw_bind_addr, expected_bind_addr): - """Check the parsing of the --bind option. - - Verify some of the supported addresses and the expected return value. - """ - assert parse_wsgi_bind_addr(raw_bind_addr) == expected_bind_addr - - -@pytest.fixture -def wsgi_app(monkeypatch): - """Return a WSGI app stub.""" - class WSGIAppMock: - """Mock of a wsgi module.""" - - def application(self): - """Empty application method. - - Default method to be called when no specific callable - is defined in the wsgi application identifier. - - It has an empty body because we are expecting to verify that - the same method is return no the actual execution of it. - """ - - def main(self): - """Empty custom method (callable) inside the mocked WSGI app. - - It has an empty body because we are expecting to verify that - the same method is return no the actual execution of it. - """ - app = WSGIAppMock() - # patch sys.modules, to include the an instance of WSGIAppMock - # under a specific namespace - monkeypatch.setitem(sys.modules, 'mypkg.wsgi', app) - return app - - -@pytest.mark.parametrize( - ('app_name', 'app_method'), - ( - (None, 'application'), - ('application', 'application'), - ('main', 'main'), - ), -) -# pylint: disable=invalid-name -def test_Aplication_resolve(app_name, app_method, wsgi_app): - """Check the wsgi application name conversion.""" - if app_name is None: - wsgi_app_spec = 'mypkg.wsgi' - else: - wsgi_app_spec = 'mypkg.wsgi:{app_name}'.format(**locals()) - expected_app = getattr(wsgi_app, app_method) - assert Application.resolve(wsgi_app_spec).wsgi_app == expected_app diff --git a/lib/cheroot/test/test_conn.py b/lib/cheroot/test/test_conn.py deleted file mode 100644 index 57c21ac7..00000000 --- a/lib/cheroot/test/test_conn.py +++ /dev/null @@ -1,1311 +0,0 @@ -"""Tests for TCP connection handling, including proper and timely close.""" - -import errno -import socket -import time -import logging -import traceback as traceback_ -from collections import namedtuple -import http.client -import urllib.request - -import pytest -from jaraco.text import trim, unwrap - -from cheroot.test import helper, webtest -from cheroot._compat import IS_CI, IS_MACOS, IS_PYPY, IS_WINDOWS -import cheroot.server - - -IS_SLOW_ENV = IS_MACOS or IS_WINDOWS - - -timeout = 1 -pov = 'pPeErRsSiIsStTeEnNcCeE oOfF vViIsSiIoOnN' - - -class Controller(helper.Controller): - """Controller for serving WSGI apps.""" - - def hello(req, resp): - """Render Hello world.""" - return 'Hello, world!' - - def pov(req, resp): - """Render ``pov`` value.""" - return pov - - def stream(req, resp): - """Render streaming response.""" - if 'set_cl' in req.environ['QUERY_STRING']: - resp.headers['Content-Length'] = str(10) - - def content(): - for x in range(10): - yield str(x) - - return content() - - def upload(req, resp): - """Process file upload and render thank.""" - if not req.environ['REQUEST_METHOD'] == 'POST': - raise AssertionError( - "'POST' != request.method %r" % - req.environ['REQUEST_METHOD'], - ) - return "thanks for '%s'" % req.environ['wsgi.input'].read() - - def custom_204(req, resp): - """Render response with status 204.""" - resp.status = '204' - return 'Code = 204' - - def custom_304(req, resp): - """Render response with status 304.""" - resp.status = '304' - return 'Code = 304' - - def err_before_read(req, resp): - """Render response with status 500.""" - resp.status = '500 Internal Server Error' - return 'ok' - - def one_megabyte_of_a(req, resp): - """Render 1MB response.""" - return ['a' * 1024] * 1024 - - def wrong_cl_buffered(req, resp): - """Render buffered response with invalid length value.""" - resp.headers['Content-Length'] = '5' - return 'I have too many bytes' - - def wrong_cl_unbuffered(req, resp): - """Render unbuffered response with invalid length value.""" - resp.headers['Content-Length'] = '5' - return ['I too', ' have too many bytes'] - - def _munge(string): - """Encode PATH_INFO correctly depending on Python version. - - WSGI 1.0 is a mess around unicode. Create endpoints - that match the PATH_INFO that it produces. - """ - return string.encode('utf-8').decode('latin-1') - - handlers = { - '/hello': hello, - '/pov': pov, - '/page1': pov, - '/page2': pov, - '/page3': pov, - '/stream': stream, - '/upload': upload, - '/custom/204': custom_204, - '/custom/304': custom_304, - '/err_before_read': err_before_read, - '/one_megabyte_of_a': one_megabyte_of_a, - '/wrong_cl_buffered': wrong_cl_buffered, - '/wrong_cl_unbuffered': wrong_cl_unbuffered, - } - - -class ErrorLogMonitor: - """Mock class to access the server error_log calls made by the server.""" - - ErrorLogCall = namedtuple('ErrorLogCall', ['msg', 'level', 'traceback']) - - def __init__(self): - """Initialize the server error log monitor/interceptor. - - If you need to ignore a particular error message use the property - ``ignored_msgs`` by appending to the list the expected error messages. - """ - self.calls = [] - # to be used the the teardown validation - self.ignored_msgs = [] - - def __call__(self, msg='', level=logging.INFO, traceback=False): - """Intercept the call to the server error_log method.""" - if traceback: - tblines = traceback_.format_exc() - else: - tblines = '' - self.calls.append(ErrorLogMonitor.ErrorLogCall(msg, level, tblines)) - - -@pytest.fixture -def raw_testing_server(wsgi_server_client): - """Attach a WSGI app to the given server and preconfigure it.""" - app = Controller() - - def _timeout(req, resp): - return str(wsgi_server.timeout) - app.handlers['/timeout'] = _timeout - wsgi_server = wsgi_server_client.server_instance - wsgi_server.wsgi_app = app - wsgi_server.max_request_body_size = 1001 - wsgi_server.timeout = timeout - wsgi_server.server_client = wsgi_server_client - wsgi_server.keep_alive_conn_limit = 2 - - return wsgi_server - - -@pytest.fixture -def testing_server(raw_testing_server, monkeypatch): - """Modify the "raw" base server to monitor the error_log messages. - - If you need to ignore a particular error message use the property - ``testing_server.error_log.ignored_msgs`` by appending to the list - the expected error messages. - """ - # patch the error_log calls of the server instance - monkeypatch.setattr(raw_testing_server, 'error_log', ErrorLogMonitor()) - - yield raw_testing_server - - # Teardown verification, in case that the server logged an - # error that wasn't notified to the client or we just made a mistake. - # pylint: disable=possibly-unused-variable - for c_msg, c_level, c_traceback in raw_testing_server.error_log.calls: - if c_level <= logging.WARNING: - continue - - assert c_msg in raw_testing_server.error_log.ignored_msgs, ( - 'Found error in the error log: ' - "message = '{c_msg}', level = '{c_level}'\n" - '{c_traceback}'.format(**locals()), - ) - - -@pytest.fixture -def test_client(testing_server): - """Get and return a test client out of the given server.""" - return testing_server.server_client - - -def header_exists(header_name, headers): - """Check that a header is present.""" - return header_name.lower() in (k.lower() for (k, _) in headers) - - -def header_has_value(header_name, header_value, headers): - """Check that a header with a given value is present.""" - return header_name.lower() in ( - k.lower() for (k, v) in headers - if v == header_value - ) - - -def test_HTTP11_persistent_connections(test_client): - """Test persistent HTTP/1.1 connections.""" - # Initialize a persistent HTTP connection - http_connection = test_client.get_connection() - http_connection.auto_open = False - http_connection.connect() - - # Make the first request and assert there's no "Connection: close". - status_line, actual_headers, actual_resp_body = test_client.get( - '/pov', http_conn=http_connection, - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == pov.encode() - assert not header_exists('Connection', actual_headers) - - # Make another request on the same connection. - status_line, actual_headers, actual_resp_body = test_client.get( - '/page1', http_conn=http_connection, - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == pov.encode() - assert not header_exists('Connection', actual_headers) - - # Test client-side close. - status_line, actual_headers, actual_resp_body = test_client.get( - '/page2', http_conn=http_connection, - headers=[('Connection', 'close')], - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == pov.encode() - assert header_has_value('Connection', 'close', actual_headers) - - # Make another request on the same connection, which should error. - with pytest.raises(http.client.NotConnected): - test_client.get('/pov', http_conn=http_connection) - - -@pytest.mark.parametrize( - 'set_cl', - ( - False, # Without Content-Length - True, # With Content-Length - ), -) -def test_streaming_11(test_client, set_cl): - """Test serving of streaming responses with HTTP/1.1 protocol.""" - # Initialize a persistent HTTP connection - http_connection = test_client.get_connection() - http_connection.auto_open = False - http_connection.connect() - - # Make the first request and assert there's no "Connection: close". - status_line, actual_headers, actual_resp_body = test_client.get( - '/pov', http_conn=http_connection, - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == pov.encode() - assert not header_exists('Connection', actual_headers) - - # Make another, streamed request on the same connection. - if set_cl: - # When a Content-Length is provided, the content should stream - # without closing the connection. - status_line, actual_headers, actual_resp_body = test_client.get( - '/stream?set_cl=Yes', http_conn=http_connection, - ) - assert header_exists('Content-Length', actual_headers) - assert not header_has_value('Connection', 'close', actual_headers) - assert not header_exists('Transfer-Encoding', actual_headers) - - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == b'0123456789' - else: - # When no Content-Length response header is provided, - # streamed output will either close the connection, or use - # chunked encoding, to determine transfer-length. - status_line, actual_headers, actual_resp_body = test_client.get( - '/stream', http_conn=http_connection, - ) - assert not header_exists('Content-Length', actual_headers) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == b'0123456789' - - chunked_response = False - for k, v in actual_headers: - if k.lower() == 'transfer-encoding': - if str(v) == 'chunked': - chunked_response = True - - if chunked_response: - assert not header_has_value('Connection', 'close', actual_headers) - else: - assert header_has_value('Connection', 'close', actual_headers) - - # Make another request on the same connection, which should - # error. - with pytest.raises(http.client.NotConnected): - test_client.get('/pov', http_conn=http_connection) - - # Try HEAD. - # See https://www.bitbucket.org/cherrypy/cherrypy/issue/864. - # TODO: figure out how can this be possible on an closed connection - # (chunked_response case) - status_line, actual_headers, actual_resp_body = test_client.head( - '/stream', http_conn=http_connection, - ) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == b'' - assert not header_exists('Transfer-Encoding', actual_headers) - - # Prevent the resource warnings: - http_connection.close() - - -@pytest.mark.parametrize( - 'set_cl', - ( - False, # Without Content-Length - True, # With Content-Length - ), -) -def test_streaming_10(test_client, set_cl): - """Test serving of streaming responses with HTTP/1.0 protocol.""" - original_server_protocol = test_client.server_instance.protocol - test_client.server_instance.protocol = 'HTTP/1.0' - - # Initialize a persistent HTTP connection - http_connection = test_client.get_connection() - http_connection.auto_open = False - http_connection.connect() - - # Make the first request and assert Keep-Alive. - status_line, actual_headers, actual_resp_body = test_client.get( - '/pov', http_conn=http_connection, - headers=[('Connection', 'Keep-Alive')], - protocol='HTTP/1.0', - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == pov.encode() - assert header_has_value('Connection', 'Keep-Alive', actual_headers) - - # Make another, streamed request on the same connection. - if set_cl: - # When a Content-Length is provided, the content should - # stream without closing the connection. - status_line, actual_headers, actual_resp_body = test_client.get( - '/stream?set_cl=Yes', http_conn=http_connection, - headers=[('Connection', 'Keep-Alive')], - protocol='HTTP/1.0', - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == b'0123456789' - - assert header_exists('Content-Length', actual_headers) - assert header_has_value('Connection', 'Keep-Alive', actual_headers) - assert not header_exists('Transfer-Encoding', actual_headers) - else: - # When a Content-Length is not provided, - # the server should close the connection. - status_line, actual_headers, actual_resp_body = test_client.get( - '/stream', http_conn=http_connection, - headers=[('Connection', 'Keep-Alive')], - protocol='HTTP/1.0', - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == b'0123456789' - - assert not header_exists('Content-Length', actual_headers) - assert not header_has_value('Connection', 'Keep-Alive', actual_headers) - assert not header_exists('Transfer-Encoding', actual_headers) - - # Make another request on the same connection, which should error. - with pytest.raises(http.client.NotConnected): - test_client.get( - '/pov', http_conn=http_connection, - protocol='HTTP/1.0', - ) - - test_client.server_instance.protocol = original_server_protocol - - # Prevent the resource warnings: - http_connection.close() - - -@pytest.mark.parametrize( - 'http_server_protocol', - ( - 'HTTP/1.0', - pytest.param( - 'HTTP/1.1', - marks=pytest.mark.xfail( - IS_PYPY and IS_CI, - reason='Fails under PyPy in CI for unknown reason', - strict=False, - ), - ), - ), -) -def test_keepalive(test_client, http_server_protocol): - """Test Keep-Alive enabled connections.""" - original_server_protocol = test_client.server_instance.protocol - test_client.server_instance.protocol = http_server_protocol - - http_client_protocol = 'HTTP/1.0' - - # Initialize a persistent HTTP connection - http_connection = test_client.get_connection() - http_connection.auto_open = False - http_connection.connect() - - # Test a normal HTTP/1.0 request. - status_line, actual_headers, actual_resp_body = test_client.get( - '/page2', - protocol=http_client_protocol, - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == pov.encode() - assert not header_exists('Connection', actual_headers) - - # Test a keep-alive HTTP/1.0 request. - - status_line, actual_headers, actual_resp_body = test_client.get( - '/page3', headers=[('Connection', 'Keep-Alive')], - http_conn=http_connection, protocol=http_client_protocol, - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == pov.encode() - assert header_has_value('Connection', 'Keep-Alive', actual_headers) - assert header_has_value( - 'Keep-Alive', - 'timeout={test_client.server_instance.timeout}'.format(**locals()), - actual_headers, - ) - - # Remove the keep-alive header again. - status_line, actual_headers, actual_resp_body = test_client.get( - '/page3', http_conn=http_connection, - protocol=http_client_protocol, - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == pov.encode() - assert not header_exists('Connection', actual_headers) - assert not header_exists('Keep-Alive', actual_headers) - - test_client.server_instance.protocol = original_server_protocol - - # Prevent the resource warnings: - http_connection.close() - - -def test_keepalive_conn_management(test_client): - """Test management of Keep-Alive connections.""" - test_client.server_instance.timeout = 2 - - def connection(): - # Initialize a persistent HTTP connection - http_connection = test_client.get_connection() - http_connection.auto_open = False - http_connection.connect() - return http_connection - - def request(conn, keepalive=True): - status_line, actual_headers, actual_resp_body = test_client.get( - '/page3', headers=[('Connection', 'Keep-Alive')], - http_conn=conn, protocol='HTTP/1.0', - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == pov.encode() - if keepalive: - assert header_has_value('Connection', 'Keep-Alive', actual_headers) - assert header_has_value( - 'Keep-Alive', - 'timeout={test_client.server_instance.timeout}'. - format(**locals()), - actual_headers, - ) - else: - assert not header_exists('Connection', actual_headers) - assert not header_exists('Keep-Alive', actual_headers) - - def check_server_idle_conn_count(count, timeout=1.0): - deadline = time.time() + timeout - while True: - n = test_client.server_instance._connections._num_connections - if n == count: - return - assert time.time() <= deadline, ( - 'idle conn count mismatch, wanted {count}, got {n}'. - format(**locals()), - ) - - disconnect_errors = ( - http.client.BadStatusLine, - http.client.CannotSendRequest, - http.client.NotConnected, - ) - - # Make a new connection. - c1 = connection() - request(c1) - check_server_idle_conn_count(1) - - # Make a second one. - c2 = connection() - request(c2) - check_server_idle_conn_count(2) - - # Reusing the first connection should still work. - request(c1) - check_server_idle_conn_count(2) - - # Creating a new connection should still work, but we should - # have run out of available connections to keep alive, so the - # server should tell us to close. - c3 = connection() - request(c3, keepalive=False) - check_server_idle_conn_count(2) - - # Show that the third connection was closed. - with pytest.raises(disconnect_errors): - request(c3) - check_server_idle_conn_count(2) - - # Wait for some of our timeout. - time.sleep(1.2) - - # Refresh the second connection. - request(c2) - check_server_idle_conn_count(2) - - # Wait for the remainder of our timeout, plus one tick. - time.sleep(1.2) - check_server_idle_conn_count(1) - - # First connection should now be expired. - with pytest.raises(disconnect_errors): - request(c1) - check_server_idle_conn_count(1) - - # But the second one should still be valid. - request(c2) - check_server_idle_conn_count(1) - - # Restore original timeout. - test_client.server_instance.timeout = timeout - - # Prevent the resource warnings: - c1.close() - c2.close() - c3.close() - - -@pytest.mark.parametrize( - ('simulated_exception', 'error_number', 'exception_leaks'), - ( - pytest.param( - socket.error, errno.ECONNRESET, False, - id='socket.error(ECONNRESET)', - ), - pytest.param( - socket.error, errno.EPIPE, False, - id='socket.error(EPIPE)', - ), - pytest.param( - socket.error, errno.ENOTCONN, False, - id='simulated socket.error(ENOTCONN)', - ), - pytest.param( - None, # <-- don't raise an artificial exception - errno.ENOTCONN, False, - id='real socket.error(ENOTCONN)', - marks=pytest.mark.xfail( - IS_WINDOWS, - reason='Now reproducible this way on Windows', - ), - ), - pytest.param( - socket.error, errno.ESHUTDOWN, False, - id='socket.error(ESHUTDOWN)', - ), - pytest.param(RuntimeError, 666, True, id='RuntimeError(666)'), - pytest.param(socket.error, -1, True, id='socket.error(-1)'), - ) + ( - pytest.param( - ConnectionResetError, errno.ECONNRESET, False, - id='ConnectionResetError(ECONNRESET)', - ), - pytest.param( - BrokenPipeError, errno.EPIPE, False, - id='BrokenPipeError(EPIPE)', - ), - pytest.param( - BrokenPipeError, errno.ESHUTDOWN, False, - id='BrokenPipeError(ESHUTDOWN)', - ), - ), -) -def test_broken_connection_during_tcp_fin( - error_number, exception_leaks, - mocker, monkeypatch, - simulated_exception, test_client, -): - """Test there's no traceback on broken connection during close. - - It artificially causes :py:data:`~errno.ECONNRESET` / - :py:data:`~errno.EPIPE` / :py:data:`~errno.ESHUTDOWN` / - :py:data:`~errno.ENOTCONN` as well as unrelated :py:exc:`RuntimeError` - and :py:exc:`socket.error(-1) ` on the server socket when - :py:meth:`socket.shutdown() ` is called. It's - triggered by closing the client socket before the server had a chance - to respond. - - The expectation is that only :py:exc:`RuntimeError` and a - :py:exc:`socket.error` with an unusual error code would leak. - - With the :py:data:`None`-parameter, a real non-simulated - :py:exc:`OSError(107, 'Transport endpoint is not connected') - ` happens. - """ - exc_instance = ( - None if simulated_exception is None - else simulated_exception(error_number, 'Simulated socket error') - ) - old_close_kernel_socket = ( - test_client.server_instance. - ConnectionClass._close_kernel_socket - ) - - def _close_kernel_socket(self): - monkeypatch.setattr( # `socket.shutdown` is read-only otherwise - self, 'socket', - mocker.mock_module.Mock(wraps=self.socket), - ) - if exc_instance is not None: - monkeypatch.setattr( - self.socket, 'shutdown', - mocker.mock_module.Mock(side_effect=exc_instance), - ) - _close_kernel_socket.fin_spy = mocker.spy(self.socket, 'shutdown') - - try: - old_close_kernel_socket(self) - except simulated_exception: - _close_kernel_socket.exception_leaked = True - else: - _close_kernel_socket.exception_leaked = False - - monkeypatch.setattr( - test_client.server_instance.ConnectionClass, - '_close_kernel_socket', - _close_kernel_socket, - ) - - conn = test_client.get_connection() - conn.auto_open = False - conn.connect() - conn.send(b'GET /hello HTTP/1.1') - conn.send(('Host: %s' % conn.host).encode('ascii')) - conn.close() - - # Let the server attempt TCP shutdown: - for _ in range(10 * (2 if IS_SLOW_ENV else 1)): - time.sleep(0.1) - if hasattr(_close_kernel_socket, 'exception_leaked'): - break - - if exc_instance is not None: # simulated by us - assert _close_kernel_socket.fin_spy.spy_exception is exc_instance - else: # real - assert isinstance( - _close_kernel_socket.fin_spy.spy_exception, socket.error, - ) - assert _close_kernel_socket.fin_spy.spy_exception.errno == error_number - - assert _close_kernel_socket.exception_leaked is exception_leaks - - -@pytest.mark.parametrize( - 'timeout_before_headers', - ( - True, - False, - ), -) -def test_HTTP11_Timeout(test_client, timeout_before_headers): - """Check timeout without sending any data. - - The server will close the connection with a 408. - """ - conn = test_client.get_connection() - conn.auto_open = False - conn.connect() - - if not timeout_before_headers: - # Connect but send half the headers only. - conn.send(b'GET /hello HTTP/1.1') - conn.send(('Host: %s' % conn.host).encode('ascii')) - # else: Connect but send nothing. - - # Wait for our socket timeout - time.sleep(timeout * 2) - - # The request should have returned 408 already. - response = conn.response_class(conn.sock, method='GET') - response.begin() - assert response.status == 408 - conn.close() - - -def test_HTTP11_Timeout_after_request(test_client): - """Check timeout after at least one request has succeeded. - - The server should close the connection without 408. - """ - fail_msg = "Writing to timed out socket didn't fail as it should have: %s" - - # Make an initial request - conn = test_client.get_connection() - conn.putrequest('GET', '/timeout?t=%s' % timeout, skip_host=True) - conn.putheader('Host', conn.host) - conn.endheaders() - response = conn.response_class(conn.sock, method='GET') - response.begin() - assert response.status == 200 - actual_body = response.read() - expected_body = str(timeout).encode() - assert actual_body == expected_body - - # Make a second request on the same socket - conn._output(b'GET /hello HTTP/1.1') - conn._output(('Host: %s' % conn.host).encode('ascii')) - conn._send_output() - response = conn.response_class(conn.sock, method='GET') - response.begin() - assert response.status == 200 - actual_body = response.read() - expected_body = b'Hello, world!' - assert actual_body == expected_body - - # Wait for our socket timeout - time.sleep(timeout * 2) - - # Make another request on the same socket, which should error - conn._output(b'GET /hello HTTP/1.1') - conn._output(('Host: %s' % conn.host).encode('ascii')) - conn._send_output() - response = conn.response_class(conn.sock, method='GET') - try: - response.begin() - except (socket.error, http.client.BadStatusLine): - pass - except Exception as ex: - pytest.fail(fail_msg % ex) - else: - if response.status != 408: - pytest.fail(fail_msg % response.read()) - - conn.close() - - # Make another request on a new socket, which should work - conn = test_client.get_connection() - conn.putrequest('GET', '/pov', skip_host=True) - conn.putheader('Host', conn.host) - conn.endheaders() - response = conn.response_class(conn.sock, method='GET') - response.begin() - assert response.status == 200 - actual_body = response.read() - expected_body = pov.encode() - assert actual_body == expected_body - - # Make another request on the same socket, - # but timeout on the headers - conn.send(b'GET /hello HTTP/1.1') - # Wait for our socket timeout - time.sleep(timeout * 2) - response = conn.response_class(conn.sock, method='GET') - try: - response.begin() - except (socket.error, http.client.BadStatusLine): - pass - except Exception as ex: - pytest.fail(fail_msg % ex) - else: - if response.status != 408: - pytest.fail(fail_msg % response.read()) - - conn.close() - - # Retry the request on a new connection, which should work - conn = test_client.get_connection() - conn.putrequest('GET', '/pov', skip_host=True) - conn.putheader('Host', conn.host) - conn.endheaders() - response = conn.response_class(conn.sock, method='GET') - response.begin() - assert response.status == 200 - actual_body = response.read() - expected_body = pov.encode() - assert actual_body == expected_body - conn.close() - - -def test_HTTP11_pipelining(test_client): - """Test HTTP/1.1 pipelining. - - :py:mod:`http.client` doesn't support this directly. - """ - conn = test_client.get_connection() - - # Put request 1 - conn.putrequest('GET', '/hello', skip_host=True) - conn.putheader('Host', conn.host) - conn.endheaders() - - for trial in range(5): - # Put next request - conn._output( - ('GET /hello?%s HTTP/1.1' % trial).encode('iso-8859-1'), - ) - conn._output(('Host: %s' % conn.host).encode('ascii')) - conn._send_output() - - # Retrieve previous response - response = conn.response_class(conn.sock, method='GET') - # there is a bug in python3 regarding the buffering of - # ``conn.sock``. Until that bug get's fixed we will - # monkey patch the ``response`` instance. - # https://bugs.python.org/issue23377 - response.fp = conn.sock.makefile('rb', 0) - response.begin() - body = response.read(13) - assert response.status == 200 - assert body == b'Hello, world!' - - # Retrieve final response - response = conn.response_class(conn.sock, method='GET') - response.begin() - body = response.read() - assert response.status == 200 - assert body == b'Hello, world!' - - conn.close() - - -def test_100_Continue(test_client): - """Test 100-continue header processing.""" - conn = test_client.get_connection() - - # Try a page without an Expect request header first. - # Note that http.client's response.begin automatically ignores - # 100 Continue responses, so we must manually check for it. - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', conn.host) - conn.putheader('Content-Type', 'text/plain') - conn.putheader('Content-Length', '4') - conn.endheaders() - conn.send(b"d'oh") - response = conn.response_class(conn.sock, method='POST') - _version, status, _reason = response._read_status() - assert status != 100 - conn.close() - - # Now try a page with an Expect header... - conn.connect() - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', conn.host) - conn.putheader('Content-Type', 'text/plain') - conn.putheader('Content-Length', '17') - conn.putheader('Expect', '100-continue') - conn.endheaders() - response = conn.response_class(conn.sock, method='POST') - - # ...assert and then skip the 100 response - version, status, reason = response._read_status() - assert status == 100 - while True: - line = response.fp.readline().strip() - if line: - pytest.fail( - '100 Continue should not output any headers. Got %r' % - line, - ) - else: - break - - # ...send the body - body = b'I am a small file' - conn.send(body) - - # ...get the final response - response.begin() - status_line, _actual_headers, actual_resp_body = webtest.shb(response) - actual_status = int(status_line[:3]) - assert actual_status == 200 - expected_resp_body = ("thanks for '%s'" % body).encode() - assert actual_resp_body == expected_resp_body - conn.close() - - -@pytest.mark.parametrize( - 'max_request_body_size', - ( - 0, - 1001, - ), -) -def test_readall_or_close(test_client, max_request_body_size): - """Test a max_request_body_size of 0 (the default) and 1001.""" - old_max = test_client.server_instance.max_request_body_size - - test_client.server_instance.max_request_body_size = max_request_body_size - - conn = test_client.get_connection() - - # Get a POST page with an error - conn.putrequest('POST', '/err_before_read', skip_host=True) - conn.putheader('Host', conn.host) - conn.putheader('Content-Type', 'text/plain') - conn.putheader('Content-Length', '1000') - conn.putheader('Expect', '100-continue') - conn.endheaders() - response = conn.response_class(conn.sock, method='POST') - - # ...assert and then skip the 100 response - _version, status, _reason = response._read_status() - assert status == 100 - skip = True - while skip: - skip = response.fp.readline().strip() - - # ...send the body - conn.send(b'x' * 1000) - - # ...get the final response - response.begin() - status_line, _actual_headers, actual_resp_body = webtest.shb(response) - actual_status = int(status_line[:3]) - assert actual_status == 500 - - # Now try a working page with an Expect header... - conn._output(b'POST /upload HTTP/1.1') - conn._output(('Host: %s' % conn.host).encode('ascii')) - conn._output(b'Content-Type: text/plain') - conn._output(b'Content-Length: 17') - conn._output(b'Expect: 100-continue') - conn._send_output() - response = conn.response_class(conn.sock, method='POST') - - # ...assert and then skip the 100 response - version, status, reason = response._read_status() - assert status == 100 - skip = True - while skip: - skip = response.fp.readline().strip() - - # ...send the body - body = b'I am a small file' - conn.send(body) - - # ...get the final response - response.begin() - status_line, actual_headers, actual_resp_body = webtest.shb(response) - actual_status = int(status_line[:3]) - assert actual_status == 200 - expected_resp_body = ("thanks for '%s'" % body).encode() - assert actual_resp_body == expected_resp_body - conn.close() - - test_client.server_instance.max_request_body_size = old_max - - -def test_No_Message_Body(test_client): - """Test HTTP queries with an empty response body.""" - # Initialize a persistent HTTP connection - http_connection = test_client.get_connection() - http_connection.auto_open = False - http_connection.connect() - - # Make the first request and assert there's no "Connection: close". - status_line, actual_headers, actual_resp_body = test_client.get( - '/pov', http_conn=http_connection, - ) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - assert actual_resp_body == pov.encode() - assert not header_exists('Connection', actual_headers) - - # Make a 204 request on the same connection. - status_line, actual_headers, actual_resp_body = test_client.get( - '/custom/204', http_conn=http_connection, - ) - actual_status = int(status_line[:3]) - assert actual_status == 204 - assert not header_exists('Content-Length', actual_headers) - assert actual_resp_body == b'' - assert not header_exists('Connection', actual_headers) - - # Make a 304 request on the same connection. - status_line, actual_headers, actual_resp_body = test_client.get( - '/custom/304', http_conn=http_connection, - ) - actual_status = int(status_line[:3]) - assert actual_status == 304 - assert not header_exists('Content-Length', actual_headers) - assert actual_resp_body == b'' - assert not header_exists('Connection', actual_headers) - - # Prevent the resource warnings: - http_connection.close() - - -@pytest.mark.xfail( - reason=unwrap( - trim(""" - Headers from earlier request leak into the request - line for a subsequent request, resulting in 400 - instead of 413. See cherrypy/cheroot#69 for details. - """), - ), -) -def test_Chunked_Encoding(test_client): - """Test HTTP uploads with chunked transfer-encoding.""" - # Initialize a persistent HTTP connection - conn = test_client.get_connection() - - # Try a normal chunked request (with extensions) - body = ( - b'8;key=value\r\nxx\r\nxxxx\r\n5\r\nyyyyy\r\n0\r\n' - b'Content-Type: application/json\r\n' - b'\r\n' - ) - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', conn.host) - conn.putheader('Transfer-Encoding', 'chunked') - conn.putheader('Trailer', 'Content-Type') - # Note that this is somewhat malformed: - # we shouldn't be sending Content-Length. - # RFC 2616 says the server should ignore it. - conn.putheader('Content-Length', '3') - conn.endheaders() - conn.send(body) - response = conn.getresponse() - status_line, _actual_headers, actual_resp_body = webtest.shb(response) - actual_status = int(status_line[:3]) - assert actual_status == 200 - assert status_line[4:] == 'OK' - expected_resp_body = ("thanks for '%s'" % b'xx\r\nxxxxyyyyy').encode() - assert actual_resp_body == expected_resp_body - - # Try a chunked request that exceeds server.max_request_body_size. - # Note that the delimiters and trailer are included. - body = b'\r\n'.join((b'3e3', b'x' * 995, b'0', b'', b'')) - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', conn.host) - conn.putheader('Transfer-Encoding', 'chunked') - conn.putheader('Content-Type', 'text/plain') - # Chunked requests don't need a content-length - # conn.putheader("Content-Length", len(body)) - conn.endheaders() - conn.send(body) - response = conn.getresponse() - status_line, actual_headers, actual_resp_body = webtest.shb(response) - actual_status = int(status_line[:3]) - assert actual_status == 413 - conn.close() - - -def test_Content_Length_in(test_client): - """Try a non-chunked request where Content-Length exceeds limit. - - (server.max_request_body_size). - Assert error before body send. - """ - # Initialize a persistent HTTP connection - conn = test_client.get_connection() - - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', conn.host) - conn.putheader('Content-Type', 'text/plain') - conn.putheader('Content-Length', '9999') - conn.endheaders() - response = conn.getresponse() - status_line, _actual_headers, actual_resp_body = webtest.shb(response) - actual_status = int(status_line[:3]) - assert actual_status == 413 - expected_resp_body = ( - b'The entity sent with the request exceeds ' - b'the maximum allowed bytes.' - ) - assert actual_resp_body == expected_resp_body - conn.close() - - -def test_Content_Length_not_int(test_client): - """Test that malicious Content-Length header returns 400.""" - status_line, _actual_headers, actual_resp_body = test_client.post( - '/upload', - headers=[ - ('Content-Type', 'text/plain'), - ('Content-Length', 'not-an-integer'), - ], - ) - actual_status = int(status_line[:3]) - - assert actual_status == 400 - assert actual_resp_body == b'Malformed Content-Length Header.' - - -@pytest.mark.parametrize( - ('uri', 'expected_resp_status', 'expected_resp_body'), - ( - ( - '/wrong_cl_buffered', 500, - ( - b'The requested resource returned more bytes than the ' - b'declared Content-Length.' - ), - ), - ('/wrong_cl_unbuffered', 200, b'I too'), - ), -) -def test_Content_Length_out( - test_client, - uri, expected_resp_status, expected_resp_body, -): - """Test response with Content-Length less than the response body. - - (non-chunked response) - """ - conn = test_client.get_connection() - conn.putrequest('GET', uri, skip_host=True) - conn.putheader('Host', conn.host) - conn.endheaders() - - response = conn.getresponse() - status_line, _actual_headers, actual_resp_body = webtest.shb(response) - actual_status = int(status_line[:3]) - - assert actual_status == expected_resp_status - assert actual_resp_body == expected_resp_body - - conn.close() - - # the server logs the exception that we had verified from the - # client perspective. Tell the error_log verification that - # it can ignore that message. - test_client.server_instance.error_log.ignored_msgs.extend(( - # Python 3.7+: - "ValueError('Response body exceeds the declared Content-Length.')", - # Python 2.7-3.6 (macOS?): - "ValueError('Response body exceeds the declared Content-Length.',)", - )) - - -@pytest.mark.xfail( - reason='Sometimes this test fails due to low timeout. ' - 'Ref: https://github.com/cherrypy/cherrypy/issues/598', -) -def test_598(test_client): - """Test serving large file with a read timeout in place.""" - # Initialize a persistent HTTP connection - conn = test_client.get_connection() - remote_data_conn = urllib.request.urlopen( - '%s://%s:%s/one_megabyte_of_a' - % ('http', conn.host, conn.port), - ) - buf = remote_data_conn.read(512) - time.sleep(timeout * 0.6) - remaining = (1024 * 1024) - 512 - while remaining: - data = remote_data_conn.read(remaining) - if not data: - break - buf += data - remaining -= len(data) - - assert len(buf) == 1024 * 1024 - assert buf == b'a' * 1024 * 1024 - assert remaining == 0 - remote_data_conn.close() - - -@pytest.mark.parametrize( - 'invalid_terminator', - ( - b'\n\n', - b'\r\n\n', - ), -) -def test_No_CRLF(test_client, invalid_terminator): - """Test HTTP queries with no valid CRLF terminators.""" - # Initialize a persistent HTTP connection - conn = test_client.get_connection() - - # (b'%s' % b'') is not supported in Python 3.4, so just use bytes.join() - conn.send(b''.join((b'GET /hello HTTP/1.1', invalid_terminator))) - response = conn.response_class(conn.sock, method='GET') - response.begin() - actual_resp_body = response.read() - expected_resp_body = b'HTTP requires CRLF terminators' - assert actual_resp_body == expected_resp_body - conn.close() - - -class FaultySelect: - """Mock class to insert errors in the selector.select method.""" - - def __init__(self, original_select): - """Initilize helper class to wrap the selector.select method.""" - self.original_select = original_select - self.request_served = False - self.os_error_triggered = False - - def __call__(self, timeout): - """Intercept the calls to selector.select.""" - if self.request_served: - self.os_error_triggered = True - raise OSError('Error while selecting the client socket.') - - return self.original_select(timeout) - - -class FaultyGetMap: - """Mock class to insert errors in the selector.get_map method.""" - - def __init__(self, original_get_map): - """Initilize helper class to wrap the selector.get_map method.""" - self.original_get_map = original_get_map - self.sabotage_conn = False - self.conn_closed = False - - def __call__(self): - """Intercept the calls to selector.get_map.""" - sabotage_targets = ( - conn for _, (_, _, _, conn) in self.original_get_map().items() - if isinstance(conn, cheroot.server.HTTPConnection) - ) if self.sabotage_conn and not self.conn_closed else () - - for conn in sabotage_targets: - # close the socket to cause OSError - conn.close() - self.conn_closed = True - - return self.original_get_map() - - -def test_invalid_selected_connection(test_client, monkeypatch): - """Test the error handling segment of HTTP connection selection. - - See :py:meth:`cheroot.connections.ConnectionManager.get_conn`. - """ - # patch the select method - faux_select = FaultySelect( - test_client.server_instance._connections._selector.select, - ) - monkeypatch.setattr( - test_client.server_instance._connections._selector, - 'select', - faux_select, - ) - - # patch the get_map method - faux_get_map = FaultyGetMap( - test_client.server_instance._connections._selector._selector.get_map, - ) - - monkeypatch.setattr( - test_client.server_instance._connections._selector._selector, - 'get_map', - faux_get_map, - ) - - # request a page with connection keep-alive to make sure - # we'll have a connection to be modified. - resp_status, _resp_headers, _resp_body = test_client.request( - '/page1', headers=[('Connection', 'Keep-Alive')], - ) - - assert resp_status == '200 OK' - # trigger the internal errors - faux_get_map.sabotage_conn = faux_select.request_served = True - # give time to make sure the error gets handled - time.sleep(test_client.server_instance.expiration_interval * 2) - assert faux_select.os_error_triggered - assert faux_get_map.conn_closed diff --git a/lib/cheroot/test/test_core.py b/lib/cheroot/test/test_core.py deleted file mode 100644 index 0203c2f7..00000000 --- a/lib/cheroot/test/test_core.py +++ /dev/null @@ -1,459 +0,0 @@ -"""Tests for managing HTTP issues (malformed requests, etc).""" - -import errno -import socket -import urllib.parse # noqa: WPS301 - -import pytest - -from cheroot.test import helper - - -HTTP_BAD_REQUEST = 400 -HTTP_LENGTH_REQUIRED = 411 -HTTP_NOT_FOUND = 404 -HTTP_REQUEST_ENTITY_TOO_LARGE = 413 -HTTP_OK = 200 -HTTP_VERSION_NOT_SUPPORTED = 505 - - -class HelloController(helper.Controller): - """Controller for serving WSGI apps.""" - - def hello(req, resp): - """Render Hello world.""" - return 'Hello world!' - - def body_required(req, resp): - """Render Hello world or set 411.""" - if req.environ.get('Content-Length', None) is None: - resp.status = '411 Length Required' - return - return 'Hello world!' - - def query_string(req, resp): - """Render QUERY_STRING value.""" - return req.environ.get('QUERY_STRING', '') - - def asterisk(req, resp): - """Render request method value.""" - # pylint: disable=possibly-unused-variable - method = req.environ.get('REQUEST_METHOD', 'NO METHOD FOUND') - tmpl = 'Got asterisk URI path with {method} method' - return tmpl.format(**locals()) - - def _munge(string): - """Encode PATH_INFO correctly depending on Python version. - - WSGI 1.0 is a mess around unicode. Create endpoints - that match the PATH_INFO that it produces. - """ - return string.encode('utf-8').decode('latin-1') - - handlers = { - '/hello': hello, - '/no_body': hello, - '/body_required': body_required, - '/query_string': query_string, - # FIXME: Unignore the pylint rules in pylint >= 2.15.4. - # Refs: - # * https://github.com/PyCQA/pylint/issues/6592 - # * https://github.com/PyCQA/pylint/pull/7395 - # pylint: disable-next=too-many-function-args - _munge('/привіт'): hello, - # pylint: disable-next=too-many-function-args - _munge('/Юххууу'): hello, - '/\xa0Ðblah key 0 900 4 data': hello, - '/*': asterisk, - } - - -def _get_http_response(connection, method='GET'): - c = connection - kwargs = {'strict': c.strict} if hasattr(c, 'strict') else {} - # Python 3.2 removed the 'strict' feature, saying: - # "http.client now always assumes HTTP/1.x compliant servers." - return c.response_class(c.sock, method=method, **kwargs) - - -@pytest.fixture -def testing_server(wsgi_server_client): - """Attach a WSGI app to the given server and preconfigure it.""" - wsgi_server = wsgi_server_client.server_instance - wsgi_server.wsgi_app = HelloController() - wsgi_server.max_request_body_size = 30000000 - wsgi_server.server_client = wsgi_server_client - return wsgi_server - - -@pytest.fixture -def test_client(testing_server): - """Get and return a test client out of the given server.""" - return testing_server.server_client - - -@pytest.fixture -def testing_server_with_defaults(wsgi_server_client): - """Attach a WSGI app to the given server and preconfigure it.""" - wsgi_server = wsgi_server_client.server_instance - wsgi_server.wsgi_app = HelloController() - wsgi_server.server_client = wsgi_server_client - return wsgi_server - - -@pytest.fixture -def test_client_with_defaults(testing_server_with_defaults): - """Get and return a test client out of the given server.""" - return testing_server_with_defaults.server_client - - -def test_http_connect_request(test_client): - """Check that CONNECT query results in Method Not Allowed status.""" - status_line = test_client.connect('/anything')[0] - actual_status = int(status_line[:3]) - assert actual_status == 405 - - -def test_normal_request(test_client): - """Check that normal GET query succeeds.""" - status_line, _, actual_resp_body = test_client.get('/hello') - actual_status = int(status_line[:3]) - assert actual_status == HTTP_OK - assert actual_resp_body == b'Hello world!' - - -def test_query_string_request(test_client): - """Check that GET param is parsed well.""" - status_line, _, actual_resp_body = test_client.get( - '/query_string?test=True', - ) - actual_status = int(status_line[:3]) - assert actual_status == HTTP_OK - assert actual_resp_body == b'test=True' - - -@pytest.mark.parametrize( - 'uri', - ( - '/hello', # plain - '/query_string?test=True', # query - '/{0}?{1}={2}'.format( # quoted unicode - *map(urllib.parse.quote, ('Юххууу', 'ї', 'йо')) - ), - ), -) -def test_parse_acceptable_uri(test_client, uri): - """Check that server responds with OK to valid GET queries.""" - status_line = test_client.get(uri)[0] - actual_status = int(status_line[:3]) - assert actual_status == HTTP_OK - - -def test_parse_uri_unsafe_uri(test_client): - """Test that malicious URI does not allow HTTP injection. - - This effectively checks that sending GET request with URL - - /%A0%D0blah%20key%200%20900%204%20data - - is not converted into - - GET / - blah key 0 900 4 data - HTTP/1.1 - - which would be a security issue otherwise. - """ - c = test_client.get_connection() - resource = '/\xa0Ðblah key 0 900 4 data'.encode('latin-1') - quoted = urllib.parse.quote(resource) - assert quoted == '/%A0%D0blah%20key%200%20900%204%20data' - request = 'GET {quoted} HTTP/1.1'.format(**locals()) - c._output(request.encode('utf-8')) - c._send_output() - response = _get_http_response(c, method='GET') - response.begin() - assert response.status == HTTP_OK - assert response.read(12) == b'Hello world!' - c.close() - - -def test_parse_uri_invalid_uri(test_client): - """Check that server responds with Bad Request to invalid GET queries. - - Invalid request line test case: it should only contain US-ASCII. - """ - c = test_client.get_connection() - c._output(u'GET /йопта! HTTP/1.1'.encode('utf-8')) - c._send_output() - response = _get_http_response(c, method='GET') - response.begin() - assert response.status == HTTP_BAD_REQUEST - assert response.read(21) == b'Malformed Request-URI' - c.close() - - -@pytest.mark.parametrize( - 'uri', - ( - 'hello', # ascii - 'привіт', # non-ascii - ), -) -def test_parse_no_leading_slash_invalid(test_client, uri): - """Check that server responds with Bad Request to invalid GET queries. - - Invalid request line test case: it should have leading slash (be absolute). - """ - status_line, _, actual_resp_body = test_client.get( - urllib.parse.quote(uri), - ) - actual_status = int(status_line[:3]) - assert actual_status == HTTP_BAD_REQUEST - assert b'starting with a slash' in actual_resp_body - - -def test_parse_uri_absolute_uri(test_client): - """Check that server responds with Bad Request to Absolute URI. - - Only proxy servers should allow this. - """ - status_line, _, actual_resp_body = test_client.get('http://google.com/') - actual_status = int(status_line[:3]) - assert actual_status == HTTP_BAD_REQUEST - expected_body = b'Absolute URI not allowed if server is not a proxy.' - assert actual_resp_body == expected_body - - -def test_parse_uri_asterisk_uri(test_client): - """Check that server responds with OK to OPTIONS with "*" Absolute URI.""" - status_line, _, actual_resp_body = test_client.options('*') - actual_status = int(status_line[:3]) - assert actual_status == HTTP_OK - expected_body = b'Got asterisk URI path with OPTIONS method' - assert actual_resp_body == expected_body - - -def test_parse_uri_fragment_uri(test_client): - """Check that server responds with Bad Request to URI with fragment.""" - status_line, _, actual_resp_body = test_client.get( - '/hello?test=something#fake', - ) - actual_status = int(status_line[:3]) - assert actual_status == HTTP_BAD_REQUEST - expected_body = b'Illegal #fragment in Request-URI.' - assert actual_resp_body == expected_body - - -def test_no_content_length(test_client): - """Test POST query with an empty body being successful.""" - # "The presence of a message-body in a request is signaled by the - # inclusion of a Content-Length or Transfer-Encoding header field in - # the request's message-headers." - # - # Send a message with neither header and no body. - c = test_client.get_connection() - c.request('POST', '/no_body') - response = c.getresponse() - actual_resp_body = response.read() - actual_status = response.status - assert actual_status == HTTP_OK - assert actual_resp_body == b'Hello world!' - - c.close() # deal with the resource warning - - -def test_content_length_required(test_client): - """Test POST query with body failing because of missing Content-Length.""" - # Now send a message that has no Content-Length, but does send a body. - # Verify that CP times out the socket and responds - # with 411 Length Required. - - c = test_client.get_connection() - c.request('POST', '/body_required') - response = c.getresponse() - response.read() - - actual_status = response.status - assert actual_status == HTTP_LENGTH_REQUIRED - - c.close() # deal with the resource warning - - -@pytest.mark.xfail( - reason='https://github.com/cherrypy/cheroot/issues/106', - strict=False, # sometimes it passes -) -def test_large_request(test_client_with_defaults): - """Test GET query with maliciously large Content-Length.""" - # If the server's max_request_body_size is not set (i.e. is set to 0) - # then this will result in an `OverflowError: Python int too large to - # convert to C ssize_t` in the server. - # We expect that this should instead return that the request is too - # large. - c = test_client_with_defaults.get_connection() - c.putrequest('GET', '/hello') - c.putheader('Content-Length', str(2**64)) - c.endheaders() - - response = c.getresponse() - actual_status = response.status - - assert actual_status == HTTP_REQUEST_ENTITY_TOO_LARGE - - -@pytest.mark.parametrize( - ('request_line', 'status_code', 'expected_body'), - ( - ( - b'GET /', # missing proto - HTTP_BAD_REQUEST, b'Malformed Request-Line', - ), - ( - b'GET / HTTPS/1.1', # invalid proto - HTTP_BAD_REQUEST, b'Malformed Request-Line: bad protocol', - ), - ( - b'GET / HTTP/1', # invalid version - HTTP_BAD_REQUEST, b'Malformed Request-Line: bad version', - ), - ( - b'GET / HTTP/2.15', # invalid ver - HTTP_VERSION_NOT_SUPPORTED, b'Cannot fulfill request', - ), - ), -) -def test_malformed_request_line( - test_client, request_line, - status_code, expected_body, -): - """Test missing or invalid HTTP version in Request-Line.""" - c = test_client.get_connection() - c._output(request_line) - c._send_output() - response = _get_http_response(c, method='GET') - response.begin() - assert response.status == status_code - assert response.read(len(expected_body)) == expected_body - c.close() - - -def test_malformed_http_method(test_client): - """Test non-uppercase HTTP method.""" - c = test_client.get_connection() - c.putrequest('GeT', '/malformed_method_case') - c.putheader('Content-Type', 'text/plain') - c.endheaders() - - response = c.getresponse() - actual_status = response.status - assert actual_status == HTTP_BAD_REQUEST - actual_resp_body = response.read(21) - assert actual_resp_body == b'Malformed method name' - - c.close() # deal with the resource warning - - -def test_malformed_header(test_client): - """Check that broken HTTP header results in Bad Request.""" - c = test_client.get_connection() - c.putrequest('GET', '/') - c.putheader('Content-Type', 'text/plain') - # See https://www.bitbucket.org/cherrypy/cherrypy/issue/941 - c._output(b'Re, 1.2.3.4#015#012') - c.endheaders() - - response = c.getresponse() - actual_status = response.status - assert actual_status == HTTP_BAD_REQUEST - actual_resp_body = response.read(20) - assert actual_resp_body == b'Illegal header line.' - - c.close() # deal with the resource warning - - -def test_request_line_split_issue_1220(test_client): - """Check that HTTP request line of exactly 256 chars length is OK.""" - Request_URI = ( - '/hello?' - 'intervenant-entreprise-evenement_classaction=' - 'evenement-mailremerciements' - '&_path=intervenant-entreprise-evenement' - '&intervenant-entreprise-evenement_action-id=19404' - '&intervenant-entreprise-evenement_id=19404' - '&intervenant-entreprise_id=28092' - ) - assert len('GET %s HTTP/1.1\r\n' % Request_URI) == 256 - - actual_resp_body = test_client.get(Request_URI)[2] - assert actual_resp_body == b'Hello world!' - - -def test_garbage_in(test_client): - """Test that server sends an error for garbage received over TCP.""" - # Connect without SSL regardless of server.scheme - - c = test_client.get_connection() - c._output(b'gjkgjklsgjklsgjkljklsg') - c._send_output() - response = c.response_class(c.sock, method='GET') - try: - response.begin() - actual_status = response.status - assert actual_status == HTTP_BAD_REQUEST - actual_resp_body = response.read(22) - assert actual_resp_body == b'Malformed Request-Line' - c.close() - except socket.error as ex: - # "Connection reset by peer" is also acceptable. - if ex.errno != errno.ECONNRESET: - raise - - -class CloseController: - """Controller for testing the close callback.""" - - def __call__(self, environ, start_response): - """Get the req to know header sent status.""" - self.req = start_response.__self__.req - resp = CloseResponse(self.close) - start_response(resp.status, resp.headers.items()) - return resp - - def close(self): - """Close, writing hello.""" - self.req.write(b'hello') - - -class CloseResponse: - """Dummy empty response to trigger the no body status.""" - - def __init__(self, close): - """Use some defaults to ensure we have a header.""" - self.status = '200 OK' - self.headers = {'Content-Type': 'text/html'} - self.close = close - - def __getitem__(self, index): - """Ensure we don't have a body.""" - raise IndexError() - - def output(self): - """Return self to hook the close method.""" - return self - - -@pytest.fixture -def testing_server_close(wsgi_server_client): - """Attach a WSGI app to the given server and preconfigure it.""" - wsgi_server = wsgi_server_client.server_instance - wsgi_server.wsgi_app = CloseController() - wsgi_server.max_request_body_size = 30000000 - wsgi_server.server_client = wsgi_server_client - return wsgi_server - - -def test_send_header_before_closing(testing_server_close): - """Test we are actually sending the headers before calling 'close'.""" - _, _, resp_body = testing_server_close.server_client.get('/') - assert resp_body == b'hello' diff --git a/lib/cheroot/test/test_dispatch.py b/lib/cheroot/test/test_dispatch.py deleted file mode 100644 index c42014fa..00000000 --- a/lib/cheroot/test/test_dispatch.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Tests for the HTTP server.""" - -from cheroot.wsgi import PathInfoDispatcher - - -def wsgi_invoke(app, environ): - """Serve 1 request from a WSGI application.""" - response = {} - - def start_response(status, headers): - response.update({ - 'status': status, - 'headers': headers, - }) - - response['body'] = b''.join( - app(environ, start_response), - ) - - return response - - -def test_dispatch_no_script_name(): - """Dispatch despite lack of ``SCRIPT_NAME`` in environ.""" - # Bare bones WSGI hello world app (from PEP 333). - def app(environ, start_response): - start_response( - '200 OK', [ - ('Content-Type', 'text/plain; charset=utf-8'), - ], - ) - return [u'Hello, world!'.encode('utf-8')] - - # Build a dispatch table. - d = PathInfoDispatcher([ - ('/', app), - ]) - - # Dispatch a request without `SCRIPT_NAME`. - response = wsgi_invoke( - d, { - 'PATH_INFO': '/foo', - }, - ) - assert response == { - 'status': '200 OK', - 'headers': [ - ('Content-Type', 'text/plain; charset=utf-8'), - ], - 'body': b'Hello, world!', - } diff --git a/lib/cheroot/test/test_errors.py b/lib/cheroot/test/test_errors.py deleted file mode 100644 index a3eee67b..00000000 --- a/lib/cheroot/test/test_errors.py +++ /dev/null @@ -1,30 +0,0 @@ -"""Test suite for ``cheroot.errors``.""" - -import pytest - -from cheroot import errors - -from .._compat import IS_LINUX, IS_MACOS, IS_WINDOWS # noqa: WPS130 - - -@pytest.mark.parametrize( - ('err_names', 'err_nums'), - ( - (('', 'some-nonsense-name'), []), - ( - ( - 'EPROTOTYPE', 'EAGAIN', 'EWOULDBLOCK', - 'WSAEWOULDBLOCK', 'EPIPE', - ), - (91, 11, 32) if IS_LINUX else - (32, 35, 41) if IS_MACOS else - (32, 10041, 11, 10035) if IS_WINDOWS else - (), - ), - ), -) -def test_plat_specific_errors(err_names, err_nums): - """Test that ``plat_specific_errors`` gets correct error numbers list.""" - actual_err_nums = errors.plat_specific_errors(*err_names) - assert len(actual_err_nums) == len(err_nums) - assert sorted(actual_err_nums) == sorted(err_nums) diff --git a/lib/cheroot/test/test_makefile.py b/lib/cheroot/test/test_makefile.py deleted file mode 100644 index 57f6f57e..00000000 --- a/lib/cheroot/test/test_makefile.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Tests for :py:mod:`cheroot.makefile`.""" - -from cheroot import makefile - - -class MockSocket: - """A mock socket.""" - - def __init__(self): - """Initialize :py:class:`MockSocket`.""" - self.messages = [] - - def recv_into(self, buf): - """Simulate ``recv_into`` for Python 3.""" - if not self.messages: - return 0 - msg = self.messages.pop(0) - for index, byte in enumerate(msg): - buf[index] = byte - return len(msg) - - def recv(self, size): - """Simulate ``recv`` for Python 2.""" - try: - return self.messages.pop(0) - except IndexError: - return '' - - def send(self, val): - """Simulate a send.""" - return len(val) - - -def test_bytes_read(): - """Reader should capture bytes read.""" - sock = MockSocket() - sock.messages.append(b'foo') - rfile = makefile.MakeFile(sock, 'r') - rfile.read() - assert rfile.bytes_read == 3 - - -def test_bytes_written(): - """Writer should capture bytes written.""" - sock = MockSocket() - sock.messages.append(b'foo') - wfile = makefile.MakeFile(sock, 'w') - wfile.write(b'bar') - assert wfile.bytes_written == 3 diff --git a/lib/cheroot/test/test_server.py b/lib/cheroot/test/test_server.py deleted file mode 100644 index 5e0a6832..00000000 --- a/lib/cheroot/test/test_server.py +++ /dev/null @@ -1,441 +0,0 @@ -"""Tests for the HTTP server.""" - -import os -import queue -import socket -import tempfile -import threading -import uuid -import urllib.parse # noqa: WPS301 - -import pytest -import requests -import requests_unixsocket - -from pypytools.gc.custom import DefaultGc - -from .._compat import bton, ntob -from .._compat import IS_LINUX, IS_MACOS, IS_WINDOWS, SYS_PLATFORM -from ..server import IS_UID_GID_RESOLVABLE, Gateway, HTTPServer -from ..testing import ( - ANY_INTERFACE_IPV4, - ANY_INTERFACE_IPV6, - EPHEMERAL_PORT, -) - - -IS_SLOW_ENV = IS_MACOS or IS_WINDOWS - - -unix_only_sock_test = pytest.mark.skipif( - not hasattr(socket, 'AF_UNIX'), - reason='UNIX domain sockets are only available under UNIX-based OS', -) - - -non_macos_sock_test = pytest.mark.skipif( - IS_MACOS, - reason='Peercreds lookup does not work under macOS/BSD currently.', -) - - -@pytest.fixture(params=('abstract', 'file')) -def unix_sock_file(request): - """Check that bound UNIX socket address is stored in server.""" - name = 'unix_{request.param}_sock'.format(**locals()) - return request.getfixturevalue(name) - - -@pytest.fixture -def unix_abstract_sock(): - """Return an abstract UNIX socket address.""" - if not IS_LINUX: - pytest.skip( - '{os} does not support an abstract ' - 'socket namespace'.format(os=SYS_PLATFORM), - ) - return b''.join(( - b'\x00cheroot-test-socket', - ntob(str(uuid.uuid4())), - )).decode() - - -@pytest.fixture -def unix_file_sock(): - """Yield a unix file socket.""" - tmp_sock_fh, tmp_sock_fname = tempfile.mkstemp() - - yield tmp_sock_fname - - os.close(tmp_sock_fh) - os.unlink(tmp_sock_fname) - - -def test_prepare_makes_server_ready(): - """Check that prepare() makes the server ready, and stop() clears it.""" - httpserver = HTTPServer( - bind_addr=(ANY_INTERFACE_IPV4, EPHEMERAL_PORT), - gateway=Gateway, - ) - - assert not httpserver.ready - assert not httpserver.requests._threads - - httpserver.prepare() - - assert httpserver.ready - assert httpserver.requests._threads - for thr in httpserver.requests._threads: - assert thr.ready - - httpserver.stop() - - assert not httpserver.requests._threads - assert not httpserver.ready - - -def test_stop_interrupts_serve(): - """Check that stop() interrupts running of serve().""" - httpserver = HTTPServer( - bind_addr=(ANY_INTERFACE_IPV4, EPHEMERAL_PORT), - gateway=Gateway, - ) - - httpserver.prepare() - serve_thread = threading.Thread(target=httpserver.serve) - serve_thread.start() - - serve_thread.join(0.5) - assert serve_thread.is_alive() - - httpserver.stop() - - serve_thread.join(0.5) - assert not serve_thread.is_alive() - - -@pytest.mark.parametrize( - 'exc_cls', - ( - IOError, - KeyboardInterrupt, - OSError, - RuntimeError, - ), -) -def test_server_interrupt(exc_cls): - """Check that assigning interrupt stops the server.""" - interrupt_msg = 'should catch {uuid!s}'.format(uuid=uuid.uuid4()) - raise_marker_sentinel = object() - - httpserver = HTTPServer( - bind_addr=(ANY_INTERFACE_IPV4, EPHEMERAL_PORT), - gateway=Gateway, - ) - - result_q = queue.Queue() - - def serve_thread(): - # ensure we catch the exception on the serve() thread - try: - httpserver.serve() - except exc_cls as e: - if str(e) == interrupt_msg: - result_q.put(raise_marker_sentinel) - - httpserver.prepare() - serve_thread = threading.Thread(target=serve_thread) - serve_thread.start() - - serve_thread.join(0.5) - assert serve_thread.is_alive() - - # this exception is raised on the serve() thread, - # not in the calling context. - httpserver.interrupt = exc_cls(interrupt_msg) - - serve_thread.join(0.5) - assert not serve_thread.is_alive() - assert result_q.get_nowait() is raise_marker_sentinel - - -def test_serving_is_false_and_stop_returns_after_ctrlc(): - """Check that stop() interrupts running of serve().""" - httpserver = HTTPServer( - bind_addr=(ANY_INTERFACE_IPV4, EPHEMERAL_PORT), - gateway=Gateway, - ) - - httpserver.prepare() - - # Simulate a Ctrl-C on the first call to `run`. - def raise_keyboard_interrupt(*args, **kwargs): - raise KeyboardInterrupt() - - httpserver._connections._selector.select = raise_keyboard_interrupt - - serve_thread = threading.Thread(target=httpserver.serve) - serve_thread.start() - - # The thread should exit right away due to the interrupt. - serve_thread.join( - httpserver.expiration_interval * (4 if IS_SLOW_ENV else 2), - ) - assert not serve_thread.is_alive() - - assert not httpserver._connections._serving - httpserver.stop() - - -@pytest.mark.parametrize( - 'ip_addr', - ( - ANY_INTERFACE_IPV4, - ANY_INTERFACE_IPV6, - ), -) -def test_bind_addr_inet(http_server, ip_addr): - """Check that bound IP address is stored in server.""" - httpserver = http_server.send((ip_addr, EPHEMERAL_PORT)) - - assert httpserver.bind_addr[0] == ip_addr - assert httpserver.bind_addr[1] != EPHEMERAL_PORT - - -@unix_only_sock_test -def test_bind_addr_unix(http_server, unix_sock_file): - """Check that bound UNIX socket address is stored in server.""" - httpserver = http_server.send(unix_sock_file) - - assert httpserver.bind_addr == unix_sock_file - - -@unix_only_sock_test -def test_bind_addr_unix_abstract(http_server, unix_abstract_sock): - """Check that bound UNIX abstract socket address is stored in server.""" - httpserver = http_server.send(unix_abstract_sock) - - assert httpserver.bind_addr == unix_abstract_sock - - -PEERCRED_IDS_URI = '/peer_creds/ids' -PEERCRED_TEXTS_URI = '/peer_creds/texts' - - -class _TestGateway(Gateway): - def respond(self): - req = self.req - conn = req.conn - req_uri = bton(req.uri) - if req_uri == PEERCRED_IDS_URI: - peer_creds = conn.peer_pid, conn.peer_uid, conn.peer_gid - self.send_payload('|'.join(map(str, peer_creds))) - return - elif req_uri == PEERCRED_TEXTS_URI: - self.send_payload('!'.join((conn.peer_user, conn.peer_group))) - return - return super(_TestGateway, self).respond() - - def send_payload(self, payload): - req = self.req - req.status = b'200 OK' - req.ensure_headers_sent() - req.write(ntob(payload)) - - -@pytest.fixture -def peercreds_enabled_server(http_server, unix_sock_file): - """Construct a test server with ``peercreds_enabled``.""" - httpserver = http_server.send(unix_sock_file) - httpserver.gateway = _TestGateway - httpserver.peercreds_enabled = True - return httpserver - - -@unix_only_sock_test -@non_macos_sock_test -def test_peercreds_unix_sock(http_request_timeout, peercreds_enabled_server): - """Check that ``PEERCRED`` lookup works when enabled.""" - httpserver = peercreds_enabled_server - bind_addr = httpserver.bind_addr - - if isinstance(bind_addr, bytes): - bind_addr = bind_addr.decode() - - # pylint: disable=possibly-unused-variable - quoted = urllib.parse.quote(bind_addr, safe='') - unix_base_uri = 'http+unix://{quoted}'.format(**locals()) - - expected_peercreds = os.getpid(), os.getuid(), os.getgid() - expected_peercreds = '|'.join(map(str, expected_peercreds)) - - with requests_unixsocket.monkeypatch(): - peercreds_resp = requests.get( - unix_base_uri + PEERCRED_IDS_URI, - timeout=http_request_timeout, - ) - peercreds_resp.raise_for_status() - assert peercreds_resp.text == expected_peercreds - - peercreds_text_resp = requests.get( - unix_base_uri + PEERCRED_TEXTS_URI, - timeout=http_request_timeout, - ) - assert peercreds_text_resp.status_code == 500 - - -@pytest.mark.skipif( - not IS_UID_GID_RESOLVABLE, - reason='Modules `grp` and `pwd` are not available ' - 'under the current platform', -) -@unix_only_sock_test -@non_macos_sock_test -def test_peercreds_unix_sock_with_lookup( - http_request_timeout, - peercreds_enabled_server, -): - """Check that ``PEERCRED`` resolution works when enabled.""" - httpserver = peercreds_enabled_server - httpserver.peercreds_resolve_enabled = True - - bind_addr = httpserver.bind_addr - - if isinstance(bind_addr, bytes): - bind_addr = bind_addr.decode() - - # pylint: disable=possibly-unused-variable - quoted = urllib.parse.quote(bind_addr, safe='') - unix_base_uri = 'http+unix://{quoted}'.format(**locals()) - - import grp - import pwd - expected_textcreds = ( - pwd.getpwuid(os.getuid()).pw_name, - grp.getgrgid(os.getgid()).gr_name, - ) - expected_textcreds = '!'.join(map(str, expected_textcreds)) - with requests_unixsocket.monkeypatch(): - peercreds_text_resp = requests.get( - unix_base_uri + PEERCRED_TEXTS_URI, - timeout=http_request_timeout, - ) - peercreds_text_resp.raise_for_status() - assert peercreds_text_resp.text == expected_textcreds - - -@pytest.mark.skipif( - IS_WINDOWS, - reason='This regression test is for a Linux bug, ' - 'and the resource module is not available on Windows', -) -@pytest.mark.parametrize( - 'resource_limit', - ( - 1024, - 2048, - ), - indirect=('resource_limit',), -) -@pytest.mark.usefixtures('many_open_sockets') -def test_high_number_of_file_descriptors(native_server_client, resource_limit): - """Test the server does not crash with a high file-descriptor value. - - This test shouldn't cause a server crash when trying to access - file-descriptor higher than 1024. - - The earlier implementation used to rely on ``select()`` syscall that - doesn't support file descriptors with numbers higher than 1024. - """ - # We want to force the server to use a file-descriptor with - # a number above resource_limit - - # Patch the method that processes - _old_process_conn = native_server_client.server_instance.process_conn - - def native_process_conn(conn): - native_process_conn.filenos.add(conn.socket.fileno()) - return _old_process_conn(conn) - native_process_conn.filenos = set() - native_server_client.server_instance.process_conn = native_process_conn - - # Trigger a crash if select() is used in the implementation - native_server_client.connect('/') - - # Ensure that at least one connection got accepted, otherwise the - # follow-up check wouldn't make sense - assert len(native_process_conn.filenos) > 0 - - # Check at least one of the sockets created are above the target number - assert any(fn >= resource_limit for fn in native_process_conn.filenos) - - -ISSUE511 = IS_MACOS - - -if not IS_WINDOWS and not ISSUE511: - test_high_number_of_file_descriptors = pytest.mark.forked( - test_high_number_of_file_descriptors, - ) - - -@pytest.fixture -def _garbage_bin(): - """Disable garbage collection when this fixture is in use.""" - with DefaultGc().nogc(): - yield - - -@pytest.fixture -def resource_limit(request): - """Set the resource limit two times bigger then requested.""" - resource = pytest.importorskip( - 'resource', - reason='The "resource" module is Unix-specific', - ) - - # Get current resource limits to restore them later - soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE) - - # We have to increase the nofile limit above 1024 - # Otherwise we see a 'Too many files open' error, instead of - # an error due to the file descriptor number being too high - resource.setrlimit( - resource.RLIMIT_NOFILE, - (request.param * 2, hard_limit), - ) - - try: # noqa: WPS501 - yield request.param - finally: - # Reset the resource limit back to the original soft limit - resource.setrlimit(resource.RLIMIT_NOFILE, (soft_limit, hard_limit)) - - -@pytest.fixture -def many_open_sockets(request, resource_limit): - """Allocate a lot of file descriptors by opening dummy sockets.""" - # NOTE: `@pytest.mark.usefixtures` doesn't work on fixtures which - # NOTE: forces us to invoke this one dynamically to avoid having an - # NOTE: unused argument. - request.getfixturevalue('_garbage_bin') - - # Hoard a lot of file descriptors by opening and storing a lot of sockets - test_sockets = [] - # Open a lot of file descriptors, so the next one the server - # opens is a high number - try: - for _ in range(resource_limit): - sock = socket.socket() - test_sockets.append(sock) - # If we reach a high enough number, we don't need to open more - if sock.fileno() >= resource_limit: - break - # Check we opened enough descriptors to reach a high number - the_highest_fileno = test_sockets[-1].fileno() - assert the_highest_fileno >= resource_limit - yield the_highest_fileno - finally: - # Close our open resources - for test_socket in test_sockets: - test_socket.close() diff --git a/lib/cheroot/test/test_ssl.py b/lib/cheroot/test/test_ssl.py deleted file mode 100644 index c55e156f..00000000 --- a/lib/cheroot/test/test_ssl.py +++ /dev/null @@ -1,714 +0,0 @@ -"""Tests for TLS support.""" - -import functools -import json -import os -import ssl -import subprocess -import sys -import threading -import time -import traceback -import http.client - -import OpenSSL.SSL -import pytest -import requests -import trustme - -from .._compat import bton, ntob, ntou -from .._compat import IS_ABOVE_OPENSSL10, IS_CI, IS_PYPY -from .._compat import IS_LINUX, IS_MACOS, IS_WINDOWS -from ..server import HTTPServer, get_ssl_adapter_class -from ..testing import ( - ANY_INTERFACE_IPV4, - ANY_INTERFACE_IPV6, - EPHEMERAL_PORT, - # get_server_client, - _get_conn_data, - _probe_ipv6_sock, -) -from ..wsgi import Gateway_10 - - -IS_GITHUB_ACTIONS_WORKFLOW = bool(os.getenv('GITHUB_WORKFLOW')) -IS_WIN2016 = ( - IS_WINDOWS - # pylint: disable=unsupported-membership-test - and b'Microsoft Windows Server 2016 Datacenter' in subprocess.check_output( - ('systeminfo',), - ) -) -IS_LIBRESSL_BACKEND = ssl.OPENSSL_VERSION.startswith('LibreSSL') -IS_PYOPENSSL_SSL_VERSION_1_0 = ( - OpenSSL.SSL.SSLeay_version(OpenSSL.SSL.SSLEAY_VERSION). - startswith(b'OpenSSL 1.0.') -) -PY310_PLUS = sys.version_info[:2] >= (3, 10) - - -_stdlib_to_openssl_verify = { - ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, - ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, - ssl.CERT_REQUIRED: - OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, -} - - -fails_under_py3 = pytest.mark.xfail( - reason='Fails under Python 3+', -) - - -fails_under_py3_in_pypy = pytest.mark.xfail( - IS_PYPY, - reason='Fails under PyPy3', -) - - -missing_ipv6 = pytest.mark.skipif( - not _probe_ipv6_sock('::1'), - reason='' - 'IPv6 is disabled ' - '(for example, under Travis CI ' - 'which runs under GCE supporting only IPv4)', -) - - -class HelloWorldGateway(Gateway_10): - """Gateway responding with Hello World to root URI.""" - - def respond(self): - """Respond with dummy content via HTTP.""" - req = self.req - req_uri = bton(req.uri) - if req_uri == '/': - req.status = b'200 OK' - req.ensure_headers_sent() - req.write(b'Hello world!') - return - if req_uri == '/env': - req.status = b'200 OK' - req.ensure_headers_sent() - env = self.get_environ() - # drop files so that it can be json dumped - env.pop('wsgi.errors') - env.pop('wsgi.input') - print(env) - req.write(json.dumps(env).encode('utf-8')) - return - return super(HelloWorldGateway, self).respond() - - -def make_tls_http_server(bind_addr, ssl_adapter, request): - """Create and start an HTTP server bound to ``bind_addr``.""" - httpserver = HTTPServer( - bind_addr=bind_addr, - gateway=HelloWorldGateway, - ) - # httpserver.gateway = HelloWorldGateway - httpserver.ssl_adapter = ssl_adapter - - threading.Thread(target=httpserver.safe_start).start() - - while not httpserver.ready: - time.sleep(0.1) - - request.addfinalizer(httpserver.stop) - - return httpserver - - -@pytest.fixture -def tls_http_server(request): - """Provision a server creator as a fixture.""" - return functools.partial(make_tls_http_server, request=request) - - -@pytest.fixture -def ca(): - """Provide a certificate authority via fixture.""" - return trustme.CA() - - -@pytest.fixture -def tls_ca_certificate_pem_path(ca): - """Provide a certificate authority certificate file via fixture.""" - with ca.cert_pem.tempfile() as ca_cert_pem: - yield ca_cert_pem - - -@pytest.fixture -def tls_certificate(ca): - """Provide a leaf certificate via fixture.""" - interface, _host, _port = _get_conn_data(ANY_INTERFACE_IPV4) - return ca.issue_cert(ntou(interface)) - - -@pytest.fixture -def tls_certificate_chain_pem_path(tls_certificate): - """Provide a certificate chain PEM file path via fixture.""" - with tls_certificate.private_key_and_cert_chain_pem.tempfile() as cert_pem: - yield cert_pem - - -@pytest.fixture -def tls_certificate_private_key_pem_path(tls_certificate): - """Provide a certificate private key PEM file path via fixture.""" - with tls_certificate.private_key_pem.tempfile() as cert_key_pem: - yield cert_key_pem - - -def _thread_except_hook(exceptions, args): - """Append uncaught exception ``args`` in threads to ``exceptions``.""" - if issubclass(args.exc_type, SystemExit): - return - # cannot store the exception, it references the thread's stack - exceptions.append(( - args.exc_type, - str(args.exc_value), - ''.join( - traceback.format_exception( - args.exc_type, args.exc_value, args.exc_traceback, - ), - ), - )) - - -@pytest.fixture -def thread_exceptions(): - """Provide a list of uncaught exceptions from threads via a fixture. - - Only catches exceptions on Python 3.8+. - The list contains: ``(type, str(value), str(traceback))`` - """ - exceptions = [] - # Python 3.8+ - orig_hook = getattr(threading, 'excepthook', None) - if orig_hook is not None: - threading.excepthook = functools.partial( - _thread_except_hook, exceptions, - ) - try: - yield exceptions - finally: - if orig_hook is not None: - threading.excepthook = orig_hook - - -@pytest.mark.parametrize( - 'adapter_type', - ( - 'builtin', - 'pyopenssl', - ), -) -def test_ssl_adapters( - http_request_timeout, - tls_http_server, adapter_type, - tls_certificate, - tls_certificate_chain_pem_path, - tls_certificate_private_key_pem_path, - tls_ca_certificate_pem_path, -): - """Test ability to connect to server via HTTPS using adapters.""" - interface, _host, port = _get_conn_data(ANY_INTERFACE_IPV4) - tls_adapter_cls = get_ssl_adapter_class(name=adapter_type) - tls_adapter = tls_adapter_cls( - tls_certificate_chain_pem_path, tls_certificate_private_key_pem_path, - ) - if adapter_type == 'pyopenssl': - tls_adapter.context = tls_adapter.get_context() - - tls_certificate.configure_cert(tls_adapter.context) - - tlshttpserver = tls_http_server((interface, port), tls_adapter) - - # testclient = get_server_client(tlshttpserver) - # testclient.get('/') - - interface, _host, port = _get_conn_data( - tlshttpserver.bind_addr, - ) - - resp = requests.get( - 'https://{host!s}:{port!s}/'.format(host=interface, port=port), - timeout=http_request_timeout, - verify=tls_ca_certificate_pem_path, - ) - - assert resp.status_code == 200 - assert resp.text == 'Hello world!' - - -@pytest.mark.parametrize( # noqa: C901 # FIXME - 'adapter_type', - ( - 'builtin', - 'pyopenssl', - ), -) -@pytest.mark.parametrize( - ('is_trusted_cert', 'tls_client_identity'), - ( - (True, 'localhost'), (True, '127.0.0.1'), - (True, '*.localhost'), (True, 'not_localhost'), - (False, 'localhost'), - ), -) -@pytest.mark.parametrize( - 'tls_verify_mode', - ( - ssl.CERT_NONE, # server shouldn't validate client cert - ssl.CERT_OPTIONAL, # same as CERT_REQUIRED in client mode, don't use - ssl.CERT_REQUIRED, # server should validate if client cert CA is OK - ), -) -@pytest.mark.xfail( - IS_PYPY and IS_CI, - reason='Fails under PyPy in CI for unknown reason', - strict=False, -) -def test_tls_client_auth( # noqa: C901, WPS213 # FIXME - # FIXME: remove twisted logic, separate tests - http_request_timeout, - mocker, - tls_http_server, adapter_type, - ca, - tls_certificate, - tls_certificate_chain_pem_path, - tls_certificate_private_key_pem_path, - tls_ca_certificate_pem_path, - is_trusted_cert, tls_client_identity, - tls_verify_mode, -): - """Verify that client TLS certificate auth works correctly.""" - test_cert_rejection = ( - tls_verify_mode != ssl.CERT_NONE - and not is_trusted_cert - ) - interface, _host, port = _get_conn_data(ANY_INTERFACE_IPV4) - - client_cert_root_ca = ca if is_trusted_cert else trustme.CA() - with mocker.mock_module.patch( - 'idna.core.ulabel', - return_value=ntob(tls_client_identity), - ): - client_cert = client_cert_root_ca.issue_cert( - ntou(tls_client_identity), - ) - del client_cert_root_ca - - with client_cert.private_key_and_cert_chain_pem.tempfile() as cl_pem: - tls_adapter_cls = get_ssl_adapter_class(name=adapter_type) - tls_adapter = tls_adapter_cls( - tls_certificate_chain_pem_path, - tls_certificate_private_key_pem_path, - ) - if adapter_type == 'pyopenssl': - tls_adapter.context = tls_adapter.get_context() - tls_adapter.context.set_verify( - _stdlib_to_openssl_verify[tls_verify_mode], - lambda conn, cert, errno, depth, preverify_ok: preverify_ok, - ) - else: - tls_adapter.context.verify_mode = tls_verify_mode - - ca.configure_trust(tls_adapter.context) - tls_certificate.configure_cert(tls_adapter.context) - - tlshttpserver = tls_http_server((interface, port), tls_adapter) - - interface, _host, port = _get_conn_data(tlshttpserver.bind_addr) - - make_https_request = functools.partial( - requests.get, - 'https://{host!s}:{port!s}/'.format(host=interface, port=port), - - # Don't wait for the first byte forever: - timeout=http_request_timeout, - - # Server TLS certificate verification: - verify=tls_ca_certificate_pem_path, - - # Client TLS certificate verification: - cert=cl_pem, - ) - - if not test_cert_rejection: - resp = make_https_request() - is_req_successful = resp.status_code == 200 - if ( - not is_req_successful - and IS_PYOPENSSL_SSL_VERSION_1_0 - and adapter_type == 'builtin' - and tls_verify_mode == ssl.CERT_REQUIRED - and tls_client_identity == 'localhost' - and is_trusted_cert - ): - pytest.xfail( - 'OpenSSL 1.0 has problems with verifying client certs', - ) - assert is_req_successful - assert resp.text == 'Hello world!' - resp.close() - return - - # xfail some flaky tests - # https://github.com/cherrypy/cheroot/issues/237 - issue_237 = ( - IS_MACOS - and adapter_type == 'builtin' - and tls_verify_mode != ssl.CERT_NONE - ) - if issue_237: - pytest.xfail('Test sometimes fails') - - expected_ssl_errors = requests.exceptions.SSLError, - if IS_WINDOWS or IS_GITHUB_ACTIONS_WORKFLOW: - expected_ssl_errors += requests.exceptions.ConnectionError, - with pytest.raises(expected_ssl_errors) as ssl_err: - make_https_request().close() - - try: - err_text = ssl_err.value.args[0].reason.args[0].args[0] - except AttributeError: - if IS_WINDOWS or IS_GITHUB_ACTIONS_WORKFLOW: - err_text = str(ssl_err.value) - else: - raise - - if isinstance(err_text, int): - err_text = str(ssl_err.value) - - expected_substrings = ( - 'sslv3 alert bad certificate' if IS_LIBRESSL_BACKEND - else 'tlsv1 alert unknown ca', - ) - if IS_MACOS and IS_PYPY and adapter_type == 'pyopenssl': - expected_substrings = ('tlsv1 alert unknown ca',) - if ( - tls_verify_mode in ( - ssl.CERT_REQUIRED, - ssl.CERT_OPTIONAL, - ) - and not is_trusted_cert - and tls_client_identity == 'localhost' - ): - expected_substrings += ( - 'bad handshake: ' - "SysCallError(10054, 'WSAECONNRESET')", - "('Connection aborted.', " - 'OSError("(10054, \'WSAECONNRESET\')"))', - "('Connection aborted.', " - 'OSError("(10054, \'WSAECONNRESET\')",))', - "('Connection aborted.', " - 'error("(10054, \'WSAECONNRESET\')",))', - "('Connection aborted.', " - 'ConnectionResetError(10054, ' - "'An existing connection was forcibly closed " - "by the remote host', None, 10054, None))", - "('Connection aborted.', " - 'error(10054, ' - "'An existing connection was forcibly closed " - "by the remote host'))", - ) if IS_WINDOWS else ( - "('Connection aborted.', " - 'OSError("(104, \'ECONNRESET\')"))', - "('Connection aborted.', " - 'OSError("(104, \'ECONNRESET\')",))', - "('Connection aborted.', " - 'error("(104, \'ECONNRESET\')",))', - "('Connection aborted.', " - "ConnectionResetError(104, 'Connection reset by peer'))", - "('Connection aborted.', " - "error(104, 'Connection reset by peer'))", - ) if ( - IS_GITHUB_ACTIONS_WORKFLOW - and IS_LINUX - ) else ( - "('Connection aborted.', " - "BrokenPipeError(32, 'Broken pipe'))", - ) - - if PY310_PLUS: - # FIXME: Figure out what's happening and correct the problem - expected_substrings += ( - 'SSLError(SSLEOFError(8, ' - "'EOF occurred in violation of protocol (_ssl.c:", - ) - if IS_GITHUB_ACTIONS_WORKFLOW and IS_WINDOWS and PY310_PLUS: - expected_substrings += ( - "('Connection aborted.', " - 'RemoteDisconnected(' - "'Remote end closed connection without response'))", - ) - - assert any(e in err_text for e in expected_substrings) - - -@pytest.mark.parametrize( # noqa: C901 # FIXME - 'adapter_type', - ( - pytest.param( - 'builtin', - marks=pytest.mark.xfail( - IS_MACOS and PY310_PLUS, - reason='Unclosed TLS resource warnings happen on macOS ' - 'under Python 3.10 (#508)', - strict=False, - ), - ), - 'pyopenssl', - ), -) -@pytest.mark.parametrize( - ('tls_verify_mode', 'use_client_cert'), - ( - (ssl.CERT_NONE, False), - (ssl.CERT_NONE, True), - (ssl.CERT_OPTIONAL, False), - (ssl.CERT_OPTIONAL, True), - (ssl.CERT_REQUIRED, True), - ), -) -def test_ssl_env( # noqa: C901 # FIXME - thread_exceptions, - recwarn, - mocker, - http_request_timeout, - tls_http_server, adapter_type, - ca, tls_verify_mode, tls_certificate, - tls_certificate_chain_pem_path, - tls_certificate_private_key_pem_path, - tls_ca_certificate_pem_path, - use_client_cert, -): - """Test the SSL environment generated by the SSL adapters.""" - interface, _host, port = _get_conn_data(ANY_INTERFACE_IPV4) - - with mocker.mock_module.patch( - 'idna.core.ulabel', - return_value=ntob('127.0.0.1'), - ): - client_cert = ca.issue_cert(ntou('127.0.0.1')) - - with client_cert.private_key_and_cert_chain_pem.tempfile() as cl_pem: - tls_adapter_cls = get_ssl_adapter_class(name=adapter_type) - tls_adapter = tls_adapter_cls( - tls_certificate_chain_pem_path, - tls_certificate_private_key_pem_path, - ) - if adapter_type == 'pyopenssl': - tls_adapter.context = tls_adapter.get_context() - tls_adapter.context.set_verify( - _stdlib_to_openssl_verify[tls_verify_mode], - lambda conn, cert, errno, depth, preverify_ok: preverify_ok, - ) - else: - tls_adapter.context.verify_mode = tls_verify_mode - - ca.configure_trust(tls_adapter.context) - tls_certificate.configure_cert(tls_adapter.context) - - tlswsgiserver = tls_http_server((interface, port), tls_adapter) - - interface, _host, port = _get_conn_data(tlswsgiserver.bind_addr) - - resp = requests.get( - 'https://' + interface + ':' + str(port) + '/env', - timeout=http_request_timeout, - verify=tls_ca_certificate_pem_path, - cert=cl_pem if use_client_cert else None, - ) - - env = json.loads(resp.content.decode('utf-8')) - - # hard coded env - assert env['wsgi.url_scheme'] == 'https' - assert env['HTTPS'] == 'on' - - # ensure these are present - for key in {'SSL_VERSION_INTERFACE', 'SSL_VERSION_LIBRARY'}: - assert key in env - - # pyOpenSSL generates the env before the handshake completes - if adapter_type == 'pyopenssl': - return - - for key in {'SSL_PROTOCOL', 'SSL_CIPHER'}: - assert key in env - - # client certificate env - if tls_verify_mode == ssl.CERT_NONE or not use_client_cert: - assert env['SSL_CLIENT_VERIFY'] == 'NONE' - else: - assert env['SSL_CLIENT_VERIFY'] == 'SUCCESS' - - with open(cl_pem, 'rt') as f: - assert env['SSL_CLIENT_CERT'] in f.read() - - for key in { - 'SSL_CLIENT_M_VERSION', 'SSL_CLIENT_M_SERIAL', - 'SSL_CLIENT_I_DN', 'SSL_CLIENT_S_DN', - }: - assert key in env - - # builtin ssl environment generation may use a loopback socket - # ensure no ResourceWarning was raised during the test - # NOTE: python 2.7 does not emit ResourceWarning for ssl sockets - if IS_PYPY: - # NOTE: PyPy doesn't have ResourceWarning - # Ref: https://doc.pypy.org/en/latest/cpython_differences.html - return - for warn in recwarn: - if not issubclass(warn.category, ResourceWarning): - continue - - # the tests can sporadically generate resource warnings - # due to timing issues - # all of these sporadic warnings appear to be about socket.socket - # and have been observed to come from requests connection pool - msg = str(warn.message) - if 'socket.socket' in msg: - pytest.xfail( - '\n'.join(( - 'Sometimes this test fails due to ' - 'a socket.socket ResourceWarning:', - msg, - )), - ) - pytest.fail(msg) - - # to perform the ssl handshake over that loopback socket, - # the builtin ssl environment generation uses a thread - for _, _, trace in thread_exceptions: - print(trace, file=sys.stderr) - assert not thread_exceptions, ': '.join(( - thread_exceptions[0][0].__name__, - thread_exceptions[0][1], - )) - - -@pytest.mark.parametrize( - 'ip_addr', - ( - ANY_INTERFACE_IPV4, - ANY_INTERFACE_IPV6, - ), -) -def test_https_over_http_error(http_server, ip_addr): - """Ensure that connecting over HTTPS to HTTP port is handled.""" - httpserver = http_server.send((ip_addr, EPHEMERAL_PORT)) - interface, _host, port = _get_conn_data(httpserver.bind_addr) - with pytest.raises(ssl.SSLError) as ssl_err: - http.client.HTTPSConnection( - '{interface}:{port}'.format( - interface=interface, - port=port, - ), - ).request('GET', '/') - expected_substring = ( - 'wrong version number' if IS_ABOVE_OPENSSL10 - else 'unknown protocol' - ) - assert expected_substring in ssl_err.value.args[-1] - - -@pytest.mark.parametrize( - 'adapter_type', - ( - 'builtin', - 'pyopenssl', - ), -) -@pytest.mark.parametrize( - 'ip_addr', - ( - ANY_INTERFACE_IPV4, - pytest.param(ANY_INTERFACE_IPV6, marks=missing_ipv6), - ), -) -@pytest.mark.flaky(reruns=3, reruns_delay=2) -def test_http_over_https_error( - http_request_timeout, - tls_http_server, adapter_type, - ca, ip_addr, - tls_certificate, - tls_certificate_chain_pem_path, - tls_certificate_private_key_pem_path, -): - """Ensure that connecting over HTTP to HTTPS port is handled.""" - # disable some flaky tests - # https://github.com/cherrypy/cheroot/issues/225 - issue_225 = ( - IS_MACOS - and adapter_type == 'builtin' - ) - if issue_225: - pytest.xfail('Test fails in Travis-CI') - - tls_adapter_cls = get_ssl_adapter_class(name=adapter_type) - tls_adapter = tls_adapter_cls( - tls_certificate_chain_pem_path, tls_certificate_private_key_pem_path, - ) - if adapter_type == 'pyopenssl': - tls_adapter.context = tls_adapter.get_context() - - tls_certificate.configure_cert(tls_adapter.context) - - interface, _host, port = _get_conn_data(ip_addr) - tlshttpserver = tls_http_server((interface, port), tls_adapter) - - interface, _host, port = _get_conn_data( - tlshttpserver.bind_addr, - ) - - fqdn = interface - if ip_addr is ANY_INTERFACE_IPV6: - fqdn = '[{fqdn}]'.format(**locals()) - - expect_fallback_response_over_plain_http = ( - ( - adapter_type == 'pyopenssl' - ) - ) - if expect_fallback_response_over_plain_http: - resp = requests.get( - 'http://{host!s}:{port!s}/'.format(host=fqdn, port=port), - timeout=http_request_timeout, - ) - assert resp.status_code == 400 - assert resp.text == ( - 'The client sent a plain HTTP request, ' - 'but this server only speaks HTTPS on this port.' - ) - return - - with pytest.raises(requests.exceptions.ConnectionError) as ssl_err: - requests.get( # FIXME: make stdlib ssl behave like PyOpenSSL - 'http://{host!s}:{port!s}/'.format(host=fqdn, port=port), - timeout=http_request_timeout, - ) - - if IS_LINUX: - expected_error_code, expected_error_text = ( - 104, 'Connection reset by peer', - ) - if IS_MACOS: - expected_error_code, expected_error_text = ( - 54, 'Connection reset by peer', - ) - if IS_WINDOWS: - expected_error_code, expected_error_text = ( - 10054, - 'An existing connection was forcibly closed by the remote host', - ) - - underlying_error = ssl_err.value.args[0].args[-1] - err_text = str(underlying_error) - assert underlying_error.errno == expected_error_code, ( - 'The underlying error is {underlying_error!r}'. - format(**locals()) - ) - assert expected_error_text in err_text diff --git a/lib/cheroot/test/test_wsgi.py b/lib/cheroot/test/test_wsgi.py deleted file mode 100644 index 14005a84..00000000 --- a/lib/cheroot/test/test_wsgi.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Test wsgi.""" - -from concurrent.futures.thread import ThreadPoolExecutor -from traceback import print_tb - -import pytest -import portend -import requests -from requests_toolbelt.sessions import BaseUrlSession as Session -from jaraco.context import ExceptionTrap - -from cheroot import wsgi -from cheroot._compat import IS_MACOS, IS_WINDOWS - - -IS_SLOW_ENV = IS_MACOS or IS_WINDOWS - - -@pytest.fixture -def simple_wsgi_server(): - """Fucking simple wsgi server fixture (duh).""" - port = portend.find_available_local_port() - - def app(_environ, start_response): - status = '200 OK' - response_headers = [('Content-type', 'text/plain')] - start_response(status, response_headers) - return [b'Hello world!'] - - host = '::' - addr = host, port - server = wsgi.Server(addr, app, timeout=600 if IS_SLOW_ENV else 20) - # pylint: disable=possibly-unused-variable - url = 'http://localhost:{port}/'.format(**locals()) - # pylint: disable=possibly-unused-variable - with server._run_in_thread() as thread: - yield locals() - - -@pytest.mark.flaky(reruns=3, reruns_delay=2) -def test_connection_keepalive(simple_wsgi_server): - """Test the connection keepalive works (duh).""" - session = Session(base_url=simple_wsgi_server['url']) - pooled = requests.adapters.HTTPAdapter( - pool_connections=1, pool_maxsize=1000, - ) - session.mount('http://', pooled) - - def do_request(): - with ExceptionTrap(requests.exceptions.ConnectionError) as trap: - resp = session.get('info') - resp.raise_for_status() - print_tb(trap.tb) - return bool(trap) - - with ThreadPoolExecutor(max_workers=10 if IS_SLOW_ENV else 50) as pool: - tasks = [ - pool.submit(do_request) - for n in range(250 if IS_SLOW_ENV else 1000) - ] - failures = sum(task.result() for task in tasks) - - session.close() - assert not failures - - -def test_gateway_start_response_called_twice(monkeypatch): - """Verify that repeat calls of ``Gateway.start_response()`` fail.""" - monkeypatch.setattr(wsgi.Gateway, 'get_environ', lambda self: {}) - wsgi_gateway = wsgi.Gateway(None) - wsgi_gateway.started_response = True - - err_msg = '^WSGI start_response called a second time with no exc_info.$' - with pytest.raises(RuntimeError, match=err_msg): - wsgi_gateway.start_response('200', (), None) - - -def test_gateway_write_needs_start_response_called_before(monkeypatch): - """Check that calling ``Gateway.write()`` needs started response.""" - monkeypatch.setattr(wsgi.Gateway, 'get_environ', lambda self: {}) - wsgi_gateway = wsgi.Gateway(None) - - err_msg = '^WSGI write called before start_response.$' - with pytest.raises(RuntimeError, match=err_msg): - wsgi_gateway.write(None) # The actual arg value is unimportant diff --git a/lib/cheroot/test/webtest.py b/lib/cheroot/test/webtest.py deleted file mode 100644 index 1630c8ef..00000000 --- a/lib/cheroot/test/webtest.py +++ /dev/null @@ -1,591 +0,0 @@ -"""Extensions to unittest for web frameworks. - -Use the :py:meth:`WebCase.getPage` method to request a page -from your HTTP server. - -Framework Integration -===================== -If you have control over your server process, you can handle errors -in the server-side of the HTTP conversation a bit better. You must run -both the client (your :py:class:`WebCase` tests) and the server in the -same process (but in separate threads, obviously). -When an error occurs in the framework, call server_error. It will print -the traceback to stdout, and keep any assertions you have from running -(the assumption is that, if the server errors, the page output will not -be of further significance to your tests). -""" - -import pprint -import re -import socket -import sys -import time -import traceback -import os -import json -import unittest # pylint: disable=deprecated-module,preferred-module -import warnings -import functools -import http.client -import urllib.parse - -from more_itertools.more import always_iterable -import jaraco.functools - - -def interface(host): - """Return an IP address for a client connection given the server host. - - If the server is listening on '0.0.0.0' (INADDR_ANY) - or '::' (IN6ADDR_ANY), this will return the proper localhost. - """ - if host == '0.0.0.0': - # INADDR_ANY, which should respond on localhost. - return '127.0.0.1' - if host == '::': - # IN6ADDR_ANY, which should respond on localhost. - return '::1' - return host - - -try: - # Jython support - if sys.platform[:4] == 'java': - def getchar(): - """Get a key press.""" - # Hopefully this is enough - return sys.stdin.read(1) - else: - # On Windows, msvcrt.getch reads a single char without output. - import msvcrt - - def getchar(): - """Get a key press.""" - return msvcrt.getch() -except ImportError: - # Unix getchr - import tty - import termios - - def getchar(): - """Get a key press.""" - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) - try: - tty.setraw(sys.stdin.fileno()) - ch = sys.stdin.read(1) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - return ch - - -# from jaraco.properties -class NonDataProperty: - """Non-data property decorator.""" - - def __init__(self, fget): - """Initialize a non-data property.""" - assert fget is not None, 'fget cannot be none' - assert callable(fget), 'fget must be callable' - self.fget = fget - - def __get__(self, obj, objtype=None): - """Return a class property.""" - if obj is None: - return self - return self.fget(obj) - - -class WebCase(unittest.TestCase): - """Helper web test suite base.""" - - HOST = '127.0.0.1' - PORT = 8000 - HTTP_CONN = http.client.HTTPConnection - PROTOCOL = 'HTTP/1.1' - - scheme = 'http' - url = None - ssl_context = None - - status = None - headers = None - body = None - - encoding = 'utf-8' - - time = None - - @property - def _Conn(self): - """Return HTTPConnection or HTTPSConnection based on self.scheme. - - * from :py:mod:`python:http.client`. - """ - cls_name = '{scheme}Connection'.format(scheme=self.scheme.upper()) - return getattr(http.client, cls_name) - - def get_conn(self, auto_open=False): - """Return a connection to our HTTP server.""" - conn = self._Conn(self.interface(), self.PORT) - # Automatically re-connect? - conn.auto_open = auto_open - conn.connect() - return conn - - def set_persistent(self, on=True, auto_open=False): - """Make our HTTP_CONN persistent (or not). - - If the 'on' argument is True (the default), then self.HTTP_CONN - will be set to an instance of HTTP(S)?Connection - to persist across requests. - As this class only allows for a single open connection, if - self already has an open connection, it will be closed. - """ - try: - self.HTTP_CONN.close() - except (TypeError, AttributeError): - pass - - self.HTTP_CONN = ( - self.get_conn(auto_open=auto_open) - if on - else self._Conn - ) - - @property - def persistent(self): - """Presence of the persistent HTTP connection.""" - return hasattr(self.HTTP_CONN, '__class__') - - @persistent.setter - def persistent(self, on): - self.set_persistent(on) - - def interface(self): - """Return an IP address for a client connection. - - If the server is listening on '0.0.0.0' (INADDR_ANY) - or '::' (IN6ADDR_ANY), this will return the proper localhost. - """ - return interface(self.HOST) - - def getPage( - self, url, headers=None, method='GET', body=None, - protocol=None, raise_subcls=(), - ): - """Open the url with debugging support. - - Return status, headers, body. - - url should be the identifier passed to the server, typically a - server-absolute path and query string (sent between method and - protocol), and should only be an absolute URI if proxy support is - enabled in the server. - - If the application under test generates absolute URIs, be sure - to wrap them first with :py:func:`strip_netloc`:: - - >>> class MyAppWebCase(WebCase): - ... def getPage(url, *args, **kwargs): - ... super(MyAppWebCase, self).getPage( - ... cheroot.test.webtest.strip_netloc(url), - ... *args, **kwargs - ... ) - - ``raise_subcls`` is passed through to :py:func:`openURL`. - """ - ServerError.on = False - - if isinstance(url, str): - url = url.encode('utf-8') - if isinstance(body, str): - body = body.encode('utf-8') - - # for compatibility, support raise_subcls is None - raise_subcls = raise_subcls or () - - self.url = url - self.time = None - start = time.time() - result = openURL( - url, headers, method, body, self.HOST, self.PORT, - self.HTTP_CONN, protocol or self.PROTOCOL, - raise_subcls=raise_subcls, - ssl_context=self.ssl_context, - ) - self.time = time.time() - start - self.status, self.headers, self.body = result - - # Build a list of request cookies from the previous response cookies. - self.cookies = [ - ('Cookie', v) for k, v in self.headers - if k.lower() == 'set-cookie' - ] - - if ServerError.on: - raise ServerError() - return result - - @NonDataProperty - def interactive(self): - """Determine whether tests are run in interactive mode. - - Load interactivity setting from environment, where - the value can be numeric or a string like true or - False or 1 or 0. - """ - env_str = os.environ.get('WEBTEST_INTERACTIVE', 'True') - is_interactive = bool(json.loads(env_str.lower())) - if is_interactive: - warnings.warn( - 'Interactive test failure interceptor support via ' - 'WEBTEST_INTERACTIVE environment variable is deprecated.', - DeprecationWarning, - ) - return is_interactive - - console_height = 30 - - def _handlewebError(self, msg): # noqa: C901 # FIXME - print('') - print(' ERROR: %s' % msg) - - if not self.interactive: - raise self.failureException(msg) - - p = ( - ' Show: ' - '[B]ody [H]eaders [S]tatus [U]RL; ' - '[I]gnore, [R]aise, or sys.e[X]it >> ' - ) - sys.stdout.write(p) - sys.stdout.flush() - while True: - i = getchar().upper() - if not isinstance(i, type('')): - i = i.decode('ascii') - if i not in 'BHSUIRX': - continue - print(i.upper()) # Also prints new line - if i == 'B': - for x, line in enumerate(self.body.splitlines()): - if (x + 1) % self.console_height == 0: - # The \r and comma should make the next line overwrite - sys.stdout.write('<-- More -->\r') - m = getchar().lower() - # Erase our "More" prompt - sys.stdout.write(' \r') - if m == 'q': - break - print(line) - elif i == 'H': - pprint.pprint(self.headers) - elif i == 'S': - print(self.status) - elif i == 'U': - print(self.url) - elif i == 'I': - # return without raising the normal exception - return - elif i == 'R': - raise self.failureException(msg) - elif i == 'X': - sys.exit() - sys.stdout.write(p) - sys.stdout.flush() - - @property - def status_code(self): # noqa: D401; irrelevant for properties - """Integer HTTP status code.""" - return int(self.status[:3]) - - def status_matches(self, expected): - """Check whether actual status matches expected.""" - actual = ( - self.status_code - if isinstance(expected, int) else - self.status - ) - return expected == actual - - def assertStatus(self, status, msg=None): - """Fail if self.status != status. - - status may be integer code, exact string status, or - iterable of allowed possibilities. - """ - if any(map(self.status_matches, always_iterable(status))): - return - - tmpl = 'Status {self.status} does not match {status}' - msg = msg or tmpl.format(**locals()) - self._handlewebError(msg) - - def assertHeader(self, key, value=None, msg=None): - """Fail if (key, [value]) not in self.headers.""" - lowkey = key.lower() - for k, v in self.headers: - if k.lower() == lowkey: - if value is None or str(value) == v: - return v - - if msg is None: - if value is None: - msg = '%r not in headers' % key - else: - msg = '%r:%r not in headers' % (key, value) - self._handlewebError(msg) - - def assertHeaderIn(self, key, values, msg=None): - """Fail if header indicated by key doesn't have one of the values.""" - lowkey = key.lower() - for k, v in self.headers: - if k.lower() == lowkey: - matches = [value for value in values if str(value) == v] - if matches: - return matches - - if msg is None: - msg = '%(key)r not in %(values)r' % vars() - self._handlewebError(msg) - - def assertHeaderItemValue(self, key, value, msg=None): - """Fail if the header does not contain the specified value.""" - actual_value = self.assertHeader(key, msg=msg) - header_values = map(str.strip, actual_value.split(',')) - if value in header_values: - return value - - if msg is None: - msg = '%r not in %r' % (value, header_values) - self._handlewebError(msg) - - def assertNoHeader(self, key, msg=None): - """Fail if key in self.headers.""" - lowkey = key.lower() - matches = [k for k, v in self.headers if k.lower() == lowkey] - if matches: - if msg is None: - msg = '%r in headers' % key - self._handlewebError(msg) - - def assertNoHeaderItemValue(self, key, value, msg=None): - """Fail if the header contains the specified value.""" - lowkey = key.lower() - hdrs = self.headers - matches = [k for k, v in hdrs if k.lower() == lowkey and v == value] - if matches: - if msg is None: - msg = '%r:%r in %r' % (key, value, hdrs) - self._handlewebError(msg) - - def assertBody(self, value, msg=None): - """Fail if value != self.body.""" - if isinstance(value, str): - value = value.encode(self.encoding) - if value != self.body: - if msg is None: - msg = 'expected body:\n%r\n\nactual body:\n%r' % ( - value, self.body, - ) - self._handlewebError(msg) - - def assertInBody(self, value, msg=None): - """Fail if value not in self.body.""" - if isinstance(value, str): - value = value.encode(self.encoding) - if value not in self.body: - if msg is None: - msg = '%r not in body: %s' % (value, self.body) - self._handlewebError(msg) - - def assertNotInBody(self, value, msg=None): - """Fail if value in self.body.""" - if isinstance(value, str): - value = value.encode(self.encoding) - if value in self.body: - if msg is None: - msg = '%r found in body' % value - self._handlewebError(msg) - - def assertMatchesBody(self, pattern, msg=None, flags=0): - """Fail if value (a regex pattern) is not in self.body.""" - if isinstance(pattern, str): - pattern = pattern.encode(self.encoding) - if re.search(pattern, self.body, flags) is None: - if msg is None: - msg = 'No match for %r in body' % pattern - self._handlewebError(msg) - - -methods_with_bodies = ('POST', 'PUT', 'PATCH') - - -def cleanHeaders(headers, method, body, host, port): - """Return request headers, with required headers added (if missing).""" - if headers is None: - headers = [] - - # Add the required Host request header if not present. - # [This specifies the host:port of the server, not the client.] - found = False - for k, _v in headers: - if k.lower() == 'host': - found = True - break - if not found: - if port == 80: - headers.append(('Host', host)) - else: - headers.append(('Host', '%s:%s' % (host, port))) - - if method in methods_with_bodies: - # Stick in default type and length headers if not present - found = False - for k, v in headers: - if k.lower() == 'content-type': - found = True - break - if not found: - headers.append( - ('Content-Type', 'application/x-www-form-urlencoded'), - ) - headers.append(('Content-Length', str(len(body or '')))) - - return headers - - -def shb(response): - """Return status, headers, body the way we like from a response.""" - resp_status_line = '%s %s' % (response.status, response.reason) - - return resp_status_line, response.getheaders(), response.read() - - -# def openURL(*args, raise_subcls=(), **kwargs): -# py27 compatible signature: -def openURL(*args, **kwargs): - """ - Open a URL, retrying when it fails. - - Specify ``raise_subcls`` (class or tuple of classes) to exclude - those socket.error subclasses from being suppressed and retried. - """ - raise_subcls = kwargs.pop('raise_subcls', ()) - opener = functools.partial(_open_url_once, *args, **kwargs) - - def on_exception(): - exc = sys.exc_info()[1] - if isinstance(exc, raise_subcls): - raise exc - time.sleep(0.5) - - # Try up to 10 times - return jaraco.functools.retry_call( - opener, - retries=9, - cleanup=on_exception, - trap=socket.error, - ) - - -def _open_url_once( - url, headers=None, method='GET', body=None, - host='127.0.0.1', port=8000, http_conn=http.client.HTTPConnection, - protocol='HTTP/1.1', ssl_context=None, -): - """Open the given HTTP resource and return status, headers, and body.""" - headers = cleanHeaders(headers, method, body, host, port) - - # Allow http_conn to be a class or an instance - if hasattr(http_conn, 'host'): - conn = http_conn - else: - kw = {} - if ssl_context: - kw['context'] = ssl_context - conn = http_conn(interface(host), port, **kw) - conn._http_vsn_str = protocol - conn._http_vsn = int(''.join([x for x in protocol if x.isdigit()])) - if isinstance(url, bytes): - url = url.decode() - conn.putrequest( - method.upper(), url, skip_host=True, - skip_accept_encoding=True, - ) - for key, value in headers: - conn.putheader(key, value.encode('Latin-1')) - conn.endheaders() - if body is not None: - conn.send(body) - # Handle response - response = conn.getresponse() - s, h, b = shb(response) - if not hasattr(http_conn, 'host'): - # We made our own conn instance. Close it. - conn.close() - return s, h, b - - -def strip_netloc(url): - """Return absolute-URI path from URL. - - Strip the scheme and host from the URL, returning the - server-absolute portion. - - Useful for wrapping an absolute-URI for which only the - path is expected (such as in calls to :py:meth:`WebCase.getPage`). - - .. testsetup:: - - from cheroot.test.webtest import strip_netloc - - >>> strip_netloc('https://google.com/foo/bar?bing#baz') - '/foo/bar?bing' - - >>> strip_netloc('//google.com/foo/bar?bing#baz') - '/foo/bar?bing' - - >>> strip_netloc('/foo/bar?bing#baz') - '/foo/bar?bing' - """ - parsed = urllib.parse.urlparse(url) - _scheme, _netloc, path, params, query, _fragment = parsed - stripped = '', '', path, params, query, '' - return urllib.parse.urlunparse(stripped) - - -# Add any exceptions which your web framework handles -# normally (that you don't want server_error to trap). -ignored_exceptions = [] - -# You'll want set this to True when you can't guarantee -# that each response will immediately follow each request; -# for example, when handling requests via multiple threads. -ignore_all = False - - -class ServerError(Exception): - """Exception for signalling server error.""" - - on = False - - -def server_error(exc=None): - """Server debug hook. - - Return True if exception handled, False if ignored. - You probably want to wrap this, so you can still handle an error using - your framework when it's ignored. - """ - if exc is None: - exc = sys.exc_info() - - if ignore_all or exc[0] in ignored_exceptions: - return False - else: - ServerError.on = True - print('') - print(''.join(traceback.format_exception(*exc))) - return True diff --git a/lib/cheroot/testing.py b/lib/cheroot/testing.py deleted file mode 100644 index 169142bf..00000000 --- a/lib/cheroot/testing.py +++ /dev/null @@ -1,151 +0,0 @@ -"""Pytest fixtures and other helpers for doing testing by end-users.""" - -from contextlib import closing, contextmanager -import errno -import socket -import threading -import time -import http.client - -import pytest - -import cheroot.server -from cheroot.test import webtest -import cheroot.wsgi - -EPHEMERAL_PORT = 0 -NO_INTERFACE = None # Using this or '' will cause an exception -ANY_INTERFACE_IPV4 = '0.0.0.0' -ANY_INTERFACE_IPV6 = '::' - -config = { - cheroot.wsgi.Server: { - 'bind_addr': (NO_INTERFACE, EPHEMERAL_PORT), - 'wsgi_app': None, - }, - cheroot.server.HTTPServer: { - 'bind_addr': (NO_INTERFACE, EPHEMERAL_PORT), - 'gateway': cheroot.server.Gateway, - }, -} - - -@contextmanager -def cheroot_server(server_factory): - """Set up and tear down a Cheroot server instance.""" - conf = config[server_factory].copy() - bind_port = conf.pop('bind_addr')[-1] - - for interface in ANY_INTERFACE_IPV6, ANY_INTERFACE_IPV4: - try: - actual_bind_addr = (interface, bind_port) - httpserver = server_factory( # create it - bind_addr=actual_bind_addr, - **conf - ) - except OSError: - pass - else: - break - - httpserver.shutdown_timeout = 0 # Speed-up tests teardown - - threading.Thread(target=httpserver.safe_start).start() # spawn it - while not httpserver.ready: # wait until fully initialized and bound - time.sleep(0.1) - - yield httpserver - - httpserver.stop() # destroy it - - -@pytest.fixture -def wsgi_server(): - """Set up and tear down a Cheroot WSGI server instance.""" - with cheroot_server(cheroot.wsgi.Server) as srv: - yield srv - - -@pytest.fixture -def native_server(): - """Set up and tear down a Cheroot HTTP server instance.""" - with cheroot_server(cheroot.server.HTTPServer) as srv: - yield srv - - -class _TestClient: - def __init__(self, server): - self._interface, self._host, self._port = _get_conn_data( - server.bind_addr, - ) - self.server_instance = server - self._http_connection = self.get_connection() - - def get_connection(self): - name = '{interface}:{port}'.format( - interface=self._interface, - port=self._port, - ) - conn_cls = ( - http.client.HTTPConnection - if self.server_instance.ssl_adapter is None else - http.client.HTTPSConnection - ) - return conn_cls(name) - - def request( - self, uri, method='GET', headers=None, http_conn=None, - protocol='HTTP/1.1', - ): - return webtest.openURL( - uri, method=method, - headers=headers, - host=self._host, port=self._port, - http_conn=http_conn or self._http_connection, - protocol=protocol, - ) - - def __getattr__(self, attr_name): - def _wrapper(uri, **kwargs): - http_method = attr_name.upper() - return self.request(uri, method=http_method, **kwargs) - - return _wrapper - - -def _probe_ipv6_sock(interface): - # Alternate way is to check IPs on interfaces using glibc, like: - # github.com/Gautier/minifail/blob/master/minifail/getifaddrs.py - try: - with closing(socket.socket(family=socket.AF_INET6)) as sock: - sock.bind((interface, 0)) - except (OSError, socket.error) as sock_err: - # In Python 3 socket.error is an alias for OSError - # In Python 2 socket.error is a subclass of IOError - if sock_err.errno != errno.EADDRNOTAVAIL: - raise - else: - return True - - return False - - -def _get_conn_data(bind_addr): - if isinstance(bind_addr, tuple): - host, port = bind_addr - else: - host, port = bind_addr, 0 - - interface = webtest.interface(host) - - if ':' in interface and not _probe_ipv6_sock(interface): - interface = '127.0.0.1' - if ':' in host: - host = interface - - return interface, host, port - - -def get_server_client(server): - """Create and return a test client for the given server.""" - return _TestClient(server) diff --git a/lib/cheroot/testing.pyi b/lib/cheroot/testing.pyi deleted file mode 100644 index 4c825f98..00000000 --- a/lib/cheroot/testing.pyi +++ /dev/null @@ -1,17 +0,0 @@ -from typing import Any, Iterator, Optional, TypeVar - -from .server import HTTPServer -from .wsgi import Server - -T = TypeVar('T', bound=HTTPServer) - -EPHEMERAL_PORT: int -NO_INTERFACE: Optional[str] -ANY_INTERFACE_IPV4: str -ANY_INTERFACE_IPV6: str -config: dict - -def cheroot_server(server_factory: T) -> Iterator[T]: ... -def wsgi_server() -> Iterator[Server]: ... -def native_server() -> Iterator[HTTPServer]: ... -def get_server_client(server) -> Any: ... diff --git a/lib/cheroot/workers/__init__.py b/lib/cheroot/workers/__init__.py deleted file mode 100644 index 098b8f25..00000000 --- a/lib/cheroot/workers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""HTTP workers pool.""" diff --git a/lib/cheroot/workers/__init__.pyi b/lib/cheroot/workers/__init__.pyi deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/cheroot/workers/threadpool.py b/lib/cheroot/workers/threadpool.py deleted file mode 100644 index 2a9878dc..00000000 --- a/lib/cheroot/workers/threadpool.py +++ /dev/null @@ -1,325 +0,0 @@ -"""A thread-based worker pool. - -.. spelling:: - - joinable -""" - -import collections -import threading -import time -import socket -import warnings -import queue - -from jaraco.functools import pass_none - - -__all__ = ('WorkerThread', 'ThreadPool') - - -class TrueyZero: - """Object which equals and does math like the integer 0 but evals True.""" - - def __add__(self, other): - return other - - def __radd__(self, other): - return other - - -trueyzero = TrueyZero() - -_SHUTDOWNREQUEST = None - - -class WorkerThread(threading.Thread): - """Thread which continuously polls a Queue for Connection objects. - - Due to the timing issues of polling a Queue, a WorkerThread does not - check its own 'ready' flag after it has started. To stop the thread, - it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue - (one for each running WorkerThread). - """ - - conn = None - """The current connection pulled off the Queue, or None.""" - - server = None - """The HTTP Server which spawned this thread, and which owns the - Queue and is placing active connections into it.""" - - ready = False - """A simple flag for the calling server to know when this thread - has begun polling the Queue.""" - - def __init__(self, server): - """Initialize WorkerThread instance. - - Args: - server (cheroot.server.HTTPServer): web server object - receiving this request - """ - self.ready = False - self.server = server - - self.requests_seen = 0 - self.bytes_read = 0 - self.bytes_written = 0 - self.start_time = None - self.work_time = 0 - self.stats = { - 'Requests': lambda s: self.requests_seen + ( - self.start_time is None - and trueyzero - or self.conn.requests_seen - ), - 'Bytes Read': lambda s: self.bytes_read + ( - self.start_time is None - and trueyzero - or self.conn.rfile.bytes_read - ), - 'Bytes Written': lambda s: self.bytes_written + ( - self.start_time is None - and trueyzero - or self.conn.wfile.bytes_written - ), - 'Work Time': lambda s: self.work_time + ( - self.start_time is None - and trueyzero - or time.time() - self.start_time - ), - 'Read Throughput': lambda s: s['Bytes Read'](s) / ( - s['Work Time'](s) or 1e-6 - ), - 'Write Throughput': lambda s: s['Bytes Written'](s) / ( - s['Work Time'](s) or 1e-6 - ), - } - threading.Thread.__init__(self) - - def run(self): - """Process incoming HTTP connections. - - Retrieves incoming connections from thread pool. - """ - self.server.stats['Worker Threads'][self.name] = self.stats - try: - self.ready = True - while True: - conn = self.server.requests.get() - if conn is _SHUTDOWNREQUEST: - return - - self.conn = conn - is_stats_enabled = self.server.stats['Enabled'] - if is_stats_enabled: - self.start_time = time.time() - keep_conn_open = False - try: - keep_conn_open = conn.communicate() - finally: - if keep_conn_open: - self.server.put_conn(conn) - else: - conn.close() - if is_stats_enabled: - self.requests_seen += self.conn.requests_seen - self.bytes_read += self.conn.rfile.bytes_read - self.bytes_written += self.conn.wfile.bytes_written - self.work_time += time.time() - self.start_time - self.start_time = None - self.conn = None - except (KeyboardInterrupt, SystemExit) as ex: - self.server.interrupt = ex - - -class ThreadPool: - """A Request Queue for an HTTPServer which pools threads. - - ThreadPool objects must provide min, get(), put(obj), start() - and stop(timeout) attributes. - """ - - def __init__( - self, server, min=10, max=-1, accepted_queue_size=-1, - accepted_queue_timeout=10, - ): - """Initialize HTTP requests queue instance. - - Args: - server (cheroot.server.HTTPServer): web server object - receiving this request - min (int): minimum number of worker threads - max (int): maximum number of worker threads - accepted_queue_size (int): maximum number of active - requests in queue - accepted_queue_timeout (int): timeout for putting request - into queue - """ - self.server = server - self.min = min - self.max = max - self._threads = [] - self._queue = queue.Queue(maxsize=accepted_queue_size) - self._queue_put_timeout = accepted_queue_timeout - self.get = self._queue.get - self._pending_shutdowns = collections.deque() - - def start(self): - """Start the pool of threads.""" - for _ in range(self.min): - self._threads.append(WorkerThread(self.server)) - for worker in self._threads: - worker.name = ( - 'CP Server {worker_name!s}'. - format(worker_name=worker.name) - ) - worker.start() - for worker in self._threads: - while not worker.ready: - time.sleep(.1) - - @property - def idle(self): # noqa: D401; irrelevant for properties - """Number of worker threads which are idle. Read-only.""" # noqa: D401 - idles = len([t for t in self._threads if t.conn is None]) - return max(idles - len(self._pending_shutdowns), 0) - - def put(self, obj): - """Put request into queue. - - Args: - obj (:py:class:`~cheroot.server.HTTPConnection`): HTTP connection - waiting to be processed - """ - self._queue.put(obj, block=True, timeout=self._queue_put_timeout) - - def _clear_dead_threads(self): - # Remove any dead threads from our list - for t in [t for t in self._threads if not t.is_alive()]: - self._threads.remove(t) - try: - self._pending_shutdowns.popleft() - except IndexError: - pass - - def grow(self, amount): - """Spawn new worker threads (not above self.max).""" - if self.max > 0: - budget = max(self.max - len(self._threads), 0) - else: - # self.max <= 0 indicates no maximum - budget = float('inf') - - n_new = min(amount, budget) - - workers = [self._spawn_worker() for i in range(n_new)] - while not all(worker.ready for worker in workers): - time.sleep(.1) - self._threads.extend(workers) - - def _spawn_worker(self): - worker = WorkerThread(self.server) - worker.name = ( - 'CP Server {worker_name!s}'. - format(worker_name=worker.name) - ) - worker.start() - return worker - - def shrink(self, amount): - """Kill off worker threads (not below self.min).""" - # Grow/shrink the pool if necessary. - # Remove any dead threads from our list - amount -= len(self._pending_shutdowns) - self._clear_dead_threads() - if amount <= 0: - return - - # calculate the number of threads above the minimum - n_extra = max(len(self._threads) - self.min, 0) - - # don't remove more than amount - n_to_remove = min(amount, n_extra) - - # put shutdown requests on the queue equal to the number of threads - # to remove. As each request is processed by a worker, that worker - # will terminate and be culled from the list. - for _ in range(n_to_remove): - self._pending_shutdowns.append(None) - self._queue.put(_SHUTDOWNREQUEST) - - def stop(self, timeout=5): - """Terminate all worker threads. - - Args: - timeout (int): time to wait for threads to stop gracefully - """ - # for compatability, negative timeouts are treated like None - # TODO: treat negative timeouts like already expired timeouts - if timeout is not None and timeout < 0: - timeout = None - warnings.warning( - 'In the future, negative timeouts to Server.stop() ' - 'will be equivalent to a timeout of zero.', - stacklevel=2, - ) - - if timeout is not None: - endtime = time.time() + timeout - - # Must shut down threads here so the code that calls - # this method can know when all threads are stopped. - for worker in self._threads: - self._queue.put(_SHUTDOWNREQUEST) - - ignored_errors = ( - # Raised when start_response called >1 time w/o exc_info or - # wsgi write is called before start_response. See cheroot#261 - RuntimeError, - # Ignore repeated Ctrl-C. See cherrypy#691. - KeyboardInterrupt, - ) - - for worker in self._clear_threads(): - remaining_time = timeout and endtime - time.time() - try: - worker.join(remaining_time) - if worker.is_alive(): - # Timeout exhausted; forcibly shut down the socket. - self._force_close(worker.conn) - worker.join() - except ignored_errors: - pass - - @staticmethod - @pass_none - def _force_close(conn): - if conn.rfile.closed: - return - try: - try: - conn.socket.shutdown(socket.SHUT_RD) - except TypeError: - # pyOpenSSL sockets don't take an arg - conn.socket.shutdown() - except OSError: - # shutdown sometimes fails (race with 'closed' check?) - # ref #238 - pass - - def _clear_threads(self): - """Clear self._threads and yield all joinable threads.""" - # threads = pop_all(self._threads) - threads, self._threads[:] = self._threads[:], [] - return ( - thread - for thread in threads - if thread is not threading.current_thread() - ) - - @property - def qsize(self): - """Return the queue size.""" - return self._queue.qsize() diff --git a/lib/cheroot/workers/threadpool.pyi b/lib/cheroot/workers/threadpool.pyi deleted file mode 100644 index 201d3914..00000000 --- a/lib/cheroot/workers/threadpool.pyi +++ /dev/null @@ -1,37 +0,0 @@ -import threading -from typing import Any - -class TrueyZero: - def __add__(self, other): ... - def __radd__(self, other): ... - -trueyzero: TrueyZero - -class WorkerThread(threading.Thread): - conn: Any - server: Any - ready: bool - requests_seen: int - bytes_read: int - bytes_written: int - start_time: Any - work_time: int - stats: Any - def __init__(self, server): ... - def run(self) -> None: ... - -class ThreadPool: - server: Any - min: Any - max: Any - get: Any - def __init__(self, server, min: int = ..., max: int = ..., accepted_queue_size: int = ..., accepted_queue_timeout: int = ...) -> None: ... - def start(self) -> None: ... - @property - def idle(self): ... - def put(self, obj) -> None: ... - def grow(self, amount) -> None: ... - def shrink(self, amount) -> None: ... - def stop(self, timeout: int = ...) -> None: ... - @property - def qsize(self) -> int: ... diff --git a/lib/cheroot/wsgi.py b/lib/cheroot/wsgi.py deleted file mode 100644 index 82faca3e..00000000 --- a/lib/cheroot/wsgi.py +++ /dev/null @@ -1,410 +0,0 @@ -"""This class holds Cheroot WSGI server implementation. - -Simplest example on how to use this server:: - - from cheroot import wsgi - - def my_crazy_app(environ, start_response): - status = '200 OK' - response_headers = [('Content-type','text/plain')] - start_response(status, response_headers) - return [b'Hello world!'] - - addr = '0.0.0.0', 8070 - server = wsgi.Server(addr, my_crazy_app) - server.start() - -The Cheroot WSGI server can serve as many WSGI applications -as you want in one instance by using a PathInfoDispatcher:: - - path_map = { - '/': my_crazy_app, - '/blog': my_blog_app, - } - d = wsgi.PathInfoDispatcher(path_map) - server = wsgi.Server(addr, d) -""" - -import sys - -from . import server -from .workers import threadpool -from ._compat import ntob, bton - - -class Server(server.HTTPServer): - """A subclass of HTTPServer which calls a WSGI application.""" - - wsgi_version = (1, 0) - """The version of WSGI to produce.""" - - def __init__( - self, bind_addr, wsgi_app, numthreads=10, server_name=None, - max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5, - accepted_queue_size=-1, accepted_queue_timeout=10, - peercreds_enabled=False, peercreds_resolve_enabled=False, - ): - """Initialize WSGI Server instance. - - Args: - bind_addr (tuple): network interface to listen to - wsgi_app (callable): WSGI application callable - numthreads (int): number of threads for WSGI thread pool - server_name (str): web server name to be advertised via - Server HTTP header - max (int): maximum number of worker threads - request_queue_size (int): the 'backlog' arg to - socket.listen(); max queued connections - timeout (int): the timeout in seconds for accepted connections - shutdown_timeout (int): the total time, in seconds, to - wait for worker threads to cleanly exit - accepted_queue_size (int): maximum number of active - requests in queue - accepted_queue_timeout (int): timeout for putting request - into queue - """ - super(Server, self).__init__( - bind_addr, - gateway=wsgi_gateways[self.wsgi_version], - server_name=server_name, - peercreds_enabled=peercreds_enabled, - peercreds_resolve_enabled=peercreds_resolve_enabled, - ) - self.wsgi_app = wsgi_app - self.request_queue_size = request_queue_size - self.timeout = timeout - self.shutdown_timeout = shutdown_timeout - self.requests = threadpool.ThreadPool( - self, min=numthreads or 1, max=max, - accepted_queue_size=accepted_queue_size, - accepted_queue_timeout=accepted_queue_timeout, - ) - - @property - def numthreads(self): - """Set minimum number of threads.""" - return self.requests.min - - @numthreads.setter - def numthreads(self, value): - self.requests.min = value - - -class Gateway(server.Gateway): - """A base class to interface HTTPServer with WSGI.""" - - def __init__(self, req): - """Initialize WSGI Gateway instance with request. - - Args: - req (HTTPRequest): current HTTP request - """ - super(Gateway, self).__init__(req) - self.started_response = False - self.env = self.get_environ() - self.remaining_bytes_out = None - - @classmethod - def gateway_map(cls): - """Create a mapping of gateways and their versions. - - Returns: - dict[tuple[int,int],class]: map of gateway version and - corresponding class - - """ - return {gw.version: gw for gw in cls.__subclasses__()} - - def get_environ(self): - """Return a new environ dict targeting the given wsgi.version.""" - raise NotImplementedError # pragma: no cover - - def respond(self): - """Process the current request. - - From :pep:`333`: - - The start_response callable must not actually transmit - the response headers. Instead, it must store them for the - server or gateway to transmit only after the first - iteration of the application return value that yields - a NON-EMPTY string, or upon the application's first - invocation of the write() callable. - """ - response = self.req.server.wsgi_app(self.env, self.start_response) - try: - for chunk in filter(None, response): - if not isinstance(chunk, bytes): - raise ValueError('WSGI Applications must yield bytes') - self.write(chunk) - finally: - # Send headers if not already sent - self.req.ensure_headers_sent() - if hasattr(response, 'close'): - response.close() - - def start_response(self, status, headers, exc_info=None): # noqa: WPS238 - """WSGI callable to begin the HTTP response.""" - # "The application may call start_response more than once, - # if and only if the exc_info argument is provided." - if self.started_response and not exc_info: - raise RuntimeError( - 'WSGI start_response called a second ' - 'time with no exc_info.', - ) - self.started_response = True - - # "if exc_info is provided, and the HTTP headers have already been - # sent, start_response must raise an error, and should raise the - # exc_info tuple." - if self.req.sent_headers: - value = exc_info[1] - raise value - - self.req.status = self._encode_status(status) - - for k, v in headers: - if not isinstance(k, str): - raise TypeError( - 'WSGI response header key %r is not of type str.' % k, - ) - if not isinstance(v, str): - raise TypeError( - 'WSGI response header value %r is not of type str.' % v, - ) - if k.lower() == 'content-length': - self.remaining_bytes_out = int(v) - out_header = ntob(k), ntob(v) - self.req.outheaders.append(out_header) - - return self.write - - @staticmethod - def _encode_status(status): - """Cast status to bytes representation of current Python version. - - According to :pep:`3333`, when using Python 3, the response status - and headers must be bytes masquerading as Unicode; that is, they - must be of type "str" but are restricted to code points in the - "Latin-1" set. - """ - if not isinstance(status, str): - raise TypeError('WSGI response status is not of type str.') - return status.encode('ISO-8859-1') - - def write(self, chunk): - """WSGI callable to write unbuffered data to the client. - - This method is also used internally by start_response (to write - data from the iterable returned by the WSGI application). - """ - if not self.started_response: - raise RuntimeError('WSGI write called before start_response.') - - chunklen = len(chunk) - rbo = self.remaining_bytes_out - if rbo is not None and chunklen > rbo: - if not self.req.sent_headers: - # Whew. We can send a 500 to the client. - self.req.simple_response( - '500 Internal Server Error', - 'The requested resource returned more bytes than the ' - 'declared Content-Length.', - ) - else: - # Dang. We have probably already sent data. Truncate the chunk - # to fit (so the client doesn't hang) and raise an error later. - chunk = chunk[:rbo] - - self.req.ensure_headers_sent() - - self.req.write(chunk) - - if rbo is not None: - rbo -= chunklen - if rbo < 0: - raise ValueError( - 'Response body exceeds the declared Content-Length.', - ) - - -class Gateway_10(Gateway): - """A Gateway class to interface HTTPServer with WSGI 1.0.x.""" - - version = 1, 0 - - def get_environ(self): - """Return a new environ dict targeting the given wsgi.version.""" - req = self.req - req_conn = req.conn - env = { - # set a non-standard environ entry so the WSGI app can know what - # the *real* server protocol is (and what features to support). - # See http://www.faqs.org/rfcs/rfc2145.html. - 'ACTUAL_SERVER_PROTOCOL': req.server.protocol, - 'PATH_INFO': bton(req.path), - 'QUERY_STRING': bton(req.qs), - 'REMOTE_ADDR': req_conn.remote_addr or '', - 'REMOTE_PORT': str(req_conn.remote_port or ''), - 'REQUEST_METHOD': bton(req.method), - 'REQUEST_URI': bton(req.uri), - 'SCRIPT_NAME': '', - 'SERVER_NAME': req.server.server_name, - # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. - 'SERVER_PROTOCOL': bton(req.request_protocol), - 'SERVER_SOFTWARE': req.server.software, - 'wsgi.errors': sys.stderr, - 'wsgi.input': req.rfile, - 'wsgi.input_terminated': bool(req.chunked_read), - 'wsgi.multiprocess': False, - 'wsgi.multithread': True, - 'wsgi.run_once': False, - 'wsgi.url_scheme': bton(req.scheme), - 'wsgi.version': self.version, - } - - if isinstance(req.server.bind_addr, str): - # AF_UNIX. This isn't really allowed by WSGI, which doesn't - # address unix domain sockets. But it's better than nothing. - env['SERVER_PORT'] = '' - try: - env['X_REMOTE_PID'] = str(req_conn.peer_pid) - env['X_REMOTE_UID'] = str(req_conn.peer_uid) - env['X_REMOTE_GID'] = str(req_conn.peer_gid) - - env['X_REMOTE_USER'] = str(req_conn.peer_user) - env['X_REMOTE_GROUP'] = str(req_conn.peer_group) - - env['REMOTE_USER'] = env['X_REMOTE_USER'] - except RuntimeError: - """Unable to retrieve peer creds data. - - Unsupported by current kernel or socket error happened, or - unsupported socket type, or disabled. - """ - else: - env['SERVER_PORT'] = str(req.server.bind_addr[1]) - - # Request headers - env.update( - ( - 'HTTP_{header_name!s}'. - format(header_name=bton(k).upper().replace('-', '_')), - bton(v), - ) - for k, v in req.inheaders.items() - ) - - # CONTENT_TYPE/CONTENT_LENGTH - ct = env.pop('HTTP_CONTENT_TYPE', None) - if ct is not None: - env['CONTENT_TYPE'] = ct - cl = env.pop('HTTP_CONTENT_LENGTH', None) - if cl is not None: - env['CONTENT_LENGTH'] = cl - - if req.conn.ssl_env: - env.update(req.conn.ssl_env) - - return env - - -class Gateway_u0(Gateway_10): - """A Gateway class to interface HTTPServer with WSGI u.0. - - WSGI u.0 is an experimental protocol, which uses Unicode for keys - and values in both Python 2 and Python 3. - """ - - version = 'u', 0 - - def get_environ(self): - """Return a new environ dict targeting the given wsgi.version.""" - req = self.req - env_10 = super(Gateway_u0, self).get_environ() - env = dict(env_10.items()) - - # Request-URI - enc = env.setdefault('wsgi.url_encoding', 'utf-8') - try: - env['PATH_INFO'] = req.path.decode(enc) - env['QUERY_STRING'] = req.qs.decode(enc) - except UnicodeDecodeError: - # Fall back to latin 1 so apps can transcode if needed. - env['wsgi.url_encoding'] = 'ISO-8859-1' - env['PATH_INFO'] = env_10['PATH_INFO'] - env['QUERY_STRING'] = env_10['QUERY_STRING'] - - env.update(env.items()) - - return env - - -wsgi_gateways = Gateway.gateway_map() - - -class PathInfoDispatcher: - """A WSGI dispatcher for dispatch based on the PATH_INFO.""" - - def __init__(self, apps): - """Initialize path info WSGI app dispatcher. - - Args: - apps (dict[str,object]|list[tuple[str,object]]): URI prefix - and WSGI app pairs - """ - try: - apps = list(apps.items()) - except AttributeError: - pass - - # Sort the apps by len(path), descending - def by_path_len(app): - return len(app[0]) - apps.sort(key=by_path_len, reverse=True) - - # The path_prefix strings must start, but not end, with a slash. - # Use "" instead of "/". - self.apps = [(p.rstrip('/'), a) for p, a in apps] - - def __call__(self, environ, start_response): - """Process incoming WSGI request. - - Ref: :pep:`3333` - - Args: - environ (Mapping): a dict containing WSGI environment variables - start_response (callable): function, which sets response - status and headers - - Returns: - list[bytes]: iterable containing bytes to be returned in - HTTP response body - - """ - path = environ['PATH_INFO'] or '/' - for p, app in self.apps: - # The apps list should be sorted by length, descending. - if path.startswith('{path!s}/'.format(path=p)) or path == p: - environ = environ.copy() - environ['SCRIPT_NAME'] = environ.get('SCRIPT_NAME', '') + p - environ['PATH_INFO'] = path[len(p):] - return app(environ, start_response) - - start_response( - '404 Not Found', [ - ('Content-Type', 'text/plain'), - ('Content-Length', '0'), - ], - ) - return [''] - - -# compatibility aliases -globals().update( - WSGIServer=Server, - WSGIGateway=Gateway, - WSGIGateway_u0=Gateway_u0, - WSGIGateway_10=Gateway_10, - WSGIPathInfoDispatcher=PathInfoDispatcher, -) diff --git a/lib/cheroot/wsgi.pyi b/lib/cheroot/wsgi.pyi deleted file mode 100644 index 96075633..00000000 --- a/lib/cheroot/wsgi.pyi +++ /dev/null @@ -1,49 +0,0 @@ -from . import server -from typing import Any - -class Server(server.HTTPServer): - wsgi_version: Any - wsgi_app: Any - request_queue_size: Any - timeout: Any - shutdown_timeout: Any - requests: Any - def __init__(self, bind_addr, wsgi_app, numthreads: int = ..., server_name: Any | None = ..., max: int = ..., request_queue_size: int = ..., timeout: int = ..., shutdown_timeout: int = ..., accepted_queue_size: int = ..., accepted_queue_timeout: int = ..., peercreds_enabled: bool = ..., peercreds_resolve_enabled: bool = ...) -> None: ... - @property - def numthreads(self): ... - @numthreads.setter - def numthreads(self, value) -> None: ... - -class Gateway(server.Gateway): - started_response: bool - env: Any - remaining_bytes_out: Any - def __init__(self, req) -> None: ... - @classmethod - def gateway_map(cls): ... - def get_environ(self) -> None: ... - def respond(self) -> None: ... - def start_response(self, status, headers, exc_info: Any | None = ...): ... - def write(self, chunk) -> None: ... - -class Gateway_10(Gateway): - version: Any - def get_environ(self): ... - -class Gateway_u0(Gateway_10): - version: Any - def get_environ(self): ... - -wsgi_gateways: Any - -class PathInfoDispatcher: - apps: Any - def __init__(self, apps): ... - def __call__(self, environ, start_response): ... - - -WSGIServer = Server -WSGIGateway = Gateway -WSGIGateway_u0 = Gateway_u0 -WSGIGateway_10 = Gateway_10 -WSGIPathInfoDispatcher = PathInfoDispatcher diff --git a/lib/cherrypy/__init__.py b/lib/cherrypy/__init__.py deleted file mode 100644 index 8e27c812..00000000 --- a/lib/cherrypy/__init__.py +++ /dev/null @@ -1,370 +0,0 @@ -"""CherryPy is a pythonic, object-oriented HTTP framework. - -CherryPy consists of not one, but four separate API layers. - -The APPLICATION LAYER is the simplest. CherryPy applications are written as -a tree of classes and methods, where each branch in the tree corresponds to -a branch in the URL path. Each method is a 'page handler', which receives -GET and POST params as keyword arguments, and returns or yields the (HTML) -body of the response. The special method name 'index' is used for paths -that end in a slash, and the special method name 'default' is used to -handle multiple paths via a single handler. This layer also includes: - - * the 'exposed' attribute (and cherrypy.expose) - * cherrypy.quickstart() - * _cp_config attributes - * cherrypy.tools (including cherrypy.session) - * cherrypy.url() - -The ENVIRONMENT LAYER is used by developers at all levels. It provides -information about the current request and response, plus the application -and server environment, via a (default) set of top-level objects: - - * cherrypy.request - * cherrypy.response - * cherrypy.engine - * cherrypy.server - * cherrypy.tree - * cherrypy.config - * cherrypy.thread_data - * cherrypy.log - * cherrypy.HTTPError, NotFound, and HTTPRedirect - * cherrypy.lib - -The EXTENSION LAYER allows advanced users to construct and share their own -plugins. It consists of: - - * Hook API - * Tool API - * Toolbox API - * Dispatch API - * Config Namespace API - -Finally, there is the CORE LAYER, which uses the core API's to construct -the default components which are available at higher layers. You can think -of the default components as the 'reference implementation' for CherryPy. -Megaframeworks (and advanced users) may replace the default components -with customized or extended components. The core API's are: - - * Application API - * Engine API - * Request API - * Server API - * WSGI API - -These API's are described in the `CherryPy specification -`_. -""" - -try: - import pkg_resources -except ImportError: - pass - -from threading import local as _local - -from ._cperror import ( - HTTPError, HTTPRedirect, InternalRedirect, - NotFound, CherryPyException, -) - -from . import _cpdispatch as dispatch - -from ._cptools import default_toolbox as tools, Tool -from ._helper import expose, popargs, url - -from . import _cprequest, _cpserver, _cptree, _cplogging, _cpconfig - -import cherrypy.lib.httputil as _httputil - -from ._cptree import Application -from . import _cpwsgi as wsgi - -from . import process -try: - from .process import win32 - engine = win32.Win32Bus() - engine.console_control_handler = win32.ConsoleCtrlHandler(engine) - del win32 -except ImportError: - engine = process.bus - -from . import _cpchecker - -__all__ = ( - 'HTTPError', 'HTTPRedirect', 'InternalRedirect', - 'NotFound', 'CherryPyException', - 'dispatch', 'tools', 'Tool', 'Application', - 'wsgi', 'process', 'tree', 'engine', - 'quickstart', 'serving', 'request', 'response', 'thread_data', - 'log', 'expose', 'popargs', 'url', 'config', -) - - -__import__('cherrypy._cptools') -__import__('cherrypy._cprequest') - - -tree = _cptree.Tree() - - -try: - __version__ = pkg_resources.require('cherrypy')[0].version -except Exception: - __version__ = 'unknown' - - -engine.listeners['before_request'] = set() -engine.listeners['after_request'] = set() - - -engine.autoreload = process.plugins.Autoreloader(engine) -engine.autoreload.subscribe() - -engine.thread_manager = process.plugins.ThreadManager(engine) -engine.thread_manager.subscribe() - -engine.signal_handler = process.plugins.SignalHandler(engine) - - -class _HandleSignalsPlugin(object): - """Handle signals from other processes. - - Based on the configured platform handlers above. - """ - - def __init__(self, bus): - self.bus = bus - - def subscribe(self): - """Add the handlers based on the platform.""" - if hasattr(self.bus, 'signal_handler'): - self.bus.signal_handler.subscribe() - if hasattr(self.bus, 'console_control_handler'): - self.bus.console_control_handler.subscribe() - - -engine.signals = _HandleSignalsPlugin(engine) - - -server = _cpserver.Server() -server.subscribe() - - -def quickstart(root=None, script_name='', config=None): - """Mount the given root, start the builtin server (and engine), then block. - - root: an instance of a "controller class" (a collection of page handler - methods) which represents the root of the application. - script_name: a string containing the "mount point" of the application. - This should start with a slash, and be the path portion of the URL - at which to mount the given root. For example, if root.index() will - handle requests to "http://www.example.com:8080/dept/app1/", then - the script_name argument would be "/dept/app1". - - It MUST NOT end in a slash. If the script_name refers to the root - of the URI, it MUST be an empty string (not "/"). - config: a file or dict containing application config. If this contains - a [global] section, those entries will be used in the global - (site-wide) config. - """ - if config: - _global_conf_alias.update(config) - - tree.mount(root, script_name, config) - - engine.signals.subscribe() - engine.start() - engine.block() - - -class _Serving(_local): - """An interface for registering request and response objects. - - Rather than have a separate "thread local" object for the request and - the response, this class works as a single threadlocal container for - both objects (and any others which developers wish to define). In this - way, we can easily dump those objects when we stop/start a new HTTP - conversation, yet still refer to them as module-level globals in a - thread-safe way. - """ - - request = _cprequest.Request(_httputil.Host('127.0.0.1', 80), - _httputil.Host('127.0.0.1', 1111)) - """ - The request object for the current thread. In the main thread, - and any threads which are not receiving HTTP requests, this is None.""" - - response = _cprequest.Response() - """ - The response object for the current thread. In the main thread, - and any threads which are not receiving HTTP requests, this is None.""" - - def load(self, request, response): - self.request = request - self.response = response - - def clear(self): - """Remove all attributes of self.""" - self.__dict__.clear() - - -serving = _Serving() - - -class _ThreadLocalProxy(object): - - __slots__ = ['__attrname__', '__dict__'] - - def __init__(self, attrname): - self.__attrname__ = attrname - - def __getattr__(self, name): - child = getattr(serving, self.__attrname__) - return getattr(child, name) - - def __setattr__(self, name, value): - if name in ('__attrname__', ): - object.__setattr__(self, name, value) - else: - child = getattr(serving, self.__attrname__) - setattr(child, name, value) - - def __delattr__(self, name): - child = getattr(serving, self.__attrname__) - delattr(child, name) - - @property - def __dict__(self): - child = getattr(serving, self.__attrname__) - d = child.__class__.__dict__.copy() - d.update(child.__dict__) - return d - - def __getitem__(self, key): - child = getattr(serving, self.__attrname__) - return child[key] - - def __setitem__(self, key, value): - child = getattr(serving, self.__attrname__) - child[key] = value - - def __delitem__(self, key): - child = getattr(serving, self.__attrname__) - del child[key] - - def __contains__(self, key): - child = getattr(serving, self.__attrname__) - return key in child - - def __len__(self): - child = getattr(serving, self.__attrname__) - return len(child) - - def __nonzero__(self): - child = getattr(serving, self.__attrname__) - return bool(child) - # Python 3 - __bool__ = __nonzero__ - - -# Create request and response object (the same objects will be used -# throughout the entire life of the webserver, but will redirect -# to the "serving" object) -request = _ThreadLocalProxy('request') -response = _ThreadLocalProxy('response') - -# Create thread_data object as a thread-specific all-purpose storage - - -class _ThreadData(_local): - """A container for thread-specific data.""" - - -thread_data = _ThreadData() - - -# Monkeypatch pydoc to allow help() to go through the threadlocal proxy. -# Jan 2007: no Googleable examples of anyone else replacing pydoc.resolve. -# The only other way would be to change what is returned from type(request) -# and that's not possible in pure Python (you'd have to fake ob_type). -def _cherrypy_pydoc_resolve(thing, forceload=0): - """Given an object or a path to an object, get the object and its name.""" - if isinstance(thing, _ThreadLocalProxy): - thing = getattr(serving, thing.__attrname__) - return _pydoc._builtin_resolve(thing, forceload) - - -try: - import pydoc as _pydoc - _pydoc._builtin_resolve = _pydoc.resolve - _pydoc.resolve = _cherrypy_pydoc_resolve -except ImportError: - pass - - -class _GlobalLogManager(_cplogging.LogManager): - """A site-wide LogManager; routes to app.log or global log as appropriate. - - This :class:`LogManager` implements - cherrypy.log() and cherrypy.log.access(). If either - function is called during a request, the message will be sent to the - logger for the current Application. If they are called outside of a - request, the message will be sent to the site-wide logger. - """ - - def __call__(self, *args, **kwargs): - """Log the given message to the app.log or global log. - - Log the given message to the app.log or global - log as appropriate. - """ - # Do NOT use try/except here. See - # https://github.com/cherrypy/cherrypy/issues/945 - if hasattr(request, 'app') and hasattr(request.app, 'log'): - log = request.app.log - else: - log = self - return log.error(*args, **kwargs) - - def access(self): - """Log an access message to the app.log or global log. - - Log the given message to the app.log or global - log as appropriate. - """ - try: - return request.app.log.access() - except AttributeError: - return _cplogging.LogManager.access(self) - - -log = _GlobalLogManager() -# Set a default screen handler on the global log. -log.screen = True -log.error_file = '' -# Using an access file makes CP about 10% slower. Leave off by default. -log.access_file = '' - - -@engine.subscribe('log') -def _buslog(msg, level): - log.error(msg, 'ENGINE', severity=level) - - -# Use _global_conf_alias so quickstart can use 'config' as an arg -# without shadowing cherrypy.config. -config = _global_conf_alias = _cpconfig.Config() -config.defaults = { - 'tools.log_tracebacks.on': True, - 'tools.log_headers.on': True, - 'tools.trailing_slash.on': True, - 'tools.encode.on': True -} -config.namespaces['log'] = lambda k, v: setattr(log, k, v) -config.namespaces['checker'] = lambda k, v: setattr(checker, k, v) -# Must reset to get our defaults applied. -config.reset() - -checker = _cpchecker.Checker() -engine.subscribe('start', checker) diff --git a/lib/cherrypy/__main__.py b/lib/cherrypy/__main__.py deleted file mode 100644 index 6674f7cb..00000000 --- a/lib/cherrypy/__main__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""CherryPy'd cherryd daemon runner.""" -from cherrypy.daemon import run - - -__name__ == '__main__' and run() diff --git a/lib/cherrypy/_cpchecker.py b/lib/cherrypy/_cpchecker.py deleted file mode 100644 index f26f319c..00000000 --- a/lib/cherrypy/_cpchecker.py +++ /dev/null @@ -1,323 +0,0 @@ -"""Checker for CherryPy sites and mounted apps.""" -import os -import warnings -import builtins - -import cherrypy - - -class Checker(object): - """A checker for CherryPy sites and their mounted applications. - - When this object is called at engine startup, it executes each - of its own methods whose names start with ``check_``. If you wish - to disable selected checks, simply add a line in your global - config which sets the appropriate method to False:: - - [global] - checker.check_skipped_app_config = False - - You may also dynamically add or replace ``check_*`` methods in this way. - """ - - on = True - """If True (the default), run all checks; if False, turn off all checks.""" - - def __init__(self): - """Initialize Checker instance.""" - self._populate_known_types() - - def __call__(self): - """Run all check_* methods.""" - if self.on: - oldformatwarning = warnings.formatwarning - warnings.formatwarning = self.formatwarning - try: - for name in dir(self): - if name.startswith('check_'): - method = getattr(self, name) - if method and hasattr(method, '__call__'): - method() - finally: - warnings.formatwarning = oldformatwarning - - def formatwarning(self, message, category, filename, lineno, line=None): - """Format a warning.""" - return 'CherryPy Checker:\n%s\n\n' % message - - # This value should be set inside _cpconfig. - global_config_contained_paths = False - - def check_app_config_entries_dont_start_with_script_name(self): - """Check for App config with sections that repeat script_name.""" - for sn, app in cherrypy.tree.apps.items(): - if not isinstance(app, cherrypy.Application): - continue - if not app.config: - continue - if sn == '': - continue - sn_atoms = sn.strip('/').split('/') - for key in app.config.keys(): - key_atoms = key.strip('/').split('/') - if key_atoms[:len(sn_atoms)] == sn_atoms: - warnings.warn( - 'The application mounted at %r has config ' - 'entries that start with its script name: %r' % (sn, - key)) - - def check_site_config_entries_in_app_config(self): - """Check for mounted Applications that have site-scoped config.""" - for sn, app in cherrypy.tree.apps.items(): - if not isinstance(app, cherrypy.Application): - continue - - msg = [] - for section, entries in app.config.items(): - if section.startswith('/'): - for key, value in entries.items(): - for n in ('engine.', 'server.', 'tree.', 'checker.'): - if key.startswith(n): - msg.append('[%s] %s = %s' % - (section, key, value)) - if msg: - msg.insert(0, - 'The application mounted at %r contains the ' - 'following config entries, which are only allowed ' - 'in site-wide config. Move them to a [global] ' - 'section and pass them to cherrypy.config.update() ' - 'instead of tree.mount().' % sn) - warnings.warn(os.linesep.join(msg)) - - def check_skipped_app_config(self): - """Check for mounted Applications that have no config.""" - for sn, app in cherrypy.tree.apps.items(): - if not isinstance(app, cherrypy.Application): - continue - if not app.config: - msg = 'The Application mounted at %r has an empty config.' % sn - if self.global_config_contained_paths: - msg += (' It looks like the config you passed to ' - 'cherrypy.config.update() contains application-' - 'specific sections. You must explicitly pass ' - 'application config via ' - 'cherrypy.tree.mount(..., config=app_config)') - warnings.warn(msg) - return - - def check_app_config_brackets(self): - """Check for App config with extraneous brackets in section names.""" - for sn, app in cherrypy.tree.apps.items(): - if not isinstance(app, cherrypy.Application): - continue - if not app.config: - continue - for key in app.config.keys(): - if key.startswith('[') or key.endswith(']'): - warnings.warn( - 'The application mounted at %r has config ' - 'section names with extraneous brackets: %r. ' - 'Config *files* need brackets; config *dicts* ' - '(e.g. passed to tree.mount) do not.' % (sn, key)) - - def check_static_paths(self): - """Check Application config for incorrect static paths.""" - # Use the dummy Request object in the main thread. - request = cherrypy.request - for sn, app in cherrypy.tree.apps.items(): - if not isinstance(app, cherrypy.Application): - continue - request.app = app - for section in app.config: - # get_resource will populate request.config - request.get_resource(section + '/dummy.html') - conf = request.config.get - - if conf('tools.staticdir.on', False): - msg = '' - root = conf('tools.staticdir.root') - dir = conf('tools.staticdir.dir') - if dir is None: - msg = 'tools.staticdir.dir is not set.' - else: - fulldir = '' - if os.path.isabs(dir): - fulldir = dir - if root: - msg = ('dir is an absolute path, even ' - 'though a root is provided.') - testdir = os.path.join(root, dir[1:]) - if os.path.exists(testdir): - msg += ( - '\nIf you meant to serve the ' - 'filesystem folder at %r, remove the ' - 'leading slash from dir.' % (testdir,)) - else: - if not root: - msg = ( - 'dir is a relative path and ' - 'no root provided.') - else: - fulldir = os.path.join(root, dir) - if not os.path.isabs(fulldir): - msg = ('%r is not an absolute path.' % ( - fulldir,)) - - if fulldir and not os.path.exists(fulldir): - if msg: - msg += '\n' - msg += ('%r (root + dir) is not an existing ' - 'filesystem path.' % fulldir) - - if msg: - warnings.warn('%s\nsection: [%s]\nroot: %r\ndir: %r' - % (msg, section, root, dir)) - - # -------------------------- Compatibility -------------------------- # - obsolete = { - 'server.default_content_type': 'tools.response_headers.headers', - 'log_access_file': 'log.access_file', - 'log_config_options': None, - 'log_file': 'log.error_file', - 'log_file_not_found': None, - 'log_request_headers': 'tools.log_headers.on', - 'log_to_screen': 'log.screen', - 'show_tracebacks': 'request.show_tracebacks', - 'throw_errors': 'request.throw_errors', - 'profiler.on': ('cherrypy.tree.mount(profiler.make_app(' - 'cherrypy.Application(Root())))'), - } - - deprecated = {} - - def _compat(self, config): - """Process config and warn on each obsolete or deprecated entry.""" - for section, conf in config.items(): - if isinstance(conf, dict): - for k in conf: - if k in self.obsolete: - warnings.warn('%r is obsolete. Use %r instead.\n' - 'section: [%s]' % - (k, self.obsolete[k], section)) - elif k in self.deprecated: - warnings.warn('%r is deprecated. Use %r instead.\n' - 'section: [%s]' % - (k, self.deprecated[k], section)) - else: - if section in self.obsolete: - warnings.warn('%r is obsolete. Use %r instead.' - % (section, self.obsolete[section])) - elif section in self.deprecated: - warnings.warn('%r is deprecated. Use %r instead.' - % (section, self.deprecated[section])) - - def check_compatibility(self): - """Process config and warn on each obsolete or deprecated entry.""" - self._compat(cherrypy.config) - for sn, app in cherrypy.tree.apps.items(): - if not isinstance(app, cherrypy.Application): - continue - self._compat(app.config) - - # ------------------------ Known Namespaces ------------------------ # - extra_config_namespaces = [] - - def _known_ns(self, app): - ns = ['wsgi'] - ns.extend(app.toolboxes) - ns.extend(app.namespaces) - ns.extend(app.request_class.namespaces) - ns.extend(cherrypy.config.namespaces) - ns += self.extra_config_namespaces - - for section, conf in app.config.items(): - is_path_section = section.startswith('/') - if is_path_section and isinstance(conf, dict): - for k in conf: - atoms = k.split('.') - if len(atoms) > 1: - if atoms[0] not in ns: - # Spit out a special warning if a known - # namespace is preceded by "cherrypy." - if atoms[0] == 'cherrypy' and atoms[1] in ns: - msg = ( - 'The config entry %r is invalid; ' - 'try %r instead.\nsection: [%s]' - % (k, '.'.join(atoms[1:]), section)) - else: - msg = ( - 'The config entry %r is invalid, ' - 'because the %r config namespace ' - 'is unknown.\n' - 'section: [%s]' % (k, atoms[0], section)) - warnings.warn(msg) - elif atoms[0] == 'tools': - if atoms[1] not in dir(cherrypy.tools): - msg = ( - 'The config entry %r may be invalid, ' - 'because the %r tool was not found.\n' - 'section: [%s]' % (k, atoms[1], section)) - warnings.warn(msg) - - def check_config_namespaces(self): - """Process config and warn on each unknown config namespace.""" - for sn, app in cherrypy.tree.apps.items(): - if not isinstance(app, cherrypy.Application): - continue - self._known_ns(app) - - # -------------------------- Config Types -------------------------- # - known_config_types = {} - - def _populate_known_types(self): - b = [x for x in vars(builtins).values() - if type(x) is type(str)] - - def traverse(obj, namespace): - for name in dir(obj): - # Hack for 3.2's warning about body_params - if name == 'body_params': - continue - vtype = type(getattr(obj, name, None)) - if vtype in b: - self.known_config_types[namespace + '.' + name] = vtype - - traverse(cherrypy.request, 'request') - traverse(cherrypy.response, 'response') - traverse(cherrypy.server, 'server') - traverse(cherrypy.engine, 'engine') - traverse(cherrypy.log, 'log') - - def _known_types(self, config): - msg = ('The config entry %r in section %r is of type %r, ' - 'which does not match the expected type %r.') - - for section, conf in config.items(): - if not isinstance(conf, dict): - conf = {section: conf} - for k, v in conf.items(): - if v is not None: - expected_type = self.known_config_types.get(k, None) - vtype = type(v) - if expected_type and vtype != expected_type: - warnings.warn(msg % (k, section, vtype.__name__, - expected_type.__name__)) - - def check_config_types(self): - """Assert that config values are of the same type as default values.""" - self._known_types(cherrypy.config) - for sn, app in cherrypy.tree.apps.items(): - if not isinstance(app, cherrypy.Application): - continue - self._known_types(app.config) - - # -------------------- Specific config warnings -------------------- # - def check_localhost(self): - """Warn if any socket_host is 'localhost'. See #711.""" - for k, v in cherrypy.config.items(): - if k == 'server.socket_host' and v == 'localhost': - warnings.warn("The use of 'localhost' as a socket host can " - 'cause problems on newer systems, since ' - "'localhost' can map to either an IPv4 or an " - "IPv6 address. You should use '127.0.0.1' " - "or '[::1]' instead.") diff --git a/lib/cherrypy/_cpcompat.py b/lib/cherrypy/_cpcompat.py deleted file mode 100644 index a43f6d36..00000000 --- a/lib/cherrypy/_cpcompat.py +++ /dev/null @@ -1,59 +0,0 @@ -"""Compatibility code for using CherryPy with various versions of Python. - -To retain compatibility with older Python versions, this module provides a -useful abstraction over the differences between Python versions, sometimes by -preferring a newer idiom, sometimes an older one, and sometimes a custom one. - -In particular, Python 2 uses str and '' for byte strings, while Python 3 -uses str and '' for unicode strings. We will call each of these the 'native -string' type for each version. Because of this major difference, this module -provides -two functions: 'ntob', which translates native strings (of type 'str') into -byte strings regardless of Python version, and 'ntou', which translates native -strings to unicode strings. - -Try not to use the compatibility functions 'ntob', 'ntou', 'tonative'. -They were created with Python 2.3-2.5 compatibility in mind. -Instead, use unicode literals (from __future__) and bytes literals -and their .encode/.decode methods as needed. -""" - -import http.client - - -def ntob(n, encoding='ISO-8859-1'): - """Return the given native string as a byte string in the given - encoding. - """ - assert_native(n) - # In Python 3, the native string type is unicode - return n.encode(encoding) - - -def ntou(n, encoding='ISO-8859-1'): - """Return the given native string as a unicode string with the given - encoding. - """ - assert_native(n) - # In Python 3, the native string type is unicode - return n - - -def tonative(n, encoding='ISO-8859-1'): - """Return the given string as a native string in the given encoding.""" - # In Python 3, the native string type is unicode - if isinstance(n, bytes): - return n.decode(encoding) - return n - - -def assert_native(n): - if not isinstance(n, str): - raise TypeError('n must be a native str (got %s)' % type(n).__name__) - - -# Some platforms don't expose HTTPSConnection, so handle it separately -HTTPSConnection = getattr(http.client, 'HTTPSConnection', None) - - -text_or_bytes = str, bytes diff --git a/lib/cherrypy/_cpconfig.py b/lib/cherrypy/_cpconfig.py deleted file mode 100644 index 8e3fd612..00000000 --- a/lib/cherrypy/_cpconfig.py +++ /dev/null @@ -1,296 +0,0 @@ -""" -Configuration system for CherryPy. - -Configuration in CherryPy is implemented via dictionaries. Keys are strings -which name the mapped value, which may be of any type. - - -Architecture ------------- - -CherryPy Requests are part of an Application, which runs in a global context, -and configuration data may apply to any of those three scopes: - -Global - Configuration entries which apply everywhere are stored in - cherrypy.config. - -Application - Entries which apply to each mounted application are stored - on the Application object itself, as 'app.config'. This is a two-level - dict where each key is a path, or "relative URL" (for example, "/" or - "/path/to/my/page"), and each value is a config dict. Usually, this - data is provided in the call to tree.mount(root(), config=conf), - although you may also use app.merge(conf). - -Request - Each Request object possesses a single 'Request.config' dict. - Early in the request process, this dict is populated by merging global - config entries, Application entries (whose path equals or is a parent - of Request.path_info), and any config acquired while looking up the - page handler (see next). - - -Declaration ------------ - -Configuration data may be supplied as a Python dictionary, as a filename, -or as an open file object. When you supply a filename or file, CherryPy -uses Python's builtin ConfigParser; you declare Application config by -writing each path as a section header:: - - [/path/to/my/page] - request.stream = True - -To declare global configuration entries, place them in a [global] section. - -You may also declare config entries directly on the classes and methods -(page handlers) that make up your CherryPy application via the ``_cp_config`` -attribute, set with the ``cherrypy.config`` decorator. For example:: - - @cherrypy.config(**{'tools.gzip.on': True}) - class Demo: - - @cherrypy.expose - @cherrypy.config(**{'request.show_tracebacks': False}) - def index(self): - return "Hello world" - -.. note:: - - This behavior is only guaranteed for the default dispatcher. - Other dispatchers may have different restrictions on where - you can attach config attributes. - - -Namespaces ----------- - -Configuration keys are separated into namespaces by the first "." in the key. -Current namespaces: - -engine - Controls the 'application engine', including autoreload. - These can only be declared in the global config. - -tree - Grafts cherrypy.Application objects onto cherrypy.tree. - These can only be declared in the global config. - -hooks - Declares additional request-processing functions. - -log - Configures the logging for each application. - These can only be declared in the global or / config. - -request - Adds attributes to each Request. - -response - Adds attributes to each Response. - -server - Controls the default HTTP server via cherrypy.server. - These can only be declared in the global config. - -tools - Runs and configures additional request-processing packages. - -wsgi - Adds WSGI middleware to an Application's "pipeline". - These can only be declared in the app's root config ("/"). - -checker - Controls the 'checker', which looks for common errors in - app state (including config) when the engine starts. - Global config only. - -The only key that does not exist in a namespace is the "environment" entry. -This special entry 'imports' other config entries from a template stored in -cherrypy._cpconfig.environments[environment]. It only applies to the global -config, and only when you use cherrypy.config.update. - -You can define your own namespaces to be called at the Global, Application, -or Request level, by adding a named handler to cherrypy.config.namespaces, -app.namespaces, or app.request_class.namespaces. The name can -be any string, and the handler must be either a callable or a (Python 2.5 -style) context manager. -""" - -import cherrypy -from cherrypy._cpcompat import text_or_bytes -from cherrypy.lib import reprconf - - -def _if_filename_register_autoreload(ob): - """Register for autoreload if ob is a string (presumed filename).""" - is_filename = isinstance(ob, text_or_bytes) - is_filename and cherrypy.engine.autoreload.files.add(ob) - - -def merge(base, other): - """Merge one app config (from a dict, file, or filename) into another. - - If the given config is a filename, it will be appended to - the list of files to monitor for "autoreload" changes. - """ - _if_filename_register_autoreload(other) - - # Load other into base - for section, value_map in reprconf.Parser.load(other).items(): - if not isinstance(value_map, dict): - raise ValueError( - 'Application config must include section headers, but the ' - "config you tried to merge doesn't have any sections. " - 'Wrap your config in another dict with paths as section ' - "headers, for example: {'/': config}.") - base.setdefault(section, {}).update(value_map) - - -class Config(reprconf.Config): - """The 'global' configuration data for the entire CherryPy process.""" - - def update(self, config): - """Update self from a dict, file or filename.""" - _if_filename_register_autoreload(config) - super(Config, self).update(config) - - def _apply(self, config): - """Update self from a dict.""" - if isinstance(config.get('global'), dict): - if len(config) > 1: - cherrypy.checker.global_config_contained_paths = True - config = config['global'] - if 'tools.staticdir.dir' in config: - config['tools.staticdir.section'] = 'global' - super(Config, self)._apply(config) - - @staticmethod - def __call__(**kwargs): - """Decorate for page handlers to set _cp_config.""" - def tool_decorator(f): - _Vars(f).setdefault('_cp_config', {}).update(kwargs) - return f - return tool_decorator - - -class _Vars(object): - """Adapter allowing setting a default attribute on a function or class.""" - - def __init__(self, target): - self.target = target - - def setdefault(self, key, default): - if not hasattr(self.target, key): - setattr(self.target, key, default) - return getattr(self.target, key) - - -# Sphinx begin config.environments -Config.environments = environments = { - 'staging': { - 'engine.autoreload.on': False, - 'checker.on': False, - 'tools.log_headers.on': False, - 'request.show_tracebacks': False, - 'request.show_mismatched_params': False, - }, - 'production': { - 'engine.autoreload.on': False, - 'checker.on': False, - 'tools.log_headers.on': False, - 'request.show_tracebacks': False, - 'request.show_mismatched_params': False, - 'log.screen': False, - }, - 'embedded': { - # For use with CherryPy embedded in another deployment stack. - 'engine.autoreload.on': False, - 'checker.on': False, - 'tools.log_headers.on': False, - 'request.show_tracebacks': False, - 'request.show_mismatched_params': False, - 'log.screen': False, - 'engine.SIGHUP': None, - 'engine.SIGTERM': None, - }, - 'test_suite': { - 'engine.autoreload.on': False, - 'checker.on': False, - 'tools.log_headers.on': False, - 'request.show_tracebacks': True, - 'request.show_mismatched_params': True, - 'log.screen': False, - }, -} -# Sphinx end config.environments - - -def _server_namespace_handler(k, v): - """Config handler for the "server" namespace.""" - atoms = k.split('.', 1) - if len(atoms) > 1: - # Special-case config keys of the form 'server.servername.socket_port' - # to configure additional HTTP servers. - if not hasattr(cherrypy, 'servers'): - cherrypy.servers = {} - - servername, k = atoms - if servername not in cherrypy.servers: - from cherrypy import _cpserver - cherrypy.servers[servername] = _cpserver.Server() - # On by default, but 'on = False' can unsubscribe it (see below). - cherrypy.servers[servername].subscribe() - - if k == 'on': - if v: - cherrypy.servers[servername].subscribe() - else: - cherrypy.servers[servername].unsubscribe() - else: - setattr(cherrypy.servers[servername], k, v) - else: - setattr(cherrypy.server, k, v) - - -Config.namespaces['server'] = _server_namespace_handler - - -def _engine_namespace_handler(k, v): - """Config handler for the "engine" namespace.""" - engine = cherrypy.engine - - if k in {'SIGHUP', 'SIGTERM'}: - engine.subscribe(k, v) - return - - if '.' in k: - plugin, attrname = k.split('.', 1) - plugin = getattr(engine, plugin) - op = 'subscribe' if v else 'unsubscribe' - sub_unsub = getattr(plugin, op, None) - if attrname == 'on' and callable(sub_unsub): - sub_unsub() - return - setattr(plugin, attrname, v) - else: - setattr(engine, k, v) - - -Config.namespaces['engine'] = _engine_namespace_handler - - -def _tree_namespace_handler(k, v): - """Namespace handler for the 'tree' config namespace.""" - if isinstance(v, dict): - for script_name, app in v.items(): - cherrypy.tree.graft(app, script_name) - msg = 'Mounted: %s on %s' % (app, script_name or '/') - cherrypy.engine.log(msg) - else: - cherrypy.tree.graft(v, v.script_name) - cherrypy.engine.log('Mounted: %s on %s' % (v, v.script_name or '/')) - - -Config.namespaces['tree'] = _tree_namespace_handler diff --git a/lib/cherrypy/_cpdispatch.py b/lib/cherrypy/_cpdispatch.py deleted file mode 100644 index 5c506e99..00000000 --- a/lib/cherrypy/_cpdispatch.py +++ /dev/null @@ -1,682 +0,0 @@ -"""CherryPy dispatchers. - -A 'dispatcher' is the object which looks up the 'page handler' callable -and collects config for the current request based on the path_info, other -request attributes, and the application architecture. The core calls the -dispatcher as early as possible, passing it a 'path_info' argument. - -The default dispatcher discovers the page handler by matching path_info -to a hierarchical arrangement of objects, starting at request.app.root. -""" - -import string -import sys -import types -try: - classtype = (type, types.ClassType) -except AttributeError: - classtype = type - -import cherrypy - - -class PageHandler(object): - - """Callable which sets response.body.""" - - def __init__(self, callable, *args, **kwargs): - self.callable = callable - self.args = args - self.kwargs = kwargs - - @property - def args(self): - """The ordered args should be accessible from post dispatch hooks.""" - return cherrypy.serving.request.args - - @args.setter - def args(self, args): - cherrypy.serving.request.args = args - return cherrypy.serving.request.args - - @property - def kwargs(self): - """The named kwargs should be accessible from post dispatch hooks.""" - return cherrypy.serving.request.kwargs - - @kwargs.setter - def kwargs(self, kwargs): - cherrypy.serving.request.kwargs = kwargs - return cherrypy.serving.request.kwargs - - def __call__(self): - try: - return self.callable(*self.args, **self.kwargs) - except TypeError: - x = sys.exc_info()[1] - try: - test_callable_spec(self.callable, self.args, self.kwargs) - except cherrypy.HTTPError: - raise sys.exc_info()[1] - except Exception: - raise x - raise - - -def test_callable_spec(callable, callable_args, callable_kwargs): - """ - Inspect callable and test to see if the given args are suitable for it. - - When an error occurs during the handler's invoking stage there are 2 - erroneous cases: - 1. Too many parameters passed to a function which doesn't define - one of *args or **kwargs. - 2. Too little parameters are passed to the function. - - There are 3 sources of parameters to a cherrypy handler. - 1. query string parameters are passed as keyword parameters to the - handler. - 2. body parameters are also passed as keyword parameters. - 3. when partial matching occurs, the final path atoms are passed as - positional args. - Both the query string and path atoms are part of the URI. If they are - incorrect, then a 404 Not Found should be raised. Conversely the body - parameters are part of the request; if they are invalid a 400 Bad Request. - """ - show_mismatched_params = getattr( - cherrypy.serving.request, 'show_mismatched_params', False) - try: - (args, varargs, varkw, defaults) = getargspec(callable) - except TypeError: - if isinstance(callable, object) and hasattr(callable, '__call__'): - (args, varargs, varkw, - defaults) = getargspec(callable.__call__) - else: - # If it wasn't one of our own types, re-raise - # the original error - raise - - if args and ( - # For callable objects, which have a __call__(self) method - hasattr(callable, '__call__') or - # For normal methods - inspect.ismethod(callable) - ): - # Strip 'self' - args = args[1:] - - arg_usage = dict([(arg, 0,) for arg in args]) - vararg_usage = 0 - varkw_usage = 0 - extra_kwargs = set() - - for i, value in enumerate(callable_args): - try: - arg_usage[args[i]] += 1 - except IndexError: - vararg_usage += 1 - - for key in callable_kwargs.keys(): - try: - arg_usage[key] += 1 - except KeyError: - varkw_usage += 1 - extra_kwargs.add(key) - - # figure out which args have defaults. - args_with_defaults = args[-len(defaults or []):] - for i, val in enumerate(defaults or []): - # Defaults take effect only when the arg hasn't been used yet. - if arg_usage[args_with_defaults[i]] == 0: - arg_usage[args_with_defaults[i]] += 1 - - missing_args = [] - multiple_args = [] - for key, usage in arg_usage.items(): - if usage == 0: - missing_args.append(key) - elif usage > 1: - multiple_args.append(key) - - if missing_args: - # In the case where the method allows body arguments - # there are 3 potential errors: - # 1. not enough query string parameters -> 404 - # 2. not enough body parameters -> 400 - # 3. not enough path parts (partial matches) -> 404 - # - # We can't actually tell which case it is, - # so I'm raising a 404 because that covers 2/3 of the - # possibilities - # - # In the case where the method does not allow body - # arguments it's definitely a 404. - message = None - if show_mismatched_params: - message = 'Missing parameters: %s' % ','.join(missing_args) - raise cherrypy.HTTPError(404, message=message) - - # the extra positional arguments come from the path - 404 Not Found - if not varargs and vararg_usage > 0: - raise cherrypy.HTTPError(404) - - body_params = cherrypy.serving.request.body.params or {} - body_params = set(body_params.keys()) - qs_params = set(callable_kwargs.keys()) - body_params - - if multiple_args: - if qs_params.intersection(set(multiple_args)): - # If any of the multiple parameters came from the query string then - # it's a 404 Not Found - error = 404 - else: - # Otherwise it's a 400 Bad Request - error = 400 - - message = None - if show_mismatched_params: - message = 'Multiple values for parameters: '\ - '%s' % ','.join(multiple_args) - raise cherrypy.HTTPError(error, message=message) - - if not varkw and varkw_usage > 0: - - # If there were extra query string parameters, it's a 404 Not Found - extra_qs_params = set(qs_params).intersection(extra_kwargs) - if extra_qs_params: - message = None - if show_mismatched_params: - message = 'Unexpected query string '\ - 'parameters: %s' % ', '.join(extra_qs_params) - raise cherrypy.HTTPError(404, message=message) - - # If there were any extra body parameters, it's a 400 Not Found - extra_body_params = set(body_params).intersection(extra_kwargs) - if extra_body_params: - message = None - if show_mismatched_params: - message = 'Unexpected body parameters: '\ - '%s' % ', '.join(extra_body_params) - raise cherrypy.HTTPError(400, message=message) - - -try: - import inspect -except ImportError: - def test_callable_spec(callable, args, kwargs): # noqa: F811 - return None -else: - def getargspec(callable): - return inspect.getfullargspec(callable)[:4] - - -class LateParamPageHandler(PageHandler): - - """When passing cherrypy.request.params to the page handler, we do not - want to capture that dict too early; we want to give tools like the - decoding tool a chance to modify the params dict in-between the lookup - of the handler and the actual calling of the handler. This subclass - takes that into account, and allows request.params to be 'bound late' - (it's more complicated than that, but that's the effect). - """ - - @property - def kwargs(self): - """Page handler kwargs (with cherrypy.request.params copied in).""" - kwargs = cherrypy.serving.request.params.copy() - if self._kwargs: - kwargs.update(self._kwargs) - return kwargs - - @kwargs.setter - def kwargs(self, kwargs): - cherrypy.serving.request.kwargs = kwargs - self._kwargs = kwargs - - -if sys.version_info < (3, 0): - punctuation_to_underscores = string.maketrans( - string.punctuation, '_' * len(string.punctuation)) - - def validate_translator(t): - if not isinstance(t, str) or len(t) != 256: - raise ValueError( - 'The translate argument must be a str of len 256.') -else: - punctuation_to_underscores = str.maketrans( - string.punctuation, '_' * len(string.punctuation)) - - def validate_translator(t): - if not isinstance(t, dict): - raise ValueError('The translate argument must be a dict.') - - -class Dispatcher(object): - - """CherryPy Dispatcher which walks a tree of objects to find a handler. - - The tree is rooted at cherrypy.request.app.root, and each hierarchical - component in the path_info argument is matched to a corresponding nested - attribute of the root object. Matching handlers must have an 'exposed' - attribute which evaluates to True. The special method name "index" - matches a URI which ends in a slash ("/"). The special method name - "default" may match a portion of the path_info (but only when no longer - substring of the path_info matches some other object). - - This is the default, built-in dispatcher for CherryPy. - """ - - dispatch_method_name = '_cp_dispatch' - """ - The name of the dispatch method that nodes may optionally implement - to provide their own dynamic dispatch algorithm. - """ - - def __init__(self, dispatch_method_name=None, - translate=punctuation_to_underscores): - validate_translator(translate) - self.translate = translate - if dispatch_method_name: - self.dispatch_method_name = dispatch_method_name - - def __call__(self, path_info): - """Set handler and config for the current request.""" - request = cherrypy.serving.request - func, vpath = self.find_handler(path_info) - - if func: - # Decode any leftover %2F in the virtual_path atoms. - vpath = [x.replace('%2F', '/') for x in vpath] - request.handler = LateParamPageHandler(func, *vpath) - else: - request.handler = cherrypy.NotFound() - - def find_handler(self, path): - """Return the appropriate page handler, plus any virtual path. - - This will return two objects. The first will be a callable, - which can be used to generate page output. Any parameters from - the query string or request body will be sent to that callable - as keyword arguments. - - The callable is found by traversing the application's tree, - starting from cherrypy.request.app.root, and matching path - components to successive objects in the tree. For example, the - URL "/path/to/handler" might return root.path.to.handler. - - The second object returned will be a list of names which are - 'virtual path' components: parts of the URL which are dynamic, - and were not used when looking up the handler. - These virtual path components are passed to the handler as - positional arguments. - """ - request = cherrypy.serving.request - app = request.app - root = app.root - dispatch_name = self.dispatch_method_name - - # Get config for the root object/path. - fullpath = [x for x in path.strip('/').split('/') if x] + ['index'] - fullpath_len = len(fullpath) - segleft = fullpath_len - nodeconf = {} - if hasattr(root, '_cp_config'): - nodeconf.update(root._cp_config) - if '/' in app.config: - nodeconf.update(app.config['/']) - object_trail = [['root', root, nodeconf, segleft]] - - node = root - iternames = fullpath[:] - while iternames: - name = iternames[0] - # map to legal Python identifiers (e.g. replace '.' with '_') - objname = name.translate(self.translate) - - nodeconf = {} - subnode = getattr(node, objname, None) - pre_len = len(iternames) - if subnode is None: - dispatch = getattr(node, dispatch_name, None) - if dispatch and hasattr(dispatch, '__call__') and not \ - getattr(dispatch, 'exposed', False) and \ - pre_len > 1: - # Don't expose the hidden 'index' token to _cp_dispatch - # We skip this if pre_len == 1 since it makes no sense - # to call a dispatcher when we have no tokens left. - index_name = iternames.pop() - subnode = dispatch(vpath=iternames) - iternames.append(index_name) - else: - # We didn't find a path, but keep processing in case there - # is a default() handler. - iternames.pop(0) - else: - # We found the path, remove the vpath entry - iternames.pop(0) - segleft = len(iternames) - if segleft > pre_len: - # No path segment was removed. Raise an error. - raise cherrypy.CherryPyException( - 'A vpath segment was added. Custom dispatchers may only ' - 'remove elements. While trying to process ' - '{0} in {1}'.format(name, fullpath) - ) - elif segleft == pre_len: - # Assume that the handler used the current path segment, but - # did not pop it. This allows things like - # return getattr(self, vpath[0], None) - iternames.pop(0) - segleft -= 1 - node = subnode - - if node is not None: - # Get _cp_config attached to this node. - if hasattr(node, '_cp_config'): - nodeconf.update(node._cp_config) - - # Mix in values from app.config for this path. - existing_len = fullpath_len - pre_len - if existing_len != 0: - curpath = '/' + '/'.join(fullpath[0:existing_len]) - else: - curpath = '' - new_segs = fullpath[fullpath_len - pre_len:fullpath_len - segleft] - for seg in new_segs: - curpath += '/' + seg - if curpath in app.config: - nodeconf.update(app.config[curpath]) - - object_trail.append([name, node, nodeconf, segleft]) - - def set_conf(): - """Collapse all object_trail config into cherrypy.request.config. - """ - base = cherrypy.config.copy() - # Note that we merge the config from each node - # even if that node was None. - for name, obj, conf, segleft in object_trail: - base.update(conf) - if 'tools.staticdir.dir' in conf: - base['tools.staticdir.section'] = '/' + \ - '/'.join(fullpath[0:fullpath_len - segleft]) - return base - - # Try successive objects (reverse order) - num_candidates = len(object_trail) - 1 - for i in range(num_candidates, -1, -1): - - name, candidate, nodeconf, segleft = object_trail[i] - if candidate is None: - continue - - # Try a "default" method on the current leaf. - if hasattr(candidate, 'default'): - defhandler = candidate.default - if getattr(defhandler, 'exposed', False): - # Insert any extra _cp_config from the default handler. - conf = getattr(defhandler, '_cp_config', {}) - object_trail.insert( - i + 1, ['default', defhandler, conf, segleft]) - request.config = set_conf() - # See https://github.com/cherrypy/cherrypy/issues/613 - request.is_index = path.endswith('/') - return defhandler, fullpath[fullpath_len - segleft:-1] - - # Uncomment the next line to restrict positional params to - # "default". - # if i < num_candidates - 2: continue - - # Try the current leaf. - if getattr(candidate, 'exposed', False): - request.config = set_conf() - if i == num_candidates: - # We found the extra ".index". Mark request so tools - # can redirect if path_info has no trailing slash. - request.is_index = True - else: - # We're not at an 'index' handler. Mark request so tools - # can redirect if path_info has NO trailing slash. - # Note that this also includes handlers which take - # positional parameters (virtual paths). - request.is_index = False - return candidate, fullpath[fullpath_len - segleft:-1] - - # We didn't find anything - request.config = set_conf() - return None, [] - - -class MethodDispatcher(Dispatcher): - - """Additional dispatch based on cherrypy.request.method.upper(). - - Methods named GET, POST, etc will be called on an exposed class. - The method names must be all caps; the appropriate Allow header - will be output showing all capitalized method names as allowable - HTTP verbs. - - Note that the containing class must be exposed, not the methods. - """ - - def __call__(self, path_info): - """Set handler and config for the current request.""" - request = cherrypy.serving.request - resource, vpath = self.find_handler(path_info) - - if resource: - # Set Allow header - avail = [m for m in dir(resource) if m.isupper()] - if 'GET' in avail and 'HEAD' not in avail: - avail.append('HEAD') - avail.sort() - cherrypy.serving.response.headers['Allow'] = ', '.join(avail) - - # Find the subhandler - meth = request.method.upper() - func = getattr(resource, meth, None) - if func is None and meth == 'HEAD': - func = getattr(resource, 'GET', None) - if func: - # Grab any _cp_config on the subhandler. - if hasattr(func, '_cp_config'): - request.config.update(func._cp_config) - - # Decode any leftover %2F in the virtual_path atoms. - vpath = [x.replace('%2F', '/') for x in vpath] - request.handler = LateParamPageHandler(func, *vpath) - else: - request.handler = cherrypy.HTTPError(405) - else: - request.handler = cherrypy.NotFound() - - -class RoutesDispatcher(object): - - """A Routes based dispatcher for CherryPy.""" - - def __init__(self, full_result=False, **mapper_options): - """ - Routes dispatcher - - Set full_result to True if you wish the controller - and the action to be passed on to the page handler - parameters. By default they won't be. - """ - import routes - self.full_result = full_result - self.controllers = {} - self.mapper = routes.Mapper(**mapper_options) - self.mapper.controller_scan = self.controllers.keys - - def connect(self, name, route, controller, **kwargs): - self.controllers[name] = controller - self.mapper.connect(name, route, controller=name, **kwargs) - - def redirect(self, url): - raise cherrypy.HTTPRedirect(url) - - def __call__(self, path_info): - """Set handler and config for the current request.""" - func = self.find_handler(path_info) - if func: - cherrypy.serving.request.handler = LateParamPageHandler(func) - else: - cherrypy.serving.request.handler = cherrypy.NotFound() - - def find_handler(self, path_info): - """Find the right page handler, and set request.config.""" - import routes - - request = cherrypy.serving.request - - config = routes.request_config() - config.mapper = self.mapper - if hasattr(request, 'wsgi_environ'): - config.environ = request.wsgi_environ - config.host = request.headers.get('Host', None) - config.protocol = request.scheme - config.redirect = self.redirect - - result = self.mapper.match(path_info) - - config.mapper_dict = result - params = {} - if result: - params = result.copy() - if not self.full_result: - params.pop('controller', None) - params.pop('action', None) - request.params.update(params) - - # Get config for the root object/path. - request.config = base = cherrypy.config.copy() - curpath = '' - - def merge(nodeconf): - if 'tools.staticdir.dir' in nodeconf: - nodeconf['tools.staticdir.section'] = curpath or '/' - base.update(nodeconf) - - app = request.app - root = app.root - if hasattr(root, '_cp_config'): - merge(root._cp_config) - if '/' in app.config: - merge(app.config['/']) - - # Mix in values from app.config. - atoms = [x for x in path_info.split('/') if x] - if atoms: - last = atoms.pop() - else: - last = None - for atom in atoms: - curpath = '/'.join((curpath, atom)) - if curpath in app.config: - merge(app.config[curpath]) - - handler = None - if result: - controller = result.get('controller') - controller = self.controllers.get(controller, controller) - if controller: - if isinstance(controller, classtype): - controller = controller() - # Get config from the controller. - if hasattr(controller, '_cp_config'): - merge(controller._cp_config) - - action = result.get('action') - if action is not None: - handler = getattr(controller, action, None) - # Get config from the handler - if hasattr(handler, '_cp_config'): - merge(handler._cp_config) - else: - handler = controller - - # Do the last path atom here so it can - # override the controller's _cp_config. - if last: - curpath = '/'.join((curpath, last)) - if curpath in app.config: - merge(app.config[curpath]) - - return handler - - -def XMLRPCDispatcher(next_dispatcher=Dispatcher()): - from cherrypy.lib import xmlrpcutil - - def xmlrpc_dispatch(path_info): - path_info = xmlrpcutil.patched_path(path_info) - return next_dispatcher(path_info) - return xmlrpc_dispatch - - -def VirtualHost(next_dispatcher=Dispatcher(), use_x_forwarded_host=True, - **domains): - """ - Select a different handler based on the Host header. - - This can be useful when running multiple sites within one CP server. - It allows several domains to point to different parts of a single - website structure. For example:: - - http://www.domain.example -> root - http://www.domain2.example -> root/domain2/ - http://www.domain2.example:443 -> root/secure - - can be accomplished via the following config:: - - [/] - request.dispatch = cherrypy.dispatch.VirtualHost( - **{'www.domain2.example': '/domain2', - 'www.domain2.example:443': '/secure', - }) - - next_dispatcher - The next dispatcher object in the dispatch chain. - The VirtualHost dispatcher adds a prefix to the URL and calls - another dispatcher. Defaults to cherrypy.dispatch.Dispatcher(). - - use_x_forwarded_host - If True (the default), any "X-Forwarded-Host" - request header will be used instead of the "Host" header. This - is commonly added by HTTP servers (such as Apache) when proxying. - - ``**domains`` - A dict of {host header value: virtual prefix} pairs. - The incoming "Host" request header is looked up in this dict, - and, if a match is found, the corresponding "virtual prefix" - value will be prepended to the URL path before calling the - next dispatcher. Note that you often need separate entries - for "example.com" and "www.example.com". In addition, "Host" - headers may contain the port number. - """ - from cherrypy.lib import httputil - - def vhost_dispatch(path_info): - request = cherrypy.serving.request - header = request.headers.get - - domain = header('Host', '') - if use_x_forwarded_host: - domain = header('X-Forwarded-Host', domain) - - prefix = domains.get(domain, '') - if prefix: - path_info = httputil.urljoin(prefix, path_info) - - result = next_dispatcher(path_info) - - # Touch up staticdir config. See - # https://github.com/cherrypy/cherrypy/issues/614. - section = request.config.get('tools.staticdir.section') - if section: - section = section[len(prefix):] - request.config['tools.staticdir.section'] = section - - return result - return vhost_dispatch diff --git a/lib/cherrypy/_cperror.py b/lib/cherrypy/_cperror.py deleted file mode 100644 index f6ff2913..00000000 --- a/lib/cherrypy/_cperror.py +++ /dev/null @@ -1,620 +0,0 @@ -"""Exception classes for CherryPy. - -CherryPy provides (and uses) exceptions for declaring that the HTTP response -should be a status other than the default "200 OK". You can ``raise`` them like -normal Python exceptions. You can also call them and they will raise -themselves; this means you can set an -:class:`HTTPError` -or :class:`HTTPRedirect` as the -:attr:`request.handler`. - -.. _redirectingpost: - -Redirecting POST -================ - -When you GET a resource and are redirected by the server to another Location, -there's generally no problem since GET is both a "safe method" (there should -be no side-effects) and an "idempotent method" (multiple calls are no different -than a single call). - -POST, however, is neither safe nor idempotent--if you -charge a credit card, you don't want to be charged twice by a redirect! - -For this reason, *none* of the 3xx responses permit a user-agent (browser) to -resubmit a POST on redirection without first confirming the action with the -user: - -===== ================================= =========== -300 Multiple Choices Confirm with the user -301 Moved Permanently Confirm with the user -302 Found (Object moved temporarily) Confirm with the user -303 See Other GET the new URI; no confirmation -304 Not modified for conditional GET only; - POST should not raise this error -305 Use Proxy Confirm with the user -307 Temporary Redirect Confirm with the user -308 Permanent Redirect No confirmation -===== ================================= =========== - -However, browsers have historically implemented these restrictions poorly; -in particular, many browsers do not force the user to confirm 301, 302 -or 307 when redirecting POST. For this reason, CherryPy defaults to 303, -which most user-agents appear to have implemented correctly. Therefore, if -you raise HTTPRedirect for a POST request, the user-agent will most likely -attempt to GET the new URI (without asking for confirmation from the user). -We realize this is confusing for developers, but it's the safest thing we -could do. You are of course free to raise ``HTTPRedirect(uri, status=302)`` -or any other 3xx status if you know what you're doing, but given the -environment, we couldn't let any of those be the default. - -Custom Error Handling -===================== - -.. image:: /refman/cperrors.gif - -Anticipated HTTP responses --------------------------- - -The 'error_page' config namespace can be used to provide custom HTML output for -expected responses (like 404 Not Found). Supply a filename from which the -output will be read. The contents will be interpolated with the values -%(status)s, %(message)s, %(traceback)s, and %(version)s using plain old Python -`string formatting -`_. - -:: - - _cp_config = { - 'error_page.404': os.path.join(localDir, "static/index.html") - } - - -Beginning in version 3.1, you may also provide a function or other callable as -an error_page entry. It will be passed the same status, message, traceback and -version arguments that are interpolated into templates:: - - def error_page_402(status, message, traceback, version): - return "Error %s - Well, I'm very sorry but you haven't paid!" % status - cherrypy.config.update({'error_page.402': error_page_402}) - -Also in 3.1, in addition to the numbered error codes, you may also supply -"error_page.default" to handle all codes which do not have their own error_page -entry. - - - -Unanticipated errors --------------------- - -CherryPy also has a generic error handling mechanism: whenever an unanticipated -error occurs in your code, it will call -:func:`Request.error_response` to -set the response status, headers, and body. By default, this is the same -output as -:class:`HTTPError(500) `. If you want to provide -some other behavior, you generally replace "request.error_response". - -Here is some sample code that shows how to display a custom error message and -send an e-mail containing the error:: - - from cherrypy import _cperror - - def handle_error(): - cherrypy.response.status = 500 - cherrypy.response.body = [ - "Sorry, an error occurred" - ] - sendMail('error@domain.com', - 'Error in your web app', - _cperror.format_exc()) - - @cherrypy.config(**{'request.error_response': handle_error}) - class Root: - pass - -Note that you have to explicitly set -:attr:`response.body ` -and not simply return an error message as a result. -""" - -import io -import contextlib -import urllib.parse -from sys import exc_info as _exc_info -from traceback import format_exception as _format_exception -from xml.sax import saxutils -import html - -from more_itertools import always_iterable - -import cherrypy -from cherrypy._cpcompat import ntob -from cherrypy._cpcompat import tonative -from cherrypy._helper import classproperty -from cherrypy.lib import httputil as _httputil - - -class CherryPyException(Exception): - - """A base class for CherryPy exceptions.""" - pass - - -class InternalRedirect(CherryPyException): - - """Exception raised to switch to the handler for a different URL. - - This exception will redirect processing to another path within the site - (without informing the client). Provide the new path as an argument when - raising the exception. Provide any params in the querystring for the new - URL. - """ - - def __init__(self, path, query_string=''): - self.request = cherrypy.serving.request - - self.query_string = query_string - if '?' in path: - # Separate any params included in the path - path, self.query_string = path.split('?', 1) - - # Note that urljoin will "do the right thing" whether url is: - # 1. a URL relative to root (e.g. "/dummy") - # 2. a URL relative to the current path - # Note that any query string will be discarded. - path = urllib.parse.urljoin(self.request.path_info, path) - - # Set a 'path' member attribute so that code which traps this - # error can have access to it. - self.path = path - - CherryPyException.__init__(self, path, self.query_string) - - -class HTTPRedirect(CherryPyException): - - """Exception raised when the request should be redirected. - - This exception will force a HTTP redirect to the URL or URL's you give it. - The new URL must be passed as the first argument to the Exception, - e.g., HTTPRedirect(newUrl). Multiple URLs are allowed in a list. - If a URL is absolute, it will be used as-is. If it is relative, it is - assumed to be relative to the current cherrypy.request.path_info. - - If one of the provided URL is a unicode object, it will be encoded - using the default encoding or the one passed in parameter. - - There are multiple types of redirect, from which you can select via the - ``status`` argument. If you do not provide a ``status`` arg, it defaults to - 303 (or 302 if responding with HTTP/1.0). - - Examples:: - - raise cherrypy.HTTPRedirect("") - raise cherrypy.HTTPRedirect("/abs/path", 307) - raise cherrypy.HTTPRedirect(["path1", "path2?a=1&b=2"], 301) - - See :ref:`redirectingpost` for additional caveats. - """ - - urls = None - """The list of URL's to emit.""" - - encoding = 'utf-8' - """The encoding when passed urls are not native strings""" - - def __init__(self, urls, status=None, encoding=None): - self.urls = abs_urls = [ - # Note that urljoin will "do the right thing" whether url is: - # 1. a complete URL with host (e.g. "http://www.example.com/test") - # 2. a URL relative to root (e.g. "/dummy") - # 3. a URL relative to the current path - # Note that any query string in cherrypy.request is discarded. - urllib.parse.urljoin( - cherrypy.url(), - tonative(url, encoding or self.encoding), - ) - for url in always_iterable(urls) - ] - - status = ( - int(status) - if status is not None - else self.default_status - ) - if not 300 <= status <= 399: - raise ValueError('status must be between 300 and 399.') - - CherryPyException.__init__(self, abs_urls, status) - - @classproperty - def default_status(cls): - """ - The default redirect status for the request. - - RFC 2616 indicates a 301 response code fits our goal; however, - browser support for 301 is quite messy. Use 302/303 instead. See - http://www.alanflavell.org.uk/www/post-redirect.html - """ - return 303 if cherrypy.serving.request.protocol >= (1, 1) else 302 - - @property - def status(self): - """The integer HTTP status code to emit.""" - _, status = self.args[:2] - return status - - def set_response(self): - """Modify cherrypy.response status, headers, and body to represent - self. - - CherryPy uses this internally, but you can also use it to create an - HTTPRedirect object and set its output without *raising* the exception. - """ - response = cherrypy.serving.response - response.status = status = self.status - - if status in (300, 301, 302, 303, 307, 308): - response.headers['Content-Type'] = 'text/html;charset=utf-8' - # "The ... URI SHOULD be given by the Location field - # in the response." - response.headers['Location'] = self.urls[0] - - # "Unless the request method was HEAD, the entity of the response - # SHOULD contain a short hypertext note with a hyperlink to the - # new URI(s)." - msg = { - 300: 'This resource can be found at ', - 301: 'This resource has permanently moved to ', - 302: 'This resource resides temporarily at ', - 303: 'This resource can be found at ', - 307: 'This resource has moved temporarily to ', - 308: 'This resource has been moved to ', - }[status] - msg += '%s.' - msgs = [ - msg % (saxutils.quoteattr(u), html.escape(u, quote=False)) - for u in self.urls - ] - response.body = ntob('
\n'.join(msgs), 'utf-8') - # Previous code may have set C-L, so we have to reset it - # (allow finalize to set it). - response.headers.pop('Content-Length', None) - elif status == 304: - # Not Modified. - # "The response MUST include the following header fields: - # Date, unless its omission is required by section 14.18.1" - # The "Date" header should have been set in Response.__init__ - - # "...the response SHOULD NOT include other entity-headers." - for key in ('Allow', 'Content-Encoding', 'Content-Language', - 'Content-Length', 'Content-Location', 'Content-MD5', - 'Content-Range', 'Content-Type', 'Expires', - 'Last-Modified'): - if key in response.headers: - del response.headers[key] - - # "The 304 response MUST NOT contain a message-body." - response.body = None - # Previous code may have set C-L, so we have to reset it. - response.headers.pop('Content-Length', None) - elif status == 305: - # Use Proxy. - # self.urls[0] should be the URI of the proxy. - response.headers['Location'] = ntob(self.urls[0], 'utf-8') - response.body = None - # Previous code may have set C-L, so we have to reset it. - response.headers.pop('Content-Length', None) - else: - raise ValueError('The %s status code is unknown.' % status) - - def __call__(self): - """Use this exception as a request.handler (raise self).""" - raise self - - -def clean_headers(status): - """Remove any headers which should not apply to an error response.""" - response = cherrypy.serving.response - - # Remove headers which applied to the original content, - # but do not apply to the error page. - respheaders = response.headers - for key in ['Accept-Ranges', 'Age', 'ETag', 'Location', 'Retry-After', - 'Vary', 'Content-Encoding', 'Content-Length', 'Expires', - 'Content-Location', 'Content-MD5', 'Last-Modified']: - if key in respheaders: - del respheaders[key] - - if status != 416: - # A server sending a response with status code 416 (Requested - # range not satisfiable) SHOULD include a Content-Range field - # with a byte-range-resp-spec of "*". The instance-length - # specifies the current length of the selected resource. - # A response with status code 206 (Partial Content) MUST NOT - # include a Content-Range field with a byte-range- resp-spec of "*". - if 'Content-Range' in respheaders: - del respheaders['Content-Range'] - - -class HTTPError(CherryPyException): - - """Exception used to return an HTTP error code (4xx-5xx) to the client. - - This exception can be used to automatically send a response using a - http status code, with an appropriate error page. It takes an optional - ``status`` argument (which must be between 400 and 599); it defaults to 500 - ("Internal Server Error"). It also takes an optional ``message`` argument, - which will be returned in the response body. See - `RFC2616 `_ - for a complete list of available error codes and when to use them. - - Examples:: - - raise cherrypy.HTTPError(403) - raise cherrypy.HTTPError( - "403 Forbidden", "You are not allowed to access this resource.") - """ - - status = None - """The HTTP status code. May be of type int or str (with a Reason-Phrase). - """ - - code = None - """The integer HTTP status code.""" - - reason = None - """The HTTP Reason-Phrase string.""" - - def __init__(self, status=500, message=None): - self.status = status - try: - self.code, self.reason, defaultmsg = _httputil.valid_status(status) - except ValueError: - raise self.__class__(500, _exc_info()[1].args[0]) - - if self.code < 400 or self.code > 599: - raise ValueError('status must be between 400 and 599.') - - # See http://www.python.org/dev/peps/pep-0352/ - # self.message = message - self._message = message or defaultmsg - CherryPyException.__init__(self, status, message) - - def set_response(self): - """Modify cherrypy.response status, headers, and body to represent - self. - - CherryPy uses this internally, but you can also use it to create an - HTTPError object and set its output without *raising* the exception. - """ - response = cherrypy.serving.response - - clean_headers(self.code) - - # In all cases, finalize will be called after this method, - # so don't bother cleaning up response values here. - response.status = self.status - tb = None - if cherrypy.serving.request.show_tracebacks: - tb = format_exc() - - response.headers.pop('Content-Length', None) - - content = self.get_error_page(self.status, traceback=tb, - message=self._message) - response.body = content - - _be_ie_unfriendly(self.code) - - def get_error_page(self, *args, **kwargs): - return get_error_page(*args, **kwargs) - - def __call__(self): - """Use this exception as a request.handler (raise self).""" - raise self - - @classmethod - @contextlib.contextmanager - def handle(cls, exception, status=500, message=''): - """Translate exception into an HTTPError.""" - try: - yield - except exception as exc: - raise cls(status, message or str(exc)) - - -class NotFound(HTTPError): - - """Exception raised when a URL could not be mapped to any handler (404). - - This is equivalent to raising - :class:`HTTPError("404 Not Found") `. - """ - - def __init__(self, path=None): - if path is None: - request = cherrypy.serving.request - path = request.script_name + request.path_info - self.args = (path,) - HTTPError.__init__(self, 404, "The path '%s' was not found." % path) - - -_HTTPErrorTemplate = ''' - - - - %(status)s - - - -

%(status)s

-

%(message)s

-
%(traceback)s
-
- - Powered by CherryPy %(version)s - -
- - -''' - - -def get_error_page(status, **kwargs): - """Return an HTML page, containing a pretty error response. - - status should be an int or a str. - kwargs will be interpolated into the page template. - """ - try: - code, reason, message = _httputil.valid_status(status) - except ValueError: - raise cherrypy.HTTPError(500, _exc_info()[1].args[0]) - - # We can't use setdefault here, because some - # callers send None for kwarg values. - if kwargs.get('status') is None: - kwargs['status'] = '%s %s' % (code, reason) - if kwargs.get('message') is None: - kwargs['message'] = message - if kwargs.get('traceback') is None: - kwargs['traceback'] = '' - if kwargs.get('version') is None: - kwargs['version'] = cherrypy.__version__ - - for k, v in kwargs.items(): - if v is None: - kwargs[k] = '' - else: - kwargs[k] = html.escape(kwargs[k], quote=False) - - # Use a custom template or callable for the error page? - pages = cherrypy.serving.request.error_page - error_page = pages.get(code) or pages.get('default') - - # Default template, can be overridden below. - template = _HTTPErrorTemplate - if error_page: - try: - if hasattr(error_page, '__call__'): - # The caller function may be setting headers manually, - # so we delegate to it completely. We may be returning - # an iterator as well as a string here. - # - # We *must* make sure any content is not unicode. - result = error_page(**kwargs) - if cherrypy.lib.is_iterator(result): - from cherrypy.lib.encoding import UTF8StreamEncoder - return UTF8StreamEncoder(result) - elif isinstance(result, str): - return result.encode('utf-8') - else: - if not isinstance(result, bytes): - raise ValueError( - 'error page function did not ' - 'return a bytestring, str or an ' - 'iterator - returned object of type %s.' - % (type(result).__name__)) - return result - else: - # Load the template from this path. - with io.open(error_page, newline='') as f: - template = f.read() - except Exception: - e = _format_exception(*_exc_info())[-1] - m = kwargs['message'] - if m: - m += '
' - m += 'In addition, the custom error page failed:\n
%s' % e - kwargs['message'] = m - - response = cherrypy.serving.response - response.headers['Content-Type'] = 'text/html;charset=utf-8' - result = template % kwargs - return result.encode('utf-8') - - -_ie_friendly_error_sizes = { - 400: 512, 403: 256, 404: 512, 405: 256, - 406: 512, 408: 512, 409: 512, 410: 256, - 500: 512, 501: 512, 505: 512, -} - - -def _be_ie_unfriendly(status): - response = cherrypy.serving.response - - # For some statuses, Internet Explorer 5+ shows "friendly error - # messages" instead of our response.body if the body is smaller - # than a given size. Fix this by returning a body over that size - # (by adding whitespace). - # See http://support.microsoft.com/kb/q218155/ - s = _ie_friendly_error_sizes.get(status, 0) - if s: - s += 1 - # Since we are issuing an HTTP error status, we assume that - # the entity is short, and we should just collapse it. - content = response.collapse_body() - content_length = len(content) - if content_length and content_length < s: - # IN ADDITION: the response must be written to IE - # in one chunk or it will still get replaced! Bah. - content = content + (b' ' * (s - content_length)) - response.body = content - response.headers['Content-Length'] = str(len(content)) - - -def format_exc(exc=None): - """Return exc (or sys.exc_info if None), formatted.""" - try: - if exc is None: - exc = _exc_info() - if exc == (None, None, None): - return '' - import traceback - return ''.join(traceback.format_exception(*exc)) - finally: - del exc - - -def bare_error(extrabody=None): - """Produce status, headers, body for a critical error. - - Returns a triple without calling any other questionable functions, - so it should be as error-free as possible. Call it from an HTTP server - if you get errors outside of the request. - - If extrabody is None, a friendly but rather unhelpful error message - is set in the body. If extrabody is a string, it will be appended - as-is to the body. - """ - - # The whole point of this function is to be a last line-of-defense - # in handling errors. That is, it must not raise any errors itself; - # it cannot be allowed to fail. Therefore, don't add to it! - # In particular, don't call any other CP functions. - - body = b'Unrecoverable error in the server.' - if extrabody is not None: - if not isinstance(extrabody, bytes): - extrabody = extrabody.encode('utf-8') - body += b'\n' + extrabody - - return (b'500 Internal Server Error', - [(b'Content-Type', b'text/plain'), - (b'Content-Length', ntob(str(len(body)), 'ISO-8859-1'))], - [body]) diff --git a/lib/cherrypy/_cplogging.py b/lib/cherrypy/_cplogging.py deleted file mode 100644 index 151d3b40..00000000 --- a/lib/cherrypy/_cplogging.py +++ /dev/null @@ -1,457 +0,0 @@ -""" -Simple config -============= - -Although CherryPy uses the :mod:`Python logging module `, it does so -behind the scenes so that simple logging is simple, but complicated logging -is still possible. "Simple" logging means that you can log to the screen -(i.e. console/stdout) or to a file, and that you can easily have separate -error and access log files. - -Here are the simplified logging settings. You use these by adding lines to -your config file or dict. You should set these at either the global level or -per application (see next), but generally not both. - - * ``log.screen``: Set this to True to have both "error" and "access" messages - printed to stdout. - * ``log.access_file``: Set this to an absolute filename where you want - "access" messages written. - * ``log.error_file``: Set this to an absolute filename where you want "error" - messages written. - -Many events are automatically logged; to log your own application events, call -:func:`cherrypy.log`. - -Architecture -============ - -Separate scopes ---------------- - -CherryPy provides log managers at both the global and application layers. -This means you can have one set of logging rules for your entire site, -and another set of rules specific to each application. The global log -manager is found at :func:`cherrypy.log`, and the log manager for each -application is found at :attr:`app.log`. -If you're inside a request, the latter is reachable from -``cherrypy.request.app.log``; if you're outside a request, you'll have to -obtain a reference to the ``app``: either the return value of -:func:`tree.mount()` or, if you used -:func:`quickstart()` instead, via -``cherrypy.tree.apps['/']``. - -By default, the global logs are named "cherrypy.error" and "cherrypy.access", -and the application logs are named "cherrypy.error.2378745" and -"cherrypy.access.2378745" (the number is the id of the Application object). -This means that the application logs "bubble up" to the site logs, so if your -application has no log handlers, the site-level handlers will still log the -messages. - -Errors vs. Access ------------------ - -Each log manager handles both "access" messages (one per HTTP request) and -"error" messages (everything else). Note that the "error" log is not just for -errors! The format of access messages is highly formalized, but the error log -isn't--it receives messages from a variety of sources (including full error -tracebacks, if enabled). - -If you are logging the access log and error log to the same source, then there -is a possibility that a specially crafted error message may replicate an access -log message as described in CWE-117. In this case it is the application -developer's responsibility to manually escape data before -using CherryPy's log() -functionality, or they may create an application that is vulnerable to CWE-117. -This would be achieved by using a custom handler escape any special characters, -and attached as described below. - -Custom Handlers -=============== - -The simple settings above work by manipulating Python's standard :mod:`logging` -module. So when you need something more complex, the full power of the standard -module is yours to exploit. You can borrow or create custom handlers, formats, -filters, and much more. Here's an example that skips the standard FileHandler -and uses a RotatingFileHandler instead: - -:: - - #python - log = app.log - - # Remove the default FileHandlers if present. - log.error_file = "" - log.access_file = "" - - maxBytes = getattr(log, "rot_maxBytes", 10000000) - backupCount = getattr(log, "rot_backupCount", 1000) - - # Make a new RotatingFileHandler for the error log. - fname = getattr(log, "rot_error_file", "error.log") - h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) - h.setLevel(DEBUG) - h.setFormatter(_cplogging.logfmt) - log.error_log.addHandler(h) - - # Make a new RotatingFileHandler for the access log. - fname = getattr(log, "rot_access_file", "access.log") - h = handlers.RotatingFileHandler(fname, 'a', maxBytes, backupCount) - h.setLevel(DEBUG) - h.setFormatter(_cplogging.logfmt) - log.access_log.addHandler(h) - - -The ``rot_*`` attributes are pulled straight from the application log object. -Since "log.*" config entries simply set attributes on the log object, you can -add custom attributes to your heart's content. Note that these handlers are -used ''instead'' of the default, simple handlers outlined above (so don't set -the "log.error_file" config entry, for example). -""" - -import datetime -import logging -import os -import sys - -import cherrypy -from cherrypy import _cperror - - -# Silence the no-handlers "warning" (stderr write!) in stdlib logging -logging.Logger.manager.emittedNoHandlerWarning = 1 -logfmt = logging.Formatter('%(message)s') - - -class NullHandler(logging.Handler): - - """A no-op logging handler to silence the logging.lastResort handler.""" - - def handle(self, record): - pass - - def emit(self, record): - pass - - def createLock(self): - self.lock = None - - -class LogManager(object): - - """An object to assist both simple and advanced logging. - - ``cherrypy.log`` is an instance of this class. - """ - - appid = None - """The id() of the Application object which owns this log manager. If this - is a global log manager, appid is None.""" - - error_log = None - """The actual :class:`logging.Logger` instance for error messages.""" - - access_log = None - """The actual :class:`logging.Logger` instance for access messages.""" - - access_log_format = '{h} {l} {u} {t} "{r}" {s} {b} "{f}" "{a}"' - - logger_root = None - """The "top-level" logger name. - - This string will be used as the first segment in the Logger names. - The default is "cherrypy", for example, in which case the Logger names - will be of the form:: - - cherrypy.error. - cherrypy.access. - """ - - def __init__(self, appid=None, logger_root='cherrypy'): - self.logger_root = logger_root - self.appid = appid - if appid is None: - self.error_log = logging.getLogger('%s.error' % logger_root) - self.access_log = logging.getLogger('%s.access' % logger_root) - else: - self.error_log = logging.getLogger( - '%s.error.%s' % (logger_root, appid)) - self.access_log = logging.getLogger( - '%s.access.%s' % (logger_root, appid)) - self.error_log.setLevel(logging.INFO) - self.access_log.setLevel(logging.INFO) - - # Silence the no-handlers "warning" (stderr write!) in stdlib logging - self.error_log.addHandler(NullHandler()) - self.access_log.addHandler(NullHandler()) - - cherrypy.engine.subscribe('graceful', self.reopen_files) - - def reopen_files(self): - """Close and reopen all file handlers.""" - for log in (self.error_log, self.access_log): - for h in log.handlers: - if isinstance(h, logging.FileHandler): - h.acquire() - h.stream.close() - h.stream = open(h.baseFilename, h.mode) - h.release() - - def error(self, msg='', context='', severity=logging.INFO, - traceback=False): - """Write the given ``msg`` to the error log. - - This is not just for errors! Applications may call this at any time - to log application-specific information. - - If ``traceback`` is True, the traceback of the current exception - (if any) will be appended to ``msg``. - """ - exc_info = None - if traceback: - exc_info = _cperror._exc_info() - - self.error_log.log( - severity, - ' '.join((self.time(), context, msg)), - exc_info=exc_info, - ) - - def __call__(self, *args, **kwargs): - """An alias for ``error``.""" - return self.error(*args, **kwargs) - - def access(self): - """Write to the access log (in Apache/NCSA Combined Log format). - - See the - `apache documentation - `_ - for format details. - - CherryPy calls this automatically for you. Note there are no arguments; - it collects the data itself from - :class:`cherrypy.request`. - - Like Apache started doing in 2.0.46, non-printable and other special - characters in %r (and we expand that to all parts) are escaped using - \\xhh sequences, where hh stands for the hexadecimal representation - of the raw byte. Exceptions from this rule are " and \\, which are - escaped by prepending a backslash, and all whitespace characters, - which are written in their C-style notation (\\n, \\t, etc). - """ - request = cherrypy.serving.request - remote = request.remote - response = cherrypy.serving.response - outheaders = response.headers - inheaders = request.headers - if response.output_status is None: - status = '-' - else: - status = response.output_status.split(b' ', 1)[0] - status = status.decode('ISO-8859-1') - - atoms = {'h': remote.name or remote.ip, - 'l': '-', - 'u': getattr(request, 'login', None) or '-', - 't': self.time(), - 'r': request.request_line, - 's': status, - 'b': dict.get(outheaders, 'Content-Length', '') or '-', - 'f': dict.get(inheaders, 'Referer', ''), - 'a': dict.get(inheaders, 'User-Agent', ''), - 'o': dict.get(inheaders, 'Host', '-'), - 'i': request.unique_id, - 'z': LazyRfc3339UtcTime(), - } - for k, v in atoms.items(): - if not isinstance(v, str): - v = str(v) - v = v.replace('"', '\\"').encode('utf8') - # Fortunately, repr(str) escapes unprintable chars, \n, \t, etc - # and backslash for us. All we have to do is strip the quotes. - v = repr(v)[2:-1] - - # in python 3.0 the repr of bytes (as returned by encode) - # uses double \'s. But then the logger escapes them yet, again - # resulting in quadruple slashes. Remove the extra one here. - v = v.replace('\\\\', '\\') - - # Escape double-quote. - atoms[k] = v - - try: - self.access_log.log( - logging.INFO, self.access_log_format.format(**atoms)) - except Exception: - self(traceback=True) - - def time(self): - """Return now() in Apache Common Log Format (no timezone).""" - now = datetime.datetime.now() - monthnames = ['jan', 'feb', 'mar', 'apr', 'may', 'jun', - 'jul', 'aug', 'sep', 'oct', 'nov', 'dec'] - month = monthnames[now.month - 1].capitalize() - return ('[%02d/%s/%04d:%02d:%02d:%02d]' % - (now.day, month, now.year, now.hour, now.minute, now.second)) - - def _get_builtin_handler(self, log, key): - for h in log.handlers: - if getattr(h, '_cpbuiltin', None) == key: - return h - - # ------------------------- Screen handlers ------------------------- # - def _set_screen_handler(self, log, enable, stream=None): - h = self._get_builtin_handler(log, 'screen') - if enable: - if not h: - if stream is None: - stream = sys.stderr - h = logging.StreamHandler(stream) - h.setFormatter(logfmt) - h._cpbuiltin = 'screen' - log.addHandler(h) - elif h: - log.handlers.remove(h) - - @property - def screen(self): - """Turn stderr/stdout logging on or off. - - If you set this to True, it'll add the appropriate StreamHandler for - you. If you set it to False, it will remove the handler. - """ - h = self._get_builtin_handler - has_h = h(self.error_log, 'screen') or h(self.access_log, 'screen') - return bool(has_h) - - @screen.setter - def screen(self, newvalue): - self._set_screen_handler(self.error_log, newvalue, stream=sys.stderr) - self._set_screen_handler(self.access_log, newvalue, stream=sys.stdout) - - # -------------------------- File handlers -------------------------- # - - def _add_builtin_file_handler(self, log, fname): - h = logging.FileHandler(fname) - h.setFormatter(logfmt) - h._cpbuiltin = 'file' - log.addHandler(h) - - def _set_file_handler(self, log, filename): - h = self._get_builtin_handler(log, 'file') - if filename: - if h: - if h.baseFilename != os.path.abspath(filename): - h.close() - log.handlers.remove(h) - self._add_builtin_file_handler(log, filename) - else: - self._add_builtin_file_handler(log, filename) - else: - if h: - h.close() - log.handlers.remove(h) - - @property - def error_file(self): - """The filename for self.error_log. - - If you set this to a string, it'll add the appropriate FileHandler for - you. If you set it to ``None`` or ``''``, it will remove the handler. - """ - h = self._get_builtin_handler(self.error_log, 'file') - if h: - return h.baseFilename - return '' - - @error_file.setter - def error_file(self, newvalue): - self._set_file_handler(self.error_log, newvalue) - - @property - def access_file(self): - """The filename for self.access_log. - - If you set this to a string, it'll add the appropriate FileHandler for - you. If you set it to ``None`` or ``''``, it will remove the handler. - """ - h = self._get_builtin_handler(self.access_log, 'file') - if h: - return h.baseFilename - return '' - - @access_file.setter - def access_file(self, newvalue): - self._set_file_handler(self.access_log, newvalue) - - # ------------------------- WSGI handlers ------------------------- # - - def _set_wsgi_handler(self, log, enable): - h = self._get_builtin_handler(log, 'wsgi') - if enable: - if not h: - h = WSGIErrorHandler() - h.setFormatter(logfmt) - h._cpbuiltin = 'wsgi' - log.addHandler(h) - elif h: - log.handlers.remove(h) - - @property - def wsgi(self): - """Write errors to wsgi.errors. - - If you set this to True, it'll add the appropriate - :class:`WSGIErrorHandler` for you - (which writes errors to ``wsgi.errors``). - If you set it to False, it will remove the handler. - """ - return bool(self._get_builtin_handler(self.error_log, 'wsgi')) - - @wsgi.setter - def wsgi(self, newvalue): - self._set_wsgi_handler(self.error_log, newvalue) - - -class WSGIErrorHandler(logging.Handler): - - "A handler class which writes logging records to environ['wsgi.errors']." - - def flush(self): - """Flushes the stream.""" - try: - stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors') - except (AttributeError, KeyError): - pass - else: - stream.flush() - - def emit(self, record): - """Emit a record.""" - try: - stream = cherrypy.serving.request.wsgi_environ.get('wsgi.errors') - except (AttributeError, KeyError): - pass - else: - try: - msg = self.format(record) - fs = '%s\n' - import types - # if no unicode support... - if not hasattr(types, 'UnicodeType'): - stream.write(fs % msg) - else: - try: - stream.write(fs % msg) - except UnicodeError: - stream.write(fs % msg.encode('UTF-8')) - self.flush() - except Exception: - self.handleError(record) - - -class LazyRfc3339UtcTime(object): - def __str__(self): - """Return now() in RFC3339 UTC Format.""" - now = datetime.datetime.now() - return now.isoformat('T') + 'Z' diff --git a/lib/cherrypy/_cpmodpy.py b/lib/cherrypy/_cpmodpy.py deleted file mode 100644 index a08f0ed9..00000000 --- a/lib/cherrypy/_cpmodpy.py +++ /dev/null @@ -1,351 +0,0 @@ -"""Native adapter for serving CherryPy via mod_python - -Basic usage: - -########################################## -# Application in a module called myapp.py -########################################## - -import cherrypy - -class Root: - @cherrypy.expose - def index(self): - return 'Hi there, Ho there, Hey there' - - -# We will use this method from the mod_python configuration -# as the entry point to our application -def setup_server(): - cherrypy.tree.mount(Root()) - cherrypy.config.update({'environment': 'production', - 'log.screen': False, - 'show_tracebacks': False}) - -########################################## -# mod_python settings for apache2 -# This should reside in your httpd.conf -# or a file that will be loaded at -# apache startup -########################################## - -# Start -DocumentRoot "/" -Listen 8080 -LoadModule python_module /usr/lib/apache2/modules/mod_python.so - - - PythonPath "sys.path+['/path/to/my/application']" - SetHandler python-program - PythonHandler cherrypy._cpmodpy::handler - PythonOption cherrypy.setup myapp::setup_server - PythonDebug On - -# End - -The actual path to your mod_python.so is dependent on your -environment. In this case we suppose a global mod_python -installation on a Linux distribution such as Ubuntu. - -We do set the PythonPath configuration setting so that -your application can be found by from the user running -the apache2 instance. Of course if your application -resides in the global site-package this won't be needed. - -Then restart apache2 and access http://127.0.0.1:8080 -""" - -import io -import logging -import os -import re -import sys - -from more_itertools import always_iterable - -import cherrypy -from cherrypy._cperror import format_exc, bare_error -from cherrypy.lib import httputil - - -# ------------------------------ Request-handling - - -def setup(req): - from mod_python import apache - - # Run any setup functions defined by a "PythonOption cherrypy.setup" - # directive. - options = req.get_options() - if 'cherrypy.setup' in options: - for function in options['cherrypy.setup'].split(): - atoms = function.split('::', 1) - if len(atoms) == 1: - mod = __import__(atoms[0], globals(), locals()) - else: - modname, fname = atoms - mod = __import__(modname, globals(), locals(), [fname]) - func = getattr(mod, fname) - func() - - cherrypy.config.update({'log.screen': False, - 'tools.ignore_headers.on': True, - 'tools.ignore_headers.headers': ['Range'], - }) - - engine = cherrypy.engine - if hasattr(engine, 'signal_handler'): - engine.signal_handler.unsubscribe() - if hasattr(engine, 'console_control_handler'): - engine.console_control_handler.unsubscribe() - engine.autoreload.unsubscribe() - cherrypy.server.unsubscribe() - - @engine.subscribe('log') - def _log(msg, level): - newlevel = apache.APLOG_ERR - if logging.DEBUG >= level: - newlevel = apache.APLOG_DEBUG - elif logging.INFO >= level: - newlevel = apache.APLOG_INFO - elif logging.WARNING >= level: - newlevel = apache.APLOG_WARNING - # On Windows, req.server is required or the msg will vanish. See - # http://www.modpython.org/pipermail/mod_python/2003-October/014291.html - # Also, "When server is not specified...LogLevel does not apply..." - apache.log_error(msg, newlevel, req.server) - - engine.start() - - def cherrypy_cleanup(data): - engine.exit() - try: - # apache.register_cleanup wasn't available until 3.1.4. - apache.register_cleanup(cherrypy_cleanup) - except AttributeError: - req.server.register_cleanup(req, cherrypy_cleanup) - - -class _ReadOnlyRequest: - expose = ('read', 'readline', 'readlines') - - def __init__(self, req): - for method in self.expose: - self.__dict__[method] = getattr(req, method) - - -recursive = False - -_isSetUp = False - - -def handler(req): - from mod_python import apache - try: - global _isSetUp - if not _isSetUp: - setup(req) - _isSetUp = True - - # Obtain a Request object from CherryPy - local = req.connection.local_addr - local = httputil.Host( - local[0], local[1], req.connection.local_host or '') - remote = req.connection.remote_addr - remote = httputil.Host( - remote[0], remote[1], req.connection.remote_host or '') - - scheme = req.parsed_uri[0] or 'http' - req.get_basic_auth_pw() - - try: - # apache.mpm_query only became available in mod_python 3.1 - q = apache.mpm_query - threaded = q(apache.AP_MPMQ_IS_THREADED) - forked = q(apache.AP_MPMQ_IS_FORKED) - except AttributeError: - bad_value = ("You must provide a PythonOption '%s', " - "either 'on' or 'off', when running a version " - 'of mod_python < 3.1') - - options = req.get_options() - - threaded = options.get('multithread', '').lower() - if threaded == 'on': - threaded = True - elif threaded == 'off': - threaded = False - else: - raise ValueError(bad_value % 'multithread') - - forked = options.get('multiprocess', '').lower() - if forked == 'on': - forked = True - elif forked == 'off': - forked = False - else: - raise ValueError(bad_value % 'multiprocess') - - sn = cherrypy.tree.script_name(req.uri or '/') - if sn is None: - send_response(req, '404 Not Found', [], '') - else: - app = cherrypy.tree.apps[sn] - method = req.method - path = req.uri - qs = req.args or '' - reqproto = req.protocol - headers = list(req.headers_in.copy().items()) - rfile = _ReadOnlyRequest(req) - prev = None - - try: - redirections = [] - while True: - request, response = app.get_serving(local, remote, scheme, - 'HTTP/1.1') - request.login = req.user - request.multithread = bool(threaded) - request.multiprocess = bool(forked) - request.app = app - request.prev = prev - - # Run the CherryPy Request object and obtain the response - try: - request.run(method, path, qs, reqproto, headers, rfile) - break - except cherrypy.InternalRedirect: - ir = sys.exc_info()[1] - app.release_serving() - prev = request - - if not recursive: - if ir.path in redirections: - raise RuntimeError( - 'InternalRedirector visited the same URL ' - 'twice: %r' % ir.path) - else: - # Add the *previous* path_info + qs to - # redirections. - if qs: - qs = '?' + qs - redirections.append(sn + path + qs) - - # Munge environment and try again. - method = 'GET' - path = ir.path - qs = ir.query_string - rfile = io.BytesIO() - - send_response( - req, response.output_status, response.header_list, - response.body, response.stream) - finally: - app.release_serving() - except Exception: - tb = format_exc() - cherrypy.log(tb, 'MOD_PYTHON', severity=logging.ERROR) - s, h, b = bare_error() - send_response(req, s, h, b) - return apache.OK - - -def send_response(req, status, headers, body, stream=False): - # Set response status - req.status = int(status[:3]) - - # Set response headers - req.content_type = 'text/plain' - for header, value in headers: - if header.lower() == 'content-type': - req.content_type = value - continue - req.headers_out.add(header, value) - - if stream: - # Flush now so the status and headers are sent immediately. - req.flush() - - # Set response body - for seg in always_iterable(body): - req.write(seg) - - -# --------------- Startup tools for CherryPy + mod_python --------------- # -try: - import subprocess - - def popen(fullcmd): - p = subprocess.Popen(fullcmd, shell=True, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - close_fds=True) - return p.stdout -except ImportError: - def popen(fullcmd): - pipein, pipeout = os.popen4(fullcmd) - return pipeout - - -def read_process(cmd, args=''): - fullcmd = '%s %s' % (cmd, args) - pipeout = popen(fullcmd) - try: - firstline = pipeout.readline() - cmd_not_found = re.search( - b'(not recognized|No such file|not found)', - firstline, - re.IGNORECASE - ) - if cmd_not_found: - raise IOError('%s must be on your system path.' % cmd) - output = firstline + pipeout.read() - finally: - pipeout.close() - return output - - -class ModPythonServer(object): - - template = """ -# Apache2 server configuration file for running CherryPy with mod_python. - -DocumentRoot "/" -Listen %(port)s -LoadModule python_module modules/mod_python.so - - - SetHandler python-program - PythonHandler %(handler)s - PythonDebug On -%(opts)s - -""" - - def __init__(self, loc='/', port=80, opts=None, apache_path='apache', - handler='cherrypy._cpmodpy::handler'): - self.loc = loc - self.port = port - self.opts = opts - self.apache_path = apache_path - self.handler = handler - - def start(self): - opts = ''.join([' PythonOption %s %s\n' % (k, v) - for k, v in self.opts]) - conf_data = self.template % {'port': self.port, - 'loc': self.loc, - 'opts': opts, - 'handler': self.handler, - } - - mpconf = os.path.join(os.path.dirname(__file__), 'cpmodpy.conf') - with open(mpconf, 'wb') as f: - f.write(conf_data) - - response = read_process(self.apache_path, '-k start -f %s' % mpconf) - self.ready = True - return response - - def stop(self): - os.popen('apache -k stop') - self.ready = False diff --git a/lib/cherrypy/_cpnative_server.py b/lib/cherrypy/_cpnative_server.py deleted file mode 100644 index e9671d28..00000000 --- a/lib/cherrypy/_cpnative_server.py +++ /dev/null @@ -1,168 +0,0 @@ -"""Native adapter for serving CherryPy via its builtin server.""" - -import logging -import sys -import io - -import cheroot.server - -import cherrypy -from cherrypy._cperror import format_exc, bare_error -from cherrypy.lib import httputil -from ._cpcompat import tonative - - -class NativeGateway(cheroot.server.Gateway): - """Native gateway implementation allowing to bypass WSGI.""" - - recursive = False - - def respond(self): - """Obtain response from CherryPy machinery and then send it.""" - req = self.req - try: - # Obtain a Request object from CherryPy - local = req.server.bind_addr # FIXME: handle UNIX sockets - local = tonative(local[0]), local[1] - local = httputil.Host(local[0], local[1], '') - remote = tonative(req.conn.remote_addr), req.conn.remote_port - remote = httputil.Host(remote[0], remote[1], '') - - scheme = tonative(req.scheme) - sn = cherrypy.tree.script_name(tonative(req.uri or '/')) - if sn is None: - self.send_response('404 Not Found', [], ['']) - else: - app = cherrypy.tree.apps[sn] - method = tonative(req.method) - path = tonative(req.path) - qs = tonative(req.qs or '') - headers = ( - (tonative(h), tonative(v)) - for h, v in req.inheaders.items() - ) - rfile = req.rfile - prev = None - - try: - redirections = [] - while True: - request, response = app.get_serving( - local, remote, scheme, 'HTTP/1.1') - request.multithread = True - request.multiprocess = False - request.app = app - request.prev = prev - - # Run the CherryPy Request object and obtain the - # response - try: - request.run( - method, path, qs, - tonative(req.request_protocol), - headers, rfile, - ) - break - except cherrypy.InternalRedirect: - ir = sys.exc_info()[1] - app.release_serving() - prev = request - - if not self.recursive: - if ir.path in redirections: - raise RuntimeError( - 'InternalRedirector visited the same ' - 'URL twice: %r' % ir.path) - else: - # Add the *previous* path_info + qs to - # redirections. - if qs: - qs = '?' + qs - redirections.append(sn + path + qs) - - # Munge environment and try again. - method = 'GET' - path = ir.path - qs = ir.query_string - rfile = io.BytesIO() - - self.send_response( - response.output_status, response.header_list, - response.body) - finally: - app.release_serving() - except Exception: - tb = format_exc() - # print tb - cherrypy.log(tb, 'NATIVE_ADAPTER', severity=logging.ERROR) - s, h, b = bare_error() - self.send_response(s, h, b) - - def send_response(self, status, headers, body): - """Send response to HTTP request.""" - req = self.req - - # Set response status - req.status = status or b'500 Server Error' - - # Set response headers - for header, value in headers: - req.outheaders.append((header, value)) - if (req.ready and not req.sent_headers): - req.sent_headers = True - req.send_headers() - - # Set response body - for seg in body: - req.write(seg) - - -class CPHTTPServer(cheroot.server.HTTPServer): - """Wrapper for cheroot.server.HTTPServer. - - cheroot has been designed to not reference CherryPy in any way, - so that it can be used in other frameworks and applications. - Therefore, we wrap it here, so we can apply some attributes - from config -> cherrypy.server -> HTTPServer. - """ - - def __init__(self, server_adapter=cherrypy.server): - """Initialize CPHTTPServer.""" - self.server_adapter = server_adapter - - server_name = (self.server_adapter.socket_host or - self.server_adapter.socket_file or - None) - - cheroot.server.HTTPServer.__init__( - self, server_adapter.bind_addr, NativeGateway, - minthreads=server_adapter.thread_pool, - maxthreads=server_adapter.thread_pool_max, - server_name=server_name) - - self.max_request_header_size = ( - self.server_adapter.max_request_header_size or 0) - self.max_request_body_size = ( - self.server_adapter.max_request_body_size or 0) - self.request_queue_size = self.server_adapter.socket_queue_size - self.timeout = self.server_adapter.socket_timeout - self.shutdown_timeout = self.server_adapter.shutdown_timeout - self.protocol = self.server_adapter.protocol_version - self.nodelay = self.server_adapter.nodelay - - ssl_module = self.server_adapter.ssl_module or 'pyopenssl' - if self.server_adapter.ssl_context: - adapter_class = cheroot.server.get_ssl_adapter_class(ssl_module) - self.ssl_adapter = adapter_class( - self.server_adapter.ssl_certificate, - self.server_adapter.ssl_private_key, - self.server_adapter.ssl_certificate_chain, - self.server_adapter.ssl_ciphers) - self.ssl_adapter.context = self.server_adapter.ssl_context - elif self.server_adapter.ssl_certificate: - adapter_class = cheroot.server.get_ssl_adapter_class(ssl_module) - self.ssl_adapter = adapter_class( - self.server_adapter.ssl_certificate, - self.server_adapter.ssl_private_key, - self.server_adapter.ssl_certificate_chain, - self.server_adapter.ssl_ciphers) diff --git a/lib/cherrypy/_cpreqbody.py b/lib/cherrypy/_cpreqbody.py deleted file mode 100644 index 4d3cefe7..00000000 --- a/lib/cherrypy/_cpreqbody.py +++ /dev/null @@ -1,993 +0,0 @@ -"""Request body processing for CherryPy. - -.. versionadded:: 3.2 - -Application authors have complete control over the parsing of HTTP request -entities. In short, -:attr:`cherrypy.request.body` -is now always set to an instance of -:class:`RequestBody`, -and *that* class is a subclass of :class:`Entity`. - -When an HTTP request includes an entity body, it is often desirable to -provide that information to applications in a form other than the raw bytes. -Different content types demand different approaches. Examples: - - * For a GIF file, we want the raw bytes in a stream. - * An HTML form is better parsed into its component fields, and each text field - decoded from bytes to unicode. - * A JSON body should be deserialized into a Python dict or list. - -When the request contains a Content-Type header, the media type is used as a -key to look up a value in the -:attr:`request.body.processors` dict. -If the full media -type is not found, then the major type is tried; for example, if no processor -is found for the 'image/jpeg' type, then we look for a processor for the -'image' types altogether. If neither the full type nor the major type has a -matching processor, then a default processor is used -(:func:`default_proc`). For most -types, this means no processing is done, and the body is left unread as a -raw byte stream. Processors are configurable in an 'on_start_resource' hook. - -Some processors, especially those for the 'text' types, attempt to decode bytes -to unicode. If the Content-Type request header includes a 'charset' parameter, -this is used to decode the entity. Otherwise, one or more default charsets may -be attempted, although this decision is up to each processor. If a processor -successfully decodes an Entity or Part, it should set the -:attr:`charset` attribute -on the Entity or Part to the name of the successful charset, so that -applications can easily re-encode or transcode the value if they wish. - -If the Content-Type of the request entity is of major type 'multipart', then -the above parsing process, and possibly a decoding process, is performed for -each part. - -For both the full entity and multipart parts, a Content-Disposition header may -be used to fill :attr:`name` and -:attr:`filename` attributes on the -request.body or the Part. - -.. _custombodyprocessors: - -Custom Processors -================= - -You can add your own processors for any specific or major MIME type. Simply add -it to the :attr:`processors` dict in a -hook/tool that runs at ``on_start_resource`` or ``before_request_body``. -Here's the built-in JSON tool for an example:: - - def json_in(force=True, debug=False): - request = cherrypy.serving.request - def json_processor(entity): - '''Read application/json data into request.json.''' - if not entity.headers.get("Content-Length", ""): - raise cherrypy.HTTPError(411) - - body = entity.fp.read() - try: - request.json = json_decode(body) - except ValueError: - raise cherrypy.HTTPError(400, 'Invalid JSON document') - if force: - request.body.processors.clear() - request.body.default_proc = cherrypy.HTTPError( - 415, 'Expected an application/json content type') - request.body.processors['application/json'] = json_processor - -We begin by defining a new ``json_processor`` function to stick in the -``processors`` dictionary. All processor functions take a single argument, -the ``Entity`` instance they are to process. It will be called whenever a -request is received (for those URI's where the tool is turned on) which -has a ``Content-Type`` of "application/json". - -First, it checks for a valid ``Content-Length`` (raising 411 if not valid), -then reads the remaining bytes on the socket. The ``fp`` object knows its -own length, so it won't hang waiting for data that never arrives. It will -return when all data has been read. Then, we decode those bytes using -Python's built-in ``json`` module, and stick the decoded result onto -``request.json`` . If it cannot be decoded, we raise 400. - -If the "force" argument is True (the default), the ``Tool`` clears the -``processors`` dict so that request entities of other ``Content-Types`` -aren't parsed at all. Since there's no entry for those invalid MIME -types, the ``default_proc`` method of ``cherrypy.request.body`` is -called. But this does nothing by default (usually to provide the page -handler an opportunity to handle it.) -But in our case, we want to raise 415, so we replace -``request.body.default_proc`` -with the error (``HTTPError`` instances, when called, raise themselves). - -If we were defining a custom processor, we can do so without making a ``Tool``. -Just add the config entry:: - - request.body.processors = {'application/json': json_processor} - -Note that you can only replace the ``processors`` dict wholesale this way, -not update the existing one. -""" - -try: - from io import DEFAULT_BUFFER_SIZE -except ImportError: - DEFAULT_BUFFER_SIZE = 8192 -import re -import sys -import tempfile -from urllib.parse import unquote - -import cheroot.server - -import cherrypy -from cherrypy._cpcompat import ntou -from cherrypy.lib import httputil - - -def unquote_plus(bs): - """Bytes version of urllib.parse.unquote_plus.""" - bs = bs.replace(b'+', b' ') - atoms = bs.split(b'%') - for i in range(1, len(atoms)): - item = atoms[i] - try: - pct = int(item[:2], 16) - atoms[i] = bytes([pct]) + item[2:] - except ValueError: - pass - return b''.join(atoms) - - -# ------------------------------- Processors -------------------------------- # - -def process_urlencoded(entity): - """Read application/x-www-form-urlencoded data into entity.params.""" - qs = entity.fp.read() - for charset in entity.attempt_charsets: - try: - params = {} - for aparam in qs.split(b'&'): - for pair in aparam.split(b';'): - if not pair: - continue - - atoms = pair.split(b'=', 1) - if len(atoms) == 1: - atoms.append(b'') - - key = unquote_plus(atoms[0]).decode(charset) - value = unquote_plus(atoms[1]).decode(charset) - - if key in params: - if not isinstance(params[key], list): - params[key] = [params[key]] - params[key].append(value) - else: - params[key] = value - except UnicodeDecodeError: - pass - else: - entity.charset = charset - break - else: - raise cherrypy.HTTPError( - 400, 'The request entity could not be decoded. The following ' - 'charsets were attempted: %s' % repr(entity.attempt_charsets)) - - # Now that all values have been successfully parsed and decoded, - # apply them to the entity.params dict. - for key, value in params.items(): - if key in entity.params: - if not isinstance(entity.params[key], list): - entity.params[key] = [entity.params[key]] - entity.params[key].append(value) - else: - entity.params[key] = value - - -def process_multipart(entity): - """Read all multipart parts into entity.parts.""" - ib = '' - if 'boundary' in entity.content_type.params: - # http://tools.ietf.org/html/rfc2046#section-5.1.1 - # "The grammar for parameters on the Content-type field is such that it - # is often necessary to enclose the boundary parameter values in quotes - # on the Content-type line" - ib = entity.content_type.params['boundary'].strip('"') - - if not re.match('^[ -~]{0,200}[!-~]$', ib): - raise ValueError('Invalid boundary in multipart form: %r' % (ib,)) - - ib = ('--' + ib).encode('ascii') - - # Find the first marker - while True: - b = entity.readline() - if not b: - return - - b = b.strip() - if b == ib: - break - - # Read all parts - while True: - part = entity.part_class.from_fp(entity.fp, ib) - entity.parts.append(part) - part.process() - if part.fp.done: - break - - -def process_multipart_form_data(entity): - """Read all multipart/form-data parts into entity.parts or entity.params. - """ - process_multipart(entity) - - kept_parts = [] - for part in entity.parts: - if part.name is None: - kept_parts.append(part) - else: - if part.filename is None: - # It's a regular field - value = part.fullvalue() - else: - # It's a file upload. Retain the whole part so consumer code - # has access to its .file and .filename attributes. - value = part - - if part.name in entity.params: - if not isinstance(entity.params[part.name], list): - entity.params[part.name] = [entity.params[part.name]] - entity.params[part.name].append(value) - else: - entity.params[part.name] = value - - entity.parts = kept_parts - - -def _old_process_multipart(entity): - """The behavior of 3.2 and lower. Deprecated and will be changed in 3.3.""" - process_multipart(entity) - - params = entity.params - - for part in entity.parts: - if part.name is None: - key = ntou('parts') - else: - key = part.name - - if part.filename is None: - # It's a regular field - value = part.fullvalue() - else: - # It's a file upload. Retain the whole part so consumer code - # has access to its .file and .filename attributes. - value = part - - if key in params: - if not isinstance(params[key], list): - params[key] = [params[key]] - params[key].append(value) - else: - params[key] = value - - -# -------------------------------- Entities --------------------------------- # -class Entity(object): - - """An HTTP request body, or MIME multipart body. - - This class collects information about the HTTP request entity. When a - given entity is of MIME type "multipart", each part is parsed into its own - Entity instance, and the set of parts stored in - :attr:`entity.parts`. - - Between the ``before_request_body`` and ``before_handler`` tools, CherryPy - tries to process the request body (if any) by calling - :func:`request.body.process`. - This uses the ``content_type`` of the Entity to look up a suitable - processor in - :attr:`Entity.processors`, - a dict. - If a matching processor cannot be found for the complete Content-Type, - it tries again using the major type. For example, if a request with an - entity of type "image/jpeg" arrives, but no processor can be found for - that complete type, then one is sought for the major type "image". If a - processor is still not found, then the - :func:`default_proc` method - of the Entity is called (which does nothing by default; you can - override this too). - - CherryPy includes processors for the "application/x-www-form-urlencoded" - type, the "multipart/form-data" type, and the "multipart" major type. - CherryPy 3.2 processes these types almost exactly as older versions. - Parts are passed as arguments to the page handler using their - ``Content-Disposition.name`` if given, otherwise in a generic "parts" - argument. Each such part is either a string, or the - :class:`Part` itself if it's a file. (In this - case it will have ``file`` and ``filename`` attributes, or possibly a - ``value`` attribute). Each Part is itself a subclass of - Entity, and has its own ``process`` method and ``processors`` dict. - - There is a separate processor for the "multipart" major type which is more - flexible, and simply stores all multipart parts in - :attr:`request.body.parts`. You can - enable it with:: - - cherrypy.request.body.processors['multipart'] = \ - _cpreqbody.process_multipart - - in an ``on_start_resource`` tool. - """ - - # http://tools.ietf.org/html/rfc2046#section-4.1.2: - # "The default character set, which must be assumed in the - # absence of a charset parameter, is US-ASCII." - # However, many browsers send data in utf-8 with no charset. - attempt_charsets = ['utf-8'] - r"""A list of strings, each of which should be a known encoding. - - When the Content-Type of the request body warrants it, each of the given - encodings will be tried in order. The first one to successfully decode the - entity without raising an error is stored as - :attr:`entity.charset`. This defaults - to ``['utf-8']`` (plus 'ISO-8859-1' for "text/\*" types, as required by - `HTTP/1.1 - `_), - but ``['us-ascii', 'utf-8']`` for multipart parts. - """ - - charset = None - """The successful decoding; see "attempt_charsets" above.""" - - content_type = None - """The value of the Content-Type request header. - - If the Entity is part of a multipart payload, this will be the Content-Type - given in the MIME headers for this part. - """ - - default_content_type = 'application/x-www-form-urlencoded' - """This defines a default ``Content-Type`` to use if no Content-Type header - is given. The empty string is used for RequestBody, which results in the - request body not being read or parsed at all. This is by design; a missing - ``Content-Type`` header in the HTTP request entity is an error at best, - and a security hole at worst. For multipart parts, however, the MIME spec - declares that a part with no Content-Type defaults to "text/plain" - (see :class:`Part`). - """ - - filename = None - """The ``Content-Disposition.filename`` header, if available.""" - - fp = None - """The readable socket file object.""" - - headers = None - """A dict of request/multipart header names and values. - - This is a copy of the ``request.headers`` for the ``request.body``; - for multipart parts, it is the set of headers for that part. - """ - - length = None - """The value of the ``Content-Length`` header, if provided.""" - - name = None - """The "name" parameter of the ``Content-Disposition`` header, if any.""" - - params = None - """ - If the request Content-Type is 'application/x-www-form-urlencoded' or - multipart, this will be a dict of the params pulled from the entity - body; that is, it will be the portion of request.params that come - from the message body (sometimes called "POST params", although they - can be sent with various HTTP method verbs). This value is set between - the 'before_request_body' and 'before_handler' hooks (assuming that - process_request_body is True).""" - - processors = {'application/x-www-form-urlencoded': process_urlencoded, - 'multipart/form-data': process_multipart_form_data, - 'multipart': process_multipart, - } - """A dict of Content-Type names to processor methods.""" - - parts = None - """A list of Part instances if ``Content-Type`` is of major type - "multipart".""" - - part_class = None - """The class used for multipart parts. - - You can replace this with custom subclasses to alter the processing of - multipart parts. - """ - - def __init__(self, fp, headers, params=None, parts=None): - # Make an instance-specific copy of the class processors - # so Tools, etc. can replace them per-request. - self.processors = self.processors.copy() - - self.fp = fp - self.headers = headers - - if params is None: - params = {} - self.params = params - - if parts is None: - parts = [] - self.parts = parts - - # Content-Type - self.content_type = headers.elements('Content-Type') - if self.content_type: - self.content_type = self.content_type[0] - else: - self.content_type = httputil.HeaderElement.from_str( - self.default_content_type) - - # Copy the class 'attempt_charsets', prepending any Content-Type - # charset - dec = self.content_type.params.get('charset', None) - if dec: - self.attempt_charsets = [dec] + [c for c in self.attempt_charsets - if c != dec] - else: - self.attempt_charsets = self.attempt_charsets[:] - - # Length - self.length = None - clen = headers.get('Content-Length', None) - # If Transfer-Encoding is 'chunked', ignore any Content-Length. - if ( - clen is not None and - 'chunked' not in headers.get('Transfer-Encoding', '') - ): - try: - self.length = int(clen) - except ValueError: - pass - - # Content-Disposition - self.name = None - self.filename = None - disp = headers.elements('Content-Disposition') - if disp: - disp = disp[0] - if 'name' in disp.params: - self.name = disp.params['name'] - if self.name.startswith('"') and self.name.endswith('"'): - self.name = self.name[1:-1] - if 'filename' in disp.params: - self.filename = disp.params['filename'] - if ( - self.filename.startswith('"') and - self.filename.endswith('"') - ): - self.filename = self.filename[1:-1] - if 'filename*' in disp.params: - # @see https://tools.ietf.org/html/rfc5987 - encoding, lang, filename = disp.params['filename*'].split("'") - self.filename = unquote(str(filename), encoding) - - def read(self, size=None, fp_out=None): - return self.fp.read(size, fp_out) - - def readline(self, size=None): - return self.fp.readline(size) - - def readlines(self, sizehint=None): - return self.fp.readlines(sizehint) - - def __iter__(self): - return self - - def __next__(self): - line = self.readline() - if not line: - raise StopIteration - return line - - def next(self): - return self.__next__() - - def read_into_file(self, fp_out=None): - """Read the request body into fp_out (or make_file() if None). - - Return fp_out. - """ - if fp_out is None: - fp_out = self.make_file() - self.read(fp_out=fp_out) - return fp_out - - def make_file(self): - """Return a file-like object into which the request body will be read. - - By default, this will return a TemporaryFile. Override as needed. - See also :attr:`cherrypy._cpreqbody.Part.maxrambytes`.""" - return tempfile.TemporaryFile() - - def fullvalue(self): - """Return this entity as a string, whether stored in a file or not.""" - if self.file: - # It was stored in a tempfile. Read it. - self.file.seek(0) - value = self.file.read() - self.file.seek(0) - else: - value = self.value - value = self.decode_entity(value) - return value - - def decode_entity(self, value): - """Return a given byte encoded value as a string""" - for charset in self.attempt_charsets: - try: - value = value.decode(charset) - except UnicodeDecodeError: - pass - else: - self.charset = charset - return value - else: - raise cherrypy.HTTPError( - 400, - 'The request entity could not be decoded. The following ' - 'charsets were attempted: %s' % repr(self.attempt_charsets) - ) - - def process(self): - """Execute the best-match processor for the given media type.""" - proc = None - ct = self.content_type.value - try: - proc = self.processors[ct] - except KeyError: - toptype = ct.split('/', 1)[0] - try: - proc = self.processors[toptype] - except KeyError: - pass - if proc is None: - self.default_proc() - else: - proc(self) - - def default_proc(self): - """Called if a more-specific processor is not found for the - ``Content-Type``. - """ - # Leave the fp alone for someone else to read. This works fine - # for request.body, but the Part subclasses need to override this - # so they can move on to the next part. - pass - - -class Part(Entity): - - """A MIME part entity, part of a multipart entity.""" - - # "The default character set, which must be assumed in the absence of a - # charset parameter, is US-ASCII." - attempt_charsets = ['us-ascii', 'utf-8'] - r"""A list of strings, each of which should be a known encoding. - - When the Content-Type of the request body warrants it, each of the given - encodings will be tried in order. The first one to successfully decode the - entity without raising an error is stored as - :attr:`entity.charset`. This defaults - to ``['utf-8']`` (plus 'ISO-8859-1' for "text/\*" types, as required by - `HTTP/1.1 - `_), - but ``['us-ascii', 'utf-8']`` for multipart parts. - """ - - boundary = None - """The MIME multipart boundary.""" - - default_content_type = 'text/plain' - """This defines a default ``Content-Type`` to use if no Content-Type header - is given. The empty string is used for RequestBody, which results in the - request body not being read or parsed at all. This is by design; a missing - ``Content-Type`` header in the HTTP request entity is an error at best, - and a security hole at worst. For multipart parts, however (this class), - the MIME spec declares that a part with no Content-Type defaults to - "text/plain". - """ - - # This is the default in stdlib cgi. We may want to increase it. - maxrambytes = 1000 - """The threshold of bytes after which point the ``Part`` will store - its data in a file (generated by - :func:`make_file`) - instead of a string. Defaults to 1000, just like the :mod:`cgi` - module in Python's standard library. - """ - - def __init__(self, fp, headers, boundary): - Entity.__init__(self, fp, headers) - self.boundary = boundary - self.file = None - self.value = None - - @classmethod - def from_fp(cls, fp, boundary): - headers = cls.read_headers(fp) - return cls(fp, headers, boundary) - - @classmethod - def read_headers(cls, fp): - headers = httputil.HeaderMap() - while True: - line = fp.readline() - if not line: - # No more data--illegal end of headers - raise EOFError('Illegal end of headers.') - - if line == b'\r\n': - # Normal end of headers - break - if not line.endswith(b'\r\n'): - raise ValueError('MIME requires CRLF terminators: %r' % line) - - if line[0] in b' \t': - # It's a continuation line. - v = line.strip().decode('ISO-8859-1') - else: - k, v = line.split(b':', 1) - k = k.strip().decode('ISO-8859-1') - v = v.strip().decode('ISO-8859-1') - - existing = headers.get(k) - if existing: - v = ', '.join((existing, v)) - headers[k] = v - - return headers - - def read_lines_to_boundary(self, fp_out=None): - """Read bytes from self.fp and return or write them to a file. - - If the 'fp_out' argument is None (the default), all bytes read are - returned in a single byte string. - - If the 'fp_out' argument is not None, it must be a file-like - object that supports the 'write' method; all bytes read will be - written to the fp, and that fp is returned. - """ - endmarker = self.boundary + b'--' - delim = b'' - prev_lf = True - lines = [] - seen = 0 - while True: - line = self.fp.readline(1 << 16) - if not line: - raise EOFError('Illegal end of multipart body.') - if line.startswith(b'--') and prev_lf: - strippedline = line.strip() - if strippedline == self.boundary: - break - if strippedline == endmarker: - self.fp.finish() - break - - line = delim + line - - if line.endswith(b'\r\n'): - delim = b'\r\n' - line = line[:-2] - prev_lf = True - elif line.endswith(b'\n'): - delim = b'\n' - line = line[:-1] - prev_lf = True - else: - delim = b'' - prev_lf = False - - if fp_out is None: - lines.append(line) - seen += len(line) - if seen > self.maxrambytes: - fp_out = self.make_file() - for line in lines: - fp_out.write(line) - else: - fp_out.write(line) - - if fp_out is None: - result = b''.join(lines) - return result - else: - fp_out.seek(0) - return fp_out - - def default_proc(self): - """Called if a more-specific processor is not found for the - ``Content-Type``. - """ - if self.filename: - # Always read into a file if a .filename was given. - self.file = self.read_into_file() - else: - result = self.read_lines_to_boundary() - if isinstance(result, bytes): - self.value = result - else: - self.file = result - - def read_into_file(self, fp_out=None): - """Read the request body into fp_out (or make_file() if None). - - Return fp_out. - """ - if fp_out is None: - fp_out = self.make_file() - self.read_lines_to_boundary(fp_out=fp_out) - return fp_out - - -Entity.part_class = Part - -inf = float('inf') - - -class SizedReader: - - def __init__(self, fp, length, maxbytes, bufsize=DEFAULT_BUFFER_SIZE, - has_trailers=False): - # Wrap our fp in a buffer so peek() works - self.fp = fp - self.length = length - self.maxbytes = maxbytes - self.buffer = b'' - self.bufsize = bufsize - self.bytes_read = 0 - self.done = False - self.has_trailers = has_trailers - - def read(self, size=None, fp_out=None): - """Read bytes from the request body and return or write them to a file. - - A number of bytes less than or equal to the 'size' argument are read - off the socket. The actual number of bytes read are tracked in - self.bytes_read. The number may be smaller than 'size' when 1) the - client sends fewer bytes, 2) the 'Content-Length' request header - specifies fewer bytes than requested, or 3) the number of bytes read - exceeds self.maxbytes (in which case, 413 is raised). - - If the 'fp_out' argument is None (the default), all bytes read are - returned in a single byte string. - - If the 'fp_out' argument is not None, it must be a file-like - object that supports the 'write' method; all bytes read will be - written to the fp, and None is returned. - """ - - if self.length is None: - if size is None: - remaining = inf - else: - remaining = size - else: - remaining = self.length - self.bytes_read - if size and size < remaining: - remaining = size - if remaining == 0: - self.finish() - if fp_out is None: - return b'' - else: - return None - - chunks = [] - - # Read bytes from the buffer. - if self.buffer: - if remaining is inf: - data = self.buffer - self.buffer = b'' - else: - data = self.buffer[:remaining] - self.buffer = self.buffer[remaining:] - datalen = len(data) - remaining -= datalen - - # Check lengths. - self.bytes_read += datalen - if self.maxbytes and self.bytes_read > self.maxbytes: - raise cherrypy.HTTPError(413) - - # Store the data. - if fp_out is None: - chunks.append(data) - else: - fp_out.write(data) - - # Read bytes from the socket. - while remaining > 0: - chunksize = min(remaining, self.bufsize) - try: - data = self.fp.read(chunksize) - except Exception: - e = sys.exc_info()[1] - if e.__class__.__name__ == 'MaxSizeExceeded': - # Post data is too big - raise cherrypy.HTTPError( - 413, 'Maximum request length: %r' % e.args[1]) - else: - raise - if not data: - self.finish() - break - datalen = len(data) - remaining -= datalen - - # Check lengths. - self.bytes_read += datalen - if self.maxbytes and self.bytes_read > self.maxbytes: - raise cherrypy.HTTPError(413) - - # Store the data. - if fp_out is None: - chunks.append(data) - else: - fp_out.write(data) - - if fp_out is None: - return b''.join(chunks) - - def readline(self, size=None): - """Read a line from the request body and return it.""" - chunks = [] - while size is None or size > 0: - chunksize = self.bufsize - if size is not None and size < self.bufsize: - chunksize = size - data = self.read(chunksize) - if not data: - break - pos = data.find(b'\n') + 1 - if pos: - chunks.append(data[:pos]) - remainder = data[pos:] - self.buffer += remainder - self.bytes_read -= len(remainder) - break - else: - chunks.append(data) - return b''.join(chunks) - - def readlines(self, sizehint=None): - """Read lines from the request body and return them.""" - if self.length is not None: - if sizehint is None: - sizehint = self.length - self.bytes_read - else: - sizehint = min(sizehint, self.length - self.bytes_read) - - lines = [] - seen = 0 - while True: - line = self.readline() - if not line: - break - lines.append(line) - seen += len(line) - if seen >= sizehint: - break - return lines - - def finish(self): - self.done = True - if self.has_trailers and hasattr(self.fp, 'read_trailer_lines'): - self.trailers = {} - - try: - for line in self.fp.read_trailer_lines(): - if line[0] in b' \t': - # It's a continuation line. - v = line.strip() - else: - try: - k, v = line.split(b':', 1) - except ValueError: - raise ValueError('Illegal header line.') - k = k.strip().title() - v = v.strip() - - if k in cheroot.server.comma_separated_headers: - existing = self.trailers.get(k) - if existing: - v = b', '.join((existing, v)) - self.trailers[k] = v - except Exception: - e = sys.exc_info()[1] - if e.__class__.__name__ == 'MaxSizeExceeded': - # Post data is too big - raise cherrypy.HTTPError( - 413, 'Maximum request length: %r' % e.args[1]) - else: - raise - - -class RequestBody(Entity): - - """The entity of the HTTP request.""" - - bufsize = 8 * 1024 - """The buffer size used when reading the socket.""" - - # Don't parse the request body at all if the client didn't provide - # a Content-Type header. See - # https://github.com/cherrypy/cherrypy/issues/790 - default_content_type = '' - """This defines a default ``Content-Type`` to use if no Content-Type header - is given. The empty string is used for RequestBody, which results in the - request body not being read or parsed at all. This is by design; a missing - ``Content-Type`` header in the HTTP request entity is an error at best, - and a security hole at worst. For multipart parts, however, the MIME spec - declares that a part with no Content-Type defaults to "text/plain" - (see :class:`Part`). - """ - - maxbytes = None - """Raise ``MaxSizeExceeded`` if more bytes than this are read from - the socket. - """ - - def __init__(self, fp, headers, params=None, request_params=None): - Entity.__init__(self, fp, headers, params) - - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1 - # When no explicit charset parameter is provided by the - # sender, media subtypes of the "text" type are defined - # to have a default charset value of "ISO-8859-1" when - # received via HTTP. - if self.content_type.value.startswith('text/'): - for c in ('ISO-8859-1', 'iso-8859-1', 'Latin-1', 'latin-1'): - if c in self.attempt_charsets: - break - else: - self.attempt_charsets.append('ISO-8859-1') - - # Temporary fix while deprecating passing .parts as .params. - self.processors['multipart'] = _old_process_multipart - - if request_params is None: - request_params = {} - self.request_params = request_params - - def process(self): - """Process the request entity based on its Content-Type.""" - # "The presence of a message-body in a request is signaled by the - # inclusion of a Content-Length or Transfer-Encoding header field in - # the request's message-headers." - # It is possible to send a POST request with no body, for example; - # however, app developers are responsible in that case to set - # cherrypy.request.process_body to False so this method isn't called. - h = cherrypy.serving.request.headers - if 'Content-Length' not in h and 'Transfer-Encoding' not in h: - raise cherrypy.HTTPError(411) - - self.fp = SizedReader(self.fp, self.length, - self.maxbytes, bufsize=self.bufsize, - has_trailers='Trailer' in h) - super(RequestBody, self).process() - - # Body params should also be a part of the request_params - # add them in here. - request_params = self.request_params - for key, value in self.params.items(): - if key in request_params: - if not isinstance(request_params[key], list): - request_params[key] = [request_params[key]] - request_params[key].append(value) - else: - request_params[key] = value diff --git a/lib/cherrypy/_cprequest.py b/lib/cherrypy/_cprequest.py deleted file mode 100644 index a661112c..00000000 --- a/lib/cherrypy/_cprequest.py +++ /dev/null @@ -1,935 +0,0 @@ -import sys -import time -import collections -import operator -from http.cookies import SimpleCookie, CookieError - -import uuid - -from more_itertools import consume - -import cherrypy -from cherrypy._cpcompat import ntob -from cherrypy import _cpreqbody -from cherrypy._cperror import format_exc, bare_error -from cherrypy.lib import httputil, reprconf, encoding - - -class Hook(object): - - """A callback and its metadata: failsafe, priority, and kwargs.""" - - callback = None - """ - The bare callable that this Hook object is wrapping, which will - be called when the Hook is called.""" - - failsafe = False - """ - If True, the callback is guaranteed to run even if other callbacks - from the same call point raise exceptions.""" - - priority = 50 - """ - Defines the order of execution for a list of Hooks. Priority numbers - should be limited to the closed interval [0, 100], but values outside - this range are acceptable, as are fractional values.""" - - kwargs = {} - """ - A set of keyword arguments that will be passed to the - callable on each call.""" - - def __init__(self, callback, failsafe=None, priority=None, **kwargs): - self.callback = callback - - if failsafe is None: - failsafe = getattr(callback, 'failsafe', False) - self.failsafe = failsafe - - if priority is None: - priority = getattr(callback, 'priority', 50) - self.priority = priority - - self.kwargs = kwargs - - def __lt__(self, other): - """ - Hooks sort by priority, ascending, such that - hooks of lower priority are run first. - """ - return self.priority < other.priority - - def __call__(self): - """Run self.callback(**self.kwargs).""" - return self.callback(**self.kwargs) - - def __repr__(self): - cls = self.__class__ - return ('%s.%s(callback=%r, failsafe=%r, priority=%r, %s)' - % (cls.__module__, cls.__name__, self.callback, - self.failsafe, self.priority, - ', '.join(['%s=%r' % (k, v) - for k, v in self.kwargs.items()]))) - - -class HookMap(dict): - - """A map of call points to lists of callbacks (Hook objects).""" - - def __new__(cls, points=None): - d = dict.__new__(cls) - for p in points or []: - d[p] = [] - return d - - def __init__(self, *a, **kw): - pass - - def attach(self, point, callback, failsafe=None, priority=None, **kwargs): - """Append a new Hook made from the supplied arguments.""" - self[point].append(Hook(callback, failsafe, priority, **kwargs)) - - def run(self, point): - """Execute all registered Hooks (callbacks) for the given point.""" - self.run_hooks(iter(sorted(self[point]))) - - @classmethod - def run_hooks(cls, hooks): - """Execute the indicated hooks, trapping errors. - - Hooks with ``.failsafe == True`` are guaranteed to run - even if others at the same hookpoint fail. In this case, - log the failure and proceed on to the next hook. The only - way to stop all processing from one of these hooks is - to raise a BaseException like SystemExit or - KeyboardInterrupt and stop the whole server. - """ - assert isinstance(hooks, collections.abc.Iterator) - quiet_errors = ( - cherrypy.HTTPError, - cherrypy.HTTPRedirect, - cherrypy.InternalRedirect, - ) - safe = filter(operator.attrgetter('failsafe'), hooks) - for hook in hooks: - try: - hook() - except quiet_errors: - cls.run_hooks(safe) - raise - except Exception: - cherrypy.log(traceback=True, severity=40) - cls.run_hooks(safe) - raise - - def __copy__(self): - newmap = self.__class__() - # We can't just use 'update' because we want copies of the - # mutable values (each is a list) as well. - for k, v in self.items(): - newmap[k] = v[:] - return newmap - copy = __copy__ - - def __repr__(self): - cls = self.__class__ - return '%s.%s(points=%r)' % ( - cls.__module__, - cls.__name__, - list(self) - ) - - -# Config namespace handlers - -def hooks_namespace(k, v): - """Attach bare hooks declared in config.""" - # Use split again to allow multiple hooks for a single - # hookpoint per path (e.g. "hooks.before_handler.1"). - # Little-known fact you only get from reading source ;) - hookpoint = k.split('.', 1)[0] - if isinstance(v, str): - v = cherrypy.lib.reprconf.attributes(v) - if not isinstance(v, Hook): - v = Hook(v) - cherrypy.serving.request.hooks[hookpoint].append(v) - - -def request_namespace(k, v): - """Attach request attributes declared in config.""" - # Provides config entries to set request.body attrs (like - # attempt_charsets). - if k[:5] == 'body.': - setattr(cherrypy.serving.request.body, k[5:], v) - else: - setattr(cherrypy.serving.request, k, v) - - -def response_namespace(k, v): - """Attach response attributes declared in config.""" - # Provides config entries to set default response headers - # http://cherrypy.dev/ticket/889 - if k[:8] == 'headers.': - cherrypy.serving.response.headers[k.split('.', 1)[1]] = v - else: - setattr(cherrypy.serving.response, k, v) - - -def error_page_namespace(k, v): - """Attach error pages declared in config.""" - if k != 'default': - k = int(k) - cherrypy.serving.request.error_page[k] = v - - -hookpoints = ['on_start_resource', 'before_request_body', - 'before_handler', 'before_finalize', - 'on_end_resource', 'on_end_request', - 'before_error_response', 'after_error_response'] - - -class Request(object): - - """An HTTP request. - - This object represents the metadata of an HTTP request message; - that is, it contains attributes which describe the environment - in which the request URL, headers, and body were sent (if you - want tools to interpret the headers and body, those are elsewhere, - mostly in Tools). This 'metadata' consists of socket data, - transport characteristics, and the Request-Line. This object - also contains data regarding the configuration in effect for - the given URL, and the execution plan for generating a response. - """ - - prev = None - """ - The previous Request object (if any). This should be None - unless we are processing an InternalRedirect.""" - - # Conversation/connection attributes - local = httputil.Host('127.0.0.1', 80) - 'An httputil.Host(ip, port, hostname) object for the server socket.' - - remote = httputil.Host('127.0.0.1', 1111) - 'An httputil.Host(ip, port, hostname) object for the client socket.' - - scheme = 'http' - """ - The protocol used between client and server. In most cases, - this will be either 'http' or 'https'.""" - - server_protocol = 'HTTP/1.1' - """ - The HTTP version for which the HTTP server is at least - conditionally compliant.""" - - base = '' - """The (scheme://host) portion of the requested URL. - In some cases (e.g. when proxying via mod_rewrite), this may contain - path segments which cherrypy.url uses when constructing url's, but - which otherwise are ignored by CherryPy. Regardless, this value - MUST NOT end in a slash.""" - - # Request-Line attributes - request_line = '' - """ - The complete Request-Line received from the client. This is a - single string consisting of the request method, URI, and protocol - version (joined by spaces). Any final CRLF is removed.""" - - method = 'GET' - """ - Indicates the HTTP method to be performed on the resource identified - by the Request-URI. Common methods include GET, HEAD, POST, PUT, and - DELETE. CherryPy allows any extension method; however, various HTTP - servers and gateways may restrict the set of allowable methods. - CherryPy applications SHOULD restrict the set (on a per-URI basis).""" - - query_string = '' - """ - The query component of the Request-URI, a string of information to be - interpreted by the resource. The query portion of a URI follows the - path component, and is separated by a '?'. For example, the URI - 'http://www.cherrypy.dev/wiki?a=3&b=4' has the query component, - 'a=3&b=4'.""" - - query_string_encoding = 'utf8' - """ - The encoding expected for query string arguments after % HEX HEX decoding). - If a query string is provided that cannot be decoded with this encoding, - 404 is raised (since technically it's a different URI). If you want - arbitrary encodings to not error, set this to 'Latin-1'; you can then - encode back to bytes and re-decode to whatever encoding you like later. - """ - - protocol = (1, 1) - """The HTTP protocol version corresponding to the set - of features which should be allowed in the response. If BOTH - the client's request message AND the server's level of HTTP - compliance is HTTP/1.1, this attribute will be the tuple (1, 1). - If either is 1.0, this attribute will be the tuple (1, 0). - Lower HTTP protocol versions are not explicitly supported.""" - - params = {} - """ - A dict which combines query string (GET) and request entity (POST) - variables. This is populated in two stages: GET params are added - before the 'on_start_resource' hook, and POST params are added - between the 'before_request_body' and 'before_handler' hooks.""" - - # Message attributes - header_list = [] - """ - A list of the HTTP request headers as (name, value) tuples. - In general, you should use request.headers (a dict) instead.""" - - headers = httputil.HeaderMap() - """ - A dict-like object containing the request headers. Keys are header - names (in Title-Case format); however, you may get and set them in - a case-insensitive manner. That is, headers['Content-Type'] and - headers['content-type'] refer to the same value. Values are header - values (decoded according to :rfc:`2047` if necessary). See also: - httputil.HeaderMap, httputil.HeaderElement.""" - - cookie = SimpleCookie() - """See help(Cookie).""" - - rfile = None - """ - If the request included an entity (body), it will be available - as a stream in this attribute. However, the rfile will normally - be read for you between the 'before_request_body' hook and the - 'before_handler' hook, and the resulting string is placed into - either request.params or the request.body attribute. - - You may disable the automatic consumption of the rfile by setting - request.process_request_body to False, either in config for the desired - path, or in an 'on_start_resource' or 'before_request_body' hook. - - WARNING: In almost every case, you should not attempt to read from the - rfile stream after CherryPy's automatic mechanism has read it. If you - turn off the automatic parsing of rfile, you should read exactly the - number of bytes specified in request.headers['Content-Length']. - Ignoring either of these warnings may result in a hung request thread - or in corruption of the next (pipelined) request. - """ - - process_request_body = True - """ - If True, the rfile (if any) is automatically read and parsed, - and the result placed into request.params or request.body.""" - - methods_with_bodies = ('POST', 'PUT', 'PATCH') - """ - A sequence of HTTP methods for which CherryPy will automatically - attempt to read a body from the rfile. If you are going to change - this property, modify it on the configuration (recommended) - or on the "hook point" `on_start_resource`. - """ - - body = None - """ - If the request Content-Type is 'application/x-www-form-urlencoded' - or multipart, this will be None. Otherwise, this will be an instance - of :class:`RequestBody` (which you - can .read()); this value is set between the 'before_request_body' and - 'before_handler' hooks (assuming that process_request_body is True).""" - - # Dispatch attributes - dispatch = cherrypy.dispatch.Dispatcher() - """ - The object which looks up the 'page handler' callable and collects - config for the current request based on the path_info, other - request attributes, and the application architecture. The core - calls the dispatcher as early as possible, passing it a 'path_info' - argument. - - The default dispatcher discovers the page handler by matching path_info - to a hierarchical arrangement of objects, starting at request.app.root. - See help(cherrypy.dispatch) for more information.""" - - script_name = '' - """ - The 'mount point' of the application which is handling this request. - - This attribute MUST NOT end in a slash. If the script_name refers to - the root of the URI, it MUST be an empty string (not "/"). - """ - - path_info = '/' - """ - The 'relative path' portion of the Request-URI. This is relative - to the script_name ('mount point') of the application which is - handling this request.""" - - login = None - """ - When authentication is used during the request processing this is - set to 'False' if it failed and to the 'username' value if it succeeded. - The default 'None' implies that no authentication happened.""" - - # Note that cherrypy.url uses "if request.app:" to determine whether - # the call is during a real HTTP request or not. So leave this None. - app = None - """The cherrypy.Application object which is handling this request.""" - - handler = None - """ - The function, method, or other callable which CherryPy will call to - produce the response. The discovery of the handler and the arguments - it will receive are determined by the request.dispatch object. - By default, the handler is discovered by walking a tree of objects - starting at request.app.root, and is then passed all HTTP params - (from the query string and POST body) as keyword arguments.""" - - toolmaps = {} - """ - A nested dict of all Toolboxes and Tools in effect for this request, - of the form: {Toolbox.namespace: {Tool.name: config dict}}.""" - - config = None - """ - A flat dict of all configuration entries which apply to the - current request. These entries are collected from global config, - application config (based on request.path_info), and from handler - config (exactly how is governed by the request.dispatch object in - effect for this request; by default, handler config can be attached - anywhere in the tree between request.app.root and the final handler, - and inherits downward).""" - - is_index = None - """ - This will be True if the current request is mapped to an 'index' - resource handler (also, a 'default' handler if path_info ends with - a slash). The value may be used to automatically redirect the - user-agent to a 'more canonical' URL which either adds or removes - the trailing slash. See cherrypy.tools.trailing_slash.""" - - hooks = HookMap(hookpoints) - """ - A HookMap (dict-like object) of the form: {hookpoint: [hook, ...]}. - Each key is a str naming the hook point, and each value is a list - of hooks which will be called at that hook point during this request. - The list of hooks is generally populated as early as possible (mostly - from Tools specified in config), but may be extended at any time. - See also: _cprequest.Hook, _cprequest.HookMap, and cherrypy.tools.""" - - error_response = cherrypy.HTTPError(500).set_response - """ - The no-arg callable which will handle unexpected, untrapped errors - during request processing. This is not used for expected exceptions - (like NotFound, HTTPError, or HTTPRedirect) which are raised in - response to expected conditions (those should be customized either - via request.error_page or by overriding HTTPError.set_response). - By default, error_response uses HTTPError(500) to return a generic - error response to the user-agent.""" - - error_page = {} - """ - A dict of {error code: response filename or callable} pairs. - - The error code must be an int representing a given HTTP error code, - or the string 'default', which will be used if no matching entry - is found for a given numeric code. - - If a filename is provided, the file should contain a Python string- - formatting template, and can expect by default to receive format - values with the mapping keys %(status)s, %(message)s, %(traceback)s, - and %(version)s. The set of format mappings can be extended by - overriding HTTPError.set_response. - - If a callable is provided, it will be called by default with keyword - arguments 'status', 'message', 'traceback', and 'version', as for a - string-formatting template. The callable must return a string or - iterable of strings which will be set to response.body. It may also - override headers or perform any other processing. - - If no entry is given for an error code, and no 'default' entry exists, - a default template will be used. - """ - - show_tracebacks = True - """ - If True, unexpected errors encountered during request processing will - include a traceback in the response body.""" - - show_mismatched_params = True - """ - If True, mismatched parameters encountered during PageHandler invocation - processing will be included in the response body.""" - - throws = (KeyboardInterrupt, SystemExit, cherrypy.InternalRedirect) - """The sequence of exceptions which Request.run does not trap.""" - - throw_errors = False - """ - If True, Request.run will not trap any errors (except HTTPRedirect and - HTTPError, which are more properly called 'exceptions', not errors).""" - - closed = False - """True once the close method has been called, False otherwise.""" - - stage = None - """ - A string containing the stage reached in the request-handling process. - This is useful when debugging a live server with hung requests.""" - - unique_id = None - """A lazy object generating and memorizing UUID4 on ``str()`` render.""" - - namespaces = reprconf.NamespaceSet( - **{'hooks': hooks_namespace, - 'request': request_namespace, - 'response': response_namespace, - 'error_page': error_page_namespace, - 'tools': cherrypy.tools, - }) - - def __init__(self, local_host, remote_host, scheme='http', - server_protocol='HTTP/1.1'): - """Populate a new Request object. - - local_host should be an httputil.Host object with the server info. - remote_host should be an httputil.Host object with the client info. - scheme should be a string, either "http" or "https". - """ - self.local = local_host - self.remote = remote_host - self.scheme = scheme - self.server_protocol = server_protocol - - self.closed = False - - # Put a *copy* of the class error_page into self. - self.error_page = self.error_page.copy() - - # Put a *copy* of the class namespaces into self. - self.namespaces = self.namespaces.copy() - - self.stage = None - - self.unique_id = LazyUUID4() - - def close(self): - """Run cleanup code. (Core)""" - if not self.closed: - self.closed = True - self.stage = 'on_end_request' - self.hooks.run('on_end_request') - self.stage = 'close' - - def run(self, method, path, query_string, req_protocol, headers, rfile): - r"""Process the Request. (Core) - - method, path, query_string, and req_protocol should be pulled directly - from the Request-Line (e.g. "GET /path?key=val HTTP/1.0"). - - path - This should be %XX-unquoted, but query_string should not be. - - When using Python 2, they both MUST be byte strings, - not unicode strings. - - When using Python 3, they both MUST be unicode strings, - not byte strings, and preferably not bytes \x00-\xFF - disguised as unicode. - - headers - A list of (name, value) tuples. - - rfile - A file-like object containing the HTTP request entity. - - When run() is done, the returned object should have 3 attributes: - - * status, e.g. "200 OK" - * header_list, a list of (name, value) tuples - * body, an iterable yielding strings - - Consumer code (HTTP servers) should then access these response - attributes to build the outbound stream. - - """ - response = cherrypy.serving.response - self.stage = 'run' - try: - self.error_response = cherrypy.HTTPError(500).set_response - - self.method = method - path = path or '/' - self.query_string = query_string or '' - self.params = {} - - # Compare request and server HTTP protocol versions, in case our - # server does not support the requested protocol. Limit our output - # to min(req, server). We want the following output: - # request server actual written supported response - # protocol protocol response protocol feature set - # a 1.0 1.0 1.0 1.0 - # b 1.0 1.1 1.1 1.0 - # c 1.1 1.0 1.0 1.0 - # d 1.1 1.1 1.1 1.1 - # Notice that, in (b), the response will be "HTTP/1.1" even though - # the client only understands 1.0. RFC 2616 10.5.6 says we should - # only return 505 if the _major_ version is different. - rp = int(req_protocol[5]), int(req_protocol[7]) - sp = int(self.server_protocol[5]), int(self.server_protocol[7]) - self.protocol = min(rp, sp) - response.headers.protocol = self.protocol - - # Rebuild first line of the request (e.g. "GET /path HTTP/1.0"). - url = path - if query_string: - url += '?' + query_string - self.request_line = '%s %s %s' % (method, url, req_protocol) - - self.header_list = list(headers) - self.headers = httputil.HeaderMap() - - self.rfile = rfile - self.body = None - - self.cookie = SimpleCookie() - self.handler = None - - # path_info should be the path from the - # app root (script_name) to the handler. - self.script_name = self.app.script_name - self.path_info = pi = path[len(self.script_name):] - - self.stage = 'respond' - self.respond(pi) - - except self.throws: - raise - except Exception: - if self.throw_errors: - raise - else: - # Failure in setup, error handler or finalize. Bypass them. - # Can't use handle_error because we may not have hooks yet. - cherrypy.log(traceback=True, severity=40) - if self.show_tracebacks: - body = format_exc() - else: - body = '' - r = bare_error(body) - response.output_status, response.header_list, response.body = r - - if self.method == 'HEAD': - # HEAD requests MUST NOT return a message-body in the response. - response.body = [] - - try: - cherrypy.log.access() - except Exception: - cherrypy.log.error(traceback=True) - - return response - - def respond(self, path_info): - """Generate a response for the resource at self.path_info. (Core)""" - try: - try: - try: - self._do_respond(path_info) - except (cherrypy.HTTPRedirect, cherrypy.HTTPError): - inst = sys.exc_info()[1] - inst.set_response() - self.stage = 'before_finalize (HTTPError)' - self.hooks.run('before_finalize') - cherrypy.serving.response.finalize() - finally: - self.stage = 'on_end_resource' - self.hooks.run('on_end_resource') - except self.throws: - raise - except Exception: - if self.throw_errors: - raise - self.handle_error() - - def _do_respond(self, path_info): - response = cherrypy.serving.response - - if self.app is None: - raise cherrypy.NotFound() - - self.hooks = self.__class__.hooks.copy() - self.toolmaps = {} - - # Get the 'Host' header, so we can HTTPRedirect properly. - self.stage = 'process_headers' - self.process_headers() - - self.stage = 'get_resource' - self.get_resource(path_info) - - self.body = _cpreqbody.RequestBody( - self.rfile, self.headers, request_params=self.params) - - self.namespaces(self.config) - - self.stage = 'on_start_resource' - self.hooks.run('on_start_resource') - - # Parse the querystring - self.stage = 'process_query_string' - self.process_query_string() - - # Process the body - if self.process_request_body: - if self.method not in self.methods_with_bodies: - self.process_request_body = False - self.stage = 'before_request_body' - self.hooks.run('before_request_body') - if self.process_request_body: - self.body.process() - - # Run the handler - self.stage = 'before_handler' - self.hooks.run('before_handler') - if self.handler: - self.stage = 'handler' - response.body = self.handler() - - # Finalize - self.stage = 'before_finalize' - self.hooks.run('before_finalize') - response.finalize() - - def process_query_string(self): - """Parse the query string into Python structures. (Core)""" - try: - p = httputil.parse_query_string( - self.query_string, encoding=self.query_string_encoding) - except UnicodeDecodeError: - raise cherrypy.HTTPError( - 404, 'The given query string could not be processed. Query ' - 'strings for this resource must be encoded with %r.' % - self.query_string_encoding) - - self.params.update(p) - - def process_headers(self): - """Parse HTTP header data into Python structures. (Core)""" - # Process the headers into self.headers - headers = self.headers - for name, value in self.header_list: - # Call title() now (and use dict.__method__(headers)) - # so title doesn't have to be called twice. - name = name.title() - value = value.strip() - - headers[name] = httputil.decode_TEXT_maybe(value) - - # Some clients, notably Konquoror, supply multiple - # cookies on different lines with the same key. To - # handle this case, store all cookies in self.cookie. - if name == 'Cookie': - try: - self.cookie.load(value) - except CookieError as exc: - raise cherrypy.HTTPError(400, str(exc)) - - if not dict.__contains__(headers, 'Host'): - # All Internet-based HTTP/1.1 servers MUST respond with a 400 - # (Bad Request) status code to any HTTP/1.1 request message - # which lacks a Host header field. - if self.protocol >= (1, 1): - msg = "HTTP/1.1 requires a 'Host' request header." - raise cherrypy.HTTPError(400, msg) - else: - headers['Host'] = httputil.SanitizedHost(dict.get(headers, 'Host')) - - host = dict.get(headers, 'Host') - if not host: - host = self.local.name or self.local.ip - self.base = '%s://%s' % (self.scheme, host) - - def get_resource(self, path): - """Call a dispatcher (which sets self.handler and .config). (Core)""" - # First, see if there is a custom dispatch at this URI. Custom - # dispatchers can only be specified in app.config, not in _cp_config - # (since custom dispatchers may not even have an app.root). - dispatch = self.app.find_config( - path, 'request.dispatch', self.dispatch) - - # dispatch() should set self.handler and self.config - dispatch(path) - - def handle_error(self): - """Handle the last unanticipated exception. (Core)""" - try: - self.hooks.run('before_error_response') - if self.error_response: - self.error_response() - self.hooks.run('after_error_response') - cherrypy.serving.response.finalize() - except cherrypy.HTTPRedirect: - inst = sys.exc_info()[1] - inst.set_response() - cherrypy.serving.response.finalize() - - -class ResponseBody(object): - - """The body of the HTTP response (the response entity).""" - - unicode_err = ('Page handlers MUST return bytes. Use tools.encode ' - 'if you wish to return unicode.') - - def __get__(self, obj, objclass=None): - if obj is None: - # When calling on the class instead of an instance... - return self - else: - return obj._body - - def __set__(self, obj, value): - # Convert the given value to an iterable object. - if isinstance(value, str): - raise ValueError(self.unicode_err) - elif isinstance(value, list): - # every item in a list must be bytes... - if any(isinstance(item, str) for item in value): - raise ValueError(self.unicode_err) - - obj._body = encoding.prepare_iter(value) - - -class Response(object): - - """An HTTP Response, including status, headers, and body.""" - - status = '' - """The HTTP Status-Code and Reason-Phrase.""" - - header_list = [] - """ - A list of the HTTP response headers as (name, value) tuples. - In general, you should use response.headers (a dict) instead. This - attribute is generated from response.headers and is not valid until - after the finalize phase.""" - - headers = httputil.HeaderMap() - """ - A dict-like object containing the response headers. Keys are header - names (in Title-Case format); however, you may get and set them in - a case-insensitive manner. That is, headers['Content-Type'] and - headers['content-type'] refer to the same value. Values are header - values (decoded according to :rfc:`2047` if necessary). - - .. seealso:: classes :class:`HeaderMap`, :class:`HeaderElement` - """ - - cookie = SimpleCookie() - """See help(Cookie).""" - - body = ResponseBody() - """The body (entity) of the HTTP response.""" - - time = None - """The value of time.time() when created. Use in HTTP dates.""" - - stream = False - """If False, buffer the response body.""" - - def __init__(self): - self.status = None - self.header_list = None - self._body = [] - self.time = time.time() - - self.headers = httputil.HeaderMap() - # Since we know all our keys are titled strings, we can - # bypass HeaderMap.update and get a big speed boost. - dict.update(self.headers, { - 'Content-Type': 'text/html', - 'Server': 'CherryPy/' + cherrypy.__version__, - 'Date': httputil.HTTPDate(self.time), - }) - self.cookie = SimpleCookie() - - def collapse_body(self): - """Collapse self.body to a single string; replace it and return it.""" - new_body = b''.join(self.body) - self.body = new_body - return new_body - - def _flush_body(self): - """ - Discard self.body but consume any generator such that - any finalization can occur, such as is required by - caching.tee_output(). - """ - consume(iter(self.body)) - - def finalize(self): - """Transform headers (and cookies) into self.header_list. (Core)""" - try: - code, reason, _ = httputil.valid_status(self.status) - except ValueError: - raise cherrypy.HTTPError(500, sys.exc_info()[1].args[0]) - - headers = self.headers - - self.status = '%s %s' % (code, reason) - self.output_status = ntob(str(code), 'ascii') + \ - b' ' + headers.encode(reason) - - if self.stream: - # The upshot: wsgiserver will chunk the response if - # you pop Content-Length (or set it explicitly to None). - # Note that lib.static sets C-L to the file's st_size. - if dict.get(headers, 'Content-Length') is None: - dict.pop(headers, 'Content-Length', None) - elif code < 200 or code in (204, 205, 304): - # "All 1xx (informational), 204 (no content), - # and 304 (not modified) responses MUST NOT - # include a message-body." - dict.pop(headers, 'Content-Length', None) - self._flush_body() - self.body = b'' - else: - # Responses which are not streamed should have a Content-Length, - # but allow user code to set Content-Length if desired. - if dict.get(headers, 'Content-Length') is None: - content = self.collapse_body() - dict.__setitem__(headers, 'Content-Length', len(content)) - - # Transform our header dict into a list of tuples. - self.header_list = h = headers.output() - - cookie = self.cookie.output() - if cookie: - for line in cookie.split('\r\n'): - name, value = line.split(': ', 1) - if isinstance(name, str): - name = name.encode('ISO-8859-1') - if isinstance(value, str): - value = headers.encode(value) - h.append((name, value)) - - -class LazyUUID4(object): - def __str__(self): - """Return UUID4 and keep it for future calls.""" - return str(self.uuid4) - - @property - def uuid4(self): - """Provide unique id on per-request basis using UUID4. - - It's evaluated lazily on render. - """ - try: - self._uuid4 - except AttributeError: - # evaluate on first access - self._uuid4 = uuid.uuid4() - - return self._uuid4 diff --git a/lib/cherrypy/_cpserver.py b/lib/cherrypy/_cpserver.py deleted file mode 100644 index 5f8d98fa..00000000 --- a/lib/cherrypy/_cpserver.py +++ /dev/null @@ -1,241 +0,0 @@ -"""Manage HTTP servers with CherryPy.""" - -import cherrypy -from cherrypy.lib.reprconf import attributes -from cherrypy._cpcompat import text_or_bytes -from cherrypy.process.servers import ServerAdapter - - -__all__ = ('Server', ) - - -class Server(ServerAdapter): - """An adapter for an HTTP server. - - You can set attributes (like socket_host and socket_port) - on *this* object (which is probably cherrypy.server), and call - quickstart. For example:: - - cherrypy.server.socket_port = 80 - cherrypy.quickstart() - """ - - socket_port = 8080 - """The TCP port on which to listen for connections.""" - - _socket_host = '127.0.0.1' - - @property - def socket_host(self): # noqa: D401; irrelevant for properties - """The hostname or IP address on which to listen for connections. - - Host values may be any IPv4 or IPv6 address, or any valid hostname. - The string 'localhost' is a synonym for '127.0.0.1' (or '::1', if - your hosts file prefers IPv6). The string '0.0.0.0' is a special - IPv4 entry meaning "any active interface" (INADDR_ANY), and '::' - is the similar IN6ADDR_ANY for IPv6. The empty string or None are - not allowed. - """ - return self._socket_host - - @socket_host.setter - def socket_host(self, value): - if value == '': - raise ValueError("The empty string ('') is not an allowed value. " - "Use '0.0.0.0' instead to listen on all active " - 'interfaces (INADDR_ANY).') - self._socket_host = value - - socket_file = None - """If given, the name of the UNIX socket to use instead of TCP/IP. - - When this option is not None, the `socket_host` and `socket_port` options - are ignored.""" - - socket_queue_size = 5 - """The 'backlog' argument to socket.listen(); specifies the maximum number - of queued connections (default 5).""" - - socket_timeout = 10 - """The timeout in seconds for accepted connections (default 10).""" - - accepted_queue_size = -1 - """The maximum number of requests which will be queued up before - the server refuses to accept it (default -1, meaning no limit).""" - - accepted_queue_timeout = 10 - """The timeout in seconds for attempting to add a request to the - queue when the queue is full (default 10).""" - - shutdown_timeout = 5 - """The time to wait for HTTP worker threads to clean up.""" - - protocol_version = 'HTTP/1.1' - """The version string to write in the Status-Line of all HTTP responses, - for example, "HTTP/1.1" (the default). Depending on the HTTP server used, - this should also limit the supported features used in the response.""" - - thread_pool = 10 - """The number of worker threads to start up in the pool.""" - - thread_pool_max = -1 - """The maximum size of the worker-thread pool. Use -1 to indicate no limit. - """ - - max_request_header_size = 500 * 1024 - """The maximum number of bytes allowable in the request headers. - If exceeded, the HTTP server should return "413 Request Entity Too Large". - """ - - max_request_body_size = 100 * 1024 * 1024 - """The maximum number of bytes allowable in the request body. If exceeded, - the HTTP server should return "413 Request Entity Too Large".""" - - instance = None - """If not None, this should be an HTTP server instance (such as - cheroot.wsgi.Server) which cherrypy.server will control. - Use this when you need - more control over object instantiation than is available in the various - configuration options.""" - - ssl_context = None - """When using PyOpenSSL, an instance of SSL.Context.""" - - ssl_certificate = None - """The filename of the SSL certificate to use.""" - - ssl_certificate_chain = None - """When using PyOpenSSL, the certificate chain to pass to - Context.load_verify_locations.""" - - ssl_private_key = None - """The filename of the private key to use with SSL.""" - - ssl_ciphers = None - """The ciphers list of SSL.""" - - ssl_module = 'builtin' - """The name of a registered SSL adaptation module to use with - the builtin WSGI server. Builtin options are: 'builtin' (to - use the SSL library built into recent versions of Python). - You may also register your own classes in the - cheroot.server.ssl_adapters dict.""" - - statistics = False - """Turns statistics-gathering on or off for aware HTTP servers.""" - - nodelay = True - """If True (the default since 3.1), sets the TCP_NODELAY socket option.""" - - wsgi_version = (1, 0) - """The WSGI version tuple to use with the builtin WSGI server. - The provided options are (1, 0) [which includes support for PEP 3333, - which declares it covers WSGI version 1.0.1 but still mandates the - wsgi.version (1, 0)] and ('u', 0), an experimental unicode version. - You may create and register your own experimental versions of the WSGI - protocol by adding custom classes to the cheroot.server.wsgi_gateways dict. - """ - - peercreds = False - """If True, peer cred lookup for UNIX domain socket will put to WSGI env. - - This information will then be available through WSGI env vars: - * X_REMOTE_PID - * X_REMOTE_UID - * X_REMOTE_GID - """ - - peercreds_resolve = False - """If True, username/group will be looked up in the OS from peercreds. - - This information will then be available through WSGI env vars: - * REMOTE_USER - * X_REMOTE_USER - * X_REMOTE_GROUP - """ - - def __init__(self): - """Initialize Server instance.""" - self.bus = cherrypy.engine - self.httpserver = None - self.interrupt = None - self.running = False - - def httpserver_from_self(self, httpserver=None): - """Return a (httpserver, bind_addr) pair based on self attributes.""" - if httpserver is None: - httpserver = self.instance - if httpserver is None: - from cherrypy import _cpwsgi_server - httpserver = _cpwsgi_server.CPWSGIServer(self) - if isinstance(httpserver, text_or_bytes): - # Is anyone using this? Can I add an arg? - httpserver = attributes(httpserver)(self) - return httpserver, self.bind_addr - - def start(self): - """Start the HTTP server.""" - if not self.httpserver: - self.httpserver, self.bind_addr = self.httpserver_from_self() - super(Server, self).start() - start.priority = 75 - - @property - def bind_addr(self): - """Return bind address. - - A (host, port) tuple for TCP sockets or a str for Unix domain sockts. - """ - if self.socket_file: - return self.socket_file - if self.socket_host is None and self.socket_port is None: - return None - return (self.socket_host, self.socket_port) - - @bind_addr.setter - def bind_addr(self, value): - if value is None: - self.socket_file = None - self.socket_host = None - self.socket_port = None - elif isinstance(value, text_or_bytes): - self.socket_file = value - self.socket_host = None - self.socket_port = None - else: - try: - self.socket_host, self.socket_port = value - self.socket_file = None - except ValueError: - raise ValueError('bind_addr must be a (host, port) tuple ' - '(for TCP sockets) or a string (for Unix ' - 'domain sockets), not %r' % value) - - def base(self): - """Return the base for this server. - - e.i. scheme://host[:port] or sock file - """ - if self.socket_file: - return self.socket_file - - host = self.socket_host - if host in ('0.0.0.0', '::'): - # 0.0.0.0 is INADDR_ANY and :: is IN6ADDR_ANY. - # Look up the host name, which should be the - # safest thing to spit out in a URL. - import socket - host = socket.gethostname() - - port = self.socket_port - - if self.ssl_certificate: - scheme = 'https' - if port != 443: - host += ':%s' % port - else: - scheme = 'http' - if port != 80: - host += ':%s' % port - - return '%s://%s' % (scheme, host) diff --git a/lib/cherrypy/_cptools.py b/lib/cherrypy/_cptools.py deleted file mode 100644 index 716f99a4..00000000 --- a/lib/cherrypy/_cptools.py +++ /dev/null @@ -1,502 +0,0 @@ -"""CherryPy tools. A "tool" is any helper, adapted to CP. - -Tools are usually designed to be used in a variety of ways (although some -may only offer one if they choose): - - Library calls - All tools are callables that can be used wherever needed. - The arguments are straightforward and should be detailed within the - docstring. - - Function decorators - All tools, when called, may be used as decorators which configure - individual CherryPy page handlers (methods on the CherryPy tree). - That is, "@tools.anytool()" should "turn on" the tool via the - decorated function's _cp_config attribute. - - CherryPy config - If a tool exposes a "_setup" callable, it will be called - once per Request (if the feature is "turned on" via config). - -Tools may be implemented as any object with a namespace. The builtins -are generally either modules or instances of the tools.Tool class. -""" - -import cherrypy -from cherrypy._helper import expose - -from cherrypy.lib import cptools, encoding, static, jsontools -from cherrypy.lib import sessions as _sessions, xmlrpcutil as _xmlrpc -from cherrypy.lib import caching as _caching -from cherrypy.lib import auth_basic, auth_digest - - -def _getargs(func): - """Return the names of all static arguments to the given function.""" - # Use this instead of importing inspect for less mem overhead. - import types - if isinstance(func, types.MethodType): - func = func.__func__ - co = func.__code__ - return co.co_varnames[:co.co_argcount] - - -_attr_error = ( - 'CherryPy Tools cannot be turned on directly. Instead, turn them ' - 'on via config, or use them as decorators on your page handlers.' -) - - -class Tool(object): - - """A registered function for use with CherryPy request-processing hooks. - - help(tool.callable) should give you more information about this Tool. - """ - - namespace = 'tools' - - def __init__(self, point, callable, name=None, priority=50): - self._point = point - self.callable = callable - self._name = name - self._priority = priority - self.__doc__ = self.callable.__doc__ - self._setargs() - - @property - def on(self): - raise AttributeError(_attr_error) - - @on.setter - def on(self, value): - raise AttributeError(_attr_error) - - def _setargs(self): - """Copy func parameter names to obj attributes.""" - try: - for arg in _getargs(self.callable): - setattr(self, arg, None) - except (TypeError, AttributeError): - if hasattr(self.callable, '__call__'): - for arg in _getargs(self.callable.__call__): - setattr(self, arg, None) - # IronPython 1.0 raises NotImplementedError because - # inspect.getargspec tries to access Python bytecode - # in co_code attribute. - except NotImplementedError: - pass - # IronPython 1B1 may raise IndexError in some cases, - # but if we trap it here it doesn't prevent CP from working. - except IndexError: - pass - - def _merged_args(self, d=None): - """Return a dict of configuration entries for this Tool.""" - if d: - conf = d.copy() - else: - conf = {} - - tm = cherrypy.serving.request.toolmaps[self.namespace] - if self._name in tm: - conf.update(tm[self._name]) - - if 'on' in conf: - del conf['on'] - - return conf - - def __call__(self, *args, **kwargs): - """Compile-time decorator (turn on the tool in config). - - For example:: - - @expose - @tools.proxy() - def whats_my_base(self): - return cherrypy.request.base - """ - if args: - raise TypeError('The %r Tool does not accept positional ' - 'arguments; you must use keyword arguments.' - % self._name) - - def tool_decorator(f): - if not hasattr(f, '_cp_config'): - f._cp_config = {} - subspace = self.namespace + '.' + self._name + '.' - f._cp_config[subspace + 'on'] = True - for k, v in kwargs.items(): - f._cp_config[subspace + k] = v - return f - return tool_decorator - - def _setup(self): - """Hook this tool into cherrypy.request. - - The standard CherryPy request object will automatically call this - method when the tool is "turned on" in config. - """ - conf = self._merged_args() - p = conf.pop('priority', None) - if p is None: - p = getattr(self.callable, 'priority', self._priority) - cherrypy.serving.request.hooks.attach(self._point, self.callable, - priority=p, **conf) - - -class HandlerTool(Tool): - - """Tool which is called 'before main', that may skip normal handlers. - - If the tool successfully handles the request (by setting response.body), - if should return True. This will cause CherryPy to skip any 'normal' page - handler. If the tool did not handle the request, it should return False - to tell CherryPy to continue on and call the normal page handler. If the - tool is declared AS a page handler (see the 'handler' method), returning - False will raise NotFound. - """ - - def __init__(self, callable, name=None): - Tool.__init__(self, 'before_handler', callable, name) - - def handler(self, *args, **kwargs): - """Use this tool as a CherryPy page handler. - - For example:: - - class Root: - nav = tools.staticdir.handler(section="/nav", dir="nav", - root=absDir) - """ - @expose - def handle_func(*a, **kw): - handled = self.callable(*args, **self._merged_args(kwargs)) - if not handled: - raise cherrypy.NotFound() - return cherrypy.serving.response.body - return handle_func - - def _wrapper(self, **kwargs): - if self.callable(**kwargs): - cherrypy.serving.request.handler = None - - def _setup(self): - """Hook this tool into cherrypy.request. - - The standard CherryPy request object will automatically call this - method when the tool is "turned on" in config. - """ - conf = self._merged_args() - p = conf.pop('priority', None) - if p is None: - p = getattr(self.callable, 'priority', self._priority) - cherrypy.serving.request.hooks.attach(self._point, self._wrapper, - priority=p, **conf) - - -class HandlerWrapperTool(Tool): - - """Tool which wraps request.handler in a provided wrapper function. - - The 'newhandler' arg must be a handler wrapper function that takes a - 'next_handler' argument, plus ``*args`` and ``**kwargs``. Like all - page handler - functions, it must return an iterable for use as cherrypy.response.body. - - For example, to allow your 'inner' page handlers to return dicts - which then get interpolated into a template:: - - def interpolator(next_handler, *args, **kwargs): - filename = cherrypy.request.config.get('template') - cherrypy.response.template = env.get_template(filename) - response_dict = next_handler(*args, **kwargs) - return cherrypy.response.template.render(**response_dict) - cherrypy.tools.jinja = HandlerWrapperTool(interpolator) - """ - - def __init__(self, newhandler, point='before_handler', name=None, - priority=50): - self.newhandler = newhandler - self._point = point - self._name = name - self._priority = priority - - def callable(self, *args, **kwargs): - innerfunc = cherrypy.serving.request.handler - - def wrap(*args, **kwargs): - return self.newhandler(innerfunc, *args, **kwargs) - cherrypy.serving.request.handler = wrap - - -class ErrorTool(Tool): - - """Tool which is used to replace the default request.error_response.""" - - def __init__(self, callable, name=None): - Tool.__init__(self, None, callable, name) - - def _wrapper(self): - self.callable(**self._merged_args()) - - def _setup(self): - """Hook this tool into cherrypy.request. - - The standard CherryPy request object will automatically call this - method when the tool is "turned on" in config. - """ - cherrypy.serving.request.error_response = self._wrapper - - -# Builtin tools # - - -class SessionTool(Tool): - - """Session Tool for CherryPy. - - sessions.locking - When 'implicit' (the default), the session will be locked for you, - just before running the page handler. - - When 'early', the session will be locked before reading the request - body. This is off by default for safety reasons; for example, - a large upload would block the session, denying an AJAX - progress meter - (`issue `_). - - When 'explicit' (or any other value), you need to call - cherrypy.session.acquire_lock() yourself before using - session data. - """ - - def __init__(self): - # _sessions.init must be bound after headers are read - Tool.__init__(self, 'before_request_body', _sessions.init) - - def _lock_session(self): - cherrypy.serving.session.acquire_lock() - - def _setup(self): - """Hook this tool into cherrypy.request. - - The standard CherryPy request object will automatically call this - method when the tool is "turned on" in config. - """ - hooks = cherrypy.serving.request.hooks - - conf = self._merged_args() - - p = conf.pop('priority', None) - if p is None: - p = getattr(self.callable, 'priority', self._priority) - - hooks.attach(self._point, self.callable, priority=p, **conf) - - locking = conf.pop('locking', 'implicit') - if locking == 'implicit': - hooks.attach('before_handler', self._lock_session) - elif locking == 'early': - # Lock before the request body (but after _sessions.init runs!) - hooks.attach('before_request_body', self._lock_session, - priority=60) - else: - # Don't lock - pass - - hooks.attach('before_finalize', _sessions.save) - hooks.attach('on_end_request', _sessions.close) - - def regenerate(self): - """Drop the current session and make a new one (with a new id).""" - sess = cherrypy.serving.session - sess.regenerate() - - # Grab cookie-relevant tool args - relevant = 'path', 'path_header', 'name', 'timeout', 'domain', 'secure' - conf = dict( - (k, v) - for k, v in self._merged_args().items() - if k in relevant - ) - _sessions.set_response_cookie(**conf) - - -class XMLRPCController(object): - - """A Controller (page handler collection) for XML-RPC. - - To use it, have your controllers subclass this base class (it will - turn on the tool for you). - - You can also supply the following optional config entries:: - - tools.xmlrpc.encoding: 'utf-8' - tools.xmlrpc.allow_none: 0 - - XML-RPC is a rather discontinuous layer over HTTP; dispatching to the - appropriate handler must first be performed according to the URL, and - then a second dispatch step must take place according to the RPC method - specified in the request body. It also allows a superfluous "/RPC2" - prefix in the URL, supplies its own handler args in the body, and - requires a 200 OK "Fault" response instead of 404 when the desired - method is not found. - - Therefore, XML-RPC cannot be implemented for CherryPy via a Tool alone. - This Controller acts as the dispatch target for the first half (based - on the URL); it then reads the RPC method from the request body and - does its own second dispatch step based on that method. It also reads - body params, and returns a Fault on error. - - The XMLRPCDispatcher strips any /RPC2 prefix; if you aren't using /RPC2 - in your URL's, you can safely skip turning on the XMLRPCDispatcher. - Otherwise, you need to use declare it in config:: - - request.dispatch: cherrypy.dispatch.XMLRPCDispatcher() - """ - - # Note we're hard-coding this into the 'tools' namespace. We could do - # a huge amount of work to make it relocatable, but the only reason why - # would be if someone actually disabled the default_toolbox. Meh. - _cp_config = {'tools.xmlrpc.on': True} - - @expose - def default(self, *vpath, **params): - rpcparams, rpcmethod = _xmlrpc.process_body() - - subhandler = self - for attr in str(rpcmethod).split('.'): - subhandler = getattr(subhandler, attr, None) - - if subhandler and getattr(subhandler, 'exposed', False): - body = subhandler(*(vpath + rpcparams), **params) - - else: - # https://github.com/cherrypy/cherrypy/issues/533 - # if a method is not found, an xmlrpclib.Fault should be returned - # raising an exception here will do that; see - # cherrypy.lib.xmlrpcutil.on_error - raise Exception('method "%s" is not supported' % attr) - - conf = cherrypy.serving.request.toolmaps['tools'].get('xmlrpc', {}) - _xmlrpc.respond(body, - conf.get('encoding', 'utf-8'), - conf.get('allow_none', 0)) - return cherrypy.serving.response.body - - -class SessionAuthTool(HandlerTool): - pass - - -class CachingTool(Tool): - - """Caching Tool for CherryPy.""" - - def _wrapper(self, **kwargs): - request = cherrypy.serving.request - if _caching.get(**kwargs): - request.handler = None - else: - if request.cacheable: - # Note the devious technique here of adding hooks on the fly - request.hooks.attach('before_finalize', _caching.tee_output, - priority=100) - _wrapper.priority = 90 - - def _setup(self): - """Hook caching into cherrypy.request.""" - conf = self._merged_args() - - p = conf.pop('priority', None) - cherrypy.serving.request.hooks.attach('before_handler', self._wrapper, - priority=p, **conf) - - -class Toolbox(object): - - """A collection of Tools. - - This object also functions as a config namespace handler for itself. - Custom toolboxes should be added to each Application's toolboxes dict. - """ - - def __init__(self, namespace): - self.namespace = namespace - - def __setattr__(self, name, value): - # If the Tool._name is None, supply it from the attribute name. - if isinstance(value, Tool): - if value._name is None: - value._name = name - value.namespace = self.namespace - object.__setattr__(self, name, value) - - def __enter__(self): - """Populate request.toolmaps from tools specified in config.""" - cherrypy.serving.request.toolmaps[self.namespace] = map = {} - - def populate(k, v): - toolname, arg = k.split('.', 1) - bucket = map.setdefault(toolname, {}) - bucket[arg] = v - return populate - - def __exit__(self, exc_type, exc_val, exc_tb): - """Run tool._setup() for each tool in our toolmap.""" - map = cherrypy.serving.request.toolmaps.get(self.namespace) - if map: - for name, settings in map.items(): - if settings.get('on', False): - tool = getattr(self, name) - tool._setup() - - def register(self, point, **kwargs): - """ - Return a decorator which registers the function - at the given hook point. - """ - def decorator(func): - attr_name = kwargs.get('name', func.__name__) - tool = Tool(point, func, **kwargs) - setattr(self, attr_name, tool) - return func - return decorator - - -default_toolbox = _d = Toolbox('tools') -_d.session_auth = SessionAuthTool(cptools.session_auth) -_d.allow = Tool('on_start_resource', cptools.allow) -_d.proxy = Tool('before_request_body', cptools.proxy, priority=30) -_d.response_headers = Tool('on_start_resource', cptools.response_headers) -_d.log_tracebacks = Tool('before_error_response', cptools.log_traceback) -_d.log_headers = Tool('before_error_response', cptools.log_request_headers) -_d.log_hooks = Tool('on_end_request', cptools.log_hooks, priority=100) -_d.err_redirect = ErrorTool(cptools.redirect) -_d.etags = Tool('before_finalize', cptools.validate_etags, priority=75) -_d.decode = Tool('before_request_body', encoding.decode) -# the order of encoding, gzip, caching is important -_d.encode = Tool('before_handler', encoding.ResponseEncoder, priority=70) -_d.gzip = Tool('before_finalize', encoding.gzip, priority=80) -_d.staticdir = HandlerTool(static.staticdir) -_d.staticfile = HandlerTool(static.staticfile) -_d.sessions = SessionTool() -_d.xmlrpc = ErrorTool(_xmlrpc.on_error) -_d.caching = CachingTool('before_handler', _caching.get, 'caching') -_d.expires = Tool('before_finalize', _caching.expires) -_d.ignore_headers = Tool('before_request_body', cptools.ignore_headers) -_d.referer = Tool('before_request_body', cptools.referer) -_d.trailing_slash = Tool('before_handler', cptools.trailing_slash, priority=60) -_d.flatten = Tool('before_finalize', cptools.flatten) -_d.accept = Tool('on_start_resource', cptools.accept) -_d.redirect = Tool('on_start_resource', cptools.redirect) -_d.autovary = Tool('on_start_resource', cptools.autovary, priority=0) -_d.json_in = Tool('before_request_body', jsontools.json_in, priority=30) -_d.json_out = Tool('before_handler', jsontools.json_out, priority=30) -_d.auth_basic = Tool('before_handler', auth_basic.basic_auth, priority=1) -_d.auth_digest = Tool('before_handler', auth_digest.digest_auth, priority=1) -_d.params = Tool('before_handler', cptools.convert_params, priority=15) - -del _d, cptools, encoding, static diff --git a/lib/cherrypy/_cptree.py b/lib/cherrypy/_cptree.py deleted file mode 100644 index 917c5b1a..00000000 --- a/lib/cherrypy/_cptree.py +++ /dev/null @@ -1,302 +0,0 @@ -"""CherryPy Application and Tree objects.""" - -import os - -import cherrypy -from cherrypy import _cpconfig, _cplogging, _cprequest, _cpwsgi, tools -from cherrypy.lib import httputil, reprconf - - -class Application(object): - """A CherryPy Application. - - Servers and gateways should not instantiate Request objects directly. - Instead, they should ask an Application object for a request object. - - An instance of this class may also be used as a WSGI callable - (WSGI application object) for itself. - """ - - root = None - """The top-most container of page handlers for this app. Handlers should - be arranged in a hierarchy of attributes, matching the expected URI - hierarchy; the default dispatcher then searches this hierarchy for a - matching handler. When using a dispatcher other than the default, - this value may be None.""" - - config = {} - """A dict of {path: pathconf} pairs, where 'pathconf' is itself a dict - of {key: value} pairs.""" - - namespaces = reprconf.NamespaceSet() - toolboxes = {'tools': cherrypy.tools} - - log = None - """A LogManager instance. See _cplogging.""" - - wsgiapp = None - """A CPWSGIApp instance. See _cpwsgi.""" - - request_class = _cprequest.Request - response_class = _cprequest.Response - - relative_urls = False - - def __init__(self, root, script_name='', config=None): - """Initialize Application with given root.""" - self.log = _cplogging.LogManager(id(self), cherrypy.log.logger_root) - self.root = root - self.script_name = script_name - self.wsgiapp = _cpwsgi.CPWSGIApp(self) - - self.namespaces = self.namespaces.copy() - self.namespaces['log'] = lambda k, v: setattr(self.log, k, v) - self.namespaces['wsgi'] = self.wsgiapp.namespace_handler - - self.config = self.__class__.config.copy() - if config: - self.merge(config) - - def __repr__(self): - """Generate a representation of the Application instance.""" - return '%s.%s(%r, %r)' % (self.__module__, self.__class__.__name__, - self.root, self.script_name) - - script_name_doc = """The URI "mount point" for this app. A mount point - is that portion of the URI which is constant for all URIs that are - serviced by this application; it does not include scheme, host, or proxy - ("virtual host") portions of the URI. - - For example, if script_name is "/my/cool/app", then the URL - "http://www.example.com/my/cool/app/page1" might be handled by a - "page1" method on the root object. - - The value of script_name MUST NOT end in a slash. If the script_name - refers to the root of the URI, it MUST be an empty string (not "/"). - - If script_name is explicitly set to None, then the script_name will be - provided for each call from request.wsgi_environ['SCRIPT_NAME']. - """ - - @property - def script_name(self): # noqa: D401; irrelevant for properties - """The URI "mount point" for this app. - - A mount point is that portion of the URI which is constant for all URIs - that are serviced by this application; it does not include scheme, - host, or proxy ("virtual host") portions of the URI. - - For example, if script_name is "/my/cool/app", then the URL - "http://www.example.com/my/cool/app/page1" might be handled by a - "page1" method on the root object. - - The value of script_name MUST NOT end in a slash. If the script_name - refers to the root of the URI, it MUST be an empty string (not "/"). - - If script_name is explicitly set to None, then the script_name will be - provided for each call from request.wsgi_environ['SCRIPT_NAME']. - """ - if self._script_name is not None: - return self._script_name - - # A `_script_name` with a value of None signals that the script name - # should be pulled from WSGI environ. - return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip('/') - - @script_name.setter - def script_name(self, value): - if value: - value = value.rstrip('/') - self._script_name = value - - def merge(self, config): - """Merge the given config into self.config.""" - _cpconfig.merge(self.config, config) - - # Handle namespaces specified in config. - self.namespaces(self.config.get('/', {})) - - def find_config(self, path, key, default=None): - """Return the most-specific value for key along path, or default.""" - trail = path or '/' - while trail: - nodeconf = self.config.get(trail, {}) - - if key in nodeconf: - return nodeconf[key] - - lastslash = trail.rfind('/') - if lastslash == -1: - break - elif lastslash == 0 and trail != '/': - trail = '/' - else: - trail = trail[:lastslash] - - return default - - def get_serving(self, local, remote, scheme, sproto): - """Create and return a Request and Response object.""" - req = self.request_class(local, remote, scheme, sproto) - req.app = self - - for name, toolbox in self.toolboxes.items(): - req.namespaces[name] = toolbox - - resp = self.response_class() - cherrypy.serving.load(req, resp) - cherrypy.engine.publish('acquire_thread') - cherrypy.engine.publish('before_request') - - return req, resp - - def release_serving(self): - """Release the current serving (request and response).""" - req = cherrypy.serving.request - - cherrypy.engine.publish('after_request') - - try: - req.close() - except Exception: - cherrypy.log(traceback=True, severity=40) - - cherrypy.serving.clear() - - def __call__(self, environ, start_response): - """Call a WSGI-callable.""" - return self.wsgiapp(environ, start_response) - - -class Tree(object): - """A registry of CherryPy applications, mounted at diverse points. - - An instance of this class may also be used as a WSGI callable - (WSGI application object), in which case it dispatches to all - mounted apps. - """ - - apps = {} - """ - A dict of the form {script name: application}, where "script name" - is a string declaring the URI mount point (no trailing slash), and - "application" is an instance of cherrypy.Application (or an arbitrary - WSGI callable if you happen to be using a WSGI server).""" - - def __init__(self): - """Initialize registry Tree.""" - self.apps = {} - - def mount(self, root, script_name='', config=None): - """Mount a new app from a root object, script_name, and config. - - root - An instance of a "controller class" (a collection of page - handler methods) which represents the root of the application. - This may also be an Application instance, or None if using - a dispatcher other than the default. - - script_name - A string containing the "mount point" of the application. - This should start with a slash, and be the path portion of the - URL at which to mount the given root. For example, if root.index() - will handle requests to "http://www.example.com:8080/dept/app1/", - then the script_name argument would be "/dept/app1". - - It MUST NOT end in a slash. If the script_name refers to the - root of the URI, it MUST be an empty string (not "/"). - - config - A file or dict containing application config. - """ - if script_name is None: - raise TypeError( - "The 'script_name' argument may not be None. Application " - 'objects may, however, possess a script_name of None (in ' - 'order to inpect the WSGI environ for SCRIPT_NAME upon each ' - 'request). You cannot mount such Applications on this Tree; ' - 'you must pass them to a WSGI server interface directly.') - - # Next line both 1) strips trailing slash and 2) maps "/" -> "". - script_name = script_name.rstrip('/') - - if isinstance(root, Application): - app = root - if script_name != '' and script_name != app.script_name: - raise ValueError( - 'Cannot specify a different script name and pass an ' - 'Application instance to cherrypy.mount') - script_name = app.script_name - else: - app = Application(root, script_name) - - # If mounted at "", add favicon.ico - needs_favicon = ( - script_name == '' - and root is not None - and not hasattr(root, 'favicon_ico') - ) - if needs_favicon: - favicon = os.path.join( - os.getcwd(), - os.path.dirname(__file__), - 'favicon.ico', - ) - root.favicon_ico = tools.staticfile.handler(favicon) - - if config: - app.merge(config) - - self.apps[script_name] = app - - return app - - def graft(self, wsgi_callable, script_name=''): - """Mount a wsgi callable at the given script_name.""" - # Next line both 1) strips trailing slash and 2) maps "/" -> "". - script_name = script_name.rstrip('/') - self.apps[script_name] = wsgi_callable - - def script_name(self, path=None): - """Return the script_name of the app at the given path, or None. - - If path is None, cherrypy.request is used. - """ - if path is None: - try: - request = cherrypy.serving.request - path = httputil.urljoin(request.script_name, - request.path_info) - except AttributeError: - return None - - while True: - if path in self.apps: - return path - - if path == '': - return None - - # Move one node up the tree and try again. - path = path[:path.rfind('/')] - - def __call__(self, environ, start_response): - """Pre-initialize WSGI env and call WSGI-callable.""" - # If you're calling this, then you're probably setting SCRIPT_NAME - # to '' (some WSGI servers always set SCRIPT_NAME to ''). - # Try to look up the app using the full path. - env1x = environ - path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''), - env1x.get('PATH_INFO', '')) - sn = self.script_name(path or '/') - if sn is None: - start_response('404 Not Found', []) - return [] - - app = self.apps[sn] - - # Correct the SCRIPT_NAME and PATH_INFO environ entries. - environ = environ.copy() - environ['SCRIPT_NAME'] = sn - environ['PATH_INFO'] = path[len(sn.rstrip('/')):] - return app(environ, start_response) diff --git a/lib/cherrypy/_cpwsgi.py b/lib/cherrypy/_cpwsgi.py deleted file mode 100644 index b4f55fd6..00000000 --- a/lib/cherrypy/_cpwsgi.py +++ /dev/null @@ -1,451 +0,0 @@ -"""WSGI interface (see PEP 333 and 3333). - -Note that WSGI environ keys and values are 'native strings'; that is, -whatever the type of "" is. For Python 2, that's a byte string; for Python 3, -it's a unicode string. But PEP 3333 says: "even if Python's str type is -actually Unicode "under the hood", the content of native strings must -still be translatable to bytes via the Latin-1 encoding!" -""" - -import sys as _sys -import io - -import cherrypy as _cherrypy -from cherrypy._cpcompat import ntou -from cherrypy import _cperror -from cherrypy.lib import httputil -from cherrypy.lib import is_closable_iterator - - -def downgrade_wsgi_ux_to_1x(environ): - """Return a new environ dict for WSGI 1.x from the given WSGI u.x environ. - """ - env1x = {} - - url_encoding = environ[ntou('wsgi.url_encoding')] - for k, v in environ.copy().items(): - if k in [ntou('PATH_INFO'), ntou('SCRIPT_NAME'), ntou('QUERY_STRING')]: - v = v.encode(url_encoding) - elif isinstance(v, str): - v = v.encode('ISO-8859-1') - env1x[k.encode('ISO-8859-1')] = v - - return env1x - - -class VirtualHost(object): - - """Select a different WSGI application based on the Host header. - - This can be useful when running multiple sites within one CP server. - It allows several domains to point to different applications. For example:: - - root = Root() - RootApp = cherrypy.Application(root) - Domain2App = cherrypy.Application(root) - SecureApp = cherrypy.Application(Secure()) - - vhost = cherrypy._cpwsgi.VirtualHost( - RootApp, - domains={ - 'www.domain2.example': Domain2App, - 'www.domain2.example:443': SecureApp, - }, - ) - - cherrypy.tree.graft(vhost) - """ - default = None - """Required. The default WSGI application.""" - - use_x_forwarded_host = True - """If True (the default), any "X-Forwarded-Host" - request header will be used instead of the "Host" header. This - is commonly added by HTTP servers (such as Apache) when proxying.""" - - domains = {} - """A dict of {host header value: application} pairs. - The incoming "Host" request header is looked up in this dict, - and, if a match is found, the corresponding WSGI application - will be called instead of the default. Note that you often need - separate entries for "example.com" and "www.example.com". - In addition, "Host" headers may contain the port number. - """ - - def __init__(self, default, domains=None, use_x_forwarded_host=True): - self.default = default - self.domains = domains or {} - self.use_x_forwarded_host = use_x_forwarded_host - - def __call__(self, environ, start_response): - domain = environ.get('HTTP_HOST', '') - if self.use_x_forwarded_host: - domain = environ.get('HTTP_X_FORWARDED_HOST', domain) - - nextapp = self.domains.get(domain) - if nextapp is None: - nextapp = self.default - return nextapp(environ, start_response) - - -class InternalRedirector(object): - - """WSGI middleware that handles raised cherrypy.InternalRedirect.""" - - def __init__(self, nextapp, recursive=False): - self.nextapp = nextapp - self.recursive = recursive - - def __call__(self, environ, start_response): - redirections = [] - while True: - environ = environ.copy() - try: - return self.nextapp(environ, start_response) - except _cherrypy.InternalRedirect: - ir = _sys.exc_info()[1] - sn = environ.get('SCRIPT_NAME', '') - path = environ.get('PATH_INFO', '') - qs = environ.get('QUERY_STRING', '') - - # Add the *previous* path_info + qs to redirections. - old_uri = sn + path - if qs: - old_uri += '?' + qs - redirections.append(old_uri) - - if not self.recursive: - # Check to see if the new URI has been redirected to - # already - new_uri = sn + ir.path - if ir.query_string: - new_uri += '?' + ir.query_string - if new_uri in redirections: - ir.request.close() - tmpl = ( - 'InternalRedirector visited the same URL twice: %r' - ) - raise RuntimeError(tmpl % new_uri) - - # Munge the environment and try again. - environ['REQUEST_METHOD'] = 'GET' - environ['PATH_INFO'] = ir.path - environ['QUERY_STRING'] = ir.query_string - environ['wsgi.input'] = io.BytesIO() - environ['CONTENT_LENGTH'] = '0' - environ['cherrypy.previous_request'] = ir.request - - -class ExceptionTrapper(object): - - """WSGI middleware that traps exceptions.""" - - def __init__(self, nextapp, throws=(KeyboardInterrupt, SystemExit)): - self.nextapp = nextapp - self.throws = throws - - def __call__(self, environ, start_response): - return _TrappedResponse( - self.nextapp, - environ, - start_response, - self.throws - ) - - -class _TrappedResponse(object): - - response = iter([]) - - def __init__(self, nextapp, environ, start_response, throws): - self.nextapp = nextapp - self.environ = environ - self.start_response = start_response - self.throws = throws - self.started_response = False - self.response = self.trap( - self.nextapp, self.environ, self.start_response, - ) - self.iter_response = iter(self.response) - - def __iter__(self): - self.started_response = True - return self - - def __next__(self): - return self.trap(next, self.iter_response) - - def close(self): - if hasattr(self.response, 'close'): - self.response.close() - - def trap(self, func, *args, **kwargs): - try: - return func(*args, **kwargs) - except self.throws: - raise - except StopIteration: - raise - except Exception: - tb = _cperror.format_exc() - _cherrypy.log(tb, severity=40) - if not _cherrypy.request.show_tracebacks: - tb = '' - s, h, b = _cperror.bare_error(tb) - if True: - # What fun. - s = s.decode('ISO-8859-1') - h = [ - (k.decode('ISO-8859-1'), v.decode('ISO-8859-1')) - for k, v in h - ] - if self.started_response: - # Empty our iterable (so future calls raise StopIteration) - self.iter_response = iter([]) - else: - self.iter_response = iter(b) - - try: - self.start_response(s, h, _sys.exc_info()) - except Exception: - # "The application must not trap any exceptions raised by - # start_response, if it called start_response with exc_info. - # Instead, it should allow such exceptions to propagate - # back to the server or gateway." - # But we still log and call close() to clean up ourselves. - _cherrypy.log(traceback=True, severity=40) - raise - - if self.started_response: - return b''.join(b) - else: - return b - - -# WSGI-to-CP Adapter # - - -class AppResponse(object): - - """WSGI response iterable for CherryPy applications.""" - - def __init__(self, environ, start_response, cpapp): - self.cpapp = cpapp - try: - self.environ = environ - self.run() - - r = _cherrypy.serving.response - - outstatus = r.output_status - if not isinstance(outstatus, bytes): - raise TypeError('response.output_status is not a byte string.') - - outheaders = [] - for k, v in r.header_list: - if not isinstance(k, bytes): - tmpl = 'response.header_list key %r is not a byte string.' - raise TypeError(tmpl % k) - if not isinstance(v, bytes): - tmpl = ( - 'response.header_list value %r is not a byte string.' - ) - raise TypeError(tmpl % v) - outheaders.append((k, v)) - - if True: - # According to PEP 3333, when using Python 3, the response - # status and headers must be bytes masquerading as unicode; - # that is, they must be of type "str" but are restricted to - # code points in the "latin-1" set. - outstatus = outstatus.decode('ISO-8859-1') - outheaders = [ - (k.decode('ISO-8859-1'), v.decode('ISO-8859-1')) - for k, v in outheaders - ] - - self.iter_response = iter(r.body) - self.write = start_response(outstatus, outheaders) - except BaseException: - self.close() - raise - - def __iter__(self): - return self - - def __next__(self): - return next(self.iter_response) - - def close(self): - """Close and de-reference the current request and response. (Core)""" - streaming = _cherrypy.serving.response.stream - self.cpapp.release_serving() - - # We avoid the expense of examining the iterator to see if it's - # closable unless we are streaming the response, as that's the - # only situation where we are going to have an iterator which - # may not have been exhausted yet. - if streaming and is_closable_iterator(self.iter_response): - iter_close = self.iter_response.close - try: - iter_close() - except Exception: - _cherrypy.log(traceback=True, severity=40) - - def run(self): - """Create a Request object using environ.""" - env = self.environ.get - - local = httputil.Host( - '', - int(env('SERVER_PORT', 80) or -1), - env('SERVER_NAME', ''), - ) - remote = httputil.Host( - env('REMOTE_ADDR', ''), - int(env('REMOTE_PORT', -1) or -1), - env('REMOTE_HOST', ''), - ) - scheme = env('wsgi.url_scheme') - sproto = env('ACTUAL_SERVER_PROTOCOL', 'HTTP/1.1') - request, resp = self.cpapp.get_serving(local, remote, scheme, sproto) - - # LOGON_USER is served by IIS, and is the name of the - # user after having been mapped to a local account. - # Both IIS and Apache set REMOTE_USER, when possible. - request.login = env('LOGON_USER') or env('REMOTE_USER') or None - request.multithread = self.environ['wsgi.multithread'] - request.multiprocess = self.environ['wsgi.multiprocess'] - request.wsgi_environ = self.environ - request.prev = env('cherrypy.previous_request', None) - - meth = self.environ['REQUEST_METHOD'] - - path = httputil.urljoin( - self.environ.get('SCRIPT_NAME', ''), - self.environ.get('PATH_INFO', ''), - ) - qs = self.environ.get('QUERY_STRING', '') - - path, qs = self.recode_path_qs(path, qs) or (path, qs) - - rproto = self.environ.get('SERVER_PROTOCOL') - headers = self.translate_headers(self.environ) - rfile = self.environ['wsgi.input'] - request.run(meth, path, qs, rproto, headers, rfile) - - headerNames = { - 'HTTP_CGI_AUTHORIZATION': 'Authorization', - 'CONTENT_LENGTH': 'Content-Length', - 'CONTENT_TYPE': 'Content-Type', - 'REMOTE_HOST': 'Remote-Host', - 'REMOTE_ADDR': 'Remote-Addr', - } - - def recode_path_qs(self, path, qs): - # This isn't perfect; if the given PATH_INFO is in the - # wrong encoding, it may fail to match the appropriate config - # section URI. But meh. - old_enc = self.environ.get('wsgi.url_encoding', 'ISO-8859-1') - new_enc = self.cpapp.find_config( - self.environ.get('PATH_INFO', ''), - 'request.uri_encoding', 'utf-8', - ) - if new_enc.lower() == old_enc.lower(): - return - - # Even though the path and qs are unicode, the WSGI server - # is required by PEP 3333 to coerce them to ISO-8859-1 - # masquerading as unicode. So we have to encode back to - # bytes and then decode again using the "correct" encoding. - try: - return ( - path.encode(old_enc).decode(new_enc), - qs.encode(old_enc).decode(new_enc), - ) - except (UnicodeEncodeError, UnicodeDecodeError): - # Just pass them through without transcoding and hope. - pass - - def translate_headers(self, environ): - """Translate CGI-environ header names to HTTP header names.""" - for cgiName in environ: - # We assume all incoming header keys are uppercase already. - if cgiName in self.headerNames: - yield self.headerNames[cgiName], environ[cgiName] - elif cgiName[:5] == 'HTTP_': - # Hackish attempt at recovering original header names. - translatedHeader = cgiName[5:].replace('_', '-') - yield translatedHeader, environ[cgiName] - - -class CPWSGIApp(object): - - """A WSGI application object for a CherryPy Application.""" - - pipeline = [ - ('ExceptionTrapper', ExceptionTrapper), - ('InternalRedirector', InternalRedirector), - ] - """A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a - constructor that takes an initial, positional 'nextapp' argument, - plus optional keyword arguments, and returns a WSGI application - (that takes environ and start_response arguments). The 'name' can - be any you choose, and will correspond to keys in self.config.""" - - head = None - """Rather than nest all apps in the pipeline on each call, it's only - done the first time, and the result is memoized into self.head. Set - this to None again if you change self.pipeline after calling self.""" - - config = {} - """A dict whose keys match names listed in the pipeline. Each - value is a further dict which will be passed to the corresponding - named WSGI callable (from the pipeline) as keyword arguments.""" - - response_class = AppResponse - """The class to instantiate and return as the next app in the WSGI chain. - """ - - def __init__(self, cpapp, pipeline=None): - self.cpapp = cpapp - self.pipeline = self.pipeline[:] - if pipeline: - self.pipeline.extend(pipeline) - self.config = self.config.copy() - - def tail(self, environ, start_response): - """WSGI application callable for the actual CherryPy application. - - You probably shouldn't call this; call self.__call__ instead, - so that any WSGI middleware in self.pipeline can run first. - """ - return self.response_class(environ, start_response, self.cpapp) - - def __call__(self, environ, start_response): - head = self.head - if head is None: - # Create and nest the WSGI apps in our pipeline (in reverse order). - # Then memoize the result in self.head. - head = self.tail - for name, callable in self.pipeline[::-1]: - conf = self.config.get(name, {}) - head = callable(head, **conf) - self.head = head - return head(environ, start_response) - - def namespace_handler(self, k, v): - """Config handler for the 'wsgi' namespace.""" - if k == 'pipeline': - # Note this allows multiple 'wsgi.pipeline' config entries - # (but each entry will be processed in a 'random' order). - # It should also allow developers to set default middleware - # in code (passed to self.__init__) that deployers can add to - # (but not remove) via config. - self.pipeline.extend(v) - elif k == 'response_class': - self.response_class = v - else: - name, arg = k.split('.', 1) - bucket = self.config.setdefault(name, {}) - bucket[arg] = v diff --git a/lib/cherrypy/_cpwsgi_server.py b/lib/cherrypy/_cpwsgi_server.py deleted file mode 100644 index 11dd846a..00000000 --- a/lib/cherrypy/_cpwsgi_server.py +++ /dev/null @@ -1,110 +0,0 @@ -""" -WSGI server interface (see PEP 333). - -This adds some CP-specific bits to the framework-agnostic cheroot package. -""" -import sys - -import cheroot.wsgi -import cheroot.server - -import cherrypy - - -class CPWSGIHTTPRequest(cheroot.server.HTTPRequest): - """Wrapper for cheroot.server.HTTPRequest. - - This is a layer, which preserves URI parsing mode like it which was - before Cheroot v5.8.0. - """ - - def __init__(self, server, conn): - """Initialize HTTP request container instance. - - Args: - server (cheroot.server.HTTPServer): - web server object receiving this request - conn (cheroot.server.HTTPConnection): - HTTP connection object for this request - """ - super(CPWSGIHTTPRequest, self).__init__( - server, conn, proxy_mode=True - ) - - -class CPWSGIServer(cheroot.wsgi.Server): - """Wrapper for cheroot.wsgi.Server. - - cheroot has been designed to not reference CherryPy in any way, - so that it can be used in other frameworks and applications. Therefore, - we wrap it here, so we can set our own mount points from cherrypy.tree - and apply some attributes from config -> cherrypy.server -> wsgi.Server. - """ - - fmt = 'CherryPy/{cherrypy.__version__} {cheroot.wsgi.Server.version}' - version = fmt.format(**globals()) - - def __init__(self, server_adapter=cherrypy.server): - """Initialize CPWSGIServer instance. - - Args: - server_adapter (cherrypy._cpserver.Server): ... - """ - self.server_adapter = server_adapter - self.max_request_header_size = ( - self.server_adapter.max_request_header_size or 0 - ) - self.max_request_body_size = ( - self.server_adapter.max_request_body_size or 0 - ) - - server_name = (self.server_adapter.socket_host or - self.server_adapter.socket_file or - None) - - self.wsgi_version = self.server_adapter.wsgi_version - - super(CPWSGIServer, self).__init__( - server_adapter.bind_addr, cherrypy.tree, - self.server_adapter.thread_pool, - server_name, - max=self.server_adapter.thread_pool_max, - request_queue_size=self.server_adapter.socket_queue_size, - timeout=self.server_adapter.socket_timeout, - shutdown_timeout=self.server_adapter.shutdown_timeout, - accepted_queue_size=self.server_adapter.accepted_queue_size, - accepted_queue_timeout=self.server_adapter.accepted_queue_timeout, - peercreds_enabled=self.server_adapter.peercreds, - peercreds_resolve_enabled=self.server_adapter.peercreds_resolve, - ) - self.ConnectionClass.RequestHandlerClass = CPWSGIHTTPRequest - - self.protocol = self.server_adapter.protocol_version - self.nodelay = self.server_adapter.nodelay - - if sys.version_info >= (3, 0): - ssl_module = self.server_adapter.ssl_module or 'builtin' - else: - ssl_module = self.server_adapter.ssl_module or 'pyopenssl' - if self.server_adapter.ssl_context: - adapter_class = cheroot.server.get_ssl_adapter_class(ssl_module) - self.ssl_adapter = adapter_class( - self.server_adapter.ssl_certificate, - self.server_adapter.ssl_private_key, - self.server_adapter.ssl_certificate_chain, - self.server_adapter.ssl_ciphers) - self.ssl_adapter.context = self.server_adapter.ssl_context - elif self.server_adapter.ssl_certificate: - adapter_class = cheroot.server.get_ssl_adapter_class(ssl_module) - self.ssl_adapter = adapter_class( - self.server_adapter.ssl_certificate, - self.server_adapter.ssl_private_key, - self.server_adapter.ssl_certificate_chain, - self.server_adapter.ssl_ciphers) - - self.stats['Enabled'] = getattr( - self.server_adapter, 'statistics', False) - - def error_log(self, msg='', level=20, traceback=False): - """Write given message to the error log.""" - cherrypy.engine.log(msg, level, traceback) diff --git a/lib/cherrypy/_helper.py b/lib/cherrypy/_helper.py deleted file mode 100644 index d57cd1f9..00000000 --- a/lib/cherrypy/_helper.py +++ /dev/null @@ -1,348 +0,0 @@ -"""Helper functions for CP apps.""" - -import urllib.parse - -from cherrypy._cpcompat import text_or_bytes - -import cherrypy - - -def expose(func=None, alias=None): - """Expose the function or class. - - Optionally provide an alias or set of aliases. - """ - def expose_(func): - func.exposed = True - if alias is not None: - if isinstance(alias, text_or_bytes): - parents[alias.replace('.', '_')] = func - else: - for a in alias: - parents[a.replace('.', '_')] = func - return func - - import sys - import types - decoratable_types = types.FunctionType, types.MethodType, type, - if isinstance(func, decoratable_types): - if alias is None: - # @expose - func.exposed = True - return func - else: - # func = expose(func, alias) - parents = sys._getframe(1).f_locals - return expose_(func) - elif func is None: - if alias is None: - # @expose() - parents = sys._getframe(1).f_locals - return expose_ - else: - # @expose(alias="alias") or - # @expose(alias=["alias1", "alias2"]) - parents = sys._getframe(1).f_locals - return expose_ - else: - # @expose("alias") or - # @expose(["alias1", "alias2"]) - parents = sys._getframe(1).f_locals - alias = func - return expose_ - - -def popargs(*args, **kwargs): - """Decorate _cp_dispatch. - - (cherrypy.dispatch.Dispatcher.dispatch_method_name) - - Optional keyword argument: handler=(Object or Function) - - Provides a _cp_dispatch function that pops off path segments into - cherrypy.request.params under the names specified. The dispatch - is then forwarded on to the next vpath element. - - Note that any existing (and exposed) member function of the class that - popargs is applied to will override that value of the argument. For - instance, if you have a method named "list" on the class decorated with - popargs, then accessing "/list" will call that function instead of popping - it off as the requested parameter. This restriction applies to all - _cp_dispatch functions. The only way around this restriction is to create - a "blank class" whose only function is to provide _cp_dispatch. - - If there are path elements after the arguments, or more arguments - are requested than are available in the vpath, then the 'handler' - keyword argument specifies the next object to handle the parameterized - request. If handler is not specified or is None, then self is used. - If handler is a function rather than an instance, then that function - will be called with the args specified and the return value from that - function used as the next object INSTEAD of adding the parameters to - cherrypy.request.args. - - This decorator may be used in one of two ways: - - As a class decorator: - - .. code-block:: python - - @cherrypy.popargs('year', 'month', 'day') - class Blog: - def index(self, year=None, month=None, day=None): - #Process the parameters here; any url like - #/, /2009, /2009/12, or /2009/12/31 - #will fill in the appropriate parameters. - - def create(self): - #This link will still be available at /create. - #Defined functions take precedence over arguments. - - Or as a member of a class: - - .. code-block:: python - - class Blog: - _cp_dispatch = cherrypy.popargs('year', 'month', 'day') - #... - - The handler argument may be used to mix arguments with built in functions. - For instance, the following setup allows different activities at the - day, month, and year level: - - .. code-block:: python - - class DayHandler: - def index(self, year, month, day): - #Do something with this day; probably list entries - - def delete(self, year, month, day): - #Delete all entries for this day - - @cherrypy.popargs('day', handler=DayHandler()) - class MonthHandler: - def index(self, year, month): - #Do something with this month; probably list entries - - def delete(self, year, month): - #Delete all entries for this month - - @cherrypy.popargs('month', handler=MonthHandler()) - class YearHandler: - def index(self, year): - #Do something with this year - - #... - - @cherrypy.popargs('year', handler=YearHandler()) - class Root: - def index(self): - #... - - """ - # Since keyword arg comes after *args, we have to process it ourselves - # for lower versions of python. - - handler = None - handler_call = False - for k, v in kwargs.items(): - if k == 'handler': - handler = v - else: - tm = "cherrypy.popargs() got an unexpected keyword argument '{0}'" - raise TypeError(tm.format(k)) - - import inspect - - if handler is not None \ - and (hasattr(handler, '__call__') or inspect.isclass(handler)): - handler_call = True - - def decorated(cls_or_self=None, vpath=None): - if inspect.isclass(cls_or_self): - # cherrypy.popargs is a class decorator - cls = cls_or_self - name = cherrypy.dispatch.Dispatcher.dispatch_method_name - setattr(cls, name, decorated) - return cls - - # We're in the actual function - self = cls_or_self - parms = {} - for arg in args: - if not vpath: - break - parms[arg] = vpath.pop(0) - - if handler is not None: - if handler_call: - return handler(**parms) - else: - cherrypy.request.params.update(parms) - return handler - - cherrypy.request.params.update(parms) - - # If we are the ultimate handler, then to prevent our _cp_dispatch - # from being called again, we will resolve remaining elements through - # getattr() directly. - if vpath: - return getattr(self, vpath.pop(0), None) - else: - return self - - return decorated - - -def url(path='', qs='', script_name=None, base=None, relative=None): - """Create an absolute URL for the given path. - - If 'path' starts with a slash ('/'), this will return - (base + script_name + path + qs). - If it does not start with a slash, this returns - (base + script_name [+ request.path_info] + path + qs). - - If script_name is None, cherrypy.request will be used - to find a script_name, if available. - - If base is None, cherrypy.request.base will be used (if available). - Note that you can use cherrypy.tools.proxy to change this. - - Finally, note that this function can be used to obtain an absolute URL - for the current request path (minus the querystring) by passing no args. - If you call url(qs=cherrypy.request.query_string), you should get the - original browser URL (assuming no internal redirections). - - If relative is None or not provided, request.app.relative_urls will - be used (if available, else False). If False, the output will be an - absolute URL (including the scheme, host, vhost, and script_name). - If True, the output will instead be a URL that is relative to the - current request path, perhaps including '..' atoms. If relative is - the string 'server', the output will instead be a URL that is - relative to the server root; i.e., it will start with a slash. - """ - if isinstance(qs, (tuple, list, dict)): - qs = urllib.parse.urlencode(qs) - if qs: - qs = '?' + qs - - if cherrypy.request.app: - if not path.startswith('/'): - # Append/remove trailing slash from path_info as needed - # (this is to support mistyped URL's without redirecting; - # if you want to redirect, use tools.trailing_slash). - pi = cherrypy.request.path_info - if cherrypy.request.is_index is True: - if not pi.endswith('/'): - pi = pi + '/' - elif cherrypy.request.is_index is False: - if pi.endswith('/') and pi != '/': - pi = pi[:-1] - - if path == '': - path = pi - else: - path = urllib.parse.urljoin(pi, path) - - if script_name is None: - script_name = cherrypy.request.script_name - if base is None: - base = cherrypy.request.base - - newurl = base + script_name + normalize_path(path) + qs - else: - # No request.app (we're being called outside a request). - # We'll have to guess the base from server.* attributes. - # This will produce very different results from the above - # if you're using vhosts or tools.proxy. - if base is None: - base = cherrypy.server.base() - - path = (script_name or '') + path - newurl = base + normalize_path(path) + qs - - # At this point, we should have a fully-qualified absolute URL. - - if relative is None: - relative = getattr(cherrypy.request.app, 'relative_urls', False) - - # See http://www.ietf.org/rfc/rfc2396.txt - if relative == 'server': - # "A relative reference beginning with a single slash character is - # termed an absolute-path reference, as defined by ..." - # This is also sometimes called "server-relative". - newurl = '/' + '/'.join(newurl.split('/', 3)[3:]) - elif relative: - # "A relative reference that does not begin with a scheme name - # or a slash character is termed a relative-path reference." - old = url(relative=False).split('/')[:-1] - new = newurl.split('/') - while old and new: - a, b = old[0], new[0] - if a != b: - break - old.pop(0) - new.pop(0) - new = (['..'] * len(old)) + new - newurl = '/'.join(new) - - return newurl - - -def normalize_path(path): - """Resolve given path from relative into absolute form.""" - if './' not in path: - return path - - # Normalize the URL by removing ./ and ../ - atoms = [] - for atom in path.split('/'): - if atom == '.': - pass - elif atom == '..': - # Don't pop from empty list - # (i.e. ignore redundant '..') - if atoms: - atoms.pop() - elif atom: - atoms.append(atom) - - newpath = '/'.join(atoms) - # Preserve leading '/' - if path.startswith('/'): - newpath = '/' + newpath - - return newpath - - -#### -# Inlined from jaraco.classes 1.4.3 -# Ref #1673 -class _ClassPropertyDescriptor(object): - """Descript for read-only class-based property. - - Turns a classmethod-decorated func into a read-only property of that class - type (means the value cannot be set). - """ - - def __init__(self, fget, fset=None): - """Initialize a class property descriptor. - - Instantiated by ``_helper.classproperty``. - """ - self.fget = fget - self.fset = fset - - def __get__(self, obj, klass=None): - """Return property value.""" - if klass is None: - klass = type(obj) - return self.fget.__get__(obj, klass)() - - -def classproperty(func): # noqa: D401; irrelevant for properties - """Decorator like classmethod to implement a static class property.""" - if not isinstance(func, (classmethod, staticmethod)): - func = classmethod(func) - - return _ClassPropertyDescriptor(func) -#### diff --git a/lib/cherrypy/_json.py b/lib/cherrypy/_json.py deleted file mode 100644 index 0c2a0f0e..00000000 --- a/lib/cherrypy/_json.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -JSON support. - -Expose preferred json module as json and provide encode/decode -convenience functions. -""" - -try: - # Prefer simplejson - import simplejson as json -except ImportError: - import json - - -__all__ = ['json', 'encode', 'decode'] - - -decode = json.JSONDecoder().decode -_encode = json.JSONEncoder().iterencode - - -def encode(value): - """Encode to bytes.""" - for chunk in _encode(value): - yield chunk.encode('utf-8') diff --git a/lib/cherrypy/daemon.py b/lib/cherrypy/daemon.py deleted file mode 100644 index 74488c06..00000000 --- a/lib/cherrypy/daemon.py +++ /dev/null @@ -1,107 +0,0 @@ -"""The CherryPy daemon.""" - -import sys - -import cherrypy -from cherrypy.process import plugins, servers -from cherrypy import Application - - -def start(configfiles=None, daemonize=False, environment=None, - fastcgi=False, scgi=False, pidfile=None, imports=None, - cgi=False): - """Subscribe all engine plugins and start the engine.""" - sys.path = [''] + sys.path - for i in imports or []: - exec('import %s' % i) - - for c in configfiles or []: - cherrypy.config.update(c) - # If there's only one app mounted, merge config into it. - if len(cherrypy.tree.apps) == 1: - for app in cherrypy.tree.apps.values(): - if isinstance(app, Application): - app.merge(c) - - engine = cherrypy.engine - - if environment is not None: - cherrypy.config.update({'environment': environment}) - - # Only daemonize if asked to. - if daemonize: - # Don't print anything to stdout/sterr. - cherrypy.config.update({'log.screen': False}) - plugins.Daemonizer(engine).subscribe() - - if pidfile: - plugins.PIDFile(engine, pidfile).subscribe() - - if hasattr(engine, 'signal_handler'): - engine.signal_handler.subscribe() - if hasattr(engine, 'console_control_handler'): - engine.console_control_handler.subscribe() - - if (fastcgi and (scgi or cgi)) or (scgi and cgi): - cherrypy.log.error('You may only specify one of the cgi, fastcgi, and ' - 'scgi options.', 'ENGINE') - sys.exit(1) - elif fastcgi or scgi or cgi: - # Turn off autoreload when using *cgi. - cherrypy.config.update({'engine.autoreload.on': False}) - # Turn off the default HTTP server (which is subscribed by default). - cherrypy.server.unsubscribe() - - addr = cherrypy.server.bind_addr - cls = ( - servers.FlupFCGIServer if fastcgi else - servers.FlupSCGIServer if scgi else - servers.FlupCGIServer - ) - f = cls(application=cherrypy.tree, bindAddress=addr) - s = servers.ServerAdapter(engine, httpserver=f, bind_addr=addr) - s.subscribe() - - # Always start the engine; this will start all other services - try: - engine.start() - except Exception: - # Assume the error has been logged already via bus.log. - sys.exit(1) - else: - engine.block() - - -def run(): - """Run cherryd CLI.""" - from optparse import OptionParser - - p = OptionParser() - p.add_option('-c', '--config', action='append', dest='config', - help='specify config file(s)') - p.add_option('-d', action='store_true', dest='daemonize', - help='run the server as a daemon') - p.add_option('-e', '--environment', dest='environment', default=None, - help='apply the given config environment') - p.add_option('-f', action='store_true', dest='fastcgi', - help='start a fastcgi server instead of the default HTTP ' - 'server') - p.add_option('-s', action='store_true', dest='scgi', - help='start a scgi server instead of the default HTTP server') - p.add_option('-x', action='store_true', dest='cgi', - help='start a cgi server instead of the default HTTP server') - p.add_option('-i', '--import', action='append', dest='imports', - help='specify modules to import') - p.add_option('-p', '--pidfile', dest='pidfile', default=None, - help='store the process id in the given file') - p.add_option('-P', '--Path', action='append', dest='Path', - help='add the given paths to sys.path') - options, args = p.parse_args() - - if options.Path: - for p in options.Path: - sys.path.insert(0, p) - - start(options.config, options.daemonize, - options.environment, options.fastcgi, options.scgi, - options.pidfile, options.imports, options.cgi) diff --git a/lib/cherrypy/favicon.ico b/lib/cherrypy/favicon.ico deleted file mode 100644 index f0d7e61b..00000000 Binary files a/lib/cherrypy/favicon.ico and /dev/null differ diff --git a/lib/cherrypy/lib/__init__.py b/lib/cherrypy/lib/__init__.py deleted file mode 100644 index 0edaaf20..00000000 --- a/lib/cherrypy/lib/__init__.py +++ /dev/null @@ -1,101 +0,0 @@ -"""CherryPy Library.""" - - -def is_iterator(obj): - """Detect if the object provided implements the iterator protocol. - - (i.e. like a generator). - - This will return False for objects which are iterable, - but not iterators themselves. - """ - from types import GeneratorType - if isinstance(obj, GeneratorType): - return True - elif not hasattr(obj, '__iter__'): - return False - else: - # Types which implement the protocol must return themselves when - # invoking 'iter' upon them. - return iter(obj) is obj - - -def is_closable_iterator(obj): - """Detect if the given object is both closable and iterator.""" - # Not an iterator. - if not is_iterator(obj): - return False - - # A generator - the easiest thing to deal with. - import inspect - if inspect.isgenerator(obj): - return True - - # A custom iterator. Look for a close method... - if not (hasattr(obj, 'close') and callable(obj.close)): - return False - - # ... which doesn't require any arguments. - try: - inspect.getcallargs(obj.close) - except TypeError: - return False - else: - return True - - -class file_generator(object): - """Yield the given input (a file object) in chunks (default 64k). - - (Core) - """ - - def __init__(self, input, chunkSize=65536): - """Initialize file_generator with file ``input`` for chunked access.""" - self.input = input - self.chunkSize = chunkSize - - def __iter__(self): - """Return iterator.""" - return self - - def __next__(self): - """Return next chunk of file.""" - chunk = self.input.read(self.chunkSize) - if chunk: - return chunk - else: - if hasattr(self.input, 'close'): - self.input.close() - raise StopIteration() - next = __next__ - - def __del__(self): - """Close input on descturct.""" - if hasattr(self.input, 'close'): - self.input.close() - - -def file_generator_limited(fileobj, count, chunk_size=65536): - """Yield the given file object in chunks. - - Stopps after `count` bytes has been emitted. - Default chunk size is 64kB. (Core) - """ - remaining = count - while remaining > 0: - chunk = fileobj.read(min(chunk_size, remaining)) - chunklen = len(chunk) - if chunklen == 0: - return - remaining -= chunklen - yield chunk - - -def set_vary_header(response, header_name): - """Add a Vary header to a response.""" - varies = response.headers.get('Vary', '') - varies = [x.strip() for x in varies.split(',') if x.strip()] - if header_name not in varies: - varies.append(header_name) - response.headers['Vary'] = ', '.join(varies) diff --git a/lib/cherrypy/lib/auth_basic.py b/lib/cherrypy/lib/auth_basic.py deleted file mode 100644 index ad379a26..00000000 --- a/lib/cherrypy/lib/auth_basic.py +++ /dev/null @@ -1,120 +0,0 @@ -# This file is part of CherryPy -# -*- coding: utf-8 -*- -# vim:ts=4:sw=4:expandtab:fileencoding=utf-8 -"""HTTP Basic Authentication tool. - -This module provides a CherryPy 3.x tool which implements -the server-side of HTTP Basic Access Authentication, as described in -:rfc:`2617`. - -Example usage, using the built-in checkpassword_dict function which uses a dict -as the credentials store:: - - userpassdict = {'bird' : 'bebop', 'ornette' : 'wayout'} - checkpassword = cherrypy.lib.auth_basic.checkpassword_dict(userpassdict) - basic_auth = {'tools.auth_basic.on': True, - 'tools.auth_basic.realm': 'earth', - 'tools.auth_basic.checkpassword': checkpassword, - 'tools.auth_basic.accept_charset': 'UTF-8', - } - app_config = { '/' : basic_auth } - -""" - -import binascii -import unicodedata -import base64 - -import cherrypy -from cherrypy._cpcompat import ntou, tonative - - -__author__ = 'visteya' -__date__ = 'April 2009' - - -def checkpassword_dict(user_password_dict): - """Returns a checkpassword function which checks credentials - against a dictionary of the form: {username : password}. - - If you want a simple dictionary-based authentication scheme, use - checkpassword_dict(my_credentials_dict) as the value for the - checkpassword argument to basic_auth(). - """ - def checkpassword(realm, user, password): - p = user_password_dict.get(user) - return p and p == password or False - - return checkpassword - - -def basic_auth(realm, checkpassword, debug=False, accept_charset='utf-8'): - """A CherryPy tool which hooks at before_handler to perform - HTTP Basic Access Authentication, as specified in :rfc:`2617` - and :rfc:`7617`. - - If the request has an 'authorization' header with a 'Basic' scheme, this - tool attempts to authenticate the credentials supplied in that header. If - the request has no 'authorization' header, or if it does but the scheme is - not 'Basic', or if authentication fails, the tool sends a 401 response with - a 'WWW-Authenticate' Basic header. - - realm - A string containing the authentication realm. - - checkpassword - A callable which checks the authentication credentials. - Its signature is checkpassword(realm, username, password). where - username and password are the values obtained from the request's - 'authorization' header. If authentication succeeds, checkpassword - returns True, else it returns False. - - """ - - fallback_charset = 'ISO-8859-1' - - if '"' in realm: - raise ValueError('Realm cannot contain the " (quote) character.') - request = cherrypy.serving.request - - auth_header = request.headers.get('authorization') - if auth_header is not None: - # split() error, base64.decodestring() error - msg = 'Bad Request' - with cherrypy.HTTPError.handle((ValueError, binascii.Error), 400, msg): - scheme, params = auth_header.split(' ', 1) - if scheme.lower() == 'basic': - charsets = accept_charset, fallback_charset - decoded_params = base64.b64decode(params.encode('ascii')) - decoded_params = _try_decode(decoded_params, charsets) - decoded_params = ntou(decoded_params) - decoded_params = unicodedata.normalize('NFC', decoded_params) - decoded_params = tonative(decoded_params) - username, password = decoded_params.split(':', 1) - if checkpassword(realm, username, password): - if debug: - cherrypy.log('Auth succeeded', 'TOOLS.AUTH_BASIC') - request.login = username - return # successful authentication - - charset = accept_charset.upper() - charset_declaration = ( - (', charset="%s"' % charset) - if charset != fallback_charset - else '' - ) - # Respond with 401 status and a WWW-Authenticate header - cherrypy.serving.response.headers['www-authenticate'] = ( - 'Basic realm="%s"%s' % (realm, charset_declaration) - ) - raise cherrypy.HTTPError( - 401, 'You are not authorized to access that resource') - - -def _try_decode(subject, charsets): - for charset in charsets[:-1]: - try: - return tonative(subject, charset) - except ValueError: - pass - return tonative(subject, charsets[-1]) diff --git a/lib/cherrypy/lib/auth_digest.py b/lib/cherrypy/lib/auth_digest.py deleted file mode 100644 index 981e9a5d..00000000 --- a/lib/cherrypy/lib/auth_digest.py +++ /dev/null @@ -1,462 +0,0 @@ -# This file is part of CherryPy -# -*- coding: utf-8 -*- -# vim:ts=4:sw=4:expandtab:fileencoding=utf-8 -"""HTTP Digest Authentication tool. - -An implementation of the server-side of HTTP Digest Access -Authentication, which is described in :rfc:`2617`. - -Example usage, using the built-in get_ha1_dict_plain function which uses a dict -of plaintext passwords as the credentials store:: - - userpassdict = {'alice' : '4x5istwelve'} - get_ha1 = cherrypy.lib.auth_digest.get_ha1_dict_plain(userpassdict) - digest_auth = {'tools.auth_digest.on': True, - 'tools.auth_digest.realm': 'wonderland', - 'tools.auth_digest.get_ha1': get_ha1, - 'tools.auth_digest.key': 'a565c27146791cfb', - 'tools.auth_digest.accept_charset': 'UTF-8', - } - app_config = { '/' : digest_auth } -""" - -import time -import functools -from hashlib import md5 -from urllib.request import parse_http_list, parse_keqv_list - -import cherrypy -from cherrypy._cpcompat import ntob, tonative - - -__author__ = 'visteya' -__date__ = 'April 2009' - - -def md5_hex(s): - return md5(ntob(s, 'utf-8')).hexdigest() - - -qop_auth = 'auth' -qop_auth_int = 'auth-int' -valid_qops = (qop_auth, qop_auth_int) - -valid_algorithms = ('MD5', 'MD5-sess') - -FALLBACK_CHARSET = 'ISO-8859-1' -DEFAULT_CHARSET = 'UTF-8' - - -def TRACE(msg): - cherrypy.log(msg, context='TOOLS.AUTH_DIGEST') - -# Three helper functions for users of the tool, providing three variants -# of get_ha1() functions for three different kinds of credential stores. - - -def get_ha1_dict_plain(user_password_dict): - """Returns a get_ha1 function which obtains a plaintext password from a - dictionary of the form: {username : password}. - - If you want a simple dictionary-based authentication scheme, with plaintext - passwords, use get_ha1_dict_plain(my_userpass_dict) as the value for the - get_ha1 argument to digest_auth(). - """ - def get_ha1(realm, username): - password = user_password_dict.get(username) - if password: - return md5_hex('%s:%s:%s' % (username, realm, password)) - return None - - return get_ha1 - - -def get_ha1_dict(user_ha1_dict): - """Returns a get_ha1 function which obtains a HA1 password hash from a - dictionary of the form: {username : HA1}. - - If you want a dictionary-based authentication scheme, but with - pre-computed HA1 hashes instead of plain-text passwords, use - get_ha1_dict(my_userha1_dict) as the value for the get_ha1 - argument to digest_auth(). - """ - def get_ha1(realm, username): - return user_ha1_dict.get(username) - - return get_ha1 - - -def get_ha1_file_htdigest(filename): - """Returns a get_ha1 function which obtains a HA1 password hash from a - flat file with lines of the same format as that produced by the Apache - htdigest utility. For example, for realm 'wonderland', username 'alice', - and password '4x5istwelve', the htdigest line would be:: - - alice:wonderland:3238cdfe91a8b2ed8e39646921a02d4c - - If you want to use an Apache htdigest file as the credentials store, - then use get_ha1_file_htdigest(my_htdigest_file) as the value for the - get_ha1 argument to digest_auth(). It is recommended that the filename - argument be an absolute path, to avoid problems. - """ - def get_ha1(realm, username): - result = None - with open(filename, 'r') as f: - for line in f: - u, r, ha1 = line.rstrip().split(':') - if u == username and r == realm: - result = ha1 - break - return result - - return get_ha1 - - -def synthesize_nonce(s, key, timestamp=None): - """Synthesize a nonce value which resists spoofing and can be checked - for staleness. Returns a string suitable as the value for 'nonce' in - the www-authenticate header. - - s - A string related to the resource, such as the hostname of the server. - - key - A secret string known only to the server. - - timestamp - An integer seconds-since-the-epoch timestamp - - """ - if timestamp is None: - timestamp = int(time.time()) - h = md5_hex('%s:%s:%s' % (timestamp, s, key)) - nonce = '%s:%s' % (timestamp, h) - return nonce - - -def H(s): - """The hash function H""" - return md5_hex(s) - - -def _try_decode_header(header, charset): - global FALLBACK_CHARSET - - for enc in (charset, FALLBACK_CHARSET): - try: - return tonative(ntob(tonative(header, 'latin1'), 'latin1'), enc) - except ValueError as ve: - last_err = ve - else: - raise last_err - - -class HttpDigestAuthorization(object): - """ - Parses a Digest Authorization header and performs - re-calculation of the digest. - """ - - scheme = 'digest' - - def errmsg(self, s): - return 'Digest Authorization header: %s' % s - - @classmethod - def matches(cls, header): - scheme, _, _ = header.partition(' ') - return scheme.lower() == cls.scheme - - def __init__( - self, auth_header, http_method, - debug=False, accept_charset=DEFAULT_CHARSET[:], - ): - self.http_method = http_method - self.debug = debug - - if not self.matches(auth_header): - raise ValueError('Authorization scheme is not "Digest"') - - self.auth_header = _try_decode_header(auth_header, accept_charset) - - scheme, params = self.auth_header.split(' ', 1) - - # make a dict of the params - items = parse_http_list(params) - paramsd = parse_keqv_list(items) - - self.realm = paramsd.get('realm') - self.username = paramsd.get('username') - self.nonce = paramsd.get('nonce') - self.uri = paramsd.get('uri') - self.method = paramsd.get('method') - self.response = paramsd.get('response') # the response digest - self.algorithm = paramsd.get('algorithm', 'MD5').upper() - self.cnonce = paramsd.get('cnonce') - self.opaque = paramsd.get('opaque') - self.qop = paramsd.get('qop') # qop - self.nc = paramsd.get('nc') # nonce count - - # perform some correctness checks - if self.algorithm not in valid_algorithms: - raise ValueError( - self.errmsg("Unsupported value for algorithm: '%s'" % - self.algorithm)) - - has_reqd = ( - self.username and - self.realm and - self.nonce and - self.uri and - self.response - ) - if not has_reqd: - raise ValueError( - self.errmsg('Not all required parameters are present.')) - - if self.qop: - if self.qop not in valid_qops: - raise ValueError( - self.errmsg("Unsupported value for qop: '%s'" % self.qop)) - if not (self.cnonce and self.nc): - raise ValueError( - self.errmsg('If qop is sent then ' - 'cnonce and nc MUST be present')) - else: - if self.cnonce or self.nc: - raise ValueError( - self.errmsg('If qop is not sent, ' - 'neither cnonce nor nc can be present')) - - def __str__(self): - return 'authorization : %s' % self.auth_header - - def validate_nonce(self, s, key): - """Validate the nonce. - Returns True if nonce was generated by synthesize_nonce() and the - timestamp is not spoofed, else returns False. - - s - A string related to the resource, such as the hostname of - the server. - - key - A secret string known only to the server. - - Both s and key must be the same values which were used to synthesize - the nonce we are trying to validate. - """ - try: - timestamp, hashpart = self.nonce.split(':', 1) - s_timestamp, s_hashpart = synthesize_nonce( - s, key, timestamp).split(':', 1) - is_valid = s_hashpart == hashpart - if self.debug: - TRACE('validate_nonce: %s' % is_valid) - return is_valid - except ValueError: # split() error - pass - return False - - def is_nonce_stale(self, max_age_seconds=600): - """Returns True if a validated nonce is stale. The nonce contains a - timestamp in plaintext and also a secure hash of the timestamp. - You should first validate the nonce to ensure the plaintext - timestamp is not spoofed. - """ - try: - timestamp, hashpart = self.nonce.split(':', 1) - if int(timestamp) + max_age_seconds > int(time.time()): - return False - except ValueError: # int() error - pass - if self.debug: - TRACE('nonce is stale') - return True - - def HA2(self, entity_body=''): - """Returns the H(A2) string. See :rfc:`2617` section 3.2.2.3.""" - # RFC 2617 3.2.2.3 - # If the "qop" directive's value is "auth" or is unspecified, - # then A2 is: - # A2 = method ":" digest-uri-value - # - # If the "qop" value is "auth-int", then A2 is: - # A2 = method ":" digest-uri-value ":" H(entity-body) - if self.qop is None or self.qop == 'auth': - a2 = '%s:%s' % (self.http_method, self.uri) - elif self.qop == 'auth-int': - a2 = '%s:%s:%s' % (self.http_method, self.uri, H(entity_body)) - else: - # in theory, this should never happen, since I validate qop in - # __init__() - raise ValueError(self.errmsg('Unrecognized value for qop!')) - return H(a2) - - def request_digest(self, ha1, entity_body=''): - """Calculates the Request-Digest. See :rfc:`2617` section 3.2.2.1. - - ha1 - The HA1 string obtained from the credentials store. - - entity_body - If 'qop' is set to 'auth-int', then A2 includes a hash - of the "entity body". The entity body is the part of the - message which follows the HTTP headers. See :rfc:`2617` section - 4.3. This refers to the entity the user agent sent in the - request which has the Authorization header. Typically GET - requests don't have an entity, and POST requests do. - - """ - ha2 = self.HA2(entity_body) - # Request-Digest -- RFC 2617 3.2.2.1 - if self.qop: - req = '%s:%s:%s:%s:%s' % ( - self.nonce, self.nc, self.cnonce, self.qop, ha2) - else: - req = '%s:%s' % (self.nonce, ha2) - - # RFC 2617 3.2.2.2 - # - # If the "algorithm" directive's value is "MD5" or is unspecified, - # then A1 is: - # A1 = unq(username-value) ":" unq(realm-value) ":" passwd - # - # If the "algorithm" directive's value is "MD5-sess", then A1 is - # calculated only once - on the first request by the client following - # receipt of a WWW-Authenticate challenge from the server. - # A1 = H( unq(username-value) ":" unq(realm-value) ":" passwd ) - # ":" unq(nonce-value) ":" unq(cnonce-value) - if self.algorithm == 'MD5-sess': - ha1 = H('%s:%s:%s' % (ha1, self.nonce, self.cnonce)) - - digest = H('%s:%s' % (ha1, req)) - return digest - - -def _get_charset_declaration(charset): - global FALLBACK_CHARSET - charset = charset.upper() - return ( - (', charset="%s"' % charset) - if charset != FALLBACK_CHARSET - else '' - ) - - -def www_authenticate( - realm, key, algorithm='MD5', nonce=None, qop=qop_auth, - stale=False, accept_charset=DEFAULT_CHARSET[:], -): - """Constructs a WWW-Authenticate header for Digest authentication.""" - if qop not in valid_qops: - raise ValueError("Unsupported value for qop: '%s'" % qop) - if algorithm not in valid_algorithms: - raise ValueError("Unsupported value for algorithm: '%s'" % algorithm) - - HEADER_PATTERN = ( - 'Digest realm="%s", nonce="%s", algorithm="%s", qop="%s"%s%s' - ) - - if nonce is None: - nonce = synthesize_nonce(realm, key) - - stale_param = ', stale="true"' if stale else '' - - charset_declaration = _get_charset_declaration(accept_charset) - - return HEADER_PATTERN % ( - realm, nonce, algorithm, qop, stale_param, charset_declaration, - ) - - -def digest_auth(realm, get_ha1, key, debug=False, accept_charset='utf-8'): - """A CherryPy tool that hooks at before_handler to perform - HTTP Digest Access Authentication, as specified in :rfc:`2617`. - - If the request has an 'authorization' header with a 'Digest' scheme, - this tool authenticates the credentials supplied in that header. - If the request has no 'authorization' header, or if it does but the - scheme is not "Digest", or if authentication fails, the tool sends - a 401 response with a 'WWW-Authenticate' Digest header. - - realm - A string containing the authentication realm. - - get_ha1 - A callable that looks up a username in a credentials store - and returns the HA1 string, which is defined in the RFC to be - MD5(username : realm : password). The function's signature is: - ``get_ha1(realm, username)`` - where username is obtained from the request's 'authorization' header. - If username is not found in the credentials store, get_ha1() returns - None. - - key - A secret string known only to the server, used in the synthesis - of nonces. - - """ - request = cherrypy.serving.request - - auth_header = request.headers.get('authorization') - - respond_401 = functools.partial( - _respond_401, realm, key, accept_charset, debug) - - if not HttpDigestAuthorization.matches(auth_header or ''): - respond_401() - - msg = 'The Authorization header could not be parsed.' - with cherrypy.HTTPError.handle(ValueError, 400, msg): - auth = HttpDigestAuthorization( - auth_header, request.method, - debug=debug, accept_charset=accept_charset, - ) - - if debug: - TRACE(str(auth)) - - if not auth.validate_nonce(realm, key): - respond_401() - - ha1 = get_ha1(realm, auth.username) - - if ha1 is None: - respond_401() - - # note that for request.body to be available we need to - # hook in at before_handler, not on_start_resource like - # 3.1.x digest_auth does. - digest = auth.request_digest(ha1, entity_body=request.body) - if digest != auth.response: - respond_401() - - # authenticated - if debug: - TRACE('digest matches auth.response') - # Now check if nonce is stale. - # The choice of ten minutes' lifetime for nonce is somewhat - # arbitrary - if auth.is_nonce_stale(max_age_seconds=600): - respond_401(stale=True) - - request.login = auth.username - if debug: - TRACE('authentication of %s successful' % auth.username) - - -def _respond_401(realm, key, accept_charset, debug, **kwargs): - """ - Respond with 401 status and a WWW-Authenticate header - """ - header = www_authenticate( - realm, key, - accept_charset=accept_charset, - **kwargs - ) - if debug: - TRACE(header) - cherrypy.serving.response.headers['WWW-Authenticate'] = header - raise cherrypy.HTTPError( - 401, 'You are not authorized to access that resource') diff --git a/lib/cherrypy/lib/caching.py b/lib/cherrypy/lib/caching.py deleted file mode 100644 index 08d2d8e4..00000000 --- a/lib/cherrypy/lib/caching.py +++ /dev/null @@ -1,478 +0,0 @@ -""" -CherryPy implements a simple caching system as a pluggable Tool. This tool -tries to be an (in-process) HTTP/1.1-compliant cache. It's not quite there -yet, but it's probably good enough for most sites. - -In general, GET responses are cached (along with selecting headers) and, if -another request arrives for the same resource, the caching Tool will return 304 -Not Modified if possible, or serve the cached response otherwise. It also sets -request.cached to True if serving a cached representation, and sets -request.cacheable to False (so it doesn't get cached again). - -If POST, PUT, or DELETE requests are made for a cached resource, they -invalidate (delete) any cached response. - -Usage -===== - -Configuration file example:: - - [/] - tools.caching.on = True - tools.caching.delay = 3600 - -You may use a class other than the default -:class:`MemoryCache` by supplying the config -entry ``cache_class``; supply the full dotted name of the replacement class -as the config value. It must implement the basic methods ``get``, ``put``, -``delete``, and ``clear``. - -You may set any attribute, including overriding methods, on the cache -instance by providing them in config. The above sets the -:attr:`delay` attribute, for example. -""" - -import datetime -import sys -import threading -import time - -import cherrypy -from cherrypy.lib import cptools, httputil - - -class Cache(object): - - """Base class for Cache implementations.""" - - def get(self): - """Return the current variant if in the cache, else None.""" - raise NotImplementedError - - def put(self, obj, size): - """Store the current variant in the cache.""" - raise NotImplementedError - - def delete(self): - """Remove ALL cached variants of the current resource.""" - raise NotImplementedError - - def clear(self): - """Reset the cache to its initial, empty state.""" - raise NotImplementedError - - -# ------------------------------ Memory Cache ------------------------------- # -class AntiStampedeCache(dict): - - """A storage system for cached items which reduces stampede collisions.""" - - def wait(self, key, timeout=5, debug=False): - """Return the cached value for the given key, or None. - - If timeout is not None, and the value is already - being calculated by another thread, wait until the given timeout has - elapsed. If the value is available before the timeout expires, it is - returned. If not, None is returned, and a sentinel placed in the cache - to signal other threads to wait. - - If timeout is None, no waiting is performed nor sentinels used. - """ - value = self.get(key) - if isinstance(value, threading.Event): - if timeout is None: - # Ignore the other thread and recalc it ourselves. - if debug: - cherrypy.log('No timeout', 'TOOLS.CACHING') - return None - - # Wait until it's done or times out. - if debug: - cherrypy.log('Waiting up to %s seconds' % - timeout, 'TOOLS.CACHING') - value.wait(timeout) - if value.result is not None: - # The other thread finished its calculation. Use it. - if debug: - cherrypy.log('Result!', 'TOOLS.CACHING') - return value.result - # Timed out. Stick an Event in the slot so other threads wait - # on this one to finish calculating the value. - if debug: - cherrypy.log('Timed out', 'TOOLS.CACHING') - e = threading.Event() - e.result = None - dict.__setitem__(self, key, e) - - return None - elif value is None: - # Stick an Event in the slot so other threads wait - # on this one to finish calculating the value. - if debug: - cherrypy.log('Timed out', 'TOOLS.CACHING') - e = threading.Event() - e.result = None - dict.__setitem__(self, key, e) - return value - - def __setitem__(self, key, value): - """Set the cached value for the given key.""" - existing = self.get(key) - dict.__setitem__(self, key, value) - if isinstance(existing, threading.Event): - # Set Event.result so other threads waiting on it have - # immediate access without needing to poll the cache again. - existing.result = value - existing.set() - - -class MemoryCache(Cache): - - """An in-memory cache for varying response content. - - Each key in self.store is a URI, and each value is an AntiStampedeCache. - The response for any given URI may vary based on the values of - "selecting request headers"; that is, those named in the Vary - response header. We assume the list of header names to be constant - for each URI throughout the lifetime of the application, and store - that list in ``self.store[uri].selecting_headers``. - - The items contained in ``self.store[uri]`` have keys which are tuples of - request header values (in the same order as the names in its - selecting_headers), and values which are the actual responses. - """ - - maxobjects = 1000 - """The maximum number of cached objects; defaults to 1000.""" - - maxobj_size = 100000 - """The maximum size of each cached object in bytes; defaults to 100 KB.""" - - maxsize = 10000000 - """The maximum size of the entire cache in bytes; defaults to 10 MB.""" - - delay = 600 - """Seconds until the cached content expires; defaults to 600 (10 minutes). - """ - - antistampede_timeout = 5 - """Seconds to wait for other threads to release a cache lock.""" - - expire_freq = 0.1 - """Seconds to sleep between cache expiration sweeps.""" - - debug = False - - def __init__(self): - self.clear() - - # Run self.expire_cache in a separate daemon thread. - t = threading.Thread(target=self.expire_cache, name='expire_cache') - self.expiration_thread = t - t.daemon = True - t.start() - - def clear(self): - """Reset the cache to its initial, empty state.""" - self.store = {} - self.expirations = {} - self.tot_puts = 0 - self.tot_gets = 0 - self.tot_hist = 0 - self.tot_expires = 0 - self.tot_non_modified = 0 - self.cursize = 0 - - def expire_cache(self): - """Continuously examine cached objects, expiring stale ones. - - This function is designed to be run in its own daemon thread, - referenced at ``self.expiration_thread``. - """ - # It's possible that "time" will be set to None - # arbitrarily, so we check "while time" to avoid exceptions. - # See tickets #99 and #180 for more information. - while time: - now = time.time() - # Must make a copy of expirations so it doesn't change size - # during iteration - for expiration_time, objects in self.expirations.copy().items(): - if expiration_time <= now: - for obj_size, uri, sel_header_values in objects: - try: - del self.store[uri][tuple(sel_header_values)] - self.tot_expires += 1 - self.cursize -= obj_size - except KeyError: - # the key may have been deleted elsewhere - pass - del self.expirations[expiration_time] - time.sleep(self.expire_freq) - - def get(self): - """Return the current variant if in the cache, else None.""" - request = cherrypy.serving.request - self.tot_gets += 1 - - uri = cherrypy.url(qs=request.query_string) - uricache = self.store.get(uri) - if uricache is None: - return None - - header_values = [request.headers.get(h, '') - for h in uricache.selecting_headers] - variant = uricache.wait(key=tuple(sorted(header_values)), - timeout=self.antistampede_timeout, - debug=self.debug) - if variant is not None: - self.tot_hist += 1 - return variant - - def put(self, variant, size): - """Store the current variant in the cache.""" - request = cherrypy.serving.request - response = cherrypy.serving.response - - uri = cherrypy.url(qs=request.query_string) - uricache = self.store.get(uri) - if uricache is None: - uricache = AntiStampedeCache() - uricache.selecting_headers = [ - e.value for e in response.headers.elements('Vary')] - self.store[uri] = uricache - - if len(self.store) < self.maxobjects: - total_size = self.cursize + size - - # checks if there's space for the object - if (size < self.maxobj_size and total_size < self.maxsize): - # add to the expirations list - expiration_time = response.time + self.delay - bucket = self.expirations.setdefault(expiration_time, []) - bucket.append((size, uri, uricache.selecting_headers)) - - # add to the cache - header_values = [request.headers.get(h, '') - for h in uricache.selecting_headers] - uricache[tuple(sorted(header_values))] = variant - self.tot_puts += 1 - self.cursize = total_size - - def delete(self): - """Remove ALL cached variants of the current resource.""" - uri = cherrypy.url(qs=cherrypy.serving.request.query_string) - self.store.pop(uri, None) - - -def get(invalid_methods=('POST', 'PUT', 'DELETE'), debug=False, **kwargs): - """Try to obtain cached output. If fresh enough, raise HTTPError(304). - - If POST, PUT, or DELETE: - * invalidates (deletes) any cached response for this resource - * sets request.cached = False - * sets request.cacheable = False - - else if a cached copy exists: - * sets request.cached = True - * sets request.cacheable = False - * sets response.headers to the cached values - * checks the cached Last-Modified response header against the - current If-(Un)Modified-Since request headers; raises 304 - if necessary. - * sets response.status and response.body to the cached values - * returns True - - otherwise: - * sets request.cached = False - * sets request.cacheable = True - * returns False - """ - request = cherrypy.serving.request - response = cherrypy.serving.response - - if not hasattr(cherrypy, '_cache'): - # Make a process-wide Cache object. - cherrypy._cache = kwargs.pop('cache_class', MemoryCache)() - - # Take all remaining kwargs and set them on the Cache object. - for k, v in kwargs.items(): - setattr(cherrypy._cache, k, v) - cherrypy._cache.debug = debug - - # POST, PUT, DELETE should invalidate (delete) the cached copy. - # See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.10. - if request.method in invalid_methods: - if debug: - cherrypy.log('request.method %r in invalid_methods %r' % - (request.method, invalid_methods), 'TOOLS.CACHING') - cherrypy._cache.delete() - request.cached = False - request.cacheable = False - return False - - if 'no-cache' in [e.value for e in request.headers.elements('Pragma')]: - request.cached = False - request.cacheable = True - return False - - cache_data = cherrypy._cache.get() - request.cached = bool(cache_data) - request.cacheable = not request.cached - if request.cached: - # Serve the cached copy. - max_age = cherrypy._cache.delay - for v in [e.value for e in request.headers.elements('Cache-Control')]: - atoms = v.split('=', 1) - directive = atoms.pop(0) - if directive == 'max-age': - if len(atoms) != 1 or not atoms[0].isdigit(): - raise cherrypy.HTTPError( - 400, 'Invalid Cache-Control header') - max_age = int(atoms[0]) - break - elif directive == 'no-cache': - if debug: - cherrypy.log( - 'Ignoring cache due to Cache-Control: no-cache', - 'TOOLS.CACHING') - request.cached = False - request.cacheable = True - return False - - if debug: - cherrypy.log('Reading response from cache', 'TOOLS.CACHING') - s, h, b, create_time = cache_data - age = int(response.time - create_time) - if (age > max_age): - if debug: - cherrypy.log('Ignoring cache due to age > %d' % max_age, - 'TOOLS.CACHING') - request.cached = False - request.cacheable = True - return False - - # Copy the response headers. See - # https://github.com/cherrypy/cherrypy/issues/721. - response.headers = rh = httputil.HeaderMap() - for k in h: - dict.__setitem__(rh, k, dict.__getitem__(h, k)) - - # Add the required Age header - response.headers['Age'] = str(age) - - try: - # Note that validate_since depends on a Last-Modified header; - # this was put into the cached copy, and should have been - # resurrected just above (response.headers = cache_data[1]). - cptools.validate_since() - except cherrypy.HTTPRedirect: - x = sys.exc_info()[1] - if x.status == 304: - cherrypy._cache.tot_non_modified += 1 - raise - - # serve it & get out from the request - response.status = s - response.body = b - else: - if debug: - cherrypy.log('request is not cached', 'TOOLS.CACHING') - return request.cached - - -def tee_output(): - """Tee response output to cache storage. Internal.""" - # Used by CachingTool by attaching to request.hooks - - request = cherrypy.serving.request - if 'no-store' in request.headers.values('Cache-Control'): - return - - def tee(body): - """Tee response.body into a list.""" - if ('no-cache' in response.headers.values('Pragma') or - 'no-store' in response.headers.values('Cache-Control')): - for chunk in body: - yield chunk - return - - output = [] - for chunk in body: - output.append(chunk) - yield chunk - - # Save the cache data, but only if the body isn't empty. - # e.g. a 304 Not Modified on a static file response will - # have an empty body. - # If the body is empty, delete the cache because it - # contains a stale Threading._Event object that will - # stall all consecutive requests until the _Event times - # out - body = b''.join(output) - if not body: - cherrypy._cache.delete() - else: - cherrypy._cache.put((response.status, response.headers or {}, - body, response.time), len(body)) - - response = cherrypy.serving.response - response.body = tee(response.body) - - -def expires(secs=0, force=False, debug=False): - """Tool for influencing cache mechanisms using the 'Expires' header. - - secs - Must be either an int or a datetime.timedelta, and indicates the - number of seconds between response.time and when the response should - expire. The 'Expires' header will be set to response.time + secs. - If secs is zero, the 'Expires' header is set one year in the past, and - the following "cache prevention" headers are also set: - - * Pragma: no-cache - * Cache-Control': no-cache, must-revalidate - - force - If False, the following headers are checked: - - * Etag - * Last-Modified - * Age - * Expires - - If any are already present, none of the above response headers are set. - - """ - - response = cherrypy.serving.response - headers = response.headers - - cacheable = False - if not force: - # some header names that indicate that the response can be cached - for indicator in ('Etag', 'Last-Modified', 'Age', 'Expires'): - if indicator in headers: - cacheable = True - break - - if not cacheable and not force: - if debug: - cherrypy.log('request is not cacheable', 'TOOLS.EXPIRES') - else: - if debug: - cherrypy.log('request is cacheable', 'TOOLS.EXPIRES') - if isinstance(secs, datetime.timedelta): - secs = (86400 * secs.days) + secs.seconds - - if secs == 0: - if force or ('Pragma' not in headers): - headers['Pragma'] = 'no-cache' - if cherrypy.serving.request.protocol >= (1, 1): - if force or 'Cache-Control' not in headers: - headers['Cache-Control'] = 'no-cache, must-revalidate' - # Set an explicit Expires date in the past. - expiry = httputil.HTTPDate(1169942400.0) - else: - expiry = httputil.HTTPDate(response.time + secs) - if force or 'Expires' not in headers: - headers['Expires'] = expiry diff --git a/lib/cherrypy/lib/covercp.py b/lib/cherrypy/lib/covercp.py deleted file mode 100644 index 6c3871fc..00000000 --- a/lib/cherrypy/lib/covercp.py +++ /dev/null @@ -1,391 +0,0 @@ -"""Code-coverage tools for CherryPy. - -To use this module, or the coverage tools in the test suite, -you need to download 'coverage.py', either Gareth Rees' `original -implementation `_ -or Ned Batchelder's `enhanced version: -`_ - -To turn on coverage tracing, use the following code:: - - cherrypy.engine.subscribe('start', covercp.start) - -DO NOT subscribe anything on the 'start_thread' channel, as previously -recommended. Calling start once in the main thread should be sufficient -to start coverage on all threads. Calling start again in each thread -effectively clears any coverage data gathered up to that point. - -Run your code, then use the ``covercp.serve()`` function to browse the -results in a web browser. If you run this module from the command line, -it will call ``serve()`` for you. -""" - -import re -import sys -import cgi -import os -import os.path -import urllib.parse - -import cherrypy - - -localFile = os.path.join(os.path.dirname(__file__), 'coverage.cache') - -the_coverage = None -try: - from coverage import coverage - the_coverage = coverage(data_file=localFile) - - def start(): - the_coverage.start() -except ImportError: - # Setting the_coverage to None will raise errors - # that need to be trapped downstream. - the_coverage = None - - import warnings - warnings.warn( - 'No code coverage will be performed; ' - 'coverage.py could not be imported.') - - def start(): - pass -start.priority = 20 - -TEMPLATE_MENU = """ - - CherryPy Coverage Menu - - - -

CherryPy Coverage

""" - -TEMPLATE_FORM = """ -
-
- - Show percentages -
- Hide files over - %%
- Exclude files matching
- -
- - -
-
""" - -TEMPLATE_FRAMESET = """ -CherryPy coverage data - - - - - -""" - -TEMPLATE_COVERAGE = """ - - Coverage for %(name)s - - - -

%(name)s

-

%(fullpath)s

-

Coverage: %(pc)s%%

""" - -TEMPLATE_LOC_COVERED = """ - %s  - %s -\n""" -TEMPLATE_LOC_NOT_COVERED = """ - %s  - %s -\n""" -TEMPLATE_LOC_EXCLUDED = """ - %s  - %s -\n""" - -TEMPLATE_ITEM = ( - "%s%s%s\n" -) - - -def _percent(statements, missing): - s = len(statements) - e = s - len(missing) - if s > 0: - return int(round(100.0 * e / s)) - return 0 - - -def _show_branch(root, base, path, pct=0, showpct=False, exclude='', - coverage=the_coverage): - - # Show the directory name and any of our children - dirs = [k for k, v in root.items() if v] - dirs.sort() - for name in dirs: - newpath = os.path.join(path, name) - - if newpath.lower().startswith(base): - relpath = newpath[len(base):] - yield '| ' * relpath.count(os.sep) - yield ( - "%s\n" % - (newpath, urllib.parse.quote_plus(exclude), name) - ) - - for chunk in _show_branch( - root[name], base, newpath, pct, showpct, - exclude, coverage=coverage - ): - yield chunk - - # Now list the files - if path.lower().startswith(base): - relpath = path[len(base):] - files = [k for k, v in root.items() if not v] - files.sort() - for name in files: - newpath = os.path.join(path, name) - - pc_str = '' - if showpct: - try: - _, statements, _, missing, _ = coverage.analysis2(newpath) - except Exception: - # Yes, we really want to pass on all errors. - pass - else: - pc = _percent(statements, missing) - pc_str = ('%3d%% ' % pc).replace(' ', ' ') - if pc < float(pct) or pc == -1: - pc_str = "%s" % pc_str - else: - pc_str = "%s" % pc_str - - yield TEMPLATE_ITEM % ('| ' * (relpath.count(os.sep) + 1), - pc_str, newpath, name) - - -def _skip_file(path, exclude): - if exclude: - return bool(re.search(exclude, path)) - - -def _graft(path, tree): - d = tree - - p = path - atoms = [] - while True: - p, tail = os.path.split(p) - if not tail: - break - atoms.append(tail) - atoms.append(p) - if p != '/': - atoms.append('/') - - atoms.reverse() - for node in atoms: - if node: - d = d.setdefault(node, {}) - - -def get_tree(base, exclude, coverage=the_coverage): - """Return covered module names as a nested dict.""" - tree = {} - runs = coverage.data.executed_files() - for path in runs: - if not _skip_file(path, exclude) and not os.path.isdir(path): - _graft(path, tree) - return tree - - -class CoverStats(object): - - def __init__(self, coverage, root=None): - self.coverage = coverage - if root is None: - # Guess initial depth. Files outside this path will not be - # reachable from the web interface. - root = os.path.dirname(cherrypy.__file__) - self.root = root - - @cherrypy.expose - def index(self): - return TEMPLATE_FRAMESET % self.root.lower() - - @cherrypy.expose - def menu(self, base='/', pct='50', showpct='', - exclude=r'python\d\.\d|test|tut\d|tutorial'): - - # The coverage module uses all-lower-case names. - base = base.lower().rstrip(os.sep) - - yield TEMPLATE_MENU - yield TEMPLATE_FORM % locals() - - # Start by showing links for parent paths - yield "
" - path = '' - atoms = base.split(os.sep) - atoms.pop() - for atom in atoms: - path += atom + os.sep - yield ("%s %s" - % (path, urllib.parse.quote_plus(exclude), atom, os.sep)) - yield '
' - - yield "
" - - # Then display the tree - tree = get_tree(base, exclude, self.coverage) - if not tree: - yield '

No modules covered.

' - else: - for chunk in _show_branch(tree, base, '/', pct, - showpct == 'checked', exclude, - coverage=self.coverage): - yield chunk - - yield '
' - yield '' - - def annotated_file(self, filename, statements, excluded, missing): - with open(filename, 'r') as source: - lines = source.readlines() - buffer = [] - for lineno, line in enumerate(lines): - lineno += 1 - line = line.strip('\n\r') - empty_the_buffer = True - if lineno in excluded: - template = TEMPLATE_LOC_EXCLUDED - elif lineno in missing: - template = TEMPLATE_LOC_NOT_COVERED - elif lineno in statements: - template = TEMPLATE_LOC_COVERED - else: - empty_the_buffer = False - buffer.append((lineno, line)) - if empty_the_buffer: - for lno, pastline in buffer: - yield template % (lno, cgi.escape(pastline)) - buffer = [] - yield template % (lineno, cgi.escape(line)) - - @cherrypy.expose - def report(self, name): - filename, statements, excluded, missing, _ = self.coverage.analysis2( - name) - pc = _percent(statements, missing) - yield TEMPLATE_COVERAGE % dict(name=os.path.basename(name), - fullpath=name, - pc=pc) - yield '\n' - for line in self.annotated_file(filename, statements, excluded, - missing): - yield line - yield '
' - yield '' - yield '' - - -def serve(path=localFile, port=8080, root=None): - if coverage is None: - raise ImportError('The coverage module could not be imported.') - from coverage import coverage - cov = coverage(data_file=path) - cov.load() - - cherrypy.config.update({'server.socket_port': int(port), - 'server.thread_pool': 10, - 'environment': 'production', - }) - cherrypy.quickstart(CoverStats(cov, root)) - - -if __name__ == '__main__': - serve(*tuple(sys.argv[1:])) diff --git a/lib/cherrypy/lib/cpstats.py b/lib/cherrypy/lib/cpstats.py deleted file mode 100644 index 111af063..00000000 --- a/lib/cherrypy/lib/cpstats.py +++ /dev/null @@ -1,694 +0,0 @@ -"""CPStats, a package for collecting and reporting on program statistics. - -Overview -======== - -Statistics about program operation are an invaluable monitoring and debugging -tool. Unfortunately, the gathering and reporting of these critical values is -usually ad-hoc. This package aims to add a centralized place for gathering -statistical performance data, a structure for recording that data which -provides for extrapolation of that data into more useful information, -and a method of serving that data to both human investigators and -monitoring software. Let's examine each of those in more detail. - -Data Gathering --------------- - -Just as Python's `logging` module provides a common importable for gathering -and sending messages, performance statistics would benefit from a similar -common mechanism, and one that does *not* require each package which wishes -to collect stats to import a third-party module. Therefore, we choose to -re-use the `logging` module by adding a `statistics` object to it. - -That `logging.statistics` object is a nested dict. It is not a custom class, -because that would: - - 1. require libraries and applications to import a third-party module in - order to participate - 2. inhibit innovation in extrapolation approaches and in reporting tools, and - 3. be slow. - -There are, however, some specifications regarding the structure of the dict.:: - - { - +----"SQLAlchemy": { - | "Inserts": 4389745, - | "Inserts per Second": - | lambda s: s["Inserts"] / (time() - s["Start"]), - | C +---"Table Statistics": { - | o | "widgets": {-----------+ - N | l | "Rows": 1.3M, | Record - a | l | "Inserts": 400, | - m | e | },---------------------+ - e | c | "froobles": { - s | t | "Rows": 7845, - p | i | "Inserts": 0, - a | o | }, - c | n +---}, - e | "Slow Queries": - | [{"Query": "SELECT * FROM widgets;", - | "Processing Time": 47.840923343, - | }, - | ], - +----}, - } - -The `logging.statistics` dict has four levels. The topmost level is nothing -more than a set of names to introduce modularity, usually along the lines of -package names. If the SQLAlchemy project wanted to participate, for example, -it might populate the item `logging.statistics['SQLAlchemy']`, whose value -would be a second-layer dict we call a "namespace". Namespaces help multiple -packages to avoid collisions over key names, and make reports easier to read, -to boot. The maintainers of SQLAlchemy should feel free to use more than one -namespace if needed (such as 'SQLAlchemy ORM'). Note that there are no case -or other syntax constraints on the namespace names; they should be chosen -to be maximally readable by humans (neither too short nor too long). - -Each namespace, then, is a dict of named statistical values, such as -'Requests/sec' or 'Uptime'. You should choose names which will look -good on a report: spaces and capitalization are just fine. - -In addition to scalars, values in a namespace MAY be a (third-layer) -dict, or a list, called a "collection". For example, the CherryPy -:class:`StatsTool` keeps track of what each request is doing (or has most -recently done) in a 'Requests' collection, where each key is a thread ID; each -value in the subdict MUST be a fourth dict (whew!) of statistical data about -each thread. We call each subdict in the collection a "record". Similarly, -the :class:`StatsTool` also keeps a list of slow queries, where each record -contains data about each slow query, in order. - -Values in a namespace or record may also be functions, which brings us to: - -Extrapolation -------------- - -The collection of statistical data needs to be fast, as close to unnoticeable -as possible to the host program. That requires us to minimize I/O, for example, -but in Python it also means we need to minimize function calls. So when you -are designing your namespace and record values, try to insert the most basic -scalar values you already have on hand. - -When it comes time to report on the gathered data, however, we usually have -much more freedom in what we can calculate. Therefore, whenever reporting -tools (like the provided :class:`StatsPage` CherryPy class) fetch the contents -of `logging.statistics` for reporting, they first call -`extrapolate_statistics` (passing the whole `statistics` dict as the only -argument). This makes a deep copy of the statistics dict so that the -reporting tool can both iterate over it and even change it without harming -the original. But it also expands any functions in the dict by calling them. -For example, you might have a 'Current Time' entry in the namespace with the -value "lambda scope: time.time()". The "scope" parameter is the current -namespace dict (or record, if we're currently expanding one of those -instead), allowing you access to existing static entries. If you're truly -evil, you can even modify more than one entry at a time. - -However, don't try to calculate an entry and then use its value in further -extrapolations; the order in which the functions are called is not guaranteed. -This can lead to a certain amount of duplicated work (or a redesign of your -schema), but that's better than complicating the spec. - -After the whole thing has been extrapolated, it's time for: - -Reporting ---------- - -The :class:`StatsPage` class grabs the `logging.statistics` dict, extrapolates -it all, and then transforms it to HTML for easy viewing. Each namespace gets -its own header and attribute table, plus an extra table for each collection. -This is NOT part of the statistics specification; other tools can format how -they like. - -You can control which columns are output and how they are formatted by updating -StatsPage.formatting, which is a dict that mirrors the keys and nesting of -`logging.statistics`. The difference is that, instead of data values, it has -formatting values. Use None for a given key to indicate to the StatsPage that a -given column should not be output. Use a string with formatting -(such as '%.3f') to interpolate the value(s), or use a callable (such as -lambda v: v.isoformat()) for more advanced formatting. Any entry which is not -mentioned in the formatting dict is output unchanged. - -Monitoring ----------- - -Although the HTML output takes pains to assign unique id's to each with -statistical data, you're probably better off fetching /cpstats/data, which -outputs the whole (extrapolated) `logging.statistics` dict in JSON format. -That is probably easier to parse, and doesn't have any formatting controls, -so you get the "original" data in a consistently-serialized format. -Note: there's no treatment yet for datetime objects. Try time.time() instead -for now if you can. Nagios will probably thank you. - -Turning Collection Off ----------------------- - -It is recommended each namespace have an "Enabled" item which, if False, -stops collection (but not reporting) of statistical data. Applications -SHOULD provide controls to pause and resume collection by setting these -entries to False or True, if present. - - -Usage -===== - -To collect statistics on CherryPy applications:: - - from cherrypy.lib import cpstats - appconfig['/']['tools.cpstats.on'] = True - -To collect statistics on your own code:: - - import logging - # Initialize the repository - if not hasattr(logging, 'statistics'): logging.statistics = {} - # Initialize my namespace - mystats = logging.statistics.setdefault('My Stuff', {}) - # Initialize my namespace's scalars and collections - mystats.update({ - 'Enabled': True, - 'Start Time': time.time(), - 'Important Events': 0, - 'Events/Second': lambda s: ( - (s['Important Events'] / (time.time() - s['Start Time']))), - }) - ... - for event in events: - ... - # Collect stats - if mystats.get('Enabled', False): - mystats['Important Events'] += 1 - -To report statistics:: - - root.cpstats = cpstats.StatsPage() - -To format statistics reports:: - - See 'Reporting', above. - -""" - -import logging -import os -import sys -import threading -import time - -import cherrypy -from cherrypy._json import json - -# ------------------------------- Statistics -------------------------------- # - -if not hasattr(logging, 'statistics'): - logging.statistics = {} - - -def extrapolate_statistics(scope): - """Return an extrapolated copy of the given scope.""" - c = {} - for k, v in scope.copy().items(): - if isinstance(v, dict): - v = extrapolate_statistics(v) - elif isinstance(v, (list, tuple)): - v = [extrapolate_statistics(record) for record in v] - elif hasattr(v, '__call__'): - v = v(scope) - c[k] = v - return c - - -# -------------------- CherryPy Applications Statistics --------------------- # - -appstats = logging.statistics.setdefault('CherryPy Applications', {}) -appstats.update({ - 'Enabled': True, - 'Bytes Read/Request': lambda s: ( - s['Total Requests'] and - (s['Total Bytes Read'] / float(s['Total Requests'])) or - 0.0 - ), - 'Bytes Read/Second': lambda s: s['Total Bytes Read'] / s['Uptime'](s), - 'Bytes Written/Request': lambda s: ( - s['Total Requests'] and - (s['Total Bytes Written'] / float(s['Total Requests'])) or - 0.0 - ), - 'Bytes Written/Second': lambda s: ( - s['Total Bytes Written'] / s['Uptime'](s) - ), - 'Current Time': lambda s: time.time(), - 'Current Requests': 0, - 'Requests/Second': lambda s: float(s['Total Requests']) / s['Uptime'](s), - 'Server Version': cherrypy.__version__, - 'Start Time': time.time(), - 'Total Bytes Read': 0, - 'Total Bytes Written': 0, - 'Total Requests': 0, - 'Total Time': 0, - 'Uptime': lambda s: time.time() - s['Start Time'], - 'Requests': {}, -}) - - -def proc_time(s): - return time.time() - s['Start Time'] - - -class ByteCountWrapper(object): - - """Wraps a file-like object, counting the number of bytes read.""" - - def __init__(self, rfile): - self.rfile = rfile - self.bytes_read = 0 - - def read(self, size=-1): - data = self.rfile.read(size) - self.bytes_read += len(data) - return data - - def readline(self, size=-1): - data = self.rfile.readline(size) - self.bytes_read += len(data) - return data - - def readlines(self, sizehint=0): - # Shamelessly stolen from StringIO - total = 0 - lines = [] - line = self.readline() - while line: - lines.append(line) - total += len(line) - if 0 < sizehint <= total: - break - line = self.readline() - return lines - - def close(self): - self.rfile.close() - - def __iter__(self): - return self - - def next(self): - data = self.rfile.next() - self.bytes_read += len(data) - return data - - -def average_uriset_time(s): - return s['Count'] and (s['Sum'] / s['Count']) or 0 - - -def _get_threading_ident(): - if sys.version_info >= (3, 3): - return threading.get_ident() - return threading._get_ident() - - -class StatsTool(cherrypy.Tool): - - """Record various information about the current request.""" - - def __init__(self): - cherrypy.Tool.__init__(self, 'on_end_request', self.record_stop) - - def _setup(self): - """Hook this tool into cherrypy.request. - - The standard CherryPy request object will automatically call this - method when the tool is "turned on" in config. - """ - if appstats.get('Enabled', False): - cherrypy.Tool._setup(self) - self.record_start() - - def record_start(self): - """Record the beginning of a request.""" - request = cherrypy.serving.request - if not hasattr(request.rfile, 'bytes_read'): - request.rfile = ByteCountWrapper(request.rfile) - request.body.fp = request.rfile - - r = request.remote - - appstats['Current Requests'] += 1 - appstats['Total Requests'] += 1 - appstats['Requests'][_get_threading_ident()] = { - 'Bytes Read': None, - 'Bytes Written': None, - # Use a lambda so the ip gets updated by tools.proxy later - 'Client': lambda s: '%s:%s' % (r.ip, r.port), - 'End Time': None, - 'Processing Time': proc_time, - 'Request-Line': request.request_line, - 'Response Status': None, - 'Start Time': time.time(), - } - - def record_stop( - self, uriset=None, slow_queries=1.0, slow_queries_count=100, - debug=False, **kwargs): - """Record the end of a request.""" - resp = cherrypy.serving.response - w = appstats['Requests'][_get_threading_ident()] - - r = cherrypy.request.rfile.bytes_read - w['Bytes Read'] = r - appstats['Total Bytes Read'] += r - - if resp.stream: - w['Bytes Written'] = 'chunked' - else: - cl = int(resp.headers.get('Content-Length', 0)) - w['Bytes Written'] = cl - appstats['Total Bytes Written'] += cl - - w['Response Status'] = \ - getattr(resp, 'output_status', resp.status).decode() - - w['End Time'] = time.time() - p = w['End Time'] - w['Start Time'] - w['Processing Time'] = p - appstats['Total Time'] += p - - appstats['Current Requests'] -= 1 - - if debug: - cherrypy.log('Stats recorded: %s' % repr(w), 'TOOLS.CPSTATS') - - if uriset: - rs = appstats.setdefault('URI Set Tracking', {}) - r = rs.setdefault(uriset, { - 'Min': None, 'Max': None, 'Count': 0, 'Sum': 0, - 'Avg': average_uriset_time}) - if r['Min'] is None or p < r['Min']: - r['Min'] = p - if r['Max'] is None or p > r['Max']: - r['Max'] = p - r['Count'] += 1 - r['Sum'] += p - - if slow_queries and p > slow_queries: - sq = appstats.setdefault('Slow Queries', []) - sq.append(w.copy()) - if len(sq) > slow_queries_count: - sq.pop(0) - - -cherrypy.tools.cpstats = StatsTool() - - -# ---------------------- CherryPy Statistics Reporting ---------------------- # - -thisdir = os.path.abspath(os.path.dirname(__file__)) - -missing = object() - - -def locale_date(v): - return time.strftime('%c', time.gmtime(v)) - - -def iso_format(v): - return time.strftime('%Y-%m-%d %H:%M:%S', time.gmtime(v)) - - -def pause_resume(ns): - def _pause_resume(enabled): - pause_disabled = '' - resume_disabled = '' - if enabled: - resume_disabled = 'disabled="disabled" ' - else: - pause_disabled = 'disabled="disabled" ' - return """ -
- - -
-
- - -
- """ % (ns, pause_disabled, ns, resume_disabled) - return _pause_resume - - -class StatsPage(object): - - formatting = { - 'CherryPy Applications': { - 'Enabled': pause_resume('CherryPy Applications'), - 'Bytes Read/Request': '%.3f', - 'Bytes Read/Second': '%.3f', - 'Bytes Written/Request': '%.3f', - 'Bytes Written/Second': '%.3f', - 'Current Time': iso_format, - 'Requests/Second': '%.3f', - 'Start Time': iso_format, - 'Total Time': '%.3f', - 'Uptime': '%.3f', - 'Slow Queries': { - 'End Time': None, - 'Processing Time': '%.3f', - 'Start Time': iso_format, - }, - 'URI Set Tracking': { - 'Avg': '%.3f', - 'Max': '%.3f', - 'Min': '%.3f', - 'Sum': '%.3f', - }, - 'Requests': { - 'Bytes Read': '%s', - 'Bytes Written': '%s', - 'End Time': None, - 'Processing Time': '%.3f', - 'Start Time': None, - }, - }, - 'CherryPy WSGIServer': { - 'Enabled': pause_resume('CherryPy WSGIServer'), - 'Connections/second': '%.3f', - 'Start time': iso_format, - }, - } - - @cherrypy.expose - def index(self): - # Transform the raw data into pretty output for HTML - yield """ - - - Statistics - - - -""" - for title, scalars, collections in self.get_namespaces(): - yield """ -

%s

- - - -""" % title - for i, (key, value) in enumerate(scalars): - colnum = i % 3 - if colnum == 0: - yield """ - """ - yield ( - """ - """ % - vars() - ) - if colnum == 2: - yield """ - """ - - if colnum == 0: - yield """ - - - """ - elif colnum == 1: - yield """ - - """ - yield """ - -
%(key)s%(value)s
""" - - for subtitle, headers, subrows in collections: - yield """ -

%s

- - - """ % subtitle - for key in headers: - yield """ - """ % key - yield """ - - - """ - for subrow in subrows: - yield """ - """ - for value in subrow: - yield """ - """ % value - yield """ - """ - yield """ - -
%s
%s
""" - yield """ - - -""" - - def get_namespaces(self): - """Yield (title, scalars, collections) for each namespace.""" - s = extrapolate_statistics(logging.statistics) - for title, ns in sorted(s.items()): - scalars = [] - collections = [] - ns_fmt = self.formatting.get(title, {}) - for k, v in sorted(ns.items()): - fmt = ns_fmt.get(k, {}) - if isinstance(v, dict): - headers, subrows = self.get_dict_collection(v, fmt) - collections.append((k, ['ID'] + headers, subrows)) - elif isinstance(v, (list, tuple)): - headers, subrows = self.get_list_collection(v, fmt) - collections.append((k, headers, subrows)) - else: - format = ns_fmt.get(k, missing) - if format is None: - # Don't output this column. - continue - if hasattr(format, '__call__'): - v = format(v) - elif format is not missing: - v = format % v - scalars.append((k, v)) - yield title, scalars, collections - - def get_dict_collection(self, v, formatting): - """Return ([headers], [rows]) for the given collection.""" - # E.g., the 'Requests' dict. - headers = [] - vals = v.values() - for record in vals: - for k3 in record: - format = formatting.get(k3, missing) - if format is None: - # Don't output this column. - continue - if k3 not in headers: - headers.append(k3) - headers.sort() - - subrows = [] - for k2, record in sorted(v.items()): - subrow = [k2] - for k3 in headers: - v3 = record.get(k3, '') - format = formatting.get(k3, missing) - if format is None: - # Don't output this column. - continue - if hasattr(format, '__call__'): - v3 = format(v3) - elif format is not missing: - v3 = format % v3 - subrow.append(v3) - subrows.append(subrow) - - return headers, subrows - - def get_list_collection(self, v, formatting): - """Return ([headers], [subrows]) for the given collection.""" - # E.g., the 'Slow Queries' list. - headers = [] - for record in v: - for k3 in record: - format = formatting.get(k3, missing) - if format is None: - # Don't output this column. - continue - if k3 not in headers: - headers.append(k3) - headers.sort() - - subrows = [] - for record in v: - subrow = [] - for k3 in headers: - v3 = record.get(k3, '') - format = formatting.get(k3, missing) - if format is None: - # Don't output this column. - continue - if hasattr(format, '__call__'): - v3 = format(v3) - elif format is not missing: - v3 = format % v3 - subrow.append(v3) - subrows.append(subrow) - - return headers, subrows - - if json is not None: - @cherrypy.expose - def data(self): - s = extrapolate_statistics(logging.statistics) - cherrypy.response.headers['Content-Type'] = 'application/json' - return json.dumps(s, sort_keys=True, indent=4).encode('utf-8') - - @cherrypy.expose - def pause(self, namespace): - logging.statistics.get(namespace, {})['Enabled'] = False - raise cherrypy.HTTPRedirect('./') - pause.cp_config = {'tools.allow.on': True, - 'tools.allow.methods': ['POST']} - - @cherrypy.expose - def resume(self, namespace): - logging.statistics.get(namespace, {})['Enabled'] = True - raise cherrypy.HTTPRedirect('./') - resume.cp_config = {'tools.allow.on': True, - 'tools.allow.methods': ['POST']} diff --git a/lib/cherrypy/lib/cptools.py b/lib/cherrypy/lib/cptools.py deleted file mode 100644 index 613a8995..00000000 --- a/lib/cherrypy/lib/cptools.py +++ /dev/null @@ -1,637 +0,0 @@ -"""Functions for builtin CherryPy tools.""" - -import logging -import re -from hashlib import md5 -import urllib.parse - -import cherrypy -from cherrypy._cpcompat import text_or_bytes -from cherrypy.lib import httputil as _httputil -from cherrypy.lib import is_iterator - - -# Conditional HTTP request support # - -def validate_etags(autotags=False, debug=False): - """Validate the current ETag against If-Match, If-None-Match headers. - - If autotags is True, an ETag response-header value will be provided - from an MD5 hash of the response body (unless some other code has - already provided an ETag header). If False (the default), the ETag - will not be automatic. - - WARNING: the autotags feature is not designed for URL's which allow - methods other than GET. For example, if a POST to the same URL returns - no content, the automatic ETag will be incorrect, breaking a fundamental - use for entity tags in a possibly destructive fashion. Likewise, if you - raise 304 Not Modified, the response body will be empty, the ETag hash - will be incorrect, and your application will break. - See :rfc:`2616` Section 14.24. - """ - response = cherrypy.serving.response - - # Guard against being run twice. - if hasattr(response, 'ETag'): - return - - status, reason, msg = _httputil.valid_status(response.status) - - etag = response.headers.get('ETag') - - # Automatic ETag generation. See warning in docstring. - if etag: - if debug: - cherrypy.log('ETag already set: %s' % etag, 'TOOLS.ETAGS') - elif not autotags: - if debug: - cherrypy.log('Autotags off', 'TOOLS.ETAGS') - elif status != 200: - if debug: - cherrypy.log('Status not 200', 'TOOLS.ETAGS') - else: - etag = response.collapse_body() - etag = '"%s"' % md5(etag).hexdigest() - if debug: - cherrypy.log('Setting ETag: %s' % etag, 'TOOLS.ETAGS') - response.headers['ETag'] = etag - - response.ETag = etag - - # "If the request would, without the If-Match header field, result in - # anything other than a 2xx or 412 status, then the If-Match header - # MUST be ignored." - if debug: - cherrypy.log('Status: %s' % status, 'TOOLS.ETAGS') - if status >= 200 and status <= 299: - request = cherrypy.serving.request - - conditions = request.headers.elements('If-Match') or [] - conditions = [str(x) for x in conditions] - if debug: - cherrypy.log('If-Match conditions: %s' % repr(conditions), - 'TOOLS.ETAGS') - if conditions and not (conditions == ['*'] or etag in conditions): - raise cherrypy.HTTPError(412, 'If-Match failed: ETag %r did ' - 'not match %r' % (etag, conditions)) - - conditions = request.headers.elements('If-None-Match') or [] - conditions = [str(x) for x in conditions] - if debug: - cherrypy.log('If-None-Match conditions: %s' % repr(conditions), - 'TOOLS.ETAGS') - if conditions == ['*'] or etag in conditions: - if debug: - cherrypy.log('request.method: %s' % - request.method, 'TOOLS.ETAGS') - if request.method in ('GET', 'HEAD'): - raise cherrypy.HTTPRedirect([], 304) - else: - raise cherrypy.HTTPError(412, 'If-None-Match failed: ETag %r ' - 'matched %r' % (etag, conditions)) - - -def validate_since(): - """Validate the current Last-Modified against If-Modified-Since headers. - - If no code has set the Last-Modified response header, then no validation - will be performed. - """ - response = cherrypy.serving.response - lastmod = response.headers.get('Last-Modified') - if lastmod: - status, reason, msg = _httputil.valid_status(response.status) - - request = cherrypy.serving.request - - since = request.headers.get('If-Unmodified-Since') - if since and since != lastmod: - if (status >= 200 and status <= 299) or status == 412: - raise cherrypy.HTTPError(412) - - since = request.headers.get('If-Modified-Since') - if since and since == lastmod: - if (status >= 200 and status <= 299) or status == 304: - if request.method in ('GET', 'HEAD'): - raise cherrypy.HTTPRedirect([], 304) - else: - raise cherrypy.HTTPError(412) - - -# Tool code # - -def allow(methods=None, debug=False): - """Raise 405 if request.method not in methods (default ['GET', 'HEAD']). - - The given methods are case-insensitive, and may be in any order. - If only one method is allowed, you may supply a single string; - if more than one, supply a list of strings. - - Regardless of whether the current method is allowed or not, this - also emits an 'Allow' response header, containing the given methods. - """ - if not isinstance(methods, (tuple, list)): - methods = [methods] - methods = [m.upper() for m in methods if m] - if not methods: - methods = ['GET', 'HEAD'] - elif 'GET' in methods and 'HEAD' not in methods: - methods.append('HEAD') - - cherrypy.response.headers['Allow'] = ', '.join(methods) - if cherrypy.request.method not in methods: - if debug: - cherrypy.log('request.method %r not in methods %r' % - (cherrypy.request.method, methods), 'TOOLS.ALLOW') - raise cherrypy.HTTPError(405) - else: - if debug: - cherrypy.log('request.method %r in methods %r' % - (cherrypy.request.method, methods), 'TOOLS.ALLOW') - - -def proxy(base=None, local='X-Forwarded-Host', remote='X-Forwarded-For', - scheme='X-Forwarded-Proto', debug=False): - """Change the base URL (scheme://host[:port][/path]). - - For running a CP server behind Apache, lighttpd, or other HTTP server. - - For Apache and lighttpd, you should leave the 'local' argument at the - default value of 'X-Forwarded-Host'. For Squid, you probably want to set - tools.proxy.local = 'Origin'. - - If you want the new request.base to include path info (not just the host), - you must explicitly set base to the full base path, and ALSO set 'local' - to '', so that the X-Forwarded-Host request header (which never includes - path info) does not override it. Regardless, the value for 'base' MUST - NOT end in a slash. - - cherrypy.request.remote.ip (the IP address of the client) will be - rewritten if the header specified by the 'remote' arg is valid. - By default, 'remote' is set to 'X-Forwarded-For'. If you do not - want to rewrite remote.ip, set the 'remote' arg to an empty string. - """ - - request = cherrypy.serving.request - - if scheme: - s = request.headers.get(scheme, None) - if debug: - cherrypy.log('Testing scheme %r:%r' % (scheme, s), 'TOOLS.PROXY') - if s == 'on' and 'ssl' in scheme.lower(): - # This handles e.g. webfaction's 'X-Forwarded-Ssl: on' header - scheme = 'https' - else: - # This is for lighttpd/pound/Mongrel's 'X-Forwarded-Proto: https' - scheme = s - if not scheme: - scheme = request.base[:request.base.find('://')] - - if local: - lbase = request.headers.get(local, None) - if debug: - cherrypy.log('Testing local %r:%r' % (local, lbase), 'TOOLS.PROXY') - if lbase is not None: - base = lbase.split(',')[0] - if not base: - default = urllib.parse.urlparse(request.base).netloc - base = request.headers.get('Host', default) - - if base.find('://') == -1: - # add http:// or https:// if needed - base = scheme + '://' + base - - request.base = base - - if remote: - xff = request.headers.get(remote) - if debug: - cherrypy.log('Testing remote %r:%r' % (remote, xff), 'TOOLS.PROXY') - if xff: - if remote == 'X-Forwarded-For': - # Grab the first IP in a comma-separated list. Ref #1268. - xff = next(ip.strip() for ip in xff.split(',')) - request.remote.ip = xff - - -def ignore_headers(headers=('Range',), debug=False): - """Delete request headers whose field names are included in 'headers'. - - This is a useful tool for working behind certain HTTP servers; - for example, Apache duplicates the work that CP does for 'Range' - headers, and will doubly-truncate the response. - """ - request = cherrypy.serving.request - for name in headers: - if name in request.headers: - if debug: - cherrypy.log('Ignoring request header %r' % name, - 'TOOLS.IGNORE_HEADERS') - del request.headers[name] - - -def response_headers(headers=None, debug=False): - """Set headers on the response.""" - if debug: - cherrypy.log('Setting response headers: %s' % repr(headers), - 'TOOLS.RESPONSE_HEADERS') - for name, value in (headers or []): - cherrypy.serving.response.headers[name] = value - - -response_headers.failsafe = True - - -def referer(pattern, accept=True, accept_missing=False, error=403, - message='Forbidden Referer header.', debug=False): - """Raise HTTPError if Referer header does/does not match the given pattern. - - pattern - A regular expression pattern to test against the Referer. - - accept - If True, the Referer must match the pattern; if False, - the Referer must NOT match the pattern. - - accept_missing - If True, permit requests with no Referer header. - - error - The HTTP error code to return to the client on failure. - - message - A string to include in the response body on failure. - - """ - try: - ref = cherrypy.serving.request.headers['Referer'] - match = bool(re.match(pattern, ref)) - if debug: - cherrypy.log('Referer %r matches %r' % (ref, pattern), - 'TOOLS.REFERER') - if accept == match: - return - except KeyError: - if debug: - cherrypy.log('No Referer header', 'TOOLS.REFERER') - if accept_missing: - return - - raise cherrypy.HTTPError(error, message) - - -class SessionAuth(object): - - """Assert that the user is logged in.""" - - session_key = 'username' - debug = False - - def check_username_and_password(self, username, password): - pass - - def anonymous(self): - """Provide a temporary user name for anonymous users.""" - pass - - def on_login(self, username): - pass - - def on_logout(self, username): - pass - - def on_check(self, username): - pass - - def login_screen(self, from_page='..', username='', error_msg='', - **kwargs): - return (str(""" -Message: %(error_msg)s -
- Login: -
- Password: -
- -
- -
-""") % vars()).encode('utf-8') - - def do_login(self, username, password, from_page='..', **kwargs): - """Login. May raise redirect, or return True if request handled.""" - response = cherrypy.serving.response - error_msg = self.check_username_and_password(username, password) - if error_msg: - body = self.login_screen(from_page, username, error_msg) - response.body = body - if 'Content-Length' in response.headers: - # Delete Content-Length header so finalize() recalcs it. - del response.headers['Content-Length'] - return True - else: - cherrypy.serving.request.login = username - cherrypy.session[self.session_key] = username - self.on_login(username) - raise cherrypy.HTTPRedirect(from_page or '/') - - def do_logout(self, from_page='..', **kwargs): - """Logout. May raise redirect, or return True if request handled.""" - sess = cherrypy.session - username = sess.get(self.session_key) - sess[self.session_key] = None - if username: - cherrypy.serving.request.login = None - self.on_logout(username) - raise cherrypy.HTTPRedirect(from_page) - - def do_check(self): - """Assert username. Raise redirect, or return True if request handled. - """ - sess = cherrypy.session - request = cherrypy.serving.request - response = cherrypy.serving.response - - username = sess.get(self.session_key) - if not username: - sess[self.session_key] = username = self.anonymous() - self._debug_message('No session[username], trying anonymous') - if not username: - url = cherrypy.url(qs=request.query_string) - self._debug_message( - 'No username, routing to login_screen with from_page %(url)r', - locals(), - ) - response.body = self.login_screen(url) - if 'Content-Length' in response.headers: - # Delete Content-Length header so finalize() recalcs it. - del response.headers['Content-Length'] - return True - self._debug_message('Setting request.login to %(username)r', locals()) - request.login = username - self.on_check(username) - - def _debug_message(self, template, context={}): - if not self.debug: - return - cherrypy.log(template % context, 'TOOLS.SESSAUTH') - - def run(self): - request = cherrypy.serving.request - response = cherrypy.serving.response - - path = request.path_info - if path.endswith('login_screen'): - self._debug_message('routing %(path)r to login_screen', locals()) - response.body = self.login_screen() - return True - elif path.endswith('do_login'): - if request.method != 'POST': - response.headers['Allow'] = 'POST' - self._debug_message('do_login requires POST') - raise cherrypy.HTTPError(405) - self._debug_message('routing %(path)r to do_login', locals()) - return self.do_login(**request.params) - elif path.endswith('do_logout'): - if request.method != 'POST': - response.headers['Allow'] = 'POST' - raise cherrypy.HTTPError(405) - self._debug_message('routing %(path)r to do_logout', locals()) - return self.do_logout(**request.params) - else: - self._debug_message('No special path, running do_check') - return self.do_check() - - -def session_auth(**kwargs): - """Session authentication hook. - - Any attribute of the SessionAuth class may be overridden - via a keyword arg to this function: - - """ + '\n '.join( - '{!s}: {!s}'.format(k, type(getattr(SessionAuth, k)).__name__) - for k in dir(SessionAuth) - if not k.startswith('__') - ) - sa = SessionAuth() - for k, v in kwargs.items(): - setattr(sa, k, v) - return sa.run() - - -def log_traceback(severity=logging.ERROR, debug=False): - """Write the last error's traceback to the cherrypy error log.""" - cherrypy.log('', 'HTTP', severity=severity, traceback=True) - - -def log_request_headers(debug=False): - """Write request headers to the cherrypy error log.""" - h = [' %s: %s' % (k, v) for k, v in cherrypy.serving.request.header_list] - cherrypy.log('\nRequest Headers:\n' + '\n'.join(h), 'HTTP') - - -def log_hooks(debug=False): - """Write request.hooks to the cherrypy error log.""" - request = cherrypy.serving.request - - msg = [] - # Sort by the standard points if possible. - from cherrypy import _cprequest - points = _cprequest.hookpoints - for k in request.hooks.keys(): - if k not in points: - points.append(k) - - for k in points: - msg.append(' %s:' % k) - v = request.hooks.get(k, []) - v.sort() - for h in v: - msg.append(' %r' % h) - cherrypy.log('\nRequest Hooks for ' + cherrypy.url() + - ':\n' + '\n'.join(msg), 'HTTP') - - -def redirect(url='', internal=True, debug=False): - """Raise InternalRedirect or HTTPRedirect to the given url.""" - if debug: - cherrypy.log('Redirecting %sto: %s' % - ({True: 'internal ', False: ''}[internal], url), - 'TOOLS.REDIRECT') - if internal: - raise cherrypy.InternalRedirect(url) - else: - raise cherrypy.HTTPRedirect(url) - - -def trailing_slash(missing=True, extra=False, status=None, debug=False): - """Redirect if path_info has (missing|extra) trailing slash.""" - request = cherrypy.serving.request - pi = request.path_info - - if debug: - cherrypy.log('is_index: %r, missing: %r, extra: %r, path_info: %r' % - (request.is_index, missing, extra, pi), - 'TOOLS.TRAILING_SLASH') - if request.is_index is True: - if missing: - if not pi.endswith('/'): - new_url = cherrypy.url(pi + '/', request.query_string) - raise cherrypy.HTTPRedirect(new_url, status=status or 301) - elif request.is_index is False: - if extra: - # If pi == '/', don't redirect to ''! - if pi.endswith('/') and pi != '/': - new_url = cherrypy.url(pi[:-1], request.query_string) - raise cherrypy.HTTPRedirect(new_url, status=status or 301) - - -def flatten(debug=False): - """Wrap response.body in a generator that recursively iterates over body. - - This allows cherrypy.response.body to consist of 'nested generators'; - that is, a set of generators that yield generators. - """ - def flattener(input): - numchunks = 0 - for x in input: - if not is_iterator(x): - numchunks += 1 - yield x - else: - for y in flattener(x): - numchunks += 1 - yield y - if debug: - cherrypy.log('Flattened %d chunks' % numchunks, 'TOOLS.FLATTEN') - response = cherrypy.serving.response - response.body = flattener(response.body) - - -def accept(media=None, debug=False): - """Return the client's preferred media-type (from the given Content-Types). - - If 'media' is None (the default), no test will be performed. - - If 'media' is provided, it should be the Content-Type value (as a string) - or values (as a list or tuple of strings) which the current resource - can emit. The client's acceptable media ranges (as declared in the - Accept request header) will be matched in order to these Content-Type - values; the first such string is returned. That is, the return value - will always be one of the strings provided in the 'media' arg (or None - if 'media' is None). - - If no match is found, then HTTPError 406 (Not Acceptable) is raised. - Note that most web browsers send */* as a (low-quality) acceptable - media range, which should match any Content-Type. In addition, "...if - no Accept header field is present, then it is assumed that the client - accepts all media types." - - Matching types are checked in order of client preference first, - and then in the order of the given 'media' values. - - Note that this function does not honor accept-params (other than "q"). - """ - if not media: - return - if isinstance(media, text_or_bytes): - media = [media] - request = cherrypy.serving.request - - # Parse the Accept request header, and try to match one - # of the requested media-ranges (in order of preference). - ranges = request.headers.elements('Accept') - if not ranges: - # Any media type is acceptable. - if debug: - cherrypy.log('No Accept header elements', 'TOOLS.ACCEPT') - return media[0] - else: - # Note that 'ranges' is sorted in order of preference - for element in ranges: - if element.qvalue > 0: - if element.value == '*/*': - # Matches any type or subtype - if debug: - cherrypy.log('Match due to */*', 'TOOLS.ACCEPT') - return media[0] - elif element.value.endswith('/*'): - # Matches any subtype - mtype = element.value[:-1] # Keep the slash - for m in media: - if m.startswith(mtype): - if debug: - cherrypy.log('Match due to %s' % element.value, - 'TOOLS.ACCEPT') - return m - else: - # Matches exact value - if element.value in media: - if debug: - cherrypy.log('Match due to %s' % element.value, - 'TOOLS.ACCEPT') - return element.value - - # No suitable media-range found. - ah = request.headers.get('Accept') - if ah is None: - msg = 'Your client did not send an Accept header.' - else: - msg = 'Your client sent this Accept header: %s.' % ah - msg += (' But this resource only emits these media types: %s.' % - ', '.join(media)) - raise cherrypy.HTTPError(406, msg) - - -class MonitoredHeaderMap(_httputil.HeaderMap): - - def transform_key(self, key): - self.accessed_headers.add(key) - return super(MonitoredHeaderMap, self).transform_key(key) - - def __init__(self): - self.accessed_headers = set() - super(MonitoredHeaderMap, self).__init__() - - -def autovary(ignore=None, debug=False): - """Auto-populate the Vary response header based on request.header access. - """ - request = cherrypy.serving.request - - req_h = request.headers - request.headers = MonitoredHeaderMap() - request.headers.update(req_h) - if ignore is None: - ignore = set(['Content-Disposition', 'Content-Length', 'Content-Type']) - - def set_response_header(): - resp_h = cherrypy.serving.response.headers - v = set([e.value for e in resp_h.elements('Vary')]) - if debug: - cherrypy.log( - 'Accessed headers: %s' % request.headers.accessed_headers, - 'TOOLS.AUTOVARY') - v = v.union(request.headers.accessed_headers) - v = v.difference(ignore) - v = list(v) - v.sort() - resp_h['Vary'] = ', '.join(v) - request.hooks.attach('before_finalize', set_response_header, 95) - - -def convert_params(exception=ValueError, error=400): - """Convert request params based on function annotations, with error handling. - - exception - Exception class to catch. - - status - The HTTP error code to return to the client on failure. - """ - request = cherrypy.serving.request - types = request.handler.callable.__annotations__ - with cherrypy.HTTPError.handle(exception, error): - for key in set(types).intersection(request.params): - request.params[key] = types[key](request.params[key]) diff --git a/lib/cherrypy/lib/encoding.py b/lib/cherrypy/lib/encoding.py deleted file mode 100644 index c2c478a5..00000000 --- a/lib/cherrypy/lib/encoding.py +++ /dev/null @@ -1,454 +0,0 @@ -import struct -import time -import io - -import cherrypy -from cherrypy._cpcompat import text_or_bytes -from cherrypy.lib import file_generator -from cherrypy.lib import is_closable_iterator -from cherrypy.lib import set_vary_header - - -_COMPRESSION_LEVEL_FAST = 1 -_COMPRESSION_LEVEL_BEST = 9 - - -def decode(encoding=None, default_encoding='utf-8'): - """Replace or extend the list of charsets used to decode a request entity. - - Either argument may be a single string or a list of strings. - - encoding - If not None, restricts the set of charsets attempted while decoding - a request entity to the given set (even if a different charset is - given in the Content-Type request header). - - default_encoding - Only in effect if the 'encoding' argument is not given. - If given, the set of charsets attempted while decoding a request - entity is *extended* with the given value(s). - - """ - body = cherrypy.request.body - if encoding is not None: - if not isinstance(encoding, list): - encoding = [encoding] - body.attempt_charsets = encoding - elif default_encoding: - if not isinstance(default_encoding, list): - default_encoding = [default_encoding] - body.attempt_charsets = body.attempt_charsets + default_encoding - - -class UTF8StreamEncoder: - def __init__(self, iterator): - self._iterator = iterator - - def __iter__(self): - return self - - def next(self): - return self.__next__() - - def __next__(self): - res = next(self._iterator) - if isinstance(res, str): - res = res.encode('utf-8') - return res - - def close(self): - if is_closable_iterator(self._iterator): - self._iterator.close() - - def __getattr__(self, attr): - if attr.startswith('__'): - raise AttributeError(self, attr) - return getattr(self._iterator, attr) - - -class ResponseEncoder: - - default_encoding = 'utf-8' - failmsg = 'Response body could not be encoded with %r.' - encoding = None - errors = 'strict' - text_only = True - add_charset = True - debug = False - - def __init__(self, **kwargs): - for k, v in kwargs.items(): - setattr(self, k, v) - - self.attempted_charsets = set() - request = cherrypy.serving.request - if request.handler is not None: - # Replace request.handler with self - if self.debug: - cherrypy.log('Replacing request.handler', 'TOOLS.ENCODE') - self.oldhandler = request.handler - request.handler = self - - def encode_stream(self, encoding): - """Encode a streaming response body. - - Use a generator wrapper, and just pray it works as the stream is - being written out. - """ - if encoding in self.attempted_charsets: - return False - self.attempted_charsets.add(encoding) - - def encoder(body): - for chunk in body: - if isinstance(chunk, str): - chunk = chunk.encode(encoding, self.errors) - yield chunk - self.body = encoder(self.body) - return True - - def encode_string(self, encoding): - """Encode a buffered response body.""" - if encoding in self.attempted_charsets: - return False - self.attempted_charsets.add(encoding) - body = [] - for chunk in self.body: - if isinstance(chunk, str): - try: - chunk = chunk.encode(encoding, self.errors) - except (LookupError, UnicodeError): - return False - body.append(chunk) - self.body = body - return True - - def find_acceptable_charset(self): - request = cherrypy.serving.request - response = cherrypy.serving.response - - if self.debug: - cherrypy.log('response.stream %r' % - response.stream, 'TOOLS.ENCODE') - if response.stream: - encoder = self.encode_stream - else: - encoder = self.encode_string - if 'Content-Length' in response.headers: - # Delete Content-Length header so finalize() recalcs it. - # Encoded strings may be of different lengths from their - # unicode equivalents, and even from each other. For example: - # >>> t = u"\u7007\u3040" - # >>> len(t) - # 2 - # >>> len(t.encode("UTF-8")) - # 6 - # >>> len(t.encode("utf7")) - # 8 - del response.headers['Content-Length'] - - # Parse the Accept-Charset request header, and try to provide one - # of the requested charsets (in order of user preference). - encs = request.headers.elements('Accept-Charset') - charsets = [enc.value.lower() for enc in encs] - if self.debug: - cherrypy.log('charsets %s' % repr(charsets), 'TOOLS.ENCODE') - - if self.encoding is not None: - # If specified, force this encoding to be used, or fail. - encoding = self.encoding.lower() - if self.debug: - cherrypy.log('Specified encoding %r' % - encoding, 'TOOLS.ENCODE') - if (not charsets) or '*' in charsets or encoding in charsets: - if self.debug: - cherrypy.log('Attempting encoding %r' % - encoding, 'TOOLS.ENCODE') - if encoder(encoding): - return encoding - else: - if not encs: - if self.debug: - cherrypy.log('Attempting default encoding %r' % - self.default_encoding, 'TOOLS.ENCODE') - # Any character-set is acceptable. - if encoder(self.default_encoding): - return self.default_encoding - else: - raise cherrypy.HTTPError(500, self.failmsg % - self.default_encoding) - else: - for element in encs: - if element.qvalue > 0: - if element.value == '*': - # Matches any charset. Try our default. - if self.debug: - cherrypy.log('Attempting default encoding due ' - 'to %r' % element, 'TOOLS.ENCODE') - if encoder(self.default_encoding): - return self.default_encoding - else: - encoding = element.value - if self.debug: - cherrypy.log('Attempting encoding %s (qvalue >' - '0)' % element, 'TOOLS.ENCODE') - if encoder(encoding): - return encoding - - if '*' not in charsets: - # If no "*" is present in an Accept-Charset field, then all - # character sets not explicitly mentioned get a quality - # value of 0, except for ISO-8859-1, which gets a quality - # value of 1 if not explicitly mentioned. - iso = 'iso-8859-1' - if iso not in charsets: - if self.debug: - cherrypy.log('Attempting ISO-8859-1 encoding', - 'TOOLS.ENCODE') - if encoder(iso): - return iso - - # No suitable encoding found. - ac = request.headers.get('Accept-Charset') - if ac is None: - msg = 'Your client did not send an Accept-Charset header.' - else: - msg = 'Your client sent this Accept-Charset header: %s.' % ac - _charsets = ', '.join(sorted(self.attempted_charsets)) - msg += ' We tried these charsets: %s.' % (_charsets,) - raise cherrypy.HTTPError(406, msg) - - def __call__(self, *args, **kwargs): - response = cherrypy.serving.response - self.body = self.oldhandler(*args, **kwargs) - - self.body = prepare_iter(self.body) - - ct = response.headers.elements('Content-Type') - if self.debug: - cherrypy.log('Content-Type: %r' % [str(h) - for h in ct], 'TOOLS.ENCODE') - if ct and self.add_charset: - ct = ct[0] - if self.text_only: - if ct.value.lower().startswith('text/'): - if self.debug: - cherrypy.log( - 'Content-Type %s starts with "text/"' % ct, - 'TOOLS.ENCODE') - do_find = True - else: - if self.debug: - cherrypy.log('Not finding because Content-Type %s ' - 'does not start with "text/"' % ct, - 'TOOLS.ENCODE') - do_find = False - else: - if self.debug: - cherrypy.log('Finding because not text_only', - 'TOOLS.ENCODE') - do_find = True - - if do_find: - # Set "charset=..." param on response Content-Type header - ct.params['charset'] = self.find_acceptable_charset() - if self.debug: - cherrypy.log('Setting Content-Type %s' % ct, - 'TOOLS.ENCODE') - response.headers['Content-Type'] = str(ct) - - return self.body - - -def prepare_iter(value): - """ - Ensure response body is iterable and resolves to False when empty. - """ - if isinstance(value, text_or_bytes): - # strings get wrapped in a list because iterating over a single - # item list is much faster than iterating over every character - # in a long string. - if value: - value = [value] - else: - # [''] doesn't evaluate to False, so replace it with []. - value = [] - # Don't use isinstance here; io.IOBase which has an ABC takes - # 1000 times as long as, say, isinstance(value, str) - elif hasattr(value, 'read'): - value = file_generator(value) - elif value is None: - value = [] - return value - - -# GZIP - - -def compress(body, compress_level): - """Compress 'body' at the given compress_level.""" - import zlib - - # See https://tools.ietf.org/html/rfc1952 - yield b'\x1f\x8b' # ID1 and ID2: gzip marker - yield b'\x08' # CM: compression method - yield b'\x00' # FLG: none set - # MTIME: 4 bytes - yield struct.pack(' 0 is present - * The 'identity' value is given with a qvalue > 0. - - """ - request = cherrypy.serving.request - response = cherrypy.serving.response - - set_vary_header(response, 'Accept-Encoding') - - if not response.body: - # Response body is empty (might be a 304 for instance) - if debug: - cherrypy.log('No response body', context='TOOLS.GZIP') - return - - # If returning cached content (which should already have been gzipped), - # don't re-zip. - if getattr(request, 'cached', False): - if debug: - cherrypy.log('Not gzipping cached response', context='TOOLS.GZIP') - return - - acceptable = request.headers.elements('Accept-Encoding') - if not acceptable: - # If no Accept-Encoding field is present in a request, - # the server MAY assume that the client will accept any - # content coding. In this case, if "identity" is one of - # the available content-codings, then the server SHOULD use - # the "identity" content-coding, unless it has additional - # information that a different content-coding is meaningful - # to the client. - if debug: - cherrypy.log('No Accept-Encoding', context='TOOLS.GZIP') - return - - ct = response.headers.get('Content-Type', '').split(';')[0] - for coding in acceptable: - if coding.value == 'identity' and coding.qvalue != 0: - if debug: - cherrypy.log('Non-zero identity qvalue: %s' % coding, - context='TOOLS.GZIP') - return - if coding.value in ('gzip', 'x-gzip'): - if coding.qvalue == 0: - if debug: - cherrypy.log('Zero gzip qvalue: %s' % coding, - context='TOOLS.GZIP') - return - - if ct not in mime_types: - # If the list of provided mime-types contains tokens - # such as 'text/*' or 'application/*+xml', - # we go through them and find the most appropriate one - # based on the given content-type. - # The pattern matching is only caring about the most - # common cases, as stated above, and doesn't support - # for extra parameters. - found = False - if '/' in ct: - ct_media_type, ct_sub_type = ct.split('/') - for mime_type in mime_types: - if '/' in mime_type: - media_type, sub_type = mime_type.split('/') - if ct_media_type == media_type: - if sub_type == '*': - found = True - break - elif '+' in sub_type and '+' in ct_sub_type: - ct_left, ct_right = ct_sub_type.split('+') - left, right = sub_type.split('+') - if left == '*' and ct_right == right: - found = True - break - - if not found: - if debug: - cherrypy.log('Content-Type %s not in mime_types %r' % - (ct, mime_types), context='TOOLS.GZIP') - return - - if debug: - cherrypy.log('Gzipping', context='TOOLS.GZIP') - # Return a generator that compresses the page - response.headers['Content-Encoding'] = 'gzip' - response.body = compress(response.body, compress_level) - if 'Content-Length' in response.headers: - # Delete Content-Length header so finalize() recalcs it. - del response.headers['Content-Length'] - - return - - if debug: - cherrypy.log('No acceptable encoding found.', context='GZIP') - cherrypy.HTTPError(406, 'identity, gzip').set_response() diff --git a/lib/cherrypy/lib/gctools.py b/lib/cherrypy/lib/gctools.py deleted file mode 100644 index 26746d78..00000000 --- a/lib/cherrypy/lib/gctools.py +++ /dev/null @@ -1,218 +0,0 @@ -import gc -import inspect -import sys -import time - -try: - import objgraph -except ImportError: - objgraph = None - -import cherrypy -from cherrypy import _cprequest, _cpwsgi -from cherrypy.process.plugins import SimplePlugin - - -class ReferrerTree(object): - - """An object which gathers all referrers of an object to a given depth.""" - - peek_length = 40 - - def __init__(self, ignore=None, maxdepth=2, maxparents=10): - self.ignore = ignore or [] - self.ignore.append(inspect.currentframe().f_back) - self.maxdepth = maxdepth - self.maxparents = maxparents - - def ascend(self, obj, depth=1): - """Return a nested list containing referrers of the given object.""" - depth += 1 - parents = [] - - # Gather all referrers in one step to minimize - # cascading references due to repr() logic. - refs = gc.get_referrers(obj) - self.ignore.append(refs) - if len(refs) > self.maxparents: - return [('[%s referrers]' % len(refs), [])] - - try: - ascendcode = self.ascend.__code__ - except AttributeError: - ascendcode = self.ascend.im_func.func_code - for parent in refs: - if inspect.isframe(parent) and parent.f_code is ascendcode: - continue - if parent in self.ignore: - continue - if depth <= self.maxdepth: - parents.append((parent, self.ascend(parent, depth))) - else: - parents.append((parent, [])) - - return parents - - def peek(self, s): - """Return s, restricted to a sane length.""" - if len(s) > (self.peek_length + 3): - half = self.peek_length // 2 - return s[:half] + '...' + s[-half:] - else: - return s - - def _format(self, obj, descend=True): - """Return a string representation of a single object.""" - if inspect.isframe(obj): - filename, lineno, func, context, index = inspect.getframeinfo(obj) - return "" % func - - if not descend: - return self.peek(repr(obj)) - - if isinstance(obj, dict): - return '{' + ', '.join(['%s: %s' % (self._format(k, descend=False), - self._format(v, descend=False)) - for k, v in obj.items()]) + '}' - elif isinstance(obj, list): - return '[' + ', '.join([self._format(item, descend=False) - for item in obj]) + ']' - elif isinstance(obj, tuple): - return '(' + ', '.join([self._format(item, descend=False) - for item in obj]) + ')' - - r = self.peek(repr(obj)) - if isinstance(obj, (str, int, float)): - return r - return '%s: %s' % (type(obj), r) - - def format(self, tree): - """Return a list of string reprs from a nested list of referrers.""" - output = [] - - def ascend(branch, depth=1): - for parent, grandparents in branch: - output.append((' ' * depth) + self._format(parent)) - if grandparents: - ascend(grandparents, depth + 1) - ascend(tree) - return output - - -def get_instances(cls): - return [x for x in gc.get_objects() if isinstance(x, cls)] - - -class RequestCounter(SimplePlugin): - - def start(self): - self.count = 0 - - def before_request(self): - self.count += 1 - - def after_request(self): - self.count -= 1 - - -request_counter = RequestCounter(cherrypy.engine) -request_counter.subscribe() - - -def get_context(obj): - if isinstance(obj, _cprequest.Request): - return 'path=%s;stage=%s' % (obj.path_info, obj.stage) - elif isinstance(obj, _cprequest.Response): - return 'status=%s' % obj.status - elif isinstance(obj, _cpwsgi.AppResponse): - return 'PATH_INFO=%s' % obj.environ.get('PATH_INFO', '') - elif hasattr(obj, 'tb_lineno'): - return 'tb_lineno=%s' % obj.tb_lineno - return '' - - -class GCRoot(object): - - """A CherryPy page handler for testing reference leaks.""" - - classes = [ - (_cprequest.Request, 2, 2, - 'Should be 1 in this request thread and 1 in the main thread.'), - (_cprequest.Response, 2, 2, - 'Should be 1 in this request thread and 1 in the main thread.'), - (_cpwsgi.AppResponse, 1, 1, - 'Should be 1 in this request thread only.'), - ] - - @cherrypy.expose - def index(self): - return 'Hello, world!' - - @cherrypy.expose - def stats(self): - output = ['Statistics:'] - - for trial in range(10): - if request_counter.count > 0: - break - time.sleep(0.5) - else: - output.append('\nNot all requests closed properly.') - - # gc_collect isn't perfectly synchronous, because it may - # break reference cycles that then take time to fully - # finalize. Call it thrice and hope for the best. - gc.collect() - gc.collect() - unreachable = gc.collect() - if unreachable: - if objgraph is not None: - final = objgraph.by_type('Nondestructible') - if final: - objgraph.show_backrefs(final, filename='finalizers.png') - - trash = {} - for x in gc.garbage: - trash[type(x)] = trash.get(type(x), 0) + 1 - if trash: - output.insert(0, '\n%s unreachable objects:' % unreachable) - trash = [(v, k) for k, v in trash.items()] - trash.sort() - for pair in trash: - output.append(' ' + repr(pair)) - - # Check declared classes to verify uncollected instances. - # These don't have to be part of a cycle; they can be - # any objects that have unanticipated referrers that keep - # them from being collected. - allobjs = {} - for cls, minobj, maxobj, msg in self.classes: - allobjs[cls] = get_instances(cls) - - for cls, minobj, maxobj, msg in self.classes: - objs = allobjs[cls] - lenobj = len(objs) - if lenobj < minobj or lenobj > maxobj: - if minobj == maxobj: - output.append( - '\nExpected %s %r references, got %s.' % - (minobj, cls, lenobj)) - else: - output.append( - '\nExpected %s to %s %r references, got %s.' % - (minobj, maxobj, cls, lenobj)) - - for obj in objs: - if objgraph is not None: - ig = [id(objs), id(inspect.currentframe())] - fname = 'graph_%s_%s.png' % (cls.__name__, id(obj)) - objgraph.show_backrefs( - obj, extra_ignore=ig, max_depth=4, too_many=20, - filename=fname, extra_info=get_context) - output.append('\nReferrers for %s (refcount=%s):' % - (repr(obj), sys.getrefcount(obj))) - t = ReferrerTree(ignore=[objs], maxdepth=3) - tree = t.ascend(obj) - output.extend(t.format(tree)) - - return '\n'.join(output) diff --git a/lib/cherrypy/lib/httputil.py b/lib/cherrypy/lib/httputil.py deleted file mode 100644 index ced310a0..00000000 --- a/lib/cherrypy/lib/httputil.py +++ /dev/null @@ -1,548 +0,0 @@ -"""HTTP library functions. - -This module contains functions for building an HTTP application -framework: any one, not just one whose name starts with "Ch". ;) If you -reference any modules from some popular framework inside *this* module, -FuManChu will personally hang you up by your thumbs and submit you -to a public caning. -""" - -import functools -import email.utils -import re -import builtins -from binascii import b2a_base64 -from cgi import parse_header -from email.header import decode_header -from http.server import BaseHTTPRequestHandler -from urllib.parse import unquote_plus - -import jaraco.collections - -import cherrypy -from cherrypy._cpcompat import ntob, ntou - -response_codes = BaseHTTPRequestHandler.responses.copy() - -# From https://github.com/cherrypy/cherrypy/issues/361 -response_codes[500] = ('Internal Server Error', - 'The server encountered an unexpected condition ' - 'which prevented it from fulfilling the request.') -response_codes[503] = ('Service Unavailable', - 'The server is currently unable to handle the ' - 'request due to a temporary overloading or ' - 'maintenance of the server.') - - -HTTPDate = functools.partial(email.utils.formatdate, usegmt=True) - - -def urljoin(*atoms): - r"""Return the given path \*atoms, joined into a single URL. - - This will correctly join a SCRIPT_NAME and PATH_INFO into the - original URL, even if either atom is blank. - """ - url = '/'.join([x for x in atoms if x]) - while '//' in url: - url = url.replace('//', '/') - # Special-case the final url of "", and return "/" instead. - return url or '/' - - -def urljoin_bytes(*atoms): - """Return the given path `*atoms`, joined into a single URL. - - This will correctly join a SCRIPT_NAME and PATH_INFO into the - original URL, even if either atom is blank. - """ - url = b'/'.join([x for x in atoms if x]) - while b'//' in url: - url = url.replace(b'//', b'/') - # Special-case the final url of "", and return "/" instead. - return url or b'/' - - -def protocol_from_http(protocol_str): - """Return a protocol tuple from the given 'HTTP/x.y' string.""" - return int(protocol_str[5]), int(protocol_str[7]) - - -def get_ranges(headervalue, content_length): - """Return a list of (start, stop) indices from a Range header, or None. - - Each (start, stop) tuple will be composed of two ints, which are suitable - for use in a slicing operation. That is, the header "Range: bytes=3-6", - if applied against a Python string, is requesting resource[3:7]. This - function will return the list [(3, 7)]. - - If this function returns an empty list, you should return HTTP 416. - """ - - if not headervalue: - return None - - result = [] - bytesunit, byteranges = headervalue.split('=', 1) - for brange in byteranges.split(','): - start, stop = [x.strip() for x in brange.split('-', 1)] - if start: - if not stop: - stop = content_length - 1 - start, stop = int(start), int(stop) - if start >= content_length: - # From rfc 2616 sec 14.16: - # "If the server receives a request (other than one - # including an If-Range request-header field) with an - # unsatisfiable Range request-header field (that is, - # all of whose byte-range-spec values have a first-byte-pos - # value greater than the current length of the selected - # resource), it SHOULD return a response code of 416 - # (Requested range not satisfiable)." - continue - if stop < start: - # From rfc 2616 sec 14.16: - # "If the server ignores a byte-range-spec because it - # is syntactically invalid, the server SHOULD treat - # the request as if the invalid Range header field - # did not exist. (Normally, this means return a 200 - # response containing the full entity)." - return None - result.append((start, stop + 1)) - else: - if not stop: - # See rfc quote above. - return None - # Negative subscript (last N bytes) - # - # RFC 2616 Section 14.35.1: - # If the entity is shorter than the specified suffix-length, - # the entire entity-body is used. - if int(stop) > content_length: - result.append((0, content_length)) - else: - result.append((content_length - int(stop), content_length)) - - return result - - -class HeaderElement(object): - - """An element (with parameters) from an HTTP header's element list.""" - - def __init__(self, value, params=None): - self.value = value - if params is None: - params = {} - self.params = params - - def __cmp__(self, other): - return builtins.cmp(self.value, other.value) - - def __lt__(self, other): - return self.value < other.value - - def __str__(self): - p = [';%s=%s' % (k, v) for k, v in self.params.items()] - return str('%s%s' % (self.value, ''.join(p))) - - def __bytes__(self): - return ntob(self.__str__()) - - def __unicode__(self): - return ntou(self.__str__()) - - @staticmethod - def parse(elementstr): - """Transform 'token;key=val' to ('token', {'key': 'val'}).""" - initial_value, params = parse_header(elementstr) - return initial_value, params - - @classmethod - def from_str(cls, elementstr): - """Construct an instance from a string of the form 'token;key=val'.""" - ival, params = cls.parse(elementstr) - return cls(ival, params) - - -q_separator = re.compile(r'; *q *=') - - -class AcceptElement(HeaderElement): - - """An element (with parameters) from an Accept* header's element list. - - AcceptElement objects are comparable; the more-preferred object will be - "less than" the less-preferred object. They are also therefore sortable; - if you sort a list of AcceptElement objects, they will be listed in - priority order; the most preferred value will be first. Yes, it should - have been the other way around, but it's too late to fix now. - """ - - @classmethod - def from_str(cls, elementstr): - qvalue = None - # The first "q" parameter (if any) separates the initial - # media-range parameter(s) (if any) from the accept-params. - atoms = q_separator.split(elementstr, 1) - media_range = atoms.pop(0).strip() - if atoms: - # The qvalue for an Accept header can have extensions. The other - # headers cannot, but it's easier to parse them as if they did. - qvalue = HeaderElement.from_str(atoms[0].strip()) - - media_type, params = cls.parse(media_range) - if qvalue is not None: - params['q'] = qvalue - return cls(media_type, params) - - @property - def qvalue(self): - 'The qvalue, or priority, of this value.' - val = self.params.get('q', '1') - if isinstance(val, HeaderElement): - val = val.value - try: - return float(val) - except ValueError as val_err: - """Fail client requests with invalid quality value. - - Ref: https://github.com/cherrypy/cherrypy/issues/1370 - """ - raise cherrypy.HTTPError( - 400, - 'Malformed HTTP header: `{}`'. - format(str(self)), - ) from val_err - - def __cmp__(self, other): - diff = builtins.cmp(self.qvalue, other.qvalue) - if diff == 0: - diff = builtins.cmp(str(self), str(other)) - return diff - - def __lt__(self, other): - if self.qvalue == other.qvalue: - return str(self) < str(other) - else: - return self.qvalue < other.qvalue - - -RE_HEADER_SPLIT = re.compile(',(?=(?:[^"]*"[^"]*")*[^"]*$)') - - -def header_elements(fieldname, fieldvalue): - """Return a sorted HeaderElement list from a comma-separated header string. - """ - if not fieldvalue: - return [] - - result = [] - for element in RE_HEADER_SPLIT.split(fieldvalue): - if fieldname.startswith('Accept') or fieldname == 'TE': - hv = AcceptElement.from_str(element) - else: - hv = HeaderElement.from_str(element) - result.append(hv) - - return list(reversed(sorted(result))) - - -def decode_TEXT(value): - r""" - Decode :rfc:`2047` TEXT - - >>> decode_TEXT("=?utf-8?q?f=C3=BCr?=") == b'f\xfcr'.decode('latin-1') - True - """ - atoms = decode_header(value) - decodedvalue = '' - for atom, charset in atoms: - if charset is not None: - atom = atom.decode(charset) - decodedvalue += atom - return decodedvalue - - -def decode_TEXT_maybe(value): - """ - Decode the text but only if '=?' appears in it. - """ - return decode_TEXT(value) if '=?' in value else value - - -def valid_status(status): - """Return legal HTTP status Code, Reason-phrase and Message. - - The status arg must be an int, a str that begins with an int - or the constant from ``http.client`` stdlib module. - - If status has no reason-phrase is supplied, a default reason- - phrase will be provided. - - >>> import http.client - >>> from http.server import BaseHTTPRequestHandler - >>> valid_status(http.client.ACCEPTED) == ( - ... int(http.client.ACCEPTED), - ... ) + BaseHTTPRequestHandler.responses[http.client.ACCEPTED] - True - """ - - if not status: - status = 200 - - code, reason = status, None - if isinstance(status, str): - code, _, reason = status.partition(' ') - reason = reason.strip() or None - - try: - code = int(code) - except (TypeError, ValueError): - raise ValueError('Illegal response status from server ' - '(%s is non-numeric).' % repr(code)) - - if code < 100 or code > 599: - raise ValueError('Illegal response status from server ' - '(%s is out of range).' % repr(code)) - - if code not in response_codes: - # code is unknown but not illegal - default_reason, message = '', '' - else: - default_reason, message = response_codes[code] - - if reason is None: - reason = default_reason - - return code, reason, message - - -# NOTE: the parse_qs functions that follow are modified version of those -# in the python3.0 source - we need to pass through an encoding to the unquote -# method, but the default parse_qs function doesn't allow us to. These do. - -def _parse_qs(qs, keep_blank_values=0, strict_parsing=0, encoding='utf-8'): - """Parse a query given as a string argument. - - Arguments: - - qs: URL-encoded query string to be parsed - - keep_blank_values: flag indicating whether blank values in - URL encoded queries should be treated as blank strings. A - true value indicates that blanks should be retained as blank - strings. The default false value indicates that blank values - are to be ignored and treated as if they were not included. - - strict_parsing: flag indicating what to do with parsing errors. If - false (the default), errors are silently ignored. If true, - errors raise a ValueError exception. - - Returns a dict, as G-d intended. - """ - pairs = [s2 for s1 in qs.split('&') for s2 in s1.split(';')] - d = {} - for name_value in pairs: - if not name_value and not strict_parsing: - continue - nv = name_value.split('=', 1) - if len(nv) != 2: - if strict_parsing: - raise ValueError('bad query field: %r' % (name_value,)) - # Handle case of a control-name with no equal sign - if keep_blank_values: - nv.append('') - else: - continue - if len(nv[1]) or keep_blank_values: - name = unquote_plus(nv[0], encoding, errors='strict') - value = unquote_plus(nv[1], encoding, errors='strict') - if name in d: - if not isinstance(d[name], list): - d[name] = [d[name]] - d[name].append(value) - else: - d[name] = value - return d - - -image_map_pattern = re.compile(r'[0-9]+,[0-9]+') - - -def parse_query_string(query_string, keep_blank_values=True, encoding='utf-8'): - """Build a params dictionary from a query_string. - - Duplicate key/value pairs in the provided query_string will be - returned as {'key': [val1, val2, ...]}. Single key/values will - be returned as strings: {'key': 'value'}. - """ - if image_map_pattern.match(query_string): - # Server-side image map. Map the coords to 'x' and 'y' - # (like CGI::Request does). - pm = query_string.split(',') - pm = {'x': int(pm[0]), 'y': int(pm[1])} - else: - pm = _parse_qs(query_string, keep_blank_values, encoding=encoding) - return pm - - -class CaseInsensitiveDict(jaraco.collections.KeyTransformingDict): - - """A case-insensitive dict subclass. - - Each key is changed on entry to title case. - """ - - @staticmethod - def transform_key(key): - if key is None: - # TODO(#1830): why? - return 'None' - return key.title() - - -# TEXT = -# -# A CRLF is allowed in the definition of TEXT only as part of a header -# field continuation. It is expected that the folding LWS will be -# replaced with a single SP before interpretation of the TEXT value." -if str == bytes: - header_translate_table = ''.join([chr(i) for i in range(256)]) - header_translate_deletechars = ''.join( - [chr(i) for i in range(32)]) + chr(127) -else: - header_translate_table = None - header_translate_deletechars = bytes(range(32)) + bytes([127]) - - -class HeaderMap(CaseInsensitiveDict): - - """A dict subclass for HTTP request and response headers. - - Each key is changed on entry to str(key).title(). This allows headers - to be case-insensitive and avoid duplicates. - - Values are header values (decoded according to :rfc:`2047` if necessary). - """ - - protocol = (1, 1) - encodings = ['ISO-8859-1'] - - # Someday, when http-bis is done, this will probably get dropped - # since few servers, clients, or intermediaries do it. But until then, - # we're going to obey the spec as is. - # "Words of *TEXT MAY contain characters from character sets other than - # ISO-8859-1 only when encoded according to the rules of RFC 2047." - use_rfc_2047 = True - - def elements(self, key): - """Return a sorted list of HeaderElements for the given header.""" - return header_elements(self.transform_key(key), self.get(key)) - - def values(self, key): - """Return a sorted list of HeaderElement.value for the given header.""" - return [e.value for e in self.elements(key)] - - def output(self): - """Transform self into a list of (name, value) tuples.""" - return list(self.encode_header_items(self.items())) - - @classmethod - def encode_header_items(cls, header_items): - """ - Prepare the sequence of name, value tuples into a form suitable for - transmitting on the wire for HTTP. - """ - for k, v in header_items: - if not isinstance(v, str) and not isinstance(v, bytes): - v = str(v) - - yield tuple(map(cls.encode_header_item, (k, v))) - - @classmethod - def encode_header_item(cls, item): - if isinstance(item, str): - item = cls.encode(item) - - # See header_translate_* constants above. - # Replace only if you really know what you're doing. - return item.translate( - header_translate_table, header_translate_deletechars) - - @classmethod - def encode(cls, v): - """Return the given header name or value, encoded for HTTP output.""" - for enc in cls.encodings: - try: - return v.encode(enc) - except UnicodeEncodeError: - continue - - if cls.protocol == (1, 1) and cls.use_rfc_2047: - # Encode RFC-2047 TEXT - # (e.g. u"\u8200" -> "=?utf-8?b?6IiA?="). - # We do our own here instead of using the email module - # because we never want to fold lines--folding has - # been deprecated by the HTTP working group. - v = b2a_base64(v.encode('utf-8')) - return (b'=?utf-8?b?' + v.strip(b'\n') + b'?=') - - raise ValueError('Could not encode header part %r using ' - 'any of the encodings %r.' % - (v, cls.encodings)) - - -class Host(object): - - """An internet address. - - name - Should be the client's host name. If not available (because no DNS - lookup is performed), the IP address should be used instead. - - """ - - ip = '0.0.0.0' - port = 80 - name = 'unknown.tld' - - def __init__(self, ip, port, name=None): - self.ip = ip - self.port = port - if name is None: - name = ip - self.name = name - - def __repr__(self): - return 'httputil.Host(%r, %r, %r)' % (self.ip, self.port, self.name) - - -class SanitizedHost(str): - r""" - Wraps a raw host header received from the network in - a sanitized version that elides dangerous characters. - - >>> SanitizedHost('foo\nbar') - 'foobar' - >>> SanitizedHost('foo\nbar').raw - 'foo\nbar' - - A SanitizedInstance is only returned if sanitization was performed. - - >>> isinstance(SanitizedHost('foobar'), SanitizedHost) - False - """ - dangerous = re.compile(r'[\n\r]') - - def __new__(cls, raw): - sanitized = cls._sanitize(raw) - if sanitized == raw: - return raw - instance = super().__new__(cls, sanitized) - instance.raw = raw - return instance - - @classmethod - def _sanitize(cls, raw): - return cls.dangerous.sub('', raw) diff --git a/lib/cherrypy/lib/jsontools.py b/lib/cherrypy/lib/jsontools.py deleted file mode 100644 index 9ca75a8f..00000000 --- a/lib/cherrypy/lib/jsontools.py +++ /dev/null @@ -1,89 +0,0 @@ -import cherrypy -from cherrypy import _json as json -from cherrypy._cpcompat import text_or_bytes, ntou - - -def json_processor(entity): - """Read application/json data into request.json.""" - if not entity.headers.get(ntou('Content-Length'), ntou('')): - raise cherrypy.HTTPError(411) - - body = entity.fp.read() - with cherrypy.HTTPError.handle(ValueError, 400, 'Invalid JSON document'): - cherrypy.serving.request.json = json.decode(body.decode('utf-8')) - - -def json_in(content_type=[ntou('application/json'), ntou('text/javascript')], - force=True, debug=False, processor=json_processor): - """Add a processor to parse JSON request entities: - The default processor places the parsed data into request.json. - - Incoming request entities which match the given content_type(s) will - be deserialized from JSON to the Python equivalent, and the result - stored at cherrypy.request.json. The 'content_type' argument may - be a Content-Type string or a list of allowable Content-Type strings. - - If the 'force' argument is True (the default), then entities of other - content types will not be allowed; "415 Unsupported Media Type" is - raised instead. - - Supply your own processor to use a custom decoder, or to handle the parsed - data differently. The processor can be configured via - tools.json_in.processor or via the decorator method. - - Note that the deserializer requires the client send a Content-Length - request header, or it will raise "411 Length Required". If for any - other reason the request entity cannot be deserialized from JSON, - it will raise "400 Bad Request: Invalid JSON document". - """ - request = cherrypy.serving.request - if isinstance(content_type, text_or_bytes): - content_type = [content_type] - - if force: - if debug: - cherrypy.log('Removing body processors %s' % - repr(request.body.processors.keys()), 'TOOLS.JSON_IN') - request.body.processors.clear() - request.body.default_proc = cherrypy.HTTPError( - 415, 'Expected an entity of content type %s' % - ', '.join(content_type)) - - for ct in content_type: - if debug: - cherrypy.log('Adding body processor for %s' % ct, 'TOOLS.JSON_IN') - request.body.processors[ct] = processor - - -def json_handler(*args, **kwargs): - value = cherrypy.serving.request._json_inner_handler(*args, **kwargs) - return json.encode(value) - - -def json_out(content_type='application/json', debug=False, - handler=json_handler): - """Wrap request.handler to serialize its output to JSON. Sets Content-Type. - - If the given content_type is None, the Content-Type response header - is not set. - - Provide your own handler to use a custom encoder. For example - cherrypy.config['tools.json_out.handler'] = , or - @json_out(handler=function). - """ - request = cherrypy.serving.request - # request.handler may be set to None by e.g. the caching tool - # to signal to all components that a response body has already - # been attached, in which case we don't need to wrap anything. - if request.handler is None: - return - if debug: - cherrypy.log('Replacing %s with JSON handler' % request.handler, - 'TOOLS.JSON_OUT') - request._json_inner_handler = request.handler - request.handler = handler - if content_type is not None: - if debug: - cherrypy.log('Setting Content-Type to %s' % - content_type, 'TOOLS.JSON_OUT') - cherrypy.serving.response.headers['Content-Type'] = content_type diff --git a/lib/cherrypy/lib/locking.py b/lib/cherrypy/lib/locking.py deleted file mode 100644 index 317fb58c..00000000 --- a/lib/cherrypy/lib/locking.py +++ /dev/null @@ -1,47 +0,0 @@ -import datetime - - -class NeverExpires(object): - def expired(self): - return False - - -class Timer(object): - """ - A simple timer that will indicate when an expiration time has passed. - """ - def __init__(self, expiration): - 'Create a timer that expires at `expiration` (UTC datetime)' - self.expiration = expiration - - @classmethod - def after(cls, elapsed): - """ - Return a timer that will expire after `elapsed` passes. - """ - return cls(datetime.datetime.utcnow() + elapsed) - - def expired(self): - return datetime.datetime.utcnow() >= self.expiration - - -class LockTimeout(Exception): - 'An exception when a lock could not be acquired before a timeout period' - - -class LockChecker(object): - """ - Keep track of the time and detect if a timeout has expired - """ - def __init__(self, session_id, timeout): - self.session_id = session_id - if timeout: - self.timer = Timer.after(timeout) - else: - self.timer = NeverExpires() - - def expired(self): - if self.timer.expired(): - raise LockTimeout( - 'Timeout acquiring lock for %(session_id)s' % vars(self)) - return False diff --git a/lib/cherrypy/lib/profiler.py b/lib/cherrypy/lib/profiler.py deleted file mode 100644 index fccf2eb8..00000000 --- a/lib/cherrypy/lib/profiler.py +++ /dev/null @@ -1,221 +0,0 @@ -"""Profiler tools for CherryPy. - -CherryPy users -============== - -You can profile any of your pages as follows:: - - from cherrypy.lib import profiler - - class Root: - p = profiler.Profiler("/path/to/profile/dir") - - @cherrypy.expose - def index(self): - self.p.run(self._index) - - def _index(self): - return "Hello, world!" - - cherrypy.tree.mount(Root()) - -You can also turn on profiling for all requests -using the ``make_app`` function as WSGI middleware. - -CherryPy developers -=================== - -This module can be used whenever you make changes to CherryPy, -to get a quick sanity-check on overall CP performance. Use the -``--profile`` flag when running the test suite. Then, use the ``serve()`` -function to browse the results in a web browser. If you run this -module from the command line, it will call ``serve()`` for you. - -""" - -import io -import os -import os.path -import sys -import warnings - -import cherrypy - - -try: - import profile - import pstats - - def new_func_strip_path(func_name): - """Make profiler output more readable by adding `__init__` modules' parents - """ - filename, line, name = func_name - if filename.endswith('__init__.py'): - return ( - os.path.basename(filename[:-12]) + filename[-12:], - line, - name, - ) - return os.path.basename(filename), line, name - - pstats.func_strip_path = new_func_strip_path -except ImportError: - profile = None - pstats = None - - -_count = 0 - - -class Profiler(object): - - def __init__(self, path=None): - if not path: - path = os.path.join(os.path.dirname(__file__), 'profile') - self.path = path - if not os.path.exists(path): - os.makedirs(path) - - def run(self, func, *args, **params): - """Dump profile data into self.path.""" - global _count - c = _count = _count + 1 - path = os.path.join(self.path, 'cp_%04d.prof' % c) - prof = profile.Profile() - result = prof.runcall(func, *args, **params) - prof.dump_stats(path) - return result - - def statfiles(self): - """:rtype: list of available profiles. - """ - return [f for f in os.listdir(self.path) - if f.startswith('cp_') and f.endswith('.prof')] - - def stats(self, filename, sortby='cumulative'): - """:rtype stats(index): output of print_stats() for the given profile. - """ - sio = io.StringIO() - if sys.version_info >= (2, 5): - s = pstats.Stats(os.path.join(self.path, filename), stream=sio) - s.strip_dirs() - s.sort_stats(sortby) - s.print_stats() - else: - # pstats.Stats before Python 2.5 didn't take a 'stream' arg, - # but just printed to stdout. So re-route stdout. - s = pstats.Stats(os.path.join(self.path, filename)) - s.strip_dirs() - s.sort_stats(sortby) - oldout = sys.stdout - try: - sys.stdout = sio - s.print_stats() - finally: - sys.stdout = oldout - response = sio.getvalue() - sio.close() - return response - - @cherrypy.expose - def index(self): - return """ - CherryPy profile data - - - - - - """ - - @cherrypy.expose - def menu(self): - yield '

Profiling runs

' - yield '

Click on one of the runs below to see profiling data.

' - runs = self.statfiles() - runs.sort() - for i in runs: - yield "%s
" % ( - i, i) - - @cherrypy.expose - def report(self, filename): - cherrypy.response.headers['Content-Type'] = 'text/plain' - return self.stats(filename) - - -class ProfileAggregator(Profiler): - - def __init__(self, path=None): - Profiler.__init__(self, path) - global _count - self.count = _count = _count + 1 - self.profiler = profile.Profile() - - def run(self, func, *args, **params): - path = os.path.join(self.path, 'cp_%04d.prof' % self.count) - result = self.profiler.runcall(func, *args, **params) - self.profiler.dump_stats(path) - return result - - -class make_app: - - def __init__(self, nextapp, path=None, aggregate=False): - """Make a WSGI middleware app which wraps 'nextapp' with profiling. - - nextapp - the WSGI application to wrap, usually an instance of - cherrypy.Application. - - path - where to dump the profiling output. - - aggregate - if True, profile data for all HTTP requests will go in - a single file. If False (the default), each HTTP request will - dump its profile data into a separate file. - - """ - if profile is None or pstats is None: - msg = ('Your installation of Python does not have a profile ' - "module. If you're on Debian, try " - '`sudo apt-get install python-profiler`. ' - 'See http://www.cherrypy.org/wiki/ProfilingOnDebian ' - 'for details.') - warnings.warn(msg) - - self.nextapp = nextapp - self.aggregate = aggregate - if aggregate: - self.profiler = ProfileAggregator(path) - else: - self.profiler = Profiler(path) - - def __call__(self, environ, start_response): - def gather(): - result = [] - for line in self.nextapp(environ, start_response): - result.append(line) - return result - return self.profiler.run(gather) - - -def serve(path=None, port=8080): - if profile is None or pstats is None: - msg = ('Your installation of Python does not have a profile module. ' - "If you're on Debian, try " - '`sudo apt-get install python-profiler`. ' - 'See http://www.cherrypy.org/wiki/ProfilingOnDebian ' - 'for details.') - warnings.warn(msg) - - cherrypy.config.update({'server.socket_port': int(port), - 'server.thread_pool': 10, - 'environment': 'production', - }) - cherrypy.quickstart(Profiler(path)) - - -if __name__ == '__main__': - serve(*tuple(sys.argv[1:])) diff --git a/lib/cherrypy/lib/reprconf.py b/lib/cherrypy/lib/reprconf.py deleted file mode 100644 index 76381d7b..00000000 --- a/lib/cherrypy/lib/reprconf.py +++ /dev/null @@ -1,394 +0,0 @@ -"""Generic configuration system using unrepr. - -Configuration data may be supplied as a Python dictionary, as a filename, -or as an open file object. When you supply a filename or file, Python's -builtin ConfigParser is used (with some extensions). - -Namespaces ----------- - -Configuration keys are separated into namespaces by the first "." in the key. - -The only key that cannot exist in a namespace is the "environment" entry. -This special entry 'imports' other config entries from a template stored in -the Config.environments dict. - -You can define your own namespaces to be called when new config is merged -by adding a named handler to Config.namespaces. The name can be any string, -and the handler must be either a callable or a context manager. -""" - -import builtins -import configparser -import operator -import sys - -from cherrypy._cpcompat import text_or_bytes - - -class NamespaceSet(dict): - - """A dict of config namespace names and handlers. - - Each config entry should begin with a namespace name; the corresponding - namespace handler will be called once for each config entry in that - namespace, and will be passed two arguments: the config key (with the - namespace removed) and the config value. - - Namespace handlers may be any Python callable; they may also be - context managers, in which case their __enter__ - method should return a callable to be used as the handler. - See cherrypy.tools (the Toolbox class) for an example. - """ - - def __call__(self, config): - """Iterate through config and pass it to each namespace handler. - - config - A flat dict, where keys use dots to separate - namespaces, and values are arbitrary. - - The first name in each config key is used to look up the corresponding - namespace handler. For example, a config entry of {'tools.gzip.on': v} - will call the 'tools' namespace handler with the args: ('gzip.on', v) - """ - # Separate the given config into namespaces - ns_confs = {} - for k in config: - if '.' in k: - ns, name = k.split('.', 1) - bucket = ns_confs.setdefault(ns, {}) - bucket[name] = config[k] - - # I chose __enter__ and __exit__ so someday this could be - # rewritten using 'with' statement: - # for ns, handler in self.items(): - # with handler as callable: - # for k, v in ns_confs.get(ns, {}).items(): - # callable(k, v) - for ns, handler in self.items(): - exit = getattr(handler, '__exit__', None) - if exit: - callable = handler.__enter__() - no_exc = True - try: - try: - for k, v in ns_confs.get(ns, {}).items(): - callable(k, v) - except Exception: - # The exceptional case is handled here - no_exc = False - if exit is None: - raise - if not exit(*sys.exc_info()): - raise - # The exception is swallowed if exit() returns true - finally: - # The normal and non-local-goto cases are handled here - if no_exc and exit: - exit(None, None, None) - else: - for k, v in ns_confs.get(ns, {}).items(): - handler(k, v) - - def __repr__(self): - return '%s.%s(%s)' % (self.__module__, self.__class__.__name__, - dict.__repr__(self)) - - def __copy__(self): - newobj = self.__class__() - newobj.update(self) - return newobj - copy = __copy__ - - -class Config(dict): - - """A dict-like set of configuration data, with defaults and namespaces. - - May take a file, filename, or dict. - """ - - defaults = {} - environments = {} - namespaces = NamespaceSet() - - def __init__(self, file=None, **kwargs): - self.reset() - if file is not None: - self.update(file) - if kwargs: - self.update(kwargs) - - def reset(self): - """Reset self to default values.""" - self.clear() - dict.update(self, self.defaults) - - def update(self, config): - """Update self from a dict, file, or filename.""" - self._apply(Parser.load(config)) - - def _apply(self, config): - """Update self from a dict.""" - which_env = config.get('environment') - if which_env: - env = self.environments[which_env] - for k in env: - if k not in config: - config[k] = env[k] - - dict.update(self, config) - self.namespaces(config) - - def __setitem__(self, k, v): - dict.__setitem__(self, k, v) - self.namespaces({k: v}) - - -class Parser(configparser.ConfigParser): - - """Sub-class of ConfigParser that keeps the case of options and that - raises an exception if the file cannot be read. - """ - - def optionxform(self, optionstr): - return optionstr - - def read(self, filenames): - if isinstance(filenames, text_or_bytes): - filenames = [filenames] - for filename in filenames: - # try: - # fp = open(filename) - # except IOError: - # continue - with open(filename) as fp: - self._read(fp, filename) - - def as_dict(self, raw=False, vars=None): - """Convert an INI file to a dictionary""" - # Load INI file into a dict - result = {} - for section in self.sections(): - if section not in result: - result[section] = {} - for option in self.options(section): - value = self.get(section, option, raw=raw, vars=vars) - try: - value = unrepr(value) - except Exception: - x = sys.exc_info()[1] - msg = ('Config error in section: %r, option: %r, ' - 'value: %r. Config values must be valid Python.' % - (section, option, value)) - raise ValueError(msg, x.__class__.__name__, x.args) - result[section][option] = value - return result - - def dict_from_file(self, file): - if hasattr(file, 'read'): - self.readfp(file) - else: - self.read(file) - return self.as_dict() - - @classmethod - def load(self, input): - """Resolve 'input' to dict from a dict, file, or filename.""" - is_file = ( - # Filename - isinstance(input, text_or_bytes) - # Open file object - or hasattr(input, 'read') - ) - return Parser().dict_from_file(input) if is_file else input.copy() - - -# public domain "unrepr" implementation, found on the web and then improved. - - -class _Builder: - - def build(self, o): - m = getattr(self, 'build_' + o.__class__.__name__, None) - if m is None: - raise TypeError('unrepr does not recognize %s' % - repr(o.__class__.__name__)) - return m(o) - - def astnode(self, s): - """Return a Python3 ast Node compiled from a string.""" - try: - import ast - except ImportError: - # Fallback to eval when ast package is not available, - # e.g. IronPython 1.0. - return eval(s) - - p = ast.parse('__tempvalue__ = ' + s) - return p.body[0].value - - def build_Subscript(self, o): - return self.build(o.value)[self.build(o.slice)] - - def build_Index(self, o): - return self.build(o.value) - - def _build_call35(self, o): - """ - Workaround for python 3.5 _ast.Call signature, docs found here - https://greentreesnakes.readthedocs.org/en/latest/nodes.html - """ - import ast - callee = self.build(o.func) - args = [] - if o.args is not None: - for a in o.args: - if isinstance(a, ast.Starred): - args.append(self.build(a.value)) - else: - args.append(self.build(a)) - kwargs = {} - for kw in o.keywords: - if kw.arg is None: # double asterix `**` - rst = self.build(kw.value) - if not isinstance(rst, dict): - raise TypeError('Invalid argument for call.' - 'Must be a mapping object.') - # give preference to the keys set directly from arg=value - for k, v in rst.items(): - if k not in kwargs: - kwargs[k] = v - else: # defined on the call as: arg=value - kwargs[kw.arg] = self.build(kw.value) - return callee(*args, **kwargs) - - def build_Call(self, o): - if sys.version_info >= (3, 5): - return self._build_call35(o) - - callee = self.build(o.func) - - if o.args is None: - args = () - else: - args = tuple([self.build(a) for a in o.args]) - - if o.starargs is None: - starargs = () - else: - starargs = tuple(self.build(o.starargs)) - - if o.kwargs is None: - kwargs = {} - else: - kwargs = self.build(o.kwargs) - if o.keywords is not None: # direct a=b keywords - for kw in o.keywords: - # preference because is a direct keyword against **kwargs - kwargs[kw.arg] = self.build(kw.value) - return callee(*(args + starargs), **kwargs) - - def build_List(self, o): - return list(map(self.build, o.elts)) - - def build_Str(self, o): - return o.s - - def build_Num(self, o): - return o.n - - def build_Dict(self, o): - return dict([(self.build(k), self.build(v)) - for k, v in zip(o.keys, o.values)]) - - def build_Tuple(self, o): - return tuple(self.build_List(o)) - - def build_Name(self, o): - name = o.id - if name == 'None': - return None - if name == 'True': - return True - if name == 'False': - return False - - # See if the Name is a package or module. If it is, import it. - try: - return modules(name) - except ImportError: - pass - - # See if the Name is in builtins. - try: - return getattr(builtins, name) - except AttributeError: - pass - - raise TypeError('unrepr could not resolve the name %s' % repr(name)) - - def build_NameConstant(self, o): - return o.value - - build_Constant = build_NameConstant # Python 3.8 change - - def build_UnaryOp(self, o): - op, operand = map(self.build, [o.op, o.operand]) - return op(operand) - - def build_BinOp(self, o): - left, op, right = map(self.build, [o.left, o.op, o.right]) - return op(left, right) - - def build_Add(self, o): - return operator.add - - def build_Mult(self, o): - return operator.mul - - def build_USub(self, o): - return operator.neg - - def build_Attribute(self, o): - parent = self.build(o.value) - return getattr(parent, o.attr) - - def build_NoneType(self, o): - return None - - -def unrepr(s): - """Return a Python object compiled from a string.""" - if not s: - return s - b = _Builder() - obj = b.astnode(s) - return b.build(obj) - - -def modules(modulePath): - """Load a module and retrieve a reference to that module.""" - __import__(modulePath) - return sys.modules[modulePath] - - -def attributes(full_attribute_name): - """Load a module and retrieve an attribute of that module.""" - - # Parse out the path, module, and attribute - last_dot = full_attribute_name.rfind('.') - attr_name = full_attribute_name[last_dot + 1:] - mod_path = full_attribute_name[:last_dot] - - mod = modules(mod_path) - # Let an AttributeError propagate outward. - try: - attr = getattr(mod, attr_name) - except AttributeError: - raise AttributeError("'%s' object has no attribute '%s'" - % (mod_path, attr_name)) - - # Return a reference to the attribute. - return attr diff --git a/lib/cherrypy/lib/sessions.py b/lib/cherrypy/lib/sessions.py deleted file mode 100644 index 0f56a4fa..00000000 --- a/lib/cherrypy/lib/sessions.py +++ /dev/null @@ -1,904 +0,0 @@ -"""Session implementation for CherryPy. - -You need to edit your config file to use sessions. Here's an example:: - - [/] - tools.sessions.on = True - tools.sessions.storage_class = cherrypy.lib.sessions.FileSession - tools.sessions.storage_path = "/home/site/sessions" - tools.sessions.timeout = 60 - -This sets the session to be stored in files in the directory -/home/site/sessions, and the session timeout to 60 minutes. If you omit -``storage_class``, the sessions will be saved in RAM. -``tools.sessions.on`` is the only required line for working sessions, -the rest are optional. - -By default, the session ID is passed in a cookie, so the client's browser must -have cookies enabled for your site. - -To set data for the current session, use -``cherrypy.session['fieldname'] = 'fieldvalue'``; -to get data use ``cherrypy.session.get('fieldname')``. - -================ -Locking sessions -================ - -By default, the ``'locking'`` mode of sessions is ``'implicit'``, which means -the session is locked early and unlocked late. Be mindful of this default mode -for any requests that take a long time to process (streaming responses, -expensive calculations, database lookups, API calls, etc), as other concurrent -requests that also utilize sessions will hang until the session is unlocked. - -If you want to control when the session data is locked and unlocked, -set ``tools.sessions.locking = 'explicit'``. Then call -``cherrypy.session.acquire_lock()`` and ``cherrypy.session.release_lock()``. -Regardless of which mode you use, the session is guaranteed to be unlocked when -the request is complete. - -================= -Expiring Sessions -================= - -You can force a session to expire with :func:`cherrypy.lib.sessions.expire`. -Simply call that function at the point you want the session to expire, and it -will cause the session cookie to expire client-side. - -=========================== -Session Fixation Protection -=========================== - -If CherryPy receives, via a request cookie, a session id that it does not -recognize, it will reject that id and create a new one to return in the -response cookie. This `helps prevent session fixation attacks -`_. -However, CherryPy "recognizes" a session id by looking up the saved session -data for that id. Therefore, if you never save any session data, -**you will get a new session id for every request**. - -A side effect of CherryPy overwriting unrecognised session ids is that if you -have multiple, separate CherryPy applications running on a single domain (e.g. -on different ports), each app will overwrite the other's session id because by -default they use the same cookie name (``"session_id"``) but do not recognise -each others sessions. It is therefore a good idea to use a different name for -each, for example:: - - [/] - ... - tools.sessions.name = "my_app_session_id" - -================ -Sharing Sessions -================ - -If you run multiple instances of CherryPy (for example via mod_python behind -Apache prefork), you most likely cannot use the RAM session backend, since each -instance of CherryPy will have its own memory space. Use a different backend -instead, and verify that all instances are pointing at the same file or db -location. Alternately, you might try a load balancer which makes sessions -"sticky". Google is your friend, there. - -================ -Expiration Dates -================ - -The response cookie will possess an expiration date to inform the client at -which point to stop sending the cookie back in requests. If the server time -and client time differ, expect sessions to be unreliable. **Make sure the -system time of your server is accurate**. - -CherryPy defaults to a 60-minute session timeout, which also applies to the -cookie which is sent to the client. Unfortunately, some versions of Safari -("4 public beta" on Windows XP at least) appear to have a bug in their parsing -of the GMT expiration date--they appear to interpret the date as one hour in -the past. Sixty minutes minus one hour is pretty close to zero, so you may -experience this bug as a new session id for every request, unless the requests -are less than one second apart. To fix, try increasing the session.timeout. - -On the other extreme, some users report Firefox sending cookies after their -expiration date, although this was on a system with an inaccurate system time. -Maybe FF doesn't trust system time. -""" -import sys -import datetime -import os -import time -import threading -import binascii -import pickle - -import zc.lockfile - -import cherrypy -from cherrypy.lib import httputil -from cherrypy.lib import locking -from cherrypy.lib import is_iterator - - -missing = object() - - -class Session(object): - - """A CherryPy dict-like Session object (one per request).""" - - _id = None - - id_observers = None - "A list of callbacks to which to pass new id's." - - @property - def id(self): - """Return the current session id.""" - return self._id - - @id.setter - def id(self, value): - self._id = value - for o in self.id_observers: - o(value) - - timeout = 60 - 'Number of minutes after which to delete session data.' - - locked = False - """ - If True, this session instance has exclusive read/write access - to session data.""" - - loaded = False - """ - If True, data has been retrieved from storage. This should happen - automatically on the first attempt to access session data.""" - - clean_thread = None - 'Class-level Monitor which calls self.clean_up.' - - clean_freq = 5 - 'The poll rate for expired session cleanup in minutes.' - - originalid = None - 'The session id passed by the client. May be missing or unsafe.' - - missing = False - 'True if the session requested by the client did not exist.' - - regenerated = False - """ - True if the application called session.regenerate(). This is not set by - internal calls to regenerate the session id.""" - - debug = False - 'If True, log debug information.' - - # --------------------- Session management methods --------------------- # - - def __init__(self, id=None, **kwargs): - self.id_observers = [] - self._data = {} - - for k, v in kwargs.items(): - setattr(self, k, v) - - self.originalid = id - self.missing = False - if id is None: - if self.debug: - cherrypy.log('No id given; making a new one', 'TOOLS.SESSIONS') - self._regenerate() - else: - self.id = id - if self._exists(): - if self.debug: - cherrypy.log('Set id to %s.' % id, 'TOOLS.SESSIONS') - else: - if self.debug: - cherrypy.log('Expired or malicious session %r; ' - 'making a new one' % id, 'TOOLS.SESSIONS') - # Expired or malicious session. Make a new one. - # See https://github.com/cherrypy/cherrypy/issues/709. - self.id = None - self.missing = True - self._regenerate() - - def now(self): - """Generate the session specific concept of 'now'. - - Other session providers can override this to use alternative, - possibly timezone aware, versions of 'now'. - """ - return datetime.datetime.now() - - def regenerate(self): - """Replace the current session (with a new id).""" - self.regenerated = True - self._regenerate() - - def _regenerate(self): - if self.id is not None: - if self.debug: - cherrypy.log( - 'Deleting the existing session %r before ' - 'regeneration.' % self.id, - 'TOOLS.SESSIONS') - self.delete() - - old_session_was_locked = self.locked - if old_session_was_locked: - self.release_lock() - if self.debug: - cherrypy.log('Old lock released.', 'TOOLS.SESSIONS') - - self.id = None - while self.id is None: - self.id = self.generate_id() - # Assert that the generated id is not already stored. - if self._exists(): - self.id = None - if self.debug: - cherrypy.log('Set id to generated %s.' % self.id, - 'TOOLS.SESSIONS') - - if old_session_was_locked: - self.acquire_lock() - if self.debug: - cherrypy.log('Regenerated lock acquired.', 'TOOLS.SESSIONS') - - def clean_up(self): - """Clean up expired sessions.""" - pass - - def generate_id(self): - """Return a new session id.""" - return binascii.hexlify(os.urandom(20)).decode('ascii') - - def save(self): - """Save session data.""" - try: - # If session data has never been loaded then it's never been - # accessed: no need to save it - if self.loaded: - t = datetime.timedelta(seconds=self.timeout * 60) - expiration_time = self.now() + t - if self.debug: - cherrypy.log('Saving session %r with expiry %s' % - (self.id, expiration_time), - 'TOOLS.SESSIONS') - self._save(expiration_time) - else: - if self.debug: - cherrypy.log( - 'Skipping save of session %r (no session loaded).' % - self.id, 'TOOLS.SESSIONS') - finally: - if self.locked: - # Always release the lock if the user didn't release it - self.release_lock() - if self.debug: - cherrypy.log('Lock released after save.', 'TOOLS.SESSIONS') - - def load(self): - """Copy stored session data into this session instance.""" - data = self._load() - # data is either None or a tuple (session_data, expiration_time) - if data is None or data[1] < self.now(): - if self.debug: - cherrypy.log('Expired session %r, flushing data.' % self.id, - 'TOOLS.SESSIONS') - self._data = {} - else: - if self.debug: - cherrypy.log('Data loaded for session %r.' % self.id, - 'TOOLS.SESSIONS') - self._data = data[0] - self.loaded = True - - # Stick the clean_thread in the class, not the instance. - # The instances are created and destroyed per-request. - cls = self.__class__ - if self.clean_freq and not cls.clean_thread: - # clean_up is an instancemethod and not a classmethod, - # so that tool config can be accessed inside the method. - t = cherrypy.process.plugins.Monitor( - cherrypy.engine, self.clean_up, self.clean_freq * 60, - name='Session cleanup') - t.subscribe() - cls.clean_thread = t - t.start() - if self.debug: - cherrypy.log('Started cleanup thread.', 'TOOLS.SESSIONS') - - def delete(self): - """Delete stored session data.""" - self._delete() - if self.debug: - cherrypy.log('Deleted session %s.' % self.id, - 'TOOLS.SESSIONS') - - # -------------------- Application accessor methods -------------------- # - - def __getitem__(self, key): - if not self.loaded: - self.load() - return self._data[key] - - def __setitem__(self, key, value): - if not self.loaded: - self.load() - self._data[key] = value - - def __delitem__(self, key): - if not self.loaded: - self.load() - del self._data[key] - - def pop(self, key, default=missing): - """Remove the specified key and return the corresponding value. - If key is not found, default is returned if given, - otherwise KeyError is raised. - """ - if not self.loaded: - self.load() - if default is missing: - return self._data.pop(key) - else: - return self._data.pop(key, default) - - def __contains__(self, key): - if not self.loaded: - self.load() - return key in self._data - - def get(self, key, default=None): - """D.get(k[,d]) -> D[k] if k in D, else d. d defaults to None.""" - if not self.loaded: - self.load() - return self._data.get(key, default) - - def update(self, d): - """D.update(E) -> None. Update D from E: for k in E: D[k] = E[k].""" - if not self.loaded: - self.load() - self._data.update(d) - - def setdefault(self, key, default=None): - """D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D.""" - if not self.loaded: - self.load() - return self._data.setdefault(key, default) - - def clear(self): - """D.clear() -> None. Remove all items from D.""" - if not self.loaded: - self.load() - self._data.clear() - - def keys(self): - """D.keys() -> list of D's keys.""" - if not self.loaded: - self.load() - return self._data.keys() - - def items(self): - """D.items() -> list of D's (key, value) pairs, as 2-tuples.""" - if not self.loaded: - self.load() - return self._data.items() - - def values(self): - """D.values() -> list of D's values.""" - if not self.loaded: - self.load() - return self._data.values() - - -class RamSession(Session): - - # Class-level objects. Don't rebind these! - cache = {} - locks = {} - - def clean_up(self): - """Clean up expired sessions.""" - - now = self.now() - for _id, (data, expiration_time) in self.cache.copy().items(): - if expiration_time <= now: - try: - del self.cache[_id] - except KeyError: - pass - try: - if self.locks[_id].acquire(blocking=False): - lock = self.locks.pop(_id) - lock.release() - except KeyError: - pass - - # added to remove obsolete lock objects - for _id in list(self.locks): - locked = ( - _id not in self.cache - and self.locks[_id].acquire(blocking=False) - ) - if locked: - lock = self.locks.pop(_id) - lock.release() - - def _exists(self): - return self.id in self.cache - - def _load(self): - return self.cache.get(self.id) - - def _save(self, expiration_time): - self.cache[self.id] = (self._data, expiration_time) - - def _delete(self): - self.cache.pop(self.id, None) - - def acquire_lock(self): - """Acquire an exclusive lock on the currently-loaded session data.""" - self.locked = True - self.locks.setdefault(self.id, threading.RLock()).acquire() - - def release_lock(self): - """Release the lock on the currently-loaded session data.""" - self.locks[self.id].release() - self.locked = False - - def __len__(self): - """Return the number of active sessions.""" - return len(self.cache) - - -class FileSession(Session): - - """Implementation of the File backend for sessions - - storage_path - The folder where session data will be saved. Each session - will be saved as pickle.dump(data, expiration_time) in its own file; - the filename will be self.SESSION_PREFIX + self.id. - - lock_timeout - A timedelta or numeric seconds indicating how long - to block acquiring a lock. If None (default), acquiring a lock - will block indefinitely. - """ - - SESSION_PREFIX = 'session-' - LOCK_SUFFIX = '.lock' - pickle_protocol = pickle.HIGHEST_PROTOCOL - - def __init__(self, id=None, **kwargs): - # The 'storage_path' arg is required for file-based sessions. - kwargs['storage_path'] = os.path.abspath(kwargs['storage_path']) - kwargs.setdefault('lock_timeout', None) - - Session.__init__(self, id=id, **kwargs) - - # validate self.lock_timeout - if isinstance(self.lock_timeout, (int, float)): - self.lock_timeout = datetime.timedelta(seconds=self.lock_timeout) - if not isinstance(self.lock_timeout, (datetime.timedelta, type(None))): - raise ValueError( - 'Lock timeout must be numeric seconds or a timedelta instance.' - ) - - @classmethod - def setup(cls, **kwargs): - """Set up the storage system for file-based sessions. - - This should only be called once per process; this will be done - automatically when using sessions.init (as the built-in Tool does). - """ - # The 'storage_path' arg is required for file-based sessions. - kwargs['storage_path'] = os.path.abspath(kwargs['storage_path']) - - for k, v in kwargs.items(): - setattr(cls, k, v) - - def _get_file_path(self): - f = os.path.join(self.storage_path, self.SESSION_PREFIX + self.id) - if not os.path.abspath(f).startswith(self.storage_path): - raise cherrypy.HTTPError(400, 'Invalid session id in cookie.') - return f - - def _exists(self): - path = self._get_file_path() - return os.path.exists(path) - - def _load(self, path=None): - assert self.locked, ('The session load without being locked. ' - "Check your tools' priority levels.") - if path is None: - path = self._get_file_path() - try: - with open(path, 'rb') as f: - return pickle.load(f) - except (IOError, EOFError): - e = sys.exc_info()[1] - if self.debug: - cherrypy.log('Error loading the session pickle: %s' % - e, 'TOOLS.SESSIONS') - return None - - def _save(self, expiration_time): - assert self.locked, ('The session was saved without being locked. ' - "Check your tools' priority levels.") - with open(self._get_file_path(), 'wb') as f: - pickle.dump((self._data, expiration_time), f, self.pickle_protocol) - - def _delete(self): - assert self.locked, ('The session deletion without being locked. ' - "Check your tools' priority levels.") - try: - os.unlink(self._get_file_path()) - except OSError: - pass - - def acquire_lock(self, path=None): - """Acquire an exclusive lock on the currently-loaded session data.""" - if path is None: - path = self._get_file_path() - path += self.LOCK_SUFFIX - checker = locking.LockChecker(self.id, self.lock_timeout) - while not checker.expired(): - try: - self.lock = zc.lockfile.LockFile(path) - except zc.lockfile.LockError: - time.sleep(0.1) - else: - break - self.locked = True - if self.debug: - cherrypy.log('Lock acquired.', 'TOOLS.SESSIONS') - - def release_lock(self, path=None): - """Release the lock on the currently-loaded session data.""" - self.lock.close() - self.locked = False - - def clean_up(self): - """Clean up expired sessions.""" - now = self.now() - # Iterate over all session files in self.storage_path - for fname in os.listdir(self.storage_path): - have_session = ( - fname.startswith(self.SESSION_PREFIX) - and not fname.endswith(self.LOCK_SUFFIX) - ) - if have_session: - # We have a session file: lock and load it and check - # if it's expired. If it fails, nevermind. - path = os.path.join(self.storage_path, fname) - self.acquire_lock(path) - if self.debug: - # This is a bit of a hack, since we're calling clean_up - # on the first instance rather than the entire class, - # so depending on whether you have "debug" set on the - # path of the first session called, this may not run. - cherrypy.log('Cleanup lock acquired.', 'TOOLS.SESSIONS') - - try: - contents = self._load(path) - # _load returns None on IOError - if contents is not None: - data, expiration_time = contents - if expiration_time < now: - # Session expired: deleting it - os.unlink(path) - finally: - self.release_lock(path) - - def __len__(self): - """Return the number of active sessions.""" - return len([fname for fname in os.listdir(self.storage_path) - if (fname.startswith(self.SESSION_PREFIX) and - not fname.endswith(self.LOCK_SUFFIX))]) - - -class MemcachedSession(Session): - - # The most popular memcached client for Python isn't thread-safe. - # Wrap all .get and .set operations in a single lock. - mc_lock = threading.RLock() - - # This is a separate set of locks per session id. - locks = {} - - servers = ['localhost:11211'] - - @classmethod - def setup(cls, **kwargs): - """Set up the storage system for memcached-based sessions. - - This should only be called once per process; this will be done - automatically when using sessions.init (as the built-in Tool does). - """ - for k, v in kwargs.items(): - setattr(cls, k, v) - - import memcache - cls.cache = memcache.Client(cls.servers) - - def _exists(self): - self.mc_lock.acquire() - try: - return bool(self.cache.get(self.id)) - finally: - self.mc_lock.release() - - def _load(self): - self.mc_lock.acquire() - try: - return self.cache.get(self.id) - finally: - self.mc_lock.release() - - def _save(self, expiration_time): - # Send the expiration time as "Unix time" (seconds since 1/1/1970) - td = int(time.mktime(expiration_time.timetuple())) - self.mc_lock.acquire() - try: - if not self.cache.set(self.id, (self._data, expiration_time), td): - raise AssertionError( - 'Session data for id %r not set.' % self.id) - finally: - self.mc_lock.release() - - def _delete(self): - self.cache.delete(self.id) - - def acquire_lock(self): - """Acquire an exclusive lock on the currently-loaded session data.""" - self.locked = True - self.locks.setdefault(self.id, threading.RLock()).acquire() - if self.debug: - cherrypy.log('Lock acquired.', 'TOOLS.SESSIONS') - - def release_lock(self): - """Release the lock on the currently-loaded session data.""" - self.locks[self.id].release() - self.locked = False - - def __len__(self): - """Return the number of active sessions.""" - raise NotImplementedError - - -# Hook functions (for CherryPy tools) - -def save(): - """Save any changed session data.""" - - if not hasattr(cherrypy.serving, 'session'): - return - request = cherrypy.serving.request - response = cherrypy.serving.response - - # Guard against running twice - if hasattr(request, '_sessionsaved'): - return - request._sessionsaved = True - - if response.stream: - # If the body is being streamed, we have to save the data - # *after* the response has been written out - request.hooks.attach('on_end_request', cherrypy.session.save) - else: - # If the body is not being streamed, we save the data now - # (so we can release the lock). - if is_iterator(response.body): - response.collapse_body() - cherrypy.session.save() - - -save.failsafe = True - - -def close(): - """Close the session object for this request.""" - sess = getattr(cherrypy.serving, 'session', None) - if getattr(sess, 'locked', False): - # If the session is still locked we release the lock - sess.release_lock() - if sess.debug: - cherrypy.log('Lock released on close.', 'TOOLS.SESSIONS') - - -close.failsafe = True -close.priority = 90 - - -def init(storage_type=None, path=None, path_header=None, name='session_id', - timeout=60, domain=None, secure=False, clean_freq=5, - persistent=True, httponly=False, debug=False, - # Py27 compat - # *, storage_class=RamSession, - **kwargs): - """Initialize session object (using cookies). - - storage_class - The Session subclass to use. Defaults to RamSession. - - storage_type - (deprecated) - One of 'ram', 'file', memcached'. This will be - used to look up the corresponding class in cherrypy.lib.sessions - globals. For example, 'file' will use the FileSession class. - - path - The 'path' value to stick in the response cookie metadata. - - path_header - If 'path' is None (the default), then the response - cookie 'path' will be pulled from request.headers[path_header]. - - name - The name of the cookie. - - timeout - The expiration timeout (in minutes) for the stored session data. - If 'persistent' is True (the default), this is also the timeout - for the cookie. - - domain - The cookie domain. - - secure - If False (the default) the cookie 'secure' value will not - be set. If True, the cookie 'secure' value will be set (to 1). - - clean_freq (minutes) - The poll rate for expired session cleanup. - - persistent - If True (the default), the 'timeout' argument will be used - to expire the cookie. If False, the cookie will not have an expiry, - and the cookie will be a "session cookie" which expires when the - browser is closed. - - httponly - If False (the default) the cookie 'httponly' value will not be set. - If True, the cookie 'httponly' value will be set (to 1). - - Any additional kwargs will be bound to the new Session instance, - and may be specific to the storage type. See the subclass of Session - you're using for more information. - """ - - # Py27 compat - storage_class = kwargs.pop('storage_class', RamSession) - - request = cherrypy.serving.request - - # Guard against running twice - if hasattr(request, '_session_init_flag'): - return - request._session_init_flag = True - - # Check if request came with a session ID - id = None - if name in request.cookie: - id = request.cookie[name].value - if debug: - cherrypy.log('ID obtained from request.cookie: %r' % id, - 'TOOLS.SESSIONS') - - first_time = not hasattr(cherrypy, 'session') - - if storage_type: - if first_time: - msg = 'storage_type is deprecated. Supply storage_class instead' - cherrypy.log(msg) - storage_class = storage_type.title() + 'Session' - storage_class = globals()[storage_class] - - # call setup first time only - if first_time: - if hasattr(storage_class, 'setup'): - storage_class.setup(**kwargs) - - # Create and attach a new Session instance to cherrypy.serving. - # It will possess a reference to (and lock, and lazily load) - # the requested session data. - kwargs['timeout'] = timeout - kwargs['clean_freq'] = clean_freq - cherrypy.serving.session = sess = storage_class(id, **kwargs) - sess.debug = debug - - def update_cookie(id): - """Update the cookie every time the session id changes.""" - cherrypy.serving.response.cookie[name] = id - sess.id_observers.append(update_cookie) - - # Create cherrypy.session which will proxy to cherrypy.serving.session - if not hasattr(cherrypy, 'session'): - cherrypy.session = cherrypy._ThreadLocalProxy('session') - - if persistent: - cookie_timeout = timeout - else: - # See http://support.microsoft.com/kb/223799/EN-US/ - # and http://support.mozilla.com/en-US/kb/Cookies - cookie_timeout = None - set_response_cookie(path=path, path_header=path_header, name=name, - timeout=cookie_timeout, domain=domain, secure=secure, - httponly=httponly) - - -def set_response_cookie(path=None, path_header=None, name='session_id', - timeout=60, domain=None, secure=False, httponly=False): - """Set a response cookie for the client. - - path - the 'path' value to stick in the response cookie metadata. - - path_header - if 'path' is None (the default), then the response - cookie 'path' will be pulled from request.headers[path_header]. - - name - the name of the cookie. - - timeout - the expiration timeout for the cookie. If 0 or other boolean - False, no 'expires' param will be set, and the cookie will be a - "session cookie" which expires when the browser is closed. - - domain - the cookie domain. - - secure - if False (the default) the cookie 'secure' value will not - be set. If True, the cookie 'secure' value will be set (to 1). - - httponly - If False (the default) the cookie 'httponly' value will not be set. - If True, the cookie 'httponly' value will be set (to 1). - - """ - # Set response cookie - cookie = cherrypy.serving.response.cookie - cookie[name] = cherrypy.serving.session.id - cookie[name]['path'] = ( - path or - cherrypy.serving.request.headers.get(path_header) or - '/' - ) - - if timeout: - cookie[name]['max-age'] = timeout * 60 - _add_MSIE_max_age_workaround(cookie[name], timeout) - if domain is not None: - cookie[name]['domain'] = domain - if secure: - cookie[name]['secure'] = 1 - if httponly: - if not cookie[name].isReservedKey('httponly'): - raise ValueError('The httponly cookie token is not supported.') - cookie[name]['httponly'] = 1 - - -def _add_MSIE_max_age_workaround(cookie, timeout): - """ - We'd like to use the "max-age" param as indicated in - http://www.faqs.org/rfcs/rfc2109.html but IE doesn't - save it to disk and the session is lost if people close - the browser. So we have to use the old "expires" ... sigh ... - """ - expires = time.time() + timeout * 60 - cookie['expires'] = httputil.HTTPDate(expires) - - -def expire(): - """Expire the current session cookie.""" - name = cherrypy.serving.request.config.get( - 'tools.sessions.name', 'session_id') - one_year = 60 * 60 * 24 * 365 - e = time.time() - one_year - cherrypy.serving.response.cookie[name]['expires'] = httputil.HTTPDate(e) - cherrypy.serving.response.cookie[name].pop('max-age', None) diff --git a/lib/cherrypy/lib/static.py b/lib/cherrypy/lib/static.py deleted file mode 100644 index 66a5a947..00000000 --- a/lib/cherrypy/lib/static.py +++ /dev/null @@ -1,416 +0,0 @@ -"""Module with helpers for serving static files.""" - -import os -import platform -import re -import stat -import mimetypes -import urllib.parse -import unicodedata - -from email.generator import _make_boundary as make_boundary -from io import UnsupportedOperation - -import cherrypy -from cherrypy._cpcompat import ntob -from cherrypy.lib import cptools, httputil, file_generator_limited - - -def _setup_mimetypes(): - """Pre-initialize global mimetype map.""" - if not mimetypes.inited: - mimetypes.init() - mimetypes.types_map['.dwg'] = 'image/x-dwg' - mimetypes.types_map['.ico'] = 'image/x-icon' - mimetypes.types_map['.bz2'] = 'application/x-bzip2' - mimetypes.types_map['.gz'] = 'application/x-gzip' - - -_setup_mimetypes() - - -def _make_content_disposition(disposition, file_name): - """Create HTTP header for downloading a file with a UTF-8 filename. - - This function implements the recommendations of :rfc:`6266#appendix-D`. - See this and related answers: https://stackoverflow.com/a/8996249/2173868. - """ - # As normalization algorithm for `unicodedata` is used composed form (NFC - # and NFKC) with compatibility equivalence criteria (NFK), so "NFKC" is the - # one. It first applies the compatibility decomposition, followed by the - # canonical composition. Should be displayed in the same manner, should be - # treated in the same way by applications such as alphabetizing names or - # searching, and may be substituted for each other. - # See: https://en.wikipedia.org/wiki/Unicode_equivalence. - ascii_name = ( - unicodedata.normalize('NFKC', file_name). - encode('ascii', errors='ignore').decode() - ) - header = '{}; filename="{}"'.format(disposition, ascii_name) - if ascii_name != file_name: - quoted_name = urllib.parse.quote(file_name) - header += '; filename*=UTF-8\'\'{}'.format(quoted_name) - return header - - -def serve_file(path, content_type=None, disposition=None, name=None, - debug=False): - """Set status, headers, and body in order to serve the given path. - - The Content-Type header will be set to the content_type arg, if provided. - If not provided, the Content-Type will be guessed by the file extension - of the 'path' argument. - - If disposition is not None, the Content-Disposition header will be set - to "; filename=; filename*=utf-8''" - as described in :rfc:`6266#appendix-D`. - If name is None, it will be set to the basename of path. - If disposition is None, no Content-Disposition header will be written. - """ - response = cherrypy.serving.response - - # If path is relative, users should fix it by making path absolute. - # That is, CherryPy should not guess where the application root is. - # It certainly should *not* use cwd (since CP may be invoked from a - # variety of paths). If using tools.staticdir, you can make your relative - # paths become absolute by supplying a value for "tools.staticdir.root". - if not os.path.isabs(path): - msg = "'%s' is not an absolute path." % path - if debug: - cherrypy.log(msg, 'TOOLS.STATICFILE') - raise ValueError(msg) - - try: - st = os.stat(path) - except (OSError, TypeError, ValueError): - # OSError when file fails to stat - # TypeError on Python 2 when there's a null byte - # ValueError on Python 3 when there's a null byte - if debug: - cherrypy.log('os.stat(%r) failed' % path, 'TOOLS.STATIC') - raise cherrypy.NotFound() - - # Check if path is a directory. - if stat.S_ISDIR(st.st_mode): - # Let the caller deal with it as they like. - if debug: - cherrypy.log('%r is a directory' % path, 'TOOLS.STATIC') - raise cherrypy.NotFound() - - # Set the Last-Modified response header, so that - # modified-since validation code can work. - response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) - cptools.validate_since() - - if content_type is None: - # Set content-type based on filename extension - ext = '' - i = path.rfind('.') - if i != -1: - ext = path[i:].lower() - content_type = mimetypes.types_map.get(ext, None) - if content_type is not None: - response.headers['Content-Type'] = content_type - if debug: - cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') - - cd = None - if disposition is not None: - if name is None: - name = os.path.basename(path) - cd = _make_content_disposition(disposition, name) - response.headers['Content-Disposition'] = cd - if debug: - cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') - - # Set Content-Length and use an iterable (file object) - # this way CP won't load the whole file in memory - content_length = st.st_size - fileobj = open(path, 'rb') - return _serve_fileobj(fileobj, content_type, content_length, debug=debug) - - -def serve_fileobj(fileobj, content_type=None, disposition=None, name=None, - debug=False): - """Set status, headers, and body in order to serve the given file object. - - The Content-Type header will be set to the content_type arg, if provided. - - If disposition is not None, the Content-Disposition header will be set - to "; filename=; filename*=utf-8''" - as described in :rfc:`6266#appendix-D`. - If name is None, 'filename' will not be set. - If disposition is None, no Content-Disposition header will be written. - - CAUTION: If the request contains a 'Range' header, one or more seek()s will - be performed on the file object. This may cause undesired behavior if - the file object is not seekable. It could also produce undesired results - if the caller set the read position of the file object prior to calling - serve_fileobj(), expecting that the data would be served starting from that - position. - """ - response = cherrypy.serving.response - - try: - st = os.fstat(fileobj.fileno()) - except AttributeError: - if debug: - cherrypy.log('os has no fstat attribute', 'TOOLS.STATIC') - content_length = None - except UnsupportedOperation: - content_length = None - else: - # Set the Last-Modified response header, so that - # modified-since validation code can work. - response.headers['Last-Modified'] = httputil.HTTPDate(st.st_mtime) - cptools.validate_since() - content_length = st.st_size - - if content_type is not None: - response.headers['Content-Type'] = content_type - if debug: - cherrypy.log('Content-Type: %r' % content_type, 'TOOLS.STATIC') - - cd = None - if disposition is not None: - if name is None: - cd = disposition - else: - cd = _make_content_disposition(disposition, name) - response.headers['Content-Disposition'] = cd - if debug: - cherrypy.log('Content-Disposition: %r' % cd, 'TOOLS.STATIC') - - return _serve_fileobj(fileobj, content_type, content_length, debug=debug) - - -def _serve_fileobj(fileobj, content_type, content_length, debug=False): - """Internal. Set response.body to the given file object, perhaps ranged.""" - response = cherrypy.serving.response - - # HTTP/1.0 didn't have Range/Accept-Ranges headers, or the 206 code - request = cherrypy.serving.request - if request.protocol >= (1, 1): - response.headers['Accept-Ranges'] = 'bytes' - r = httputil.get_ranges(request.headers.get('Range'), content_length) - if r == []: - response.headers['Content-Range'] = 'bytes */%s' % content_length - message = ('Invalid Range (first-byte-pos greater than ' - 'Content-Length)') - if debug: - cherrypy.log(message, 'TOOLS.STATIC') - raise cherrypy.HTTPError(416, message) - - if r: - if len(r) == 1: - # Return a single-part response. - start, stop = r[0] - if stop > content_length: - stop = content_length - r_len = stop - start - if debug: - cherrypy.log( - 'Single part; start: %r, stop: %r' % (start, stop), - 'TOOLS.STATIC') - response.status = '206 Partial Content' - response.headers['Content-Range'] = ( - 'bytes %s-%s/%s' % (start, stop - 1, content_length)) - response.headers['Content-Length'] = r_len - fileobj.seek(start) - response.body = file_generator_limited(fileobj, r_len) - else: - # Return a multipart/byteranges response. - response.status = '206 Partial Content' - boundary = make_boundary() - ct = 'multipart/byteranges; boundary=%s' % boundary - response.headers['Content-Type'] = ct - if 'Content-Length' in response.headers: - # Delete Content-Length header so finalize() recalcs it. - del response.headers['Content-Length'] - - def file_ranges(): - # Apache compatibility: - yield b'\r\n' - - for start, stop in r: - if debug: - cherrypy.log( - 'Multipart; start: %r, stop: %r' % ( - start, stop), - 'TOOLS.STATIC') - yield ntob('--' + boundary, 'ascii') - yield ntob('\r\nContent-type: %s' % content_type, - 'ascii') - yield ntob( - '\r\nContent-range: bytes %s-%s/%s\r\n\r\n' % ( - start, stop - 1, content_length), - 'ascii') - fileobj.seek(start) - gen = file_generator_limited(fileobj, stop - start) - for chunk in gen: - yield chunk - yield b'\r\n' - # Final boundary - yield ntob('--' + boundary + '--', 'ascii') - - # Apache compatibility: - yield b'\r\n' - response.body = file_ranges() - return response.body - else: - if debug: - cherrypy.log('No byteranges requested', 'TOOLS.STATIC') - - # Set Content-Length and use an iterable (file object) - # this way CP won't load the whole file in memory - response.headers['Content-Length'] = content_length - response.body = fileobj - return response.body - - -def serve_download(path, name=None): - """Serve 'path' as an application/x-download attachment.""" - # This is such a common idiom I felt it deserved its own wrapper. - return serve_file(path, 'application/x-download', 'attachment', name) - - -def _attempt(filename, content_types, debug=False): - if debug: - cherrypy.log('Attempting %r (content_types %r)' % - (filename, content_types), 'TOOLS.STATICDIR') - try: - # you can set the content types for a - # complete directory per extension - content_type = None - if content_types: - r, ext = os.path.splitext(filename) - content_type = content_types.get(ext[1:], None) - serve_file(filename, content_type=content_type, debug=debug) - return True - except cherrypy.NotFound: - # If we didn't find the static file, continue handling the - # request. We might find a dynamic handler instead. - if debug: - cherrypy.log('NotFound', 'TOOLS.STATICFILE') - return False - - -def staticdir(section, dir, root='', match='', content_types=None, index='', - debug=False): - """Serve a static resource from the given (root +) dir. - - match - If given, request.path_info will be searched for the given - regular expression before attempting to serve static content. - - content_types - If given, it should be a Python dictionary of - {file-extension: content-type} pairs, where 'file-extension' is - a string (e.g. "gif") and 'content-type' is the value to write - out in the Content-Type response header (e.g. "image/gif"). - - index - If provided, it should be the (relative) name of a file to - serve for directory requests. For example, if the dir argument is - '/home/me', the Request-URI is 'myapp', and the index arg is - 'index.html', the file '/home/me/myapp/index.html' will be sought. - """ - request = cherrypy.serving.request - if request.method not in ('GET', 'HEAD'): - if debug: - cherrypy.log('request.method not GET or HEAD', 'TOOLS.STATICDIR') - return False - - if match and not re.search(match, request.path_info): - if debug: - cherrypy.log('request.path_info %r does not match pattern %r' % - (request.path_info, match), 'TOOLS.STATICDIR') - return False - - # Allow the use of '~' to refer to a user's home directory. - dir = os.path.expanduser(dir) - - # If dir is relative, make absolute using "root". - if not os.path.isabs(dir): - if not root: - msg = 'Static dir requires an absolute dir (or root).' - if debug: - cherrypy.log(msg, 'TOOLS.STATICDIR') - raise ValueError(msg) - dir = os.path.join(root, dir) - - # Determine where we are in the object tree relative to 'section' - # (where the static tool was defined). - if section == 'global': - section = '/' - section = section.rstrip(r'\/') - branch = request.path_info[len(section) + 1:] - branch = urllib.parse.unquote(branch.lstrip(r'\/')) - - # Requesting a file in sub-dir of the staticdir results - # in mixing of delimiter styles, e.g. C:\static\js/script.js. - # Windows accepts this form except not when the path is - # supplied in extended-path notation, e.g. \\?\C:\static\js/script.js. - # http://bit.ly/1vdioCX - if platform.system() == 'Windows': - branch = branch.replace('/', '\\') - - # If branch is "", filename will end in a slash - filename = os.path.join(dir, branch) - if debug: - cherrypy.log('Checking file %r to fulfill %r' % - (filename, request.path_info), 'TOOLS.STATICDIR') - - # There's a chance that the branch pulled from the URL might - # have ".." or similar uplevel attacks in it. Check that the final - # filename is a child of dir. - if not os.path.normpath(filename).startswith(os.path.normpath(dir)): - raise cherrypy.HTTPError(403) # Forbidden - - handled = _attempt(filename, content_types) - if not handled: - # Check for an index file if a folder was requested. - if index: - handled = _attempt(os.path.join(filename, index), content_types) - if handled: - request.is_index = filename[-1] in (r'\/') - return handled - - -def staticfile(filename, root=None, match='', content_types=None, debug=False): - """Serve a static resource from the given (root +) filename. - - match - If given, request.path_info will be searched for the given - regular expression before attempting to serve static content. - - content_types - If given, it should be a Python dictionary of - {file-extension: content-type} pairs, where 'file-extension' is - a string (e.g. "gif") and 'content-type' is the value to write - out in the Content-Type response header (e.g. "image/gif"). - - """ - request = cherrypy.serving.request - if request.method not in ('GET', 'HEAD'): - if debug: - cherrypy.log('request.method not GET or HEAD', 'TOOLS.STATICFILE') - return False - - if match and not re.search(match, request.path_info): - if debug: - cherrypy.log('request.path_info %r does not match pattern %r' % - (request.path_info, match), 'TOOLS.STATICFILE') - return False - - # If filename is relative, make absolute using "root". - if not os.path.isabs(filename): - if not root: - msg = "Static tool requires an absolute filename (got '%s')." % ( - filename,) - if debug: - cherrypy.log(msg, 'TOOLS.STATICFILE') - raise ValueError(msg) - filename = os.path.join(root, filename) - - return _attempt(filename, content_types, debug=debug) diff --git a/lib/cherrypy/lib/xmlrpcutil.py b/lib/cherrypy/lib/xmlrpcutil.py deleted file mode 100644 index 29d9c4a2..00000000 --- a/lib/cherrypy/lib/xmlrpcutil.py +++ /dev/null @@ -1,60 +0,0 @@ -"""XML-RPC tool helpers.""" -import sys -from xmlrpc.client import ( - loads as xmlrpc_loads, dumps as xmlrpc_dumps, - Fault as XMLRPCFault -) - -import cherrypy -from cherrypy._cpcompat import ntob - - -def process_body(): - """Return (params, method) from request body.""" - try: - return xmlrpc_loads(cherrypy.request.body.read()) - except Exception: - return ('ERROR PARAMS', ), 'ERRORMETHOD' - - -def patched_path(path): - """Return 'path', doctored for RPC.""" - if not path.endswith('/'): - path += '/' - if path.startswith('/RPC2/'): - # strip the first /rpc2 - path = path[5:] - return path - - -def _set_response(body): - """Set up HTTP status, headers and body within CherryPy.""" - # The XML-RPC spec (http://www.xmlrpc.com/spec) says: - # "Unless there's a lower-level error, always return 200 OK." - # Since Python's xmlrpc_client interprets a non-200 response - # as a "Protocol Error", we'll just return 200 every time. - response = cherrypy.response - response.status = '200 OK' - response.body = ntob(body, 'utf-8') - response.headers['Content-Type'] = 'text/xml' - response.headers['Content-Length'] = len(body) - - -def respond(body, encoding='utf-8', allow_none=0): - """Construct HTTP response body.""" - if not isinstance(body, XMLRPCFault): - body = (body,) - - _set_response( - xmlrpc_dumps( - body, methodresponse=1, - encoding=encoding, - allow_none=allow_none - ) - ) - - -def on_error(*args, **kwargs): - """Construct HTTP response body for an error response.""" - body = str(sys.exc_info()[1]) - _set_response(xmlrpc_dumps(XMLRPCFault(1, body))) diff --git a/lib/cherrypy/process/__init__.py b/lib/cherrypy/process/__init__.py deleted file mode 100644 index f242d226..00000000 --- a/lib/cherrypy/process/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Site container for an HTTP server. - -A Web Site Process Bus object is used to connect applications, servers, -and frameworks with site-wide services such as daemonization, process -reload, signal handling, drop privileges, PID file management, logging -for all of these, and many more. - -The 'plugins' module defines a few abstract and concrete services for -use with the bus. Some use tool-specific channels; see the documentation -for each class. -""" - -from .wspbus import bus -from . import plugins, servers - - -__all__ = ('bus', 'plugins', 'servers') diff --git a/lib/cherrypy/process/plugins.py b/lib/cherrypy/process/plugins.py deleted file mode 100644 index e96fb1ce..00000000 --- a/lib/cherrypy/process/plugins.py +++ /dev/null @@ -1,755 +0,0 @@ -"""Site services for use with a Web Site Process Bus.""" - -import os -import re -import signal as _signal -import sys -import time -import threading -import _thread - -from cherrypy._cpcompat import text_or_bytes -from cherrypy._cpcompat import ntob - -# _module__file__base is used by Autoreload to make -# absolute any filenames retrieved from sys.modules which are not -# already absolute paths. This is to work around Python's quirk -# of importing the startup script and using a relative filename -# for it in sys.modules. -# -# Autoreload examines sys.modules afresh every time it runs. If an application -# changes the current directory by executing os.chdir(), then the next time -# Autoreload runs, it will not be able to find any filenames which are -# not absolute paths, because the current directory is not the same as when the -# module was first imported. Autoreload will then wrongly conclude the file -# has "changed", and initiate the shutdown/re-exec sequence. -# See ticket #917. -# For this workaround to have a decent probability of success, this module -# needs to be imported as early as possible, before the app has much chance -# to change the working directory. -_module__file__base = os.getcwd() - - -class SimplePlugin(object): - - """Plugin base class which auto-subscribes methods for known channels.""" - - bus = None - """A :class:`Bus `, usually cherrypy.engine. - """ - - def __init__(self, bus): - self.bus = bus - - def subscribe(self): - """Register this object as a (multi-channel) listener on the bus.""" - for channel in self.bus.listeners: - # Subscribe self.start, self.exit, etc. if present. - method = getattr(self, channel, None) - if method is not None: - self.bus.subscribe(channel, method) - - def unsubscribe(self): - """Unregister this object as a listener on the bus.""" - for channel in self.bus.listeners: - # Unsubscribe self.start, self.exit, etc. if present. - method = getattr(self, channel, None) - if method is not None: - self.bus.unsubscribe(channel, method) - - -class SignalHandler(object): - - """Register bus channels (and listeners) for system signals. - - You can modify what signals your application listens for, and what it does - when it receives signals, by modifying :attr:`SignalHandler.handlers`, - a dict of {signal name: callback} pairs. The default set is:: - - handlers = {'SIGTERM': self.bus.exit, - 'SIGHUP': self.handle_SIGHUP, - 'SIGUSR1': self.bus.graceful, - } - - The :func:`SignalHandler.handle_SIGHUP`` method calls - :func:`bus.restart()` - if the process is daemonized, but - :func:`bus.exit()` - if the process is attached to a TTY. This is because Unix window - managers tend to send SIGHUP to terminal windows when the user closes them. - - Feel free to add signals which are not available on every platform. - The :class:`SignalHandler` will ignore errors raised from attempting - to register handlers for unknown signals. - """ - - handlers = {} - """A map from signal names (e.g. 'SIGTERM') to handlers (e.g. bus.exit).""" - - signals = {} - """A map from signal numbers to names.""" - - for k, v in vars(_signal).items(): - if k.startswith('SIG') and not k.startswith('SIG_'): - signals[v] = k - del k, v - - def __init__(self, bus): - self.bus = bus - # Set default handlers - self.handlers = {'SIGTERM': self.bus.exit, - 'SIGHUP': self.handle_SIGHUP, - 'SIGUSR1': self.bus.graceful, - } - - if sys.platform[:4] == 'java': - del self.handlers['SIGUSR1'] - self.handlers['SIGUSR2'] = self.bus.graceful - self.bus.log('SIGUSR1 cannot be set on the JVM platform. ' - 'Using SIGUSR2 instead.') - self.handlers['SIGINT'] = self._jython_SIGINT_handler - - self._previous_handlers = {} - # used to determine is the process is a daemon in `self._is_daemonized` - self._original_pid = os.getpid() - - def _jython_SIGINT_handler(self, signum=None, frame=None): - # See http://bugs.jython.org/issue1313 - self.bus.log('Keyboard Interrupt: shutting down bus') - self.bus.exit() - - def _is_daemonized(self): - """Return boolean indicating if the current process is - running as a daemon. - - The criteria to determine the `daemon` condition is to verify - if the current pid is not the same as the one that got used on - the initial construction of the plugin *and* the stdin is not - connected to a terminal. - - The sole validation of the tty is not enough when the plugin - is executing inside other process like in a CI tool - (Buildbot, Jenkins). - """ - return ( - self._original_pid != os.getpid() and - not os.isatty(sys.stdin.fileno()) - ) - - def subscribe(self): - """Subscribe self.handlers to signals.""" - for sig, func in self.handlers.items(): - try: - self.set_handler(sig, func) - except ValueError: - pass - - def unsubscribe(self): - """Unsubscribe self.handlers from signals.""" - for signum, handler in self._previous_handlers.items(): - signame = self.signals[signum] - - if handler is None: - self.bus.log('Restoring %s handler to SIG_DFL.' % signame) - handler = _signal.SIG_DFL - else: - self.bus.log('Restoring %s handler %r.' % (signame, handler)) - - try: - our_handler = _signal.signal(signum, handler) - if our_handler is None: - self.bus.log('Restored old %s handler %r, but our ' - 'handler was not registered.' % - (signame, handler), level=30) - except ValueError: - self.bus.log('Unable to restore %s handler %r.' % - (signame, handler), level=40, traceback=True) - - def set_handler(self, signal, listener=None): - """Subscribe a handler for the given signal (number or name). - - If the optional 'listener' argument is provided, it will be - subscribed as a listener for the given signal's channel. - - If the given signal name or number is not available on the current - platform, ValueError is raised. - """ - if isinstance(signal, text_or_bytes): - signum = getattr(_signal, signal, None) - if signum is None: - raise ValueError('No such signal: %r' % signal) - signame = signal - else: - try: - signame = self.signals[signal] - except KeyError: - raise ValueError('No such signal: %r' % signal) - signum = signal - - prev = _signal.signal(signum, self._handle_signal) - self._previous_handlers[signum] = prev - - if listener is not None: - self.bus.log('Listening for %s.' % signame) - self.bus.subscribe(signame, listener) - - def _handle_signal(self, signum=None, frame=None): - """Python signal handler (self.set_handler subscribes it for you).""" - signame = self.signals[signum] - self.bus.log('Caught signal %s.' % signame) - self.bus.publish(signame) - - def handle_SIGHUP(self): - """Restart if daemonized, else exit.""" - if self._is_daemonized(): - self.bus.log('SIGHUP caught while daemonized. Restarting.') - self.bus.restart() - else: - # not daemonized (may be foreground or background) - self.bus.log('SIGHUP caught but not daemonized. Exiting.') - self.bus.exit() - - -try: - import pwd - import grp -except ImportError: - pwd, grp = None, None - - -class DropPrivileges(SimplePlugin): - - """Drop privileges. uid/gid arguments not available on Windows. - - Special thanks to `Gavin Baker - `_ - """ - - def __init__(self, bus, umask=None, uid=None, gid=None): - SimplePlugin.__init__(self, bus) - self.finalized = False - self.uid = uid - self.gid = gid - self.umask = umask - - @property - def uid(self): - """The uid under which to run. Availability: Unix.""" - return self._uid - - @uid.setter - def uid(self, val): - if val is not None: - if pwd is None: - self.bus.log('pwd module not available; ignoring uid.', - level=30) - val = None - elif isinstance(val, text_or_bytes): - val = pwd.getpwnam(val)[2] - self._uid = val - - @property - def gid(self): - """The gid under which to run. Availability: Unix.""" - return self._gid - - @gid.setter - def gid(self, val): - if val is not None: - if grp is None: - self.bus.log('grp module not available; ignoring gid.', - level=30) - val = None - elif isinstance(val, text_or_bytes): - val = grp.getgrnam(val)[2] - self._gid = val - - @property - def umask(self): - """The default permission mode for newly created files and directories. - - Usually expressed in octal format, for example, ``0644``. - Availability: Unix, Windows. - """ - return self._umask - - @umask.setter - def umask(self, val): - if val is not None: - try: - os.umask - except AttributeError: - self.bus.log('umask function not available; ignoring umask.', - level=30) - val = None - self._umask = val - - def start(self): - # uid/gid - def current_ids(): - """Return the current (uid, gid) if available.""" - name, group = None, None - if pwd: - name = pwd.getpwuid(os.getuid())[0] - if grp: - group = grp.getgrgid(os.getgid())[0] - return name, group - - if self.finalized: - if not (self.uid is None and self.gid is None): - self.bus.log('Already running as uid: %r gid: %r' % - current_ids()) - else: - if self.uid is None and self.gid is None: - if pwd or grp: - self.bus.log('uid/gid not set', level=30) - else: - self.bus.log('Started as uid: %r gid: %r' % current_ids()) - if self.gid is not None: - os.setgid(self.gid) - os.setgroups([]) - if self.uid is not None: - os.setuid(self.uid) - self.bus.log('Running as uid: %r gid: %r' % current_ids()) - - # umask - if self.finalized: - if self.umask is not None: - self.bus.log('umask already set to: %03o' % self.umask) - else: - if self.umask is None: - self.bus.log('umask not set', level=30) - else: - old_umask = os.umask(self.umask) - self.bus.log('umask old: %03o, new: %03o' % - (old_umask, self.umask)) - - self.finalized = True - # This is slightly higher than the priority for server.start - # in order to facilitate the most common use: starting on a low - # port (which requires root) and then dropping to another user. - start.priority = 77 - - -class Daemonizer(SimplePlugin): - - """Daemonize the running script. - - Use this with a Web Site Process Bus via:: - - Daemonizer(bus).subscribe() - - When this component finishes, the process is completely decoupled from - the parent environment. Please note that when this component is used, - the return code from the parent process will still be 0 if a startup - error occurs in the forked children. Errors in the initial daemonizing - process still return proper exit codes. Therefore, if you use this - plugin to daemonize, don't use the return code as an accurate indicator - of whether the process fully started. In fact, that return code only - indicates if the process successfully finished the first fork. - """ - - def __init__(self, bus, stdin='/dev/null', stdout='/dev/null', - stderr='/dev/null'): - SimplePlugin.__init__(self, bus) - self.stdin = stdin - self.stdout = stdout - self.stderr = stderr - self.finalized = False - - def start(self): - if self.finalized: - self.bus.log('Already deamonized.') - - # forking has issues with threads: - # http://www.opengroup.org/onlinepubs/000095399/functions/fork.html - # "The general problem with making fork() work in a multi-threaded - # world is what to do with all of the threads..." - # So we check for active threads: - if threading.active_count() != 1: - self.bus.log('There are %r active threads. ' - 'Daemonizing now may cause strange failures.' % - threading.enumerate(), level=30) - - self.daemonize(self.stdin, self.stdout, self.stderr, self.bus.log) - - self.finalized = True - start.priority = 65 - - @staticmethod - def daemonize( - stdin='/dev/null', stdout='/dev/null', stderr='/dev/null', - logger=lambda msg: None): - # See http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 - # (or http://www.faqs.org/faqs/unix-faq/programmer/faq/ section 1.7) - # and http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/66012 - - # Finish up with the current stdout/stderr - sys.stdout.flush() - sys.stderr.flush() - - error_tmpl = ( - '{sys.argv[0]}: fork #{n} failed: ({exc.errno}) {exc.strerror}\n' - ) - - for fork in range(2): - msg = ['Forking once.', 'Forking twice.'][fork] - try: - pid = os.fork() - if pid > 0: - # This is the parent; exit. - logger(msg) - os._exit(0) - except OSError as exc: - # Python raises OSError rather than returning negative numbers. - sys.exit(error_tmpl.format(sys=sys, exc=exc, n=fork + 1)) - if fork == 0: - os.setsid() - - os.umask(0) - - si = open(stdin, 'r') - so = open(stdout, 'a+') - se = open(stderr, 'a+') - - # os.dup2(fd, fd2) will close fd2 if necessary, - # so we don't explicitly close stdin/out/err. - # See http://docs.python.org/lib/os-fd-ops.html - os.dup2(si.fileno(), sys.stdin.fileno()) - os.dup2(so.fileno(), sys.stdout.fileno()) - os.dup2(se.fileno(), sys.stderr.fileno()) - - logger('Daemonized to PID: %s' % os.getpid()) - - -class PIDFile(SimplePlugin): - - """Maintain a PID file via a WSPBus.""" - - def __init__(self, bus, pidfile): - SimplePlugin.__init__(self, bus) - self.pidfile = pidfile - self.finalized = False - - def start(self): - pid = os.getpid() - if self.finalized: - self.bus.log('PID %r already written to %r.' % (pid, self.pidfile)) - else: - with open(self.pidfile, 'wb') as f: - f.write(ntob('%s\n' % pid, 'utf8')) - self.bus.log('PID %r written to %r.' % (pid, self.pidfile)) - self.finalized = True - start.priority = 70 - - def exit(self): - try: - os.remove(self.pidfile) - self.bus.log('PID file removed: %r.' % self.pidfile) - except (KeyboardInterrupt, SystemExit): - raise - except Exception: - pass - - -class PerpetualTimer(threading.Timer): - - """A responsive subclass of threading.Timer whose run() method repeats. - - Use this timer only when you really need a very interruptible timer; - this checks its 'finished' condition up to 20 times a second, which can - results in pretty high CPU usage - """ - - def __init__(self, *args, **kwargs): - "Override parent constructor to allow 'bus' to be provided." - self.bus = kwargs.pop('bus', None) - super(PerpetualTimer, self).__init__(*args, **kwargs) - - def run(self): - while True: - self.finished.wait(self.interval) - if self.finished.isSet(): - return - try: - self.function(*self.args, **self.kwargs) - except Exception: - if self.bus: - self.bus.log( - 'Error in perpetual timer thread function %r.' % - self.function, level=40, traceback=True) - # Quit on first error to avoid massive logs. - raise - - -class BackgroundTask(threading.Thread): - - """A subclass of threading.Thread whose run() method repeats. - - Use this class for most repeating tasks. It uses time.sleep() to wait - for each interval, which isn't very responsive; that is, even if you call - self.cancel(), you'll have to wait until the sleep() call finishes before - the thread stops. To compensate, it defaults to being daemonic, which means - it won't delay stopping the whole process. - """ - - def __init__(self, interval, function, args=[], kwargs={}, bus=None): - super(BackgroundTask, self).__init__() - self.interval = interval - self.function = function - self.args = args - self.kwargs = kwargs - self.running = False - self.bus = bus - - # default to daemonic - self.daemon = True - - def cancel(self): - self.running = False - - def run(self): - self.running = True - while self.running: - time.sleep(self.interval) - if not self.running: - return - try: - self.function(*self.args, **self.kwargs) - except Exception: - if self.bus: - self.bus.log('Error in background task thread function %r.' - % self.function, level=40, traceback=True) - # Quit on first error to avoid massive logs. - raise - - -class Monitor(SimplePlugin): - - """WSPBus listener to periodically run a callback in its own thread.""" - - callback = None - """The function to call at intervals.""" - - frequency = 60 - """The time in seconds between callback runs.""" - - thread = None - """A :class:`BackgroundTask` - thread. - """ - - def __init__(self, bus, callback, frequency=60, name=None): - SimplePlugin.__init__(self, bus) - self.callback = callback - self.frequency = frequency - self.thread = None - self.name = name - - def start(self): - """Start our callback in its own background thread.""" - if self.frequency > 0: - threadname = self.name or self.__class__.__name__ - if self.thread is None: - self.thread = BackgroundTask(self.frequency, self.callback, - bus=self.bus) - self.thread.name = threadname - self.thread.start() - self.bus.log('Started monitor thread %r.' % threadname) - else: - self.bus.log('Monitor thread %r already started.' % threadname) - start.priority = 70 - - def stop(self): - """Stop our callback's background task thread.""" - if self.thread is None: - self.bus.log('No thread running for %s.' % - self.name or self.__class__.__name__) - else: - if self.thread is not threading.current_thread(): - name = self.thread.name - self.thread.cancel() - if not self.thread.daemon: - self.bus.log('Joining %r' % name) - self.thread.join() - self.bus.log('Stopped thread %r.' % name) - self.thread = None - - def graceful(self): - """Stop the callback's background task thread and restart it.""" - self.stop() - self.start() - - -class Autoreloader(Monitor): - - """Monitor which re-executes the process when files change. - - This :ref:`plugin` restarts the process (via :func:`os.execv`) - if any of the files it monitors change (or is deleted). By default, the - autoreloader monitors all imported modules; you can add to the - set by adding to ``autoreload.files``:: - - cherrypy.engine.autoreload.files.add(myFile) - - If there are imported files you do *not* wish to monitor, you can - adjust the ``match`` attribute, a regular expression. For example, - to stop monitoring cherrypy itself:: - - cherrypy.engine.autoreload.match = r'^(?!cherrypy).+' - - Like all :class:`Monitor` plugins, - the autoreload plugin takes a ``frequency`` argument. The default is - 1 second; that is, the autoreloader will examine files once each second. - """ - - files = None - """The set of files to poll for modifications.""" - - frequency = 1 - """The interval in seconds at which to poll for modified files.""" - - match = '.*' - """A regular expression by which to match filenames.""" - - def __init__(self, bus, frequency=1, match='.*'): - self.mtimes = {} - self.files = set() - self.match = match - Monitor.__init__(self, bus, self.run, frequency) - - def start(self): - """Start our own background task thread for self.run.""" - if self.thread is None: - self.mtimes = {} - Monitor.start(self) - start.priority = 70 - - def sysfiles(self): - """Return a Set of sys.modules filenames to monitor.""" - search_mod_names = filter( - re.compile(self.match).match, - list(sys.modules.keys()), - ) - mods = map(sys.modules.get, search_mod_names) - return set(filter(None, map(self._file_for_module, mods))) - - @classmethod - def _file_for_module(cls, module): - """Return the relevant file for the module.""" - return ( - cls._archive_for_zip_module(module) - or cls._file_for_file_module(module) - ) - - @staticmethod - def _archive_for_zip_module(module): - """Return the archive filename for the module if relevant.""" - try: - return module.__loader__.archive - except AttributeError: - pass - - @classmethod - def _file_for_file_module(cls, module): - """Return the file for the module.""" - try: - return module.__file__ and cls._make_absolute(module.__file__) - except AttributeError: - pass - - @staticmethod - def _make_absolute(filename): - """Ensure filename is absolute to avoid effect of os.chdir.""" - return filename if os.path.isabs(filename) else ( - os.path.normpath(os.path.join(_module__file__base, filename)) - ) - - def run(self): - """Reload the process if registered files have been modified.""" - for filename in self.sysfiles() | self.files: - if filename: - if filename.endswith('.pyc'): - filename = filename[:-1] - - oldtime = self.mtimes.get(filename, 0) - if oldtime is None: - # Module with no .py file. Skip it. - continue - - try: - mtime = os.stat(filename).st_mtime - except OSError: - # Either a module with no .py file, or it's been deleted. - mtime = None - - if filename not in self.mtimes: - # If a module has no .py file, this will be None. - self.mtimes[filename] = mtime - else: - if mtime is None or mtime > oldtime: - # The file has been deleted or modified. - self.bus.log('Restarting because %s changed.' % - filename) - self.thread.cancel() - self.bus.log('Stopped thread %r.' % - self.thread.name) - self.bus.restart() - return - - -class ThreadManager(SimplePlugin): - - """Manager for HTTP request threads. - - If you have control over thread creation and destruction, publish to - the 'acquire_thread' and 'release_thread' channels (for each thread). - This will register/unregister the current thread and publish to - 'start_thread' and 'stop_thread' listeners in the bus as needed. - - If threads are created and destroyed by code you do not control - (e.g., Apache), then, at the beginning of every HTTP request, - publish to 'acquire_thread' only. You should not publish to - 'release_thread' in this case, since you do not know whether - the thread will be re-used or not. The bus will call - 'stop_thread' listeners for you when it stops. - """ - - threads = None - """A map of {thread ident: index number} pairs.""" - - def __init__(self, bus): - self.threads = {} - SimplePlugin.__init__(self, bus) - self.bus.listeners.setdefault('acquire_thread', set()) - self.bus.listeners.setdefault('start_thread', set()) - self.bus.listeners.setdefault('release_thread', set()) - self.bus.listeners.setdefault('stop_thread', set()) - - def acquire_thread(self): - """Run 'start_thread' listeners for the current thread. - - If the current thread has already been seen, any 'start_thread' - listeners will not be run again. - """ - thread_ident = _thread.get_ident() - if thread_ident not in self.threads: - # We can't just use get_ident as the thread ID - # because some platforms reuse thread ID's. - i = len(self.threads) + 1 - self.threads[thread_ident] = i - self.bus.publish('start_thread', i) - - def release_thread(self): - """Release the current thread and run 'stop_thread' listeners.""" - thread_ident = _thread.get_ident() - i = self.threads.pop(thread_ident, None) - if i is not None: - self.bus.publish('stop_thread', i) - - def stop(self): - """Release all threads and run all 'stop_thread' listeners.""" - for thread_ident, i in self.threads.items(): - self.bus.publish('stop_thread', i) - self.threads.clear() - graceful = stop diff --git a/lib/cherrypy/process/servers.py b/lib/cherrypy/process/servers.py deleted file mode 100644 index 717a8de0..00000000 --- a/lib/cherrypy/process/servers.py +++ /dev/null @@ -1,416 +0,0 @@ -r""" -Starting in CherryPy 3.1, cherrypy.server is implemented as an -:ref:`Engine Plugin`. It's an instance of -:class:`cherrypy._cpserver.Server`, which is a subclass of -:class:`cherrypy.process.servers.ServerAdapter`. The ``ServerAdapter`` class -is designed to control other servers, as well. - -Multiple servers/ports -====================== - -If you need to start more than one HTTP server (to serve on multiple ports, or -protocols, etc.), you can manually register each one and then start them all -with engine.start:: - - s1 = ServerAdapter( - cherrypy.engine, - MyWSGIServer(host='0.0.0.0', port=80) - ) - s2 = ServerAdapter( - cherrypy.engine, - another.HTTPServer(host='127.0.0.1', SSL=True) - ) - s1.subscribe() - s2.subscribe() - cherrypy.engine.start() - -.. index:: SCGI - -FastCGI/SCGI -============ - -There are also Flup\ **F**\ CGIServer and Flup\ **S**\ CGIServer classes in -:mod:`cherrypy.process.servers`. To start an fcgi server, for example, -wrap an instance of it in a ServerAdapter:: - - addr = ('0.0.0.0', 4000) - f = servers.FlupFCGIServer(application=cherrypy.tree, bindAddress=addr) - s = servers.ServerAdapter(cherrypy.engine, httpserver=f, bind_addr=addr) - s.subscribe() - -The :doc:`cherryd` startup script will do the above for -you via its `-f` flag. -Note that you need to download and install `flup `_ -yourself, whether you use ``cherryd`` or not. - -.. _fastcgi: -.. index:: FastCGI - -FastCGI -------- - -A very simple setup lets your cherry run with FastCGI. -You just need the flup library, -plus a running Apache server (with ``mod_fastcgi``) or lighttpd server. - -CherryPy code -^^^^^^^^^^^^^ - -hello.py:: - - #!/usr/bin/python - import cherrypy - - class HelloWorld: - '''Sample request handler class.''' - @cherrypy.expose - def index(self): - return "Hello world!" - - cherrypy.tree.mount(HelloWorld()) - # CherryPy autoreload must be disabled for the flup server to work - cherrypy.config.update({'engine.autoreload.on':False}) - -Then run :doc:`/deployguide/cherryd` with the '-f' arg:: - - cherryd -c -d -f -i hello.py - -Apache -^^^^^^ - -At the top level in httpd.conf:: - - FastCgiIpcDir /tmp - FastCgiServer /path/to/cherry.fcgi -idle-timeout 120 -processes 4 - -And inside the relevant VirtualHost section:: - - # FastCGI config - AddHandler fastcgi-script .fcgi - ScriptAliasMatch (.*$) /path/to/cherry.fcgi$1 - -Lighttpd -^^^^^^^^ - -For `Lighttpd `_ you can follow these -instructions. Within ``lighttpd.conf`` make sure ``mod_fastcgi`` is -active within ``server.modules``. Then, within your ``$HTTP["host"]`` -directive, configure your fastcgi script like the following:: - - $HTTP["url"] =~ "" { - fastcgi.server = ( - "/" => ( - "script.fcgi" => ( - "bin-path" => "/path/to/your/script.fcgi", - "socket" => "/tmp/script.sock", - "check-local" => "disable", - "disable-time" => 1, - "min-procs" => 1, - "max-procs" => 1, # adjust as needed - ), - ), - ) - } # end of $HTTP["url"] =~ "^/" - -Please see `Lighttpd FastCGI Docs -`_ for -an explanation of the possible configuration options. -""" - -import os -import sys -import time -import warnings -import contextlib - -import portend - - -class Timeouts: - occupied = 5 - free = 1 - - -class ServerAdapter(object): - - """Adapter for an HTTP server. - - If you need to start more than one HTTP server (to serve on multiple - ports, or protocols, etc.), you can manually register each one and then - start them all with bus.start:: - - s1 = ServerAdapter(bus, MyWSGIServer(host='0.0.0.0', port=80)) - s2 = ServerAdapter(bus, another.HTTPServer(host='127.0.0.1', SSL=True)) - s1.subscribe() - s2.subscribe() - bus.start() - """ - - def __init__(self, bus, httpserver=None, bind_addr=None): - self.bus = bus - self.httpserver = httpserver - self.bind_addr = bind_addr - self.interrupt = None - self.running = False - - def subscribe(self): - self.bus.subscribe('start', self.start) - self.bus.subscribe('stop', self.stop) - - def unsubscribe(self): - self.bus.unsubscribe('start', self.start) - self.bus.unsubscribe('stop', self.stop) - - def start(self): - """Start the HTTP server.""" - if self.running: - self.bus.log('Already serving on %s' % self.description) - return - - self.interrupt = None - if not self.httpserver: - raise ValueError('No HTTP server has been created.') - - if not os.environ.get('LISTEN_PID', None): - # Start the httpserver in a new thread. - if isinstance(self.bind_addr, tuple): - portend.free(*self.bind_addr, timeout=Timeouts.free) - - import threading - t = threading.Thread(target=self._start_http_thread) - t.name = 'HTTPServer ' + t.name - t.start() - - self.wait() - self.running = True - self.bus.log('Serving on %s' % self.description) - start.priority = 75 - - @property - def description(self): - """ - A description about where this server is bound. - """ - if self.bind_addr is None: - on_what = 'unknown interface (dynamic?)' - elif isinstance(self.bind_addr, tuple): - on_what = self._get_base() - else: - on_what = 'socket file: %s' % self.bind_addr - return on_what - - def _get_base(self): - if not self.httpserver: - return '' - host, port = self.bound_addr - if getattr(self.httpserver, 'ssl_adapter', None): - scheme = 'https' - if port != 443: - host += ':%s' % port - else: - scheme = 'http' - if port != 80: - host += ':%s' % port - - return '%s://%s' % (scheme, host) - - def _start_http_thread(self): - """HTTP servers MUST be running in new threads, so that the - main thread persists to receive KeyboardInterrupt's. If an - exception is raised in the httpserver's thread then it's - trapped here, and the bus (and therefore our httpserver) - are shut down. - """ - try: - self.httpserver.start() - except KeyboardInterrupt: - self.bus.log(' hit: shutting down HTTP server') - self.interrupt = sys.exc_info()[1] - self.bus.exit() - except SystemExit: - self.bus.log('SystemExit raised: shutting down HTTP server') - self.interrupt = sys.exc_info()[1] - self.bus.exit() - raise - except Exception: - self.interrupt = sys.exc_info()[1] - self.bus.log('Error in HTTP server: shutting down', - traceback=True, level=40) - self.bus.exit() - raise - - def wait(self): - """Wait until the HTTP server is ready to receive requests.""" - while not getattr(self.httpserver, 'ready', False): - if self.interrupt: - raise self.interrupt - time.sleep(.1) - - # bypass check when LISTEN_PID is set - if os.environ.get('LISTEN_PID', None): - return - - # bypass check when running via socket-activation - # (for socket-activation the port will be managed by systemd) - if not isinstance(self.bind_addr, tuple): - return - - # wait for port to be occupied - with _safe_wait(*self.bound_addr): - portend.occupied(*self.bound_addr, timeout=Timeouts.occupied) - - @property - def bound_addr(self): - """ - The bind address, or if it's an ephemeral port and the - socket has been bound, return the actual port bound. - """ - host, port = self.bind_addr - if port == 0 and self.httpserver.socket: - # Bound to ephemeral port. Get the actual port allocated. - port = self.httpserver.socket.getsockname()[1] - return host, port - - def stop(self): - """Stop the HTTP server.""" - if self.running: - # stop() MUST block until the server is *truly* stopped. - self.httpserver.stop() - # Wait for the socket to be truly freed. - if isinstance(self.bind_addr, tuple): - portend.free(*self.bound_addr, timeout=Timeouts.free) - self.running = False - self.bus.log('HTTP Server %s shut down' % self.httpserver) - else: - self.bus.log('HTTP Server %s already shut down' % self.httpserver) - stop.priority = 25 - - def restart(self): - """Restart the HTTP server.""" - self.stop() - self.start() - - -class FlupCGIServer(object): - - """Adapter for a flup.server.cgi.WSGIServer.""" - - def __init__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs - self.ready = False - - def start(self): - """Start the CGI server.""" - # We have to instantiate the server class here because its __init__ - # starts a threadpool. If we do it too early, daemonize won't work. - from flup.server.cgi import WSGIServer - - self.cgiserver = WSGIServer(*self.args, **self.kwargs) - self.ready = True - self.cgiserver.run() - - def stop(self): - """Stop the HTTP server.""" - self.ready = False - - -class FlupFCGIServer(object): - - """Adapter for a flup.server.fcgi.WSGIServer.""" - - def __init__(self, *args, **kwargs): - if kwargs.get('bindAddress', None) is None: - import socket - if not hasattr(socket, 'fromfd'): - raise ValueError( - 'Dynamic FCGI server not available on this platform. ' - 'You must use a static or external one by providing a ' - 'legal bindAddress.') - self.args = args - self.kwargs = kwargs - self.ready = False - - def start(self): - """Start the FCGI server.""" - # We have to instantiate the server class here because its __init__ - # starts a threadpool. If we do it too early, daemonize won't work. - from flup.server.fcgi import WSGIServer - self.fcgiserver = WSGIServer(*self.args, **self.kwargs) - # TODO: report this bug upstream to flup. - # If we don't set _oldSIGs on Windows, we get: - # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", - # line 108, in run - # self._restoreSignalHandlers() - # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", - # line 156, in _restoreSignalHandlers - # for signum,handler in self._oldSIGs: - # AttributeError: 'WSGIServer' object has no attribute '_oldSIGs' - self.fcgiserver._installSignalHandlers = lambda: None - self.fcgiserver._oldSIGs = [] - self.ready = True - self.fcgiserver.run() - - def stop(self): - """Stop the HTTP server.""" - # Forcibly stop the fcgi server main event loop. - self.fcgiserver._keepGoing = False - # Force all worker threads to die off. - self.fcgiserver._threadPool.maxSpare = ( - self.fcgiserver._threadPool._idleCount) - self.ready = False - - -class FlupSCGIServer(object): - - """Adapter for a flup.server.scgi.WSGIServer.""" - - def __init__(self, *args, **kwargs): - self.args = args - self.kwargs = kwargs - self.ready = False - - def start(self): - """Start the SCGI server.""" - # We have to instantiate the server class here because its __init__ - # starts a threadpool. If we do it too early, daemonize won't work. - from flup.server.scgi import WSGIServer - self.scgiserver = WSGIServer(*self.args, **self.kwargs) - # TODO: report this bug upstream to flup. - # If we don't set _oldSIGs on Windows, we get: - # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", - # line 108, in run - # self._restoreSignalHandlers() - # File "C:\Python24\Lib\site-packages\flup\server\threadedserver.py", - # line 156, in _restoreSignalHandlers - # for signum,handler in self._oldSIGs: - # AttributeError: 'WSGIServer' object has no attribute '_oldSIGs' - self.scgiserver._installSignalHandlers = lambda: None - self.scgiserver._oldSIGs = [] - self.ready = True - self.scgiserver.run() - - def stop(self): - """Stop the HTTP server.""" - self.ready = False - # Forcibly stop the scgi server main event loop. - self.scgiserver._keepGoing = False - # Force all worker threads to die off. - self.scgiserver._threadPool.maxSpare = 0 - - -@contextlib.contextmanager -def _safe_wait(host, port): - """ - On systems where a loopback interface is not available and the - server is bound to all interfaces, it's difficult to determine - whether the server is in fact occupying the port. In this case, - just issue a warning and move on. See issue #1100. - """ - try: - yield - except portend.Timeout: - if host == portend.client_host(host): - raise - msg = 'Unable to verify that the server is bound on %r' % port - warnings.warn(msg) diff --git a/lib/cherrypy/process/win32.py b/lib/cherrypy/process/win32.py deleted file mode 100644 index b7a79b1b..00000000 --- a/lib/cherrypy/process/win32.py +++ /dev/null @@ -1,183 +0,0 @@ -"""Windows service. Requires pywin32.""" - -import os -import win32api -import win32con -import win32event -import win32service -import win32serviceutil - -from cherrypy.process import wspbus, plugins - - -class ConsoleCtrlHandler(plugins.SimplePlugin): - - """A WSPBus plugin for handling Win32 console events (like Ctrl-C).""" - - def __init__(self, bus): - self.is_set = False - plugins.SimplePlugin.__init__(self, bus) - - def start(self): - if self.is_set: - self.bus.log('Handler for console events already set.', level=20) - return - - result = win32api.SetConsoleCtrlHandler(self.handle, 1) - if result == 0: - self.bus.log('Could not SetConsoleCtrlHandler (error %r)' % - win32api.GetLastError(), level=40) - else: - self.bus.log('Set handler for console events.', level=20) - self.is_set = True - - def stop(self): - if not self.is_set: - self.bus.log('Handler for console events already off.', level=20) - return - - try: - result = win32api.SetConsoleCtrlHandler(self.handle, 0) - except ValueError: - # "ValueError: The object has not been registered" - result = 1 - - if result == 0: - self.bus.log('Could not remove SetConsoleCtrlHandler (error %r)' % - win32api.GetLastError(), level=40) - else: - self.bus.log('Removed handler for console events.', level=20) - self.is_set = False - - def handle(self, event): - """Handle console control events (like Ctrl-C).""" - if event in (win32con.CTRL_C_EVENT, win32con.CTRL_LOGOFF_EVENT, - win32con.CTRL_BREAK_EVENT, win32con.CTRL_SHUTDOWN_EVENT, - win32con.CTRL_CLOSE_EVENT): - self.bus.log('Console event %s: shutting down bus' % event) - - # Remove self immediately so repeated Ctrl-C doesn't re-call it. - try: - self.stop() - except ValueError: - pass - - self.bus.exit() - # 'First to return True stops the calls' - return 1 - return 0 - - -class Win32Bus(wspbus.Bus): - - """A Web Site Process Bus implementation for Win32. - - Instead of time.sleep, this bus blocks using native win32event objects. - """ - - def __init__(self): - self.events = {} - wspbus.Bus.__init__(self) - - def _get_state_event(self, state): - """Return a win32event for the given state (creating it if needed).""" - try: - return self.events[state] - except KeyError: - event = win32event.CreateEvent(None, 0, 0, - 'WSPBus %s Event (pid=%r)' % - (state.name, os.getpid())) - self.events[state] = event - return event - - @property - def state(self): - return self._state - - @state.setter - def state(self, value): - self._state = value - event = self._get_state_event(value) - win32event.PulseEvent(event) - - def wait(self, state, interval=0.1, channel=None): - """Wait for the given state(s), KeyboardInterrupt or SystemExit. - - Since this class uses native win32event objects, the interval - argument is ignored. - """ - if isinstance(state, (tuple, list)): - # Don't wait for an event that beat us to the punch ;) - if self.state not in state: - events = tuple([self._get_state_event(s) for s in state]) - win32event.WaitForMultipleObjects( - events, 0, win32event.INFINITE) - else: - # Don't wait for an event that beat us to the punch ;) - if self.state != state: - event = self._get_state_event(state) - win32event.WaitForSingleObject(event, win32event.INFINITE) - - -class _ControlCodes(dict): - - """Control codes used to "signal" a service via ControlService. - - User-defined control codes are in the range 128-255. We generally use - the standard Python value for the Linux signal and add 128. Example: - - >>> signal.SIGUSR1 - 10 - control_codes['graceful'] = 128 + 10 - """ - - def key_for(self, obj): - """For the given value, return its corresponding key.""" - for key, val in self.items(): - if val is obj: - return key - raise ValueError('The given object could not be found: %r' % obj) - - -control_codes = _ControlCodes({'graceful': 138}) - - -def signal_child(service, command): - if command == 'stop': - win32serviceutil.StopService(service) - elif command == 'restart': - win32serviceutil.RestartService(service) - else: - win32serviceutil.ControlService(service, control_codes[command]) - - -class PyWebService(win32serviceutil.ServiceFramework): - - """Python Web Service.""" - - _svc_name_ = 'Python Web Service' - _svc_display_name_ = 'Python Web Service' - _svc_deps_ = None # sequence of service names on which this depends - _exe_name_ = 'pywebsvc' - _exe_args_ = None # Default to no arguments - - # Only exists on Windows 2000 or later, ignored on windows NT - _svc_description_ = 'Python Web Service' - - def SvcDoRun(self): - from cherrypy import process - process.bus.start() - process.bus.block() - - def SvcStop(self): - from cherrypy import process - self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING) - process.bus.exit() - - def SvcOther(self, control): - from cherrypy import process - process.bus.publish(control_codes.key_for(control)) - - -if __name__ == '__main__': - win32serviceutil.HandleCommandLine(PyWebService) diff --git a/lib/cherrypy/process/wspbus.py b/lib/cherrypy/process/wspbus.py deleted file mode 100644 index 1d2789b1..00000000 --- a/lib/cherrypy/process/wspbus.py +++ /dev/null @@ -1,587 +0,0 @@ -r"""An implementation of the Web Site Process Bus. - -This module is completely standalone, depending only on the stdlib. - -Web Site Process Bus --------------------- - -A Bus object is used to contain and manage site-wide behavior: -daemonization, HTTP server start/stop, process reload, signal handling, -drop privileges, PID file management, logging for all of these, -and many more. - -In addition, a Bus object provides a place for each web framework -to register code that runs in response to site-wide events (like -process start and stop), or which controls or otherwise interacts with -the site-wide components mentioned above. For example, a framework which -uses file-based templates would add known template filenames to an -autoreload component. - -Ideally, a Bus object will be flexible enough to be useful in a variety -of invocation scenarios: - - 1. The deployer starts a site from the command line via a - framework-neutral deployment script; applications from multiple frameworks - are mixed in a single site. Command-line arguments and configuration - files are used to define site-wide components such as the HTTP server, - WSGI component graph, autoreload behavior, signal handling, etc. - 2. The deployer starts a site via some other process, such as Apache; - applications from multiple frameworks are mixed in a single site. - Autoreload and signal handling (from Python at least) are disabled. - 3. The deployer starts a site via a framework-specific mechanism; - for example, when running tests, exploring tutorials, or deploying - single applications from a single framework. The framework controls - which site-wide components are enabled as it sees fit. - -The Bus object in this package uses topic-based publish-subscribe -messaging to accomplish all this. A few topic channels are built in -('start', 'stop', 'exit', 'graceful', 'log', and 'main'). Frameworks and -site containers are free to define their own. If a message is sent to a -channel that has not been defined or has no listeners, there is no effect. - -In general, there should only ever be a single Bus object per process. -Frameworks and site containers share a single Bus object by publishing -messages and subscribing listeners. - -The Bus object works as a finite state machine which models the current -state of the process. Bus methods move it from one state to another; -those methods then publish to subscribed listeners on the channel for -the new state.:: - - O - | - V - STOPPING --> STOPPED --> EXITING -> X - A A | - | \___ | - | \ | - | V V - STARTED <-- STARTING - -""" - -import atexit - -try: - import ctypes -except ImportError: - """Google AppEngine is shipped without ctypes - - :seealso: http://stackoverflow.com/a/6523777/70170 - """ - ctypes = None - -import operator -import os -import sys -import threading -import time -import traceback as _traceback -import warnings -import subprocess -import functools - -from more_itertools import always_iterable - - -# Here I save the value of os.getcwd(), which, if I am imported early enough, -# will be the directory from which the startup script was run. This is needed -# by _do_execv(), to change back to the original directory before execv()ing a -# new process. This is a defense against the application having changed the -# current working directory (which could make sys.executable "not found" if -# sys.executable is a relative-path, and/or cause other problems). -_startup_cwd = os.getcwd() - - -class ChannelFailures(Exception): - """Exception raised during errors on Bus.publish().""" - - delimiter = '\n' - - def __init__(self, *args, **kwargs): - """Initialize ChannelFailures errors wrapper.""" - super(ChannelFailures, self).__init__(*args, **kwargs) - self._exceptions = list() - - def handle_exception(self): - """Append the current exception to self.""" - self._exceptions.append(sys.exc_info()[1]) - - def get_instances(self): - """Return a list of seen exception instances.""" - return self._exceptions[:] - - def __str__(self): - """Render the list of errors, which happened in channel.""" - exception_strings = map(repr, self.get_instances()) - return self.delimiter.join(exception_strings) - - __repr__ = __str__ - - def __bool__(self): - """Determine whether any error happened in channel.""" - return bool(self._exceptions) - __nonzero__ = __bool__ - -# Use a flag to indicate the state of the bus. - - -class _StateEnum(object): - - class State(object): - name = None - - def __repr__(self): - return 'states.%s' % self.name - - def __setattr__(self, key, value): - if isinstance(value, self.State): - value.name = key - object.__setattr__(self, key, value) - - -states = _StateEnum() -states.STOPPED = states.State() -states.STARTING = states.State() -states.STARTED = states.State() -states.STOPPING = states.State() -states.EXITING = states.State() - - -try: - import fcntl -except ImportError: - max_files = 0 -else: - try: - max_files = os.sysconf('SC_OPEN_MAX') - except AttributeError: - max_files = 1024 - - -class Bus(object): - """Process state-machine and messenger for HTTP site deployment. - - All listeners for a given channel are guaranteed to be called even - if others at the same channel fail. Each failure is logged, but - execution proceeds on to the next listener. The only way to stop all - processing from inside a listener is to raise SystemExit and stop the - whole server. - """ - - states = states - state = states.STOPPED - execv = False - max_cloexec_files = max_files - - def __init__(self): - """Initialize pub/sub bus.""" - self.execv = False - self.state = states.STOPPED - channels = 'start', 'stop', 'exit', 'graceful', 'log', 'main' - self.listeners = dict( - (channel, set()) - for channel in channels - ) - self._priorities = {} - - def subscribe(self, channel, callback=None, priority=None): - """Add the given callback at the given channel (if not present). - - If callback is None, return a partial suitable for decorating - the callback. - """ - if callback is None: - return functools.partial( - self.subscribe, - channel, - priority=priority, - ) - - ch_listeners = self.listeners.setdefault(channel, set()) - ch_listeners.add(callback) - - if priority is None: - priority = getattr(callback, 'priority', 50) - self._priorities[(channel, callback)] = priority - - def unsubscribe(self, channel, callback): - """Discard the given callback (if present).""" - listeners = self.listeners.get(channel) - if listeners and callback in listeners: - listeners.discard(callback) - del self._priorities[(channel, callback)] - - def publish(self, channel, *args, **kwargs): - """Return output of all subscribers for the given channel.""" - if channel not in self.listeners: - return [] - - exc = ChannelFailures() - output = [] - - raw_items = ( - (self._priorities[(channel, listener)], listener) - for listener in self.listeners[channel] - ) - items = sorted(raw_items, key=operator.itemgetter(0)) - for priority, listener in items: - try: - output.append(listener(*args, **kwargs)) - except KeyboardInterrupt: - raise - except SystemExit: - e = sys.exc_info()[1] - # If we have previous errors ensure the exit code is non-zero - if exc and e.code == 0: - e.code = 1 - raise - except Exception: - exc.handle_exception() - if channel == 'log': - # Assume any further messages to 'log' will fail. - pass - else: - self.log('Error in %r listener %r' % (channel, listener), - level=40, traceback=True) - if exc: - raise exc - return output - - def _clean_exit(self): - """Assert that the Bus is not running in atexit handler callback.""" - if self.state != states.EXITING: - warnings.warn( - 'The main thread is exiting, but the Bus is in the %r state; ' - 'shutting it down automatically now. You must either call ' - 'bus.block() after start(), or call bus.exit() before the ' - 'main thread exits.' % self.state, RuntimeWarning) - self.exit() - - def start(self): - """Start all services.""" - atexit.register(self._clean_exit) - - self.state = states.STARTING - self.log('Bus STARTING') - try: - self.publish('start') - self.state = states.STARTED - self.log('Bus STARTED') - except (KeyboardInterrupt, SystemExit): - raise - except Exception: - self.log('Shutting down due to error in start listener:', - level=40, traceback=True) - e_info = sys.exc_info()[1] - try: - self.exit() - except Exception: - # Any stop/exit errors will be logged inside publish(). - pass - # Re-raise the original error - raise e_info - - def exit(self): - """Stop all services and prepare to exit the process.""" - exitstate = self.state - EX_SOFTWARE = 70 - try: - self.stop() - - self.state = states.EXITING - self.log('Bus EXITING') - self.publish('exit') - # This isn't strictly necessary, but it's better than seeing - # "Waiting for child threads to terminate..." and then nothing. - self.log('Bus EXITED') - except Exception: - # This method is often called asynchronously (whether thread, - # signal handler, console handler, or atexit handler), so we - # can't just let exceptions propagate out unhandled. - # Assume it's been logged and just die. - os._exit(EX_SOFTWARE) - - if exitstate == states.STARTING: - # exit() was called before start() finished, possibly due to - # Ctrl-C because a start listener got stuck. In this case, - # we could get stuck in a loop where Ctrl-C never exits the - # process, so we just call os.exit here. - os._exit(EX_SOFTWARE) - - def restart(self): - """Restart the process (may close connections). - - This method does not restart the process from the calling thread; - instead, it stops the bus and asks the main thread to call execv. - """ - self.execv = True - self.exit() - - def graceful(self): - """Advise all services to reload.""" - self.log('Bus graceful') - self.publish('graceful') - - def block(self, interval=0.1): - """Wait for the EXITING state, KeyboardInterrupt or SystemExit. - - This function is intended to be called only by the main thread. - After waiting for the EXITING state, it also waits for all threads - to terminate, and then calls os.execv if self.execv is True. This - design allows another thread to call bus.restart, yet have the main - thread perform the actual execv call (required on some platforms). - """ - try: - self.wait(states.EXITING, interval=interval, channel='main') - except (KeyboardInterrupt, IOError): - # The time.sleep call might raise - # "IOError: [Errno 4] Interrupted function call" on KBInt. - self.log('Keyboard Interrupt: shutting down bus') - self.exit() - except SystemExit: - self.log('SystemExit raised: shutting down bus') - self.exit() - raise - - # Waiting for ALL child threads to finish is necessary on OS X. - # See https://github.com/cherrypy/cherrypy/issues/581. - # It's also good to let them all shut down before allowing - # the main thread to call atexit handlers. - # See https://github.com/cherrypy/cherrypy/issues/751. - self.log('Waiting for child threads to terminate...') - for t in threading.enumerate(): - # Validate the we're not trying to join the MainThread - # that will cause a deadlock and the case exist when - # implemented as a windows service and in any other case - # that another thread executes cherrypy.engine.exit() - if ( - t != threading.current_thread() and - not isinstance(t, threading._MainThread) and - # Note that any dummy (external) threads are - # always daemonic. - not t.daemon - ): - self.log('Waiting for thread %s.' % t.name) - t.join() - - if self.execv: - self._do_execv() - - def wait(self, state, interval=0.1, channel=None): - """Poll for the given state(s) at intervals; publish to channel.""" - states = set(always_iterable(state)) - - while self.state not in states: - time.sleep(interval) - self.publish(channel) - - def _do_execv(self): - """Re-execute the current process. - - This must be called from the main thread, because certain platforms - (OS X) don't allow execv to be called in a child thread very well. - """ - try: - args = self._get_true_argv() - except NotImplementedError: - """It's probably win32 or GAE""" - args = [sys.executable] + self._get_interpreter_argv() + sys.argv - - self.log('Re-spawning %s' % ' '.join(args)) - - self._extend_pythonpath(os.environ) - - if sys.platform[:4] == 'java': - from _systemrestart import SystemRestart - raise SystemRestart - else: - if sys.platform == 'win32': - args = ['"%s"' % arg for arg in args] - - os.chdir(_startup_cwd) - if self.max_cloexec_files: - self._set_cloexec() - os.execv(sys.executable, args) - - @staticmethod - def _get_interpreter_argv(): - """Retrieve current Python interpreter's arguments. - - Returns empty tuple in case of frozen mode, uses built-in arguments - reproduction function otherwise. - - Frozen mode is possible for the app has been packaged into a binary - executable using py2exe. In this case the interpreter's arguments are - already built-in into that executable. - - :seealso: https://github.com/cherrypy/cherrypy/issues/1526 - Ref: https://pythonhosted.org/PyInstaller/runtime-information.html - """ - return ([] - if getattr(sys, 'frozen', False) - else subprocess._args_from_interpreter_flags()) - - @staticmethod - def _get_true_argv(): - """Retrieve all real arguments of the python interpreter. - - ...even those not listed in ``sys.argv`` - - :seealso: http://stackoverflow.com/a/28338254/595220 - :seealso: http://stackoverflow.com/a/6683222/595220 - :seealso: http://stackoverflow.com/a/28414807/595220 - """ - try: - char_p = ctypes.c_wchar_p - - argv = ctypes.POINTER(char_p)() - argc = ctypes.c_int() - - ctypes.pythonapi.Py_GetArgcArgv( - ctypes.byref(argc), - ctypes.byref(argv), - ) - - _argv = argv[:argc.value] - - # The code below is trying to correctly handle special cases. - # `-c`'s argument interpreted by Python itself becomes `-c` as - # well. Same applies to `-m`. This snippet is trying to survive - # at least the case with `-m` - # Ref: https://github.com/cherrypy/cherrypy/issues/1545 - # Ref: python/cpython@418baf9 - argv_len, is_command, is_module = len(_argv), False, False - - try: - m_ind = _argv.index('-m') - if m_ind < argv_len - 1 and _argv[m_ind + 1] in ('-c', '-m'): - """ - In some older Python versions `-m`'s argument may be - substituted with `-c`, not `-m` - """ - is_module = True - except (IndexError, ValueError): - m_ind = None - - try: - c_ind = _argv.index('-c') - if c_ind < argv_len - 1 and _argv[c_ind + 1] == '-c': - is_command = True - except (IndexError, ValueError): - c_ind = None - - if is_module: - """It's containing `-m -m` sequence of arguments""" - if is_command and c_ind < m_ind: - """There's `-c -c` before `-m`""" - raise RuntimeError( - "Cannot reconstruct command from '-c'. Ref: " - 'https://github.com/cherrypy/cherrypy/issues/1545') - # Survive module argument here - original_module = sys.argv[0] - if not os.access(original_module, os.R_OK): - """There's no such module exist""" - raise AttributeError( - "{} doesn't seem to be a module " - 'accessible by current user'.format(original_module)) - del _argv[m_ind:m_ind + 2] # remove `-m -m` - # ... and substitute it with the original module path: - _argv.insert(m_ind, original_module) - elif is_command: - """It's containing just `-c -c` sequence of arguments""" - raise RuntimeError( - "Cannot reconstruct command from '-c'. " - 'Ref: https://github.com/cherrypy/cherrypy/issues/1545') - except AttributeError: - """It looks Py_GetArgcArgv is completely absent in some environments - - It is known, that there's no Py_GetArgcArgv in MS Windows and - ``ctypes`` module is completely absent in Google AppEngine - - :seealso: https://github.com/cherrypy/cherrypy/issues/1506 - :seealso: https://github.com/cherrypy/cherrypy/issues/1512 - :ref: http://bit.ly/2gK6bXK - """ - raise NotImplementedError - else: - return _argv - - @staticmethod - def _extend_pythonpath(env): - """Prepend current working dir to PATH environment variable if needed. - - If sys.path[0] is an empty string, the interpreter was likely - invoked with -m and the effective path is about to change on - re-exec. Add the current directory to $PYTHONPATH to ensure - that the new process sees the same path. - - This issue cannot be addressed in the general case because - Python cannot reliably reconstruct the - original command line (http://bugs.python.org/issue14208). - - (This idea filched from tornado.autoreload) - """ - path_prefix = '.' + os.pathsep - existing_path = env.get('PYTHONPATH', '') - needs_patch = ( - sys.path[0] == '' and - not existing_path.startswith(path_prefix) - ) - - if needs_patch: - env['PYTHONPATH'] = path_prefix + existing_path - - def _set_cloexec(self): - """Set the CLOEXEC flag on all open files (except stdin/out/err). - - If self.max_cloexec_files is an integer (the default), then on - platforms which support it, it represents the max open files setting - for the operating system. This function will be called just before - the process is restarted via os.execv() to prevent open files - from persisting into the new process. - - Set self.max_cloexec_files to 0 to disable this behavior. - """ - for fd in range(3, self.max_cloexec_files): # skip stdin/out/err - try: - flags = fcntl.fcntl(fd, fcntl.F_GETFD) - except IOError: - continue - fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) - - def stop(self): - """Stop all services.""" - self.state = states.STOPPING - self.log('Bus STOPPING') - self.publish('stop') - self.state = states.STOPPED - self.log('Bus STOPPED') - - def start_with_callback(self, func, args=None, kwargs=None): - """Start 'func' in a new thread T, then start self (and return T).""" - if args is None: - args = () - if kwargs is None: - kwargs = {} - args = (func,) + args - - def _callback(func, *a, **kw): - self.wait(states.STARTED) - func(*a, **kw) - t = threading.Thread(target=_callback, args=args, kwargs=kwargs) - t.name = 'Bus Callback ' + t.name - t.start() - - self.start() - - return t - - def log(self, msg='', level=20, traceback=False): - """Log the given message. Append the last traceback if requested.""" - if traceback: - msg += '\n' + ''.join(_traceback.format_exception(*sys.exc_info())) - self.publish('log', msg, level) - - -bus = Bus() diff --git a/lib/cherrypy/scaffold/__init__.py b/lib/cherrypy/scaffold/__init__.py deleted file mode 100644 index bcddba2d..00000000 --- a/lib/cherrypy/scaffold/__init__.py +++ /dev/null @@ -1,63 +0,0 @@ -""", a CherryPy application. - -Use this as a base for creating new CherryPy applications. When you want -to make a new app, copy and paste this folder to some other location -(maybe site-packages) and rename it to the name of your project, -then tweak as desired. - -Even before any tweaking, this should serve a few demonstration pages. -Change to this directory and run: - - cherryd -c site.conf - -""" - -import cherrypy -from cherrypy import tools, url - -import os -local_dir = os.path.join(os.getcwd(), os.path.dirname(__file__)) - - -@cherrypy.config(**{'tools.log_tracebacks.on': True}) -class Root: - """Declaration of the CherryPy app URI structure.""" - - @cherrypy.expose - def index(self): - """Render HTML-template at the root path of the web-app.""" - return """ -Try some other path, -or a default path.
-Or, just look at the pretty picture:
- -""" % (url('other'), url('else'), - url('files/made_with_cherrypy_small.png')) - - @cherrypy.expose - def default(self, *args, **kwargs): - """Render catch-all args and kwargs.""" - return 'args: %s kwargs: %s' % (args, kwargs) - - @cherrypy.expose - def other(self, a=2, b='bananas', c=None): - """Render number of fruits based on third argument.""" - cherrypy.response.headers['Content-Type'] = 'text/plain' - if c is None: - return 'Have %d %s.' % (int(a), b) - else: - return 'Have %d %s, %s.' % (int(a), b, c) - - files = tools.staticdir.handler( - section='/files', - dir=os.path.join(local_dir, 'static'), - # Ignore .php files, etc. - match=r'\.(css|gif|html?|ico|jpe?g|js|png|swf|xml)$', - ) - - -root = Root() - -# Uncomment the following to use your own favicon instead of CP's default. -# favicon_path = os.path.join(local_dir, "favicon.ico") -# root.favicon_ico = tools.staticfile.handler(filename=favicon_path) diff --git a/lib/cherrypy/scaffold/apache-fcgi.conf b/lib/cherrypy/scaffold/apache-fcgi.conf deleted file mode 100644 index 6e4f144c..00000000 --- a/lib/cherrypy/scaffold/apache-fcgi.conf +++ /dev/null @@ -1,22 +0,0 @@ -# Apache2 server conf file for using CherryPy with mod_fcgid. - -# This doesn't have to be "C:/", but it has to be a directory somewhere, and -# MUST match the directory used in the FastCgiExternalServer directive, below. -DocumentRoot "C:/" - -ServerName 127.0.0.1 -Listen 80 -LoadModule fastcgi_module modules/mod_fastcgi.dll -LoadModule rewrite_module modules/mod_rewrite.so - -Options ExecCGI -SetHandler fastcgi-script -RewriteEngine On -# Send requests for any URI to our fastcgi handler. -RewriteRule ^(.*)$ /fastcgi.pyc [L] - -# The FastCgiExternalServer directive defines filename as an external FastCGI application. -# If filename does not begin with a slash (/) then it is assumed to be relative to the ServerRoot. -# The filename does not have to exist in the local filesystem. URIs that Apache resolves to this -# filename will be handled by this external FastCGI application. -FastCgiExternalServer "C:/fastcgi.pyc" -host 127.0.0.1:8088 diff --git a/lib/cherrypy/scaffold/example.conf b/lib/cherrypy/scaffold/example.conf deleted file mode 100644 index 63250fe3..00000000 --- a/lib/cherrypy/scaffold/example.conf +++ /dev/null @@ -1,3 +0,0 @@ -[/] -log.error_file: "error.log" -log.access_file: "access.log" diff --git a/lib/cherrypy/scaffold/site.conf b/lib/cherrypy/scaffold/site.conf deleted file mode 100644 index 6ed38983..00000000 --- a/lib/cherrypy/scaffold/site.conf +++ /dev/null @@ -1,14 +0,0 @@ -[global] -# Uncomment this when you're done developing -#environment: "production" - -server.socket_host: "0.0.0.0" -server.socket_port: 8088 - -# Uncomment the following lines to run on HTTPS at the same time -#server.2.socket_host: "0.0.0.0" -#server.2.socket_port: 8433 -#server.2.ssl_certificate: '../test/test.pem' -#server.2.ssl_private_key: '../test/test.pem' - -tree.myapp: cherrypy.Application(scaffold.root, "/", "example.conf") diff --git a/lib/cherrypy/scaffold/static/made_with_cherrypy_small.png b/lib/cherrypy/scaffold/static/made_with_cherrypy_small.png deleted file mode 100644 index 724f9d72..00000000 Binary files a/lib/cherrypy/scaffold/static/made_with_cherrypy_small.png and /dev/null differ diff --git a/lib/cherrypy/test/__init__.py b/lib/cherrypy/test/__init__.py deleted file mode 100644 index 068382be..00000000 --- a/lib/cherrypy/test/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -""" -Regression test suite for CherryPy. -""" - -import os -import sys - - -def newexit(): - os._exit(1) - - -def setup(): - # We want to monkey patch sys.exit so that we can get some - # information about where exit is being called. - newexit._old = sys.exit - sys.exit = newexit - - -def teardown(): - try: - sys.exit = sys.exit._old - except AttributeError: - sys.exit = sys._exit diff --git a/lib/cherrypy/test/_test_decorators.py b/lib/cherrypy/test/_test_decorators.py deleted file mode 100644 index 74832e40..00000000 --- a/lib/cherrypy/test/_test_decorators.py +++ /dev/null @@ -1,39 +0,0 @@ -"""Test module for the @-decorator syntax, which is version-specific""" - -import cherrypy -from cherrypy import expose, tools - - -class ExposeExamples(object): - - @expose - def no_call(self): - return 'Mr E. R. Bradshaw' - - @expose() - def call_empty(self): - return 'Mrs. B.J. Smegma' - - @expose('call_alias') - def nesbitt(self): - return 'Mr Nesbitt' - - @expose(['alias1', 'alias2']) - def andrews(self): - return 'Mr Ken Andrews' - - @expose(alias='alias3') - def watson(self): - return 'Mr. and Mrs. Watson' - - -class ToolExamples(object): - - @expose - # This is here to demonstrate that using the config decorator - # does not overwrite other config attributes added by the Tool - # decorator (in this case response_headers). - @cherrypy.config(**{'response.stream': True}) - @tools.response_headers(headers=[('Content-Type', 'application/data')]) - def blah(self): - yield b'blah' diff --git a/lib/cherrypy/test/_test_states_demo.py b/lib/cherrypy/test/_test_states_demo.py deleted file mode 100644 index a49407ba..00000000 --- a/lib/cherrypy/test/_test_states_demo.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -import sys -import time - -import cherrypy - -starttime = time.time() - - -class Root: - - @cherrypy.expose - def index(self): - return 'Hello World' - - @cherrypy.expose - def mtimes(self): - return repr(cherrypy.engine.publish('Autoreloader', 'mtimes')) - - @cherrypy.expose - def pid(self): - return str(os.getpid()) - - @cherrypy.expose - def start(self): - return repr(starttime) - - @cherrypy.expose - def exit(self): - # This handler might be called before the engine is STARTED if an - # HTTP worker thread handles it before the HTTP server returns - # control to engine.start. We avoid that race condition here - # by waiting for the Bus to be STARTED. - cherrypy.engine.wait(state=cherrypy.engine.states.STARTED) - cherrypy.engine.exit() - - -@cherrypy.engine.subscribe('start', priority=100) -def unsub_sig(): - cherrypy.log('unsubsig: %s' % cherrypy.config.get('unsubsig', False)) - if cherrypy.config.get('unsubsig', False): - cherrypy.log('Unsubscribing the default cherrypy signal handler') - cherrypy.engine.signal_handler.unsubscribe() - try: - from signal import signal, SIGTERM - except ImportError: - pass - else: - def old_term_handler(signum=None, frame=None): - cherrypy.log('I am an old SIGTERM handler.') - sys.exit(0) - cherrypy.log('Subscribing the new one.') - signal(SIGTERM, old_term_handler) - - -@cherrypy.engine.subscribe('start', priority=6) -def starterror(): - if cherrypy.config.get('starterror', False): - 1 / 0 - - -@cherrypy.engine.subscribe('start', priority=6) -def log_test_case_name(): - if cherrypy.config.get('test_case_name', False): - cherrypy.log('STARTED FROM: %s' % - cherrypy.config.get('test_case_name')) - - -cherrypy.tree.mount(Root(), '/', {'/': {}}) diff --git a/lib/cherrypy/test/benchmark.py b/lib/cherrypy/test/benchmark.py deleted file mode 100644 index 44dfeff1..00000000 --- a/lib/cherrypy/test/benchmark.py +++ /dev/null @@ -1,425 +0,0 @@ -"""CherryPy Benchmark Tool - - Usage: - benchmark.py [options] - - --null: use a null Request object (to bench the HTTP server only) - --notests: start the server but do not run the tests; this allows - you to check the tested pages with a browser - --help: show this help message - --cpmodpy: run tests via apache on 54583 (with the builtin _cpmodpy) - --modpython: run tests via apache on 54583 (with modpython_gateway) - --ab=path: Use the ab script/executable at 'path' (see below) - --apache=path: Use the apache script/exe at 'path' (see below) - - To run the benchmarks, the Apache Benchmark tool "ab" must either be on - your system path, or specified via the --ab=path option. - - To run the modpython tests, the "apache" executable or script must be - on your system path, or provided via the --apache=path option. On some - platforms, "apache" may be called "apachectl" or "apache2ctl"--create - a symlink to them if needed. -""" - -import getopt -import os -import re -import sys -import time - -import cherrypy -from cherrypy import _cperror, _cpmodpy -from cherrypy.lib import httputil - - -curdir = os.path.join(os.getcwd(), os.path.dirname(__file__)) - -AB_PATH = '' -APACHE_PATH = 'apache' -SCRIPT_NAME = '/cpbench/users/rdelon/apps/blog' - -__all__ = ['ABSession', 'Root', 'print_report', - 'run_standard_benchmarks', 'safe_threads', - 'size_report', 'thread_report', - ] - -size_cache = {} - - -class Root: - - @cherrypy.expose - def index(self): - return """ - - CherryPy Benchmark - - - - -""" - - @cherrypy.expose - def hello(self): - return 'Hello, world\r\n' - - @cherrypy.expose - def sizer(self, size): - resp = size_cache.get(size, None) - if resp is None: - size_cache[size] = resp = 'X' * int(size) - return resp - - -def init(): - - cherrypy.config.update({ - 'log.error.file': '', - 'environment': 'production', - 'server.socket_host': '127.0.0.1', - 'server.socket_port': 54583, - 'server.max_request_header_size': 0, - 'server.max_request_body_size': 0, - }) - - # Cheat mode on ;) - del cherrypy.config['tools.log_tracebacks.on'] - del cherrypy.config['tools.log_headers.on'] - del cherrypy.config['tools.trailing_slash.on'] - - appconf = { - '/static': { - 'tools.staticdir.on': True, - 'tools.staticdir.dir': 'static', - 'tools.staticdir.root': curdir, - }, - } - globals().update( - app=cherrypy.tree.mount(Root(), SCRIPT_NAME, appconf), - ) - - -class NullRequest: - - """A null HTTP request class, returning 200 and an empty body.""" - - def __init__(self, local, remote, scheme='http'): - pass - - def close(self): - pass - - def run(self, method, path, query_string, protocol, headers, rfile): - cherrypy.response.status = '200 OK' - cherrypy.response.header_list = [('Content-Type', 'text/html'), - ('Server', 'Null CherryPy'), - ('Date', httputil.HTTPDate()), - ('Content-Length', '0'), - ] - cherrypy.response.body = [''] - return cherrypy.response - - -class NullResponse: - pass - - -class ABSession: - - """A session of 'ab', the Apache HTTP server benchmarking tool. - -Example output from ab: - -This is ApacheBench, Version 2.0.40-dev <$Revision: 1.121.2.1 $> apache-2.0 -Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ -Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/ - -Benchmarking 127.0.0.1 (be patient) -Completed 100 requests -Completed 200 requests -Completed 300 requests -Completed 400 requests -Completed 500 requests -Completed 600 requests -Completed 700 requests -Completed 800 requests -Completed 900 requests - - -Server Software: CherryPy/3.1beta -Server Hostname: 127.0.0.1 -Server Port: 54583 - -Document Path: /static/index.html -Document Length: 14 bytes - -Concurrency Level: 10 -Time taken for tests: 9.643867 seconds -Complete requests: 1000 -Failed requests: 0 -Write errors: 0 -Total transferred: 189000 bytes -HTML transferred: 14000 bytes -Requests per second: 103.69 [#/sec] (mean) -Time per request: 96.439 [ms] (mean) -Time per request: 9.644 [ms] (mean, across all concurrent requests) -Transfer rate: 19.08 [Kbytes/sec] received - -Connection Times (ms) - min mean[+/-sd] median max -Connect: 0 0 2.9 0 10 -Processing: 20 94 7.3 90 130 -Waiting: 0 43 28.1 40 100 -Total: 20 95 7.3 100 130 - -Percentage of the requests served within a certain time (ms) - 50% 100 - 66% 100 - 75% 100 - 80% 100 - 90% 100 - 95% 100 - 98% 100 - 99% 110 - 100% 130 (longest request) -Finished 1000 requests -""" - - parse_patterns = [ - ('complete_requests', 'Completed', - br'^Complete requests:\s*(\d+)'), - ('failed_requests', 'Failed', - br'^Failed requests:\s*(\d+)'), - ('requests_per_second', 'req/sec', - br'^Requests per second:\s*([0-9.]+)'), - ('time_per_request_concurrent', 'msec/req', - br'^Time per request:\s*([0-9.]+).*concurrent requests\)$'), - ('transfer_rate', 'KB/sec', - br'^Transfer rate:\s*([0-9.]+)') - ] - - def __init__(self, path=SCRIPT_NAME + '/hello', requests=1000, - concurrency=10): - self.path = path - self.requests = requests - self.concurrency = concurrency - - def args(self): - port = cherrypy.server.socket_port - assert self.concurrency > 0 - assert self.requests > 0 - # Don't use "localhost". - # Cf - # http://mail.python.org/pipermail/python-win32/2008-March/007050.html - return ('-k -n %s -c %s http://127.0.0.1:%s%s' % - (self.requests, self.concurrency, port, self.path)) - - def run(self): - # Parse output of ab, setting attributes on self - try: - self.output = _cpmodpy.read_process(AB_PATH or 'ab', self.args()) - except Exception: - print(_cperror.format_exc()) - raise - - for attr, name, pattern in self.parse_patterns: - val = re.search(pattern, self.output, re.MULTILINE) - if val: - val = val.group(1) - setattr(self, attr, val) - else: - setattr(self, attr, None) - - -safe_threads = (25, 50, 100, 200, 400) -if sys.platform in ('win32',): - # For some reason, ab crashes with > 50 threads on my Win2k laptop. - safe_threads = (10, 20, 30, 40, 50) - - -def thread_report(path=SCRIPT_NAME + '/hello', concurrency=safe_threads): - sess = ABSession(path) - attrs, names, patterns = list(zip(*sess.parse_patterns)) - avg = dict.fromkeys(attrs, 0.0) - - yield ('threads',) + names - for c in concurrency: - sess.concurrency = c - sess.run() - row = [c] - for attr in attrs: - val = getattr(sess, attr) - if val is None: - print(sess.output) - row = None - break - val = float(val) - avg[attr] += float(val) - row.append(val) - if row: - yield row - - # Add a row of averages. - yield ['Average'] + [str(avg[attr] / len(concurrency)) for attr in attrs] - - -def size_report(sizes=(10, 100, 1000, 10000, 100000, 100000000), - concurrency=50): - sess = ABSession(concurrency=concurrency) - attrs, names, patterns = list(zip(*sess.parse_patterns)) - yield ('bytes',) + names - for sz in sizes: - sess.path = '%s/sizer?size=%s' % (SCRIPT_NAME, sz) - sess.run() - yield [sz] + [getattr(sess, attr) for attr in attrs] - - -def print_report(rows): - for row in rows: - print('') - for val in row: - sys.stdout.write(str(val).rjust(10) + ' | ') - print('') - - -def run_standard_benchmarks(): - print('') - print('Client Thread Report (1000 requests, 14 byte response body, ' - '%s server threads):' % cherrypy.server.thread_pool) - print_report(thread_report()) - - print('') - print('Client Thread Report (1000 requests, 14 bytes via staticdir, ' - '%s server threads):' % cherrypy.server.thread_pool) - print_report(thread_report('%s/static/index.html' % SCRIPT_NAME)) - - print('') - print('Size Report (1000 requests, 50 client threads, ' - '%s server threads):' % cherrypy.server.thread_pool) - print_report(size_report()) - - -# modpython and other WSGI # - -def startup_modpython(req=None): - """Start the CherryPy app server in 'serverless' mode (for modpython/WSGI). - """ - if cherrypy.engine.state == cherrypy._cpengine.STOPPED: - if req: - if 'nullreq' in req.get_options(): - cherrypy.engine.request_class = NullRequest - cherrypy.engine.response_class = NullResponse - ab_opt = req.get_options().get('ab', '') - if ab_opt: - global AB_PATH - AB_PATH = ab_opt - cherrypy.engine.start() - if cherrypy.engine.state == cherrypy._cpengine.STARTING: - cherrypy.engine.wait() - return 0 # apache.OK - - -def run_modpython(use_wsgi=False): - print('Starting mod_python...') - pyopts = [] - - # Pass the null and ab=path options through Apache - if '--null' in opts: - pyopts.append(('nullreq', '')) - - if '--ab' in opts: - pyopts.append(('ab', opts['--ab'])) - - s = _cpmodpy.ModPythonServer - if use_wsgi: - pyopts.append(('wsgi.application', 'cherrypy::tree')) - pyopts.append( - ('wsgi.startup', 'cherrypy.test.benchmark::startup_modpython')) - handler = 'modpython_gateway::handler' - s = s(port=54583, opts=pyopts, - apache_path=APACHE_PATH, handler=handler) - else: - pyopts.append( - ('cherrypy.setup', 'cherrypy.test.benchmark::startup_modpython')) - s = s(port=54583, opts=pyopts, apache_path=APACHE_PATH) - - try: - s.start() - run() - finally: - s.stop() - - -if __name__ == '__main__': - init() - - longopts = ['cpmodpy', 'modpython', 'null', 'notests', - 'help', 'ab=', 'apache='] - try: - switches, args = getopt.getopt(sys.argv[1:], '', longopts) - opts = dict(switches) - except getopt.GetoptError: - print(__doc__) - sys.exit(2) - - if '--help' in opts: - print(__doc__) - sys.exit(0) - - if '--ab' in opts: - AB_PATH = opts['--ab'] - - if '--notests' in opts: - # Return without stopping the server, so that the pages - # can be tested from a standard web browser. - def run(): - port = cherrypy.server.socket_port - print('You may now open http://127.0.0.1:%s%s/' % - (port, SCRIPT_NAME)) - - if '--null' in opts: - print('Using null Request object') - else: - def run(): - end = time.time() - start - print('Started in %s seconds' % end) - if '--null' in opts: - print('\nUsing null Request object') - try: - try: - run_standard_benchmarks() - except Exception: - print(_cperror.format_exc()) - raise - finally: - cherrypy.engine.exit() - - print('Starting CherryPy app server...') - - class NullWriter(object): - - """Suppresses the printing of socket errors.""" - - def write(self, data): - pass - sys.stderr = NullWriter() - - start = time.time() - - if '--cpmodpy' in opts: - run_modpython() - elif '--modpython' in opts: - run_modpython(use_wsgi=True) - else: - if '--null' in opts: - cherrypy.server.request_class = NullRequest - cherrypy.server.response_class = NullResponse - - cherrypy.engine.start_with_callback(run) - cherrypy.engine.block() diff --git a/lib/cherrypy/test/checkerdemo.py b/lib/cherrypy/test/checkerdemo.py deleted file mode 100644 index 3438bd0c..00000000 --- a/lib/cherrypy/test/checkerdemo.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Demonstration app for cherrypy.checker. - -This application is intentionally broken and badly designed. -To demonstrate the output of the CherryPy Checker, simply execute -this module. -""" - -import os -import cherrypy -thisdir = os.path.dirname(os.path.abspath(__file__)) - - -class Root: - pass - - -if __name__ == '__main__': - conf = {'/base': {'tools.staticdir.root': thisdir, - # Obsolete key. - 'throw_errors': True, - }, - # This entry should be OK. - '/base/static': {'tools.staticdir.on': True, - 'tools.staticdir.dir': 'static'}, - # Warn on missing folder. - '/base/js': {'tools.staticdir.on': True, - 'tools.staticdir.dir': 'js'}, - # Warn on dir with an abs path even though we provide root. - '/base/static2': {'tools.staticdir.on': True, - 'tools.staticdir.dir': '/static'}, - # Warn on dir with a relative path with no root. - '/static3': {'tools.staticdir.on': True, - 'tools.staticdir.dir': 'static'}, - # Warn on unknown namespace - '/unknown': {'toobles.gzip.on': True}, - # Warn special on cherrypy..* - '/cpknown': {'cherrypy.tools.encode.on': True}, - # Warn on mismatched types - '/conftype': {'request.show_tracebacks': 14}, - # Warn on unknown tool. - '/web': {'tools.unknown.on': True}, - # Warn on server.* in app config. - '/app1': {'server.socket_host': '0.0.0.0'}, - # Warn on 'localhost' - 'global': {'server.socket_host': 'localhost'}, - # Warn on '[name]' - '[/extra_brackets]': {}, - } - cherrypy.quickstart(Root(), config=conf) diff --git a/lib/cherrypy/test/fastcgi.conf b/lib/cherrypy/test/fastcgi.conf deleted file mode 100644 index e5c5163c..00000000 --- a/lib/cherrypy/test/fastcgi.conf +++ /dev/null @@ -1,18 +0,0 @@ - -# Apache2 server conf file for testing CherryPy with mod_fastcgi. -# fumanchu: I had to hard-code paths due to crazy Debian layouts :( -ServerRoot /usr/lib/apache2 -User #1000 -ErrorLog /usr/lib/python2.5/site-packages/cproot/trunk/cherrypy/test/mod_fastcgi.error.log - -DocumentRoot "/usr/lib/python2.5/site-packages/cproot/trunk/cherrypy/test" -ServerName 127.0.0.1 -Listen 8080 -LoadModule fastcgi_module modules/mod_fastcgi.so -LoadModule rewrite_module modules/mod_rewrite.so - -Options +ExecCGI -SetHandler fastcgi-script -RewriteEngine On -RewriteRule ^(.*)$ /fastcgi.pyc [L] -FastCgiExternalServer "/usr/lib/python2.5/site-packages/cproot/trunk/cherrypy/test/fastcgi.pyc" -host 127.0.0.1:4000 diff --git a/lib/cherrypy/test/fcgi.conf b/lib/cherrypy/test/fcgi.conf deleted file mode 100644 index 3062eb35..00000000 --- a/lib/cherrypy/test/fcgi.conf +++ /dev/null @@ -1,14 +0,0 @@ - -# Apache2 server conf file for testing CherryPy with mod_fcgid. - -DocumentRoot "/usr/lib/python2.6/site-packages/cproot/trunk/cherrypy/test" -ServerName 127.0.0.1 -Listen 8080 -LoadModule fastcgi_module modules/mod_fastcgi.dll -LoadModule rewrite_module modules/mod_rewrite.so - -Options ExecCGI -SetHandler fastcgi-script -RewriteEngine On -RewriteRule ^(.*)$ /fastcgi.pyc [L] -FastCgiExternalServer "/usr/lib/python2.6/site-packages/cproot/trunk/cherrypy/test/fastcgi.pyc" -host 127.0.0.1:4000 diff --git a/lib/cherrypy/test/helper.py b/lib/cherrypy/test/helper.py deleted file mode 100644 index cae49533..00000000 --- a/lib/cherrypy/test/helper.py +++ /dev/null @@ -1,520 +0,0 @@ -"""A library of helper functions for the CherryPy test suite.""" - -import datetime -import io -import logging -import os -import re -import subprocess -import sys -import time -import unittest -import warnings -import contextlib - -import portend -import pytest - -from cheroot.test import webtest - -import cherrypy -from cherrypy._cpcompat import text_or_bytes, HTTPSConnection, ntob -from cherrypy.lib import httputil -from cherrypy.lib import gctools - -log = logging.getLogger(__name__) -thisdir = os.path.abspath(os.path.dirname(__file__)) -serverpem = os.path.join(os.getcwd(), thisdir, 'test.pem') - - -class Supervisor(object): - - """Base class for modeling and controlling servers during testing.""" - - def __init__(self, **kwargs): - for k, v in kwargs.items(): - if k == 'port': - setattr(self, k, int(v)) - setattr(self, k, v) - - -def log_to_stderr(msg, level): - return sys.stderr.write(msg + os.linesep) - - -class LocalSupervisor(Supervisor): - - """Base class for modeling/controlling servers which run in the same - process. - - When the server side runs in a different process, start/stop can dump all - state between each test module easily. When the server side runs in the - same process as the client, however, we have to do a bit more work to - ensure config and mounted apps are reset between tests. - """ - - using_apache = False - using_wsgi = False - - def __init__(self, **kwargs): - for k, v in kwargs.items(): - setattr(self, k, v) - - cherrypy.server.httpserver = self.httpserver_class - - # This is perhaps the wrong place for this call but this is the only - # place that i've found so far that I KNOW is early enough to set this. - cherrypy.config.update({'log.screen': False}) - engine = cherrypy.engine - if hasattr(engine, 'signal_handler'): - engine.signal_handler.subscribe() - if hasattr(engine, 'console_control_handler'): - engine.console_control_handler.subscribe() - - def start(self, modulename=None): - """Load and start the HTTP server.""" - if modulename: - # Unhook httpserver so cherrypy.server.start() creates a new - # one (with config from setup_server, if declared). - cherrypy.server.httpserver = None - - cherrypy.engine.start() - - self.sync_apps() - - def sync_apps(self): - """Tell the server about any apps which the setup functions mounted.""" - pass - - def stop(self): - td = getattr(self, 'teardown', None) - if td: - td() - - cherrypy.engine.exit() - - for name, server in getattr(cherrypy, 'servers', {}).copy().items(): - server.unsubscribe() - del cherrypy.servers[name] - - -class NativeServerSupervisor(LocalSupervisor): - - """Server supervisor for the builtin HTTP server.""" - - httpserver_class = 'cherrypy._cpnative_server.CPHTTPServer' - using_apache = False - using_wsgi = False - - def __str__(self): - return 'Builtin HTTP Server on %s:%s' % (self.host, self.port) - - -class LocalWSGISupervisor(LocalSupervisor): - - """Server supervisor for the builtin WSGI server.""" - - httpserver_class = 'cherrypy._cpwsgi_server.CPWSGIServer' - using_apache = False - using_wsgi = True - - def __str__(self): - return 'Builtin WSGI Server on %s:%s' % (self.host, self.port) - - def sync_apps(self): - """Hook a new WSGI app into the origin server.""" - cherrypy.server.httpserver.wsgi_app = self.get_app() - - def get_app(self, app=None): - """Obtain a new (decorated) WSGI app to hook into the origin server.""" - if app is None: - app = cherrypy.tree - - if self.validate: - try: - from wsgiref import validate - except ImportError: - warnings.warn( - 'Error importing wsgiref. The validator will not run.') - else: - # wraps the app in the validator - app = validate.validator(app) - - return app - - -def get_cpmodpy_supervisor(**options): - from cherrypy.test import modpy - sup = modpy.ModPythonSupervisor(**options) - sup.template = modpy.conf_cpmodpy - return sup - - -def get_modpygw_supervisor(**options): - from cherrypy.test import modpy - sup = modpy.ModPythonSupervisor(**options) - sup.template = modpy.conf_modpython_gateway - sup.using_wsgi = True - return sup - - -def get_modwsgi_supervisor(**options): - from cherrypy.test import modwsgi - return modwsgi.ModWSGISupervisor(**options) - - -def get_modfcgid_supervisor(**options): - from cherrypy.test import modfcgid - return modfcgid.ModFCGISupervisor(**options) - - -def get_modfastcgi_supervisor(**options): - from cherrypy.test import modfastcgi - return modfastcgi.ModFCGISupervisor(**options) - - -def get_wsgi_u_supervisor(**options): - cherrypy.server.wsgi_version = ('u', 0) - return LocalWSGISupervisor(**options) - - -class CPWebCase(webtest.WebCase): - - script_name = '' - scheme = 'http' - - available_servers = {'wsgi': LocalWSGISupervisor, - 'wsgi_u': get_wsgi_u_supervisor, - 'native': NativeServerSupervisor, - 'cpmodpy': get_cpmodpy_supervisor, - 'modpygw': get_modpygw_supervisor, - 'modwsgi': get_modwsgi_supervisor, - 'modfcgid': get_modfcgid_supervisor, - 'modfastcgi': get_modfastcgi_supervisor, - } - default_server = 'wsgi' - - @classmethod - def _setup_server(cls, supervisor, conf): - v = sys.version.split()[0] - log.info('Python version used to run this test script: %s' % v) - log.info('CherryPy version: %s' % cherrypy.__version__) - if supervisor.scheme == 'https': - ssl = ' (ssl)' - else: - ssl = '' - log.info('HTTP server version: %s%s' % (supervisor.protocol, ssl)) - log.info('PID: %s' % os.getpid()) - - cherrypy.server.using_apache = supervisor.using_apache - cherrypy.server.using_wsgi = supervisor.using_wsgi - - if sys.platform[:4] == 'java': - cherrypy.config.update({'server.nodelay': False}) - - if isinstance(conf, text_or_bytes): - parser = cherrypy.lib.reprconf.Parser() - conf = parser.dict_from_file(conf).get('global', {}) - else: - conf = conf or {} - baseconf = conf.copy() - baseconf.update({'server.socket_host': supervisor.host, - 'server.socket_port': supervisor.port, - 'server.protocol_version': supervisor.protocol, - 'environment': 'test_suite', - }) - if supervisor.scheme == 'https': - # baseconf['server.ssl_module'] = 'builtin' - baseconf['server.ssl_certificate'] = serverpem - baseconf['server.ssl_private_key'] = serverpem - - # helper must be imported lazily so the coverage tool - # can run against module-level statements within cherrypy. - # Also, we have to do "from cherrypy.test import helper", - # exactly like each test module does, because a relative import - # would stick a second instance of webtest in sys.modules, - # and we wouldn't be able to globally override the port anymore. - if supervisor.scheme == 'https': - webtest.WebCase.HTTP_CONN = HTTPSConnection - return baseconf - - @classmethod - def setup_class(cls): - '' - # Creates a server - conf = { - 'scheme': 'http', - 'protocol': 'HTTP/1.1', - 'port': 54583, - 'host': '127.0.0.1', - 'validate': False, - 'server': 'wsgi', - } - supervisor_factory = cls.available_servers.get( - conf.get('server', 'wsgi')) - if supervisor_factory is None: - raise RuntimeError('Unknown server in config: %s' % conf['server']) - supervisor = supervisor_factory(**conf) - - # Copied from "run_test_suite" - cherrypy.config.reset() - baseconf = cls._setup_server(supervisor, conf) - cherrypy.config.update(baseconf) - setup_client() - - if hasattr(cls, 'setup_server'): - # Clear the cherrypy tree and clear the wsgi server so that - # it can be updated with the new root - cherrypy.tree = cherrypy._cptree.Tree() - cherrypy.server.httpserver = None - cls.setup_server() - # Add a resource for verifying there are no refleaks - # to *every* test class. - cherrypy.tree.mount(gctools.GCRoot(), '/gc') - cls.do_gc_test = True - supervisor.start(cls.__module__) - - cls.supervisor = supervisor - - @classmethod - def teardown_class(cls): - '' - if hasattr(cls, 'setup_server'): - cls.supervisor.stop() - - do_gc_test = False - - def test_gc(self): - if not self.do_gc_test: - return - - self.getPage('/gc/stats') - try: - self.assertBody('Statistics:') - except Exception: - 'Failures occur intermittently. See #1420' - - def prefix(self): - return self.script_name.rstrip('/') - - def base(self): - if ((self.scheme == 'http' and self.PORT == 80) or - (self.scheme == 'https' and self.PORT == 443)): - port = '' - else: - port = ':%s' % self.PORT - - return '%s://%s%s%s' % (self.scheme, self.HOST, port, - self.script_name.rstrip('/')) - - def exit(self): - sys.exit() - - def getPage(self, url, *args, **kwargs): - """Open the url. - """ - if self.script_name: - url = httputil.urljoin(self.script_name, url) - return webtest.WebCase.getPage(self, url, *args, **kwargs) - - def skip(self, msg='skipped '): - pytest.skip(msg) - - def assertErrorPage(self, status, message=None, pattern=''): - """Compare the response body with a built in error page. - - The function will optionally look for the regexp pattern, - within the exception embedded in the error page.""" - - # This will never contain a traceback - page = cherrypy._cperror.get_error_page(status, message=message) - - # First, test the response body without checking the traceback. - # Stick a match-all group (.*) in to grab the traceback. - def esc(text): - return re.escape(ntob(text)) - epage = re.escape(page) - epage = epage.replace( - esc('
'),
-            esc('
') + b'(.*)' + esc('
')) - m = re.match(epage, self.body, re.DOTALL) - if not m: - self._handlewebError( - 'Error page does not match; expected:\n' + page) - return - - # Now test the pattern against the traceback - if pattern is None: - # Special-case None to mean that there should be *no* traceback. - if m and m.group(1): - self._handlewebError('Error page contains traceback') - else: - if (m is None) or ( - not re.search(ntob(re.escape(pattern), self.encoding), - m.group(1))): - msg = 'Error page does not contain %s in traceback' - self._handlewebError(msg % repr(pattern)) - - date_tolerance = 2 - - def assertEqualDates(self, dt1, dt2, seconds=None): - """Assert abs(dt1 - dt2) is within Y seconds.""" - if seconds is None: - seconds = self.date_tolerance - - if dt1 > dt2: - diff = dt1 - dt2 - else: - diff = dt2 - dt1 - if not diff < datetime.timedelta(seconds=seconds): - raise AssertionError('%r and %r are not within %r seconds.' % - (dt1, dt2, seconds)) - - -def _test_method_sorter(_, x, y): - """Monkeypatch the test sorter to always run test_gc last in each suite.""" - if x == 'test_gc': - return 1 - if y == 'test_gc': - return -1 - if x > y: - return 1 - if x < y: - return -1 - return 0 - - -unittest.TestLoader.sortTestMethodsUsing = _test_method_sorter - - -def setup_client(): - """Set up the WebCase classes to match the server's socket settings.""" - webtest.WebCase.PORT = cherrypy.server.socket_port - webtest.WebCase.HOST = cherrypy.server.socket_host - if cherrypy.server.ssl_certificate: - CPWebCase.scheme = 'https' - -# --------------------------- Spawning helpers --------------------------- # - - -class CPProcess(object): - - pid_file = os.path.join(thisdir, 'test.pid') - config_file = os.path.join(thisdir, 'test.conf') - config_template = """[global] -server.socket_host: '%(host)s' -server.socket_port: %(port)s -checker.on: False -log.screen: False -log.error_file: r'%(error_log)s' -log.access_file: r'%(access_log)s' -%(ssl)s -%(extra)s -""" - error_log = os.path.join(thisdir, 'test.error.log') - access_log = os.path.join(thisdir, 'test.access.log') - - def __init__(self, wait=False, daemonize=False, ssl=False, - socket_host=None, socket_port=None): - self.wait = wait - self.daemonize = daemonize - self.ssl = ssl - self.host = socket_host or cherrypy.server.socket_host - self.port = socket_port or cherrypy.server.socket_port - - def write_conf(self, extra=''): - if self.ssl: - serverpem = os.path.join(thisdir, 'test.pem') - ssl = """ -server.ssl_certificate: r'%s' -server.ssl_private_key: r'%s' -""" % (serverpem, serverpem) - else: - ssl = '' - - conf = self.config_template % { - 'host': self.host, - 'port': self.port, - 'error_log': self.error_log, - 'access_log': self.access_log, - 'ssl': ssl, - 'extra': extra, - } - with io.open(self.config_file, 'w', encoding='utf-8') as f: - f.write(str(conf)) - - def start(self, imports=None): - """Start cherryd in a subprocess.""" - portend.free(self.host, self.port, timeout=1) - - args = [ - '-m', - 'cherrypy', - '-c', self.config_file, - '-p', self.pid_file, - ] - r""" - Command for running cherryd server with autoreload enabled - - Using - - ``` - ['-c', - "__requires__ = 'CherryPy'; \ - import pkg_resources, re, sys; \ - sys.argv[0] = re.sub(r'(-script\.pyw?|\.exe)?$', '', sys.argv[0]); \ - sys.exit(\ - pkg_resources.load_entry_point(\ - 'CherryPy', 'console_scripts', 'cherryd')())"] - ``` - - doesn't work as it's impossible to reconstruct the `-c`'s contents. - Ref: https://github.com/cherrypy/cherrypy/issues/1545 - """ - - if not isinstance(imports, (list, tuple)): - imports = [imports] - for i in imports: - if i: - args.append('-i') - args.append(i) - - if self.daemonize: - args.append('-d') - - env = os.environ.copy() - # Make sure we import the cherrypy package in which this module is - # defined. - grandparentdir = os.path.abspath(os.path.join(thisdir, '..', '..')) - if env.get('PYTHONPATH', ''): - env['PYTHONPATH'] = os.pathsep.join( - (grandparentdir, env['PYTHONPATH'])) - else: - env['PYTHONPATH'] = grandparentdir - self._proc = subprocess.Popen([sys.executable] + args, env=env) - if self.wait: - self.exit_code = self._proc.wait() - else: - portend.occupied(self.host, self.port, timeout=5) - - # Give the engine a wee bit more time to finish STARTING - if self.daemonize: - time.sleep(2) - else: - time.sleep(1) - - def get_pid(self): - if self.daemonize: - with open(self.pid_file, 'rb') as f: - return int(f.read()) - return self._proc.pid - - def join(self): - """Wait for the process to exit.""" - if self.daemonize: - return self._join_daemon() - self._proc.wait() - - def _join_daemon(self): - with contextlib.suppress(IOError): - os.waitpid(self.get_pid(), 0) diff --git a/lib/cherrypy/test/logtest.py b/lib/cherrypy/test/logtest.py deleted file mode 100644 index 112bdc25..00000000 --- a/lib/cherrypy/test/logtest.py +++ /dev/null @@ -1,236 +0,0 @@ -"""logtest, a unittest.TestCase helper for testing log output.""" - -import sys -import time -from uuid import UUID - -import pytest - -from cherrypy._cpcompat import text_or_bytes - - -try: - # On Windows, msvcrt.getch reads a single char without output. - import msvcrt - - def getchar(): - return msvcrt.getch() -except ImportError: - # Unix getchr - import tty - import termios - - def getchar(): - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) - try: - tty.setraw(sys.stdin.fileno()) - ch = sys.stdin.read(1) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - return ch - - -class LogCase(object): - - """unittest.TestCase mixin for testing log messages. - - logfile: a filename for the desired log. Yes, I know modes are evil, - but it makes the test functions so much cleaner to set this once. - - lastmarker: the last marker in the log. This can be used to search for - messages since the last marker. - - markerPrefix: a string with which to prefix log markers. This should be - unique enough from normal log output to use for marker identification. - """ - - interactive = False - logfile = None - lastmarker = None - markerPrefix = b'test suite marker: ' - - def _handleLogError(self, msg, data, marker, pattern): - print('') - print(' ERROR: %s' % msg) - - if not self.interactive: - raise pytest.fail(msg) - - p = (' Show: ' - '[L]og [M]arker [P]attern; ' - '[I]gnore, [R]aise, or sys.e[X]it >> ') - sys.stdout.write(p + ' ') - # ARGH - sys.stdout.flush() - while True: - i = getchar().upper() - if i not in 'MPLIRX': - continue - print(i.upper()) # Also prints new line - if i == 'L': - for x, line in enumerate(data): - if (x + 1) % self.console_height == 0: - # The \r and comma should make the next line overwrite - sys.stdout.write('<-- More -->\r ') - m = getchar().lower() - # Erase our "More" prompt - sys.stdout.write(' \r ') - if m == 'q': - break - print(line.rstrip()) - elif i == 'M': - print(repr(marker or self.lastmarker)) - elif i == 'P': - print(repr(pattern)) - elif i == 'I': - # return without raising the normal exception - return - elif i == 'R': - raise pytest.fail(msg) - elif i == 'X': - self.exit() - sys.stdout.write(p + ' ') - - def exit(self): - sys.exit() - - def emptyLog(self): - """Overwrite self.logfile with 0 bytes.""" - with open(self.logfile, 'wb') as f: - f.write('') - - def markLog(self, key=None): - """Insert a marker line into the log and set self.lastmarker.""" - if key is None: - key = str(time.time()) - self.lastmarker = key - - with open(self.logfile, 'ab+') as f: - f.write( - b'%s%s\n' - % (self.markerPrefix, key.encode('utf-8')) - ) - - def _read_marked_region(self, marker=None): - """Return lines from self.logfile in the marked region. - - If marker is None, self.lastmarker is used. If the log hasn't - been marked (using self.markLog), the entire log will be returned. - """ -# Give the logger time to finish writing? -# time.sleep(0.5) - - logfile = self.logfile - marker = marker or self.lastmarker - if marker is None: - with open(logfile, 'rb') as f: - return f.readlines() - - if isinstance(marker, str): - marker = marker.encode('utf-8') - data = [] - in_region = False - with open(logfile, 'rb') as f: - for line in f: - if in_region: - if (line.startswith(self.markerPrefix) - and marker not in line): - break - else: - data.append(line) - elif marker in line: - in_region = True - return data - - def assertInLog(self, line, marker=None): - """Fail if the given (partial) line is not in the log. - - The log will be searched from the given marker to the next marker. - If marker is None, self.lastmarker is used. If the log hasn't - been marked (using self.markLog), the entire log will be searched. - """ - data = self._read_marked_region(marker) - for logline in data: - if line in logline: - return - msg = '%r not found in log' % line - self._handleLogError(msg, data, marker, line) - - def assertNotInLog(self, line, marker=None): - """Fail if the given (partial) line is in the log. - - The log will be searched from the given marker to the next marker. - If marker is None, self.lastmarker is used. If the log hasn't - been marked (using self.markLog), the entire log will be searched. - """ - data = self._read_marked_region(marker) - for logline in data: - if line in logline: - msg = '%r found in log' % line - self._handleLogError(msg, data, marker, line) - - def assertValidUUIDv4(self, marker=None): - """Fail if the given UUIDv4 is not valid. - - The log will be searched from the given marker to the next marker. - If marker is None, self.lastmarker is used. If the log hasn't - been marked (using self.markLog), the entire log will be searched. - """ - data = self._read_marked_region(marker) - data = [ - chunk.decode('utf-8').rstrip('\n').rstrip('\r') - for chunk in data - ] - for log_chunk in data: - try: - uuid_log = data[-1] - uuid_obj = UUID(uuid_log, version=4) - except (TypeError, ValueError): - pass # it might be in other chunk - else: - if str(uuid_obj) == uuid_log: - return - msg = '%r is not a valid UUIDv4' % uuid_log - self._handleLogError(msg, data, marker, log_chunk) - - msg = 'UUIDv4 not found in log' - self._handleLogError(msg, data, marker, log_chunk) - - def assertLog(self, sliceargs, lines, marker=None): - """Fail if log.readlines()[sliceargs] is not contained in 'lines'. - - The log will be searched from the given marker to the next marker. - If marker is None, self.lastmarker is used. If the log hasn't - been marked (using self.markLog), the entire log will be searched. - """ - data = self._read_marked_region(marker) - if isinstance(sliceargs, int): - # Single arg. Use __getitem__ and allow lines to be str or list. - if isinstance(lines, (tuple, list)): - lines = lines[0] - if isinstance(lines, str): - lines = lines.encode('utf-8') - if lines not in data[sliceargs]: - msg = '%r not found on log line %r' % (lines, sliceargs) - self._handleLogError( - msg, - [data[sliceargs], '--EXTRA CONTEXT--'] + data[ - sliceargs + 1:sliceargs + 6], - marker, - lines) - else: - # Multiple args. Use __getslice__ and require lines to be list. - if isinstance(lines, tuple): - lines = list(lines) - elif isinstance(lines, text_or_bytes): - raise TypeError("The 'lines' arg must be a list when " - "'sliceargs' is a tuple.") - - start, stop = sliceargs - for line, logline in zip(lines, data[start:stop]): - if isinstance(line, str): - line = line.encode('utf-8') - if line not in logline: - msg = '%r not found in log' % line - self._handleLogError(msg, data[start:stop], marker, line) diff --git a/lib/cherrypy/test/modfastcgi.py b/lib/cherrypy/test/modfastcgi.py deleted file mode 100644 index 16927d3c..00000000 --- a/lib/cherrypy/test/modfastcgi.py +++ /dev/null @@ -1,133 +0,0 @@ -"""Wrapper for mod_fastcgi, for use as a CherryPy HTTP server when testing. - -To autostart fastcgi, the "apache" executable or script must be -on your system path, or you must override the global APACHE_PATH. -On some platforms, "apache" may be called "apachectl", "apache2ctl", -or "httpd"--create a symlink to them if needed. - -You'll also need the WSGIServer from flup.servers. -See http://projects.amor.org/misc/wiki/ModPythonGateway - - -KNOWN BUGS -========== - -1. Apache processes Range headers automatically; CherryPy's truncated - output is then truncated again by Apache. See test_core.testRanges. - This was worked around in http://www.cherrypy.dev/changeset/1319. -2. Apache does not allow custom HTTP methods like CONNECT as per the spec. - See test_core.testHTTPMethods. -3. Max request header and body settings do not work with Apache. -4. Apache replaces status "reason phrases" automatically. For example, - CherryPy may set "304 Not modified" but Apache will write out - "304 Not Modified" (capital "M"). -5. Apache does not allow custom error codes as per the spec. -6. Apache (or perhaps modpython, or modpython_gateway) unquotes %xx in the - Request-URI too early. -7. mod_python will not read request bodies which use the "chunked" - transfer-coding (it passes REQUEST_CHUNKED_ERROR to ap_setup_client_block - instead of REQUEST_CHUNKED_DECHUNK, see Apache2's http_protocol.c and - mod_python's requestobject.c). -8. Apache will output a "Content-Length: 0" response header even if there's - no response entity body. This isn't really a bug; it just differs from - the CherryPy default. -""" - -import os -import re - -import cherrypy -from cherrypy.process import servers -from cherrypy.test import helper - -curdir = os.path.join(os.getcwd(), os.path.dirname(__file__)) - - -def read_process(cmd, args=''): - pipein, pipeout = os.popen4('%s %s' % (cmd, args)) - try: - firstline = pipeout.readline() - if (re.search(r'(not recognized|No such file|not found)', firstline, - re.IGNORECASE)): - raise IOError('%s must be on your system path.' % cmd) - output = firstline + pipeout.read() - finally: - pipeout.close() - return output - - -APACHE_PATH = 'apache2ctl' -CONF_PATH = 'fastcgi.conf' - -conf_fastcgi = """ -# Apache2 server conf file for testing CherryPy with mod_fastcgi. -# fumanchu: I had to hard-code paths due to crazy Debian layouts :( -ServerRoot /usr/lib/apache2 -User #1000 -ErrorLog %(root)s/mod_fastcgi.error.log - -DocumentRoot "%(root)s" -ServerName 127.0.0.1 -Listen %(port)s -LoadModule fastcgi_module modules/mod_fastcgi.so -LoadModule rewrite_module modules/mod_rewrite.so - -Options +ExecCGI -SetHandler fastcgi-script -RewriteEngine On -RewriteRule ^(.*)$ /fastcgi.pyc [L] -FastCgiExternalServer "%(server)s" -host 127.0.0.1:4000 -""" - - -def erase_script_name(environ, start_response): - environ['SCRIPT_NAME'] = '' - return cherrypy.tree(environ, start_response) - - -class ModFCGISupervisor(helper.LocalWSGISupervisor): - - httpserver_class = 'cherrypy.process.servers.FlupFCGIServer' - using_apache = True - using_wsgi = True - template = conf_fastcgi - - def __str__(self): - return 'FCGI Server on %s:%s' % (self.host, self.port) - - def start(self, modulename): - cherrypy.server.httpserver = servers.FlupFCGIServer( - application=erase_script_name, bindAddress=('127.0.0.1', 4000)) - cherrypy.server.httpserver.bind_addr = ('127.0.0.1', 4000) - cherrypy.server.socket_port = 4000 - # For FCGI, we both start apache... - self.start_apache() - # ...and our local server - cherrypy.engine.start() - self.sync_apps() - - def start_apache(self): - fcgiconf = CONF_PATH - if not os.path.isabs(fcgiconf): - fcgiconf = os.path.join(curdir, fcgiconf) - - # Write the Apache conf file. - with open(fcgiconf, 'wb') as f: - server = repr(os.path.join(curdir, 'fastcgi.pyc'))[1:-1] - output = self.template % {'port': self.port, 'root': curdir, - 'server': server} - output = output.replace('\r\n', '\n') - f.write(output) - - result = read_process(APACHE_PATH, '-k start -f %s' % fcgiconf) - if result: - print(result) - - def stop(self): - """Gracefully shutdown a server that is serving forever.""" - read_process(APACHE_PATH, '-k stop') - helper.LocalWSGISupervisor.stop(self) - - def sync_apps(self): - cherrypy.server.httpserver.fcgiserver.application = self.get_app( - erase_script_name) diff --git a/lib/cherrypy/test/modfcgid.py b/lib/cherrypy/test/modfcgid.py deleted file mode 100644 index 6dd48a94..00000000 --- a/lib/cherrypy/test/modfcgid.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Wrapper for mod_fcgid, for use as a CherryPy HTTP server when testing. - -To autostart fcgid, the "apache" executable or script must be -on your system path, or you must override the global APACHE_PATH. -On some platforms, "apache" may be called "apachectl", "apache2ctl", -or "httpd"--create a symlink to them if needed. - -You'll also need the WSGIServer from flup.servers. -See http://projects.amor.org/misc/wiki/ModPythonGateway - - -KNOWN BUGS -========== - -1. Apache processes Range headers automatically; CherryPy's truncated - output is then truncated again by Apache. See test_core.testRanges. - This was worked around in http://www.cherrypy.dev/changeset/1319. -2. Apache does not allow custom HTTP methods like CONNECT as per the spec. - See test_core.testHTTPMethods. -3. Max request header and body settings do not work with Apache. -4. Apache replaces status "reason phrases" automatically. For example, - CherryPy may set "304 Not modified" but Apache will write out - "304 Not Modified" (capital "M"). -5. Apache does not allow custom error codes as per the spec. -6. Apache (or perhaps modpython, or modpython_gateway) unquotes %xx in the - Request-URI too early. -7. mod_python will not read request bodies which use the "chunked" - transfer-coding (it passes REQUEST_CHUNKED_ERROR to ap_setup_client_block - instead of REQUEST_CHUNKED_DECHUNK, see Apache2's http_protocol.c and - mod_python's requestobject.c). -8. Apache will output a "Content-Length: 0" response header even if there's - no response entity body. This isn't really a bug; it just differs from - the CherryPy default. -""" - -import os -import re - -import cherrypy -from cherrypy._cpcompat import ntob -from cherrypy.process import servers -from cherrypy.test import helper - -curdir = os.path.join(os.getcwd(), os.path.dirname(__file__)) - - -def read_process(cmd, args=''): - pipein, pipeout = os.popen4('%s %s' % (cmd, args)) - try: - firstline = pipeout.readline() - if (re.search(r'(not recognized|No such file|not found)', firstline, - re.IGNORECASE)): - raise IOError('%s must be on your system path.' % cmd) - output = firstline + pipeout.read() - finally: - pipeout.close() - return output - - -APACHE_PATH = 'httpd' -CONF_PATH = 'fcgi.conf' - -conf_fcgid = """ -# Apache2 server conf file for testing CherryPy with mod_fcgid. - -DocumentRoot "%(root)s" -ServerName 127.0.0.1 -Listen %(port)s -LoadModule fastcgi_module modules/mod_fastcgi.dll -LoadModule rewrite_module modules/mod_rewrite.so - -Options ExecCGI -SetHandler fastcgi-script -RewriteEngine On -RewriteRule ^(.*)$ /fastcgi.pyc [L] -FastCgiExternalServer "%(server)s" -host 127.0.0.1:4000 -""" - - -class ModFCGISupervisor(helper.LocalSupervisor): - - using_apache = True - using_wsgi = True - template = conf_fcgid - - def __str__(self): - return 'FCGI Server on %s:%s' % (self.host, self.port) - - def start(self, modulename): - cherrypy.server.httpserver = servers.FlupFCGIServer( - application=cherrypy.tree, bindAddress=('127.0.0.1', 4000)) - cherrypy.server.httpserver.bind_addr = ('127.0.0.1', 4000) - # For FCGI, we both start apache... - self.start_apache() - # ...and our local server - helper.LocalServer.start(self, modulename) - - def start_apache(self): - fcgiconf = CONF_PATH - if not os.path.isabs(fcgiconf): - fcgiconf = os.path.join(curdir, fcgiconf) - - # Write the Apache conf file. - with open(fcgiconf, 'wb') as f: - server = repr(os.path.join(curdir, 'fastcgi.pyc'))[1:-1] - output = self.template % {'port': self.port, 'root': curdir, - 'server': server} - output = ntob(output.replace('\r\n', '\n')) - f.write(output) - - result = read_process(APACHE_PATH, '-k start -f %s' % fcgiconf) - if result: - print(result) - - def stop(self): - """Gracefully shutdown a server that is serving forever.""" - read_process(APACHE_PATH, '-k stop') - helper.LocalServer.stop(self) - - def sync_apps(self): - cherrypy.server.httpserver.fcgiserver.application = self.get_app() diff --git a/lib/cherrypy/test/modpy.py b/lib/cherrypy/test/modpy.py deleted file mode 100644 index f4f68523..00000000 --- a/lib/cherrypy/test/modpy.py +++ /dev/null @@ -1,161 +0,0 @@ -"""Wrapper for mod_python, for use as a CherryPy HTTP server when testing. - -To autostart modpython, the "apache" executable or script must be -on your system path, or you must override the global APACHE_PATH. -On some platforms, "apache" may be called "apachectl" or "apache2ctl"-- -create a symlink to them if needed. - -If you wish to test the WSGI interface instead of our _cpmodpy interface, -you also need the 'modpython_gateway' module at: -http://projects.amor.org/misc/wiki/ModPythonGateway - - -KNOWN BUGS -========== - -1. Apache processes Range headers automatically; CherryPy's truncated - output is then truncated again by Apache. See test_core.testRanges. - This was worked around in http://www.cherrypy.dev/changeset/1319. -2. Apache does not allow custom HTTP methods like CONNECT as per the spec. - See test_core.testHTTPMethods. -3. Max request header and body settings do not work with Apache. -4. Apache replaces status "reason phrases" automatically. For example, - CherryPy may set "304 Not modified" but Apache will write out - "304 Not Modified" (capital "M"). -5. Apache does not allow custom error codes as per the spec. -6. Apache (or perhaps modpython, or modpython_gateway) unquotes %xx in the - Request-URI too early. -7. mod_python will not read request bodies which use the "chunked" - transfer-coding (it passes REQUEST_CHUNKED_ERROR to ap_setup_client_block - instead of REQUEST_CHUNKED_DECHUNK, see Apache2's http_protocol.c and - mod_python's requestobject.c). -8. Apache will output a "Content-Length: 0" response header even if there's - no response entity body. This isn't really a bug; it just differs from - the CherryPy default. -""" - -import os -import re - -import cherrypy -from cherrypy.test import helper - -curdir = os.path.join(os.getcwd(), os.path.dirname(__file__)) - - -def read_process(cmd, args=''): - pipein, pipeout = os.popen4('%s %s' % (cmd, args)) - try: - firstline = pipeout.readline() - if (re.search(r'(not recognized|No such file|not found)', firstline, - re.IGNORECASE)): - raise IOError('%s must be on your system path.' % cmd) - output = firstline + pipeout.read() - finally: - pipeout.close() - return output - - -APACHE_PATH = 'httpd' -CONF_PATH = 'test_mp.conf' - -conf_modpython_gateway = """ -# Apache2 server conf file for testing CherryPy with modpython_gateway. - -ServerName 127.0.0.1 -DocumentRoot "/" -Listen %(port)s -LoadModule python_module modules/mod_python.so - -SetHandler python-program -PythonFixupHandler cherrypy.test.modpy::wsgisetup -PythonOption testmod %(modulename)s -PythonHandler modpython_gateway::handler -PythonOption wsgi.application cherrypy::tree -PythonOption socket_host %(host)s -PythonDebug On -""" - -conf_cpmodpy = """ -# Apache2 server conf file for testing CherryPy with _cpmodpy. - -ServerName 127.0.0.1 -DocumentRoot "/" -Listen %(port)s -LoadModule python_module modules/mod_python.so - -SetHandler python-program -PythonFixupHandler cherrypy.test.modpy::cpmodpysetup -PythonHandler cherrypy._cpmodpy::handler -PythonOption cherrypy.setup cherrypy.test.%(modulename)s::setup_server -PythonOption socket_host %(host)s -PythonDebug On -""" - - -class ModPythonSupervisor(helper.Supervisor): - - using_apache = True - using_wsgi = False - template = None - - def __str__(self): - return 'ModPython Server on %s:%s' % (self.host, self.port) - - def start(self, modulename): - mpconf = CONF_PATH - if not os.path.isabs(mpconf): - mpconf = os.path.join(curdir, mpconf) - - with open(mpconf, 'wb') as f: - f.write(self.template % - {'port': self.port, 'modulename': modulename, - 'host': self.host}) - - result = read_process(APACHE_PATH, '-k start -f %s' % mpconf) - if result: - print(result) - - def stop(self): - """Gracefully shutdown a server that is serving forever.""" - read_process(APACHE_PATH, '-k stop') - - -loaded = False - - -def wsgisetup(req): - global loaded - if not loaded: - loaded = True - options = req.get_options() - - cherrypy.config.update({ - 'log.error_file': os.path.join(curdir, 'test.log'), - 'environment': 'test_suite', - 'server.socket_host': options['socket_host'], - }) - - modname = options['testmod'] - mod = __import__(modname, globals(), locals(), ['']) - mod.setup_server() - - cherrypy.server.unsubscribe() - cherrypy.engine.start() - from mod_python import apache - return apache.OK - - -def cpmodpysetup(req): - global loaded - if not loaded: - loaded = True - options = req.get_options() - - cherrypy.config.update({ - 'log.error_file': os.path.join(curdir, 'test.log'), - 'environment': 'test_suite', - 'server.socket_host': options['socket_host'], - }) - from mod_python import apache - return apache.OK diff --git a/lib/cherrypy/test/modwsgi.py b/lib/cherrypy/test/modwsgi.py deleted file mode 100644 index c3216284..00000000 --- a/lib/cherrypy/test/modwsgi.py +++ /dev/null @@ -1,151 +0,0 @@ -"""Wrapper for mod_wsgi, for use as a CherryPy HTTP server. - -To autostart modwsgi, the "apache" executable or script must be -on your system path, or you must override the global APACHE_PATH. -On some platforms, "apache" may be called "apachectl" or "apache2ctl"-- -create a symlink to them if needed. - - -KNOWN BUGS -========== - -1. Apache processes Range headers automatically; CherryPy's truncated - output is then truncated again by Apache. See test_core.testRanges. - This was worked around in http://www.cherrypy.dev/changeset/1319. -2. Apache does not allow custom HTTP methods like CONNECT as per the spec. - See test_core.testHTTPMethods. -3. Max request header and body settings do not work with Apache. -4. Apache replaces status "reason phrases" automatically. For example, - CherryPy may set "304 Not modified" but Apache will write out - "304 Not Modified" (capital "M"). -5. Apache does not allow custom error codes as per the spec. -6. Apache (or perhaps modpython, or modpython_gateway) unquotes %xx in the - Request-URI too early. -7. mod_wsgi will not read request bodies which use the "chunked" - transfer-coding (it passes REQUEST_CHUNKED_ERROR to ap_setup_client_block - instead of REQUEST_CHUNKED_DECHUNK, see Apache2's http_protocol.c and - mod_python's requestobject.c). -8. When responding with 204 No Content, mod_wsgi adds a Content-Length - header for you. -9. When an error is raised, mod_wsgi has no facility for printing a - traceback as the response content (it's sent to the Apache log instead). -10. Startup and shutdown of Apache when running mod_wsgi seems slow. -""" - -import os -import re -import sys -import time - -import portend - -from cheroot.test import webtest - -import cherrypy -from cherrypy.test import helper - -curdir = os.path.abspath(os.path.dirname(__file__)) - - -def read_process(cmd, args=''): - pipein, pipeout = os.popen4('%s %s' % (cmd, args)) - try: - firstline = pipeout.readline() - if (re.search(r'(not recognized|No such file|not found)', firstline, - re.IGNORECASE)): - raise IOError('%s must be on your system path.' % cmd) - output = firstline + pipeout.read() - finally: - pipeout.close() - return output - - -if sys.platform == 'win32': - APACHE_PATH = 'httpd' -else: - APACHE_PATH = 'apache' - -CONF_PATH = 'test_mw.conf' - -conf_modwsgi = r""" -# Apache2 server conf file for testing CherryPy with modpython_gateway. - -ServerName 127.0.0.1 -DocumentRoot "/" -Listen %(port)s - -AllowEncodedSlashes On -LoadModule rewrite_module modules/mod_rewrite.so -RewriteEngine on -RewriteMap escaping int:escape - -LoadModule log_config_module modules/mod_log_config.so -LogFormat "%%h %%l %%u %%t \"%%r\" %%>s %%b \"%%{Referer}i\" \"%%{User-agent}i\"" combined -CustomLog "%(curdir)s/apache.access.log" combined -ErrorLog "%(curdir)s/apache.error.log" -LogLevel debug - -LoadModule wsgi_module modules/mod_wsgi.so -LoadModule env_module modules/mod_env.so - -WSGIScriptAlias / "%(curdir)s/modwsgi.py" -SetEnv testmod %(testmod)s -""" # noqa E501 - - -class ModWSGISupervisor(helper.Supervisor): - - """Server Controller for ModWSGI and CherryPy.""" - - using_apache = True - using_wsgi = True - template = conf_modwsgi - - def __str__(self): - return 'ModWSGI Server on %s:%s' % (self.host, self.port) - - def start(self, modulename): - mpconf = CONF_PATH - if not os.path.isabs(mpconf): - mpconf = os.path.join(curdir, mpconf) - - with open(mpconf, 'wb') as f: - output = (self.template % - {'port': self.port, 'testmod': modulename, - 'curdir': curdir}) - f.write(output) - - result = read_process(APACHE_PATH, '-k start -f %s' % mpconf) - if result: - print(result) - - # Make a request so mod_wsgi starts up our app. - # If we don't, concurrent initial requests will 404. - portend.occupied('127.0.0.1', self.port, timeout=5) - webtest.openURL('/ihopetheresnodefault', port=self.port) - time.sleep(1) - - def stop(self): - """Gracefully shutdown a server that is serving forever.""" - read_process(APACHE_PATH, '-k stop') - - -loaded = False - - -def application(environ, start_response): - global loaded - if not loaded: - loaded = True - modname = 'cherrypy.test.' + environ['testmod'] - mod = __import__(modname, globals(), locals(), ['']) - mod.setup_server() - - cherrypy.config.update({ - 'log.error_file': os.path.join(curdir, 'test.error.log'), - 'log.access_file': os.path.join(curdir, 'test.access.log'), - 'environment': 'test_suite', - 'engine.SIGHUP': None, - 'engine.SIGTERM': None, - }) - return cherrypy.tree(environ, start_response) diff --git a/lib/cherrypy/test/sessiondemo.py b/lib/cherrypy/test/sessiondemo.py deleted file mode 100644 index 3849a259..00000000 --- a/lib/cherrypy/test/sessiondemo.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/python -"""A session demonstration app.""" - -import calendar -from datetime import datetime -import sys - -import cherrypy -from cherrypy.lib import sessions - - -page = """ - - - - - - - -

Session Demo

-

Reload this page. The session ID should not change from one reload to the next

-

Index | Expire | Regenerate

- - - - - - - - - -
Session ID:%(sessionid)s

%(changemsg)s

Request Cookie%(reqcookie)s
Response Cookie%(respcookie)s

Session Data%(sessiondata)s
Server Time%(servertime)s (Unix time: %(serverunixtime)s)
Browser Time 
Cherrypy Version:%(cpversion)s
Python Version:%(pyversion)s
- -""" # noqa E501 - - -class Root(object): - - def page(self): - changemsg = [] - if cherrypy.session.id != cherrypy.session.originalid: - if cherrypy.session.originalid is None: - changemsg.append( - 'Created new session because no session id was given.') - if cherrypy.session.missing: - changemsg.append( - 'Created new session due to missing ' - '(expired or malicious) session.') - if cherrypy.session.regenerated: - changemsg.append('Application generated a new session.') - - try: - expires = cherrypy.response.cookie['session_id']['expires'] - except KeyError: - expires = '' - - return page % { - 'sessionid': cherrypy.session.id, - 'changemsg': '
'.join(changemsg), - 'respcookie': cherrypy.response.cookie.output(), - 'reqcookie': cherrypy.request.cookie.output(), - 'sessiondata': list(cherrypy.session.items()), - 'servertime': ( - datetime.utcnow().strftime('%Y/%m/%d %H:%M') + ' UTC' - ), - 'serverunixtime': calendar.timegm(datetime.utcnow().timetuple()), - 'cpversion': cherrypy.__version__, - 'pyversion': sys.version, - 'expires': expires, - } - - @cherrypy.expose - def index(self): - # Must modify data or the session will not be saved. - cherrypy.session['color'] = 'green' - return self.page() - - @cherrypy.expose - def expire(self): - sessions.expire() - return self.page() - - @cherrypy.expose - def regen(self): - cherrypy.session.regenerate() - # Must modify data or the session will not be saved. - cherrypy.session['color'] = 'yellow' - return self.page() - - -if __name__ == '__main__': - cherrypy.config.update({ - # 'environment': 'production', - 'log.screen': True, - 'tools.sessions.on': True, - }) - cherrypy.quickstart(Root()) diff --git a/lib/cherrypy/test/static/404.html b/lib/cherrypy/test/static/404.html deleted file mode 100644 index 01b17b09..00000000 --- a/lib/cherrypy/test/static/404.html +++ /dev/null @@ -1,5 +0,0 @@ - - -

I couldn't find that thing you were looking for!

- - diff --git a/lib/cherrypy/test/static/dirback.jpg b/lib/cherrypy/test/static/dirback.jpg deleted file mode 100644 index 80403dc2..00000000 Binary files a/lib/cherrypy/test/static/dirback.jpg and /dev/null differ diff --git a/lib/cherrypy/test/static/index.html b/lib/cherrypy/test/static/index.html deleted file mode 100644 index a5c19667..00000000 --- a/lib/cherrypy/test/static/index.html +++ /dev/null @@ -1 +0,0 @@ -Hello, world diff --git a/lib/cherrypy/test/style.css b/lib/cherrypy/test/style.css deleted file mode 100644 index b266e93d..00000000 --- a/lib/cherrypy/test/style.css +++ /dev/null @@ -1 +0,0 @@ -Dummy stylesheet diff --git a/lib/cherrypy/test/test.pem b/lib/cherrypy/test/test.pem deleted file mode 100644 index 47a47042..00000000 --- a/lib/cherrypy/test/test.pem +++ /dev/null @@ -1,38 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDBKo554mzIMY+AByUNpaUOP9bJnQ7ZLQe9XgHwoLJR4VzpyZZZ -R9L4WtImEew05FY3Izerfm3MN3+MC0tJ6yQU9sOiU3vBW6RrLIMlfKsnRwBRZ0Kn -da+O6xldVSosu8Ev3z9VZ94iC/ZgKzrH7Mjj/U8/MQO7RBS/LAqee8bFNQIDAQAB -AoGAWOCF0ZrWxn3XMucWq2LNwPKqlvVGwbIwX3cDmX22zmnM4Fy6arXbYh4XlyCj -9+ofqRrxIFz5k/7tFriTmZ0xag5+Jdx+Kwg0/twiP7XCNKipFogwe1Hznw8OFAoT -enKBdj2+/n2o0Bvo/tDB59m9L/538d46JGQUmJlzMyqYikECQQDyoq+8CtMNvE18 -8VgHcR/KtApxWAjj4HpaHYL637ATjThetUZkW92mgDgowyplthusxdNqhHWyv7E8 -tWNdYErZAkEAy85ShTR0M5aWmrE7o0r0SpWInAkNBH9aXQRRARFYsdBtNfRu6I0i -0lvU9wiu3eF57FMEC86yViZ5UBnQfTu7vQJAVesj/Zt7pwaCDfdMa740OsxMUlyR -MVhhGx4OLpYdPJ8qUecxGQKq13XZ7R1HGyNEY4bd2X80Smq08UFuATfC6QJAH8UB -yBHtKz2GLIcELOg6PIYizW/7v3+6rlVF60yw7sb2vzpjL40QqIn4IKoR2DSVtOkb -8FtAIX3N21aq0VrGYQJBAIPiaEc2AZ8Bq2GC4F3wOz/BxJ/izvnkiotR12QK4fh5 -yjZMhTjWCas5zwHR5PDjlD88AWGDMsZ1PicD4348xJQ= ------END RSA PRIVATE KEY----- ------BEGIN CERTIFICATE----- -MIIDxTCCAy6gAwIBAgIJAI18BD7eQxlGMA0GCSqGSIb3DQEBBAUAMIGeMQswCQYD -VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTESMBAGA1UEBxMJU2FuIERpZWdv -MRkwFwYDVQQKExBDaGVycnlQeSBQcm9qZWN0MREwDwYDVQQLEwhkZXYtdGVzdDEW -MBQGA1UEAxMNQ2hlcnJ5UHkgVGVhbTEgMB4GCSqGSIb3DQEJARYRcmVtaUBjaGVy -cnlweS5vcmcwHhcNMDYwOTA5MTkyMDIwWhcNMzQwMTI0MTkyMDIwWjCBnjELMAkG -A1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVNhbiBEaWVn -bzEZMBcGA1UEChMQQ2hlcnJ5UHkgUHJvamVjdDERMA8GA1UECxMIZGV2LXRlc3Qx -FjAUBgNVBAMTDUNoZXJyeVB5IFRlYW0xIDAeBgkqhkiG9w0BCQEWEXJlbWlAY2hl -cnJ5cHkub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBKo554mzIMY+A -ByUNpaUOP9bJnQ7ZLQe9XgHwoLJR4VzpyZZZR9L4WtImEew05FY3Izerfm3MN3+M -C0tJ6yQU9sOiU3vBW6RrLIMlfKsnRwBRZ0Knda+O6xldVSosu8Ev3z9VZ94iC/Zg -KzrH7Mjj/U8/MQO7RBS/LAqee8bFNQIDAQABo4IBBzCCAQMwHQYDVR0OBBYEFDIQ -2feb71tVZCWpU0qJ/Tw+wdtoMIHTBgNVHSMEgcswgciAFDIQ2feb71tVZCWpU0qJ -/Tw+wdtooYGkpIGhMIGeMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5p -YTESMBAGA1UEBxMJU2FuIERpZWdvMRkwFwYDVQQKExBDaGVycnlQeSBQcm9qZWN0 -MREwDwYDVQQLEwhkZXYtdGVzdDEWMBQGA1UEAxMNQ2hlcnJ5UHkgVGVhbTEgMB4G -CSqGSIb3DQEJARYRcmVtaUBjaGVycnlweS5vcmeCCQCNfAQ+3kMZRjAMBgNVHRME -BTADAQH/MA0GCSqGSIb3DQEBBAUAA4GBAL7AAQz7IePV48ZTAFHKr88ntPALsL5S -8vHCZPNMevNkLTj3DYUw2BcnENxMjm1kou2F2BkvheBPNZKIhc6z4hAml3ed1xa2 -D7w6e6OTcstdK/+KrPDDHeOP1dhMWNs2JE1bNlfF1LiXzYKSXpe88eCKjCXsCT/T -NluCaWQys3MS ------END CERTIFICATE----- diff --git a/lib/cherrypy/test/test_auth_basic.py b/lib/cherrypy/test/test_auth_basic.py deleted file mode 100644 index f178f8f9..00000000 --- a/lib/cherrypy/test/test_auth_basic.py +++ /dev/null @@ -1,135 +0,0 @@ -# This file is part of CherryPy -# -*- coding: utf-8 -*- -# vim:ts=4:sw=4:expandtab:fileencoding=utf-8 - -from hashlib import md5 - -import cherrypy -from cherrypy._cpcompat import ntob -from cherrypy.lib import auth_basic -from cherrypy.test import helper - - -class BasicAuthTest(helper.CPWebCase): - - @staticmethod - def setup_server(): - class Root: - - @cherrypy.expose - def index(self): - return 'This is public.' - - class BasicProtected: - - @cherrypy.expose - def index(self): - return "Hello %s, you've been authorized." % ( - cherrypy.request.login) - - class BasicProtected2: - - @cherrypy.expose - def index(self): - return "Hello %s, you've been authorized." % ( - cherrypy.request.login) - - class BasicProtected2_u: - - @cherrypy.expose - def index(self): - return "Hello %s, you've been authorized." % ( - cherrypy.request.login) - - userpassdict = {'xuser': 'xpassword'} - userhashdict = {'xuser': md5(b'xpassword').hexdigest()} - userhashdict_u = {'xюзер': md5(ntob('їжа', 'utf-8')).hexdigest()} - - def checkpasshash(realm, user, password): - p = userhashdict.get(user) - return p and p == md5(ntob(password)).hexdigest() or False - - def checkpasshash_u(realm, user, password): - p = userhashdict_u.get(user) - return p and p == md5(ntob(password, 'utf-8')).hexdigest() or False - - basic_checkpassword_dict = auth_basic.checkpassword_dict(userpassdict) - conf = { - '/basic': { - 'tools.auth_basic.on': True, - 'tools.auth_basic.realm': 'wonderland', - 'tools.auth_basic.checkpassword': basic_checkpassword_dict - }, - '/basic2': { - 'tools.auth_basic.on': True, - 'tools.auth_basic.realm': 'wonderland', - 'tools.auth_basic.checkpassword': checkpasshash, - 'tools.auth_basic.accept_charset': 'ISO-8859-1', - }, - '/basic2_u': { - 'tools.auth_basic.on': True, - 'tools.auth_basic.realm': 'wonderland', - 'tools.auth_basic.checkpassword': checkpasshash_u, - 'tools.auth_basic.accept_charset': 'UTF-8', - }, - } - - root = Root() - root.basic = BasicProtected() - root.basic2 = BasicProtected2() - root.basic2_u = BasicProtected2_u() - cherrypy.tree.mount(root, config=conf) - - def testPublic(self): - self.getPage('/') - self.assertStatus('200 OK') - self.assertHeader('Content-Type', 'text/html;charset=utf-8') - self.assertBody('This is public.') - - def testBasic(self): - self.getPage('/basic/') - self.assertStatus(401) - self.assertHeader( - 'WWW-Authenticate', - 'Basic realm="wonderland", charset="UTF-8"' - ) - - self.getPage('/basic/', - [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3JX')]) - self.assertStatus(401) - - self.getPage('/basic/', - [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3Jk')]) - self.assertStatus('200 OK') - self.assertBody("Hello xuser, you've been authorized.") - - def testBasic2(self): - self.getPage('/basic2/') - self.assertStatus(401) - self.assertHeader('WWW-Authenticate', 'Basic realm="wonderland"') - - self.getPage('/basic2/', - [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3JX')]) - self.assertStatus(401) - - self.getPage('/basic2/', - [('Authorization', 'Basic eHVzZXI6eHBhc3N3b3Jk')]) - self.assertStatus('200 OK') - self.assertBody("Hello xuser, you've been authorized.") - - def testBasic2_u(self): - self.getPage('/basic2_u/') - self.assertStatus(401) - self.assertHeader( - 'WWW-Authenticate', - 'Basic realm="wonderland", charset="UTF-8"' - ) - - self.getPage('/basic2_u/', - [('Authorization', 'Basic eNGO0LfQtdGAOtGX0LbRgw==')]) - self.assertStatus(401) - - self.getPage('/basic2_u/', - [('Authorization', 'Basic eNGO0LfQtdGAOtGX0LbQsA==')]) - self.assertStatus('200 OK') - self.assertBody("Hello xюзер, you've been authorized.") diff --git a/lib/cherrypy/test/test_auth_digest.py b/lib/cherrypy/test/test_auth_digest.py deleted file mode 100644 index 4b7b5298..00000000 --- a/lib/cherrypy/test/test_auth_digest.py +++ /dev/null @@ -1,131 +0,0 @@ -# This file is part of CherryPy -# -*- coding: utf-8 -*- -# vim:ts=4:sw=4:expandtab:fileencoding=utf-8 - - -import cherrypy -from cherrypy.lib import auth_digest -from cherrypy._cpcompat import ntob - -from cherrypy.test import helper - - -def _fetch_users(): - return {'test': 'test', '☃йюзер': 'їпароль'} - - -get_ha1 = cherrypy.lib.auth_digest.get_ha1_dict_plain(_fetch_users()) - - -class DigestAuthTest(helper.CPWebCase): - - @staticmethod - def setup_server(): - class Root: - - @cherrypy.expose - def index(self): - return 'This is public.' - - class DigestProtected: - - @cherrypy.expose - def index(self, *args, **kwargs): - return "Hello %s, you've been authorized." % ( - cherrypy.request.login) - - conf = {'/digest': {'tools.auth_digest.on': True, - 'tools.auth_digest.realm': 'localhost', - 'tools.auth_digest.get_ha1': get_ha1, - 'tools.auth_digest.key': 'a565c27146791cfb', - 'tools.auth_digest.debug': True, - 'tools.auth_digest.accept_charset': 'UTF-8'}} - - root = Root() - root.digest = DigestProtected() - cherrypy.tree.mount(root, config=conf) - - def testPublic(self): - self.getPage('/') - assert self.status == '200 OK' - self.assertHeader('Content-Type', 'text/html;charset=utf-8') - assert self.body == b'This is public.' - - def _test_parametric_digest(self, username, realm): - test_uri = '/digest/?@/=%2F%40&%f0%9f%99%88=path' - - self.getPage(test_uri) - assert self.status_code == 401 - - msg = 'Digest authentification scheme was not found' - www_auth_digest = tuple(filter( - lambda kv: kv[0].lower() == 'www-authenticate' - and kv[1].startswith('Digest '), - self.headers, - )) - assert len(www_auth_digest) == 1, msg - - items = www_auth_digest[0][-1][7:].split(', ') - tokens = {} - for item in items: - key, value = item.split('=') - tokens[key.lower()] = value - - assert tokens['realm'] == '"localhost"' - assert tokens['algorithm'] == '"MD5"' - assert tokens['qop'] == '"auth"' - assert tokens['charset'] == '"UTF-8"' - - nonce = tokens['nonce'].strip('"') - - # Test user agent response with a wrong value for 'realm' - base_auth = ('Digest username="%s", ' - 'realm="%s", ' - 'nonce="%s", ' - 'uri="%s", ' - 'algorithm=MD5, ' - 'response="%s", ' - 'qop=auth, ' - 'nc=%s, ' - 'cnonce="1522e61005789929"') - - encoded_user = username - encoded_user = encoded_user.encode('utf-8') - encoded_user = encoded_user.decode('latin1') - auth_header = base_auth % ( - encoded_user, realm, nonce, test_uri, - '11111111111111111111111111111111', '00000001', - ) - auth = auth_digest.HttpDigestAuthorization(auth_header, 'GET') - # calculate the response digest - ha1 = get_ha1(auth.realm, auth.username) - response = auth.request_digest(ha1) - auth_header = base_auth % ( - encoded_user, realm, nonce, test_uri, - response, '00000001', - ) - self.getPage(test_uri, [('Authorization', auth_header)]) - - def test_wrong_realm(self): - # send response with correct response digest, but wrong realm - self._test_parametric_digest(username='test', realm='wrong realm') - assert self.status_code == 401 - - def test_ascii_user(self): - self._test_parametric_digest(username='test', realm='localhost') - assert self.status == '200 OK' - assert self.body == b"Hello test, you've been authorized." - - def test_unicode_user(self): - self._test_parametric_digest(username='☃йюзер', realm='localhost') - assert self.status == '200 OK' - assert self.body == ntob( - "Hello ☃йюзер, you've been authorized.", 'utf-8', - ) - - def test_wrong_scheme(self): - basic_auth = { - 'Authorization': 'Basic foo:bar', - } - self.getPage('/digest/', headers=list(basic_auth.items())) - assert self.status_code == 401 diff --git a/lib/cherrypy/test/test_bus.py b/lib/cherrypy/test/test_bus.py deleted file mode 100644 index 594023a2..00000000 --- a/lib/cherrypy/test/test_bus.py +++ /dev/null @@ -1,327 +0,0 @@ -"""Publish-subscribe bus tests.""" -# pylint: disable=redefined-outer-name - -import os -import sys -import threading -import time -import unittest.mock - -import pytest - -from cherrypy.process import wspbus - - -CI_ON_MACOS = bool(os.getenv('CI')) and sys.platform == 'darwin' -msg = 'Listener %d on channel %s: %s.' # pylint: disable=invalid-name - - -@pytest.fixture -def bus(): - """Return a wspbus instance.""" - return wspbus.Bus() - - -@pytest.fixture -def log_tracker(bus): - """Return an instance of bus log tracker.""" - class LogTracker: # pylint: disable=too-few-public-methods - """Bus log tracker.""" - - log_entries = [] - - def __init__(self, bus): - def logit(msg, level): # pylint: disable=unused-argument - self.log_entries.append(msg) - bus.subscribe('log', logit) - - return LogTracker(bus) - - -@pytest.fixture -def listener(): - """Return an instance of bus response tracker.""" - class Listner: # pylint: disable=too-few-public-methods - """Bus handler return value tracker.""" - - responses = [] - - def get_listener(self, channel, index): - """Return an argument tracking listener.""" - def listener(arg=None): - self.responses.append(msg % (index, channel, arg)) - return listener - - return Listner() - - -def test_builtin_channels(bus, listener): - """Test that built-in channels trigger corresponding listeners.""" - expected = [] - - for channel in bus.listeners: - for index, priority in enumerate([100, 50, 0, 51]): - bus.subscribe( - channel, - listener.get_listener(channel, index), - priority, - ) - - for channel in bus.listeners: - bus.publish(channel) - expected.extend([msg % (i, channel, None) for i in (2, 1, 3, 0)]) - bus.publish(channel, arg=79347) - expected.extend([msg % (i, channel, 79347) for i in (2, 1, 3, 0)]) - - assert listener.responses == expected - - -def test_custom_channels(bus, listener): - """Test that custom pub-sub channels work as built-in ones.""" - expected = [] - - custom_listeners = ('hugh', 'louis', 'dewey') - for channel in custom_listeners: - for index, priority in enumerate([None, 10, 60, 40]): - bus.subscribe( - channel, - listener.get_listener(channel, index), - priority, - ) - - for channel in custom_listeners: - bus.publish(channel, 'ah so') - expected.extend(msg % (i, channel, 'ah so') for i in (1, 3, 0, 2)) - bus.publish(channel) - expected.extend(msg % (i, channel, None) for i in (1, 3, 0, 2)) - - assert listener.responses == expected - - -def test_listener_errors(bus, listener): - """Test that unhandled exceptions raise channel failures.""" - expected = [] - channels = [c for c in bus.listeners if c != 'log'] - - for channel in channels: - bus.subscribe(channel, listener.get_listener(channel, 1)) - # This will break since the lambda takes no args. - bus.subscribe(channel, lambda: None, priority=20) - - for channel in channels: - with pytest.raises(wspbus.ChannelFailures): - bus.publish(channel, 123) - expected.append(msg % (1, channel, 123)) - - assert listener.responses == expected - - -def test_start(bus, listener, log_tracker): - """Test that bus start sequence calls all listeners.""" - num = 3 - for index in range(num): - bus.subscribe('start', listener.get_listener('start', index)) - - bus.start() - try: - # The start method MUST call all 'start' listeners. - assert ( - set(listener.responses) == - set(msg % (i, 'start', None) for i in range(num))) - # The start method MUST move the state to STARTED - # (or EXITING, if errors occur) - assert bus.state == bus.states.STARTED - # The start method MUST log its states. - assert log_tracker.log_entries == ['Bus STARTING', 'Bus STARTED'] - finally: - # Exit so the atexit handler doesn't complain. - bus.exit() - - -def test_stop(bus, listener, log_tracker): - """Test that bus stop sequence calls all listeners.""" - num = 3 - - for index in range(num): - bus.subscribe('stop', listener.get_listener('stop', index)) - - bus.stop() - - # The stop method MUST call all 'stop' listeners. - assert (set(listener.responses) == - set(msg % (i, 'stop', None) for i in range(num))) - - # The stop method MUST move the state to STOPPED - assert bus.state == bus.states.STOPPED - - # The stop method MUST log its states. - assert log_tracker.log_entries == ['Bus STOPPING', 'Bus STOPPED'] - - -def test_graceful(bus, listener, log_tracker): - """Test that bus graceful state triggers all listeners.""" - num = 3 - - for index in range(num): - bus.subscribe('graceful', listener.get_listener('graceful', index)) - - bus.graceful() - - # The graceful method MUST call all 'graceful' listeners. - assert ( - set(listener.responses) == - set(msg % (i, 'graceful', None) for i in range(num))) - - # The graceful method MUST log its states. - assert log_tracker.log_entries == ['Bus graceful'] - - -def test_exit(bus, listener, log_tracker): - """Test that bus exit sequence is correct.""" - num = 3 - - for index in range(num): - bus.subscribe('stop', listener.get_listener('stop', index)) - bus.subscribe('exit', listener.get_listener('exit', index)) - - bus.exit() - - # The exit method MUST call all 'stop' listeners, - # and then all 'exit' listeners. - assert (set(listener.responses) == - set([msg % (i, 'stop', None) for i in range(num)] + - [msg % (i, 'exit', None) for i in range(num)])) - - # The exit method MUST move the state to EXITING - assert bus.state == bus.states.EXITING - - # The exit method MUST log its states. - assert (log_tracker.log_entries == - ['Bus STOPPING', 'Bus STOPPED', 'Bus EXITING', 'Bus EXITED']) - - -def test_wait(bus): - """Test that bus wait awaits for states.""" - def f(method): # pylint: disable=invalid-name - time.sleep(0.2) - getattr(bus, method)() - - flow = [ - ('start', [bus.states.STARTED]), - ('stop', [bus.states.STOPPED]), - ('start', [bus.states.STARTING, bus.states.STARTED]), - ('exit', [bus.states.EXITING]), - ] - - for method, states in flow: - threading.Thread(target=f, args=(method,)).start() - bus.wait(states) - - # The wait method MUST wait for the given state(s). - assert bus.state in states, 'State %r not in %r' % (bus.state, states) - - -@pytest.mark.xfail(CI_ON_MACOS, reason='continuous integration on macOS fails') -def test_wait_publishes_periodically(bus): - """Test that wait publishes each tick.""" - callback = unittest.mock.MagicMock() - bus.subscribe('main', callback) - - def set_start(): - time.sleep(0.05) - bus.start() - threading.Thread(target=set_start).start() - bus.wait(bus.states.STARTED, interval=0.01, channel='main') - assert callback.call_count > 3 - - -def test_block(bus, log_tracker): - """Test that bus block waits for exiting.""" - def f(): # pylint: disable=invalid-name - time.sleep(0.2) - bus.exit() - - def g(): # pylint: disable=invalid-name - time.sleep(0.4) - - threading.Thread(target=f).start() - threading.Thread(target=g).start() - threads = [t for t in threading.enumerate() if not t.daemon] - assert len(threads) == 3 - - bus.block() - - # The block method MUST wait for the EXITING state. - assert bus.state == bus.states.EXITING - - # The block method MUST wait for ALL non-main, non-daemon threads to - # finish. - threads = [t for t in threading.enumerate() if not t.daemon] - assert len(threads) == 1 - - # The last message will mention an indeterminable thread name; ignore - # it - expected_bus_messages = [ - 'Bus STOPPING', - 'Bus STOPPED', - 'Bus EXITING', - 'Bus EXITED', - 'Waiting for child threads to terminate...', - ] - bus_msg_num = len(expected_bus_messages) - - # If the last message mentions an indeterminable thread name then ignore it - assert log_tracker.log_entries[:bus_msg_num] == expected_bus_messages - assert len(log_tracker.log_entries[bus_msg_num:]) <= 1, ( - 'No more than one extra log line with the thread name expected' - ) - - -def test_start_with_callback(bus): - """Test that callback fires on bus start.""" - try: - events = [] - - def f(*args, **kwargs): # pylint: disable=invalid-name - events.append(('f', args, kwargs)) - - def g(): # pylint: disable=invalid-name - events.append('g') - bus.subscribe('start', g) - bus.start_with_callback(f, (1, 3, 5), {'foo': 'bar'}) - - # Give wait() time to run f() - time.sleep(0.2) - - # The callback method MUST wait for the STARTED state. - assert bus.state == bus.states.STARTED - - # The callback method MUST run after all start methods. - assert events == ['g', ('f', (1, 3, 5), {'foo': 'bar'})] - finally: - bus.exit() - - -def test_log(bus, log_tracker): - """Test that bus messages and errors are logged.""" - assert log_tracker.log_entries == [] - - # Try a normal message. - expected = [] - for msg_ in ["O mah darlin'"] * 3 + ['Clementiiiiiiiine']: - bus.log(msg_) - expected.append(msg_) - assert log_tracker.log_entries == expected - - # Try an error message - try: - foo - except NameError: - bus.log('You are lost and gone forever', traceback=True) - lastmsg = log_tracker.log_entries[-1] - assert 'Traceback' in lastmsg and 'NameError' in lastmsg, ( - 'Last log message %r did not contain ' - 'the expected traceback.' % lastmsg - ) - else: - pytest.fail('NameError was not raised as expected.') diff --git a/lib/cherrypy/test/test_caching.py b/lib/cherrypy/test/test_caching.py deleted file mode 100644 index c0b89797..00000000 --- a/lib/cherrypy/test/test_caching.py +++ /dev/null @@ -1,390 +0,0 @@ -import datetime -from itertools import count -import os -import threading -import time -import urllib.parse - -import pytest - -import cherrypy -from cherrypy.lib import httputil - -from cherrypy.test import helper - - -curdir = os.path.join(os.getcwd(), os.path.dirname(__file__)) - -gif_bytes = ( - b'GIF89a\x01\x00\x01\x00\x82\x00\x01\x99"\x1e\x00\x00\x00\x00\x00' - b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' - b'\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x02\x03\x02\x08\t\x00;' -) - - -class CacheTest(helper.CPWebCase): - - @staticmethod - def setup_server(): - - @cherrypy.config(**{'tools.caching.on': True}) - class Root: - - def __init__(self): - self.counter = 0 - self.control_counter = 0 - self.longlock = threading.Lock() - - @cherrypy.expose - def index(self): - self.counter += 1 - msg = 'visit #%s' % self.counter - return msg - - @cherrypy.expose - def control(self): - self.control_counter += 1 - return 'visit #%s' % self.control_counter - - @cherrypy.expose - def a_gif(self): - cherrypy.response.headers[ - 'Last-Modified'] = httputil.HTTPDate() - return gif_bytes - - @cherrypy.expose - def long_process(self, seconds='1'): - try: - self.longlock.acquire() - time.sleep(float(seconds)) - finally: - self.longlock.release() - return 'success!' - - @cherrypy.expose - def clear_cache(self, path): - cherrypy._cache.store[cherrypy.request.base + path].clear() - - @cherrypy.config(**{ - 'tools.caching.on': True, - 'tools.response_headers.on': True, - 'tools.response_headers.headers': [ - ('Vary', 'Our-Varying-Header') - ], - }) - class VaryHeaderCachingServer(object): - - def __init__(self): - self.counter = count(1) - - @cherrypy.expose - def index(self): - return 'visit #%s' % next(self.counter) - - @cherrypy.config(**{ - 'tools.expires.on': True, - 'tools.expires.secs': 60, - 'tools.staticdir.on': True, - 'tools.staticdir.dir': 'static', - 'tools.staticdir.root': curdir, - }) - class UnCached(object): - - @cherrypy.expose - @cherrypy.config(**{'tools.expires.secs': 0}) - def force(self): - cherrypy.response.headers['Etag'] = 'bibbitybobbityboo' - self._cp_config['tools.expires.force'] = True - self._cp_config['tools.expires.secs'] = 0 - return 'being forceful' - - @cherrypy.expose - def dynamic(self): - cherrypy.response.headers['Etag'] = 'bibbitybobbityboo' - cherrypy.response.headers['Cache-Control'] = 'private' - return 'D-d-d-dynamic!' - - @cherrypy.expose - def cacheable(self): - cherrypy.response.headers['Etag'] = 'bibbitybobbityboo' - return "Hi, I'm cacheable." - - @cherrypy.expose - @cherrypy.config(**{'tools.expires.secs': 86400}) - def specific(self): - cherrypy.response.headers[ - 'Etag'] = 'need_this_to_make_me_cacheable' - return 'I am being specific' - - class Foo(object): - pass - - @cherrypy.expose - @cherrypy.config(**{'tools.expires.secs': Foo()}) - def wrongtype(self): - cherrypy.response.headers[ - 'Etag'] = 'need_this_to_make_me_cacheable' - return 'Woops' - - @cherrypy.config(**{ - 'tools.gzip.mime_types': ['text/*', 'image/*'], - 'tools.caching.on': True, - 'tools.staticdir.on': True, - 'tools.staticdir.dir': 'static', - 'tools.staticdir.root': curdir - }) - class GzipStaticCache(object): - pass - - cherrypy.tree.mount(Root()) - cherrypy.tree.mount(UnCached(), '/expires') - cherrypy.tree.mount(VaryHeaderCachingServer(), '/varying_headers') - cherrypy.tree.mount(GzipStaticCache(), '/gzip_static_cache') - cherrypy.config.update({'tools.gzip.on': True}) - - def testCaching(self): - elapsed = 0.0 - for trial in range(10): - self.getPage('/') - # The response should be the same every time, - # except for the Age response header. - self.assertBody('visit #1') - if trial != 0: - age = int(self.assertHeader('Age')) - assert age >= elapsed - elapsed = age - - # POST, PUT, DELETE should not be cached. - self.getPage('/', method='POST') - self.assertBody('visit #2') - # Because gzip is turned on, the Vary header should always Vary for - # content-encoding - self.assertHeader('Vary', 'Accept-Encoding') - # The previous request should have invalidated the cache, - # so this request will recalc the response. - self.getPage('/', method='GET') - self.assertBody('visit #3') - # ...but this request should get the cached copy. - self.getPage('/', method='GET') - self.assertBody('visit #3') - self.getPage('/', method='DELETE') - self.assertBody('visit #4') - - # The previous request should have invalidated the cache, - # so this request will recalc the response. - self.getPage('/', method='GET', headers=[('Accept-Encoding', 'gzip')]) - self.assertHeader('Content-Encoding', 'gzip') - self.assertHeader('Vary') - self.assertEqual( - cherrypy.lib.encoding.decompress(self.body), b'visit #5') - - # Now check that a second request gets the gzip header and gzipped body - # This also tests a bug in 3.0 to 3.0.2 whereby the cached, gzipped - # response body was being gzipped a second time. - self.getPage('/', method='GET', headers=[('Accept-Encoding', 'gzip')]) - self.assertHeader('Content-Encoding', 'gzip') - self.assertEqual( - cherrypy.lib.encoding.decompress(self.body), b'visit #5') - - # Now check that a third request that doesn't accept gzip - # skips the cache (because the 'Vary' header denies it). - self.getPage('/', method='GET') - self.assertNoHeader('Content-Encoding') - self.assertBody('visit #6') - - def testVaryHeader(self): - self.getPage('/varying_headers/') - self.assertStatus('200 OK') - self.assertHeaderItemValue('Vary', 'Our-Varying-Header') - self.assertBody('visit #1') - - # Now check that different 'Vary'-fields don't evict each other. - # This test creates 2 requests with different 'Our-Varying-Header' - # and then tests if the first one still exists. - self.getPage('/varying_headers/', - headers=[('Our-Varying-Header', 'request 2')]) - self.assertStatus('200 OK') - self.assertBody('visit #2') - - self.getPage('/varying_headers/', - headers=[('Our-Varying-Header', 'request 2')]) - self.assertStatus('200 OK') - self.assertBody('visit #2') - - self.getPage('/varying_headers/') - self.assertStatus('200 OK') - self.assertBody('visit #1') - - def testExpiresTool(self): - # test setting an expires header - self.getPage('/expires/specific') - self.assertStatus('200 OK') - self.assertHeader('Expires') - - # test exceptions for bad time values - self.getPage('/expires/wrongtype') - self.assertStatus(500) - self.assertInBody('TypeError') - - # static content should not have "cache prevention" headers - self.getPage('/expires/index.html') - self.assertStatus('200 OK') - self.assertNoHeader('Pragma') - self.assertNoHeader('Cache-Control') - self.assertHeader('Expires') - - # dynamic content that sets indicators should not have - # "cache prevention" headers - self.getPage('/expires/cacheable') - self.assertStatus('200 OK') - self.assertNoHeader('Pragma') - self.assertNoHeader('Cache-Control') - self.assertHeader('Expires') - - self.getPage('/expires/dynamic') - self.assertBody('D-d-d-dynamic!') - # the Cache-Control header should be untouched - self.assertHeader('Cache-Control', 'private') - self.assertHeader('Expires') - - # configure the tool to ignore indicators and replace existing headers - self.getPage('/expires/force') - self.assertStatus('200 OK') - # This also gives us a chance to test 0 expiry with no other headers - self.assertHeader('Pragma', 'no-cache') - if cherrypy.server.protocol_version == 'HTTP/1.1': - self.assertHeader('Cache-Control', 'no-cache, must-revalidate') - self.assertHeader('Expires', 'Sun, 28 Jan 2007 00:00:00 GMT') - - # static content should now have "cache prevention" headers - self.getPage('/expires/index.html') - self.assertStatus('200 OK') - self.assertHeader('Pragma', 'no-cache') - if cherrypy.server.protocol_version == 'HTTP/1.1': - self.assertHeader('Cache-Control', 'no-cache, must-revalidate') - self.assertHeader('Expires', 'Sun, 28 Jan 2007 00:00:00 GMT') - - # the cacheable handler should now have "cache prevention" headers - self.getPage('/expires/cacheable') - self.assertStatus('200 OK') - self.assertHeader('Pragma', 'no-cache') - if cherrypy.server.protocol_version == 'HTTP/1.1': - self.assertHeader('Cache-Control', 'no-cache, must-revalidate') - self.assertHeader('Expires', 'Sun, 28 Jan 2007 00:00:00 GMT') - - self.getPage('/expires/dynamic') - self.assertBody('D-d-d-dynamic!') - # dynamic sets Cache-Control to private but it should be - # overwritten here ... - self.assertHeader('Pragma', 'no-cache') - if cherrypy.server.protocol_version == 'HTTP/1.1': - self.assertHeader('Cache-Control', 'no-cache, must-revalidate') - self.assertHeader('Expires', 'Sun, 28 Jan 2007 00:00:00 GMT') - - def _assert_resp_len_and_enc_for_gzip(self, uri): - """ - Test that after querying gzipped content it's remains valid in - cache and available non-gzipped as well. - """ - ACCEPT_GZIP_HEADERS = [('Accept-Encoding', 'gzip')] - content_len = None - - for _ in range(3): - self.getPage(uri, method='GET', headers=ACCEPT_GZIP_HEADERS) - - if content_len is not None: - # all requests should get the same length - self.assertHeader('Content-Length', content_len) - self.assertHeader('Content-Encoding', 'gzip') - - content_len = dict(self.headers)['Content-Length'] - - # check that we can still get non-gzipped version - self.getPage(uri, method='GET') - self.assertNoHeader('Content-Encoding') - # non-gzipped version should have a different content length - self.assertNoHeaderItemValue('Content-Length', content_len) - - def testGzipStaticCache(self): - """Test that cache and gzip tools play well together when both enabled. - - Ref GitHub issue #1190. - """ - GZIP_STATIC_CACHE_TMPL = '/gzip_static_cache/{}' - resource_files = ('index.html', 'dirback.jpg') - - for f in resource_files: - uri = GZIP_STATIC_CACHE_TMPL.format(f) - self._assert_resp_len_and_enc_for_gzip(uri) - - def testLastModified(self): - self.getPage('/a.gif') - self.assertStatus(200) - self.assertBody(gif_bytes) - lm1 = self.assertHeader('Last-Modified') - - # this request should get the cached copy. - self.getPage('/a.gif') - self.assertStatus(200) - self.assertBody(gif_bytes) - self.assertHeader('Age') - lm2 = self.assertHeader('Last-Modified') - self.assertEqual(lm1, lm2) - - # this request should match the cached copy, but raise 304. - self.getPage('/a.gif', [('If-Modified-Since', lm1)]) - self.assertStatus(304) - self.assertNoHeader('Last-Modified') - if not getattr(cherrypy.server, 'using_apache', False): - self.assertHeader('Age') - - @pytest.mark.xfail(reason='#1536') - def test_antistampede(self): - SECONDS = 4 - slow_url = '/long_process?seconds={SECONDS}'.format(**locals()) - # We MUST make an initial synchronous request in order to create the - # AntiStampedeCache object, and populate its selecting_headers, - # before the actual stampede. - self.getPage(slow_url) - self.assertBody('success!') - path = urllib.parse.quote(slow_url, safe='') - self.getPage('/clear_cache?path=' + path) - self.assertStatus(200) - - start = datetime.datetime.now() - - def run(): - self.getPage(slow_url) - # The response should be the same every time - self.assertBody('success!') - ts = [threading.Thread(target=run) for i in range(100)] - for t in ts: - t.start() - for t in ts: - t.join() - finish = datetime.datetime.now() - # Allow for overhead, two seconds for slow hosts - allowance = SECONDS + 2 - self.assertEqualDates(start, finish, seconds=allowance) - - def test_cache_control(self): - self.getPage('/control') - self.assertBody('visit #1') - self.getPage('/control') - self.assertBody('visit #1') - - self.getPage('/control', headers=[('Cache-Control', 'no-cache')]) - self.assertBody('visit #2') - self.getPage('/control') - self.assertBody('visit #2') - - self.getPage('/control', headers=[('Pragma', 'no-cache')]) - self.assertBody('visit #3') - self.getPage('/control') - self.assertBody('visit #3') - - time.sleep(1) - self.getPage('/control', headers=[('Cache-Control', 'max-age=0')]) - self.assertBody('visit #4') - self.getPage('/control') - self.assertBody('visit #4') diff --git a/lib/cherrypy/test/test_config.py b/lib/cherrypy/test/test_config.py deleted file mode 100644 index ecd46019..00000000 --- a/lib/cherrypy/test/test_config.py +++ /dev/null @@ -1,291 +0,0 @@ -"""Tests for the CherryPy configuration system.""" - -import io -import os -import sys -import unittest - -import cherrypy - -from cherrypy.test import helper - - -localDir = os.path.join(os.getcwd(), os.path.dirname(__file__)) - - -def StringIOFromNative(x): - return io.StringIO(str(x)) - - -def setup_server(): - - @cherrypy.config(foo='this', bar='that') - class Root: - - def __init__(self): - cherrypy.config.namespaces['db'] = self.db_namespace - - def db_namespace(self, k, v): - if k == 'scheme': - self.db = v - - @cherrypy.expose(alias=('global_', 'xyz')) - def index(self, key): - return cherrypy.request.config.get(key, 'None') - - @cherrypy.expose - def repr(self, key): - return repr(cherrypy.request.config.get(key, None)) - - @cherrypy.expose - def dbscheme(self): - return self.db - - @cherrypy.expose - @cherrypy.config(**{'request.body.attempt_charsets': ['utf-16']}) - def plain(self, x): - return x - - favicon_ico = cherrypy.tools.staticfile.handler( - filename=os.path.join(localDir, '../favicon.ico')) - - @cherrypy.config(foo='this2', baz='that2') - class Foo: - - @cherrypy.expose - def index(self, key): - return cherrypy.request.config.get(key, 'None') - nex = index - - @cherrypy.expose - @cherrypy.config(**{'response.headers.X-silly': 'sillyval'}) - def silly(self): - return 'Hello world' - - # Test the expose and config decorators - @cherrypy.config(foo='this3', **{'bax': 'this4'}) - @cherrypy.expose - def bar(self, key): - return repr(cherrypy.request.config.get(key, None)) - - class Another: - - @cherrypy.expose - def index(self, key): - return str(cherrypy.request.config.get(key, 'None')) - - def raw_namespace(key, value): - if key == 'input.map': - handler = cherrypy.request.handler - - def wrapper(): - params = cherrypy.request.params - for name, coercer in value.copy().items(): - try: - params[name] = coercer(params[name]) - except KeyError: - pass - return handler() - cherrypy.request.handler = wrapper - elif key == 'output': - handler = cherrypy.request.handler - - def wrapper(): - # 'value' is a type (like int or str). - return value(handler()) - cherrypy.request.handler = wrapper - - @cherrypy.config(**{'raw.output': repr}) - class Raw: - - @cherrypy.expose - @cherrypy.config(**{'raw.input.map': {'num': int}}) - def incr(self, num): - return num + 1 - - ioconf = StringIOFromNative(""" -[/] -neg: -1234 -filename: os.path.join(sys.prefix, "hello.py") -thing1: cherrypy.lib.httputil.response_codes[404] -thing2: __import__('cherrypy.tutorial', globals(), locals(), ['']).thing2 -complex: 3+2j -mul: 6*3 -ones: "11" -twos: "22" -stradd: %%(ones)s + %%(twos)s + "33" - -[/favicon.ico] -tools.staticfile.filename = %r -""" % os.path.join(localDir, 'static/dirback.jpg')) - - root = Root() - root.foo = Foo() - root.raw = Raw() - app = cherrypy.tree.mount(root, config=ioconf) - app.request_class.namespaces['raw'] = raw_namespace - - cherrypy.tree.mount(Another(), '/another') - cherrypy.config.update({'luxuryyacht': 'throatwobblermangrove', - 'db.scheme': r'sqlite///memory', - }) - - -# Client-side code # - - -class ConfigTests(helper.CPWebCase): - setup_server = staticmethod(setup_server) - - def testConfig(self): - tests = [ - ('/', 'nex', 'None'), - ('/', 'foo', 'this'), - ('/', 'bar', 'that'), - ('/xyz', 'foo', 'this'), - ('/foo/', 'foo', 'this2'), - ('/foo/', 'bar', 'that'), - ('/foo/', 'bax', 'None'), - ('/foo/bar', 'baz', "'that2'"), - ('/foo/nex', 'baz', 'that2'), - # If 'foo' == 'this', then the mount point '/another' leaks into - # '/'. - ('/another/', 'foo', 'None'), - ] - for path, key, expected in tests: - self.getPage(path + '?key=' + key) - self.assertBody(expected) - - expectedconf = { - # From CP defaults - 'tools.log_headers.on': False, - 'tools.log_tracebacks.on': True, - 'request.show_tracebacks': True, - 'log.screen': False, - 'environment': 'test_suite', - 'engine.autoreload.on': False, - # From global config - 'luxuryyacht': 'throatwobblermangrove', - # From Root._cp_config - 'bar': 'that', - # From Foo._cp_config - 'baz': 'that2', - # From Foo.bar._cp_config - 'foo': 'this3', - 'bax': 'this4', - } - for key, expected in expectedconf.items(): - self.getPage('/foo/bar?key=' + key) - self.assertBody(repr(expected)) - - def testUnrepr(self): - self.getPage('/repr?key=neg') - self.assertBody('-1234') - - self.getPage('/repr?key=filename') - self.assertBody(repr(os.path.join(sys.prefix, 'hello.py'))) - - self.getPage('/repr?key=thing1') - self.assertBody(repr(cherrypy.lib.httputil.response_codes[404])) - - if not getattr(cherrypy.server, 'using_apache', False): - # The object ID's won't match up when using Apache, since the - # server and client are running in different processes. - self.getPage('/repr?key=thing2') - from cherrypy.tutorial import thing2 - self.assertBody(repr(thing2)) - - self.getPage('/repr?key=complex') - self.assertBody('(3+2j)') - - self.getPage('/repr?key=mul') - self.assertBody('18') - - self.getPage('/repr?key=stradd') - self.assertBody(repr('112233')) - - def testRespNamespaces(self): - self.getPage('/foo/silly') - self.assertHeader('X-silly', 'sillyval') - self.assertBody('Hello world') - - def testCustomNamespaces(self): - self.getPage('/raw/incr?num=12') - self.assertBody('13') - - self.getPage('/dbscheme') - self.assertBody(r'sqlite///memory') - - def testHandlerToolConfigOverride(self): - # Assert that config overrides tool constructor args. Above, we set - # the favicon in the page handler to be '../favicon.ico', - # but then overrode it in config to be './static/dirback.jpg'. - self.getPage('/favicon.ico') - with open(os.path.join(localDir, 'static/dirback.jpg'), 'rb') as tf: - self.assertBody(tf.read()) - - def test_request_body_namespace(self): - self.getPage('/plain', method='POST', headers=[ - ('Content-Type', 'application/x-www-form-urlencoded'), - ('Content-Length', '13')], - body=b'\xff\xfex\x00=\xff\xfea\x00b\x00c\x00') - self.assertBody('abc') - - -class VariableSubstitutionTests(unittest.TestCase): - setup_server = staticmethod(setup_server) - - def test_config(self): - from textwrap import dedent - - # variable substitution with [DEFAULT] - conf = dedent(""" - [DEFAULT] - dir = "/some/dir" - my.dir = %(dir)s + "/sub" - - [my] - my.dir = %(dir)s + "/my/dir" - my.dir2 = %(my.dir)s + '/dir2' - - """) - - fp = StringIOFromNative(conf) - - cherrypy.config.update(fp) - self.assertEqual(cherrypy.config['my']['my.dir'], '/some/dir/my/dir') - self.assertEqual(cherrypy.config['my'] - ['my.dir2'], '/some/dir/my/dir/dir2') - - -class CallablesInConfigTest(unittest.TestCase): - setup_server = staticmethod(setup_server) - - def test_call_with_literal_dict(self): - from textwrap import dedent - conf = dedent(""" - [my] - value = dict(**{'foo': 'bar'}) - """) - fp = StringIOFromNative(conf) - cherrypy.config.update(fp) - self.assertEqual(cherrypy.config['my']['value'], {'foo': 'bar'}) - - def test_call_with_kwargs(self): - from textwrap import dedent - conf = dedent(""" - [my] - value = dict(foo="buzz", **cherrypy._test_dict) - """) - test_dict = { - 'foo': 'bar', - 'bar': 'foo', - 'fizz': 'buzz' - } - cherrypy._test_dict = test_dict - fp = StringIOFromNative(conf) - cherrypy.config.update(fp) - test_dict['foo'] = 'buzz' - self.assertEqual(cherrypy.config['my']['value']['foo'], 'buzz') - self.assertEqual(cherrypy.config['my']['value'], test_dict) - del cherrypy._test_dict diff --git a/lib/cherrypy/test/test_config_server.py b/lib/cherrypy/test/test_config_server.py deleted file mode 100644 index 7b183530..00000000 --- a/lib/cherrypy/test/test_config_server.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Tests for the CherryPy configuration system.""" - -import os - -import cherrypy -from cherrypy.test import helper - - -localDir = os.path.join(os.getcwd(), os.path.dirname(__file__)) - - -# Client-side code # - - -class ServerConfigTests(helper.CPWebCase): - - @staticmethod - def setup_server(): - - class Root: - - @cherrypy.expose - def index(self): - return cherrypy.request.wsgi_environ['SERVER_PORT'] - - @cherrypy.expose - def upload(self, file): - return 'Size: %s' % len(file.file.read()) - - @cherrypy.expose - @cherrypy.config(**{'request.body.maxbytes': 100}) - def tinyupload(self): - return cherrypy.request.body.read() - - cherrypy.tree.mount(Root()) - - cherrypy.config.update({ - 'server.socket_host': '0.0.0.0', - 'server.socket_port': 9876, - 'server.max_request_body_size': 200, - 'server.max_request_header_size': 500, - 'server.socket_timeout': 0.5, - - # Test explicit server.instance - 'server.2.instance': 'cherrypy._cpwsgi_server.CPWSGIServer', - 'server.2.socket_port': 9877, - - # Test non-numeric - # Also test default server.instance = builtin server - 'server.yetanother.socket_port': 9878, - }) - - PORT = 9876 - - def testBasicConfig(self): - self.getPage('/') - self.assertBody(str(self.PORT)) - - def testAdditionalServers(self): - if self.scheme == 'https': - return self.skip('not available under ssl') - self.PORT = 9877 - self.getPage('/') - self.assertBody(str(self.PORT)) - self.PORT = 9878 - self.getPage('/') - self.assertBody(str(self.PORT)) - - def testMaxRequestSizePerHandler(self): - if getattr(cherrypy.server, 'using_apache', False): - return self.skip('skipped due to known Apache differences... ') - - self.getPage('/tinyupload', method='POST', - headers=[('Content-Type', 'text/plain'), - ('Content-Length', '100')], - body='x' * 100) - self.assertStatus(200) - self.assertBody('x' * 100) - - self.getPage('/tinyupload', method='POST', - headers=[('Content-Type', 'text/plain'), - ('Content-Length', '101')], - body='x' * 101) - self.assertStatus(413) - - def testMaxRequestSize(self): - if getattr(cherrypy.server, 'using_apache', False): - return self.skip('skipped due to known Apache differences... ') - - for size in (500, 5000, 50000): - self.getPage('/', headers=[('From', 'x' * 500)]) - self.assertStatus(413) - - # Test for https://github.com/cherrypy/cherrypy/issues/421 - # (Incorrect border condition in readline of SizeCheckWrapper). - # This hangs in rev 891 and earlier. - lines256 = 'x' * 248 - self.getPage('/', - headers=[('Host', '%s:%s' % (self.HOST, self.PORT)), - ('From', lines256)]) - - # Test upload - cd = ( - 'Content-Disposition: form-data; ' - 'name="file"; ' - 'filename="hello.txt"' - ) - body = '\r\n'.join([ - '--x', - cd, - 'Content-Type: text/plain', - '', - '%s', - '--x--']) - partlen = 200 - len(body) - b = body % ('x' * partlen) - h = [('Content-type', 'multipart/form-data; boundary=x'), - ('Content-Length', '%s' % len(b))] - self.getPage('/upload', h, 'POST', b) - self.assertBody('Size: %d' % partlen) - - b = body % ('x' * 200) - h = [('Content-type', 'multipart/form-data; boundary=x'), - ('Content-Length', '%s' % len(b))] - self.getPage('/upload', h, 'POST', b) - self.assertStatus(413) diff --git a/lib/cherrypy/test/test_conn.py b/lib/cherrypy/test/test_conn.py deleted file mode 100644 index e4426c42..00000000 --- a/lib/cherrypy/test/test_conn.py +++ /dev/null @@ -1,864 +0,0 @@ -"""Tests for TCP connection handling, including proper and timely close.""" - -import errno -import socket -import sys -import time -import urllib.parse -from http.client import BadStatusLine, HTTPConnection, NotConnected - -from cheroot.test import webtest - -import cherrypy -from cherrypy._cpcompat import HTTPSConnection, ntob, tonative -from cherrypy.test import helper - - -timeout = 1 -pov = 'pPeErRsSiIsStTeEnNcCeE oOfF vViIsSiIoOnN' - - -def setup_server(): - - def raise500(): - raise cherrypy.HTTPError(500) - - class Root: - - @cherrypy.expose - def index(self): - return pov - page1 = index - page2 = index - page3 = index - - @cherrypy.expose - def hello(self): - return 'Hello, world!' - - @cherrypy.expose - def timeout(self, t): - return str(cherrypy.server.httpserver.timeout) - - @cherrypy.expose - @cherrypy.config(**{'response.stream': True}) - def stream(self, set_cl=False): - if set_cl: - cherrypy.response.headers['Content-Length'] = 10 - - def content(): - for x in range(10): - yield str(x) - - return content() - - @cherrypy.expose - def error(self, code=500): - raise cherrypy.HTTPError(code) - - @cherrypy.expose - def upload(self): - if not cherrypy.request.method == 'POST': - raise AssertionError("'POST' != request.method %r" % - cherrypy.request.method) - return "thanks for '%s'" % cherrypy.request.body.read() - - @cherrypy.expose - def custom(self, response_code): - cherrypy.response.status = response_code - return 'Code = %s' % response_code - - @cherrypy.expose - @cherrypy.config(**{'hooks.on_start_resource': raise500}) - def err_before_read(self): - return 'ok' - - @cherrypy.expose - def one_megabyte_of_a(self): - return ['a' * 1024] * 1024 - - @cherrypy.expose - # Turn off the encoding tool so it doens't collapse - # our response body and reclaculate the Content-Length. - @cherrypy.config(**{'tools.encode.on': False}) - def custom_cl(self, body, cl): - cherrypy.response.headers['Content-Length'] = cl - if not isinstance(body, list): - body = [body] - newbody = [] - for chunk in body: - if isinstance(chunk, str): - chunk = chunk.encode('ISO-8859-1') - newbody.append(chunk) - return newbody - - cherrypy.tree.mount(Root()) - cherrypy.config.update({ - 'server.max_request_body_size': 1001, - 'server.socket_timeout': timeout, - }) - - -class ConnectionCloseTests(helper.CPWebCase): - setup_server = staticmethod(setup_server) - - def test_HTTP11(self): - if cherrypy.server.protocol_version != 'HTTP/1.1': - return self.skip() - - self.PROTOCOL = 'HTTP/1.1' - - self.persistent = True - - # Make the first request and assert there's no "Connection: close". - self.getPage('/') - self.assertStatus('200 OK') - self.assertBody(pov) - self.assertNoHeader('Connection') - - # Make another request on the same connection. - self.getPage('/page1') - self.assertStatus('200 OK') - self.assertBody(pov) - self.assertNoHeader('Connection') - - # Test client-side close. - self.getPage('/page2', headers=[('Connection', 'close')]) - self.assertStatus('200 OK') - self.assertBody(pov) - self.assertHeader('Connection', 'close') - - # Make another request on the same connection, which should error. - self.assertRaises(NotConnected, self.getPage, '/') - - def test_Streaming_no_len(self): - try: - self._streaming(set_cl=False) - finally: - try: - self.HTTP_CONN.close() - except (TypeError, AttributeError): - pass - - def test_Streaming_with_len(self): - try: - self._streaming(set_cl=True) - finally: - try: - self.HTTP_CONN.close() - except (TypeError, AttributeError): - pass - - def _streaming(self, set_cl): - if cherrypy.server.protocol_version == 'HTTP/1.1': - self.PROTOCOL = 'HTTP/1.1' - - self.persistent = True - - # Make the first request and assert there's no "Connection: close". - self.getPage('/') - self.assertStatus('200 OK') - self.assertBody(pov) - self.assertNoHeader('Connection') - - # Make another, streamed request on the same connection. - if set_cl: - # When a Content-Length is provided, the content should stream - # without closing the connection. - self.getPage('/stream?set_cl=Yes') - self.assertHeader('Content-Length') - self.assertNoHeader('Connection', 'close') - self.assertNoHeader('Transfer-Encoding') - - self.assertStatus('200 OK') - self.assertBody('0123456789') - else: - # When no Content-Length response header is provided, - # streamed output will either close the connection, or use - # chunked encoding, to determine transfer-length. - self.getPage('/stream') - self.assertNoHeader('Content-Length') - self.assertStatus('200 OK') - self.assertBody('0123456789') - - chunked_response = False - for k, v in self.headers: - if k.lower() == 'transfer-encoding': - if str(v) == 'chunked': - chunked_response = True - - if chunked_response: - self.assertNoHeader('Connection', 'close') - else: - self.assertHeader('Connection', 'close') - - # Make another request on the same connection, which should - # error. - self.assertRaises(NotConnected, self.getPage, '/') - - # Try HEAD. See - # https://github.com/cherrypy/cherrypy/issues/864. - self.getPage('/stream', method='HEAD') - self.assertStatus('200 OK') - self.assertBody('') - self.assertNoHeader('Transfer-Encoding') - else: - self.PROTOCOL = 'HTTP/1.0' - - self.persistent = True - - # Make the first request and assert Keep-Alive. - self.getPage('/', headers=[('Connection', 'Keep-Alive')]) - self.assertStatus('200 OK') - self.assertBody(pov) - self.assertHeader('Connection', 'Keep-Alive') - - # Make another, streamed request on the same connection. - if set_cl: - # When a Content-Length is provided, the content should - # stream without closing the connection. - self.getPage('/stream?set_cl=Yes', - headers=[('Connection', 'Keep-Alive')]) - self.assertHeader('Content-Length') - self.assertHeader('Connection', 'Keep-Alive') - self.assertNoHeader('Transfer-Encoding') - self.assertStatus('200 OK') - self.assertBody('0123456789') - else: - # When a Content-Length is not provided, - # the server should close the connection. - self.getPage('/stream', headers=[('Connection', 'Keep-Alive')]) - self.assertStatus('200 OK') - self.assertBody('0123456789') - - self.assertNoHeader('Content-Length') - self.assertNoHeader('Connection', 'Keep-Alive') - self.assertNoHeader('Transfer-Encoding') - - # Make another request on the same connection, which should - # error. - self.assertRaises(NotConnected, self.getPage, '/') - - def test_HTTP10_KeepAlive(self): - self.PROTOCOL = 'HTTP/1.0' - if self.scheme == 'https': - self.HTTP_CONN = HTTPSConnection - else: - self.HTTP_CONN = HTTPConnection - - # Test a normal HTTP/1.0 request. - self.getPage('/page2') - self.assertStatus('200 OK') - self.assertBody(pov) - # Apache, for example, may emit a Connection header even for HTTP/1.0 - # self.assertNoHeader("Connection") - - # Test a keep-alive HTTP/1.0 request. - self.persistent = True - - self.getPage('/page3', headers=[('Connection', 'Keep-Alive')]) - self.assertStatus('200 OK') - self.assertBody(pov) - self.assertHeader('Connection', 'Keep-Alive') - - # Remove the keep-alive header again. - self.getPage('/page3') - self.assertStatus('200 OK') - self.assertBody(pov) - # Apache, for example, may emit a Connection header even for HTTP/1.0 - # self.assertNoHeader("Connection") - - -class PipelineTests(helper.CPWebCase): - setup_server = staticmethod(setup_server) - - def test_HTTP11_Timeout(self): - # If we timeout without sending any data, - # the server will close the conn with a 408. - if cherrypy.server.protocol_version != 'HTTP/1.1': - return self.skip() - - self.PROTOCOL = 'HTTP/1.1' - - # Connect but send nothing. - self.persistent = True - conn = self.HTTP_CONN - conn.auto_open = False - conn.connect() - - # Wait for our socket timeout - time.sleep(timeout * 2) - - # The request should have returned 408 already. - response = conn.response_class(conn.sock, method='GET') - response.begin() - self.assertEqual(response.status, 408) - conn.close() - - # Connect but send half the headers only. - self.persistent = True - conn = self.HTTP_CONN - conn.auto_open = False - conn.connect() - conn.send(b'GET /hello HTTP/1.1') - conn.send(('Host: %s' % self.HOST).encode('ascii')) - - # Wait for our socket timeout - time.sleep(timeout * 2) - - # The conn should have already sent 408. - response = conn.response_class(conn.sock, method='GET') - response.begin() - self.assertEqual(response.status, 408) - conn.close() - - def test_HTTP11_Timeout_after_request(self): - # If we timeout after at least one request has succeeded, - # the server will close the conn without 408. - if cherrypy.server.protocol_version != 'HTTP/1.1': - return self.skip() - - self.PROTOCOL = 'HTTP/1.1' - - # Make an initial request - self.persistent = True - conn = self.HTTP_CONN - conn.putrequest('GET', '/timeout?t=%s' % timeout, skip_host=True) - conn.putheader('Host', self.HOST) - conn.endheaders() - response = conn.response_class(conn.sock, method='GET') - response.begin() - self.assertEqual(response.status, 200) - self.body = response.read() - self.assertBody(str(timeout)) - - # Make a second request on the same socket - conn._output(b'GET /hello HTTP/1.1') - conn._output(ntob('Host: %s' % self.HOST, 'ascii')) - conn._send_output() - response = conn.response_class(conn.sock, method='GET') - response.begin() - self.assertEqual(response.status, 200) - self.body = response.read() - self.assertBody('Hello, world!') - - # Wait for our socket timeout - time.sleep(timeout * 2) - - # Make another request on the same socket, which should error - conn._output(b'GET /hello HTTP/1.1') - conn._output(ntob('Host: %s' % self.HOST, 'ascii')) - conn._send_output() - response = conn.response_class(conn.sock, method='GET') - msg = ( - "Writing to timed out socket didn't fail as it should have: %s") - try: - response.begin() - except Exception: - if not isinstance(sys.exc_info()[1], - (socket.error, BadStatusLine)): - self.fail(msg % sys.exc_info()[1]) - else: - if response.status != 408: - self.fail(msg % response.read()) - - conn.close() - - # Make another request on a new socket, which should work - self.persistent = True - conn = self.HTTP_CONN - conn.putrequest('GET', '/', skip_host=True) - conn.putheader('Host', self.HOST) - conn.endheaders() - response = conn.response_class(conn.sock, method='GET') - response.begin() - self.assertEqual(response.status, 200) - self.body = response.read() - self.assertBody(pov) - - # Make another request on the same socket, - # but timeout on the headers - conn.send(b'GET /hello HTTP/1.1') - # Wait for our socket timeout - time.sleep(timeout * 2) - response = conn.response_class(conn.sock, method='GET') - try: - response.begin() - except Exception: - if not isinstance(sys.exc_info()[1], - (socket.error, BadStatusLine)): - self.fail(msg % sys.exc_info()[1]) - else: - if response.status != 408: - self.fail(msg % response.read()) - - conn.close() - - # Retry the request on a new connection, which should work - self.persistent = True - conn = self.HTTP_CONN - conn.putrequest('GET', '/', skip_host=True) - conn.putheader('Host', self.HOST) - conn.endheaders() - response = conn.response_class(conn.sock, method='GET') - response.begin() - self.assertEqual(response.status, 200) - self.body = response.read() - self.assertBody(pov) - conn.close() - - def test_HTTP11_pipelining(self): - if cherrypy.server.protocol_version != 'HTTP/1.1': - return self.skip() - - self.PROTOCOL = 'HTTP/1.1' - - # Test pipelining. httplib doesn't support this directly. - self.persistent = True - conn = self.HTTP_CONN - - # Put request 1 - conn.putrequest('GET', '/hello', skip_host=True) - conn.putheader('Host', self.HOST) - conn.endheaders() - - for trial in range(5): - # Put next request - conn._output(b'GET /hello HTTP/1.1') - conn._output(ntob('Host: %s' % self.HOST, 'ascii')) - conn._send_output() - - # Retrieve previous response - response = conn.response_class(conn.sock, method='GET') - # there is a bug in python3 regarding the buffering of - # ``conn.sock``. Until that bug get's fixed we will - # monkey patch the ``response`` instance. - # https://bugs.python.org/issue23377 - response.fp = conn.sock.makefile('rb', 0) - response.begin() - body = response.read(13) - self.assertEqual(response.status, 200) - self.assertEqual(body, b'Hello, world!') - - # Retrieve final response - response = conn.response_class(conn.sock, method='GET') - response.begin() - body = response.read() - self.assertEqual(response.status, 200) - self.assertEqual(body, b'Hello, world!') - - conn.close() - - def test_100_Continue(self): - if cherrypy.server.protocol_version != 'HTTP/1.1': - return self.skip() - - self.PROTOCOL = 'HTTP/1.1' - - self.persistent = True - conn = self.HTTP_CONN - - # Try a page without an Expect request header first. - # Note that httplib's response.begin automatically ignores - # 100 Continue responses, so we must manually check for it. - try: - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', self.HOST) - conn.putheader('Content-Type', 'text/plain') - conn.putheader('Content-Length', '4') - conn.endheaders() - conn.send(ntob("d'oh")) - response = conn.response_class(conn.sock, method='POST') - version, status, reason = response._read_status() - self.assertNotEqual(status, 100) - finally: - conn.close() - - # Now try a page with an Expect header... - try: - conn.connect() - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', self.HOST) - conn.putheader('Content-Type', 'text/plain') - conn.putheader('Content-Length', '17') - conn.putheader('Expect', '100-continue') - conn.endheaders() - response = conn.response_class(conn.sock, method='POST') - - # ...assert and then skip the 100 response - version, status, reason = response._read_status() - self.assertEqual(status, 100) - while True: - line = response.fp.readline().strip() - if line: - self.fail( - '100 Continue should not output any headers. Got %r' % - line) - else: - break - - # ...send the body - body = b'I am a small file' - conn.send(body) - - # ...get the final response - response.begin() - self.status, self.headers, self.body = webtest.shb(response) - self.assertStatus(200) - self.assertBody("thanks for '%s'" % body) - finally: - conn.close() - - -class ConnectionTests(helper.CPWebCase): - setup_server = staticmethod(setup_server) - - def test_readall_or_close(self): - if cherrypy.server.protocol_version != 'HTTP/1.1': - return self.skip() - - self.PROTOCOL = 'HTTP/1.1' - - if self.scheme == 'https': - self.HTTP_CONN = HTTPSConnection - else: - self.HTTP_CONN = HTTPConnection - - # Test a max of 0 (the default) and then reset to what it was above. - old_max = cherrypy.server.max_request_body_size - for new_max in (0, old_max): - cherrypy.server.max_request_body_size = new_max - - self.persistent = True - conn = self.HTTP_CONN - - # Get a POST page with an error - conn.putrequest('POST', '/err_before_read', skip_host=True) - conn.putheader('Host', self.HOST) - conn.putheader('Content-Type', 'text/plain') - conn.putheader('Content-Length', '1000') - conn.putheader('Expect', '100-continue') - conn.endheaders() - response = conn.response_class(conn.sock, method='POST') - - # ...assert and then skip the 100 response - version, status, reason = response._read_status() - self.assertEqual(status, 100) - while True: - skip = response.fp.readline().strip() - if not skip: - break - - # ...send the body - conn.send(ntob('x' * 1000)) - - # ...get the final response - response.begin() - self.status, self.headers, self.body = webtest.shb(response) - self.assertStatus(500) - - # Now try a working page with an Expect header... - conn._output(b'POST /upload HTTP/1.1') - conn._output(ntob('Host: %s' % self.HOST, 'ascii')) - conn._output(b'Content-Type: text/plain') - conn._output(b'Content-Length: 17') - conn._output(b'Expect: 100-continue') - conn._send_output() - response = conn.response_class(conn.sock, method='POST') - - # ...assert and then skip the 100 response - version, status, reason = response._read_status() - self.assertEqual(status, 100) - while True: - skip = response.fp.readline().strip() - if not skip: - break - - # ...send the body - body = b'I am a small file' - conn.send(body) - - # ...get the final response - response.begin() - self.status, self.headers, self.body = webtest.shb(response) - self.assertStatus(200) - self.assertBody("thanks for '%s'" % body) - conn.close() - - def test_No_Message_Body(self): - if cherrypy.server.protocol_version != 'HTTP/1.1': - return self.skip() - - self.PROTOCOL = 'HTTP/1.1' - - # Set our HTTP_CONN to an instance so it persists between requests. - self.persistent = True - - # Make the first request and assert there's no "Connection: close". - self.getPage('/') - self.assertStatus('200 OK') - self.assertBody(pov) - self.assertNoHeader('Connection') - - # Make a 204 request on the same connection. - self.getPage('/custom/204') - self.assertStatus(204) - self.assertNoHeader('Content-Length') - self.assertBody('') - self.assertNoHeader('Connection') - - # Make a 304 request on the same connection. - self.getPage('/custom/304') - self.assertStatus(304) - self.assertNoHeader('Content-Length') - self.assertBody('') - self.assertNoHeader('Connection') - - def test_Chunked_Encoding(self): - if cherrypy.server.protocol_version != 'HTTP/1.1': - return self.skip() - - if (hasattr(self, 'harness') and - 'modpython' in self.harness.__class__.__name__.lower()): - # mod_python forbids chunked encoding - return self.skip() - - self.PROTOCOL = 'HTTP/1.1' - - # Set our HTTP_CONN to an instance so it persists between requests. - self.persistent = True - conn = self.HTTP_CONN - - # Try a normal chunked request (with extensions) - body = ntob('8;key=value\r\nxx\r\nxxxx\r\n5\r\nyyyyy\r\n0\r\n' - 'Content-Type: application/json\r\n' - '\r\n') - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', self.HOST) - conn.putheader('Transfer-Encoding', 'chunked') - conn.putheader('Trailer', 'Content-Type') - # Note that this is somewhat malformed: - # we shouldn't be sending Content-Length. - # RFC 2616 says the server should ignore it. - conn.putheader('Content-Length', '3') - conn.endheaders() - conn.send(body) - response = conn.getresponse() - self.status, self.headers, self.body = webtest.shb(response) - self.assertStatus('200 OK') - self.assertBody("thanks for '%s'" % b'xx\r\nxxxxyyyyy') - - # Try a chunked request that exceeds server.max_request_body_size. - # Note that the delimiters and trailer are included. - body = ntob('3e3\r\n' + ('x' * 995) + '\r\n0\r\n\r\n') - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', self.HOST) - conn.putheader('Transfer-Encoding', 'chunked') - conn.putheader('Content-Type', 'text/plain') - # Chunked requests don't need a content-length - # # conn.putheader("Content-Length", len(body)) - conn.endheaders() - conn.send(body) - response = conn.getresponse() - self.status, self.headers, self.body = webtest.shb(response) - self.assertStatus(413) - conn.close() - - def test_Content_Length_in(self): - # Try a non-chunked request where Content-Length exceeds - # server.max_request_body_size. Assert error before body send. - self.persistent = True - conn = self.HTTP_CONN - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', self.HOST) - conn.putheader('Content-Type', 'text/plain') - conn.putheader('Content-Length', '9999') - conn.endheaders() - response = conn.getresponse() - self.status, self.headers, self.body = webtest.shb(response) - self.assertStatus(413) - self.assertBody('The entity sent with the request exceeds ' - 'the maximum allowed bytes.') - conn.close() - - def test_Content_Length_out_preheaders(self): - # Try a non-chunked response where Content-Length is less than - # the actual bytes in the response body. - self.persistent = True - conn = self.HTTP_CONN - conn.putrequest('GET', '/custom_cl?body=I+have+too+many+bytes&cl=5', - skip_host=True) - conn.putheader('Host', self.HOST) - conn.endheaders() - response = conn.getresponse() - self.status, self.headers, self.body = webtest.shb(response) - self.assertStatus(500) - self.assertBody( - 'The requested resource returned more bytes than the ' - 'declared Content-Length.') - conn.close() - - def test_Content_Length_out_postheaders(self): - # Try a non-chunked response where Content-Length is less than - # the actual bytes in the response body. - self.persistent = True - conn = self.HTTP_CONN - conn.putrequest( - 'GET', '/custom_cl?body=I+too&body=+have+too+many&cl=5', - skip_host=True) - conn.putheader('Host', self.HOST) - conn.endheaders() - response = conn.getresponse() - self.status, self.headers, self.body = webtest.shb(response) - self.assertStatus(200) - self.assertBody('I too') - conn.close() - - def test_598(self): - tmpl = '{scheme}://{host}:{port}/one_megabyte_of_a/' - url = tmpl.format( - scheme=self.scheme, - host=self.HOST, - port=self.PORT, - ) - remote_data_conn = urllib.request.urlopen(url) - buf = remote_data_conn.read(512) - time.sleep(timeout * 0.6) - remaining = (1024 * 1024) - 512 - while remaining: - data = remote_data_conn.read(remaining) - if not data: - break - else: - buf += data - remaining -= len(data) - - self.assertEqual(len(buf), 1024 * 1024) - self.assertEqual(buf, ntob('a' * 1024 * 1024)) - self.assertEqual(remaining, 0) - remote_data_conn.close() - - -def setup_upload_server(): - - class Root: - @cherrypy.expose - def upload(self): - if not cherrypy.request.method == 'POST': - raise AssertionError("'POST' != request.method %r" % - cherrypy.request.method) - return "thanks for '%s'" % tonative(cherrypy.request.body.read()) - - cherrypy.tree.mount(Root()) - cherrypy.config.update({ - 'server.max_request_body_size': 1001, - 'server.socket_timeout': 10, - 'server.accepted_queue_size': 5, - 'server.accepted_queue_timeout': 0.1, - }) - - -reset_names = 'ECONNRESET', 'WSAECONNRESET' -socket_reset_errors = [ - getattr(errno, name) - for name in reset_names - if hasattr(errno, name) -] -'reset error numbers available on this platform' - -socket_reset_errors += [ - # Python 3.5 raises an http.client.RemoteDisconnected - # with this message - 'Remote end closed connection without response', -] - - -class LimitedRequestQueueTests(helper.CPWebCase): - setup_server = staticmethod(setup_upload_server) - - def test_queue_full(self): - conns = [] - overflow_conn = None - - try: - # Make 15 initial requests and leave them open, which should use - # all of wsgiserver's WorkerThreads and fill its Queue. - for i in range(15): - conn = self.HTTP_CONN(self.HOST, self.PORT) - conn.putrequest('POST', '/upload', skip_host=True) - conn.putheader('Host', self.HOST) - conn.putheader('Content-Type', 'text/plain') - conn.putheader('Content-Length', '4') - conn.endheaders() - conns.append(conn) - - # Now try a 16th conn, which should be closed by the - # server immediately. - overflow_conn = self.HTTP_CONN(self.HOST, self.PORT) - # Manually connect since httplib won't let us set a timeout - for res in socket.getaddrinfo(self.HOST, self.PORT, 0, - socket.SOCK_STREAM): - af, socktype, proto, canonname, sa = res - overflow_conn.sock = socket.socket(af, socktype, proto) - overflow_conn.sock.settimeout(5) - overflow_conn.sock.connect(sa) - break - - overflow_conn.putrequest('GET', '/', skip_host=True) - overflow_conn.putheader('Host', self.HOST) - overflow_conn.endheaders() - response = overflow_conn.response_class( - overflow_conn.sock, - method='GET', - ) - try: - response.begin() - except socket.error as exc: - if exc.args[0] in socket_reset_errors: - pass # Expected. - else: - tmpl = ( - 'Overflow conn did not get RST. ' - 'Got {exc.args!r} instead' - ) - raise AssertionError(tmpl.format(**locals())) - except BadStatusLine: - # This is a special case in OS X. Linux and Windows will - # RST correctly. - assert sys.platform == 'darwin' - else: - raise AssertionError('Overflow conn did not get RST ') - finally: - for conn in conns: - conn.send(b'done') - response = conn.response_class(conn.sock, method='POST') - response.begin() - self.body = response.read() - self.assertBody("thanks for 'done'") - self.assertEqual(response.status, 200) - conn.close() - if overflow_conn: - overflow_conn.close() - - -class BadRequestTests(helper.CPWebCase): - setup_server = staticmethod(setup_server) - - def test_No_CRLF(self): - self.persistent = True - - conn = self.HTTP_CONN - conn.send(b'GET /hello HTTP/1.1\n\n') - response = conn.response_class(conn.sock, method='GET') - response.begin() - self.body = response.read() - self.assertBody('HTTP requires CRLF terminators') - conn.close() - - conn.connect() - conn.send(b'GET /hello HTTP/1.1\r\n\n') - response = conn.response_class(conn.sock, method='GET') - response.begin() - self.body = response.read() - self.assertBody('HTTP requires CRLF terminators') - conn.close() diff --git a/lib/cherrypy/test/test_core.py b/lib/cherrypy/test/test_core.py deleted file mode 100644 index 42460b3f..00000000 --- a/lib/cherrypy/test/test_core.py +++ /dev/null @@ -1,825 +0,0 @@ -# coding: utf-8 - -"""Basic tests for the CherryPy core: request handling.""" - -import os -import sys -import types - -import cherrypy -from cherrypy._cpcompat import ntou -from cherrypy import _cptools, tools -from cherrypy.lib import httputil, static - -from cherrypy.test._test_decorators import ExposeExamples -from cherrypy.test import helper - - -localDir = os.path.dirname(__file__) -favicon_path = os.path.join(os.getcwd(), localDir, '../favicon.ico') - -# Client-side code # - - -class CoreRequestHandlingTest(helper.CPWebCase): - - @staticmethod - def setup_server(): - class Root: - - @cherrypy.expose - def index(self): - return 'hello' - - favicon_ico = tools.staticfile.handler(filename=favicon_path) - - @cherrypy.expose - def defct(self, newct): - newct = 'text/%s' % newct - cherrypy.config.update({'tools.response_headers.on': True, - 'tools.response_headers.headers': - [('Content-Type', newct)]}) - - @cherrypy.expose - def baseurl(self, path_info, relative=None): - return cherrypy.url(path_info, relative=bool(relative)) - - root = Root() - root.expose_dec = ExposeExamples() - - class TestType(type): - - """Metaclass which automatically exposes all functions in each - subclass, and adds an instance of the subclass as an attribute - of root. - """ - def __init__(cls, name, bases, dct): - type.__init__(cls, name, bases, dct) - for value in dct.values(): - if isinstance(value, types.FunctionType): - value.exposed = True - setattr(root, name.lower(), cls()) - Test = TestType('Test', (object, ), {}) - - @cherrypy.config(**{'tools.trailing_slash.on': False}) - class URL(Test): - - def index(self, path_info, relative=None): - if relative != 'server': - relative = bool(relative) - return cherrypy.url(path_info, relative=relative) - - def leaf(self, path_info, relative=None): - if relative != 'server': - relative = bool(relative) - return cherrypy.url(path_info, relative=relative) - - def qs(self, qs): - return cherrypy.url(qs=qs) - - def log_status(): - Status.statuses.append(cherrypy.response.status) - cherrypy.tools.log_status = cherrypy.Tool( - 'on_end_resource', log_status) - - class Status(Test): - - def index(self): - return 'normal' - - def blank(self): - cherrypy.response.status = '' - - # According to RFC 2616, new status codes are OK as long as they - # are between 100 and 599. - - # Here is an illegal code... - def illegal(self): - cherrypy.response.status = 781 - return 'oops' - - # ...and here is an unknown but legal code. - def unknown(self): - cherrypy.response.status = '431 My custom error' - return 'funky' - - # Non-numeric code - def bad(self): - cherrypy.response.status = 'error' - return 'bad news' - - statuses = [] - - @cherrypy.config(**{'tools.log_status.on': True}) - def on_end_resource_stage(self): - return repr(self.statuses) - - class Redirect(Test): - - @cherrypy.config(**{ - 'tools.err_redirect.on': True, - 'tools.err_redirect.url': '/errpage', - 'tools.err_redirect.internal': False, - }) - class Error: - @cherrypy.expose - def index(self): - raise NameError('redirect_test') - - error = Error() - - def index(self): - return 'child' - - def custom(self, url, code): - raise cherrypy.HTTPRedirect(url, code) - - @cherrypy.config(**{'tools.trailing_slash.extra': True}) - def by_code(self, code): - raise cherrypy.HTTPRedirect('somewhere%20else', code) - - def nomodify(self): - raise cherrypy.HTTPRedirect('', 304) - - def proxy(self): - raise cherrypy.HTTPRedirect('proxy', 305) - - def stringify(self): - return str(cherrypy.HTTPRedirect('/')) - - def fragment(self, frag): - raise cherrypy.HTTPRedirect('/some/url#%s' % frag) - - def url_with_quote(self): - raise cherrypy.HTTPRedirect("/some\"url/that'we/want") - - def url_with_xss(self): - raise cherrypy.HTTPRedirect( - "/someurl/that'we/want") - - def url_with_unicode(self): - raise cherrypy.HTTPRedirect(ntou('тест', 'utf-8')) - - def login_redir(): - if not getattr(cherrypy.request, 'login', None): - raise cherrypy.InternalRedirect('/internalredirect/login') - tools.login_redir = _cptools.Tool('before_handler', login_redir) - - def redir_custom(): - raise cherrypy.InternalRedirect('/internalredirect/custom_err') - - class InternalRedirect(Test): - - def index(self): - raise cherrypy.InternalRedirect('/') - - @cherrypy.expose - @cherrypy.config(**{'hooks.before_error_response': redir_custom}) - def choke(self): - return 3 / 0 - - def relative(self, a, b): - raise cherrypy.InternalRedirect('cousin?t=6') - - def cousin(self, t): - assert cherrypy.request.prev.closed - return cherrypy.request.prev.query_string - - def petshop(self, user_id): - if user_id == 'parrot': - # Trade it for a slug when redirecting - raise cherrypy.InternalRedirect( - '/image/getImagesByUser?user_id=slug') - elif user_id == 'terrier': - # Trade it for a fish when redirecting - raise cherrypy.InternalRedirect( - '/image/getImagesByUser?user_id=fish') - else: - # This should pass the user_id through to getImagesByUser - raise cherrypy.InternalRedirect( - '/image/getImagesByUser?user_id=%s' % str(user_id)) - - # We support Python 2.3, but the @-deco syntax would look like - # this: - # @tools.login_redir() - def secure(self): - return 'Welcome!' - secure = tools.login_redir()(secure) - # Since calling the tool returns the same function you pass in, - # you could skip binding the return value, and just write: - # tools.login_redir()(secure) - - def login(self): - return 'Please log in' - - def custom_err(self): - return 'Something went horribly wrong.' - - @cherrypy.config(**{'hooks.before_request_body': redir_custom}) - def early_ir(self, arg): - return 'whatever' - - class Image(Test): - - def getImagesByUser(self, user_id): - return '0 images for %s' % user_id - - class Flatten(Test): - - def as_string(self): - return 'content' - - def as_list(self): - return ['con', 'tent'] - - def as_yield(self): - yield b'content' - - @cherrypy.config(**{'tools.flatten.on': True}) - def as_dblyield(self): - yield self.as_yield() - - def as_refyield(self): - for chunk in self.as_yield(): - yield chunk - - class Ranges(Test): - - def get_ranges(self, bytes): - return repr(httputil.get_ranges('bytes=%s' % bytes, 8)) - - def slice_file(self): - path = os.path.join(os.getcwd(), os.path.dirname(__file__)) - return static.serve_file( - os.path.join(path, 'static/index.html')) - - class Cookies(Test): - - def single(self, name): - cookie = cherrypy.request.cookie[name] - # Python2's SimpleCookie.__setitem__ won't take unicode keys. - cherrypy.response.cookie[str(name)] = cookie.value - - def multiple(self, names): - list(map(self.single, names)) - - def append_headers(header_list, debug=False): - if debug: - cherrypy.log( - 'Extending response headers with %s' % repr(header_list), - 'TOOLS.APPEND_HEADERS') - cherrypy.serving.response.header_list.extend(header_list) - cherrypy.tools.append_headers = cherrypy.Tool( - 'on_end_resource', append_headers) - - class MultiHeader(Test): - - def header_list(self): - pass - header_list = cherrypy.tools.append_headers(header_list=[ - (b'WWW-Authenticate', b'Negotiate'), - (b'WWW-Authenticate', b'Basic realm="foo"'), - ])(header_list) - - def commas(self): - cherrypy.response.headers[ - 'WWW-Authenticate'] = 'Negotiate,Basic realm="foo"' - - cherrypy.tree.mount(root) - - def testStatus(self): - self.getPage('/status/') - self.assertBody('normal') - self.assertStatus(200) - - self.getPage('/status/blank') - self.assertBody('') - self.assertStatus(200) - - self.getPage('/status/illegal') - self.assertStatus(500) - msg = 'Illegal response status from server (781 is out of range).' - self.assertErrorPage(500, msg) - - if not getattr(cherrypy.server, 'using_apache', False): - self.getPage('/status/unknown') - self.assertBody('funky') - self.assertStatus(431) - - self.getPage('/status/bad') - self.assertStatus(500) - msg = "Illegal response status from server ('error' is non-numeric)." - self.assertErrorPage(500, msg) - - def test_on_end_resource_status(self): - self.getPage('/status/on_end_resource_stage') - self.assertBody('[]') - self.getPage('/status/on_end_resource_stage') - self.assertBody(repr(['200 OK'])) - - def testSlashes(self): - # Test that requests for index methods without a trailing slash - # get redirected to the same URI path with a trailing slash. - # Make sure GET params are preserved. - self.getPage('/redirect?id=3') - self.assertStatus(301) - self.assertMatchesBody( - '' - '%s/redirect/[?]id=3' % (self.base(), self.base()) - ) - - if self.prefix(): - # Corner case: the "trailing slash" redirect could be tricky if - # we're using a virtual root and the URI is "/vroot" (no slash). - self.getPage('') - self.assertStatus(301) - self.assertMatchesBody("%s/" % - (self.base(), self.base())) - - # Test that requests for NON-index methods WITH a trailing slash - # get redirected to the same URI path WITHOUT a trailing slash. - # Make sure GET params are preserved. - self.getPage('/redirect/by_code/?code=307') - self.assertStatus(301) - self.assertMatchesBody( - "" - '%s/redirect/by_code[?]code=307' - % (self.base(), self.base()) - ) - - # If the trailing_slash tool is off, CP should just continue - # as if the slashes were correct. But it needs some help - # inside cherrypy.url to form correct output. - self.getPage('/url?path_info=page1') - self.assertBody('%s/url/page1' % self.base()) - self.getPage('/url/leaf/?path_info=page1') - self.assertBody('%s/url/page1' % self.base()) - - def testRedirect(self): - self.getPage('/redirect/') - self.assertBody('child') - self.assertStatus(200) - - self.getPage('/redirect/by_code?code=300') - self.assertMatchesBody( - r"\2somewhere%20else") - self.assertStatus(300) - - self.getPage('/redirect/by_code?code=301') - self.assertMatchesBody( - r"\2somewhere%20else") - self.assertStatus(301) - - self.getPage('/redirect/by_code?code=302') - self.assertMatchesBody( - r"\2somewhere%20else") - self.assertStatus(302) - - self.getPage('/redirect/by_code?code=303') - self.assertMatchesBody( - r"\2somewhere%20else") - self.assertStatus(303) - - self.getPage('/redirect/by_code?code=307') - self.assertMatchesBody( - r"\2somewhere%20else") - self.assertStatus(307) - - self.getPage('/redirect/by_code?code=308') - self.assertMatchesBody( - r"\2somewhere%20else") - self.assertStatus(308) - - self.getPage('/redirect/nomodify') - self.assertBody('') - self.assertStatus(304) - - self.getPage('/redirect/proxy') - self.assertBody('') - self.assertStatus(305) - - # HTTPRedirect on error - self.getPage('/redirect/error/') - self.assertStatus(('302 Found', '303 See Other')) - self.assertInBody('/errpage') - - # Make sure str(HTTPRedirect()) works. - self.getPage('/redirect/stringify', protocol='HTTP/1.0') - self.assertStatus(200) - self.assertBody("(['%s/'], 302)" % self.base()) - if cherrypy.server.protocol_version == 'HTTP/1.1': - self.getPage('/redirect/stringify', protocol='HTTP/1.1') - self.assertStatus(200) - self.assertBody("(['%s/'], 303)" % self.base()) - - # check that #fragments are handled properly - # http://skrb.org/ietf/http_errata.html#location-fragments - frag = 'foo' - self.getPage('/redirect/fragment/%s' % frag) - self.assertMatchesBody( - r"\2\/some\/url\#%s" % ( - frag, frag)) - loc = self.assertHeader('Location') - assert loc.endswith('#%s' % frag) - self.assertStatus(('302 Found', '303 See Other')) - - # check injection protection - # See https://github.com/cherrypy/cherrypy/issues/1003 - self.getPage( - '/redirect/custom?' - 'code=303&url=/foobar/%0d%0aSet-Cookie:%20somecookie=someval') - self.assertStatus(303) - loc = self.assertHeader('Location') - assert 'Set-Cookie' in loc - self.assertNoHeader('Set-Cookie') - - def assertValidXHTML(): - from xml.etree import ElementTree - try: - ElementTree.fromstring( - '%s' % self.body, - ) - except ElementTree.ParseError: - self._handlewebError( - 'automatically generated redirect did not ' - 'generate well-formed html', - ) - - # check redirects to URLs generated valid HTML - we check this - # by seeing if it appears as valid XHTML. - self.getPage('/redirect/by_code?code=303') - self.assertStatus(303) - assertValidXHTML() - - # do the same with a url containing quote characters. - self.getPage('/redirect/url_with_quote') - self.assertStatus(303) - assertValidXHTML() - - def test_redirect_with_xss(self): - """A redirect to a URL with HTML injected should result - in page contents escaped.""" - self.getPage('/redirect/url_with_xss') - self.assertStatus(303) - assert b'\n' - - mathjax_url = 'file:/usr/share/javascript/mathjax/MathJax.js' - """ - URL of the MathJax javascript library. - - The MathJax library ought to be installed on the same - server as the rest of the deployed site files and specified - in the `math-output` setting appended to "mathjax". - See `Docutils Configuration`__. - - __ https://docutils.sourceforge.io/docs/user/config.html#math-output - - The fallback tries a local MathJax installation at - ``/usr/share/javascript/mathjax/MathJax.js``. - """ - - stylesheet_link = '\n' - embedded_stylesheet = '\n' - words_and_spaces = re.compile(r'[^ \n]+| +|\n') - # wrap point inside word: - in_word_wrap_point = re.compile(r'.+\W\W.+|[-?].+') - lang_attribute = 'lang' # name changes to 'xml:lang' in XHTML 1.1 - - special_characters = {ord('&'): '&', - ord('<'): '<', - ord('"'): '"', - ord('>'): '>', - ord('@'): '@', # may thwart address harvesters - } - """Character references for characters with a special meaning in HTML.""" - - def __init__(self, document): - nodes.NodeVisitor.__init__(self, document) - self.settings = settings = document.settings - lcode = settings.language_code - self.language = languages.get_language(lcode, document.reporter) - self.meta = [self.generator % docutils.__version__] - self.head_prefix = [] - self.html_prolog = [] - if settings.xml_declaration: - self.head_prefix.append(self.xml_declaration - % settings.output_encoding) - # self.content_type = "" - # encoding not interpolated: - self.html_prolog.append(self.xml_declaration) - self.head = self.meta[:] - self.stylesheet = [self.stylesheet_call(path) - for path in utils.get_stylesheet_list(settings)] - self.body_prefix = ['\n\n'] - # document title, subtitle display - self.body_pre_docinfo = [] - # author, date, etc. - self.docinfo = [] - self.body = [] - self.fragment = [] - self.body_suffix = ['\n\n'] - self.section_level = 0 - self.initial_header_level = int(settings.initial_header_level) - # image_loading only defined for HTML5 writer - self.image_loading = getattr(settings, 'image_loading', None) - # legacy setting embed_images: - if getattr(settings, 'embed_images', None) is True: - warnings.warn('The configuration setting "embed_images" ' - 'will be removed in Docutils 2.0. ' - 'Use "image_loading: embed".', - FutureWarning, stacklevel=8) - if self.image_loading is None: - self.image_loading = 'embed' - if getattr(settings, 'embed_images', None) is False: - warnings.warn('The configuration setting "embed_images" ' - 'will be removed in Docutils 2.0. ' - 'Use "image_loading: link".', - FutureWarning, stacklevel=8) - if self.image_loading is None: - self.image_loading = 'link' # default - self.math_output = settings.math_output.split() - self.math_output_options = self.math_output[1:] - self.math_output = self.math_output[0].lower() - - self.context = [] - """Heterogeneous stack. - - Used by visit_* and depart_* functions in conjunction with the tree - traversal. Make sure that the pops correspond to the pushes.""" - - self.colspecs = [] - self.compact_p = True - self.compact_simple = False - self.compact_field_list = False - self.in_docinfo = False - self.in_sidebar = False - self.title = [] - self.subtitle = [] - self.header = [] - self.footer = [] - self.html_head = [self.content_type] # charset not interpolated - self.html_title = [] - self.html_subtitle = [] - self.html_body = [] - self.in_document_title = 0 # len(self.body) or 0 - self.in_mailto = False - self.author_in_authors = False # for html4css1 - self.math_header = [] - - def astext(self): - return ''.join(self.head_prefix + self.head - + self.stylesheet + self.body_prefix - + self.body_pre_docinfo + self.docinfo - + self.body + self.body_suffix) - - def encode(self, text): - """Encode special characters in `text` & return.""" - # Use only named entities known in both XML and HTML - # other characters are automatically encoded "by number" if required. - # @@@ A codec to do these and all other HTML entities would be nice. - text = str(text) - return text.translate(self.special_characters) - - def cloak_mailto(self, uri): - """Try to hide a mailto: URL from harvesters.""" - # Encode "@" using a URL octet reference (see RFC 1738). - # Further cloaking with HTML entities will be done in the - # `attval` function. - return uri.replace('@', '%40') - - def cloak_email(self, addr): - """Try to hide the link text of a email link from harversters.""" - # Surround at-signs and periods with tags. ("@" has - # already been encoded to "@" by the `encode` method.) - addr = addr.replace('@', '@') - return addr.replace('.', '.') - - def attval(self, text, - whitespace=re.compile('[\n\r\t\v\f]')): - """Cleanse, HTML encode, and return attribute value text.""" - encoded = self.encode(whitespace.sub(' ', text)) - if self.in_mailto and self.settings.cloak_email_addresses: - # Cloak at-signs ("%40") and periods with HTML entities. - encoded = encoded.replace('%40', '%40') - encoded = encoded.replace('.', '.') - return encoded - - def stylesheet_call(self, path, adjust_path=None): - """Return code to reference or embed stylesheet file `path`""" - if adjust_path is None: - adjust_path = bool(self.settings.stylesheet_path) - if self.settings.embed_stylesheet: - try: - content = docutils.io.FileInput(source_path=path, - encoding='utf-8').read() - except OSError as err: - msg = f'Cannot embed stylesheet: {err}' - self.document.reporter.error(msg) - return '<--- %s --->\n' % msg - else: - self.settings.record_dependencies.add(path) - return self.embedded_stylesheet % content - # else link to style file: - if adjust_path: - # rewrite path relative to output (cf. config.html#stylesheet-path) - path = utils.relative_path(self.settings._destination, path) - return self.stylesheet_link % self.encode(path) - - def starttag(self, node, tagname, suffix='\n', empty=False, **attributes): - """ - Construct and return a start tag given a node (id & class attributes - are extracted), tag name, and optional attributes. - """ - tagname = tagname.lower() - prefix = [] - atts = {} - for (name, value) in attributes.items(): - atts[name.lower()] = value - classes = atts.pop('classes', []) - languages = [] - # unify class arguments and move language specification - for cls in node.get('classes', []) + atts.pop('class', '').split(): - if cls.startswith('language-'): - languages.append(cls[9:]) - elif cls.strip() and cls not in classes: - classes.append(cls) - if languages: - # attribute name is 'lang' in XHTML 1.0 but 'xml:lang' in 1.1 - atts[self.lang_attribute] = languages[0] - # filter classes that are processed by the writer: - internal = ('colwidths-auto', 'colwidths-given', 'colwidths-grid') - if isinstance(node, nodes.table): - classes = [cls for cls in classes if cls not in internal] - if classes: - atts['class'] = ' '.join(classes) - assert 'id' not in atts - ids = node.get('ids', []) - ids.extend(atts.pop('ids', [])) - if ids: - atts['id'] = ids[0] - for id in ids[1:]: - # Add empty "span" elements for additional IDs. Note - # that we cannot use empty "a" elements because there - # may be targets inside of references, but nested "a" - # elements aren't allowed in XHTML (even if they do - # not all have a "href" attribute). - if empty or isinstance(node, (nodes.Sequential, - nodes.docinfo, - nodes.table)): - # Insert target right in front of element. - prefix.append('' % id) - else: - # Non-empty tag. Place the auxiliary tag - # *inside* the element, as the first child. - suffix += '' % id - attlist = sorted(atts.items()) - parts = [tagname] - for name, value in attlist: - # value=None was used for boolean attributes without - # value, but this isn't supported by XHTML. - assert value is not None - if isinstance(value, list): - values = [str(v) for v in value] - parts.append('%s="%s"' % (name.lower(), - self.attval(' '.join(values)))) - else: - parts.append('%s="%s"' % (name.lower(), - self.attval(str(value)))) - if empty: - infix = ' /' - else: - infix = '' - return ''.join(prefix) + '<%s%s>' % (' '.join(parts), infix) + suffix - - def emptytag(self, node, tagname, suffix='\n', **attributes): - """Construct and return an XML-compatible empty tag.""" - return self.starttag(node, tagname, suffix, empty=True, **attributes) - - def set_class_on_child(self, node, class_, index=0): - """ - Set class `class_` on the visible child no. index of `node`. - Do nothing if node has fewer children than `index`. - """ - children = [n for n in node if not isinstance(n, nodes.Invisible)] - try: - child = children[index] - except IndexError: - return - child['classes'].append(class_) - - def visit_Text(self, node): - text = node.astext() - encoded = self.encode(text) - if self.in_mailto and self.settings.cloak_email_addresses: - encoded = self.cloak_email(encoded) - self.body.append(encoded) - - def depart_Text(self, node): - pass - - def visit_abbreviation(self, node): - # @@@ implementation incomplete ("title" attribute) - self.body.append(self.starttag(node, 'abbr', '')) - - def depart_abbreviation(self, node): - self.body.append('') - - def visit_acronym(self, node): - # @@@ implementation incomplete ("title" attribute) - self.body.append(self.starttag(node, 'acronym', '')) - - def depart_acronym(self, node): - self.body.append('') - - def visit_address(self, node): - self.visit_docinfo_item(node, 'address', meta=False) - self.body.append(self.starttag(node, 'pre', - suffix='', CLASS='address')) - - def depart_address(self, node): - self.body.append('\n\n') - self.depart_docinfo_item() - - def visit_admonition(self, node): - self.body.append(self.starttag(node, 'aside', classes=['admonition'])) - - def depart_admonition(self, node=None): - self.body.append('\n') - - attribution_formats = {'dash': ('\u2014', ''), - 'parentheses': ('(', ')'), - 'parens': ('(', ')'), - 'none': ('', '')} - - def visit_attribution(self, node): - prefix, suffix = self.attribution_formats[self.settings.attribution] - self.context.append(suffix) - self.body.append( - self.starttag(node, 'p', prefix, CLASS='attribution')) - - def depart_attribution(self, node): - self.body.append(self.context.pop() + '

\n') - - def visit_author(self, node): - if not(isinstance(node.parent, nodes.authors)): - self.visit_docinfo_item(node, 'author') - self.body.append('

') - - def depart_author(self, node): - self.body.append('

') - if isinstance(node.parent, nodes.authors): - self.body.append('\n') - else: - self.depart_docinfo_item() - - def visit_authors(self, node): - self.visit_docinfo_item(node, 'authors') - - def depart_authors(self, node): - self.depart_docinfo_item() - - def visit_block_quote(self, node): - self.body.append(self.starttag(node, 'blockquote')) - - def depart_block_quote(self, node): - self.body.append('\n') - - def check_simple_list(self, node): - """Check for a simple list that can be rendered compactly.""" - visitor = SimpleListChecker(self.document) - try: - node.walk(visitor) - except nodes.NodeFound: - return False - else: - return True - - # Compact lists - # ------------ - # Include definition lists and field lists (in addition to ordered - # and unordered lists) in the test if a list is "simple" (cf. the - # html4css1.HTMLTranslator docstring and the SimpleListChecker class at - # the end of this file). - - def is_compactable(self, node): - # explicit class arguments have precedence - if 'compact' in node['classes']: - return True - if 'open' in node['classes']: - return False - # check config setting: - if (isinstance(node, (nodes.field_list, nodes.definition_list)) - and not self.settings.compact_field_lists): - return False - if (isinstance(node, (nodes.enumerated_list, nodes.bullet_list)) - and not self.settings.compact_lists): - return False - # Table of Contents: - if 'contents' in node.parent['classes']: - return True - # check the list items: - return self.check_simple_list(node) - - def visit_bullet_list(self, node): - atts = {} - old_compact_simple = self.compact_simple - self.context.append((self.compact_simple, self.compact_p)) - self.compact_p = None - self.compact_simple = self.is_compactable(node) - if self.compact_simple and not old_compact_simple: - atts['class'] = 'simple' - self.body.append(self.starttag(node, 'ul', **atts)) - - def depart_bullet_list(self, node): - self.compact_simple, self.compact_p = self.context.pop() - self.body.append('\n') - - def visit_caption(self, node): - self.body.append(self.starttag(node, 'p', '', CLASS='caption')) - - def depart_caption(self, node): - self.body.append('

\n') - - # Use semantic tag and DPub role (HTML4 uses a table) - def visit_citation(self, node): - # role 'doc-bibloentry' requires wrapping in an element with - # role 'list' and an element with role 'doc-bibliography' - # https://www.w3.org/TR/dpub-aria-1.0/#doc-biblioentry) - if not isinstance(node.previous_sibling(), type(node)): - self.body.append('
\n') - self.body.append(self.starttag(node, 'div', classes=[node.tagname], - role="doc-biblioentry")) - - def depart_citation(self, node): - self.body.append('
\n') - if not isinstance(node.next_node(descend=False, siblings=True), - type(node)): - self.body.append('\n') - - # Use DPub role (overwritten in HTML4) - def visit_citation_reference(self, node): - href = '#' - if 'refid' in node: - href += node['refid'] - elif 'refname' in node: - href += self.document.nameids[node['refname']] - # else: # TODO system message (or already in the transform)? - # 'Citation reference missing.' - self.body.append(self.starttag(node, 'a', suffix='[', href=href, - classes=['citation-reference'], - role='doc-biblioref')) - - def depart_citation_reference(self, node): - self.body.append(']') - - # classifier - # ---------- - # don't insert classifier-delimiter here (done by CSS) - - def visit_classifier(self, node): - self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) - - def depart_classifier(self, node): - self.body.append('
') - - def visit_colspec(self, node): - self.colspecs.append(node) - # "stubs" list is an attribute of the tgroup element: - node.parent.stubs.append(node.attributes.get('stub')) - - def depart_colspec(self, node): - # write out when all colspecs are processed - if isinstance(node.next_node(descend=False, siblings=True), - nodes.colspec): - return - if 'colwidths-auto' in node.parent.parent['classes'] or ( - 'colwidths-grid' not in self.settings.table_style - and 'colwidths-given' not in node.parent.parent['classes']): - return - self.body.append(self.starttag(node, 'colgroup')) - total_width = sum(node['colwidth'] for node in self.colspecs) - for node in self.colspecs: - colwidth = node['colwidth'] / total_width - self.body.append(self.emptytag(node, 'col', - style=f'width: {colwidth:.1%}')) - self.body.append('\n') - - def visit_comment(self, node, - sub=re.compile('-(?=-)').sub): - """Escape double-dashes in comment text.""" - self.body.append('\n' % sub('- ', node.astext())) - # Content already processed: - raise nodes.SkipNode - - def visit_compound(self, node): - self.body.append(self.starttag(node, 'div', CLASS='compound')) - - def depart_compound(self, node): - self.body.append('\n') - - def visit_container(self, node): - self.body.append(self.starttag(node, 'div', - CLASS='docutils container')) - - def depart_container(self, node): - self.body.append('\n') - - def visit_contact(self, node): - self.visit_docinfo_item(node, 'contact', meta=False) - - def depart_contact(self, node): - self.depart_docinfo_item() - - def visit_copyright(self, node): - self.visit_docinfo_item(node, 'copyright') - - def depart_copyright(self, node): - self.depart_docinfo_item() - - def visit_date(self, node): - self.visit_docinfo_item(node, 'date') - - def depart_date(self, node): - self.depart_docinfo_item() - - def visit_decoration(self, node): - pass - - def depart_decoration(self, node): - pass - - def visit_definition(self, node): - if "details" in node.parent.parent['classes']: - self.body.append('\n') - else: - self.body.append('\n') - self.body.append(self.starttag(node, 'dd', '')) - - def depart_definition(self, node): - if "details" not in node.parent.parent['classes']: - self.body.append('\n') - - def visit_definition_list(self, node): - if "details" not in node['classes']: - classes = ['simple'] if self.is_compactable(node) else [] - self.body.append(self.starttag(node, 'dl', classes=classes)) - - def depart_definition_list(self, node): - if "details" not in node['classes']: - self.body.append('\n') - - # Use a "details" disclosure element if parent has "class" arg "details". - def visit_definition_list_item(self, node): - if "details" in node.parent['classes']: - atts = {} - if "open" in node.parent['classes']: - atts['open'] = 'open' - self.body.append(self.starttag(node, 'details', **atts)) - - def depart_definition_list_item(self, node): - if "details" in node.parent['classes']: - self.body.append('\n') - - def visit_description(self, node): - self.body.append(self.starttag(node, 'dd', '')) - - def depart_description(self, node): - self.body.append('\n') - - def visit_docinfo(self, node): - self.context.append(len(self.body)) - classes = ['docinfo'] - if self.is_compactable(node): - classes.append('simple') - self.body.append(self.starttag(node, 'dl', classes=classes)) - - def depart_docinfo(self, node): - self.body.append('\n') - start = self.context.pop() - self.docinfo = self.body[start:] - self.body = [] - - def visit_docinfo_item(self, node, name, meta=True): - if meta: - meta_tag = '\n' \ - % (name, self.attval(node.astext())) - self.add_meta(meta_tag) - self.body.append( - '
%s:
\n' - % (name, self.language.labels[name])) - self.body.append(self.starttag(node, 'dd', '', CLASS=name)) - - def depart_docinfo_item(self): - self.body.append('\n') - - def visit_doctest_block(self, node): - self.body.append(self.starttag(node, 'pre', suffix='', - classes=['code', 'python', 'doctest'])) - - def depart_doctest_block(self, node): - self.body.append('\n\n') - - def visit_document(self, node): - title = (node.get('title', '') or os.path.basename(node['source']) - or 'docutils document without title') - self.head.append('%s\n' % self.encode(title)) - - def depart_document(self, node): - self.head_prefix.extend([self.doctype, - self.head_prefix_template % - {'lang': self.settings.language_code}]) - self.html_prolog.append(self.doctype) - self.meta.insert(0, self.content_type % self.settings.output_encoding) - self.head.insert(0, self.content_type % self.settings.output_encoding) - if 'name="dcterms.' in ''.join(self.meta): - self.head.append('') - if self.math_header: - if self.math_output == 'mathjax': - self.head.extend(self.math_header) - else: - self.stylesheet.extend(self.math_header) - # skip content-type meta tag with interpolated charset value: - self.html_head.extend(self.head[1:]) - self.body_prefix.append(self.starttag(node, 'div', CLASS='document')) - self.body_suffix.insert(0, '\n') - self.fragment.extend(self.body) # self.fragment is the "naked" body - self.html_body.extend(self.body_prefix[1:] + self.body_pre_docinfo - + self.docinfo + self.body - + self.body_suffix[:-1]) - assert not self.context, 'len(context) = %s' % len(self.context) - - def visit_emphasis(self, node): - self.body.append(self.starttag(node, 'em', '')) - - def depart_emphasis(self, node): - self.body.append('') - - def visit_entry(self, node): - atts = {'classes': []} - if isinstance(node.parent.parent, nodes.thead): - atts['classes'].append('head') - if node.parent.parent.parent.stubs[node.parent.column]: - # "stubs" list is an attribute of the tgroup element - atts['classes'].append('stub') - if atts['classes']: - tagname = 'th' - else: - tagname = 'td' - node.parent.column += 1 - if 'morerows' in node: - atts['rowspan'] = node['morerows'] + 1 - if 'morecols' in node: - atts['colspan'] = node['morecols'] + 1 - node.parent.column += node['morecols'] - self.body.append(self.starttag(node, tagname, '', **atts)) - self.context.append('\n' % tagname.lower()) - - def depart_entry(self, node): - self.body.append(self.context.pop()) - - def visit_enumerated_list(self, node): - atts = {'classes': []} - if 'start' in node: - atts['start'] = node['start'] - if 'enumtype' in node: - atts['classes'].append(node['enumtype']) - if self.is_compactable(node): - atts['classes'].append('simple') - self.body.append(self.starttag(node, 'ol', **atts)) - - def depart_enumerated_list(self, node): - self.body.append('\n') - - def visit_field_list(self, node): - atts = {} - classes = node.setdefault('classes', []) - for i, cls in enumerate(classes): - if cls.startswith('field-indent-'): - try: - indent_length = length_or_percentage_or_unitless( - cls[13:], 'px') - except ValueError: - break - atts['style'] = '--field-indent: %s;' % indent_length - classes.pop(i) - break - classes.append('field-list') - if self.is_compactable(node): - classes.append('simple') - self.body.append(self.starttag(node, 'dl', **atts)) - - def depart_field_list(self, node): - self.body.append('\n') - - def visit_field(self, node): - pass - - def depart_field(self, node): - pass - - # as field is ignored, pass class arguments to field-name and field-body: - def visit_field_name(self, node): - self.body.append(self.starttag(node, 'dt', '', - classes=node.parent['classes'])) - - def depart_field_name(self, node): - self.body.append(':\n') - - def visit_field_body(self, node): - self.body.append(self.starttag(node, 'dd', '', - classes=node.parent['classes'])) - # prevent misalignment of following content if the field is empty: - if not node.children: - self.body.append('

') - - def depart_field_body(self, node): - self.body.append('\n') - - def visit_figure(self, node): - atts = {'class': 'figure'} - if node.get('width'): - atts['style'] = 'width: %s' % node['width'] - if node.get('align'): - atts['class'] += " align-" + node['align'] - self.body.append(self.starttag(node, 'div', **atts)) - - def depart_figure(self, node): - self.body.append('\n') - - def visit_footer(self, node): - self.context.append(len(self.body)) - - def depart_footer(self, node): - start = self.context.pop() - footer = [self.starttag(node, 'div', CLASS='footer'), - '\n'] - footer.extend(self.body[start:]) - footer.append('\n\n') - self.footer.extend(footer) - self.body_suffix[:0] = footer - del self.body[start:] - - def visit_footnote(self, node): - # No native HTML element: use \n') - - def visit_footnote_reference(self, node): - href = '#' + node['refid'] - classes = ['footnote-reference', self.settings.footnote_references] - self.body.append(self.starttag(node, 'a', suffix='', classes=classes, - role='doc-noteref', href=href)) - self.body.append('[') - - def depart_footnote_reference(self, node): - self.body.append(']') - self.body.append('') - - # Docutils-generated text: put section numbers in a span for CSS styling: - def visit_generated(self, node): - if 'sectnum' in node['classes']: - # get section number (strip trailing no-break-spaces) - sectnum = node.astext().rstrip(' ') - self.body.append('%s ' - % self.encode(sectnum)) - # Content already processed: - raise nodes.SkipNode - - def depart_generated(self, node): - pass - - def visit_header(self, node): - self.context.append(len(self.body)) - - def depart_header(self, node): - start = self.context.pop() - header = [self.starttag(node, 'div', CLASS='header')] - header.extend(self.body[start:]) - header.append('\n
\n\n') - self.body_prefix.extend(header) - self.header.extend(header) - del self.body[start:] - - def visit_image(self, node): - atts = {} - uri = node['uri'] - mimetype = mimetypes.guess_type(uri)[0] - # image size - if 'width' in node: - atts['width'] = node['width'] - if 'height' in node: - atts['height'] = node['height'] - if 'scale' in node: - if (PIL and ('width' not in node or 'height' not in node) - and self.settings.file_insertion_enabled): - imagepath = url2pathname(uri) - try: - with PIL.Image.open(imagepath) as img: - imgsize = img.size - except (OSError, UnicodeEncodeError): - pass # TODO: warn? - else: - self.settings.record_dependencies.add( - imagepath.replace('\\', '/')) - if 'width' not in atts: - atts['width'] = '%dpx' % imgsize[0] - if 'height' not in atts: - atts['height'] = '%dpx' % imgsize[1] - del img - for att_name in 'width', 'height': - if att_name in atts: - match = re.match(r'([0-9.]+)(\S*)$', atts[att_name]) - assert match - atts[att_name] = '%s%s' % ( - float(match.group(1)) * (float(node['scale']) / 100), - match.group(2)) - style = [] - for att_name in 'width', 'height': - if att_name in atts: - if re.match(r'^[0-9.]+$', atts[att_name]): - # Interpret unitless values as pixels. - atts[att_name] += 'px' - style.append('%s: %s;' % (att_name, atts[att_name])) - del atts[att_name] - if style: - atts['style'] = ' '.join(style) - if (isinstance(node.parent, nodes.TextElement) - or (isinstance(node.parent, nodes.reference) - and not isinstance(node.parent.parent, nodes.TextElement))): - # Inline context or surrounded by .... - suffix = '' - else: - suffix = '\n' - if 'align' in node: - atts['class'] = 'align-%s' % node['align'] - # Embed image file (embedded SVG or data URI): - if self.image_loading == 'embed': - try: - with open(url2pathname(uri), 'rb') as imagefile: - imagedata = imagefile.read() - except OSError as err: - self.document.reporter.error('Cannot embed image %r: %s' - % (uri, err.strerror)) - else: - self.settings.record_dependencies.add( - uri.replace('\\', '/')) - # TODO: insert SVG as-is? - # if mimetype == 'image/svg+xml': - # read/parse, apply arguments, - # insert as ... # (about 1/3 less data) - data64 = base64.b64encode(imagedata).decode() - uri = 'data:%s;base64,%s' % (mimetype, data64) - elif self.image_loading == 'lazy': - atts['loading'] = 'lazy' - if mimetype == 'application/x-shockwave-flash': - atts['type'] = mimetype - # do NOT use an empty tag: incorrect rendering in browsers - tag = (self.starttag(node, 'object', '', data=uri, **atts) - + node.get('alt', uri) + '
' + suffix) - else: - atts['alt'] = node.get('alt', node['uri']) - tag = self.emptytag(node, 'img', suffix, src=uri, **atts) - self.body.append(tag) - - def depart_image(self, node): - pass - - def visit_inline(self, node): - self.body.append(self.starttag(node, 'span', '')) - - def depart_inline(self, node): - self.body.append('') - - # footnote and citation labels: - def visit_label(self, node): - self.body.append('') - self.body.append('[') - # footnote/citation backrefs: - if self.settings.footnote_backlinks: - backrefs = node.parent.get('backrefs', []) - if len(backrefs) == 1: - self.body.append('' % backrefs[0]) - - def depart_label(self, node): - backrefs = [] - if self.settings.footnote_backlinks: - backrefs = node.parent.get('backrefs', backrefs) - if len(backrefs) == 1: - self.body.append('') - self.body.append(']\n') - if len(backrefs) > 1: - backlinks = ['%s' % (ref, i) - for (i, ref) in enumerate(backrefs, 1)] - self.body.append('(%s)\n' - % ','.join(backlinks)) - - def visit_legend(self, node): - self.body.append(self.starttag(node, 'div', CLASS='legend')) - - def depart_legend(self, node): - self.body.append('\n') - - def visit_line(self, node): - self.body.append(self.starttag(node, 'div', suffix='', CLASS='line')) - if not len(node): - self.body.append('
') - - def depart_line(self, node): - self.body.append('\n') - - def visit_line_block(self, node): - self.body.append(self.starttag(node, 'div', CLASS='line-block')) - - def depart_line_block(self, node): - self.body.append('\n') - - def visit_list_item(self, node): - self.body.append(self.starttag(node, 'li', '')) - - def depart_list_item(self, node): - self.body.append('\n') - - # inline literal - def visit_literal(self, node): - # special case: "code" role - classes = node['classes'] - if 'code' in classes: - # filter 'code' from class arguments - classes.pop(classes.index('code')) - self.body.append(self.starttag(node, 'code', '')) - return - self.body.append( - self.starttag(node, 'span', '', CLASS='docutils literal')) - text = node.astext() - if not isinstance(node.parent, nodes.literal_block): - text = text.replace('\n', ' ') - # Protect text like ``--an-option`` and the regular expression - # ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping - for token in self.words_and_spaces.findall(text): - if token.strip() and self.in_word_wrap_point.search(token): - self.body.append('%s' - % self.encode(token)) - else: - self.body.append(self.encode(token)) - self.body.append('') - raise nodes.SkipNode # content already processed - - def depart_literal(self, node): - # skipped unless literal element is from "code" role: - self.body.append('') - - def visit_literal_block(self, node): - self.body.append(self.starttag(node, 'pre', '', CLASS='literal-block')) - if 'code' in node['classes']: - self.body.append('') - - def depart_literal_block(self, node): - if 'code' in node['classes']: - self.body.append('') - self.body.append('\n') - - # Mathematics: - # As there is no native HTML math support, we provide alternatives - # for the math-output: LaTeX and MathJax simply wrap the content, - # HTML and MathML also convert the math_code. - # HTML container - math_tags = { - # math_output: (block, inline, class-arguments) - 'html': ('div', 'span', 'formula'), - 'latex': ('pre', 'tt', 'math'), - 'mathml': ('div', '', ''), - 'mathjax': ('div', 'span', 'math'), - } - - def visit_math(self, node, math_env=''): - # If the method is called from visit_math_block(), math_env != ''. - - if self.math_output not in self.math_tags: - self.document.reporter.error( - f'math-output format "{self.math_output}" not supported ' - 'falling back to "latex"') - self.math_output = 'latex' - tag = self.math_tags[self.math_output][math_env == ''] - clsarg = self.math_tags[self.math_output][2] - # LaTeX container - wrappers = { - # math_mode: (inline, block) - 'html': ('$%s$', '\\begin{%s}\n%s\n\\end{%s}'), - 'latex': (None, None), - 'mathml': ('$%s$', '\\begin{%s}\n%s\n\\end{%s}'), - 'mathjax': (r'\(%s\)', '\\begin{%s}\n%s\n\\end{%s}'), - } - wrapper = wrappers[self.math_output][math_env != ''] - if (self.math_output == 'mathml' - and (not self.math_output_options - or self.math_output_options[0] == 'blahtexml')): - wrapper = None - # get and wrap content - math_code = node.astext().translate(unichar2tex.uni2tex_table) - if wrapper: - try: # wrapper with three "%s" - math_code = wrapper % (math_env, math_code, math_env) - except TypeError: # wrapper with one "%s" - math_code = wrapper % math_code - # settings and conversion - if self.math_output in ('latex', 'mathjax'): - math_code = self.encode(math_code) - if self.math_output == 'mathjax' and not self.math_header: - try: - self.mathjax_url = self.math_output_options[0] - except IndexError: - self.document.reporter.warning('No MathJax URL specified, ' - 'using local fallback ' - '(see config.html)') - # append configuration, if not already present in the URL: - # input LaTeX with AMS, output common HTML - if '?' not in self.mathjax_url: - self.mathjax_url += '?config=TeX-AMS_CHTML' - self.math_header = [self.mathjax_script % self.mathjax_url] - elif self.math_output == 'html': - if self.math_output_options and not self.math_header: - self.math_header = [self.stylesheet_call( - utils.find_file_in_dirs(s, self.settings.stylesheet_dirs), - adjust_path=True) - for s in self.math_output_options[0].split(',')] - # TODO: fix display mode in matrices and fractions - math2html.DocumentParameters.displaymode = (math_env != '') - math_code = math2html.math2html(math_code) - elif self.math_output == 'mathml': - if 'XHTML 1' in self.doctype: - self.doctype = self.doctype_mathml - self.content_type = self.content_type_mathml - converter = ' '.join(self.math_output_options).lower() - try: - if converter == 'latexml': - math_code = tex2mathml_extern.latexml( - math_code, self.document.reporter) - elif converter == 'ttm': - math_code = tex2mathml_extern.ttm( - math_code, self.document.reporter) - elif converter == 'blahtexml': - math_code = tex2mathml_extern.blahtexml( - math_code, - inline=not(math_env), - reporter=self.document.reporter) - elif not converter: - math_code = latex2mathml.tex2mathml( - math_code, inline=not(math_env)) - else: - self.document.reporter.error('option "%s" not supported ' - 'with math-output "MathML"') - except OSError: - raise OSError('is "latexmlmath" in your PATH?') - except SyntaxError as err: - err_node = self.document.reporter.error(err, base_node=node) - self.visit_system_message(err_node) - self.body.append(self.starttag(node, 'p')) - self.body.append(','.join(err.args)) - self.body.append('

\n') - self.body.append(self.starttag(node, 'pre', - CLASS='literal-block')) - self.body.append(self.encode(math_code)) - self.body.append('\n\n') - self.depart_system_message(err_node) - raise nodes.SkipNode - # append to document body - if tag: - self.body.append(self.starttag(node, tag, - suffix='\n'*bool(math_env), - CLASS=clsarg)) - self.body.append(math_code) - if math_env: # block mode (equation, display) - self.body.append('\n') - if tag: - self.body.append('' % tag) - if math_env: - self.body.append('\n') - # Content already processed: - raise nodes.SkipNode - - def depart_math(self, node): - pass # never reached - - def visit_math_block(self, node): - math_env = pick_math_environment(node.astext()) - self.visit_math(node, math_env=math_env) - - def depart_math_block(self, node): - pass # never reached - - # Meta tags: 'lang' attribute replaced by 'xml:lang' in XHTML 1.1 - # HTML5/polyglot recommends using both - def visit_meta(self, node): - meta = self.emptytag(node, 'meta', **node.non_default_attributes()) - self.add_meta(meta) - - def depart_meta(self, node): - pass - - def add_meta(self, tag): - self.meta.append(tag) - self.head.append(tag) - - def visit_option(self, node): - self.body.append(self.starttag(node, 'span', '', CLASS='option')) - - def depart_option(self, node): - self.body.append('') - if isinstance(node.next_node(descend=False, siblings=True), - nodes.option): - self.body.append(', ') - - def visit_option_argument(self, node): - self.body.append(node.get('delimiter', ' ')) - self.body.append(self.starttag(node, 'var', '')) - - def depart_option_argument(self, node): - self.body.append('') - - def visit_option_group(self, node): - self.body.append(self.starttag(node, 'dt', '')) - self.body.append('') - - def depart_option_group(self, node): - self.body.append('\n') - - def visit_option_list(self, node): - self.body.append( - self.starttag(node, 'dl', CLASS='option-list')) - - def depart_option_list(self, node): - self.body.append('\n') - - def visit_option_list_item(self, node): - pass - - def depart_option_list_item(self, node): - pass - - def visit_option_string(self, node): - pass - - def depart_option_string(self, node): - pass - - def visit_organization(self, node): - self.visit_docinfo_item(node, 'organization') - - def depart_organization(self, node): - self.depart_docinfo_item() - - # Do not omit

tags - # -------------------- - # - # The HTML4CSS1 writer does this to "produce - # visually compact lists (less vertical whitespace)". This writer - # relies on CSS rules for visual compactness. - # - # * In XHTML 1.1, e.g., a

element may not contain - # character data, so you cannot drop the

tags. - # * Keeping simple paragraphs in the field_body enables a CSS - # rule to start the field-body on a new line if the label is too long - # * it makes the code simpler. - # - # TODO: omit paragraph tags in simple table cells? - - def visit_paragraph(self, node): - self.body.append(self.starttag(node, 'p', '')) - - def depart_paragraph(self, node): - self.body.append('

') - if not (isinstance(node.parent, (nodes.list_item, nodes.entry)) - and (len(node.parent) == 1)): - self.body.append('\n') - - def visit_problematic(self, node): - if node.hasattr('refid'): - self.body.append('' % node['refid']) - self.context.append('') - else: - self.context.append('') - self.body.append(self.starttag(node, 'span', '', CLASS='problematic')) - - def depart_problematic(self, node): - self.body.append('') - self.body.append(self.context.pop()) - - def visit_raw(self, node): - if 'html' in node.get('format', '').split(): - if isinstance(node.parent, nodes.TextElement): - tagname = 'span' - else: - tagname = 'div' - if node['classes']: - self.body.append(self.starttag(node, tagname, suffix='')) - self.body.append(node.astext()) - if node['classes']: - self.body.append('' % tagname) - # Keep non-HTML raw text out of output: - raise nodes.SkipNode - - def visit_reference(self, node): - atts = {'class': 'reference'} - if 'refuri' in node: - atts['href'] = node['refuri'] - if (self.settings.cloak_email_addresses - and atts['href'].startswith('mailto:')): - atts['href'] = self.cloak_mailto(atts['href']) - self.in_mailto = True - atts['class'] += ' external' - else: - assert 'refid' in node, \ - 'References must have "refuri" or "refid" attribute.' - atts['href'] = '#' + node['refid'] - atts['class'] += ' internal' - if len(node) == 1 and isinstance(node[0], nodes.image): - atts['class'] += ' image-reference' - if not isinstance(node.parent, nodes.TextElement): - assert len(node) == 1 and isinstance(node[0], nodes.image) - atts['class'] += ' image-reference' - self.body.append(self.starttag(node, 'a', '', **atts)) - - def depart_reference(self, node): - self.body.append('') - if not isinstance(node.parent, nodes.TextElement): - self.body.append('\n') - self.in_mailto = False - - def visit_revision(self, node): - self.visit_docinfo_item(node, 'revision', meta=False) - - def depart_revision(self, node): - self.depart_docinfo_item() - - def visit_row(self, node): - self.body.append(self.starttag(node, 'tr', '')) - node.column = 0 - - def depart_row(self, node): - self.body.append('\n') - - def visit_rubric(self, node): - self.body.append(self.starttag(node, 'p', '', CLASS='rubric')) - - def depart_rubric(self, node): - self.body.append('

\n') - - def visit_section(self, node): - self.section_level += 1 - self.body.append( - self.starttag(node, 'div', CLASS='section')) - - def depart_section(self, node): - self.section_level -= 1 - self.body.append('\n') - - # TODO: use the new HTML5 element \n') - - def visit_table(self, node): - atts = {'classes': self.settings.table_style.replace(',', ' ').split()} - if 'align' in node: - atts['classes'].append('align-%s' % node['align']) - if 'width' in node: - atts['style'] = 'width: %s;' % node['width'] - tag = self.starttag(node, 'table', **atts) - self.body.append(tag) - - def depart_table(self, node): - self.body.append('\n') - - def visit_target(self, node): - if ('refuri' not in node - and 'refid' not in node - and 'refname' not in node): - self.body.append(self.starttag(node, 'span', '', CLASS='target')) - self.context.append('') - else: - self.context.append('') - - def depart_target(self, node): - self.body.append(self.context.pop()) - - # no hard-coded vertical alignment in table body - def visit_tbody(self, node): - self.body.append(self.starttag(node, 'tbody')) - - def depart_tbody(self, node): - self.body.append('\n') - - def visit_term(self, node): - if "details" in node.parent.parent['classes']: - self.body.append(self.starttag(node, 'summary', '')) - else: - # The parent node (definition_list_item) is omitted in HTML. - self.body.append(self.starttag(node, 'dt', '', - classes=node.parent['classes'], - ids=node.parent['ids'])) - - def depart_term(self, node): - # Leave the end tag to `self.visit_definition()`, - # in case there's a classifier. - pass - - def visit_tgroup(self, node): - self.colspecs = [] - node.stubs = [] - - def depart_tgroup(self, node): - pass - - def visit_thead(self, node): - self.body.append(self.starttag(node, 'thead')) - - def depart_thead(self, node): - self.body.append('\n') - - def section_title_tags(self, node): - atts = {} - h_level = self.section_level + self.initial_header_level - 1 - # Only 6 heading levels have dedicated HTML tags. - tagname = 'h%i' % min(h_level, 6) - if h_level > 6: - atts['aria-level'] = h_level - start_tag = self.starttag(node, tagname, '', **atts) - if node.hasattr('refid'): - atts = {} - atts['class'] = 'toc-backref' - atts['role'] = 'doc-backlink' # HTML5 only - atts['href'] = '#' + node['refid'] - start_tag += self.starttag(nodes.reference(), 'a', '', **atts) - close_tag = '\n' % tagname - else: - close_tag = '\n' % tagname - return start_tag, close_tag - - def visit_title(self, node): - close_tag = '

\n' - if isinstance(node.parent, nodes.topic): - # TODO: use role="heading" or

? (HTML5 only) - self.body.append( - self.starttag(node, 'p', '', CLASS='topic-title')) - elif isinstance(node.parent, nodes.sidebar): - # TODO: use role="heading" or

? (HTML5 only) - self.body.append( - self.starttag(node, 'p', '', CLASS='sidebar-title')) - elif isinstance(node.parent, nodes.Admonition): - self.body.append( - self.starttag(node, 'p', '', CLASS='admonition-title')) - elif isinstance(node.parent, nodes.table): - self.body.append( - self.starttag(node, 'caption', '')) - close_tag = '\n' - elif isinstance(node.parent, nodes.document): - self.body.append(self.starttag(node, 'h1', '', CLASS='title')) - close_tag = '

\n' - self.in_document_title = len(self.body) - else: - assert isinstance(node.parent, nodes.section) - # Get correct heading and evt. backlink tags - start_tag, close_tag = self.section_title_tags(node) - self.body.append(start_tag) - self.context.append(close_tag) - - def depart_title(self, node): - self.body.append(self.context.pop()) - if self.in_document_title: - self.title = self.body[self.in_document_title:-1] - self.in_document_title = 0 - self.body_pre_docinfo.extend(self.body) - self.html_title.extend(self.body) - del self.body[:] - - def visit_title_reference(self, node): - self.body.append(self.starttag(node, 'cite', '')) - - def depart_title_reference(self, node): - self.body.append('') - - def visit_topic(self, node): - self.body.append(self.starttag(node, 'div', CLASS='topic')) - - def depart_topic(self, node): - self.body.append('\n') - - def visit_transition(self, node): - self.body.append(self.emptytag(node, 'hr', CLASS='docutils')) - - def depart_transition(self, node): - pass - - def visit_version(self, node): - self.visit_docinfo_item(node, 'version', meta=False) - - def depart_version(self, node): - self.depart_docinfo_item() - - def unimplemented_visit(self, node): - raise NotImplementedError('visiting unimplemented node type: %s' - % node.__class__.__name__) - - -class SimpleListChecker(nodes.GenericNodeVisitor): - - """ - Raise `nodes.NodeFound` if non-simple list item is encountered. - - Here "simple" means a list item containing nothing other than a single - paragraph, a simple list, or a paragraph followed by a simple list. - - This version also checks for simple field lists and docinfo. - """ - - def default_visit(self, node): - raise nodes.NodeFound - - def visit_list_item(self, node): - children = [child for child in node.children - if not isinstance(child, nodes.Invisible)] - if (children and isinstance(children[0], nodes.paragraph) - and (isinstance(children[-1], nodes.bullet_list) - or isinstance(children[-1], nodes.enumerated_list) - or isinstance(children[-1], nodes.field_list))): - children.pop() - if len(children) <= 1: - return - else: - raise nodes.NodeFound - - def pass_node(self, node): - pass - - def ignore_node(self, node): - # ignore nodes that are never complex (can contain only inline nodes) - raise nodes.SkipNode - - # Paragraphs and text - visit_Text = ignore_node - visit_paragraph = ignore_node - - # Lists - visit_bullet_list = pass_node - visit_enumerated_list = pass_node - visit_docinfo = pass_node - - # Docinfo nodes: - visit_author = ignore_node - visit_authors = visit_list_item - visit_address = visit_list_item - visit_contact = pass_node - visit_copyright = ignore_node - visit_date = ignore_node - visit_organization = ignore_node - visit_status = ignore_node - visit_version = visit_list_item - - # Definition list: - visit_definition_list = pass_node - visit_definition_list_item = pass_node - visit_term = ignore_node - visit_classifier = pass_node - visit_definition = visit_list_item - - # Field list: - visit_field_list = pass_node - visit_field = pass_node - # the field body corresponds to a list item - visit_field_body = visit_list_item - visit_field_name = ignore_node - - # Invisible nodes should be ignored. - visit_comment = ignore_node - visit_substitution_definition = ignore_node - visit_target = ignore_node - visit_pending = ignore_node diff --git a/lib/docutils/writers/docutils_xml.py b/lib/docutils/writers/docutils_xml.py deleted file mode 100644 index 80e33312..00000000 --- a/lib/docutils/writers/docutils_xml.py +++ /dev/null @@ -1,188 +0,0 @@ -# $Id: docutils_xml.py 9038 2022-03-05 23:31:46Z milde $ -# Author: David Goodger, Paul Tremblay, Guenter Milde -# Maintainer: docutils-develop@lists.sourceforge.net -# Copyright: This module has been placed in the public domain. - -""" -Simple document tree Writer, writes Docutils XML according to -https://docutils.sourceforge.io/docs/ref/docutils.dtd. -""" - -__docformat__ = 'reStructuredText' - -from io import StringIO -import xml.sax.saxutils - -import docutils -from docutils import frontend, writers, nodes - - -class RawXmlError(docutils.ApplicationError): - pass - - -class Writer(writers.Writer): - - supported = ('xml',) - """Formats this writer supports.""" - - settings_spec = ( - '"Docutils XML" Writer Options', - None, - (('Generate XML with newlines before and after tags.', - ['--newlines'], - {'action': 'store_true', 'validator': frontend.validate_boolean}), - ('Generate XML with indents and newlines.', - ['--indents'], # TODO use integer value for number of spaces? - {'action': 'store_true', 'validator': frontend.validate_boolean}), - ('Omit the XML declaration. Use with caution.', - ['--no-xml-declaration'], - {'dest': 'xml_declaration', 'default': 1, 'action': 'store_false', - 'validator': frontend.validate_boolean}), - ('Omit the DOCTYPE declaration.', - ['--no-doctype'], - {'dest': 'doctype_declaration', 'default': 1, - 'action': 'store_false', 'validator': frontend.validate_boolean}),)) - - settings_defaults = {'output_encoding_error_handler': 'xmlcharrefreplace'} - - config_section = 'docutils_xml writer' - config_section_dependencies = ('writers',) - - output = None - """Final translated form of `document`.""" - - def __init__(self): - writers.Writer.__init__(self) - self.translator_class = XMLTranslator - - def translate(self): - self.visitor = visitor = self.translator_class(self.document) - self.document.walkabout(visitor) - self.output = ''.join(visitor.output) - - -class XMLTranslator(nodes.GenericNodeVisitor): - - xml_declaration = '\n' - # TODO: add stylesheet options similar to HTML and LaTeX writers? - # xml_stylesheet = '\n' - doctype = ( - '\n') - generator = '\n' - - xmlparser = xml.sax.make_parser() - """SAX parser instance to check/extract raw XML.""" - xmlparser.setFeature( - "http://xml.org/sax/features/external-general-entities", True) - - def __init__(self, document): - nodes.NodeVisitor.__init__(self, document) - - # Reporter - self.warn = self.document.reporter.warning - self.error = self.document.reporter.error - - # Settings - self.settings = settings = document.settings - self.indent = self.newline = '' - if settings.newlines: - self.newline = '\n' - if settings.indents: - self.newline = '\n' - self.indent = ' ' # TODO make this configurable? - self.level = 0 # indentation level - self.in_simple = 0 # level of nesting inside mixed-content elements - self.fixed_text = 0 # level of nesting inside FixedText elements - - # Output - self.output = [] - if settings.xml_declaration: - self.output.append( - self.xml_declaration % settings.output_encoding) - if settings.doctype_declaration: - self.output.append(self.doctype) - self.output.append(self.generator % docutils.__version__) - - # initialize XML parser - self.the_handle = TestXml() - self.xmlparser.setContentHandler(self.the_handle) - - # generic visit and depart methods - # -------------------------------- - - simple_nodes = (nodes.TextElement, - nodes.image, nodes.colspec, nodes.transition) - - def default_visit(self, node): - """Default node visit method.""" - if not self.in_simple: - self.output.append(self.indent*self.level) - self.output.append(node.starttag(xml.sax.saxutils.quoteattr)) - self.level += 1 - # @@ make nodes.literal an instance of FixedTextElement? - if isinstance(node, (nodes.FixedTextElement, nodes.literal)): - self.fixed_text += 1 - if isinstance(node, self.simple_nodes): - self.in_simple += 1 - if not self.in_simple: - self.output.append(self.newline) - - def default_departure(self, node): - """Default node depart method.""" - self.level -= 1 - if not self.in_simple: - self.output.append(self.indent*self.level) - self.output.append(node.endtag()) - if isinstance(node, (nodes.FixedTextElement, nodes.literal)): - self.fixed_text -= 1 - if isinstance(node, self.simple_nodes): - self.in_simple -= 1 - if not self.in_simple: - self.output.append(self.newline) - - # specific visit and depart methods - # --------------------------------- - - def visit_Text(self, node): - text = xml.sax.saxutils.escape(node.astext()) - # indent text if we are not in a FixedText element: - if not self.fixed_text: - text = text.replace('\n', '\n'+self.indent*self.level) - self.output.append(text) - - def depart_Text(self, node): - pass - - def visit_raw(self, node): - if 'xml' not in node.get('format', '').split(): - # skip other raw content? - # raise nodes.SkipNode - self.default_visit(node) - return - # wrap in element - self.default_visit(node) # or not? - xml_string = node.astext() - self.output.append(xml_string) - self.default_departure(node) # or not? - # Check validity of raw XML: - try: - self.xmlparser.parse(StringIO(xml_string)) - except xml.sax._exceptions.SAXParseException: - col_num = self.the_handle.locator.getColumnNumber() - line_num = self.the_handle.locator.getLineNumber() - srcline = node.line - if not isinstance(node.parent, nodes.TextElement): - srcline += 2 # directive content start line - msg = 'Invalid raw XML in column %d, line offset %d:\n%s' % ( - col_num, line_num, node.astext()) - self.warn(msg, source=node.source, line=srcline+line_num-1) - raise nodes.SkipNode # content already processed - - -class TestXml(xml.sax.handler.ContentHandler): - - def setDocumentLocator(self, locator): - self.locator = locator diff --git a/lib/docutils/writers/html4css1/__init__.py b/lib/docutils/writers/html4css1/__init__.py deleted file mode 100644 index 7baab10b..00000000 --- a/lib/docutils/writers/html4css1/__init__.py +++ /dev/null @@ -1,947 +0,0 @@ -# $Id: __init__.py 9037 2022-03-05 23:31:10Z milde $ -# Author: David Goodger -# Maintainer: docutils-develop@lists.sourceforge.net -# Copyright: This module has been placed in the public domain. - -""" -Simple HyperText Markup Language document tree Writer. - -The output conforms to the XHTML version 1.0 Transitional DTD -(*almost* strict). The output contains a minimum of formatting -information. The cascading style sheet "html4css1.css" is required -for proper viewing with a modern graphical browser. -""" - -__docformat__ = 'reStructuredText' - -import os.path -import re - -from docutils import frontend, nodes, writers -from docutils.writers import _html_base -from docutils.writers._html_base import PIL, url2pathname - - -class Writer(writers._html_base.Writer): - - supported = ('html', 'html4', 'html4css1', 'xhtml', 'xhtml10') - """Formats this writer supports.""" - - default_stylesheets = ['html4css1.css'] - default_stylesheet_dirs = ['.', - os.path.abspath(os.path.dirname(__file__)), - os.path.abspath(os.path.join( - os.path.dirname(os.path.dirname(__file__)), - 'html5_polyglot')) # for math.css - ] - default_template = os.path.join( - os.path.dirname(os.path.abspath(__file__)), 'template.txt') - - # use a copy of the parent spec with some modifications - settings_spec = frontend.filter_settings_spec( - writers._html_base.Writer.settings_spec, - template=( - 'Template file. (UTF-8 encoded, default: "%s")' % default_template, - ['--template'], - {'default': default_template, 'metavar': ''}), - stylesheet_path=( - 'Comma separated list of stylesheet paths. ' - 'Relative paths are expanded if a matching file is found in ' - 'the --stylesheet-dirs. With --link-stylesheet, ' - 'the path is rewritten relative to the output HTML file. ' - '(default: "%s")' % ','.join(default_stylesheets), - ['--stylesheet-path'], - {'metavar': '', 'overrides': 'stylesheet', - 'validator': frontend.validate_comma_separated_list, - 'default': default_stylesheets}), - stylesheet_dirs=( - 'Comma-separated list of directories where stylesheets are found. ' - 'Used by --stylesheet-path when expanding relative path ' - 'arguments. (default: "%s")' % ','.join(default_stylesheet_dirs), - ['--stylesheet-dirs'], - {'metavar': '', - 'validator': frontend.validate_comma_separated_list, - 'default': default_stylesheet_dirs}), - initial_header_level=( - 'Specify the initial header level. Does not affect document ' - 'title & subtitle (see --no-doc-title). (default: 1 for "

")', - ['--initial-header-level'], - {'choices': '1 2 3 4 5 6'.split(), 'default': '1', - 'metavar': ''}), - xml_declaration=( - 'Prepend an XML declaration (default). ', - ['--xml-declaration'], - {'default': True, 'action': 'store_true', - 'validator': frontend.validate_boolean}), - ) - settings_spec = settings_spec + ( - 'HTML4 Writer Options', - '', - (('Specify the maximum width (in characters) for one-column field ' - 'names. Longer field names will span an entire row of the table ' - 'used to render the field list. Default is 14 characters. ' - 'Use 0 for "no limit".', - ['--field-name-limit'], - {'default': 14, 'metavar': '', - 'validator': frontend.validate_nonnegative_int}), - ('Specify the maximum width (in characters) for options in option ' - 'lists. Longer options will span an entire row of the table used ' - 'to render the option list. Default is 14 characters. ' - 'Use 0 for "no limit".', - ['--option-limit'], - {'default': 14, 'metavar': '', - 'validator': frontend.validate_nonnegative_int}), - ) - ) - - config_section = 'html4css1 writer' - - def __init__(self): - self.parts = {} - self.translator_class = HTMLTranslator - - -class HTMLTranslator(writers._html_base.HTMLTranslator): - """ - The html4css1 writer has been optimized to produce visually compact - lists (less vertical whitespace). HTML's mixed content models - allow list items to contain "
  • body elements

  • " or - "
  • just text
  • " or even "
  • text

    and body - elements

    combined
  • ", each with different effects. It would - be best to stick with strict body elements in list items, but they - affect vertical spacing in older browsers (although they really - shouldn't). - The html5_polyglot writer solves this using CSS2. - - Here is an outline of the optimization: - - - Check for and omit

    tags in "simple" lists: list items - contain either a single paragraph, a nested simple list, or a - paragraph followed by a nested simple list. This means that - this list can be compact: - - - Item 1. - - Item 2. - - But this list cannot be compact: - - - Item 1. - - This second paragraph forces space between list items. - - - Item 2. - - - In non-list contexts, omit

    tags on a paragraph if that - paragraph is the only child of its parent (footnotes & citations - are allowed a label first). - - - Regardless of the above, in definitions, table cells, field bodies, - option descriptions, and list items, mark the first child with - 'class="first"' and the last child with 'class="last"'. The stylesheet - sets the margins (top & bottom respectively) to 0 for these elements. - - The ``no_compact_lists`` setting (``--no-compact-lists`` command-line - option) disables list whitespace optimization. - """ - - # The following definitions are required for display in browsers limited - # to CSS1 or backwards compatible behaviour of the writer: - - doctype = ( - '\n') - - content_type = ('\n') - content_type_mathml = ('\n') - - # encode also non-breaking space - special_characters = _html_base.HTMLTranslator.special_characters.copy() - special_characters[0xa0] = ' ' - - # use character reference for dash (not valid in HTML5) - attribution_formats = {'dash': ('—', ''), - 'parentheses': ('(', ')'), - 'parens': ('(', ')'), - 'none': ('', '')} - - # ersatz for first/last pseudo-classes missing in CSS1 - def set_first_last(self, node): - self.set_class_on_child(node, 'first', 0) - self.set_class_on_child(node, 'last', -1) - - # add newline after opening tag - def visit_address(self, node): - self.visit_docinfo_item(node, 'address', meta=False) - self.body.append(self.starttag(node, 'pre', CLASS='address')) - - def depart_address(self, node): - self.body.append('\n\n') - self.depart_docinfo_item() - - # ersatz for first/last pseudo-classes - def visit_admonition(self, node): - node['classes'].insert(0, 'admonition') - self.body.append(self.starttag(node, 'div')) - self.set_first_last(node) - - def depart_admonition(self, node=None): - self.body.append('\n') - - # author, authors: use
    instead of paragraphs - def visit_author(self, node): - if isinstance(node.parent, nodes.authors): - if self.author_in_authors: - self.body.append('\n
    ') - else: - self.visit_docinfo_item(node, 'author') - - def depart_author(self, node): - if isinstance(node.parent, nodes.authors): - self.author_in_authors = True - else: - self.depart_docinfo_item() - - def visit_authors(self, node): - self.visit_docinfo_item(node, 'authors') - self.author_in_authors = False # initialize - - def depart_authors(self, node): - self.depart_docinfo_item() - - # use "width" argument instead of "style: 'width'": - def visit_colspec(self, node): - self.colspecs.append(node) - # "stubs" list is an attribute of the tgroup element: - node.parent.stubs.append(node.attributes.get('stub')) - - def depart_colspec(self, node): - # write out when all colspecs are processed - if isinstance(node.next_node(descend=False, siblings=True), - nodes.colspec): - return - if ('colwidths-auto' in node.parent.parent['classes'] - or ('colwidths-auto' in self.settings.table_style - and 'colwidths-given' not in node.parent.parent['classes'])): - return - total_width = sum(node['colwidth'] for node in self.colspecs) - self.body.append(self.starttag(node, 'colgroup')) - for node in self.colspecs: - colwidth = int(node['colwidth'] * 100.0 / total_width + 0.5) - self.body.append(self.emptytag(node, 'col', - width='%i%%' % colwidth)) - self.body.append('\n') - - # Compact lists: - # exclude definition lists and field lists (non-compact by default) - - def is_compactable(self, node): - return ('compact' in node['classes'] - or (self.settings.compact_lists - and 'open' not in node['classes'] - and (self.compact_simple - or 'contents' in node.parent['classes'] - # TODO: self.in_contents - or self.check_simple_list(node)))) - - # citations: Use table for bibliographic references. - def visit_citation(self, node): - self.body.append(self.starttag(node, 'table', - CLASS='docutils citation', - frame="void", rules="none")) - self.body.append('\n' - '\n' - '') - self.footnote_backrefs(node) - - def depart_citation(self, node): - self.body.append('\n' - '\n\n') - - def visit_citation_reference(self, node): - href = '#' - if 'refid' in node: - href += node['refid'] - elif 'refname' in node: - href += self.document.nameids[node['refname']] - self.body.append(self.starttag(node, 'a', suffix='[', href=href, - classes=['citation-reference'])) - - def depart_citation_reference(self, node): - self.body.append(']') - - # insert classifier-delimiter (not required with CSS2) - def visit_classifier(self, node): - self.body.append(' : ') - self.body.append(self.starttag(node, 'span', '', CLASS='classifier')) - - def depart_classifier(self, node): - self.body.append('') - - # ersatz for first/last pseudo-classes - def visit_compound(self, node): - self.body.append(self.starttag(node, 'div', CLASS='compound')) - if len(node) > 1: - node[0]['classes'].append('compound-first') - node[-1]['classes'].append('compound-last') - for child in node[1:-1]: - child['classes'].append('compound-middle') - - def depart_compound(self, node): - self.body.append('\n') - - # ersatz for first/last pseudo-classes, no special handling of "details" - def visit_definition(self, node): - self.body.append('\n') - self.body.append(self.starttag(node, 'dd', '')) - self.set_first_last(node) - - def depart_definition(self, node): - self.body.append('\n') - - # don't add "simple" class value - def visit_definition_list(self, node): - self.body.append(self.starttag(node, 'dl', CLASS='docutils')) - - def depart_definition_list(self, node): - self.body.append('\n') - - # no special handling of "details" - def visit_definition_list_item(self, node): - pass - - def depart_definition_list_item(self, node): - pass - - # use a table for description lists - def visit_description(self, node): - self.body.append(self.starttag(node, 'td', '')) - self.set_first_last(node) - - def depart_description(self, node): - self.body.append('') - - # use table for docinfo - def visit_docinfo(self, node): - self.context.append(len(self.body)) - self.body.append(self.starttag(node, 'table', - CLASS='docinfo', - frame="void", rules="none")) - self.body.append('\n' - '\n' - '\n') - self.in_docinfo = True - - def depart_docinfo(self, node): - self.body.append('\n\n') - self.in_docinfo = False - start = self.context.pop() - self.docinfo = self.body[start:] - self.body = [] - - def visit_docinfo_item(self, node, name, meta=True): - if meta: - meta_tag = '\n' \ - % (name, self.attval(node.astext())) - self.add_meta(meta_tag) - self.body.append(self.starttag(node, 'tr', '')) - self.body.append('%s:\n' - % self.language.labels[name]) - if len(node): - if isinstance(node[0], nodes.Element): - node[0]['classes'].append('first') - if isinstance(node[-1], nodes.Element): - node[-1]['classes'].append('last') - - def depart_docinfo_item(self): - self.body.append('\n') - - # add newline after opening tag - def visit_doctest_block(self, node): - self.body.append(self.starttag(node, 'pre', CLASS='doctest-block')) - - def depart_doctest_block(self, node): - self.body.append('\n\n') - - # insert an NBSP into empty cells, ersatz for first/last - def visit_entry(self, node): - writers._html_base.HTMLTranslator.visit_entry(self, node) - if len(node) == 0: # empty cell - self.body.append(' ') - self.set_first_last(node) - - def depart_entry(self, node): - self.body.append(self.context.pop()) - - # ersatz for first/last pseudo-classes - def visit_enumerated_list(self, node): - """ - The 'start' attribute does not conform to HTML 4.01's strict.dtd, but - cannot be emulated in CSS1 (HTML 5 reincludes it). - """ - atts = {} - if 'start' in node: - atts['start'] = node['start'] - if 'enumtype' in node: - atts['class'] = node['enumtype'] - # @@@ To do: prefix, suffix. How? Change prefix/suffix to a - # single "format" attribute? Use CSS2? - old_compact_simple = self.compact_simple - self.context.append((self.compact_simple, self.compact_p)) - self.compact_p = None - self.compact_simple = self.is_compactable(node) - if self.compact_simple and not old_compact_simple: - atts['class'] = (atts.get('class', '') + ' simple').strip() - self.body.append(self.starttag(node, 'ol', **atts)) - - def depart_enumerated_list(self, node): - self.compact_simple, self.compact_p = self.context.pop() - self.body.append('\n') - - # use table for field-list: - def visit_field(self, node): - self.body.append(self.starttag(node, 'tr', '', CLASS='field')) - - def depart_field(self, node): - self.body.append('\n') - - def visit_field_body(self, node): - self.body.append(self.starttag(node, 'td', '', CLASS='field-body')) - self.set_class_on_child(node, 'first', 0) - field = node.parent - if (self.compact_field_list - or isinstance(field.parent, nodes.docinfo) - or field.parent.index(field) == len(field.parent) - 1): - # If we are in a compact list, the docinfo, or if this is - # the last field of the field list, do not add vertical - # space after last element. - self.set_class_on_child(node, 'last', -1) - - def depart_field_body(self, node): - self.body.append('\n') - - def visit_field_list(self, node): - self.context.append((self.compact_field_list, self.compact_p)) - self.compact_p = None - if 'compact' in node['classes']: - self.compact_field_list = True - elif (self.settings.compact_field_lists - and 'open' not in node['classes']): - self.compact_field_list = True - if self.compact_field_list: - for field in node: - field_body = field[-1] - assert isinstance(field_body, nodes.field_body) - children = [n for n in field_body - if not isinstance(n, nodes.Invisible)] - if not (len(children) == 0 - or len(children) == 1 - and isinstance(children[0], - (nodes.paragraph, nodes.line_block))): - self.compact_field_list = False - break - self.body.append(self.starttag(node, 'table', frame='void', - rules='none', - CLASS='docutils field-list')) - self.body.append('\n' - '\n' - '\n') - - def depart_field_list(self, node): - self.body.append('\n\n') - self.compact_field_list, self.compact_p = self.context.pop() - - def visit_field_name(self, node): - atts = {} - if self.in_docinfo: - atts['class'] = 'docinfo-name' - else: - atts['class'] = 'field-name' - if (self.settings.field_name_limit - and len(node.astext()) > self.settings.field_name_limit): - atts['colspan'] = 2 - self.context.append('\n' - + self.starttag(node.parent, 'tr', '', - CLASS='field') - + ' ') - else: - self.context.append('') - self.body.append(self.starttag(node, 'th', '', **atts)) - - def depart_field_name(self, node): - self.body.append(':') - self.body.append(self.context.pop()) - - # use table for footnote text - def visit_footnote(self, node): - self.body.append(self.starttag(node, 'table', - CLASS='docutils footnote', - frame="void", rules="none")) - self.body.append('\n' - '\n' - '') - self.footnote_backrefs(node) - - def footnote_backrefs(self, node): - backlinks = [] - backrefs = node['backrefs'] - if self.settings.footnote_backlinks and backrefs: - if len(backrefs) == 1: - self.context.append('') - self.context.append('') - self.context.append('' - % backrefs[0]) - else: - for (i, backref) in enumerate(backrefs, 1): - backlinks.append('%s' - % (backref, i)) - self.context.append('(%s) ' % ', '.join(backlinks)) - self.context += ['', ''] - else: - self.context.append('') - self.context += ['', ''] - # If the node does not only consist of a label. - if len(node) > 1: - # If there are preceding backlinks, we do not set class - # 'first', because we need to retain the top-margin. - if not backlinks: - node[1]['classes'].append('first') - node[-1]['classes'].append('last') - - def depart_footnote(self, node): - self.body.append('\n' - '\n\n') - - # insert markers in text (pseudo-classes are not supported in CSS1): - def visit_footnote_reference(self, node): - href = '#' + node['refid'] - format = self.settings.footnote_references - if format == 'brackets': - suffix = '[' - self.context.append(']') - else: - assert format == 'superscript' - suffix = '' - self.context.append('') - self.body.append(self.starttag(node, 'a', suffix, - CLASS='footnote-reference', href=href)) - - def depart_footnote_reference(self, node): - self.body.append(self.context.pop() + '') - - # just pass on generated text - def visit_generated(self, node): - pass - - # Backwards-compatibility implementation: - # * Do not use

    tags to produce visually compact lists (less vertical - # whitespace) as CSS styling requires CSS2. - def should_be_compact_paragraph(self, node): - """ - Determine if the

    tags around paragraph ``node`` can be omitted. - """ - if (isinstance(node.parent, nodes.document) - or isinstance(node.parent, nodes.compound)): - # Never compact paragraphs in document or compound. - return False - for key, value in node.attlist(): - if (node.is_not_default(key) - and not (key == 'classes' - and value in ([], ['first'], - ['last'], ['first', 'last']))): - # Attribute which needs to survive. - return False - first = isinstance(node.parent[0], nodes.label) # skip label - for child in node.parent.children[first:]: - # only first paragraph can be compact - if isinstance(child, nodes.Invisible): - continue - if child is node: - break - return False - parent_length = len([n for n in node.parent if not isinstance( - n, (nodes.Invisible, nodes.label))]) - if (self.compact_simple - or self.compact_field_list - or self.compact_p and parent_length == 1): - return True - return False - - def visit_paragraph(self, node): - if self.should_be_compact_paragraph(node): - self.context.append('') - else: - self.body.append(self.starttag(node, 'p', '')) - self.context.append('

    \n') - - def depart_paragraph(self, node): - self.body.append(self.context.pop()) - - # ersatz for first/last pseudo-classes - def visit_sidebar(self, node): - self.body.append( - self.starttag(node, 'div', CLASS='sidebar')) - self.set_first_last(node) - self.in_sidebar = True - - def depart_sidebar(self, node): - self.body.append('\n') - self.in_sidebar = False - - # not allowed in
    -    def visit_subscript(self, node):
    -        if isinstance(node.parent, nodes.literal_block):
    -            self.body.append(self.starttag(node, 'span', '',
    -                                           CLASS='subscript'))
    -        else:
    -            self.body.append(self.starttag(node, 'sub', ''))
    -
    -    def depart_subscript(self, node):
    -        if isinstance(node.parent, nodes.literal_block):
    -            self.body.append('')
    -        else:
    -            self.body.append('')
    -
    -    # Use  for subtitles (deprecated in HTML 5)
    -    def visit_subtitle(self, node):
    -        if isinstance(node.parent, nodes.sidebar):
    -            self.body.append(self.starttag(node, 'p', '',
    -                                           CLASS='sidebar-subtitle'))
    -            self.context.append('

    \n') - elif isinstance(node.parent, nodes.document): - self.body.append(self.starttag(node, 'h2', '', CLASS='subtitle')) - self.context.append('

    \n') - self.in_document_title = len(self.body) - elif isinstance(node.parent, nodes.section): - tag = 'h%s' % (self.section_level + self.initial_header_level - 1) - self.body.append( - self.starttag(node, tag, '', CLASS='section-subtitle') - + self.starttag({}, 'span', '', CLASS='section-subtitle')) - self.context.append('\n' % tag) - - def depart_subtitle(self, node): - self.body.append(self.context.pop()) - if self.in_document_title: - self.subtitle = self.body[self.in_document_title:-1] - self.in_document_title = 0 - self.body_pre_docinfo.extend(self.body) - self.html_subtitle.extend(self.body) - del self.body[:] - - # not allowed in
     in HTML 4
    -    def visit_superscript(self, node):
    -        if isinstance(node.parent, nodes.literal_block):
    -            self.body.append(self.starttag(node, 'span', '',
    -                                           CLASS='superscript'))
    -        else:
    -            self.body.append(self.starttag(node, 'sup', ''))
    -
    -    def depart_superscript(self, node):
    -        if isinstance(node.parent, nodes.literal_block):
    -            self.body.append('')
    -        else:
    -            self.body.append('')
    -
    -    #  element deprecated in HTML 5
    -    def visit_system_message(self, node):
    -        self.body.append(self.starttag(node, 'div', CLASS='system-message'))
    -        self.body.append('

    ') - backref_text = '' - if len(node['backrefs']): - backrefs = node['backrefs'] - if len(backrefs) == 1: - backref_text = ('; backlink' - % backrefs[0]) - else: - i = 1 - backlinks = [] - for backref in backrefs: - backlinks.append('%s' % (backref, i)) - i += 1 - backref_text = ('; backlinks: %s' - % ', '.join(backlinks)) - if node.hasattr('line'): - line = ', line %s' % node['line'] - else: - line = '' - self.body.append('System Message: %s/%s ' - '(%s%s)%s

    \n' - % (node['type'], node['level'], - self.encode(node['source']), line, backref_text)) - - def depart_system_message(self, node): - self.body.append('\n') - - # "hard coded" border setting - def visit_table(self, node): - self.context.append(self.compact_p) - self.compact_p = True - atts = {'border': 1} - classes = ['docutils', self.settings.table_style] - if 'align' in node: - classes.append('align-%s' % node['align']) - if 'width' in node: - atts['style'] = 'width: %s' % node['width'] - self.body.append( - self.starttag(node, 'table', CLASS=' '.join(classes), **atts)) - - def depart_table(self, node): - self.compact_p = self.context.pop() - self.body.append('\n') - - # hard-coded vertical alignment - def visit_tbody(self, node): - self.body.append(self.starttag(node, 'tbody', valign='top')) - - def depart_tbody(self, node): - self.body.append('\n') - - # no special handling of "details" in definition list - def visit_term(self, node): - self.body.append(self.starttag(node, 'dt', '', - classes=node.parent['classes'], - ids=node.parent['ids'])) - - def depart_term(self, node): - pass - - # hard-coded vertical alignment - def visit_thead(self, node): - self.body.append(self.starttag(node, 'thead', valign='bottom')) - - def depart_thead(self, node): - self.body.append('\n') - - # auxiliary method, called by visit_title() - # "with-subtitle" class, no ARIA roles - def section_title_tags(self, node): - classes = [] - h_level = self.section_level + self.initial_header_level - 1 - if (len(node.parent) >= 2 - and isinstance(node.parent[1], nodes.subtitle)): - classes.append('with-subtitle') - if h_level > 6: - classes.append('h%i' % h_level) - tagname = 'h%i' % min(h_level, 6) - start_tag = self.starttag(node, tagname, '', classes=classes) - if node.hasattr('refid'): - atts = {} - atts['class'] = 'toc-backref' - atts['href'] = '#' + node['refid'] - start_tag += self.starttag({}, 'a', '', **atts) - close_tag = '\n' % tagname - else: - close_tag = '\n' % tagname - return start_tag, close_tag - - -class SimpleListChecker(writers._html_base.SimpleListChecker): - - """ - Raise `nodes.NodeFound` if non-simple list item is encountered. - - Here "simple" means a list item containing nothing other than a single - paragraph, a simple list, or a paragraph followed by a simple list. - """ - - def visit_list_item(self, node): - children = [] - for child in node.children: - if not isinstance(child, nodes.Invisible): - children.append(child) - if (children and isinstance(children[0], nodes.paragraph) - and (isinstance(children[-1], nodes.bullet_list) - or isinstance(children[-1], nodes.enumerated_list))): - children.pop() - if len(children) <= 1: - return - else: - raise nodes.NodeFound - - # def visit_bullet_list(self, node): - # pass - - # def visit_enumerated_list(self, node): - # pass - - def visit_paragraph(self, node): - raise nodes.SkipNode - - def visit_definition_list(self, node): - raise nodes.NodeFound - - def visit_docinfo(self, node): - raise nodes.NodeFound diff --git a/lib/docutils/writers/html4css1/html4css1.css b/lib/docutils/writers/html4css1/html4css1.css deleted file mode 100644 index 1443ff95..00000000 --- a/lib/docutils/writers/html4css1/html4css1.css +++ /dev/null @@ -1,349 +0,0 @@ -/* -:Author: David Goodger (goodger@python.org) -:Id: $Id: html4css1.css 8954 2022-01-20 10:10:25Z milde $ -:Copyright: This stylesheet has been placed in the public domain. - -Default cascading style sheet for the HTML output of Docutils. - -See https://docutils.sourceforge.io/docs/howto/html-stylesheets.html for how to -customize this style sheet. -*/ - -/* used to remove borders from tables and images */ -.borderless, table.borderless td, table.borderless th { - border: 0 } - -table.borderless td, table.borderless th { - /* Override padding for "table.docutils td" with "! important". - The right padding separates the table cells. */ - padding: 0 0.5em 0 0 ! important } - -.first { - /* Override more specific margin styles with "! important". */ - margin-top: 0 ! important } - -.last, .with-subtitle { - margin-bottom: 0 ! important } - -.hidden { - display: none } - -.subscript { - vertical-align: sub; - font-size: smaller } - -.superscript { - vertical-align: super; - font-size: smaller } - -a.toc-backref { - text-decoration: none ; - color: black } - -blockquote.epigraph { - margin: 2em 5em ; } - -dl.docutils dd { - margin-bottom: 0.5em } - -object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { - overflow: hidden; -} - -/* Uncomment (and remove this text!) to get bold-faced definition list terms -dl.docutils dt { - font-weight: bold } -*/ - -div.abstract { - margin: 2em 5em } - -div.abstract p.topic-title { - font-weight: bold ; - text-align: center } - -div.admonition, div.attention, div.caution, div.danger, div.error, -div.hint, div.important, div.note, div.tip, div.warning { - margin: 2em ; - border: medium outset ; - padding: 1em } - -div.admonition p.admonition-title, div.hint p.admonition-title, -div.important p.admonition-title, div.note p.admonition-title, -div.tip p.admonition-title { - font-weight: bold ; - font-family: sans-serif } - -div.attention p.admonition-title, div.caution p.admonition-title, -div.danger p.admonition-title, div.error p.admonition-title, -div.warning p.admonition-title, .code .error { - color: red ; - font-weight: bold ; - font-family: sans-serif } - -/* Uncomment (and remove this text!) to get reduced vertical space in - compound paragraphs. -div.compound .compound-first, div.compound .compound-middle { - margin-bottom: 0.5em } - -div.compound .compound-last, div.compound .compound-middle { - margin-top: 0.5em } -*/ - -div.dedication { - margin: 2em 5em ; - text-align: center ; - font-style: italic } - -div.dedication p.topic-title { - font-weight: bold ; - font-style: normal } - -div.figure { - margin-left: 2em ; - margin-right: 2em } - -div.footer, div.header { - clear: both; - font-size: smaller } - -div.line-block { - display: block ; - margin-top: 1em ; - margin-bottom: 1em } - -div.line-block div.line-block { - margin-top: 0 ; - margin-bottom: 0 ; - margin-left: 1.5em } - -div.sidebar { - margin: 0 0 0.5em 1em ; - border: medium outset ; - padding: 1em ; - background-color: #ffffee ; - width: 40% ; - float: right ; - clear: right } - -div.sidebar p.rubric { - font-family: sans-serif ; - font-size: medium } - -div.system-messages { - margin: 5em } - -div.system-messages h1 { - color: red } - -div.system-message { - border: medium outset ; - padding: 1em } - -div.system-message p.system-message-title { - color: red ; - font-weight: bold } - -div.topic { - margin: 2em } - -h1.section-subtitle, h2.section-subtitle, h3.section-subtitle, -h4.section-subtitle, h5.section-subtitle, h6.section-subtitle { - margin-top: 0.4em } - -h1.title { - text-align: center } - -h2.subtitle { - text-align: center } - -hr.docutils { - width: 75% } - -img.align-left, .figure.align-left, object.align-left, table.align-left { - clear: left ; - float: left ; - margin-right: 1em } - -img.align-right, .figure.align-right, object.align-right, table.align-right { - clear: right ; - float: right ; - margin-left: 1em } - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -table.align-center { - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left } - -.align-center { - clear: both ; - text-align: center } - -.align-right { - text-align: right } - -/* reset inner alignment in figures */ -div.align-right { - text-align: inherit } - -/* div.align-center * { */ -/* text-align: left } */ - -.align-top { - vertical-align: top } - -.align-middle { - vertical-align: middle } - -.align-bottom { - vertical-align: bottom } - -ol.simple, ul.simple { - margin-bottom: 1em } - -ol.arabic { - list-style: decimal } - -ol.loweralpha { - list-style: lower-alpha } - -ol.upperalpha { - list-style: upper-alpha } - -ol.lowerroman { - list-style: lower-roman } - -ol.upperroman { - list-style: upper-roman } - -p.attribution { - text-align: right ; - margin-left: 50% } - -p.caption { - font-style: italic } - -p.credits { - font-style: italic ; - font-size: smaller } - -p.label { - white-space: nowrap } - -p.rubric { - font-weight: bold ; - font-size: larger ; - color: maroon ; - text-align: center } - -p.sidebar-title { - font-family: sans-serif ; - font-weight: bold ; - font-size: larger } - -p.sidebar-subtitle { - font-family: sans-serif ; - font-weight: bold } - -p.topic-title { - font-weight: bold } - -pre.address { - margin-bottom: 0 ; - margin-top: 0 ; - font: inherit } - -pre.literal-block, pre.doctest-block, pre.math, pre.code { - margin-left: 2em ; - margin-right: 2em } - -pre.code .ln { color: grey; } /* line numbers */ -pre.code, code { background-color: #eeeeee } -pre.code .comment, code .comment { color: #5C6576 } -pre.code .keyword, code .keyword { color: #3B0D06; font-weight: bold } -pre.code .literal.string, code .literal.string { color: #0C5404 } -pre.code .name.builtin, code .name.builtin { color: #352B84 } -pre.code .deleted, code .deleted { background-color: #DEB0A1} -pre.code .inserted, code .inserted { background-color: #A3D289} - -span.classifier { - font-family: sans-serif ; - font-style: oblique } - -span.classifier-delimiter { - font-family: sans-serif ; - font-weight: bold } - -span.interpreted { - font-family: sans-serif } - -span.option { - white-space: nowrap } - -span.pre { - white-space: pre } - -span.problematic { - color: red } - -span.section-subtitle { - /* font-size relative to parent (h1..h6 element) */ - font-size: 80% } - -table.citation { - border-left: solid 1px gray; - margin-left: 1px } - -table.docinfo { - margin: 2em 4em } - -table.docutils { - margin-top: 0.5em ; - margin-bottom: 0.5em } - -table.footnote { - border-left: solid 1px black; - margin-left: 1px } - -table.docutils td, table.docutils th, -table.docinfo td, table.docinfo th { - padding-left: 0.5em ; - padding-right: 0.5em ; - vertical-align: top } - -table.docutils th.field-name, table.docinfo th.docinfo-name { - font-weight: bold ; - text-align: left ; - white-space: nowrap ; - padding-left: 0 } - -/* "booktabs" style (no vertical lines) */ -table.docutils.booktabs { - border: 0px; - border-top: 2px solid; - border-bottom: 2px solid; - border-collapse: collapse; -} -table.docutils.booktabs * { - border: 0px; -} -table.docutils.booktabs th { - border-bottom: thin solid; - text-align: left; -} - -h1 tt.docutils, h2 tt.docutils, h3 tt.docutils, -h4 tt.docutils, h5 tt.docutils, h6 tt.docutils { - font-size: 100% } - -ul.auto-toc { - list-style-type: none } diff --git a/lib/docutils/writers/html4css1/template.txt b/lib/docutils/writers/html4css1/template.txt deleted file mode 100644 index 2591bce3..00000000 --- a/lib/docutils/writers/html4css1/template.txt +++ /dev/null @@ -1,8 +0,0 @@ -%(head_prefix)s -%(head)s -%(stylesheet)s -%(body_prefix)s -%(body_pre_docinfo)s -%(docinfo)s -%(body)s -%(body_suffix)s diff --git a/lib/docutils/writers/html5_polyglot/__init__.py b/lib/docutils/writers/html5_polyglot/__init__.py deleted file mode 100644 index 5f9898d8..00000000 --- a/lib/docutils/writers/html5_polyglot/__init__.py +++ /dev/null @@ -1,454 +0,0 @@ -# $Id: __init__.py 9057 2022-05-06 10:30:25Z milde $ -# :Author: Günter Milde -# Based on the html4css1 writer by David Goodger. -# :Maintainer: docutils-develop@lists.sourceforge.net -# :Copyright: © 2005, 2009, 2015 Günter Milde, -# portions from html4css1 © David Goodger. -# :License: Released under the terms of the `2-Clause BSD license`_, in short: -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. -# This file is offered as-is, without any warranty. -# -# .. _2-Clause BSD license: https://opensource.org/licenses/BSD-2-Clause - -# Use "best practice" as recommended by the W3C: -# http://www.w3.org/2009/cheatsheet/ - -""" -Plain HyperText Markup Language document tree Writer. - -The output conforms to the `HTML 5` specification. - -The cascading style sheet "minimal.css" is required for proper viewing, -the style sheet "plain.css" improves reading experience. -""" -__docformat__ = 'reStructuredText' - -import mimetypes -import os.path - -from docutils import frontend, nodes -from docutils.writers import _html_base - - -class Writer(_html_base.Writer): - - supported = ('html5', 'xhtml', 'html') - """Formats this writer supports.""" - - default_stylesheets = ['minimal.css', 'plain.css'] - default_stylesheet_dirs = ['.', os.path.abspath(os.path.dirname(__file__))] - default_template = os.path.join( - os.path.dirname(os.path.abspath(__file__)), 'template.txt') - - # use a copy of the parent spec with some modifications - settings_spec = frontend.filter_settings_spec( - _html_base.Writer.settings_spec, - template=( - f'Template file. (UTF-8 encoded, default: "{default_template}")', - ['--template'], - {'default': default_template, 'metavar': ''}), - stylesheet_path=( - 'Comma separated list of stylesheet paths. ' - 'Relative paths are expanded if a matching file is found in ' - 'the --stylesheet-dirs. With --link-stylesheet, ' - 'the path is rewritten relative to the output HTML file. ' - '(default: "%s")' % ','.join(default_stylesheets), - ['--stylesheet-path'], - {'metavar': '', 'overrides': 'stylesheet', - 'validator': frontend.validate_comma_separated_list, - 'default': default_stylesheets}), - stylesheet_dirs=( - 'Comma-separated list of directories where stylesheets are found. ' - 'Used by --stylesheet-path when expanding relative path ' - 'arguments. (default: "%s")' % ','.join(default_stylesheet_dirs), - ['--stylesheet-dirs'], - {'metavar': '', - 'validator': frontend.validate_comma_separated_list, - 'default': default_stylesheet_dirs}), - initial_header_level=( - 'Specify the initial header level. Does not affect document ' - 'title & subtitle (see --no-doc-title). (default: 2 for "

    ")', - ['--initial-header-level'], - {'choices': '1 2 3 4 5 6'.split(), 'default': '2', - 'metavar': ''}), - no_xml_declaration=( - 'Omit the XML declaration.', - ['--no-xml-declaration'], - {'dest': 'xml_declaration', 'action': 'store_false'}), - ) - settings_spec = settings_spec + ( - 'HTML5 Writer Options', - '', - ((frontend.SUPPRESS_HELP, # Obsoleted by "--image-loading" - ['--embed-images'], - {'action': 'store_true', - 'validator': frontend.validate_boolean}), - (frontend.SUPPRESS_HELP, # Obsoleted by "--image-loading" - ['--link-images'], - {'dest': 'embed_images', 'action': 'store_false'}), - ('Suggest at which point images should be loaded: ' - '"embed", "link" (default), or "lazy".', - ['--image-loading'], - {'choices': ('embed', 'link', 'lazy'), - # 'default': 'link' # default set in _html_base.py - }), - ('Append a self-link to section headings.', - ['--section-self-link'], - {'default': 0, 'action': 'store_true'}), - ('Do not append a self-link to section headings. (default)', - ['--no-section-self-link'], - {'dest': 'section_self_link', 'action': 'store_false'}), - ) - ) - - config_section = 'html5 writer' - - def __init__(self): - self.parts = {} - self.translator_class = HTMLTranslator - - -class HTMLTranslator(_html_base.HTMLTranslator): - """ - This writer generates `polyglot markup`: HTML5 that is also valid XML. - - Safe subclassing: when overriding, treat ``visit_*`` and ``depart_*`` - methods as a unit to prevent breaks due to internal changes. See the - docstring of docutils.writers._html_base.HTMLTranslator for details - and examples. - """ - - # meta tag to fix rendering in mobile browsers - viewport = ('\n') - - # tag obsolete in HTML5. Use the tag instead. - def visit_acronym(self, node): - # @@@ implementation incomplete ("title" attribute) - self.body.append(self.starttag(node, 'abbr', '')) - - def depart_acronym(self, node): - self.body.append('') - - # no standard meta tag name in HTML5, use separate "author" meta tags - # https://www.w3.org/TR/html5/document-metadata.html#standard-metadata-names - def visit_authors(self, node): - self.visit_docinfo_item(node, 'authors', meta=False) - for subnode in node: - self.add_meta('\n' % - self.attval(subnode.astext())) - - def depart_authors(self, node): - self.depart_docinfo_item() - - # use the
    semantic tag. - def visit_caption(self, node): - if isinstance(node.parent, nodes.figure): - self.body.append('
    \n') - self.body.append(self.starttag(node, 'p', '')) - - def depart_caption(self, node): - self.body.append('

    \n') - #
    is closed in depart_figure(), as legend may follow. - - # use HTML block-level tags if matching class value found - supported_block_tags = {'ins', 'del'} - - def visit_container(self, node): - # If there is exactly one of the "supported block tags" in - # the list of class values, use it as tag name: - classes = node['classes'] - tags = [cls for cls in classes - if cls in self.supported_block_tags] - if len(tags) == 1: - node.html5tagname = tags[0] - classes.remove(tags[0]) - else: - node.html5tagname = 'div' - self.body.append(self.starttag(node, node.html5tagname, - CLASS='docutils container')) - - def depart_container(self, node): - self.body.append(f'\n') - - # no standard meta tag name in HTML5, use dcterms.rights - # see https://wiki.whatwg.org/wiki/MetaExtensions - def visit_copyright(self, node): - self.visit_docinfo_item(node, 'copyright', meta=False) - self.add_meta('\n' - % self.attval(node.astext())) - - def depart_copyright(self, node): - self.depart_docinfo_item() - - # no standard meta tag name in HTML5, use dcterms.date - def visit_date(self, node): - self.visit_docinfo_item(node, 'date', meta=False) - self.add_meta('\n' - % self.attval(node.astext())) - - def depart_date(self, node): - self.depart_docinfo_item() - - def visit_document(self, node): - title = (node.get('title', '') or os.path.basename(node['source']) - or 'untitled Docutils document') - self.head.append(f'{self.encode(title)}\n') - - def depart_document(self, node): - self.head_prefix.extend([self.doctype, - self.head_prefix_template % - {'lang': self.settings.language_code}]) - self.html_prolog.append(self.doctype) - self.meta.insert(0, self.viewport) - self.head.insert(0, self.viewport) - self.meta.insert(0, self.content_type % self.settings.output_encoding) - self.head.insert(0, self.content_type % self.settings.output_encoding) - if 'name="dcterms.' in ''.join(self.meta): - self.head.append('') - if self.math_header: - if self.math_output == 'mathjax': - self.head.extend(self.math_header) - else: - self.stylesheet.extend(self.math_header) - # skip content-type meta tag with interpolated charset value: - self.html_head.extend(self.head[1:]) - self.body_prefix.append(self.starttag(node, 'main')) - self.body_suffix.insert(0, '\n') - self.fragment.extend(self.body) # self.fragment is the "naked" body - self.html_body.extend(self.body_prefix[1:] + self.body_pre_docinfo - + self.docinfo + self.body - + self.body_suffix[:-1]) - assert not self.context, f'len(context) = {len(self.context)}' - - # use new HTML5
    and
    elements - def visit_figure(self, node): - atts = {} - if node.get('width'): - atts['style'] = f"width: {node['width']}" - if node.get('align'): - atts['class'] = f"align-{node['align']}" - self.body.append(self.starttag(node, 'figure', **atts)) - - def depart_figure(self, node): - if len(node) > 1: - self.body.append('
    \n') - self.body.append('
    \n') - - # use HTML5
    element - def visit_footer(self, node): - self.context.append(len(self.body)) - - def depart_footer(self, node): - start = self.context.pop() - footer = [self.starttag(node, 'footer')] - footer.extend(self.body[start:]) - footer.append('
    \n') - self.footer.extend(footer) - self.body_suffix[:0] = footer - del self.body[start:] - - # use HTML5
    element - def visit_header(self, node): - self.context.append(len(self.body)) - - def depart_header(self, node): - start = self.context.pop() - header = [self.starttag(node, 'header')] - header.extend(self.body[start:]) - header.append('
    \n') - self.body_prefix.extend(header) - self.header.extend(header) - del self.body[start:] - - # MIME types supported by the HTML5 {suffix}') - - def depart_image(self, node): - pass - - # use HTML text-level tags if matching class value found - supported_inline_tags = {'code', 'kbd', 'dfn', 'samp', 'var', - 'bdi', 'del', 'ins', 'mark', 'small', - 'b', 'i', 'q', 's', 'u'} - - # Use `supported_inline_tags` if found in class values - def visit_inline(self, node): - classes = node['classes'] - tags = [cls for cls in self.supported_inline_tags - if cls in classes] - if len(tags): - node.html5tagname = tags[0] - classes.remove(tags[0]) - elif (classes == ['ln'] - and isinstance(node.parent, nodes.literal_block) - and 'code' in node.parent.get('classes')): - if self.body[-1] == '': - del(self.body[-1]) - else: - self.body.append('') - node.html5tagname = 'small' - else: - node.html5tagname = 'span' - self.body.append(self.starttag(node, node.html5tagname, '')) - - def depart_inline(self, node): - self.body.append(f'') - if (node.html5tagname == 'small' and node.get('classes') == ['ln'] - and isinstance(node.parent, nodes.literal_block)): - self.body.append(f'') - del node.html5tagname - - # place inside HTML5
    element (together with caption) - def visit_legend(self, node): - if not isinstance(node.parent[1], nodes.caption): - self.body.append('
    \n') - self.body.append(self.starttag(node, 'div', CLASS='legend')) - - def depart_legend(self, node): - self.body.append('\n') - #
    closed in visit_figure() - - # use HTML text-level tags if matching class value found - def visit_literal(self, node): - classes = node['classes'] - tags = [cls for cls in self.supported_inline_tags - if cls in classes] - if len(tags): - tagname = tags[0] - classes.remove(tags[0]) - else: - tagname = 'span' - if tagname == 'code': - self.body.append(self.starttag(node, 'code', '')) - return - self.body.append( - self.starttag(node, tagname, '', CLASS='docutils literal')) - text = node.astext() - # remove hard line breaks (except if in a parsed-literal block) - if not isinstance(node.parent, nodes.literal_block): - text = text.replace('\n', ' ') - # Protect text like ``--an-option`` and the regular expression - # ``[+]?(\d+(\.\d*)?|\.\d+)`` from bad line wrapping - for token in self.words_and_spaces.findall(text): - if token.strip() and self.in_word_wrap_point.search(token): - self.body.append( - f'{self.encode(token)}') - else: - self.body.append(self.encode(token)) - self.body.append(f'') - # Content already processed: - raise nodes.SkipNode - - def depart_literal(self, node): - # skipped unless literal element is from "code" role: - self.body.append('') - - # Meta tags: 'lang' attribute replaced by 'xml:lang' in XHTML 1.1 - # HTML5/polyglot recommends using both - def visit_meta(self, node): - if node.hasattr('lang'): - node['xml:lang'] = node['lang'] - meta = self.emptytag(node, 'meta', **node.non_default_attributes()) - self.add_meta(meta) - - def depart_meta(self, node): - pass - - # no standard meta tag name in HTML5 - def visit_organization(self, node): - self.visit_docinfo_item(node, 'organization', meta=False) - - def depart_organization(self, node): - self.depart_docinfo_item() - - # use the new HTML5 element
    - def visit_section(self, node): - self.section_level += 1 - self.body.append( - self.starttag(node, 'section')) - - def depart_section(self, node): - self.section_level -= 1 - self.body.append('
    \n') - - # use the new HTML5 element \n') - self.in_sidebar = False - - # Use new HTML5 element