From 43a97878ce14b72f0981164f87f2e35e14151312 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:22:09 +0200 Subject: Adding upstream version 110.0.1. Signed-off-by: Daniel Baumann --- .../tools/third_party/atomicwrites/.gitignore | 9 + .../tools/third_party/atomicwrites/.travis.yml | 35 + .../third_party/atomicwrites/CONTRIBUTING.rst | 11 + .../tests/tools/third_party/atomicwrites/LICENSE | 19 + .../tools/third_party/atomicwrites/MANIFEST.in | 6 + .../tests/tools/third_party/atomicwrites/Makefile | 2 + .../tools/third_party/atomicwrites/README.rst | 102 + .../tools/third_party/atomicwrites/appveyor.yml | 18 + .../atomicwrites/atomicwrites/__init__.py | 201 + .../tools/third_party/atomicwrites/docs/Makefile | 177 + .../tools/third_party/atomicwrites/docs/conf.py | 107 + .../tools/third_party/atomicwrites/docs/index.rst | 35 + .../tools/third_party/atomicwrites/docs/make.bat | 242 + .../tests/tools/third_party/atomicwrites/setup.cfg | 2 + .../tests/tools/third_party/atomicwrites/setup.py | 27 + .../tests/tools/third_party/atomicwrites/tox.ini | 11 + .../third_party/attrs/.github/CODE_OF_CONDUCT.md | 133 + .../third_party/attrs/.github/CONTRIBUTING.md | 230 + .../tools/third_party/attrs/.github/FUNDING.yml | 5 + .../attrs/.github/PULL_REQUEST_TEMPLATE.md | 34 + .../tools/third_party/attrs/.github/SECURITY.md | 2 + .../third_party/attrs/.github/workflows/main.yml | 113 + .../tests/tools/third_party/attrs/.gitignore | 13 + .../third_party/attrs/.pre-commit-config.yaml | 43 + .../tests/tools/third_party/attrs/.readthedocs.yml | 16 + .../tests/tools/third_party/attrs/AUTHORS.rst | 11 + .../tests/tools/third_party/attrs/CHANGELOG.rst | 1027 + .../tests/tools/third_party/attrs/LICENSE | 21 + .../tests/tools/third_party/attrs/MANIFEST.in | 24 + .../tests/tools/third_party/attrs/README.rst | 135 + .../tools/third_party/attrs/changelog.d/.gitignore | 0 .../attrs/changelog.d/towncrier_template.rst | 35 + .../tests/tools/third_party/attrs/conftest.py | 29 + .../tests/tools/third_party/attrs/docs/Makefile | 177 + .../third_party/attrs/docs/_static/attrs_logo.png | Bin 0 -> 7639 bytes .../third_party/attrs/docs/_static/attrs_logo.svg | 10 + .../attrs/docs/_static/attrs_logo_white.svg | 10 + .../tests/tools/third_party/attrs/docs/api.rst | 826 + .../tools/third_party/attrs/docs/changelog.rst | 1 + .../tools/third_party/attrs/docs/comparison.rst | 66 + .../tests/tools/third_party/attrs/docs/conf.py | 155 + .../tools/third_party/attrs/docs/docutils.conf | 3 + .../tools/third_party/attrs/docs/examples.rst | 709 + .../tools/third_party/attrs/docs/extending.rst | 313 + .../tools/third_party/attrs/docs/glossary.rst | 104 + .../tests/tools/third_party/attrs/docs/hashing.rst | 86 + .../third_party/attrs/docs/how-does-it-work.rst | 109 + .../tests/tools/third_party/attrs/docs/index.rst | 100 + .../tests/tools/third_party/attrs/docs/init.rst | 489 + .../tests/tools/third_party/attrs/docs/license.rst | 8 + .../tests/tools/third_party/attrs/docs/names.rst | 122 + .../tools/third_party/attrs/docs/overview.rst | 58 + .../tools/third_party/attrs/docs/python-2.rst | 25 + .../tests/tools/third_party/attrs/docs/types.rst | 108 + .../tests/tools/third_party/attrs/docs/why.rst | 290 + .../tests/tools/third_party/attrs/mypy.ini | 3 + .../tests/tools/third_party/attrs/pyproject.toml | 71 + .../tests/tools/third_party/attrs/setup.py | 151 + .../tools/third_party/attrs/src/attr/__init__.py | 80 + .../tools/third_party/attrs/src/attr/__init__.pyi | 484 + .../tests/tools/third_party/attrs/src/attr/_cmp.py | 154 + .../tools/third_party/attrs/src/attr/_cmp.pyi | 13 + .../tools/third_party/attrs/src/attr/_compat.py | 261 + .../tools/third_party/attrs/src/attr/_config.py | 33 + .../tools/third_party/attrs/src/attr/_funcs.py | 422 + .../tools/third_party/attrs/src/attr/_make.py | 3173 ++ .../tools/third_party/attrs/src/attr/_next_gen.py | 216 + .../third_party/attrs/src/attr/_version_info.py | 87 + .../third_party/attrs/src/attr/_version_info.pyi | 9 + .../tools/third_party/attrs/src/attr/converters.py | 155 + .../third_party/attrs/src/attr/converters.pyi | 13 + .../tools/third_party/attrs/src/attr/exceptions.py | 94 + .../third_party/attrs/src/attr/exceptions.pyi | 17 + .../tools/third_party/attrs/src/attr/filters.py | 54 + .../tools/third_party/attrs/src/attr/filters.pyi | 6 + .../tools/third_party/attrs/src/attr/py.typed | 0 .../tools/third_party/attrs/src/attr/setters.py | 79 + .../tools/third_party/attrs/src/attr/setters.pyi | 19 + .../tools/third_party/attrs/src/attr/validators.py | 561 + .../third_party/attrs/src/attr/validators.pyi | 78 + .../tools/third_party/attrs/src/attrs/__init__.py | 70 + .../tools/third_party/attrs/src/attrs/__init__.pyi | 63 + .../third_party/attrs/src/attrs/converters.py | 3 + .../third_party/attrs/src/attrs/exceptions.py | 3 + .../tools/third_party/attrs/src/attrs/filters.py | 3 + .../tools/third_party/attrs/src/attrs/py.typed | 0 .../tools/third_party/attrs/src/attrs/setters.py | 3 + .../third_party/attrs/src/attrs/validators.py | 3 + .../tools/third_party/attrs/tests/__init__.py | 1 + .../third_party/attrs/tests/attr_import_star.py | 10 + .../attrs/tests/dataclass_transform_example.py | 45 + .../tools/third_party/attrs/tests/strategies.py | 198 + .../third_party/attrs/tests/test_3rd_party.py | 31 + .../third_party/attrs/tests/test_annotations.py | 671 + .../tools/third_party/attrs/tests/test_cmp.py | 510 + .../tools/third_party/attrs/tests/test_compat.py | 52 + .../tools/third_party/attrs/tests/test_config.py | 45 + .../third_party/attrs/tests/test_converters.py | 163 + .../tools/third_party/attrs/tests/test_dunders.py | 1008 + .../tools/third_party/attrs/tests/test_filters.py | 111 + .../tools/third_party/attrs/tests/test_funcs.py | 680 + .../third_party/attrs/tests/test_functional.py | 790 + .../tools/third_party/attrs/tests/test_hooks.py | 209 + .../tools/third_party/attrs/tests/test_import.py | 11 + .../third_party/attrs/tests/test_init_subclass.py | 48 + .../tools/third_party/attrs/tests/test_make.py | 2462 + .../tools/third_party/attrs/tests/test_mypy.yml | 1395 + .../tools/third_party/attrs/tests/test_next_gen.py | 440 + .../attrs/tests/test_pattern_matching.py | 101 + .../tools/third_party/attrs/tests/test_pyright.py | 71 + .../tools/third_party/attrs/tests/test_setattr.py | 437 + .../tools/third_party/attrs/tests/test_slots.py | 740 + .../third_party/attrs/tests/test_validators.py | 952 + .../third_party/attrs/tests/test_version_info.py | 62 + .../third_party/attrs/tests/typing_example.py | 420 + .../tests/tools/third_party/attrs/tests/utils.py | 86 + .../tests/tools/third_party/attrs/tox.ini | 129 + .../tests/tools/third_party/certifi/LICENSE | 21 + .../tests/tools/third_party/certifi/MANIFEST.in | 1 + .../tests/tools/third_party/certifi/PKG-INFO | 69 + .../tests/tools/third_party/certifi/README.rst | 46 + .../tools/third_party/certifi/certifi/__init__.py | 3 + .../tools/third_party/certifi/certifi/__main__.py | 2 + .../tools/third_party/certifi/certifi/cacert.pem | 4400 ++ .../tools/third_party/certifi/certifi/core.py | 37 + .../tests/tools/third_party/certifi/setup.cfg | 11 + .../tests/tools/third_party/certifi/setup.py | 67 + .../tests/tools/third_party/enum/MANIFEST.in | 9 + .../tests/tools/third_party/enum/PKG-INFO | 60 + .../tests/tools/third_party/enum/README | 3 + .../tests/tools/third_party/enum/enum/LICENSE | 32 + .../tests/tools/third_party/enum/enum/README | 3 + .../tests/tools/third_party/enum/enum/__init__.py | 838 + .../tests/tools/third_party/enum/enum/doc/enum.rst | 735 + .../tests/tools/third_party/enum/enum/test.py | 1841 + .../tests/tools/third_party/enum/setup.cfg | 4 + .../tests/tools/third_party/enum/setup.py | 105 + .../tests/tools/third_party/funcsigs/.coveragerc | 6 + .../tests/tools/third_party/funcsigs/.gitignore | 19 + .../tests/tools/third_party/funcsigs/.travis.yml | 18 + .../tests/tools/third_party/funcsigs/CHANGELOG | 24 + .../tests/tools/third_party/funcsigs/LICENSE | 13 + .../tests/tools/third_party/funcsigs/MANIFEST.in | 7 + .../tests/tools/third_party/funcsigs/Makefile | 39 + .../tests/tools/third_party/funcsigs/README.rst | 353 + .../tests/tools/third_party/funcsigs/docs/Makefile | 153 + .../third_party/funcsigs/docs/_templates/page.html | 9 + .../tests/tools/third_party/funcsigs/docs/conf.py | 251 + .../third_party/funcsigs/funcsigs/__init__.py | 829 + .../tools/third_party/funcsigs/funcsigs/version.py | 1 + .../funcsigs/requirements/development.txt | 5 + .../tests/tools/third_party/funcsigs/setup.cfg | 2 + .../tests/tools/third_party/funcsigs/setup.py | 52 + .../tools/third_party/funcsigs/tests/__init__.py | 0 .../funcsigs/tests/test_formatannotation.py | 17 + .../third_party/funcsigs/tests/test_funcsigs.py | 91 + .../third_party/funcsigs/tests/test_inspect.py | 1002 + .../tests/tools/third_party/funcsigs/tox.ini | 8 + .../tests/tools/third_party/h2/.coveragerc | 18 + .../tests/tools/third_party/h2/CONTRIBUTORS.rst | 115 + .../tests/tools/third_party/h2/HISTORY.rst | 760 + .../tests/tools/third_party/h2/LICENSE | 21 + .../tests/tools/third_party/h2/MANIFEST.in | 8 + .../tests/tools/third_party/h2/Makefile | 9 + .../tests/tools/third_party/h2/README.rst | 65 + .../tests/tools/third_party/h2/docs/Makefile | 177 + .../tests/tools/third_party/h2/docs/make.bat | 242 + .../tools/third_party/h2/docs/source/_static/.keep | 0 .../h2.connection.H2ConnectionStateMachine.dot.png | Bin 0 -> 714520 bytes .../_static/h2.stream.H2StreamStateMachine.dot.png | Bin 0 -> 1856508 bytes .../third_party/h2/docs/source/advanced-usage.rst | 325 + .../tests/tools/third_party/h2/docs/source/api.rst | 169 + .../third_party/h2/docs/source/asyncio-example.rst | 17 + .../third_party/h2/docs/source/basic-usage.rst | 746 + .../tests/tools/third_party/h2/docs/source/conf.py | 270 + .../third_party/h2/docs/source/contributors.rst | 4 + .../third_party/h2/docs/source/curio-example.rst | 17 + .../h2/docs/source/eventlet-example.rst | 19 + .../tools/third_party/h2/docs/source/examples.rst | 28 + .../tools/third_party/h2/docs/source/index.rst | 41 + .../third_party/h2/docs/source/installation.rst | 18 + .../tools/third_party/h2/docs/source/low-level.rst | 159 + .../h2/docs/source/negotiating-http2.rst | 103 + .../third_party/h2/docs/source/release-notes.rst | 101 + .../third_party/h2/docs/source/release-process.rst | 56 + .../third_party/h2/docs/source/testimonials.rst | 9 + .../third_party/h2/docs/source/tornado-example.rst | 16 + .../third_party/h2/docs/source/twisted-example.rst | 18 + .../h2/docs/source/twisted-head-example.rst | 17 + .../h2/docs/source/twisted-post-example.rst | 18 + .../third_party/h2/docs/source/wsgi-example.rst | 23 + .../h2/examples/asyncio/asyncio-server.py | 210 + .../tools/third_party/h2/examples/asyncio/cert.crt | 21 + .../tools/third_party/h2/examples/asyncio/cert.key | 27 + .../third_party/h2/examples/asyncio/wsgi-server.py | 760 + .../third_party/h2/examples/curio/curio-server.py | 206 + .../h2/examples/curio/localhost.crt.pem | 21 + .../third_party/h2/examples/curio/localhost.key | 27 + .../h2/examples/eventlet/eventlet-server.py | 102 + .../third_party/h2/examples/eventlet/server.crt | 20 + .../third_party/h2/examples/eventlet/server.key | 27 + .../fragments/client_https_setup_fragment.py | 110 + .../examples/fragments/client_upgrade_fragment.py | 103 + .../fragments/server_https_setup_fragment.py | 112 + .../examples/fragments/server_upgrade_fragment.py | 100 + .../third_party/h2/examples/tornado/server.crt | 20 + .../third_party/h2/examples/tornado/server.key | 27 + .../h2/examples/tornado/tornado-server.py | 92 + .../h2/examples/twisted/head_request.py | 111 + .../h2/examples/twisted/post_request.py | 249 + .../third_party/h2/examples/twisted/server.crt | 20 + .../third_party/h2/examples/twisted/server.csr | 17 + .../third_party/h2/examples/twisted/server.key | 27 + .../h2/examples/twisted/twisted-server.py | 192 + .../tests/tools/third_party/h2/h2/__init__.py | 8 + .../tests/tools/third_party/h2/h2/config.py | 170 + .../tests/tools/third_party/h2/h2/connection.py | 2048 + .../tests/tools/third_party/h2/h2/errors.py | 75 + .../tests/tools/third_party/h2/h2/events.py | 648 + .../tests/tools/third_party/h2/h2/exceptions.py | 186 + .../tests/tools/third_party/h2/h2/frame_buffer.py | 175 + .../tests/tools/third_party/h2/h2/settings.py | 339 + .../tests/tools/third_party/h2/h2/stream.py | 1369 + .../tests/tools/third_party/h2/h2/utilities.py | 660 + .../tests/tools/third_party/h2/h2/windows.py | 139 + .../tests/tools/third_party/h2/setup.cfg | 10 + .../tests/tools/third_party/h2/setup.py | 76 + .../tests/tools/third_party/h2/test/conftest.py | 16 + .../tools/third_party/h2/test/coroutine_tests.py | 74 + .../tests/tools/third_party/h2/test/helpers.py | 176 + .../tools/third_party/h2/test/test_basic_logic.py | 1877 + .../third_party/h2/test/test_closed_streams.py | 555 + .../third_party/h2/test/test_complex_logic.py | 586 + .../tests/tools/third_party/h2/test/test_config.py | 130 + .../tests/tools/third_party/h2/test/test_events.py | 367 + .../tools/third_party/h2/test/test_exceptions.py | 15 + .../h2/test/test_flow_control_window.py | 952 + .../tools/third_party/h2/test/test_h2_upgrade.py | 302 + .../tools/third_party/h2/test/test_head_request.py | 55 + .../third_party/h2/test/test_header_indexing.py | 637 + .../h2/test/test_informational_responses.py | 444 + .../third_party/h2/test/test_interacting_stacks.py | 120 + .../h2/test/test_invalid_content_lengths.py | 136 + .../h2/test/test_invalid_frame_sequences.py | 488 + .../third_party/h2/test/test_invalid_headers.py | 952 + .../tools/third_party/h2/test/test_priority.py | 358 + .../third_party/h2/test/test_related_events.py | 370 + .../tools/third_party/h2/test/test_rfc7838.py | 447 + .../tools/third_party/h2/test/test_settings.py | 470 + .../third_party/h2/test/test_state_machines.py | 163 + .../tools/third_party/h2/test/test_stream_reset.py | 137 + .../third_party/h2/test/test_utility_functions.py | 226 + .../tools/third_party/h2/test_requirements.txt | 5 + .../tests/tools/third_party/h2/tox.ini | 48 + .../tests/tools/third_party/h2/utils/backport.sh | 31 + .../third_party/h2/visualizer/NOTICES.visualizer | 24 + .../tools/third_party/h2/visualizer/visualize.py | 252 + .../tests/tools/third_party/hpack/CONTRIBUTORS.rst | 62 + .../tests/tools/third_party/hpack/HISTORY.rst | 134 + .../tests/tools/third_party/hpack/LICENSE | 21 + .../tests/tools/third_party/hpack/MANIFEST.in | 2 + .../tests/tools/third_party/hpack/PKG-INFO | 199 + .../tests/tools/third_party/hpack/README.rst | 41 + .../tools/third_party/hpack/hpack/__init__.py | 20 + .../tests/tools/third_party/hpack/hpack/compat.py | 42 + .../tools/third_party/hpack/hpack/exceptions.py | 49 + .../tests/tools/third_party/hpack/hpack/hpack.py | 629 + .../tests/tools/third_party/hpack/hpack/huffman.py | 68 + .../third_party/hpack/hpack/huffman_constants.py | 288 + .../tools/third_party/hpack/hpack/huffman_table.py | 4739 ++ .../tests/tools/third_party/hpack/hpack/struct.py | 39 + .../tests/tools/third_party/hpack/hpack/table.py | 215 + .../tests/tools/third_party/hpack/setup.cfg | 12 + .../tests/tools/third_party/hpack/setup.py | 57 + .../third_party/hpack/test/test_encode_decode.py | 141 + .../tools/third_party/hpack/test/test_hpack.py | 828 + .../hpack/test/test_hpack_integration.py | 75 + .../tools/third_party/hpack/test/test_huffman.py | 55 + .../tools/third_party/hpack/test/test_struct.py | 77 + .../tools/third_party/hpack/test/test_table.py | 158 + .../tests/tools/third_party/html5lib/.appveyor.yml | 31 + .../tests/tools/third_party/html5lib/.coveragerc | 8 + .../tests/tools/third_party/html5lib/.gitignore | 85 + .../tools/third_party/html5lib/.prospector.yaml | 21 + .../tests/tools/third_party/html5lib/.pylintrc | 10 + .../tools/third_party/html5lib/.pytest.expect | 1322 + .../tests/tools/third_party/html5lib/.travis.yml | 32 + .../tests/tools/third_party/html5lib/AUTHORS.rst | 66 + .../tests/tools/third_party/html5lib/CHANGES.rst | 359 + .../tools/third_party/html5lib/CONTRIBUTING.rst | 60 + .../tests/tools/third_party/html5lib/LICENSE | 20 + .../tests/tools/third_party/html5lib/MANIFEST.in | 10 + .../tests/tools/third_party/html5lib/README.rst | 151 + .../third_party/html5lib/benchmarks/bench_html.py | 57 + .../third_party/html5lib/benchmarks/bench_wpt.py | 45 + .../third_party/html5lib/benchmarks/data/README.md | 8 + .../third_party/html5lib/benchmarks/data/html.html | 5000 ++ .../html5lib/benchmarks/data/wpt/LICENSE.md | 11 + .../html5lib/benchmarks/data/wpt/README.md | 52 + .../html5lib/benchmarks/data/wpt/random/001.html | 3 + .../data/wpt/random/background-origin-007-ref.html | 18 + .../background_shorthand_css_relative_url.html | 24 + .../wpt/random/beforeunload-on-history-back-1.html | 5 + .../data/wpt/random/euckr-encode-form.html | 52 + .../wpt/random/frame-ancestors-self-allow.html | 16 + .../benchmarks/data/wpt/random/grouping-dl.html | 30 + .../data/wpt/random/heavy-styling-005.html | 15 + .../benchmarks/data/wpt/random/htb-ltr-ltr.html | 74 + .../benchmarks/data/wpt/random/idbindex_get8.htm | 27 + .../benchmarks/data/wpt/random/idlharness.html | 34 + .../data/wpt/random/li-type-unsupported-ref.html | 13 + .../moz-css21-float-page-break-inside-avoid-6.html | 19 + .../wpt/random/shape-outside-content-box-002.html | 66 + .../data/wpt/random/worker-constructor.https.html | 86 + .../2d.composite.image.destination-over.html | 33 + .../data/wpt/weighted/align-content-wrap-002.html | 108 + .../data/wpt/weighted/big5_chars_extra.html | 1 + .../benchmarks/data/wpt/weighted/fetch.http.html | 143 + .../weighted/filter-turbulence-invalid-001.html | 51 + .../data/wpt/weighted/grid-auto-fill-rows-001.html | 184 + ...-orientation-from-image-content-images-ref.html | 86 + .../wpt/weighted/masonry-item-placement-006.html | 149 + .../moz-css21-table-page-break-inside-avoid-2.html | 29 + .../position-sticky-table-th-bottom-ref.html | 62 + .../data/wpt/weighted/pre-float-001.html | 36 + .../benchmarks/data/wpt/weighted/resize-004.html | 20 + .../data/wpt/weighted/test-plan.src.html | 1616 + .../benchmarks/data/wpt/weighted/toBlob.png.html | 17 + .../wpt/weighted/will-change-abspos-cb-001.html | 30 + .../tests/tools/third_party/html5lib/debug-info.py | 37 + .../tests/tools/third_party/html5lib/doc/Makefile | 177 + .../tools/third_party/html5lib/doc/changes.rst | 3 + .../tests/tools/third_party/html5lib/doc/conf.py | 123 + .../third_party/html5lib/doc/html5lib.filters.rst | 58 + .../tools/third_party/html5lib/doc/html5lib.rst | 38 + .../html5lib/doc/html5lib.treeadapters.rst | 20 + .../html5lib/doc/html5lib.treebuilders.rst | 42 + .../html5lib/doc/html5lib.treewalkers.rst | 50 + .../tests/tools/third_party/html5lib/doc/index.rst | 22 + .../tools/third_party/html5lib/doc/license.rst | 4 + .../tests/tools/third_party/html5lib/doc/make.bat | 242 + .../tools/third_party/html5lib/doc/modules.rst | 7 + .../tools/third_party/html5lib/doc/movingparts.rst | 165 + .../tests/tools/third_party/html5lib/flake8-run.sh | 9 + .../third_party/html5lib/html5lib/__init__.py | 35 + .../third_party/html5lib/html5lib/_ihatexml.py | 289 + .../third_party/html5lib/html5lib/_inputstream.py | 918 + .../third_party/html5lib/html5lib/_tokenizer.py | 1735 + .../html5lib/html5lib/_trie/__init__.py | 5 + .../third_party/html5lib/html5lib/_trie/_base.py | 40 + .../third_party/html5lib/html5lib/_trie/py.py | 67 + .../tools/third_party/html5lib/html5lib/_utils.py | 159 + .../third_party/html5lib/html5lib/constants.py | 2946 + .../html5lib/html5lib/filters/__init__.py | 0 .../html5lib/filters/alphabeticalattributes.py | 29 + .../third_party/html5lib/html5lib/filters/base.py | 12 + .../html5lib/filters/inject_meta_charset.py | 73 + .../third_party/html5lib/html5lib/filters/lint.py | 93 + .../html5lib/html5lib/filters/optionaltags.py | 207 + .../html5lib/html5lib/filters/sanitizer.py | 916 + .../html5lib/html5lib/filters/whitespace.py | 38 + .../third_party/html5lib/html5lib/html5parser.py | 2795 + .../third_party/html5lib/html5lib/serializer.py | 409 + .../html5lib/html5lib/tests/__init__.py | 1 + .../html5lib/html5lib/tests/conftest.py | 108 + .../html5lib/tests/sanitizer-testdata/tests1.dat | 433 + .../html5lib/html5lib/tests/sanitizer.py | 51 + .../html5lib/tests/serializer-testdata/core.test | 395 + .../tests/serializer-testdata/injectmeta.test | 350 + .../tests/serializer-testdata/optionaltags.test | 3254 ++ .../tests/serializer-testdata/options.test | 334 + .../tests/serializer-testdata/whitespace.test | 198 + .../third_party/html5lib/html5lib/tests/support.py | 199 + .../html5lib/tests/test_alphabeticalattributes.py | 78 + .../html5lib/html5lib/tests/test_encoding.py | 117 + .../html5lib/html5lib/tests/test_meta.py | 41 + .../html5lib/tests/test_optionaltags_filter.py | 7 + .../html5lib/html5lib/tests/test_parser2.py | 94 + .../html5lib/html5lib/tests/test_sanitizer.py | 133 + .../html5lib/html5lib/tests/test_serializer.py | 226 + .../html5lib/html5lib/tests/test_stream.py | 325 + .../html5lib/html5lib/tests/test_tokenizer2.py | 66 + .../html5lib/html5lib/tests/test_treeadapters.py | 40 + .../html5lib/html5lib/tests/test_treewalkers.py | 205 + .../html5lib/tests/test_whitespace_filter.py | 125 + .../html5lib/html5lib/tests/tokenizer.py | 253 + .../html5lib/html5lib/tests/tokenizertotree.py | 69 + .../html5lib/html5lib/tests/tree_construction.py | 205 + .../html5lib/html5lib/tests/us-ascii.html | 3 + .../html5lib/html5lib/tests/utf-8-bom.html | 3 + .../html5lib/html5lib/treeadapters/__init__.py | 30 + .../html5lib/html5lib/treeadapters/genshi.py | 54 + .../html5lib/html5lib/treeadapters/sax.py | 50 + .../html5lib/html5lib/treebuilders/__init__.py | 88 + .../html5lib/html5lib/treebuilders/base.py | 417 + .../html5lib/html5lib/treebuilders/dom.py | 239 + .../html5lib/html5lib/treebuilders/etree.py | 343 + .../html5lib/html5lib/treebuilders/etree_lxml.py | 392 + .../html5lib/html5lib/treewalkers/__init__.py | 154 + .../html5lib/html5lib/treewalkers/base.py | 252 + .../html5lib/html5lib/treewalkers/dom.py | 43 + .../html5lib/html5lib/treewalkers/etree.py | 131 + .../html5lib/html5lib/treewalkers/etree_lxml.py | 215 + .../html5lib/html5lib/treewalkers/genshi.py | 69 + .../tests/tools/third_party/html5lib/parse.py | 236 + .../tests/tools/third_party/html5lib/pytest.ini | 17 + .../third_party/html5lib/requirements-install.sh | 15 + .../third_party/html5lib/requirements-optional.txt | 13 + .../third_party/html5lib/requirements-test.txt | 10 + .../tools/third_party/html5lib/requirements.txt | 2 + .../tests/tools/third_party/html5lib/setup.cfg | 11 + .../tests/tools/third_party/html5lib/setup.py | 127 + .../tests/tools/third_party/html5lib/tox.ini | 20 + .../tools/third_party/html5lib/utils/entities.py | 101 + .../tools/third_party/hyperframe/CONTRIBUTORS.rst | 56 + .../tests/tools/third_party/hyperframe/HISTORY.rst | 179 + .../tests/tools/third_party/hyperframe/LICENSE | 21 + .../tests/tools/third_party/hyperframe/MANIFEST.in | 2 + .../tests/tools/third_party/hyperframe/PKG-INFO | 242 + .../tests/tools/third_party/hyperframe/README.rst | 39 + .../third_party/hyperframe/hyperframe/__init__.py | 8 + .../hyperframe/hyperframe/exceptions.py | 41 + .../third_party/hyperframe/hyperframe/flags.py | 50 + .../third_party/hyperframe/hyperframe/frame.py | 822 + .../tests/tools/third_party/hyperframe/setup.cfg | 10 + .../tests/tools/third_party/hyperframe/setup.py | 59 + .../third_party/hyperframe/test/test_flags.py | 35 + .../third_party/hyperframe/test/test_frames.py | 791 + .../importlib_metadata/.github/workflows/main.yml | 126 + .../third_party/importlib_metadata/.gitignore | 13 + .../importlib_metadata/.readthedocs.yml | 5 + .../tools/third_party/importlib_metadata/LICENSE | 13 + .../third_party/importlib_metadata/MANIFEST.in | 5 + .../third_party/importlib_metadata/README.rst | 42 + .../third_party/importlib_metadata/codecov.yml | 2 + .../third_party/importlib_metadata/coverage.ini | 24 + .../third_party/importlib_metadata/coverplug.py | 21 + .../importlib_metadata/docs/__init__.py | 0 .../importlib_metadata/docs/changelog.rst | 314 + .../third_party/importlib_metadata/docs/conf.py | 185 + .../third_party/importlib_metadata/docs/index.rst | 52 + .../third_party/importlib_metadata/docs/using.rst | 260 + .../importlib_metadata/__init__.py | 627 + .../importlib_metadata/_compat.py | 152 + .../prepare/example/example/__init__.py | 2 + .../importlib_metadata/prepare/example/setup.py | 10 + .../third_party/importlib_metadata/pyproject.toml | 2 + .../tools/third_party/importlib_metadata/setup.cfg | 47 + .../tools/third_party/importlib_metadata/setup.py | 3 + .../importlib_metadata/tests/__init__.py | 0 .../importlib_metadata/tests/data/__init__.py | 0 .../tests/data/example-21.12-py3-none-any.whl | Bin 0 -> 1455 bytes .../tests/data/example-21.12-py3.6.egg | Bin 0 -> 1497 bytes .../importlib_metadata/tests/fixtures.py | 263 + .../importlib_metadata/tests/py39compat.py | 4 + .../importlib_metadata/tests/test_api.py | 196 + .../importlib_metadata/tests/test_integration.py | 54 + .../importlib_metadata/tests/test_main.py | 285 + .../importlib_metadata/tests/test_zip.py | 80 + .../tools/third_party/importlib_metadata/tox.ini | 97 + .../tests/tools/third_party/iniconfig/.gitignore | 8 + .../tools/third_party/iniconfig/.landscape.yml | 5 + .../tests/tools/third_party/iniconfig/.travis.yml | 18 + .../tests/tools/third_party/iniconfig/CHANGELOG | 32 + .../tests/tools/third_party/iniconfig/LICENSE | 19 + .../tests/tools/third_party/iniconfig/MANIFEST.in | 5 + .../tests/tools/third_party/iniconfig/README.txt | 51 + .../tests/tools/third_party/iniconfig/example.ini | 10 + .../tools/third_party/iniconfig/pyproject.toml | 5 + .../tests/tools/third_party/iniconfig/setup.cfg | 2 + .../tests/tools/third_party/iniconfig/setup.py | 46 + .../iniconfig/src/iniconfig/__init__.py | 165 + .../iniconfig/src/iniconfig/__init__.pyi | 31 + .../third_party/iniconfig/src/iniconfig/py.typed | 0 .../third_party/iniconfig/testing/conftest.py | 2 + .../iniconfig/testing/test_iniconfig.py | 314 + .../tests/tools/third_party/iniconfig/tox.ini | 14 + .../tools/third_party/more-itertools/.gitignore | 34 + .../tools/third_party/more-itertools/.travis.yml | 26 + .../tests/tools/third_party/more-itertools/LICENSE | 19 + .../tools/third_party/more-itertools/MANIFEST.in | 8 + .../tools/third_party/more-itertools/README.rst | 59 + .../tools/third_party/more-itertools/docs/Makefile | 153 + .../tools/third_party/more-itertools/docs/api.rst | 234 + .../tools/third_party/more-itertools/docs/conf.py | 244 + .../third_party/more-itertools/docs/index.rst | 16 + .../third_party/more-itertools/docs/license.rst | 16 + .../tools/third_party/more-itertools/docs/make.bat | 190 + .../third_party/more-itertools/docs/testing.rst | 19 + .../third_party/more-itertools/docs/versions.rst | 237 + .../more-itertools/more_itertools/__init__.py | 2 + .../more-itertools/more_itertools/more.py | 2068 + .../more-itertools/more_itertools/recipes.py | 565 + .../more_itertools/tests/__init__.py | 0 .../more_itertools/tests/test_more.py | 1848 + .../more_itertools/tests/test_recipes.py | 607 + .../tools/third_party/more-itertools/setup.cfg | 3 + .../tools/third_party/more-itertools/setup.py | 59 + .../tests/tools/third_party/more-itertools/tox.ini | 5 + .../tests/tools/third_party/packaging/.coveragerc | 9 + .../tests/tools/third_party/packaging/.flake8 | 3 + .../packaging/.github/workflows/docs.yml | 30 + .../packaging/.github/workflows/lint.yml | 59 + .../packaging/.github/workflows/test.yml | 56 + .../tests/tools/third_party/packaging/.gitignore | 18 + .../third_party/packaging/.pre-commit-config.yaml | 39 + .../tools/third_party/packaging/.readthedocs.yml | 15 + .../tools/third_party/packaging/CHANGELOG.rst | 347 + .../tools/third_party/packaging/CONTRIBUTING.rst | 23 + .../tests/tools/third_party/packaging/LICENSE | 3 + .../tools/third_party/packaging/LICENSE.APACHE | 177 + .../tests/tools/third_party/packaging/LICENSE.BSD | 23 + .../tests/tools/third_party/packaging/MANIFEST.in | 24 + .../tests/tools/third_party/packaging/README.rst | 73 + .../tools/third_party/packaging/docs/Makefile | 153 + .../third_party/packaging/docs/_static/.empty | 0 .../tools/third_party/packaging/docs/changelog.rst | 1 + .../tests/tools/third_party/packaging/docs/conf.py | 111 + .../packaging/docs/development/getting-started.rst | 77 + .../packaging/docs/development/index.rst | 19 + .../packaging/docs/development/release-process.rst | 25 + .../docs/development/reviewing-patches.rst | 37 + .../docs/development/submitting-patches.rst | 74 + .../tools/third_party/packaging/docs/index.rst | 38 + .../tools/third_party/packaging/docs/markers.rst | 93 + .../third_party/packaging/docs/requirements.rst | 89 + .../third_party/packaging/docs/requirements.txt | 1 + .../tools/third_party/packaging/docs/security.rst | 18 + .../third_party/packaging/docs/specifiers.rst | 222 + .../tools/third_party/packaging/docs/tags.rst | 225 + .../tools/third_party/packaging/docs/utils.rst | 92 + .../tools/third_party/packaging/docs/version.rst | 292 + .../tests/tools/third_party/packaging/mypy.ini | 17 + .../tests/tools/third_party/packaging/noxfile.py | 321 + .../third_party/packaging/packaging/__about__.py | 26 + .../third_party/packaging/packaging/__init__.py | 25 + .../third_party/packaging/packaging/_manylinux.py | 301 + .../third_party/packaging/packaging/_musllinux.py | 136 + .../third_party/packaging/packaging/_structures.py | 61 + .../third_party/packaging/packaging/markers.py | 304 + .../tools/third_party/packaging/packaging/py.typed | 0 .../packaging/packaging/requirements.py | 146 + .../third_party/packaging/packaging/specifiers.py | 802 + .../tools/third_party/packaging/packaging/tags.py | 487 + .../tools/third_party/packaging/packaging/utils.py | 136 + .../third_party/packaging/packaging/version.py | 504 + .../tools/third_party/packaging/pyproject.toml | 3 + .../tests/tools/third_party/packaging/setup.cfg | 3 + .../tests/tools/third_party/packaging/setup.py | 70 + .../tools/third_party/packaging/tasks/__init__.py | 9 + .../tools/third_party/packaging/tasks/check.py | 141 + .../tools/third_party/packaging/tasks/paths.py | 9 + .../third_party/packaging/tasks/requirements.txt | 3 + .../tools/third_party/packaging/tests/__init__.py | 3 + .../third_party/packaging/tests/hello-world.c | 7 + .../third_party/packaging/tests/manylinux/build.sh | 39 + .../tests/manylinux/hello-world-armv7l-armel | Bin 0 -> 52 bytes .../tests/manylinux/hello-world-armv7l-armhf | Bin 0 -> 52 bytes .../tests/manylinux/hello-world-invalid-class | Bin 0 -> 52 bytes .../tests/manylinux/hello-world-invalid-data | Bin 0 -> 52 bytes .../tests/manylinux/hello-world-invalid-magic | Bin 0 -> 52 bytes .../tests/manylinux/hello-world-s390x-s390x | Bin 0 -> 64 bytes .../tests/manylinux/hello-world-too-short | Bin 0 -> 40 bytes .../tests/manylinux/hello-world-x86_64-amd64 | Bin 0 -> 64 bytes .../tests/manylinux/hello-world-x86_64-i386 | Bin 0 -> 52 bytes .../tests/manylinux/hello-world-x86_64-x32 | Bin 0 -> 52 bytes .../third_party/packaging/tests/musllinux/build.sh | 61 + .../third_party/packaging/tests/test_manylinux.py | 253 + .../third_party/packaging/tests/test_markers.py | 310 + .../third_party/packaging/tests/test_musllinux.py | 146 + .../packaging/tests/test_requirements.py | 197 + .../third_party/packaging/tests/test_specifiers.py | 998 + .../third_party/packaging/tests/test_structures.py | 59 + .../tools/third_party/packaging/tests/test_tags.py | 1191 + .../third_party/packaging/tests/test_utils.py | 124 + .../third_party/packaging/tests/test_version.py | 904 + .../tests/tools/third_party/pathlib2/.gitignore | 54 + .../tests/tools/third_party/pathlib2/.travis.yml | 47 + .../tests/tools/third_party/pathlib2/CHANGELOG.rst | 163 + .../tests/tools/third_party/pathlib2/LICENSE.rst | 23 + .../tests/tools/third_party/pathlib2/MANIFEST.in | 10 + .../tests/tools/third_party/pathlib2/README.rst | 66 + .../tests/tools/third_party/pathlib2/VERSION | 1 + .../tests/tools/third_party/pathlib2/appveyor.yml | 30 + .../third_party/pathlib2/appveyor/install.ps1 | 44 + .../tests/tools/third_party/pathlib2/codecov.yml | 1 + .../third_party/pathlib2/pathlib2/__init__.py | 1809 + .../tools/third_party/pathlib2/requirements.txt | 3 + .../tests/tools/third_party/pathlib2/setup.cfg | 8 + .../tests/tools/third_party/pathlib2/setup.py | 48 + .../third_party/pathlib2/tests/test_pathlib2.py | 2406 + .../tests/tools/third_party/pdf_js/LICENSE | 177 + .../tests/tools/third_party/pdf_js/pdf.js | 24624 ++++++++ .../tests/tools/third_party/pdf_js/pdf.worker.js | 56199 +++++++++++++++++++ .../tests/tools/third_party/pluggy/.coveragerc | 14 + .../third_party/pluggy/.github/workflows/main.yml | 148 + .../tests/tools/third_party/pluggy/.gitignore | 64 + .../third_party/pluggy/.pre-commit-config.yaml | 34 + .../tests/tools/third_party/pluggy/CHANGELOG.rst | 409 + .../tests/tools/third_party/pluggy/LICENSE | 21 + .../tests/tools/third_party/pluggy/MANIFEST.in | 7 + .../tests/tools/third_party/pluggy/README.rst | 101 + .../tests/tools/third_party/pluggy/RELEASING.rst | 23 + .../tools/third_party/pluggy/changelog/README.rst | 32 + .../third_party/pluggy/changelog/_template.rst | 40 + .../tests/tools/third_party/pluggy/codecov.yml | 7 + .../third_party/pluggy/docs/_static/img/plug.png | Bin 0 -> 9350 bytes .../third_party/pluggy/docs/api_reference.rst | 19 + .../tools/third_party/pluggy/docs/changelog.rst | 1 + .../tests/tools/third_party/pluggy/docs/conf.py | 87 + .../docs/examples/eggsample-spam/eggsample_spam.py | 22 + .../pluggy/docs/examples/eggsample-spam/setup.py | 8 + .../docs/examples/eggsample/eggsample/__init__.py | 4 + .../docs/examples/eggsample/eggsample/hookspecs.py | 21 + .../docs/examples/eggsample/eggsample/host.py | 57 + .../docs/examples/eggsample/eggsample/lib.py | 14 + .../pluggy/docs/examples/eggsample/setup.py | 8 + .../pluggy/docs/examples/toy-example.py | 41 + .../tests/tools/third_party/pluggy/docs/index.rst | 957 + .../tests/tools/third_party/pluggy/pyproject.toml | 47 + .../tools/third_party/pluggy/scripts/release.py | 69 + .../third_party/pluggy/scripts/upload-coverage.sh | 16 + .../tests/tools/third_party/pluggy/setup.cfg | 52 + .../tests/tools/third_party/pluggy/setup.py | 5 + .../third_party/pluggy/src/pluggy/__init__.py | 18 + .../third_party/pluggy/src/pluggy/_callers.py | 60 + .../tools/third_party/pluggy/src/pluggy/_hooks.py | 325 + .../third_party/pluggy/src/pluggy/_manager.py | 373 + .../tools/third_party/pluggy/src/pluggy/_result.py | 60 + .../third_party/pluggy/src/pluggy/_tracing.py | 62 + .../tools/third_party/pluggy/testing/benchmark.py | 102 + .../tools/third_party/pluggy/testing/conftest.py | 26 + .../third_party/pluggy/testing/test_details.py | 135 + .../third_party/pluggy/testing/test_helpers.py | 84 + .../third_party/pluggy/testing/test_hookcaller.py | 272 + .../third_party/pluggy/testing/test_invocations.py | 215 + .../third_party/pluggy/testing/test_multicall.py | 147 + .../pluggy/testing/test_pluginmanager.py | 544 + .../third_party/pluggy/testing/test_tracer.py | 78 + .../tests/tools/third_party/pluggy/tox.ini | 57 + .../tests/tools/third_party/py/.flake8 | 4 + .../third_party/py/.github/workflows/main.yml | 66 + .../tests/tools/third_party/py/.gitignore | 15 + .../tests/tools/third_party/py/AUTHORS | 25 + .../tests/tools/third_party/py/CHANGELOG.rst | 1236 + .../tests/tools/third_party/py/LICENSE | 19 + .../tests/tools/third_party/py/MANIFEST.in | 11 + .../tests/tools/third_party/py/README.rst | 31 + .../tests/tools/third_party/py/RELEASING.rst | 17 + .../tests/tools/third_party/py/bench/localpath.py | 73 + .../tests/tools/third_party/py/codecov.yml | 7 + .../tests/tools/third_party/py/conftest.py | 60 + .../tests/tools/third_party/py/doc/Makefile | 133 + .../third_party/py/doc/_templates/layout.html | 18 + .../third_party/py/doc/announce/release-0.9.0.txt | 7 + .../third_party/py/doc/announce/release-0.9.2.txt | 27 + .../third_party/py/doc/announce/release-1.0.0.txt | 63 + .../third_party/py/doc/announce/release-1.0.1.txt | 48 + .../third_party/py/doc/announce/release-1.0.2.txt | 5 + .../third_party/py/doc/announce/release-1.1.0.txt | 115 + .../third_party/py/doc/announce/release-1.1.1.txt | 48 + .../third_party/py/doc/announce/release-1.2.0.txt | 116 + .../third_party/py/doc/announce/release-1.2.1.txt | 66 + .../third_party/py/doc/announce/release-1.3.0.txt | 580 + .../third_party/py/doc/announce/release-1.3.1.txt | 104 + .../third_party/py/doc/announce/release-1.3.2.txt | 720 + .../third_party/py/doc/announce/release-1.3.3.txt | 26 + .../third_party/py/doc/announce/release-1.3.4.txt | 22 + .../third_party/py/doc/announce/release-1.4.0.txt | 47 + .../third_party/py/doc/announce/release-1.4.1.txt | 47 + .../tools/third_party/py/doc/announce/releases.txt | 16 + .../tests/tools/third_party/py/doc/changelog.txt | 3 + .../tests/tools/third_party/py/doc/code.txt | 150 + .../tests/tools/third_party/py/doc/conf.py | 263 + .../tests/tools/third_party/py/doc/download.html | 18 + .../tools/third_party/py/doc/example/genhtml.py | 13 + .../tools/third_party/py/doc/example/genhtmlcss.py | 23 + .../tools/third_party/py/doc/example/genxml.py | 17 + .../tests/tools/third_party/py/doc/faq.txt | 170 + .../tests/tools/third_party/py/doc/img/pylib.png | Bin 0 -> 8276 bytes .../tests/tools/third_party/py/doc/index.txt | 39 + .../tests/tools/third_party/py/doc/install.txt | 91 + .../tests/tools/third_party/py/doc/io.txt | 59 + .../tests/tools/third_party/py/doc/links.inc | 15 + .../tests/tools/third_party/py/doc/log.txt | 208 + .../tests/tools/third_party/py/doc/misc.txt | 93 + .../tests/tools/third_party/py/doc/path.txt | 264 + .../tests/tools/third_party/py/doc/style.css | 1044 + .../tests/tools/third_party/py/doc/xml.txt | 164 + .../tests/tools/third_party/py/py/__init__.py | 156 + .../tests/tools/third_party/py/py/__init__.pyi | 20 + .../tests/tools/third_party/py/py/__metainfo.py | 2 + .../tests/tools/third_party/py/py/_builtin.py | 149 + .../tools/third_party/py/py/_code/__init__.py | 1 + .../tools/third_party/py/py/_code/_assertionnew.py | 322 + .../tools/third_party/py/py/_code/_assertionold.py | 556 + .../tools/third_party/py/py/_code/_py2traceback.py | 79 + .../tools/third_party/py/py/_code/assertion.py | 90 + .../tests/tools/third_party/py/py/_code/code.py | 796 + .../tests/tools/third_party/py/py/_code/source.py | 410 + .../tests/tools/third_party/py/py/_error.py | 91 + .../tests/tools/third_party/py/py/_io/__init__.py | 1 + .../tests/tools/third_party/py/py/_io/capture.py | 371 + .../tests/tools/third_party/py/py/_io/saferepr.py | 71 + .../tools/third_party/py/py/_io/terminalwriter.py | 423 + .../tests/tools/third_party/py/py/_log/__init__.py | 2 + .../tests/tools/third_party/py/py/_log/log.py | 206 + .../tests/tools/third_party/py/py/_log/warning.py | 79 + .../tools/third_party/py/py/_path/__init__.py | 1 + .../tools/third_party/py/py/_path/cacheutil.py | 114 + .../tests/tools/third_party/py/py/_path/common.py | 459 + .../tests/tools/third_party/py/py/_path/local.py | 1030 + .../tests/tools/third_party/py/py/_path/svnurl.py | 380 + .../tests/tools/third_party/py/py/_path/svnwc.py | 1240 + .../tools/third_party/py/py/_process/__init__.py | 1 + .../tools/third_party/py/py/_process/cmdexec.py | 49 + .../tools/third_party/py/py/_process/forkedfunc.py | 120 + .../tools/third_party/py/py/_process/killproc.py | 23 + .../tests/tools/third_party/py/py/_std.py | 27 + .../py/py/_vendored_packages/__init__.py | 0 .../apipkg-2.0.0.dist-info/INSTALLER | 1 + .../apipkg-2.0.0.dist-info/LICENSE | 18 + .../apipkg-2.0.0.dist-info/METADATA | 125 + .../apipkg-2.0.0.dist-info/RECORD | 11 + .../apipkg-2.0.0.dist-info/REQUESTED | 0 .../apipkg-2.0.0.dist-info/WHEEL | 6 + .../apipkg-2.0.0.dist-info/top_level.txt | 1 + .../py/py/_vendored_packages/apipkg/__init__.py | 217 + .../py/py/_vendored_packages/apipkg/version.py | 5 + .../iniconfig-1.1.1.dist-info/INSTALLER | 1 + .../iniconfig-1.1.1.dist-info/LICENSE | 19 + .../iniconfig-1.1.1.dist-info/METADATA | 78 + .../iniconfig-1.1.1.dist-info/RECORD | 11 + .../iniconfig-1.1.1.dist-info/REQUESTED | 0 .../iniconfig-1.1.1.dist-info/WHEEL | 6 + .../iniconfig-1.1.1.dist-info/top_level.txt | 1 + .../py/py/_vendored_packages/iniconfig/__init__.py | 165 + .../py/_vendored_packages/iniconfig/__init__.pyi | 31 + .../py/py/_vendored_packages/iniconfig/py.typed | 0 .../tests/tools/third_party/py/py/_xmlgen.py | 255 + .../tests/tools/third_party/py/py/error.pyi | 129 + .../tests/tools/third_party/py/py/iniconfig.pyi | 31 + .../tests/tools/third_party/py/py/io.pyi | 130 + .../tests/tools/third_party/py/py/path.pyi | 197 + .../tests/tools/third_party/py/py/py.typed | 0 .../tests/tools/third_party/py/py/test.py | 10 + .../tests/tools/third_party/py/py/xml.pyi | 25 + .../tests/tools/third_party/py/pyproject.toml | 6 + .../tests/tools/third_party/py/setup.cfg | 8 + .../tests/tools/third_party/py/setup.py | 48 + .../tests/tools/third_party/py/tasks/__init__.py | 0 .../tests/tools/third_party/py/tasks/vendoring.py | 41 + .../third_party/py/testing/code/test_assertion.py | 305 + .../tools/third_party/py/testing/code/test_code.py | 159 + .../third_party/py/testing/code/test_excinfo.py | 956 + .../third_party/py/testing/code/test_source.py | 656 + .../tests/tools/third_party/py/testing/conftest.py | 3 + .../tools/third_party/py/testing/io_/__init__.py | 1 + .../third_party/py/testing/io_/test_capture.py | 501 + .../third_party/py/testing/io_/test_saferepr.py | 75 + .../py/testing/io_/test_terminalwriter.py | 341 + .../testing/io_/test_terminalwriter_linewidth.py | 56 + .../tools/third_party/py/testing/log/__init__.py | 0 .../tools/third_party/py/testing/log/test_log.py | 191 + .../third_party/py/testing/log/test_warning.py | 85 + .../tools/third_party/py/testing/path/common.py | 492 + .../tools/third_party/py/testing/path/conftest.py | 80 + .../third_party/py/testing/path/repotest.dump | 228 + .../third_party/py/testing/path/svntestbase.py | 31 + .../third_party/py/testing/path/test_cacheutil.py | 89 + .../third_party/py/testing/path/test_local.py | 1078 + .../third_party/py/testing/path/test_svnauth.py | 460 + .../third_party/py/testing/path/test_svnurl.py | 95 + .../third_party/py/testing/path/test_svnwc.py | 557 + .../third_party/py/testing/process/__init__.py | 1 + .../third_party/py/testing/process/test_cmdexec.py | 41 + .../py/testing/process/test_forkedfunc.py | 173 + .../py/testing/process/test_killproc.py | 18 + .../tools/third_party/py/testing/root/__init__.py | 1 + .../third_party/py/testing/root/test_builtin.py | 156 + .../third_party/py/testing/root/test_error.py | 76 + .../third_party/py/testing/root/test_py_imports.py | 71 + .../tools/third_party/py/testing/root/test_std.py | 13 + .../third_party/py/testing/root/test_xmlgen.py | 146 + .../tests/tools/third_party/py/tox.ini | 44 + .../tests/tools/third_party/pytest-asyncio/LICENSE | 202 + .../tools/third_party/pytest-asyncio/PKG-INFO | 302 + .../tools/third_party/pytest-asyncio/README.rst | 281 + .../pytest_asyncio.egg-info/PKG-INFO | 302 + .../pytest_asyncio.egg-info/SOURCES.txt | 12 + .../pytest_asyncio.egg-info/dependency_links.txt | 1 + .../pytest_asyncio.egg-info/entry_points.txt | 3 + .../pytest_asyncio.egg-info/requires.txt | 9 + .../pytest_asyncio.egg-info/top_level.txt | 1 + .../pytest-asyncio/pytest_asyncio/__init__.py | 2 + .../pytest-asyncio/pytest_asyncio/plugin.py | 240 + .../tools/third_party/pytest-asyncio/setup.cfg | 18 + .../tools/third_party/pytest-asyncio/setup.py | 54 + .../tests/tools/third_party/pytest/.coveragerc | 31 + .../tests/tools/third_party/pytest/.gitblameignore | 28 + .../tools/third_party/pytest/.github/FUNDING.yml | 5 + .../pytest/.github/ISSUE_TEMPLATE/1_bug_report.md | 16 + .../.github/ISSUE_TEMPLATE/2_feature_request.md | 25 + .../pytest/.github/ISSUE_TEMPLATE/config.yml | 5 + .../pytest/.github/PULL_REQUEST_TEMPLATE.md | 26 + .../tools/third_party/pytest/.github/config.yml | 2 + .../third_party/pytest/.github/dependabot.yml | 11 + .../tools/third_party/pytest/.github/labels.toml | 149 + .../third_party/pytest/.github/workflows/main.yml | 231 + .../.github/workflows/prepare-release-pr.yml | 52 + .../.github/workflows/update-plugin-list.yml | 49 + .../tests/tools/third_party/pytest/.gitignore | 58 + .../third_party/pytest/.pre-commit-config.yaml | 99 + .../tools/third_party/pytest/.readthedocs.yml | 19 + .../tests/tools/third_party/pytest/AUTHORS | 356 + .../tests/tools/third_party/pytest/CHANGELOG.rst | 7 + .../tests/tools/third_party/pytest/CITATION | 16 + .../tools/third_party/pytest/CODE_OF_CONDUCT.md | 83 + .../tools/third_party/pytest/CONTRIBUTING.rst | 481 + .../tests/tools/third_party/pytest/LICENSE | 21 + .../tools/third_party/pytest/OPENCOLLECTIVE.rst | 44 + .../tests/tools/third_party/pytest/README.rst | 167 + .../tests/tools/third_party/pytest/RELEASING.rst | 173 + .../tests/tools/third_party/pytest/TIDELIFT.rst | 60 + .../tests/tools/third_party/pytest/bench/bench.py | 13 + .../third_party/pytest/bench/bench_argcomplete.py | 19 + .../tests/tools/third_party/pytest/bench/empty.py | 2 + .../tools/third_party/pytest/bench/manyparam.py | 14 + .../tests/tools/third_party/pytest/bench/skip.py | 9 + .../tools/third_party/pytest/bench/unit_test.py | 13 + .../tests/tools/third_party/pytest/bench/xunit.py | 11 + .../tools/third_party/pytest/changelog/README.rst | 37 + .../third_party/pytest/changelog/_template.rst | 40 + .../tests/tools/third_party/pytest/codecov.yml | 6 + .../tests/tools/third_party/pytest/doc/en/Makefile | 43 + .../pytest/doc/en/_templates/globaltoc.html | 34 + .../pytest/doc/en/_templates/layout.html | 52 + .../pytest/doc/en/_templates/links.html | 7 + .../pytest/doc/en/_templates/relations.html | 19 + .../pytest/doc/en/_templates/sidebarintro.html | 5 + .../pytest/doc/en/_templates/slim_searchbox.html | 15 + .../tools/third_party/pytest/doc/en/adopt.rst | 78 + .../third_party/pytest/doc/en/announce/index.rst | 154 + .../pytest/doc/en/announce/release-2.0.0.rst | 129 + .../pytest/doc/en/announce/release-2.0.1.rst | 67 + .../pytest/doc/en/announce/release-2.0.2.rst | 73 + .../pytest/doc/en/announce/release-2.0.3.rst | 39 + .../pytest/doc/en/announce/release-2.1.0.rst | 47 + .../pytest/doc/en/announce/release-2.1.1.rst | 36 + .../pytest/doc/en/announce/release-2.1.2.rst | 32 + .../pytest/doc/en/announce/release-2.1.3.rst | 32 + .../pytest/doc/en/announce/release-2.2.0.rst | 95 + .../pytest/doc/en/announce/release-2.2.1.rst | 41 + .../pytest/doc/en/announce/release-2.2.2.rst | 43 + .../pytest/doc/en/announce/release-2.2.4.rst | 38 + .../pytest/doc/en/announce/release-2.3.0.rst | 133 + .../pytest/doc/en/announce/release-2.3.1.rst | 39 + .../pytest/doc/en/announce/release-2.3.2.rst | 57 + .../pytest/doc/en/announce/release-2.3.3.rst | 61 + .../pytest/doc/en/announce/release-2.3.4.rst | 39 + .../pytest/doc/en/announce/release-2.3.5.rst | 96 + .../pytest/doc/en/announce/release-2.4.0.rst | 223 + .../pytest/doc/en/announce/release-2.4.1.rst | 25 + .../pytest/doc/en/announce/release-2.4.2.rst | 39 + .../pytest/doc/en/announce/release-2.5.0.rst | 174 + .../pytest/doc/en/announce/release-2.5.1.rst | 46 + .../pytest/doc/en/announce/release-2.5.2.rst | 63 + .../pytest/doc/en/announce/release-2.6.0.rst | 153 + .../pytest/doc/en/announce/release-2.6.1.rst | 58 + .../pytest/doc/en/announce/release-2.6.2.rst | 51 + .../pytest/doc/en/announce/release-2.6.3.rst | 51 + .../pytest/doc/en/announce/release-2.7.0.rst | 100 + .../pytest/doc/en/announce/release-2.7.1.rst | 58 + .../pytest/doc/en/announce/release-2.7.2.rst | 57 + .../pytest/doc/en/announce/release-2.8.2.rst | 44 + .../pytest/doc/en/announce/release-2.8.3.rst | 58 + .../pytest/doc/en/announce/release-2.8.4.rst | 52 + .../pytest/doc/en/announce/release-2.8.5.rst | 39 + .../pytest/doc/en/announce/release-2.8.6.rst | 67 + .../pytest/doc/en/announce/release-2.8.7.rst | 31 + .../pytest/doc/en/announce/release-2.9.0.rst | 134 + .../pytest/doc/en/announce/release-2.9.1.rst | 57 + .../pytest/doc/en/announce/release-2.9.2.rst | 65 + .../pytest/doc/en/announce/release-3.0.0.rst | 82 + .../pytest/doc/en/announce/release-3.0.1.rst | 26 + .../pytest/doc/en/announce/release-3.0.2.rst | 24 + .../pytest/doc/en/announce/release-3.0.3.rst | 27 + .../pytest/doc/en/announce/release-3.0.4.rst | 29 + .../pytest/doc/en/announce/release-3.0.5.rst | 27 + .../pytest/doc/en/announce/release-3.0.6.rst | 33 + .../pytest/doc/en/announce/release-3.0.7.rst | 33 + .../pytest/doc/en/announce/release-3.1.0.rst | 61 + .../pytest/doc/en/announce/release-3.1.1.rst | 23 + .../pytest/doc/en/announce/release-3.1.2.rst | 23 + .../pytest/doc/en/announce/release-3.1.3.rst | 23 + .../pytest/doc/en/announce/release-3.10.0.rst | 43 + .../pytest/doc/en/announce/release-3.10.1.rst | 24 + .../pytest/doc/en/announce/release-3.2.0.rst | 48 + .../pytest/doc/en/announce/release-3.2.1.rst | 22 + .../pytest/doc/en/announce/release-3.2.2.rst | 28 + .../pytest/doc/en/announce/release-3.2.3.rst | 23 + .../pytest/doc/en/announce/release-3.2.4.rst | 36 + .../pytest/doc/en/announce/release-3.2.5.rst | 18 + .../pytest/doc/en/announce/release-3.3.0.rst | 50 + .../pytest/doc/en/announce/release-3.3.1.rst | 25 + .../pytest/doc/en/announce/release-3.3.2.rst | 28 + .../pytest/doc/en/announce/release-3.4.0.rst | 52 + .../pytest/doc/en/announce/release-3.4.1.rst | 27 + .../pytest/doc/en/announce/release-3.4.2.rst | 28 + .../pytest/doc/en/announce/release-3.5.0.rst | 51 + .../pytest/doc/en/announce/release-3.5.1.rst | 30 + .../pytest/doc/en/announce/release-3.6.0.rst | 41 + .../pytest/doc/en/announce/release-3.6.1.rst | 24 + .../pytest/doc/en/announce/release-3.6.2.rst | 29 + .../pytest/doc/en/announce/release-3.6.3.rst | 27 + .../pytest/doc/en/announce/release-3.6.4.rst | 24 + .../pytest/doc/en/announce/release-3.7.0.rst | 41 + .../pytest/doc/en/announce/release-3.7.1.rst | 21 + .../pytest/doc/en/announce/release-3.7.2.rst | 25 + .../pytest/doc/en/announce/release-3.7.3.rst | 32 + .../pytest/doc/en/announce/release-3.7.4.rst | 22 + .../pytest/doc/en/announce/release-3.8.0.rst | 38 + .../pytest/doc/en/announce/release-3.8.1.rst | 25 + .../pytest/doc/en/announce/release-3.8.2.rst | 28 + .../pytest/doc/en/announce/release-3.9.0.rst | 43 + .../pytest/doc/en/announce/release-3.9.1.rst | 20 + .../pytest/doc/en/announce/release-3.9.2.rst | 23 + .../pytest/doc/en/announce/release-3.9.3.rst | 24 + .../pytest/doc/en/announce/release-4.0.0.rst | 30 + .../pytest/doc/en/announce/release-4.0.1.rst | 23 + .../pytest/doc/en/announce/release-4.0.2.rst | 24 + .../pytest/doc/en/announce/release-4.1.0.rst | 44 + .../pytest/doc/en/announce/release-4.1.1.rst | 27 + .../pytest/doc/en/announce/release-4.2.0.rst | 37 + .../pytest/doc/en/announce/release-4.2.1.rst | 30 + .../pytest/doc/en/announce/release-4.3.0.rst | 36 + .../pytest/doc/en/announce/release-4.3.1.rst | 28 + .../pytest/doc/en/announce/release-4.4.0.rst | 39 + .../pytest/doc/en/announce/release-4.4.1.rst | 20 + .../pytest/doc/en/announce/release-4.4.2.rst | 33 + .../pytest/doc/en/announce/release-4.5.0.rst | 34 + .../pytest/doc/en/announce/release-4.6.0.rst | 43 + .../pytest/doc/en/announce/release-4.6.1.rst | 19 + .../pytest/doc/en/announce/release-4.6.2.rst | 18 + .../pytest/doc/en/announce/release-4.6.3.rst | 21 + .../pytest/doc/en/announce/release-4.6.4.rst | 22 + .../pytest/doc/en/announce/release-4.6.5.rst | 21 + .../pytest/doc/en/announce/release-4.6.6.rst | 20 + .../pytest/doc/en/announce/release-4.6.7.rst | 19 + .../pytest/doc/en/announce/release-4.6.8.rst | 20 + .../pytest/doc/en/announce/release-4.6.9.rst | 21 + .../pytest/doc/en/announce/release-5.0.0.rst | 46 + .../pytest/doc/en/announce/release-5.0.1.rst | 25 + .../pytest/doc/en/announce/release-5.1.0.rst | 56 + .../pytest/doc/en/announce/release-5.1.1.rst | 24 + .../pytest/doc/en/announce/release-5.1.2.rst | 23 + .../pytest/doc/en/announce/release-5.1.3.rst | 23 + .../pytest/doc/en/announce/release-5.2.0.rst | 35 + .../pytest/doc/en/announce/release-5.2.1.rst | 23 + .../pytest/doc/en/announce/release-5.2.2.rst | 29 + .../pytest/doc/en/announce/release-5.2.3.rst | 28 + .../pytest/doc/en/announce/release-5.2.4.rst | 22 + .../pytest/doc/en/announce/release-5.3.0.rst | 45 + .../pytest/doc/en/announce/release-5.3.1.rst | 26 + .../pytest/doc/en/announce/release-5.3.2.rst | 26 + .../pytest/doc/en/announce/release-5.3.3.rst | 30 + .../pytest/doc/en/announce/release-5.3.4.rst | 20 + .../pytest/doc/en/announce/release-5.3.5.rst | 19 + .../pytest/doc/en/announce/release-5.4.0.rst | 59 + .../pytest/doc/en/announce/release-5.4.1.rst | 18 + .../pytest/doc/en/announce/release-5.4.2.rst | 22 + .../pytest/doc/en/announce/release-5.4.3.rst | 21 + .../pytest/doc/en/announce/release-6.0.0.rst | 40 + .../pytest/doc/en/announce/release-6.0.0rc1.rst | 67 + .../pytest/doc/en/announce/release-6.0.1.rst | 21 + .../pytest/doc/en/announce/release-6.0.2.rst | 19 + .../pytest/doc/en/announce/release-6.1.0.rst | 44 + .../pytest/doc/en/announce/release-6.1.1.rst | 18 + .../pytest/doc/en/announce/release-6.1.2.rst | 22 + .../pytest/doc/en/announce/release-6.2.0.rst | 76 + .../pytest/doc/en/announce/release-6.2.1.rst | 20 + .../pytest/doc/en/announce/release-6.2.2.rst | 21 + .../pytest/doc/en/announce/release-6.2.3.rst | 19 + .../pytest/doc/en/announce/release-6.2.4.rst | 22 + .../pytest/doc/en/announce/release-6.2.5.rst | 30 + .../pytest/doc/en/announce/release-7.0.0.rst | 74 + .../pytest/doc/en/announce/release-7.0.0rc1.rst | 74 + .../pytest/doc/en/announce/release-7.0.1.rst | 20 + .../pytest/doc/en/announce/sprint2016.rst | 64 + .../pytest/doc/en/backwards-compatibility.rst | 79 + .../tools/third_party/pytest/doc/en/builtin.rst | 197 + .../tools/third_party/pytest/doc/en/changelog.rst | 9044 +++ .../tests/tools/third_party/pytest/doc/en/conf.py | 478 + .../tools/third_party/pytest/doc/en/conftest.py | 1 + .../tools/third_party/pytest/doc/en/contact.rst | 54 + .../tools/third_party/pytest/doc/en/contents.rst | 116 + .../third_party/pytest/doc/en/contributing.rst | 3 + .../third_party/pytest/doc/en/deprecations.rst | 954 + .../pytest/doc/en/development_guide.rst | 7 + .../doc/en/example/assertion/failure_demo.py | 281 + .../assertion/global_testmodule_config/conftest.py | 14 + .../global_testmodule_config/test_hello_world.py | 5 + .../doc/en/example/assertion/test_failures.py | 13 + .../example/assertion/test_setup_flow_example.py | 44 + .../third_party/pytest/doc/en/example/attic.rst | 83 + .../third_party/pytest/doc/en/example/conftest.py | 1 + .../en/example/fixtures/fixture_availability.svg | 132 + .../fixtures/fixture_availability_plugins.svg | 142 + .../fixtures/test_fixtures_order_autouse.py | 45 + .../fixtures/test_fixtures_order_autouse.svg | 64 + .../fixtures/test_fixtures_order_autouse_flat.svg | 56 + .../test_fixtures_order_autouse_multiple_scopes.py | 31 + ...test_fixtures_order_autouse_multiple_scopes.svg | 76 + .../test_fixtures_order_autouse_temp_effects.py | 36 + .../test_fixtures_order_autouse_temp_effects.svg | 100 + .../fixtures/test_fixtures_order_dependencies.py | 45 + .../fixtures/test_fixtures_order_dependencies.svg | 60 + .../test_fixtures_order_dependencies_flat.svg | 51 + .../test_fixtures_order_dependencies_unclear.svg | 60 + .../example/fixtures/test_fixtures_order_scope.py | 36 + .../example/fixtures/test_fixtures_order_scope.svg | 55 + .../test_fixtures_request_different_scope.py | 29 + .../test_fixtures_request_different_scope.svg | 115 + .../third_party/pytest/doc/en/example/index.rst | 34 + .../third_party/pytest/doc/en/example/markers.rst | 734 + .../pytest/doc/en/example/multipython.py | 72 + .../pytest/doc/en/example/nonpython.rst | 102 + .../pytest/doc/en/example/nonpython/__init__.py | 0 .../pytest/doc/en/example/nonpython/conftest.py | 47 + .../doc/en/example/nonpython/test_simple.yaml | 7 + .../pytest/doc/en/example/parametrize.rst | 708 + .../pytest/doc/en/example/pythoncollection.py | 14 + .../pytest/doc/en/example/pythoncollection.rst | 321 + .../pytest/doc/en/example/reportingdemo.rst | 708 + .../third_party/pytest/doc/en/example/simple.rst | 1086 + .../third_party/pytest/doc/en/example/special.rst | 84 + .../pytest/doc/en/example/xfail_demo.py | 38 + .../pytest/doc/en/explanation/anatomy.rst | 46 + .../pytest/doc/en/explanation/fixtures.rst | 174 + .../pytest/doc/en/explanation/flaky.rst | 126 + .../pytest/doc/en/explanation/goodpractices.rst | 288 + .../pytest/doc/en/explanation/index.rst | 15 + .../pytest/doc/en/explanation/pythonpath.rst | 133 + .../third_party/pytest/doc/en/funcarg_compare.rst | 230 + .../tools/third_party/pytest/doc/en/funcargs.rst | 13 + .../third_party/pytest/doc/en/getting-started.rst | 257 + .../third_party/pytest/doc/en/historical-notes.rst | 312 + .../tools/third_party/pytest/doc/en/history.rst | 145 + .../third_party/pytest/doc/en/how-to/assert.rst | 336 + .../pytest/doc/en/how-to/bash-completion.rst | 33 + .../third_party/pytest/doc/en/how-to/cache.rst | 329 + .../pytest/doc/en/how-to/capture-stdout-stderr.rst | 170 + .../pytest/doc/en/how-to/capture-warnings.rst | 443 + .../third_party/pytest/doc/en/how-to/doctest.rst | 312 + .../pytest/doc/en/how-to/existingtestsuite.rst | 34 + .../third_party/pytest/doc/en/how-to/failures.rst | 160 + .../third_party/pytest/doc/en/how-to/fixtures.rst | 1887 + .../third_party/pytest/doc/en/how-to/index.rst | 64 + .../third_party/pytest/doc/en/how-to/logging.rst | 292 + .../third_party/pytest/doc/en/how-to/mark.rst | 93 + .../pytest/doc/en/how-to/monkeypatch.rst | 444 + .../third_party/pytest/doc/en/how-to/nose.rst | 79 + .../third_party/pytest/doc/en/how-to/output.rst | 710 + .../pytest/doc/en/how-to/parametrize.rst | 298 + .../third_party/pytest/doc/en/how-to/plugins.rst | 136 + .../third_party/pytest/doc/en/how-to/skipping.rst | 430 + .../third_party/pytest/doc/en/how-to/tmp_path.rst | 139 + .../third_party/pytest/doc/en/how-to/unittest.rst | 251 + .../third_party/pytest/doc/en/how-to/usage.rst | 214 + .../doc/en/how-to/writing_hook_functions.rst | 352 + .../pytest/doc/en/how-to/writing_plugins.rst | 458 + .../pytest/doc/en/how-to/xunit_setup.rst | 117 + .../third_party/pytest/doc/en/img/cramer2.png | Bin 0 -> 25291 bytes .../third_party/pytest/doc/en/img/favicon.png | Bin 0 -> 1334 bytes .../third_party/pytest/doc/en/img/freiburg2.jpg | Bin 0 -> 104057 bytes .../third_party/pytest/doc/en/img/gaynor3.png | Bin 0 -> 23032 bytes .../third_party/pytest/doc/en/img/keleshev.png | Bin 0 -> 23246 bytes .../third_party/pytest/doc/en/img/pullrequest.png | Bin 0 -> 17035 bytes .../tools/third_party/pytest/doc/en/img/pylib.png | Bin 0 -> 8276 bytes .../third_party/pytest/doc/en/img/pytest1.png | Bin 0 -> 6010 bytes .../pytest/doc/en/img/pytest_logo_curves.svg | 29 + .../tools/third_party/pytest/doc/en/img/theuni.png | Bin 0 -> 31476 bytes .../tools/third_party/pytest/doc/en/index.rst | 148 + .../tools/third_party/pytest/doc/en/license.rst | 32 + .../tools/third_party/pytest/doc/en/naming20.rst | 20 + .../doc/en/proposals/parametrize_with_fixtures.rst | 164 + .../pytest/doc/en/py27-py34-deprecation.rst | 99 + .../tools/third_party/pytest/doc/en/pytest.ini | 2 + .../tools/third_party/pytest/doc/en/recwarn.rst | 3 + .../pytest/doc/en/reference/customize.rst | 248 + .../pytest/doc/en/reference/exit-codes.rst | 26 + .../pytest/doc/en/reference/fixtures.rst | 455 + .../third_party/pytest/doc/en/reference/index.rst | 15 + .../pytest/doc/en/reference/plugin_list.rst | 7728 +++ .../pytest/doc/en/reference/reference.rst | 2101 + .../third_party/pytest/doc/en/requirements.txt | 7 + .../tools/third_party/pytest/doc/en/sponsor.rst | 26 + .../tools/third_party/pytest/doc/en/talks.rst | 109 + .../tools/third_party/pytest/doc/en/tidelift.rst | 45 + .../third_party/pytest/doc/en/yieldfixture.rst | 18 + .../tools/third_party/pytest/extra/get_issues.py | 85 + .../pytest/extra/setup-py.test/setup.py | 11 + .../tests/tools/third_party/pytest/pyproject.toml | 116 + .../pytest/scripts/prepare-release-pr.py | 174 + .../pytest/scripts/publish-gh-release-notes.py | 102 + .../third_party/pytest/scripts/release.major.rst | 24 + .../third_party/pytest/scripts/release.minor.rst | 24 + .../third_party/pytest/scripts/release.patch.rst | 17 + .../third_party/pytest/scripts/release.pre.rst | 29 + .../tools/third_party/pytest/scripts/release.py | 131 + .../pytest/scripts/towncrier-draft-to-file.py | 15 + .../pytest/scripts/update-plugin-list.py | 140 + .../tests/tools/third_party/pytest/setup.cfg | 105 + .../tests/tools/third_party/pytest/setup.py | 4 + .../third_party/pytest/src/_pytest/__init__.py | 9 + .../third_party/pytest/src/_pytest/_argcomplete.py | 117 + .../pytest/src/_pytest/_code/__init__.py | 22 + .../third_party/pytest/src/_pytest/_code/code.py | 1274 + .../third_party/pytest/src/_pytest/_code/source.py | 217 + .../third_party/pytest/src/_pytest/_io/__init__.py | 8 + .../third_party/pytest/src/_pytest/_io/saferepr.py | 153 + .../pytest/src/_pytest/_io/terminalwriter.py | 233 + .../third_party/pytest/src/_pytest/_io/wcwidth.py | 55 + .../third_party/pytest/src/_pytest/_version.py | 5 + .../pytest/src/_pytest/assertion/__init__.py | 181 + .../pytest/src/_pytest/assertion/rewrite.py | 1136 + .../pytest/src/_pytest/assertion/truncate.py | 94 + .../pytest/src/_pytest/assertion/util.py | 498 + .../pytest/src/_pytest/cacheprovider.py | 580 + .../third_party/pytest/src/_pytest/capture.py | 942 + .../tools/third_party/pytest/src/_pytest/compat.py | 417 + .../pytest/src/_pytest/config/__init__.py | 1697 + .../pytest/src/_pytest/config/argparsing.py | 535 + .../pytest/src/_pytest/config/compat.py | 71 + .../pytest/src/_pytest/config/exceptions.py | 11 + .../pytest/src/_pytest/config/findpaths.py | 213 + .../third_party/pytest/src/_pytest/debugging.py | 388 + .../third_party/pytest/src/_pytest/deprecated.py | 155 + .../third_party/pytest/src/_pytest/doctest.py | 734 + .../third_party/pytest/src/_pytest/faulthandler.py | 97 + .../third_party/pytest/src/_pytest/fixtures.py | 1686 + .../pytest/src/_pytest/freeze_support.py | 44 + .../third_party/pytest/src/_pytest/helpconfig.py | 264 + .../third_party/pytest/src/_pytest/hookspec.py | 928 + .../third_party/pytest/src/_pytest/junitxml.py | 696 + .../third_party/pytest/src/_pytest/legacypath.py | 467 + .../third_party/pytest/src/_pytest/logging.py | 831 + .../tools/third_party/pytest/src/_pytest/main.py | 896 + .../pytest/src/_pytest/mark/__init__.py | 282 + .../pytest/src/_pytest/mark/expression.py | 225 + .../pytest/src/_pytest/mark/structures.py | 595 + .../third_party/pytest/src/_pytest/monkeypatch.py | 383 + .../tools/third_party/pytest/src/_pytest/nodes.py | 762 + .../tools/third_party/pytest/src/_pytest/nose.py | 42 + .../third_party/pytest/src/_pytest/outcomes.py | 307 + .../third_party/pytest/src/_pytest/pastebin.py | 110 + .../third_party/pytest/src/_pytest/pathlib.py | 724 + .../tools/third_party/pytest/src/_pytest/py.typed | 0 .../third_party/pytest/src/_pytest/pytester.py | 1748 + .../pytest/src/_pytest/pytester_assertions.py | 75 + .../tools/third_party/pytest/src/_pytest/python.py | 1764 + .../third_party/pytest/src/_pytest/python_api.py | 961 + .../third_party/pytest/src/_pytest/python_path.py | 24 + .../third_party/pytest/src/_pytest/recwarn.py | 296 + .../third_party/pytest/src/_pytest/reports.py | 598 + .../tools/third_party/pytest/src/_pytest/runner.py | 548 + .../tools/third_party/pytest/src/_pytest/scope.py | 91 + .../third_party/pytest/src/_pytest/setuponly.py | 97 + .../third_party/pytest/src/_pytest/setupplan.py | 40 + .../third_party/pytest/src/_pytest/skipping.py | 296 + .../tools/third_party/pytest/src/_pytest/stash.py | 112 + .../third_party/pytest/src/_pytest/stepwise.py | 122 + .../third_party/pytest/src/_pytest/terminal.py | 1394 + .../pytest/src/_pytest/threadexception.py | 88 + .../tools/third_party/pytest/src/_pytest/timing.py | 12 + .../tools/third_party/pytest/src/_pytest/tmpdir.py | 211 + .../third_party/pytest/src/_pytest/unittest.py | 414 + .../pytest/src/_pytest/unraisableexception.py | 93 + .../pytest/src/_pytest/warning_types.py | 145 + .../third_party/pytest/src/_pytest/warnings.py | 141 + .../third_party/pytest/src/pytest/__init__.py | 171 + .../third_party/pytest/src/pytest/__main__.py | 5 + .../tools/third_party/pytest/src/pytest/collect.py | 38 + .../tools/third_party/pytest/src/pytest/py.typed | 0 .../third_party/pytest/testing/acceptance_test.py | 1297 + .../third_party/pytest/testing/code/test_code.py | 212 + .../pytest/testing/code/test_excinfo.py | 1470 + .../third_party/pytest/testing/code/test_source.py | 656 + .../tools/third_party/pytest/testing/conftest.py | 216 + .../third_party/pytest/testing/deprecated_test.py | 310 + .../pytest/testing/example_scripts/README.rst | 9 + .../pytest/testing/example_scripts/__init__.py | 0 .../acceptance/fixture_mock_integration.py | 16 + .../collect/collect_init_tests/pytest.ini | 2 + .../collect/collect_init_tests/tests/__init__.py | 2 + .../collect/collect_init_tests/tests/test_foo.py | 2 + .../package_infinite_recursion/__init__.pyi | 0 .../collect/package_infinite_recursion/conftest.py | 2 + .../package_infinite_recursion/tests/__init__.py | 0 .../package_infinite_recursion/tests/test_basic.py | 2 + .../package_init_given_as_arg/pkg/__init__.py | 0 .../package_init_given_as_arg/pkg/test_foo.py | 2 + .../config/collect_pytest_prefix/__init__.pyi | 0 .../config/collect_pytest_prefix/conftest.py | 2 + .../config/collect_pytest_prefix/test_foo.py | 2 + .../conftest_usageerror/__init__.pyi | 0 .../conftest_usageerror/conftest.py | 8 + .../dataclasses/test_compare_dataclasses.py | 14 + ...est_compare_dataclasses_field_comparison_off.py | 14 + .../test_compare_dataclasses_verbose.py | 14 + .../test_compare_recursive_dataclasses.py | 44 + .../test_compare_two_different_dataclasses.py | 19 + .../example_scripts/doctest/main_py/__main__.py | 2 + .../doctest/main_py/test_normal_module.py | 6 + .../fixtures/custom_item/__init__.pyi | 0 .../fixtures/custom_item/conftest.py | 15 + .../fixtures/custom_item/foo/__init__.py | 0 .../fixtures/custom_item/foo/test_foo.py | 2 + .../sub1/__init__.py | 0 .../sub1/conftest.py | 7 + .../sub1/test_in_sub1.py | 2 + .../sub2/__init__.py | 0 .../sub2/conftest.py | 6 + .../sub2/test_in_sub2.py | 2 + .../test_detect_recursive_dependency_error.py | 15 + .../__init__.pyi | 0 .../conftest.py | 6 + .../pkg/__init__.py | 0 .../pkg/conftest.py | 6 + .../pkg/test_spam.py | 2 + .../__init__.pyi | 0 .../conftest.py | 6 + .../test_extend_fixture_conftest_module.py | 10 + .../test_extend_fixture_module_class.py | 15 + .../fixtures/fill_fixtures/test_funcarg_basic.py | 15 + .../test_funcarg_lookup_classlevel.py | 10 + .../test_funcarg_lookup_modulelevel.py | 15 + .../fill_fixtures/test_funcarg_lookupfails.py | 10 + .../fixtures/test_fixture_named_request.py | 10 + .../fixtures/test_getfixturevalue_dynamic.py | 20 + .../issue88_initial_file_multinodes/__init__.pyi | 0 .../issue88_initial_file_multinodes/conftest.py | 14 + .../issue88_initial_file_multinodes/test_hello.py | 2 + .../pytest/testing/example_scripts/issue_519.py | 53 + .../pytest/testing/example_scripts/junit-10.xsd | 147 + .../marks/marks_considered_keywords/__init__.pyi | 0 .../marks/marks_considered_keywords/conftest.py | 0 .../test_marks_as_keywords.py | 6 + .../perf_examples/collect_stats/.gitignore | 1 + .../collect_stats/generate_folders.py | 27 + .../perf_examples/collect_stats/template_test.py | 2 + .../pytest/testing/example_scripts/pytest.ini | 2 + .../example_scripts/tmpdir/tmp_path_fixture.py | 7 + .../test_parametrized_fixture_error_message.py | 14 + .../example_scripts/unittest/test_setup_skip.py | 13 + .../unittest/test_setup_skip_class.py | 14 + .../unittest/test_setup_skip_module.py | 12 + .../unittest/test_unittest_asyncio.py | 25 + .../unittest/test_unittest_asynctest.py | 23 + .../unittest/test_unittest_plain_async.py | 6 + .../warnings/test_group_warnings_by_message.py | 21 + .../test_1.py | 21 + .../test_2.py | 5 + .../pytest/testing/examples/test_issue519.py | 7 + .../third_party/pytest/testing/freeze/.gitignore | 3 + .../pytest/testing/freeze/create_executable.py | 11 + .../pytest/testing/freeze/runtests_script.py | 10 + .../pytest/testing/freeze/tests/test_doctest.txt | 6 + .../pytest/testing/freeze/tests/test_trivial.py | 6 + .../third_party/pytest/testing/freeze/tox_run.py | 12 + .../third_party/pytest/testing/io/test_saferepr.py | 181 + .../pytest/testing/io/test_terminalwriter.py | 293 + .../third_party/pytest/testing/io/test_wcwidth.py | 38 + .../pytest/testing/logging/test_fixture.py | 310 + .../pytest/testing/logging/test_formatter.py | 173 + .../pytest/testing/logging/test_reporting.py | 1167 + .../pytest/testing/plugins_integration/.gitignore | 2 + .../pytest/testing/plugins_integration/README.rst | 13 + .../testing/plugins_integration/bdd_wallet.feature | 9 + .../testing/plugins_integration/bdd_wallet.py | 39 + .../testing/plugins_integration/django_settings.py | 1 + .../pytest/testing/plugins_integration/pytest.ini | 5 + .../pytest_anyio_integration.py | 8 + .../pytest_asyncio_integration.py | 8 + .../plugins_integration/pytest_mock_integration.py | 2 + .../plugins_integration/pytest_trio_integration.py | 8 + .../pytest_twisted_integration.py | 18 + .../testing/plugins_integration/requirements.txt | 15 + .../plugins_integration/simple_integration.py | 10 + .../third_party/pytest/testing/python/approx.py | 872 + .../third_party/pytest/testing/python/collect.py | 1493 + .../third_party/pytest/testing/python/fixtures.py | 4474 ++ .../pytest/testing/python/integration.py | 503 + .../third_party/pytest/testing/python/metafunc.py | 1907 + .../third_party/pytest/testing/python/raises.py | 298 + .../testing/python/show_fixtures_per_test.py | 254 + .../third_party/pytest/testing/test_argcomplete.py | 95 + .../third_party/pytest/testing/test_assertion.py | 1685 + .../pytest/testing/test_assertrewrite.py | 1841 + .../pytest/testing/test_cacheprovider.py | 1251 + .../third_party/pytest/testing/test_capture.py | 1666 + .../third_party/pytest/testing/test_collection.py | 1506 + .../third_party/pytest/testing/test_compat.py | 265 + .../third_party/pytest/testing/test_config.py | 2115 + .../third_party/pytest/testing/test_conftest.py | 696 + .../third_party/pytest/testing/test_debugging.py | 1327 + .../third_party/pytest/testing/test_doctest.py | 1572 + .../pytest/testing/test_entry_points.py | 7 + .../third_party/pytest/testing/test_error_diffs.py | 283 + .../pytest/testing/test_faulthandler.py | 172 + .../third_party/pytest/testing/test_findpaths.py | 135 + .../third_party/pytest/testing/test_helpconfig.py | 124 + .../third_party/pytest/testing/test_junitxml.py | 1703 + .../third_party/pytest/testing/test_legacypath.py | 180 + .../pytest/testing/test_link_resolve.py | 80 + .../tools/third_party/pytest/testing/test_main.py | 264 + .../tools/third_party/pytest/testing/test_mark.py | 1130 + .../pytest/testing/test_mark_expression.py | 195 + .../tools/third_party/pytest/testing/test_meta.py | 32 + .../third_party/pytest/testing/test_monkeypatch.py | 455 + .../tools/third_party/pytest/testing/test_nodes.py | 167 + .../tools/third_party/pytest/testing/test_nose.py | 498 + .../third_party/pytest/testing/test_parseopt.py | 344 + .../third_party/pytest/testing/test_pastebin.py | 184 + .../third_party/pytest/testing/test_pathlib.py | 574 + .../pytest/testing/test_pluginmanager.py | 427 + .../third_party/pytest/testing/test_pytester.py | 855 + .../third_party/pytest/testing/test_python_path.py | 110 + .../third_party/pytest/testing/test_recwarn.py | 410 + .../third_party/pytest/testing/test_reports.py | 488 + .../third_party/pytest/testing/test_runner.py | 1061 + .../pytest/testing/test_runner_xunit.py | 297 + .../tools/third_party/pytest/testing/test_scope.py | 39 + .../third_party/pytest/testing/test_session.py | 369 + .../third_party/pytest/testing/test_setuponly.py | 318 + .../third_party/pytest/testing/test_setupplan.py | 120 + .../third_party/pytest/testing/test_skipping.py | 1533 + .../tools/third_party/pytest/testing/test_stash.py | 67 + .../third_party/pytest/testing/test_stepwise.py | 280 + .../third_party/pytest/testing/test_terminal.py | 2486 + .../pytest/testing/test_threadexception.py | 137 + .../third_party/pytest/testing/test_tmpdir.py | 480 + .../third_party/pytest/testing/test_unittest.py | 1500 + .../pytest/testing/test_unraisableexception.py | 133 + .../pytest/testing/test_warning_types.py | 38 + .../third_party/pytest/testing/test_warnings.py | 775 + .../third_party/pytest/testing/typing_checks.py | 24 + .../tests/tools/third_party/pytest/tox.ini | 184 + .../tools/third_party/pywebsocket3/.gitignore | 4 + .../tools/third_party/pywebsocket3/.travis.yml | 17 + .../tools/third_party/pywebsocket3/CONTRIBUTING | 30 + .../tests/tools/third_party/pywebsocket3/LICENSE | 28 + .../tools/third_party/pywebsocket3/MANIFEST.in | 6 + .../tests/tools/third_party/pywebsocket3/README.md | 36 + .../pywebsocket3/example/abort_handshake_wsh.py | 43 + .../third_party/pywebsocket3/example/abort_wsh.py | 43 + .../example/arraybuffer_benchmark.html | 134 + .../third_party/pywebsocket3/example/bench_wsh.py | 59 + .../pywebsocket3/example/benchmark.html | 175 + .../third_party/pywebsocket3/example/benchmark.js | 238 + .../pywebsocket3/example/benchmark_helper_wsh.py | 84 + .../third_party/pywebsocket3/example/cgi-bin/hi.py | 5 + .../third_party/pywebsocket3/example/close_wsh.py | 70 + .../third_party/pywebsocket3/example/console.html | 317 + .../third_party/pywebsocket3/example/cookie_wsh.py | 54 + .../pywebsocket3/example/echo_client.py | 699 + .../pywebsocket3/example/echo_noext_wsh.py | 62 + .../third_party/pywebsocket3/example/echo_wsh.py | 55 + .../pywebsocket3/example/handler_map.txt | 11 + .../third_party/pywebsocket3/example/hsts_wsh.py | 40 + .../pywebsocket3/example/internal_error_wsh.py | 42 + .../pywebsocket3/example/origin_check_wsh.py | 44 + .../example/performance_test_iframe.html | 37 + .../example/performance_test_iframe.js | 86 + .../pywebsocket3/example/special_headers.cgi | 26 + .../tools/third_party/pywebsocket3/example/util.js | 323 + .../third_party/pywebsocket3/example/util_main.js | 89 + .../pywebsocket3/example/util_worker.js | 44 + .../pywebsocket3/mod_pywebsocket/__init__.py | 172 + .../mod_pywebsocket/_stream_exceptions.py | 82 + .../pywebsocket3/mod_pywebsocket/common.py | 273 + .../pywebsocket3/mod_pywebsocket/dispatch.py | 385 + .../pywebsocket3/mod_pywebsocket/extensions.py | 474 + .../pywebsocket3/mod_pywebsocket/fast_masking.i | 98 + .../mod_pywebsocket/handshake/__init__.py | 101 + .../pywebsocket3/mod_pywebsocket/handshake/base.py | 396 + .../pywebsocket3/mod_pywebsocket/handshake/hybi.py | 223 + .../mod_pywebsocket/http_header_util.py | 254 + .../pywebsocket3/mod_pywebsocket/memorizingfile.py | 99 + .../pywebsocket3/mod_pywebsocket/msgutil.py | 214 + .../mod_pywebsocket/request_handler.py | 319 + .../pywebsocket3/mod_pywebsocket/server_util.py | 87 + .../pywebsocket3/mod_pywebsocket/standalone.py | 481 + .../pywebsocket3/mod_pywebsocket/stream.py | 950 + .../pywebsocket3/mod_pywebsocket/util.py | 386 + .../mod_pywebsocket/websocket_server.py | 285 + .../tests/tools/third_party/pywebsocket3/setup.py | 73 + .../third_party/pywebsocket3/test/__init__.py | 0 .../third_party/pywebsocket3/test/cert/cacert.pem | 17 + .../third_party/pywebsocket3/test/cert/cert.pem | 61 + .../pywebsocket3/test/cert/client_cert.p12 | Bin 0 -> 2582 bytes .../third_party/pywebsocket3/test/cert/key.pem | 15 + .../pywebsocket3/test/client_for_testing.py | 726 + .../tools/third_party/pywebsocket3/test/mock.py | 227 + .../tools/third_party/pywebsocket3/test/run_all.py | 88 + .../third_party/pywebsocket3/test/set_sys_path.py | 41 + .../third_party/pywebsocket3/test/test_dispatch.py | 298 + .../third_party/pywebsocket3/test/test_endtoend.py | 738 + .../pywebsocket3/test/test_extensions.py | 192 + .../pywebsocket3/test/test_handshake.py | 172 + .../pywebsocket3/test/test_handshake_hybi.py | 422 + .../pywebsocket3/test/test_http_header_util.py | 93 + .../pywebsocket3/test/test_memorizingfile.py | 100 + .../third_party/pywebsocket3/test/test_mock.py | 137 + .../third_party/pywebsocket3/test/test_msgutil.py | 912 + .../third_party/pywebsocket3/test/test_stream.py | 70 + .../third_party/pywebsocket3/test/test_util.py | 191 + .../third_party/pywebsocket3/test/testdata/README | 1 + .../test/testdata/handlers/abort_by_user_wsh.py | 41 + .../test/testdata/handlers/blank_wsh.py | 30 + .../test/testdata/handlers/origin_check_wsh.py | 43 + .../handlers/sub/exception_in_transfer_wsh.py | 42 + .../testdata/handlers/sub/no_wsh_at_the_end.py | 43 + .../test/testdata/handlers/sub/non_callable_wsh.py | 35 + .../test/testdata/handlers/sub/plain_wsh.py | 41 + .../handlers/sub/wrong_handshake_sig_wsh.py | 43 + .../handlers/sub/wrong_transfer_sig_wsh.py | 43 + .../pywebsocket3/test/testdata/hello.pl | 32 + .../tests/tools/third_party/six/CHANGES | 315 + .../tests/tools/third_party/six/LICENSE | 18 + .../tests/tools/third_party/six/MANIFEST.in | 6 + .../tests/tools/third_party/six/README.rst | 32 + .../tools/third_party/six/documentation/Makefile | 130 + .../tools/third_party/six/documentation/conf.py | 217 + .../tools/third_party/six/documentation/index.rst | 875 + .../tests/tools/third_party/six/setup.cfg | 20 + .../tests/tools/third_party/six/setup.py | 58 + .../third_party/six/six-1.15.0.dist-info/INSTALLER | 1 + .../third_party/six/six-1.15.0.dist-info/LICENSE | 18 + .../third_party/six/six-1.15.0.dist-info/METADATA | 49 + .../third_party/six/six-1.15.0.dist-info/RECORD | 8 + .../third_party/six/six-1.15.0.dist-info/WHEEL | 6 + .../six/six-1.15.0.dist-info/top_level.txt | 1 + .../tests/tools/third_party/six/six.py | 982 + .../tests/tools/third_party/six/test_six.py | 1052 + .../tests/tools/third_party/tooltool/tooltool.py | 1316 + .../tests/tools/third_party/webencodings/PKG-INFO | 50 + .../tools/third_party/webencodings/README.rst | 25 + .../tests/tools/third_party/webencodings/setup.cfg | 14 + .../tests/tools/third_party/webencodings/setup.py | 47 + .../webencodings/webencodings/__init__.py | 342 + .../webencodings/webencodings/labels.py | 231 + .../webencodings/webencodings/mklabels.py | 59 + .../third_party/webencodings/webencodings/tests.py | 153 + .../webencodings/webencodings/x_user_defined.py | 325 + .../tools/third_party/websockets/.appveyor.yml | 27 + .../third_party/websockets/.circleci/config.yml | 55 + .../third_party/websockets/.github/FUNDING.yml | 1 + .../tests/tools/third_party/websockets/.gitignore | 12 + .../tools/third_party/websockets/.readthedocs.yml | 7 + .../tests/tools/third_party/websockets/.travis.yml | 36 + .../third_party/websockets/CODE_OF_CONDUCT.md | 46 + .../tests/tools/third_party/websockets/LICENSE | 25 + .../tests/tools/third_party/websockets/MANIFEST.in | 2 + .../tests/tools/third_party/websockets/Makefile | 29 + .../tests/tools/third_party/websockets/README.rst | 154 + .../third_party/websockets/compliance/README.rst | 50 + .../websockets/compliance/fuzzingclient.json | 11 + .../websockets/compliance/fuzzingserver.json | 12 + .../websockets/compliance/test_client.py | 49 + .../websockets/compliance/test_server.py | 27 + .../tools/third_party/websockets/docs/Makefile | 160 + .../websockets/docs/_static/tidelift.png | Bin 0 -> 4069 bytes .../websockets/docs/_static/websockets.svg | 31 + .../tools/third_party/websockets/docs/api.rst | 152 + .../third_party/websockets/docs/changelog.rst | 563 + .../third_party/websockets/docs/cheatsheet.rst | 109 + .../tools/third_party/websockets/docs/conf.py | 272 + .../third_party/websockets/docs/contributing.rst | 61 + .../third_party/websockets/docs/deployment.rst | 162 + .../tools/third_party/websockets/docs/design.rst | 571 + .../third_party/websockets/docs/extensions.rst | 87 + .../tools/third_party/websockets/docs/faq.rst | 261 + .../tools/third_party/websockets/docs/index.rst | 99 + .../tools/third_party/websockets/docs/intro.rst | 209 + .../tools/third_party/websockets/docs/license.rst | 4 + .../third_party/websockets/docs/lifecycle.graffle | Bin 0 -> 3134 bytes .../third_party/websockets/docs/lifecycle.svg | 3 + .../third_party/websockets/docs/limitations.rst | 10 + .../third_party/websockets/docs/protocol.graffle | Bin 0 -> 4740 bytes .../tools/third_party/websockets/docs/protocol.svg | 3 + .../third_party/websockets/docs/requirements.txt | 4 + .../tools/third_party/websockets/docs/security.rst | 39 + .../websockets/docs/spelling_wordlist.txt | 39 + .../tools/third_party/websockets/docs/tidelift.rst | 112 + .../websockets/example/basic_auth_client.py | 14 + .../websockets/example/basic_auth_server.py | 20 + .../tools/third_party/websockets/example/client.py | 19 + .../third_party/websockets/example/counter.html | 80 + .../third_party/websockets/example/counter.py | 69 + .../tools/third_party/websockets/example/echo.py | 13 + .../websockets/example/health_check_server.py | 22 + .../tools/third_party/websockets/example/hello.py | 12 + .../third_party/websockets/example/localhost.pem | 48 + .../websockets/example/secure_client.py | 27 + .../websockets/example/secure_server.py | 28 + .../tools/third_party/websockets/example/server.py | 20 + .../third_party/websockets/example/show_time.html | 20 + .../third_party/websockets/example/show_time.py | 19 + .../third_party/websockets/example/shutdown.py | 22 + .../third_party/websockets/example/unix_client.py | 19 + .../third_party/websockets/example/unix_server.py | 22 + .../third_party/websockets/logo/horizontal.svg | 31 + .../tools/third_party/websockets/logo/icon.svg | 15 + .../tools/third_party/websockets/logo/old.svg | 14 + .../tools/third_party/websockets/logo/tidelift.png | Bin 0 -> 4069 bytes .../tools/third_party/websockets/logo/vertical.svg | 31 + .../websockets/performance/mem_client.py | 54 + .../websockets/performance/mem_server.py | 63 + .../tests/tools/third_party/websockets/setup.cfg | 30 + .../tests/tools/third_party/websockets/setup.py | 66 + .../websockets/src/websockets/__init__.py | 55 + .../websockets/src/websockets/__main__.py | 206 + .../third_party/websockets/src/websockets/auth.py | 160 + .../websockets/src/websockets/client.py | 584 + .../websockets/src/websockets/exceptions.py | 366 + .../src/websockets/extensions/__init__.py | 0 .../websockets/src/websockets/extensions/base.py | 119 + .../websockets/extensions/permessage_deflate.py | 588 + .../websockets/src/websockets/framing.py | 342 + .../websockets/src/websockets/handshake.py | 185 + .../websockets/src/websockets/headers.py | 515 + .../third_party/websockets/src/websockets/http.py | 360 + .../websockets/src/websockets/protocol.py | 1429 + .../third_party/websockets/src/websockets/py.typed | 0 .../websockets/src/websockets/server.py | 996 + .../websockets/src/websockets/speedups.c | 206 + .../websockets/src/websockets/speedups.pyi | 1 + .../websockets/src/websockets/typing.py | 49 + .../third_party/websockets/src/websockets/uri.py | 81 + .../third_party/websockets/src/websockets/utils.py | 18 + .../websockets/src/websockets/version.py | 1 + .../tools/third_party/websockets/tests/__init__.py | 5 + .../websockets/tests/extensions/__init__.py | 0 .../websockets/tests/extensions/test_base.py | 4 + .../tests/extensions/test_permessage_deflate.py | 792 + .../third_party/websockets/tests/test_auth.py | 139 + .../websockets/tests/test_client_server.py | 1546 + .../websockets/tests/test_exceptions.py | 145 + .../third_party/websockets/tests/test_exports.py | 22 + .../third_party/websockets/tests/test_framing.py | 242 + .../third_party/websockets/tests/test_handshake.py | 190 + .../third_party/websockets/tests/test_headers.py | 185 + .../third_party/websockets/tests/test_http.py | 249 + .../websockets/tests/test_localhost.cnf | 26 + .../websockets/tests/test_localhost.pem | 48 + .../third_party/websockets/tests/test_protocol.py | 1475 + .../tools/third_party/websockets/tests/test_uri.py | 33 + .../third_party/websockets/tests/test_utils.py | 92 + .../tools/third_party/websockets/tests/utils.py | 93 + .../tests/tools/third_party/websockets/tox.ini | 28 + .../tests/tools/third_party/zipp/.flake8 | 9 + .../third_party/zipp/.github/workflows/main.yml | 42 + .../tools/third_party/zipp/.pre-commit-config.yaml | 5 + .../tests/tools/third_party/zipp/.readthedocs.yml | 5 + .../tests/tools/third_party/zipp/.travis.yml | 28 + .../tests/tools/third_party/zipp/CHANGES.rst | 100 + .../tests/tools/third_party/zipp/LICENSE | 7 + .../tests/tools/third_party/zipp/PKG-INFO | 39 + .../tests/tools/third_party/zipp/README.rst | 21 + .../tests/tools/third_party/zipp/appveyor.yml | 24 + .../tests/tools/third_party/zipp/conftest.py | 0 .../tests/tools/third_party/zipp/docs/conf.py | 26 + .../tests/tools/third_party/zipp/docs/history.rst | 8 + .../tests/tools/third_party/zipp/docs/index.rst | 22 + .../tests/tools/third_party/zipp/mypy.ini | 2 + .../tests/tools/third_party/zipp/pyproject.toml | 6 + .../tests/tools/third_party/zipp/pytest.ini | 9 + .../tests/tools/third_party/zipp/setup.cfg | 45 + .../tests/tools/third_party/zipp/setup.py | 6 + .../tests/tools/third_party/zipp/skeleton.md | 137 + .../tests/tools/third_party/zipp/test_zipp.py | 245 + .../tests/tools/third_party/zipp/tox.ini | 36 + .../tools/third_party/zipp/zipp.egg-info/PKG-INFO | 39 + .../third_party/zipp/zipp.egg-info/SOURCES.txt | 24 + .../zipp/zipp.egg-info/dependency_links.txt | 1 + .../third_party/zipp/zipp.egg-info/requires.txt | 14 + .../third_party/zipp/zipp.egg-info/top_level.txt | 1 + .../tests/tools/third_party/zipp/zipp.py | 286 + 1589 files changed, 398078 insertions(+) create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/.travis.yml create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/CONTRIBUTING.rst create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/appveyor.yml create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/atomicwrites/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/docs/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/docs/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/docs/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/docs/make.bat create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/atomicwrites/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/attrs/.github/CODE_OF_CONDUCT.md create mode 100644 testing/web-platform/tests/tools/third_party/attrs/.github/CONTRIBUTING.md create mode 100644 testing/web-platform/tests/tools/third_party/attrs/.github/FUNDING.yml create mode 100644 testing/web-platform/tests/tools/third_party/attrs/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 testing/web-platform/tests/tools/third_party/attrs/.github/SECURITY.md create mode 100644 testing/web-platform/tests/tools/third_party/attrs/.github/workflows/main.yml create mode 100644 testing/web-platform/tests/tools/third_party/attrs/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/attrs/.pre-commit-config.yaml create mode 100644 testing/web-platform/tests/tools/third_party/attrs/.readthedocs.yml create mode 100644 testing/web-platform/tests/tools/third_party/attrs/AUTHORS.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/CHANGELOG.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/attrs/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/attrs/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/changelog.d/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/attrs/changelog.d/towncrier_template.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo.png create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo.svg create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo_white.svg create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/api.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/changelog.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/comparison.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/docutils.conf create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/examples.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/extending.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/glossary.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/hashing.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/how-does-it-work.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/init.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/license.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/names.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/overview.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/python-2.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/types.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/docs/why.rst create mode 100644 testing/web-platform/tests/tools/third_party/attrs/mypy.ini create mode 100644 testing/web-platform/tests/tools/third_party/attrs/pyproject.toml create mode 100644 testing/web-platform/tests/tools/third_party/attrs/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/_cmp.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/_cmp.pyi create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/_compat.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/_config.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/_funcs.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/_make.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/_next_gen.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/_version_info.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/_version_info.pyi create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/converters.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/converters.pyi create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/exceptions.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/exceptions.pyi create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/filters.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/filters.pyi create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/py.typed create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/setters.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/setters.pyi create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/validators.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attr/validators.pyi create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attrs/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attrs/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attrs/converters.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attrs/exceptions.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attrs/filters.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attrs/py.typed create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attrs/setters.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/src/attrs/validators.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/attr_import_star.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/dataclass_transform_example.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/strategies.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_3rd_party.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_annotations.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_cmp.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_compat.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_config.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_converters.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_dunders.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_filters.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_funcs.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_functional.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_hooks.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_import.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_init_subclass.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_make.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_mypy.yml create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_next_gen.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_pattern_matching.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_pyright.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_setattr.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_slots.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_validators.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/test_version_info.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/typing_example.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tests/utils.py create mode 100644 testing/web-platform/tests/tools/third_party/attrs/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/certifi/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/certifi/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/certifi/PKG-INFO create mode 100644 testing/web-platform/tests/tools/third_party/certifi/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/certifi/certifi/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/certifi/certifi/__main__.py create mode 100644 testing/web-platform/tests/tools/third_party/certifi/certifi/cacert.pem create mode 100644 testing/web-platform/tests/tools/third_party/certifi/certifi/core.py create mode 100644 testing/web-platform/tests/tools/third_party/certifi/setup.cfg create mode 100755 testing/web-platform/tests/tools/third_party/certifi/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/enum/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/enum/PKG-INFO create mode 100644 testing/web-platform/tests/tools/third_party/enum/README create mode 100644 testing/web-platform/tests/tools/third_party/enum/enum/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/enum/enum/README create mode 100644 testing/web-platform/tests/tools/third_party/enum/enum/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/enum/enum/doc/enum.rst create mode 100644 testing/web-platform/tests/tools/third_party/enum/enum/test.py create mode 100644 testing/web-platform/tests/tools/third_party/enum/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/enum/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/.coveragerc create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/.travis.yml create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/CHANGELOG create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/docs/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/docs/_templates/page.html create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/docs/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/funcsigs/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/funcsigs/version.py create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/requirements/development.txt create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/tests/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/tests/test_formatannotation.py create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/tests/test_funcsigs.py create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/tests/test_inspect.py create mode 100644 testing/web-platform/tests/tools/third_party/funcsigs/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/h2/.coveragerc create mode 100644 testing/web-platform/tests/tools/third_party/h2/CONTRIBUTORS.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/HISTORY.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/h2/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/h2/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/h2/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/make.bat create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/_static/.keep create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/_static/h2.connection.H2ConnectionStateMachine.dot.png create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/_static/h2.stream.H2StreamStateMachine.dot.png create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/advanced-usage.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/api.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/asyncio-example.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/basic-usage.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/contributors.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/curio-example.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/eventlet-example.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/examples.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/installation.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/low-level.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/negotiating-http2.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/release-notes.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/release-process.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/testimonials.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/tornado-example.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-example.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-head-example.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-post-example.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/docs/source/wsgi-example.rst create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/asyncio/asyncio-server.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/asyncio/cert.crt create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/asyncio/cert.key create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/asyncio/wsgi-server.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/curio/curio-server.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/curio/localhost.crt.pem create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/curio/localhost.key create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/eventlet/eventlet-server.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/eventlet/server.crt create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/eventlet/server.key create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/fragments/client_https_setup_fragment.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/fragments/client_upgrade_fragment.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/fragments/server_https_setup_fragment.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/fragments/server_upgrade_fragment.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/tornado/server.crt create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/tornado/server.key create mode 100755 testing/web-platform/tests/tools/third_party/h2/examples/tornado/tornado-server.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/twisted/head_request.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/twisted/post_request.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.crt create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.csr create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.key create mode 100644 testing/web-platform/tests/tools/third_party/h2/examples/twisted/twisted-server.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/config.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/connection.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/errors.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/events.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/exceptions.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/frame_buffer.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/settings.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/stream.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/utilities.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/h2/windows.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/h2/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/coroutine_tests.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/helpers.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_basic_logic.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_closed_streams.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_complex_logic.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_config.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_events.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_exceptions.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_flow_control_window.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_h2_upgrade.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_head_request.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_header_indexing.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_informational_responses.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_interacting_stacks.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_invalid_content_lengths.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_invalid_frame_sequences.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_invalid_headers.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_priority.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_related_events.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_rfc7838.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_settings.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_state_machines.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_stream_reset.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test/test_utility_functions.py create mode 100644 testing/web-platform/tests/tools/third_party/h2/test_requirements.txt create mode 100644 testing/web-platform/tests/tools/third_party/h2/tox.ini create mode 100755 testing/web-platform/tests/tools/third_party/h2/utils/backport.sh create mode 100644 testing/web-platform/tests/tools/third_party/h2/visualizer/NOTICES.visualizer create mode 100644 testing/web-platform/tests/tools/third_party/h2/visualizer/visualize.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/CONTRIBUTORS.rst create mode 100644 testing/web-platform/tests/tools/third_party/hpack/HISTORY.rst create mode 100644 testing/web-platform/tests/tools/third_party/hpack/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/hpack/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/hpack/PKG-INFO create mode 100644 testing/web-platform/tests/tools/third_party/hpack/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/hpack/hpack/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/hpack/compat.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/hpack/exceptions.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/hpack/hpack.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/hpack/huffman.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/hpack/huffman_constants.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/hpack/huffman_table.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/hpack/struct.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/hpack/table.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/hpack/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/test/test_encode_decode.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/test/test_hpack.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/test/test_hpack_integration.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/test/test_huffman.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/test/test_struct.py create mode 100644 testing/web-platform/tests/tools/third_party/hpack/test/test_table.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/.appveyor.yml create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/.coveragerc create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/.prospector.yaml create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/.pylintrc create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/.pytest.expect create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/.travis.yml create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/AUTHORS.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/CHANGES.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/CONTRIBUTING.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/bench_html.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/bench_wpt.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/README.md create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/html.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/LICENSE.md create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/README.md create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/001.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/background-origin-007-ref.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/background_shorthand_css_relative_url.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/beforeunload-on-history-back-1.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/euckr-encode-form.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/frame-ancestors-self-allow.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/grouping-dl.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/heavy-styling-005.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/htb-ltr-ltr.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/idbindex_get8.htm create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/idlharness.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/li-type-unsupported-ref.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/moz-css21-float-page-break-inside-avoid-6.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/shape-outside-content-box-002.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/random/worker-constructor.https.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/2d.composite.image.destination-over.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/align-content-wrap-002.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/big5_chars_extra.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/fetch.http.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/filter-turbulence-invalid-001.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/grid-auto-fill-rows-001.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/image-orientation-from-image-content-images-ref.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/masonry-item-placement-006.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/moz-css21-table-page-break-inside-avoid-2.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/position-sticky-table-th-bottom-ref.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/pre-float-001.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/resize-004.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/test-plan.src.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/toBlob.png.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/benchmarks/data/wpt/weighted/will-change-abspos-cb-001.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/debug-info.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/changes.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/html5lib.filters.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/html5lib.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/html5lib.treeadapters.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/html5lib.treebuilders.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/html5lib.treewalkers.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/license.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/make.bat create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/modules.rst create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/doc/movingparts.rst create mode 100755 testing/web-platform/tests/tools/third_party/html5lib/flake8-run.sh create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/_ihatexml.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/_inputstream.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/_tokenizer.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/_trie/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/_trie/_base.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/_trie/py.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/_utils.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/constants.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/filters/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/filters/alphabeticalattributes.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/filters/base.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/filters/inject_meta_charset.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/filters/lint.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/filters/optionaltags.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/filters/sanitizer.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/filters/whitespace.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/html5parser.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/serializer.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/sanitizer-testdata/tests1.dat create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/sanitizer.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/serializer-testdata/core.test create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/serializer-testdata/injectmeta.test create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/serializer-testdata/optionaltags.test create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/serializer-testdata/options.test create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/serializer-testdata/whitespace.test create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/support.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_alphabeticalattributes.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_encoding.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_meta.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_optionaltags_filter.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_parser2.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_sanitizer.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_serializer.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_stream.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_tokenizer2.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_treeadapters.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_treewalkers.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/test_whitespace_filter.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/tokenizer.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/tokenizertotree.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/tree_construction.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/us-ascii.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/tests/utf-8-bom.html create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treeadapters/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treeadapters/genshi.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treeadapters/sax.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treebuilders/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treebuilders/base.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treebuilders/dom.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treebuilders/etree.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treebuilders/etree_lxml.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treewalkers/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treewalkers/base.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treewalkers/dom.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treewalkers/etree.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treewalkers/etree_lxml.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/html5lib/treewalkers/genshi.py create mode 100755 testing/web-platform/tests/tools/third_party/html5lib/parse.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/pytest.ini create mode 100755 testing/web-platform/tests/tools/third_party/html5lib/requirements-install.sh create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/requirements-optional.txt create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/requirements-test.txt create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/requirements.txt create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/html5lib/utils/entities.py create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/CONTRIBUTORS.rst create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/HISTORY.rst create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/PKG-INFO create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/exceptions.py create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/flags.py create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/hyperframe/frame.py create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/test/test_flags.py create mode 100644 testing/web-platform/tests/tools/third_party/hyperframe/test/test_frames.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/.github/workflows/main.yml create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/.readthedocs.yml create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/codecov.yml create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/coverage.ini create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/coverplug.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/docs/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/docs/changelog.rst create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/docs/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/docs/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/docs/using.rst create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/importlib_metadata/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/importlib_metadata/_compat.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/prepare/example/example/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/prepare/example/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/pyproject.toml create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tests/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tests/data/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tests/data/example-21.12-py3-none-any.whl create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tests/data/example-21.12-py3.6.egg create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tests/fixtures.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tests/py39compat.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tests/test_api.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tests/test_integration.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tests/test_main.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tests/test_zip.py create mode 100644 testing/web-platform/tests/tools/third_party/importlib_metadata/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/.landscape.yml create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/.travis.yml create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/CHANGELOG create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/README.txt create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/example.ini create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/pyproject.toml create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/src/iniconfig/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/src/iniconfig/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/src/iniconfig/py.typed create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/testing/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/testing/test_iniconfig.py create mode 100644 testing/web-platform/tests/tools/third_party/iniconfig/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/.travis.yml create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/docs/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/docs/api.rst create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/docs/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/docs/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/docs/license.rst create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/docs/make.bat create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/docs/testing.rst create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/docs/versions.rst create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/more_itertools/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/more_itertools/more.py create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/more_itertools/recipes.py create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/more_itertools/tests/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/more_itertools/tests/test_more.py create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/more_itertools/tests/test_recipes.py create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/more-itertools/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/packaging/.coveragerc create mode 100644 testing/web-platform/tests/tools/third_party/packaging/.flake8 create mode 100644 testing/web-platform/tests/tools/third_party/packaging/.github/workflows/docs.yml create mode 100644 testing/web-platform/tests/tools/third_party/packaging/.github/workflows/lint.yml create mode 100644 testing/web-platform/tests/tools/third_party/packaging/.github/workflows/test.yml create mode 100644 testing/web-platform/tests/tools/third_party/packaging/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/packaging/.pre-commit-config.yaml create mode 100644 testing/web-platform/tests/tools/third_party/packaging/.readthedocs.yml create mode 100644 testing/web-platform/tests/tools/third_party/packaging/CHANGELOG.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/CONTRIBUTING.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/packaging/LICENSE.APACHE create mode 100644 testing/web-platform/tests/tools/third_party/packaging/LICENSE.BSD create mode 100644 testing/web-platform/tests/tools/third_party/packaging/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/packaging/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/_static/.empty create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/changelog.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/development/getting-started.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/development/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/development/release-process.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/development/reviewing-patches.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/development/submitting-patches.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/markers.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/requirements.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/requirements.txt create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/security.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/specifiers.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/tags.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/utils.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/docs/version.rst create mode 100644 testing/web-platform/tests/tools/third_party/packaging/mypy.ini create mode 100644 testing/web-platform/tests/tools/third_party/packaging/noxfile.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/__about__.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/_manylinux.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/_musllinux.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/_structures.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/markers.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/py.typed create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/requirements.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/specifiers.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/tags.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/utils.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/packaging/version.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/pyproject.toml create mode 100644 testing/web-platform/tests/tools/third_party/packaging/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/packaging/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tasks/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tasks/check.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tasks/paths.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tasks/requirements.txt create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/hello-world.c create mode 100755 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/build.sh create mode 100755 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/hello-world-armv7l-armel create mode 100755 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/hello-world-armv7l-armhf create mode 100755 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/hello-world-invalid-class create mode 100755 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/hello-world-invalid-data create mode 100755 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/hello-world-invalid-magic create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/hello-world-s390x-s390x create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/hello-world-too-short create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/hello-world-x86_64-amd64 create mode 100755 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/hello-world-x86_64-i386 create mode 100755 testing/web-platform/tests/tools/third_party/packaging/tests/manylinux/hello-world-x86_64-x32 create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/musllinux/build.sh create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/test_manylinux.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/test_markers.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/test_musllinux.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/test_requirements.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/test_specifiers.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/test_structures.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/test_tags.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/test_utils.py create mode 100644 testing/web-platform/tests/tools/third_party/packaging/tests/test_version.py create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/.travis.yml create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/CHANGELOG.rst create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/LICENSE.rst create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/VERSION create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/appveyor.yml create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/appveyor/install.ps1 create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/codecov.yml create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/pathlib2/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/requirements.txt create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/pathlib2/tests/test_pathlib2.py create mode 100644 testing/web-platform/tests/tools/third_party/pdf_js/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/pdf_js/pdf.js create mode 100644 testing/web-platform/tests/tools/third_party/pdf_js/pdf.worker.js create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/.coveragerc create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/.github/workflows/main.yml create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/.pre-commit-config.yaml create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/CHANGELOG.rst create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/RELEASING.rst create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/changelog/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/changelog/_template.rst create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/codecov.yml create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/_static/img/plug.png create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/api_reference.rst create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/changelog.rst create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/examples/eggsample-spam/eggsample_spam.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/examples/eggsample-spam/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/hookspecs.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/host.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/examples/eggsample/eggsample/lib.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/examples/eggsample/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/examples/toy-example.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/docs/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/pyproject.toml create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/scripts/release.py create mode 100755 testing/web-platform/tests/tools/third_party/pluggy/scripts/upload-coverage.sh create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/src/pluggy/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/src/pluggy/_callers.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/src/pluggy/_hooks.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/src/pluggy/_manager.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/src/pluggy/_result.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/src/pluggy/_tracing.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/testing/benchmark.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/testing/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/testing/test_details.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/testing/test_helpers.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/testing/test_hookcaller.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/testing/test_invocations.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/testing/test_multicall.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/testing/test_pluginmanager.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/testing/test_tracer.py create mode 100644 testing/web-platform/tests/tools/third_party/pluggy/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/py/.flake8 create mode 100644 testing/web-platform/tests/tools/third_party/py/.github/workflows/main.yml create mode 100644 testing/web-platform/tests/tools/third_party/py/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/py/AUTHORS create mode 100644 testing/web-platform/tests/tools/third_party/py/CHANGELOG.rst create mode 100644 testing/web-platform/tests/tools/third_party/py/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/py/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/py/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/py/RELEASING.rst create mode 100644 testing/web-platform/tests/tools/third_party/py/bench/localpath.py create mode 100644 testing/web-platform/tests/tools/third_party/py/codecov.yml create mode 100644 testing/web-platform/tests/tools/third_party/py/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/_templates/layout.html create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-0.9.0.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-0.9.2.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.0.0.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.0.1.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.0.2.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.1.0.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.1.1.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.2.0.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.2.1.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.3.0.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.3.1.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.3.2.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.3.3.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.3.4.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.4.0.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/release-1.4.1.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/announce/releases.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/changelog.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/code.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/download.html create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/example/genhtml.py create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/example/genhtmlcss.py create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/example/genxml.py create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/faq.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/img/pylib.png create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/index.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/install.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/io.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/links.inc create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/log.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/misc.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/path.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/style.css create mode 100644 testing/web-platform/tests/tools/third_party/py/doc/xml.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/py/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/py/py/__metainfo.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_builtin.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_code/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_code/_assertionnew.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_code/_assertionold.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_code/_py2traceback.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_code/assertion.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_code/code.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_code/source.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_error.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_io/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_io/capture.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_io/saferepr.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_io/terminalwriter.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_log/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_log/log.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_log/warning.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_path/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_path/cacheutil.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_path/common.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_path/local.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_path/svnurl.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_path/svnwc.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_process/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_process/cmdexec.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_process/forkedfunc.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_process/killproc.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_std.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/apipkg-2.0.0.dist-info/INSTALLER create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/apipkg-2.0.0.dist-info/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/apipkg-2.0.0.dist-info/METADATA create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/apipkg-2.0.0.dist-info/RECORD create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/apipkg-2.0.0.dist-info/REQUESTED create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/apipkg-2.0.0.dist-info/WHEEL create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/apipkg-2.0.0.dist-info/top_level.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/apipkg/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/apipkg/version.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/iniconfig-1.1.1.dist-info/INSTALLER create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/iniconfig-1.1.1.dist-info/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/iniconfig-1.1.1.dist-info/METADATA create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/iniconfig-1.1.1.dist-info/RECORD create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/iniconfig-1.1.1.dist-info/REQUESTED create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/iniconfig-1.1.1.dist-info/WHEEL create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/iniconfig-1.1.1.dist-info/top_level.txt create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/iniconfig/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/iniconfig/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_vendored_packages/iniconfig/py.typed create mode 100644 testing/web-platform/tests/tools/third_party/py/py/_xmlgen.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/error.pyi create mode 100644 testing/web-platform/tests/tools/third_party/py/py/iniconfig.pyi create mode 100644 testing/web-platform/tests/tools/third_party/py/py/io.pyi create mode 100644 testing/web-platform/tests/tools/third_party/py/py/path.pyi create mode 100644 testing/web-platform/tests/tools/third_party/py/py/py.typed create mode 100644 testing/web-platform/tests/tools/third_party/py/py/test.py create mode 100644 testing/web-platform/tests/tools/third_party/py/py/xml.pyi create mode 100644 testing/web-platform/tests/tools/third_party/py/pyproject.toml create mode 100644 testing/web-platform/tests/tools/third_party/py/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/py/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/py/tasks/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/tasks/vendoring.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/code/test_assertion.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/code/test_code.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/code/test_excinfo.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/code/test_source.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/io_/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/io_/test_capture.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/io_/test_saferepr.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/io_/test_terminalwriter.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/io_/test_terminalwriter_linewidth.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/log/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/log/test_log.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/log/test_warning.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/path/common.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/path/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/path/repotest.dump create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/path/svntestbase.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/path/test_cacheutil.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/path/test_local.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/path/test_svnauth.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/path/test_svnurl.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/path/test_svnwc.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/process/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/process/test_cmdexec.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/process/test_forkedfunc.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/process/test_killproc.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/root/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/root/test_builtin.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/root/test_error.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/root/test_py_imports.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/root/test_std.py create mode 100644 testing/web-platform/tests/tools/third_party/py/testing/root/test_xmlgen.py create mode 100644 testing/web-platform/tests/tools/third_party/py/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/PKG-INFO create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/PKG-INFO create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/SOURCES.txt create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/dependency_links.txt create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/entry_points.txt create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/requires.txt create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/pytest_asyncio.egg-info/top_level.txt create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/pytest_asyncio/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/pytest_asyncio/plugin.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/pytest-asyncio/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.coveragerc create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.gitblameignore create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/FUNDING.yml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/1_bug_report.md create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/2_feature_request.md create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/ISSUE_TEMPLATE/config.yml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/PULL_REQUEST_TEMPLATE.md create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/config.yml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/dependabot.yml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/labels.toml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/workflows/main.yml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/workflows/prepare-release-pr.yml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.github/workflows/update-plugin-list.yml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.pre-commit-config.yaml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/.readthedocs.yml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/AUTHORS create mode 100644 testing/web-platform/tests/tools/third_party/pytest/CHANGELOG.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/CITATION create mode 100644 testing/web-platform/tests/tools/third_party/pytest/CODE_OF_CONDUCT.md create mode 100644 testing/web-platform/tests/tools/third_party/pytest/CONTRIBUTING.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/pytest/OPENCOLLECTIVE.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/RELEASING.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/TIDELIFT.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/bench/bench.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/bench/bench_argcomplete.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/bench/empty.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/bench/manyparam.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/bench/skip.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/bench/unit_test.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/bench/xunit.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/changelog/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/changelog/_template.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/codecov.yml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/_templates/globaltoc.html create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/_templates/layout.html create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/_templates/links.html create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/_templates/relations.html create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/_templates/sidebarintro.html create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/_templates/slim_searchbox.html create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/adopt.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.0.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.0.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.0.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.0.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.1.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.1.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.1.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.1.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.2.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.2.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.2.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.2.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.3.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.3.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.3.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.3.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.3.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.3.5.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.4.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.4.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.4.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.5.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.5.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.5.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.6.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.6.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.6.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.6.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.7.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.7.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.7.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.8.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.8.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.8.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.8.5.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.8.6.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.8.7.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.9.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.9.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-2.9.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.0.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.0.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.0.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.0.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.0.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.0.5.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.0.6.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.0.7.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.1.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.1.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.1.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.1.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.10.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.10.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.2.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.2.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.2.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.2.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.2.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.2.5.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.3.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.3.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.3.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.4.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.4.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.4.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.5.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.5.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.6.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.6.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.6.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.6.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.6.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.7.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.7.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.7.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.7.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.7.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.8.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.8.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.8.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.9.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.9.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.9.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-3.9.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.0.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.0.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.0.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.1.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.1.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.2.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.2.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.3.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.3.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.4.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.4.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.4.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.5.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.6.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.6.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.6.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.6.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.6.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.6.5.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.6.6.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.6.7.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.6.8.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-4.6.9.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.0.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.0.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.1.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.1.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.1.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.1.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.2.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.2.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.2.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.2.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.2.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.3.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.3.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.3.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.3.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.3.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.3.5.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.4.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.4.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.4.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-5.4.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.0.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.0.0rc1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.0.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.0.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.1.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.1.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.1.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.2.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.2.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.2.2.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.2.3.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.2.4.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-6.2.5.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-7.0.0.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-7.0.0rc1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/release-7.0.1.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/announce/sprint2016.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/backwards-compatibility.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/builtin.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/changelog.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/contact.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/contents.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/contributing.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/deprecations.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/development_guide.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/assertion/failure_demo.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/assertion/global_testmodule_config/test_hello_world.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/assertion/test_failures.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/assertion/test_setup_flow_example.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/attic.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/fixture_availability.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/fixture_availability_plugins.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse_flat.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies_flat.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_dependencies_unclear.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_scope.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_order_scope.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_request_different_scope.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/fixtures/test_fixtures_request_different_scope.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/markers.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/multipython.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/nonpython.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/nonpython/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/nonpython/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/nonpython/test_simple.yaml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/parametrize.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/pythoncollection.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/pythoncollection.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/reportingdemo.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/simple.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/special.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/example/xfail_demo.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/explanation/anatomy.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/explanation/fixtures.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/explanation/flaky.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/explanation/goodpractices.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/explanation/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/explanation/pythonpath.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/funcarg_compare.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/funcargs.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/getting-started.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/historical-notes.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/history.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/assert.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/bash-completion.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/cache.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/capture-stdout-stderr.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/capture-warnings.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/doctest.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/existingtestsuite.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/failures.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/fixtures.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/logging.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/mark.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/monkeypatch.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/nose.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/output.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/parametrize.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/plugins.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/skipping.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/tmp_path.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/unittest.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/usage.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/writing_hook_functions.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/writing_plugins.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/how-to/xunit_setup.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/img/cramer2.png create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/img/favicon.png create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/img/freiburg2.jpg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/img/gaynor3.png create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/img/keleshev.png create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/img/pullrequest.png create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/img/pylib.png create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/img/pytest1.png create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/img/pytest_logo_curves.svg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/img/theuni.png create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/license.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/naming20.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/proposals/parametrize_with_fixtures.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/py27-py34-deprecation.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/pytest.ini create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/recwarn.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/reference/customize.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/reference/exit-codes.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/reference/fixtures.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/reference/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/reference/plugin_list.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/reference/reference.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/requirements.txt create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/sponsor.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/talks.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/tidelift.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/doc/en/yieldfixture.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/extra/get_issues.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/extra/setup-py.test/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/pyproject.toml create mode 100644 testing/web-platform/tests/tools/third_party/pytest/scripts/prepare-release-pr.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/scripts/publish-gh-release-notes.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/scripts/release.major.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/scripts/release.minor.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/scripts/release.patch.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/scripts/release.pre.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/scripts/release.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/scripts/towncrier-draft-to-file.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/scripts/update-plugin-list.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/pytest/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/_argcomplete.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/_code/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/_code/code.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/_code/source.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/_io/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/_io/saferepr.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/_io/terminalwriter.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/_io/wcwidth.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/_version.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/assertion/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/assertion/rewrite.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/assertion/truncate.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/assertion/util.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/cacheprovider.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/capture.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/compat.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/config/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/config/argparsing.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/config/compat.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/config/exceptions.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/config/findpaths.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/debugging.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/deprecated.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/doctest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/faulthandler.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/fixtures.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/freeze_support.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/helpconfig.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/hookspec.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/junitxml.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/legacypath.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/logging.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/main.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/mark/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/mark/expression.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/mark/structures.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/monkeypatch.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/nodes.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/nose.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/outcomes.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/pastebin.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/pathlib.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/py.typed create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/pytester.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/pytester_assertions.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/python.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/python_api.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/python_path.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/recwarn.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/reports.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/runner.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/scope.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/setuponly.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/setupplan.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/skipping.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/stash.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/stepwise.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/terminal.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/threadexception.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/timing.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/tmpdir.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/unittest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/unraisableexception.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/warning_types.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/_pytest/warnings.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/pytest/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/pytest/__main__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/pytest/collect.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/src/pytest/py.typed create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/acceptance_test.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/code/test_code.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/code/test_excinfo.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/code/test_source.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/deprecated_test.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/acceptance/fixture_mock_integration.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/pytest.ini create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/config/collect_pytest_prefix/test_foo.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/conftest_usageerror/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/__main__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/doctest/main_py/test_normal_module.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/custom_item/foo/test_foo.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_fixture_named_request.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/issue_519.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/junit-10.xsd create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/__init__.pyi create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/generate_folders.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/perf_examples/collect_stats/template_test.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/pytest.ini create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/tmpdir/tmp_path_fixture.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_class.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/unittest/test_setup_skip_module.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asyncio.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_asynctest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/unittest/test_unittest_plain_async.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/examples/test_issue519.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/freeze/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/freeze/create_executable.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/freeze/runtests_script.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/freeze/tests/test_doctest.txt create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/freeze/tests/test_trivial.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/freeze/tox_run.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/io/test_saferepr.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/io/test_terminalwriter.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/io/test_wcwidth.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/logging/test_fixture.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/logging/test_formatter.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/logging/test_reporting.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.feature create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/bdd_wallet.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/django_settings.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/pytest.ini create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/pytest_anyio_integration.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/pytest_asyncio_integration.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/pytest_mock_integration.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/pytest_trio_integration.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/pytest_twisted_integration.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/requirements.txt create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/plugins_integration/simple_integration.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/python/approx.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/python/collect.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/python/fixtures.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/python/integration.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/python/metafunc.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/python/raises.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/python/show_fixtures_per_test.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_argcomplete.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_assertion.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_assertrewrite.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_cacheprovider.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_capture.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_collection.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_compat.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_config.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_debugging.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_doctest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_entry_points.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_error_diffs.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_faulthandler.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_findpaths.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_helpconfig.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_junitxml.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_legacypath.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_link_resolve.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_main.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_mark.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_mark_expression.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_meta.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_monkeypatch.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_nodes.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_nose.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_parseopt.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_pastebin.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_pathlib.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_pluginmanager.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_pytester.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_python_path.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_recwarn.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_reports.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_runner.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_runner_xunit.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_scope.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_session.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_setuponly.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_setupplan.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_skipping.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_stash.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_stepwise.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_terminal.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_threadexception.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_tmpdir.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_unittest.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_unraisableexception.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_warning_types.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/test_warnings.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/testing/typing_checks.py create mode 100644 testing/web-platform/tests/tools/third_party/pytest/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/.travis.yml create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/CONTRIBUTING create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/README.md create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/abort_handshake_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/abort_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/arraybuffer_benchmark.html create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/bench_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/benchmark.html create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/benchmark.js create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/benchmark_helper_wsh.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/example/cgi-bin/hi.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/close_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/console.html create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/cookie_wsh.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/example/echo_client.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/echo_noext_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/echo_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/handler_map.txt create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/hsts_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/internal_error_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/origin_check_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/performance_test_iframe.html create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/performance_test_iframe.js create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/example/special_headers.cgi create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/util.js create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/util_main.js create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/example/util_worker.js create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/_stream_exceptions.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/common.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/dispatch.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/extensions.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/fast_masking.i create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/handshake/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/handshake/base.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/handshake/hybi.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/http_header_util.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/memorizingfile.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/msgutil.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/request_handler.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/server_util.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/standalone.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/stream.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/util.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/mod_pywebsocket/websocket_server.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/cert/cacert.pem create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/cert/cert.pem create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/cert/client_cert.p12 create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/cert/key.pem create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/client_for_testing.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/mock.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/run_all.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/set_sys_path.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_dispatch.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_endtoend.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_extensions.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_handshake.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_handshake_hybi.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_http_header_util.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_memorizingfile.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_mock.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_msgutil.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_stream.py create mode 100755 testing/web-platform/tests/tools/third_party/pywebsocket3/test/test_util.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/README create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/handlers/abort_by_user_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/handlers/blank_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/handlers/origin_check_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/exception_in_transfer_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/no_wsh_at_the_end.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/non_callable_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/plain_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/wrong_handshake_sig_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/handlers/sub/wrong_transfer_sig_wsh.py create mode 100644 testing/web-platform/tests/tools/third_party/pywebsocket3/test/testdata/hello.pl create mode 100644 testing/web-platform/tests/tools/third_party/six/CHANGES create mode 100644 testing/web-platform/tests/tools/third_party/six/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/six/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/six/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/six/documentation/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/six/documentation/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/six/documentation/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/six/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/six/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/six/six-1.15.0.dist-info/INSTALLER create mode 100644 testing/web-platform/tests/tools/third_party/six/six-1.15.0.dist-info/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/six/six-1.15.0.dist-info/METADATA create mode 100644 testing/web-platform/tests/tools/third_party/six/six-1.15.0.dist-info/RECORD create mode 100644 testing/web-platform/tests/tools/third_party/six/six-1.15.0.dist-info/WHEEL create mode 100644 testing/web-platform/tests/tools/third_party/six/six-1.15.0.dist-info/top_level.txt create mode 100644 testing/web-platform/tests/tools/third_party/six/six.py create mode 100644 testing/web-platform/tests/tools/third_party/six/test_six.py create mode 100755 testing/web-platform/tests/tools/third_party/tooltool/tooltool.py create mode 100644 testing/web-platform/tests/tools/third_party/webencodings/PKG-INFO create mode 100644 testing/web-platform/tests/tools/third_party/webencodings/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/webencodings/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/webencodings/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/webencodings/webencodings/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/webencodings/webencodings/labels.py create mode 100644 testing/web-platform/tests/tools/third_party/webencodings/webencodings/mklabels.py create mode 100644 testing/web-platform/tests/tools/third_party/webencodings/webencodings/tests.py create mode 100644 testing/web-platform/tests/tools/third_party/webencodings/webencodings/x_user_defined.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/.appveyor.yml create mode 100644 testing/web-platform/tests/tools/third_party/websockets/.circleci/config.yml create mode 100644 testing/web-platform/tests/tools/third_party/websockets/.github/FUNDING.yml create mode 100644 testing/web-platform/tests/tools/third_party/websockets/.gitignore create mode 100644 testing/web-platform/tests/tools/third_party/websockets/.readthedocs.yml create mode 100644 testing/web-platform/tests/tools/third_party/websockets/.travis.yml create mode 100644 testing/web-platform/tests/tools/third_party/websockets/CODE_OF_CONDUCT.md create mode 100644 testing/web-platform/tests/tools/third_party/websockets/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/websockets/MANIFEST.in create mode 100644 testing/web-platform/tests/tools/third_party/websockets/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/websockets/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/compliance/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/compliance/fuzzingclient.json create mode 100644 testing/web-platform/tests/tools/third_party/websockets/compliance/fuzzingserver.json create mode 100644 testing/web-platform/tests/tools/third_party/websockets/compliance/test_client.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/compliance/test_server.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/Makefile create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/_static/tidelift.png create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/_static/websockets.svg create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/api.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/changelog.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/cheatsheet.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/contributing.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/deployment.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/design.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/extensions.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/faq.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/intro.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/license.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/lifecycle.graffle create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/lifecycle.svg create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/limitations.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/protocol.graffle create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/protocol.svg create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/requirements.txt create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/security.rst create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/spelling_wordlist.txt create mode 100644 testing/web-platform/tests/tools/third_party/websockets/docs/tidelift.rst create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/basic_auth_client.py create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/basic_auth_server.py create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/client.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/example/counter.html create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/counter.py create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/echo.py create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/health_check_server.py create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/hello.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/example/localhost.pem create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/secure_client.py create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/secure_server.py create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/server.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/example/show_time.html create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/show_time.py create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/shutdown.py create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/unix_client.py create mode 100755 testing/web-platform/tests/tools/third_party/websockets/example/unix_server.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/logo/horizontal.svg create mode 100644 testing/web-platform/tests/tools/third_party/websockets/logo/icon.svg create mode 100644 testing/web-platform/tests/tools/third_party/websockets/logo/old.svg create mode 100644 testing/web-platform/tests/tools/third_party/websockets/logo/tidelift.png create mode 100644 testing/web-platform/tests/tools/third_party/websockets/logo/vertical.svg create mode 100644 testing/web-platform/tests/tools/third_party/websockets/performance/mem_client.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/performance/mem_server.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/websockets/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/__main__.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/auth.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/client.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/exceptions.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/extensions/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/extensions/base.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/extensions/permessage_deflate.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/framing.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/handshake.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/headers.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/http.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/protocol.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/py.typed create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/server.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/speedups.c create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/speedups.pyi create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/typing.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/uri.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/utils.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/src/websockets/version.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/extensions/__init__.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/extensions/test_base.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/extensions/test_permessage_deflate.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_auth.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_client_server.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_exceptions.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_exports.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_framing.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_handshake.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_headers.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_http.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_localhost.cnf create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_localhost.pem create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_protocol.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_uri.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/test_utils.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tests/utils.py create mode 100644 testing/web-platform/tests/tools/third_party/websockets/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/zipp/.flake8 create mode 100644 testing/web-platform/tests/tools/third_party/zipp/.github/workflows/main.yml create mode 100644 testing/web-platform/tests/tools/third_party/zipp/.pre-commit-config.yaml create mode 100644 testing/web-platform/tests/tools/third_party/zipp/.readthedocs.yml create mode 100644 testing/web-platform/tests/tools/third_party/zipp/.travis.yml create mode 100644 testing/web-platform/tests/tools/third_party/zipp/CHANGES.rst create mode 100644 testing/web-platform/tests/tools/third_party/zipp/LICENSE create mode 100644 testing/web-platform/tests/tools/third_party/zipp/PKG-INFO create mode 100644 testing/web-platform/tests/tools/third_party/zipp/README.rst create mode 100644 testing/web-platform/tests/tools/third_party/zipp/appveyor.yml create mode 100644 testing/web-platform/tests/tools/third_party/zipp/conftest.py create mode 100644 testing/web-platform/tests/tools/third_party/zipp/docs/conf.py create mode 100644 testing/web-platform/tests/tools/third_party/zipp/docs/history.rst create mode 100644 testing/web-platform/tests/tools/third_party/zipp/docs/index.rst create mode 100644 testing/web-platform/tests/tools/third_party/zipp/mypy.ini create mode 100644 testing/web-platform/tests/tools/third_party/zipp/pyproject.toml create mode 100644 testing/web-platform/tests/tools/third_party/zipp/pytest.ini create mode 100644 testing/web-platform/tests/tools/third_party/zipp/setup.cfg create mode 100644 testing/web-platform/tests/tools/third_party/zipp/setup.py create mode 100644 testing/web-platform/tests/tools/third_party/zipp/skeleton.md create mode 100644 testing/web-platform/tests/tools/third_party/zipp/test_zipp.py create mode 100644 testing/web-platform/tests/tools/third_party/zipp/tox.ini create mode 100644 testing/web-platform/tests/tools/third_party/zipp/zipp.egg-info/PKG-INFO create mode 100644 testing/web-platform/tests/tools/third_party/zipp/zipp.egg-info/SOURCES.txt create mode 100644 testing/web-platform/tests/tools/third_party/zipp/zipp.egg-info/dependency_links.txt create mode 100644 testing/web-platform/tests/tools/third_party/zipp/zipp.egg-info/requires.txt create mode 100644 testing/web-platform/tests/tools/third_party/zipp/zipp.egg-info/top_level.txt create mode 100644 testing/web-platform/tests/tools/third_party/zipp/zipp.py (limited to 'testing/web-platform/tests/tools/third_party') diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/.gitignore b/testing/web-platform/tests/tools/third_party/atomicwrites/.gitignore new file mode 100644 index 0000000000..2b2d312875 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/.gitignore @@ -0,0 +1,9 @@ +.tox +*.pyc +*.pyo +__pycache__ +*.egg-info +docs/_build +build +dist +.cache diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/.travis.yml b/testing/web-platform/tests/tools/third_party/atomicwrites/.travis.yml new file mode 100644 index 0000000000..e9779018ad --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/.travis.yml @@ -0,0 +1,35 @@ +sudo: false +os: linux +language: python + +matrix: + include: + - os: osx + language: generic + env: TOXENV_SUFFIX=test + +python: + - "2.6" + - "2.7" + - "pypy" + - "3.3" + - "3.4" + +install: + - # The OS X VM doesn't have any Python support at all + # See https://github.com/travis-ci/travis-ci/issues/2312 + if [ "$TRAVIS_OS_NAME" = "osx" ]; then + brew update; + brew install python3; + virtualenv -p python3 $HOME/osx-py3; + . $HOME/osx-py3/bin/activate; + export TRAVIS_PYTHON_VERSION="$(python --version | cut -d ' ' -f 2 | cut -d . -f -2)"; + fi + - pip install tox + +script: + - export TOX_PY="$(echo py$TRAVIS_PYTHON_VERSION | tr -d . | sed -e 's/pypypy/pypy/')" + - tox -e $TOX_PY-test + - if [ "$TRAVIS_PYTHON_VERSION" = "2.7" ] || [ "$TRAVIS_PYTHON_VERSION" = ".3.5" ]; then + tox -e $TOX_PY-stylecheck; + fi diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/CONTRIBUTING.rst b/testing/web-platform/tests/tools/third_party/atomicwrites/CONTRIBUTING.rst new file mode 100644 index 0000000000..86d3e4a65e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/CONTRIBUTING.rst @@ -0,0 +1,11 @@ +Thanks for contributing to python-atomicwrites! This document is a +work-in-progress. Below are a few notes that are useful for writing patches. + +Running the tests +================= + +:: + + pip install tox + tox -e py-test + tox -e py-stylecheck diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/LICENSE b/testing/web-platform/tests/tools/third_party/atomicwrites/LICENSE new file mode 100644 index 0000000000..3bbadc3af2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2016 Markus Unterwaditzer + +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/testing/web-platform/tests/tools/third_party/atomicwrites/MANIFEST.in b/testing/web-platform/tests/tools/third_party/atomicwrites/MANIFEST.in new file mode 100644 index 0000000000..1b28469174 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/MANIFEST.in @@ -0,0 +1,6 @@ +include LICENSE +include README.rst + +recursive-include docs * +recursive-include tests * +prune docs/_build diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/Makefile b/testing/web-platform/tests/tools/third_party/atomicwrites/Makefile new file mode 100644 index 0000000000..d257e7b673 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/Makefile @@ -0,0 +1,2 @@ +release: + python setup.py sdist bdist_wheel upload diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/README.rst b/testing/web-platform/tests/tools/third_party/atomicwrites/README.rst new file mode 100644 index 0000000000..3a5658cbd8 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/README.rst @@ -0,0 +1,102 @@ +=================== +python-atomicwrites +=================== + +.. image:: https://travis-ci.org/untitaker/python-atomicwrites.svg?branch=master + :target: https://travis-ci.org/untitaker/python-atomicwrites + +.. image:: https://ci.appveyor.com/api/projects/status/vadc4le3c27to59x/branch/master?svg=true + :target: https://ci.appveyor.com/project/untitaker/python-atomicwrites/branch/master + +Atomic file writes. + +.. code-block:: python + + from atomicwrites import atomic_write + + with atomic_write('foo.txt', overwrite=True) as f: + f.write('Hello world.') + # "foo.txt" doesn't exist yet. + + # Now it does. + + +Features that distinguish it from other similar libraries (see `Alternatives and Credit`_): + +- Race-free assertion that the target file doesn't yet exist. This can be + controlled with the ``overwrite`` parameter. + +- Windows support, although not well-tested. The MSDN resources are not very + explicit about which operations are atomic. + +- Simple high-level API that wraps a very flexible class-based API. + +- Consistent error handling across platforms. + + +How it works +============ + +It uses a temporary file in the same directory as the given path. This ensures +that the temporary file resides on the same filesystem. + +The temporary file will then be atomically moved to the target location: On +POSIX, it will use ``rename`` if files should be overwritten, otherwise a +combination of ``link`` and ``unlink``. On Windows, it uses MoveFileEx_ through +stdlib's ``ctypes`` with the appropriate flags. + +Note that with ``link`` and ``unlink``, there's a timewindow where the file +might be available under two entries in the filesystem: The name of the +temporary file, and the name of the target file. + +Also note that the permissions of the target file may change this way. In some +situations a ``chmod`` can be issued without any concurrency problems, but +since that is not always the case, this library doesn't do it by itself. + +.. _MoveFileEx: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365240%28v=vs.85%29.aspx + +fsync +----- + +On POSIX, ``fsync`` is invoked on the temporary file after it is written (to +flush file content and metadata), and on the parent directory after the file is +moved (to flush filename). + +``fsync`` does not take care of disks' internal buffers, but there don't seem +to be any standard POSIX APIs for that. On OS X, ``fcntl`` is used with +``F_FULLFSYNC`` instead of ``fsync`` for that reason. + +On Windows, `_commit `_ +is used, but there are no guarantees about disk internal buffers. + +Alternatives and Credit +======================= + +Atomicwrites is directly inspired by the following libraries (and shares a +minimal amount of code): + +- The Trac project's `utility functions + `_, + also used in `Werkzeug `_ and + `mitsuhiko/python-atomicfile + `_. The idea to use + ``ctypes`` instead of ``PyWin32`` originated there. + +- `abarnert/fatomic `_. Windows support + (based on ``PyWin32``) was originally taken from there. + +Other alternatives to atomicwrites include: + +- `sashka/atomicfile `_. Originally I + considered using that, but at the time it was lacking a lot of features I + needed (Windows support, overwrite-parameter, overriding behavior through + subclassing). + +- The `Boltons library collection `_ + features a class for atomic file writes, which seems to have a very similar + ``overwrite`` parameter. It is lacking Windows support though. + +License +======= + +Licensed under the MIT, see ``LICENSE``. diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/appveyor.yml b/testing/web-platform/tests/tools/third_party/atomicwrites/appveyor.yml new file mode 100644 index 0000000000..a5d47a0768 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/appveyor.yml @@ -0,0 +1,18 @@ +build: false # Not a C# project, build stuff at the test step instead. +environment: + matrix: + - PYTHON: "C:/Python27" + - PYTHON: "C:/Python33" + - PYTHON: "C:/Python34" + +init: + - "ECHO %PYTHON%" + - ps: "ls C:/Python*" + +install: + - ps: (new-object net.webclient).DownloadFile('https://bootstrap.pypa.io/get-pip.py', 'C:/get-pip.py') + - "%PYTHON%/python.exe C:/get-pip.py" + - "%PYTHON%/Scripts/pip.exe install tox" + +test_script: + - "%PYTHON%/Scripts/tox.exe -e py-test" diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/atomicwrites/__init__.py b/testing/web-platform/tests/tools/third_party/atomicwrites/atomicwrites/__init__.py new file mode 100644 index 0000000000..a182c07afd --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/atomicwrites/__init__.py @@ -0,0 +1,201 @@ +import contextlib +import os +import sys +import tempfile + +try: + import fcntl +except ImportError: + fcntl = None + +__version__ = '1.1.5' + + +PY2 = sys.version_info[0] == 2 + +text_type = unicode if PY2 else str # noqa + + +def _path_to_unicode(x): + if not isinstance(x, text_type): + return x.decode(sys.getfilesystemencoding()) + return x + + +_proper_fsync = os.fsync + + +if sys.platform != 'win32': + if hasattr(fcntl, 'F_FULLFSYNC'): + def _proper_fsync(fd): + # https://lists.apple.com/archives/darwin-dev/2005/Feb/msg00072.html + # https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/fsync.2.html + # https://github.com/untitaker/python-atomicwrites/issues/6 + fcntl.fcntl(fd, fcntl.F_FULLFSYNC) + + def _sync_directory(directory): + # Ensure that filenames are written to disk + fd = os.open(directory, 0) + try: + _proper_fsync(fd) + finally: + os.close(fd) + + def _replace_atomic(src, dst): + os.rename(src, dst) + _sync_directory(os.path.normpath(os.path.dirname(dst))) + + def _move_atomic(src, dst): + os.link(src, dst) + os.unlink(src) + + src_dir = os.path.normpath(os.path.dirname(src)) + dst_dir = os.path.normpath(os.path.dirname(dst)) + _sync_directory(dst_dir) + if src_dir != dst_dir: + _sync_directory(src_dir) +else: + from ctypes import windll, WinError + + _MOVEFILE_REPLACE_EXISTING = 0x1 + _MOVEFILE_WRITE_THROUGH = 0x8 + _windows_default_flags = _MOVEFILE_WRITE_THROUGH + + def _handle_errors(rv): + if not rv: + raise WinError() + + def _replace_atomic(src, dst): + _handle_errors(windll.kernel32.MoveFileExW( + _path_to_unicode(src), _path_to_unicode(dst), + _windows_default_flags | _MOVEFILE_REPLACE_EXISTING + )) + + def _move_atomic(src, dst): + _handle_errors(windll.kernel32.MoveFileExW( + _path_to_unicode(src), _path_to_unicode(dst), + _windows_default_flags + )) + + +def replace_atomic(src, dst): + ''' + Move ``src`` to ``dst``. If ``dst`` exists, it will be silently + overwritten. + + Both paths must reside on the same filesystem for the operation to be + atomic. + ''' + return _replace_atomic(src, dst) + + +def move_atomic(src, dst): + ''' + Move ``src`` to ``dst``. There might a timewindow where both filesystem + entries exist. If ``dst`` already exists, :py:exc:`FileExistsError` will be + raised. + + Both paths must reside on the same filesystem for the operation to be + atomic. + ''' + return _move_atomic(src, dst) + + +class AtomicWriter(object): + ''' + A helper class for performing atomic writes. Usage:: + + with AtomicWriter(path).open() as f: + f.write(...) + + :param path: The destination filepath. May or may not exist. + :param mode: The filemode for the temporary file. + :param overwrite: If set to false, an error is raised if ``path`` exists. + Errors are only raised after the file has been written to. Either way, + the operation is atomic. + + If you need further control over the exact behavior, you are encouraged to + subclass. + ''' + + def __init__(self, path, mode='w', overwrite=False): + if 'a' in mode: + raise ValueError( + 'Appending to an existing file is not supported, because that ' + 'would involve an expensive `copy`-operation to a temporary ' + 'file. Open the file in normal `w`-mode and copy explicitly ' + 'if that\'s what you\'re after.' + ) + if 'x' in mode: + raise ValueError('Use the `overwrite`-parameter instead.') + if 'w' not in mode: + raise ValueError('AtomicWriters can only be written to.') + + self._path = path + self._mode = mode + self._overwrite = overwrite + + def open(self): + ''' + Open the temporary file. + ''' + return self._open(self.get_fileobject) + + @contextlib.contextmanager + def _open(self, get_fileobject): + f = None # make sure f exists even if get_fileobject() fails + try: + success = False + with get_fileobject() as f: + yield f + self.sync(f) + self.commit(f) + success = True + finally: + if not success: + try: + self.rollback(f) + except Exception: + pass + + def get_fileobject(self, dir=None, **kwargs): + '''Return the temporary file to use.''' + if dir is None: + dir = os.path.normpath(os.path.dirname(self._path)) + return tempfile.NamedTemporaryFile(mode=self._mode, dir=dir, + delete=False, **kwargs) + + def sync(self, f): + '''responsible for clearing as many file caches as possible before + commit''' + f.flush() + _proper_fsync(f.fileno()) + + def commit(self, f): + '''Move the temporary file to the target location.''' + if self._overwrite: + replace_atomic(f.name, self._path) + else: + move_atomic(f.name, self._path) + + def rollback(self, f): + '''Clean up all temporary resources.''' + os.unlink(f.name) + + +def atomic_write(path, writer_cls=AtomicWriter, **cls_kwargs): + ''' + Simple atomic writes. This wraps :py:class:`AtomicWriter`:: + + with atomic_write(path) as f: + f.write(...) + + :param path: The target path to write to. + :param writer_cls: The writer class to use. This parameter is useful if you + subclassed :py:class:`AtomicWriter` to change some behavior and want to + use that new subclass. + + Additional keyword arguments are passed to the writer class. See + :py:class:`AtomicWriter`. + ''' + return writer_cls(path, **cls_kwargs).open() diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/docs/Makefile b/testing/web-platform/tests/tools/third_party/atomicwrites/docs/Makefile new file mode 100644 index 0000000000..af5f9d9aa5 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/atomicwrites.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/atomicwrites.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/atomicwrites" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/atomicwrites" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/docs/conf.py b/testing/web-platform/tests/tools/third_party/atomicwrites/docs/conf.py new file mode 100644 index 0000000000..b7c7b59c4d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/docs/conf.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python + +import os +import sys +import pkg_resources + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'atomicwrites' +copyright = '2015, Markus Unterwaditzer' + +try: + # The full version, including alpha/beta/rc tags. + release = pkg_resources.require('atomicwrites')[0].version +except pkg_resources.DistributionNotFound: + print('To build the documentation, the distribution information of ' + 'atomicwrites has to be available. Run "setup.py develop" to do ' + 'this.') + sys.exit(1) + +version = '.'.join(release.split('.')[:2]) # The short X.Y version. + +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + +try: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +except ImportError: + html_theme = 'default' + if not on_rtd: + print('-' * 74) + print('Warning: sphinx-rtd-theme not installed, building with default ' + 'theme.') + print('-' * 74) + + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + + +# Output file base name for HTML help builder. +htmlhelp_basename = 'atomicwritesdoc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = {} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'atomicwrites.tex', 'atomicwrites Documentation', + 'Markus Unterwaditzer', 'manual'), +] + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'atomicwrites', 'atomicwrites Documentation', + ['Markus Unterwaditzer'], 1) +] + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'atomicwrites', 'atomicwrites Documentation', + 'Markus Unterwaditzer', 'atomicwrites', 'One line description of project.', + 'Miscellaneous'), +] + +# Bibliographic Dublin Core info. +epub_title = 'atomicwrites' +epub_author = 'Markus Unterwaditzer' +epub_publisher = 'Markus Unterwaditzer' +epub_copyright = '2015, Markus Unterwaditzer' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/docs/index.rst b/testing/web-platform/tests/tools/third_party/atomicwrites/docs/index.rst new file mode 100644 index 0000000000..0391c04477 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/docs/index.rst @@ -0,0 +1,35 @@ +.. include:: ../README.rst + +.. module:: atomicwrites + +API +=== + +.. autofunction:: atomic_write + + +Errorhandling +------------- + +All filesystem errors are subclasses of :py:exc:`OSError`. + +- On UNIX systems, errors from the Python stdlib calls are thrown. +- On Windows systems, errors from Python's ``ctypes`` are thrown. + +In either case, the ``errno`` attribute on the thrown exception maps to an +errorcode in the ``errno`` module. + +Low-level API +------------- + +.. autofunction:: replace_atomic + +.. autofunction:: move_atomic + +.. autoclass:: AtomicWriter + :members: + +License +======= + +.. include:: ../LICENSE diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/docs/make.bat b/testing/web-platform/tests/tools/third_party/atomicwrites/docs/make.bat new file mode 100644 index 0000000000..36fd3f6baf --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/docs/make.bat @@ -0,0 +1,242 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\atomicwrites.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\atomicwrites.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/setup.cfg b/testing/web-platform/tests/tools/third_party/atomicwrites/setup.cfg new file mode 100644 index 0000000000..5e4090017a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/setup.cfg @@ -0,0 +1,2 @@ +[wheel] +universal = 1 diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/setup.py b/testing/web-platform/tests/tools/third_party/atomicwrites/setup.py new file mode 100644 index 0000000000..98488e9b98 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/setup.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +import ast +import re + +from setuptools import find_packages, setup + + +_version_re = re.compile(r'__version__\s+=\s+(.*)') + + +with open('atomicwrites/__init__.py', 'rb') as f: + version = str(ast.literal_eval(_version_re.search( + f.read().decode('utf-8')).group(1))) + +setup( + name='atomicwrites', + version=version, + author='Markus Unterwaditzer', + author_email='markus@unterwaditzer.net', + url='https://github.com/untitaker/python-atomicwrites', + description='Atomic file writes.', + license='MIT', + long_description=open('README.rst').read(), + packages=find_packages(exclude=['tests.*', 'tests']), + include_package_data=True, +) diff --git a/testing/web-platform/tests/tools/third_party/atomicwrites/tox.ini b/testing/web-platform/tests/tools/third_party/atomicwrites/tox.ini new file mode 100644 index 0000000000..dfadf03336 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/atomicwrites/tox.ini @@ -0,0 +1,11 @@ +[tox] +envlist = py{26,27,py,33,34,35}-{test,stylecheck} + +[testenv] +deps = + test: pytest + stylecheck: flake8 + stylecheck: flake8-import-order +commands = + test: py.test [] + stylecheck: flake8 [] diff --git a/testing/web-platform/tests/tools/third_party/attrs/.github/CODE_OF_CONDUCT.md b/testing/web-platform/tests/tools/third_party/attrs/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..1d8ad1833e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,133 @@ + +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/testing/web-platform/tests/tools/third_party/attrs/.github/CONTRIBUTING.md b/testing/web-platform/tests/tools/third_party/attrs/.github/CONTRIBUTING.md new file mode 100644 index 0000000000..bbdc20f193 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/.github/CONTRIBUTING.md @@ -0,0 +1,230 @@ +# How To Contribute + +First off, thank you for considering contributing to `attrs`! +It's people like *you* who make it such a great tool for everyone. + +This document intends to make contribution more accessible by codifying tribal knowledge and expectations. +Don't be afraid to open half-finished PRs, and ask questions if something is unclear! + +Please note that this project is released with a Contributor [Code of Conduct](https://github.com/python-attrs/attrs/blob/main/.github/CODE_OF_CONDUCT.md). +By participating in this project you agree to abide by its terms. +Please report any harm to [Hynek Schlawack] in any way you find appropriate. + + +## Support + +In case you'd like to help out but don't want to deal with GitHub, there's a great opportunity: +help your fellow developers on [Stack Overflow](https://stackoverflow.com/questions/tagged/python-attrs)! + +The official tag is `python-attrs` and helping out in support frees us up to improve `attrs` instead! + + +## Workflow + +- No contribution is too small! + Please submit as many fixes for typos and grammar bloopers as you can! +- Try to limit each pull request to *one* change only. +- Since we squash on merge, it's up to you how you handle updates to the main branch. + Whether you prefer to rebase on main or merge main into your branch, do whatever is more comfortable for you. +- *Always* add tests and docs for your code. + This is a hard rule; patches with missing tests or documentation can't be merged. +- Make sure your changes pass our [CI]. + You won't get any feedback until it's green unless you ask for it. +- For the CI to pass, the coverage must be 100%. + If you have problems to test something, open anyway and ask for advice. + In some situations, we may agree to add an `# pragma: no cover`. +- Once you've addressed review feedback, make sure to bump the pull request with a short note, so we know you're done. +- Don’t break backwards compatibility. + + +## Code + +- Obey [PEP 8](https://www.python.org/dev/peps/pep-0008/) and [PEP 257](https://www.python.org/dev/peps/pep-0257/). + We use the `"""`-on-separate-lines style for docstrings: + + ```python + def func(x): + """ + Do something. + + :param str x: A very important parameter. + + :rtype: str + """ + ``` +- If you add or change public APIs, tag the docstring using `.. versionadded:: 16.0.0 WHAT` or `.. versionchanged:: 16.2.0 WHAT`. +- We use [*isort*](https://github.com/PyCQA/isort) to sort our imports, and we use [*Black*](https://github.com/psf/black) with line length of 79 characters to format our code. + As long as you run our full [*tox*] suite before committing, or install our [*pre-commit*] hooks (ideally you'll do both – see [*Local Development Environment*](#local-development-environment) below), you won't have to spend any time on formatting your code at all. + If you don't, [CI] will catch it for you – but that seems like a waste of your time! + + +## Tests + +- Write your asserts as `expected == actual` to line them up nicely: + + ```python + x = f() + + assert 42 == x.some_attribute + assert "foo" == x._a_private_attribute + ``` + +- To run the test suite, all you need is a recent [*tox*]. + It will ensure the test suite runs with all dependencies against all Python versions just as it will in our [CI]. + If you lack some Python versions, you can can always limit the environments like `tox -e py27,py38`, or make it a non-failure using `tox --skip-missing-interpreters`. + + In that case you should look into [*asdf*](https://asdf-vm.com) or [*pyenv*](https://github.com/pyenv/pyenv), which make it very easy to install many different Python versions in parallel. +- Write [good test docstrings](https://jml.io/pages/test-docstrings.html). +- To ensure new features work well with the rest of the system, they should be also added to our [*Hypothesis*](https://hypothesis.readthedocs.io/) testing strategy, which can be found in `tests/strategies.py`. +- If you've changed or added public APIs, please update our type stubs (files ending in `.pyi`). + + +## Documentation + +- Use [semantic newlines] in [*reStructuredText*] files (files ending in `.rst`): + + ```rst + This is a sentence. + This is another sentence. + ``` + +- If you start a new section, add two blank lines before and one blank line after the header, except if two headers follow immediately after each other: + + ```rst + Last line of previous section. + + + Header of New Top Section + ------------------------- + + Header of New Section + ^^^^^^^^^^^^^^^^^^^^^ + + First line of new section. + ``` + +- If you add a new feature, demonstrate its awesomeness on the [examples page](https://github.com/python-attrs/attrs/blob/main/docs/examples.rst)! + + +### Changelog + +If your change is noteworthy, there needs to be a changelog entry so our users can learn about it! + +To avoid merge conflicts, we use the [*towncrier*](https://pypi.org/project/towncrier) package to manage our changelog. +*towncrier* uses independent files for each pull request – so called *news fragments* – instead of one monolithic changelog file. +On release, those news fragments are compiled into our [`CHANGELOG.rst`](https://github.com/python-attrs/attrs/blob/main/CHANGELOG.rst). + +You don't need to install *towncrier* yourself, you just have to abide by a few simple rules: + +- For each pull request, add a new file into `changelog.d` with a filename adhering to the `pr#.(change|deprecation|breaking).rst` schema: + For example, `changelog.d/42.change.rst` for a non-breaking change that is proposed in pull request #42. +- As with other docs, please use [semantic newlines] within news fragments. +- Wrap symbols like modules, functions, or classes into double backticks so they are rendered in a `monospace font`. +- Wrap arguments into asterisks like in docstrings: + `Added new argument *an_argument*.` +- If you mention functions or other callables, add parentheses at the end of their names: + `attrs.func()` or `attrs.Class.method()`. + This makes the changelog a lot more readable. +- Prefer simple past tense or constructions with "now". + For example: + + + Added `attrs.validators.func()`. + + `attrs.func()` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument. +- If you want to reference multiple issues, copy the news fragment to another filename. + *towncrier* will merge all news fragments with identical contents into one entry with multiple links to the respective pull requests. + +Example entries: + + ```rst + Added ``attrs.validators.func()``. + The feature really *is* awesome. + ``` + +or: + + ```rst + ``attrs.func()`` now doesn't crash the Large Hadron Collider anymore when passed the *foobar* argument. + The bug really *was* nasty. + ``` + +--- + +``tox -e changelog`` will render the current changelog to the terminal if you have any doubts. + + +## Local Development Environment + +You can (and should) run our test suite using [*tox*]. +However, you’ll probably want a more traditional environment as well. +We highly recommend to develop using the latest Python release because we try to take advantage of modern features whenever possible. + +First create a [virtual environment](https://virtualenv.pypa.io/) so you don't break your system-wide Python installation. +It’s out of scope for this document to list all the ways to manage virtual environments in Python, but if you don’t already have a pet way, take some time to look at tools like [*direnv*](https://hynek.me/til/python-project-local-venvs/), [*virtualfish*](https://virtualfish.readthedocs.io/), and [*virtualenvwrapper*](https://virtualenvwrapper.readthedocs.io/). + +Next, get an up to date checkout of the `attrs` repository: + +```console +$ git clone git@github.com:python-attrs/attrs.git +``` + +or if you want to use git via `https`: + +```console +$ git clone https://github.com/python-attrs/attrs.git +``` + +Change into the newly created directory and **after activating your virtual environment** install an editable version of `attrs` along with its tests and docs requirements: + +```console +$ cd attrs +$ pip install --upgrade pip setuptools # PLEASE don't skip this step +$ pip install -e '.[dev]' +``` + +At this point, + +```console +$ python -m pytest +``` + +should work and pass, as should: + +```console +$ cd docs +$ make html +``` + +The built documentation can then be found in `docs/_build/html/`. + +To avoid committing code that violates our style guide, we strongly advise you to install [*pre-commit*] [^dev] hooks: + +```console +$ pre-commit install +``` + +You can also run them anytime (as our tox does) using: + +```console +$ pre-commit run --all-files +``` + +[^dev]: *pre-commit* should have been installed into your virtualenv automatically when you ran `pip install -e '.[dev]'` above. + If *pre-commit* is missing, your probably need to run `pip install -e '.[dev]'` again. + + +## Governance + +`attrs` is maintained by [team of volunteers](https://github.com/python-attrs) that is always open to new members that share our vision of a fast, lean, and magic-free library that empowers programmers to write better code with less effort. +If you'd like to join, just get a pull request merged and ask to be added in the very same pull request! + +**The simple rule is that everyone is welcome to review/merge pull requests of others but nobody is allowed to merge their own code.** + +[Hynek Schlawack] acts reluctantly as the [BDFL](https://en.wikipedia.org/wiki/Benevolent_dictator_for_life) and has the final say over design decisions. + + +[CI]: https://github.com/python-attrs/attrs/actions?query=workflow%3ACI +[Hynek Schlawack]: https://hynek.me/about/ +[*pre-commit*]: https://pre-commit.com/ +[*tox*]: https://https://tox.wiki/ +[semantic newlines]: https://rhodesmill.org/brandon/2012/one-sentence-per-line/ +[*reStructuredText*]: https://www.sphinx-doc.org/en/stable/usage/restructuredtext/basics.html diff --git a/testing/web-platform/tests/tools/third_party/attrs/.github/FUNDING.yml b/testing/web-platform/tests/tools/third_party/attrs/.github/FUNDING.yml new file mode 100644 index 0000000000..ef4f212162 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/.github/FUNDING.yml @@ -0,0 +1,5 @@ +--- + +github: hynek +ko_fi: the_hynek +tidelift: "pypi/attrs" diff --git a/testing/web-platform/tests/tools/third_party/attrs/.github/PULL_REQUEST_TEMPLATE.md b/testing/web-platform/tests/tools/third_party/attrs/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000000..88f6415e96 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,34 @@ +# Summary + + + + +# Pull Request Check List + + + +- [ ] Added **tests** for changed code. + Our CI fails if coverage is not 100%. +- [ ] New features have been added to our [Hypothesis testing strategy](https://github.com/python-attrs/attrs/blob/main/tests/strategies.py). +- [ ] Changes or additions to public APIs are reflected in our type stubs (files ending in ``.pyi``). + - [ ] ...and used in the stub test file `tests/typing_example.py`. + - [ ] If they've been added to `attr/__init__.pyi`, they've *also* been re-imported in `attrs/__init__.pyi`. +- [ ] Updated **documentation** for changed code. + - [ ] New functions/classes have to be added to `docs/api.rst` by hand. + - [ ] Changes to the signature of `@attr.s()` have to be added by hand too. + - [ ] Changed/added classes/methods/functions have appropriate `versionadded`, `versionchanged`, or `deprecated` [directives](http://www.sphinx-doc.org/en/stable/markup/para.html#directive-versionadded). + Find the appropriate next version in our [``__init__.py``](https://github.com/python-attrs/attrs/blob/main/src/attr/__init__.py) file. +- [ ] Documentation in `.rst` files is written using [semantic newlines](https://rhodesmill.org/brandon/2012/one-sentence-per-line/). +- [ ] Changes (and possible deprecations) have news fragments in [`changelog.d`](https://github.com/python-attrs/attrs/blob/main/changelog.d). + + diff --git a/testing/web-platform/tests/tools/third_party/attrs/.github/SECURITY.md b/testing/web-platform/tests/tools/third_party/attrs/.github/SECURITY.md new file mode 100644 index 0000000000..5e565ec19c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/.github/SECURITY.md @@ -0,0 +1,2 @@ +To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). +Tidelift will coordinate the fix and disclosure. diff --git a/testing/web-platform/tests/tools/third_party/attrs/.github/workflows/main.yml b/testing/web-platform/tests/tools/third_party/attrs/.github/workflows/main.yml new file mode 100644 index 0000000000..f38fd91509 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/.github/workflows/main.yml @@ -0,0 +1,113 @@ +--- +name: CI + +on: + push: + branches: ["main"] + pull_request: + branches: ["main"] + workflow_dispatch: + +env: + FORCE_COLOR: "1" # Make tools pretty. + TOX_TESTENV_PASSENV: FORCE_COLOR + PYTHON_LATEST: "3.10" + + +jobs: + tests: + name: tox on ${{ matrix.python-version }} + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + python-version: ["2.7", "3.5", "3.6", "3.7", "3.8", "3.9", "3.10", "pypy-2.7", "pypy-3.7", "pypy-3.8"] + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: "Install dependencies" + run: | + python -VV + python -m site + python -m pip install --upgrade pip setuptools wheel + python -m pip install --upgrade virtualenv tox tox-gh-actions + + - run: "python -m tox" + + - name: Upload coverage data + uses: "actions/upload-artifact@v2" + with: + name: coverage-data + path: ".coverage.*" + if-no-files-found: ignore + + + coverage: + runs-on: ubuntu-latest + needs: tests + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + # Use latest Python, so it understands all syntax. + python-version: ${{env.PYTHON_LATEST}} + + - run: python -m pip install --upgrade coverage[toml] + + - name: Download coverage data + uses: actions/download-artifact@v2 + with: + name: coverage-data + + - name: Combine coverage and fail if it's <100%. + run: | + python -m coverage combine + python -m coverage html --skip-covered --skip-empty + python -m coverage report --fail-under=100 + + - name: Upload HTML report if check failed. + uses: actions/upload-artifact@v2 + with: + name: html-report + path: htmlcov + if: ${{ failure() }} + + + package: + name: Build & verify package + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{env.PYTHON_LATEST}} + + - run: python -m pip install build twine check-wheel-contents + - run: python -m build --sdist --wheel . + - run: ls -l dist + - run: check-wheel-contents dist/*.whl + - name: Check long_description + run: python -m twine check dist/* + + + install-dev: + name: Verify dev env + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: ["ubuntu-latest", "windows-latest"] + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-python@v2 + with: + python-version: ${{env.PYTHON_LATEST}} + - run: python -m pip install -e .[dev] + - run: python -c 'import attr; print(attr.__version__)' diff --git a/testing/web-platform/tests/tools/third_party/attrs/.gitignore b/testing/web-platform/tests/tools/third_party/attrs/.gitignore new file mode 100644 index 0000000000..d054dc6267 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/.gitignore @@ -0,0 +1,13 @@ +*.egg-info +*.pyc +.cache +.coverage* +.hypothesis +.mypy_cache +.pytest_cache +.tox +build +dist +docs/_build/ +htmlcov +pip-wheel-metadata diff --git a/testing/web-platform/tests/tools/third_party/attrs/.pre-commit-config.yaml b/testing/web-platform/tests/tools/third_party/attrs/.pre-commit-config.yaml new file mode 100644 index 0000000000..a913b068f5 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/.pre-commit-config.yaml @@ -0,0 +1,43 @@ +--- +ci: + autoupdate_schedule: monthly + +repos: + - repo: https://github.com/psf/black + rev: 21.12b0 + hooks: + - id: black + exclude: tests/test_pattern_matching.py + language_version: python3.10 + + - repo: https://github.com/PyCQA/isort + rev: 5.10.1 + hooks: + - id: isort + additional_dependencies: [toml] + files: \.py$ + language_version: python3.10 + + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 + hooks: + - id: flake8 + language_version: python3.10 + + - repo: https://github.com/econchick/interrogate + rev: 1.5.0 + hooks: + - id: interrogate + exclude: tests/test_pattern_matching.py + args: [tests] + language_version: python3.10 + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.1.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: debug-statements + language_version: python3.10 + - id: check-toml + - id: check-yaml diff --git a/testing/web-platform/tests/tools/third_party/attrs/.readthedocs.yml b/testing/web-platform/tests/tools/third_party/attrs/.readthedocs.yml new file mode 100644 index 0000000000..d335c40d56 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/.readthedocs.yml @@ -0,0 +1,16 @@ +--- +version: 2 +formats: all + +build: + os: ubuntu-20.04 + tools: + # Keep version in sync with tox.ini (docs and gh-actions). + python: "3.10" + +python: + install: + - method: pip + path: . + extra_requirements: + - docs diff --git a/testing/web-platform/tests/tools/third_party/attrs/AUTHORS.rst b/testing/web-platform/tests/tools/third_party/attrs/AUTHORS.rst new file mode 100644 index 0000000000..f14ef6c607 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/AUTHORS.rst @@ -0,0 +1,11 @@ +Credits +======= + +``attrs`` is written and maintained by `Hynek Schlawack `_. + +The development is kindly supported by `Variomedia AG `_. + +A full list of contributors can be found in `GitHub's overview `_. + +It’s the spiritual successor of `characteristic `_ and aspires to fix some of it clunkiness and unfortunate decisions. +Both were inspired by Twisted’s `FancyEqMixin `_ but both are implemented using class decorators because `subclassing is bad for you `_, m’kay? diff --git a/testing/web-platform/tests/tools/third_party/attrs/CHANGELOG.rst b/testing/web-platform/tests/tools/third_party/attrs/CHANGELOG.rst new file mode 100644 index 0000000000..1d194add22 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/CHANGELOG.rst @@ -0,0 +1,1027 @@ +Changelog +========= + +Versions follow `CalVer `_ with a strict backwards-compatibility policy. + +The **first number** of the version is the year. +The **second number** is incremented with each release, starting at 1 for each year. +The **third number** is when we need to start branches for older releases (only for emergencies). + +Put simply, you shouldn't ever be afraid to upgrade ``attrs`` if you're only using its public APIs. +Whenever there is a need to break compatibility, it is announced here in the changelog, and raises a ``DeprecationWarning`` for a year (if possible) before it's finally really broken. + +.. warning:: + + The structure of the `attrs.Attribute` class is exempt from this rule. + It *will* change in the future, but since it should be considered read-only, that shouldn't matter. + + However if you intend to build extensions on top of ``attrs`` you have to anticipate that. + +.. towncrier release notes start + +21.4.0 (2021-12-29) +------------------- + +Changes +^^^^^^^ + +- Fixed the test suite on PyPy3.8 where ``cloudpickle`` does not work. + `#892 `_ +- Fixed ``coverage report`` for projects that use ``attrs`` and don't set a ``--source``. + `#895 `_, + `#896 `_ + + +---- + + +21.3.0 (2021-12-28) +------------------- + +Backward-incompatible Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- When using ``@define``, converters are now run by default when setting an attribute on an instance -- additionally to validators. + I.e. the new default is ``on_setattr=[attrs.setters.convert, attrs.setters.validate]``. + + This is unfortunately a breaking change, but it was an oversight, impossible to raise a ``DeprecationWarning`` about, and it's better to fix it now while the APIs are very fresh with few users. + `#835 `_, + `#886 `_ +- ``import attrs`` has finally landed! + As of this release, you can finally import ``attrs`` using its proper name. + + Not all names from the ``attr`` namespace have been transferred; most notably ``attr.s`` and ``attr.ib`` are missing. + See ``attrs.define`` and ``attrs.field`` if you haven't seen our next-generation APIs yet. + A more elaborate explanation can be found `On The Core API Names `_ + + This feature is at least for one release **provisional**. + We don't *plan* on changing anything, but such a big change is unlikely to go perfectly on the first strike. + + The API docs have been mostly updated, but it will be an ongoing effort to change everything to the new APIs. + Please note that we have **not** moved -- or even removed -- anything from ``attr``! + + Please do report any bugs or documentation inconsistencies! + `#887 `_ + + +Changes +^^^^^^^ + +- ``attr.asdict(retain_collection_types=False)`` (default) dumps collection-esque keys as tuples. + `#646 `_, + `#888 `_ +- ``__match_args__`` are now generated to support Python 3.10's + `Structural Pattern Matching `_. + This can be controlled by the ``match_args`` argument to the class decorators on Python 3.10 and later. + On older versions, it is never added and the argument is ignored. + `#815 `_ +- If the class-level *on_setattr* is set to ``attrs.setters.validate`` (default in ``@define`` and ``@mutable``) but no field defines a validator, pretend that it's not set. + `#817 `_ +- The generated ``__repr__`` is significantly faster on Pythons with f-strings. + `#819 `_ +- Attributes transformed via ``field_transformer`` are wrapped with ``AttrsClass`` again. + `#824 `_ +- Generated source code is now cached more efficiently for identical classes. + `#828 `_ +- Added ``attrs.converters.to_bool()``. + `#830 `_ +- ``attrs.resolve_types()`` now resolves types of subclasses after the parents are resolved. + `#842 `_ + `#843 `_ +- Added new validators: ``lt(val)`` (< val), ``le(va)`` (≤ val), ``ge(val)`` (≥ val), ``gt(val)`` (> val), and ``maxlen(n)``. + `#845 `_ +- ``attrs`` classes are now fully compatible with `cloudpickle `_ (no need to disable ``repr`` anymore). + `#857 `_ +- Added new context manager ``attrs.validators.disabled()`` and functions ``attrs.validators.(set|get)_disabled()``. + They deprecate ``attrs.(set|get)_run_validators()``. + All functions are interoperable and modify the same internal state. + They are not – and never were – thread-safe, though. + `#859 `_ +- ``attrs.validators.matches_re()`` now accepts pre-compiled regular expressions in addition to pattern strings. + `#877 `_ + + +---- + + +21.2.0 (2021-05-07) +------------------- + +Backward-incompatible Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- We had to revert the recursive feature for ``attr.evolve()`` because it broke some use-cases -- sorry! + `#806 `_ +- Python 3.4 is now blocked using packaging metadata because ``attrs`` can't be imported on it anymore. + To ensure that 3.4 users can keep installing ``attrs`` easily, we will `yank `_ 21.1.0 from PyPI. + This has **no** consequences if you pin ``attrs`` to 21.1.0. + `#807 `_ + + +---- + + +21.1.0 (2021-05-06) +------------------- + +Deprecations +^^^^^^^^^^^^ + +- The long-awaited, much-talked-about, little-delivered ``import attrs`` is finally upon us! + + Since the NG APIs have now been proclaimed stable, the **next** release of ``attrs`` will allow you to actually ``import attrs``. + We're taking this opportunity to replace some defaults in our APIs that made sense in 2015, but don't in 2021. + + So please, if you have any pet peeves about defaults in ``attrs``'s APIs, *now* is the time to air your grievances in #487! + We're not gonna get such a chance for a second time, without breaking our backward-compatibility guarantees, or long deprecation cycles. + Therefore, speak now or forever hold you peace! + `#487 `_ +- The *cmp* argument to ``attr.s()`` and `attr.ib()` has been **undeprecated** + It will continue to be supported as syntactic sugar to set *eq* and *order* in one go. + + I'm terribly sorry for the hassle around this argument! + The reason we're bringing it back is it's usefulness regarding customization of equality/ordering. + + The ``cmp`` attribute and argument on ``attr.Attribute`` remains deprecated and will be removed later this year. + `#773 `_ + + +Changes +^^^^^^^ + +- It's now possible to customize the behavior of ``eq`` and ``order`` by passing in a callable. + `#435 `_, + `#627 `_ +- The instant favorite next-generation APIs are not provisional anymore! + + They are also officially supported by Mypy as of their `0.800 release `_. + + We hope the next release will already contain an (additional) importable package called ``attrs``. + `#668 `_, + `#786 `_ +- If an attribute defines a converter, the type of its parameter is used as type annotation for its corresponding ``__init__`` parameter. + + If an ``attr.converters.pipe`` is used, the first one's is used. + `#710 `_ +- Fixed the creation of an extra slot for an ``attr.ib`` when the parent class already has a slot with the same name. + `#718 `_ +- ``__attrs__init__()`` will now be injected if ``init=False``, or if ``auto_detect=True`` and a user-defined ``__init__()`` exists. + + This enables users to do "pre-init" work in their ``__init__()`` (such as ``super().__init__()``). + + ``__init__()`` can then delegate constructor argument processing to ``self.__attrs_init__(*args, **kwargs)``. + `#731 `_ +- ``bool(attr.NOTHING)`` is now ``False``. + `#732 `_ +- It's now possible to use ``super()`` inside of properties of slotted classes. + `#747 `_ +- Allow for a ``__attrs_pre_init__()`` method that -- if defined -- will get called at the beginning of the ``attrs``-generated ``__init__()`` method. + `#750 `_ +- Added forgotten ``attr.Attribute.evolve()`` to type stubs. + `#752 `_ +- ``attrs.evolve()`` now works recursively with nested ``attrs`` classes. + `#759 `_ +- Python 3.10 is now officially supported. + `#763 `_ +- ``attr.resolve_types()`` now takes an optional *attrib* argument to work inside a ``field_transformer``. + `#774 `_ +- ``ClassVar``\ s are now also detected if they come from `typing-extensions `_. + `#782 `_ +- To make it easier to customize attribute comparison (#435), we have added the ``attr.cmp_with()`` helper. + + See the `new docs on comparison `_ for more details. + `#787 `_ +- Added **provisional** support for static typing in ``pyright`` via the `dataclass_transforms specification `_. + Both the ``pyright`` specification and ``attrs`` implementation may change in future versions of both projects. + + Your constructive feedback is welcome in both `attrs#795 `_ and `pyright#1782 `_. + `#796 `_ + + +---- + + +20.3.0 (2020-11-05) +------------------- + +Backward-incompatible Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- ``attr.define()``, ``attr.frozen()``, ``attr.mutable()``, and ``attr.field()`` remain **provisional**. + + This release does **not** change anything about them and they are already used widely in production though. + + If you wish to use them together with mypy, you can simply drop `this plugin `_ into your project. + + Feel free to provide feedback to them in the linked issue #668. + + We will release the ``attrs`` namespace once we have the feeling that the APIs have properly settled. + `#668 `_ + + +Changes +^^^^^^^ + +- ``attr.s()`` now has a *field_transformer* hook that is called for all ``Attribute``\ s and returns a (modified or updated) list of ``Attribute`` instances. + ``attr.asdict()`` has a *value_serializer* hook that can change the way values are converted. + Both hooks are meant to help with data (de-)serialization workflows. + `#653 `_ +- ``kw_only=True`` now works on Python 2. + `#700 `_ +- ``raise from`` now works on frozen classes on PyPy. + `#703 `_, + `#712 `_ +- ``attr.asdict()`` and ``attr.astuple()`` now treat ``frozenset``\ s like ``set``\ s with regards to the *retain_collection_types* argument. + `#704 `_ +- The type stubs for ``attr.s()`` and ``attr.make_class()`` are not missing the *collect_by_mro* argument anymore. + `#711 `_ + + +---- + + +20.2.0 (2020-09-05) +------------------- + +Backward-incompatible Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- ``attr.define()``, ``attr.frozen()``, ``attr.mutable()``, and ``attr.field()`` remain **provisional**. + + This release fixes a bunch of bugs and ergonomics but they remain mostly unchanged. + + If you wish to use them together with mypy, you can simply drop `this plugin `_ into your project. + + Feel free to provide feedback to them in the linked issue #668. + + We will release the ``attrs`` namespace once we have the feeling that the APIs have properly settled. + `#668 `_ + + +Changes +^^^^^^^ + +- ``attr.define()`` et al now correct detect ``__eq__`` and ``__ne__``. + `#671 `_ +- ``attr.define()`` et al's hybrid behavior now also works correctly when arguments are passed. + `#675 `_ +- It's possible to define custom ``__setattr__`` methods on slotted classes again. + `#681 `_ +- In 20.1.0 we introduced the ``inherited`` attribute on the ``attr.Attribute`` class to differentiate attributes that have been inherited and those that have been defined directly on the class. + + It has shown to be problematic to involve that attribute when comparing instances of ``attr.Attribute`` though, because when sub-classing, attributes from base classes are suddenly not equal to themselves in a super class. + + Therefore the ``inherited`` attribute will now be ignored when hashing and comparing instances of ``attr.Attribute``. + `#684 `_ +- ``zope.interface`` is now a "soft dependency" when running the test suite; if ``zope.interface`` is not installed when running the test suite, the interface-related tests will be automatically skipped. + `#685 `_ +- The ergonomics of creating frozen classes using ``@define(frozen=True)`` and sub-classing frozen classes has been improved: + you don't have to set ``on_setattr=None`` anymore. + `#687 `_ + + +---- + + +20.1.0 (2020-08-20) +------------------- + +Backward-incompatible Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Python 3.4 is not supported anymore. + It has been unsupported by the Python core team for a while now, its PyPI downloads are negligible, and our CI provider removed it as a supported option. + + It's very unlikely that ``attrs`` will break under 3.4 anytime soon, which is why we do *not* block its installation on Python 3.4. + But we don't test it anymore and will block it once someone reports breakage. + `#608 `_ + + +Deprecations +^^^^^^^^^^^^ + +- Less of a deprecation and more of a heads up: the next release of ``attrs`` will introduce an ``attrs`` namespace. + That means that you'll finally be able to run ``import attrs`` with new functions that aren't cute abbreviations and that will carry better defaults. + + This should not break any of your code, because project-local packages have priority before installed ones. + If this is a problem for you for some reason, please report it to our bug tracker and we'll figure something out. + + The old ``attr`` namespace isn't going anywhere and its defaults are not changing – this is a purely additive measure. + Please check out the linked issue for more details. + + These new APIs have been added *provisionally* as part of #666 so you can try them out today and provide feedback. + Learn more in the `API docs `_. + `#408 `_ + + +Changes +^^^^^^^ + +- Added ``attr.resolve_types()``. + It ensures that all forward-references and types in string form are resolved into concrete types. + + You need this only if you need concrete types at runtime. + That means that if you only use types for static type checking, you do **not** need this function. + `#288 `_, + `#302 `_ +- Added ``@attr.s(collect_by_mro=False)`` argument that if set to ``True`` fixes the collection of attributes from base classes. + + It's only necessary for certain cases of multiple-inheritance but is kept off for now for backward-compatibility reasons. + It will be turned on by default in the future. + + As a side-effect, ``attr.Attribute`` now *always* has an ``inherited`` attribute indicating whether an attribute on a class was directly defined or inherited. + `#428 `_, + `#635 `_ +- On Python 3, all generated methods now have a docstring explaining that they have been created by ``attrs``. + `#506 `_ +- It is now possible to prevent ``attrs`` from auto-generating the ``__setstate__`` and ``__getstate__`` methods that are required for pickling of slotted classes. + + Either pass ``@attr.s(getstate_setstate=False)`` or pass ``@attr.s(auto_detect=True)`` and implement them yourself: + if ``attrs`` finds either of the two methods directly on the decorated class, it assumes implicitly ``getstate_setstate=False`` (and implements neither). + + This option works with dict classes but should never be necessary. + `#512 `_, + `#513 `_, + `#642 `_ +- Fixed a ``ValueError: Cell is empty`` bug that could happen in some rare edge cases. + `#590 `_ +- ``attrs`` can now automatically detect your own implementations and infer ``init=False``, ``repr=False``, ``eq=False``, ``order=False``, and ``hash=False`` if you set ``@attr.s(auto_detect=True)``. + ``attrs`` will ignore inherited methods. + If the argument implies more than one method (e.g. ``eq=True`` creates both ``__eq__`` and ``__ne__``), it's enough for *one* of them to exist and ``attrs`` will create *neither*. + + This feature requires Python 3. + `#607 `_ +- Added ``attr.converters.pipe()``. + The feature allows combining multiple conversion callbacks into one by piping the value through all of them, and retuning the last result. + + As part of this feature, we had to relax the type information for converter callables. + `#618 `_ +- Fixed serialization behavior of non-slots classes with ``cache_hash=True``. + The hash cache will be cleared on operations which make "deep copies" of instances of classes with hash caching, + though the cache will not be cleared with shallow copies like those made by ``copy.copy()``. + + Previously, ``copy.deepcopy()`` or serialization and deserialization with ``pickle`` would result in an un-initialized object. + + This change also allows the creation of ``cache_hash=True`` classes with a custom ``__setstate__``, + which was previously forbidden (`#494 `_). + `#620 `_ +- It is now possible to specify hooks that are called whenever an attribute is set **after** a class has been instantiated. + + You can pass ``on_setattr`` both to ``@attr.s()`` to set the default for all attributes on a class, and to ``@attr.ib()`` to overwrite it for individual attributes. + + ``attrs`` also comes with a new module ``attr.setters`` that brings helpers that run validators, converters, or allow to freeze a subset of attributes. + `#645 `_, + `#660 `_ +- **Provisional** APIs called ``attr.define()``, ``attr.mutable()``, and ``attr.frozen()`` have been added. + + They are only available on Python 3.6 and later, and call ``attr.s()`` with different default values. + + If nothing comes up, they will become the official way for creating classes in 20.2.0 (see above). + + **Please note** that it may take some time until mypy – and other tools that have dedicated support for ``attrs`` – recognize these new APIs. + Please **do not** open issues on our bug tracker, there is nothing we can do about it. + `#666 `_ +- We have also provisionally added ``attr.field()`` that supplants ``attr.ib()``. + It also requires at least Python 3.6 and is keyword-only. + Other than that, it only dropped a few arguments, but changed no defaults. + + As with ``attr.s()``: ``attr.ib()`` is not going anywhere. + `#669 `_ + + +---- + + +19.3.0 (2019-10-15) +------------------- + +Changes +^^^^^^^ + +- Fixed ``auto_attribs`` usage when default values cannot be compared directly with ``==``, such as ``numpy`` arrays. + `#585 `_ + + +---- + + +19.2.0 (2019-10-01) +------------------- + +Backward-incompatible Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Removed deprecated ``Attribute`` attribute ``convert`` per scheduled removal on 2019/1. + This planned deprecation is tracked in issue `#307 `_. + `#504 `_ +- ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` do not consider subclasses comparable anymore. + + This has been deprecated since 18.2.0 and was raising a ``DeprecationWarning`` for over a year. + `#570 `_ + + +Deprecations +^^^^^^^^^^^^ + +- The ``cmp`` argument to ``attr.s()`` and ``attr.ib()`` is now deprecated. + + Please use ``eq`` to add equality methods (``__eq__`` and ``__ne__``) and ``order`` to add ordering methods (``__lt__``, ``__le__``, ``__gt__``, and ``__ge__``) instead – just like with `dataclasses `_. + + Both are effectively ``True`` by default but it's enough to set ``eq=False`` to disable both at once. + Passing ``eq=False, order=True`` explicitly will raise a ``ValueError`` though. + + Since this is arguably a deeper backward-compatibility break, it will have an extended deprecation period until 2021-06-01. + After that day, the ``cmp`` argument will be removed. + + ``attr.Attribute`` also isn't orderable anymore. + `#574 `_ + + +Changes +^^^^^^^ + +- Updated ``attr.validators.__all__`` to include new validators added in `#425`_. + `#517 `_ +- Slotted classes now use a pure Python mechanism to rewrite the ``__class__`` cell when rebuilding the class, so ``super()`` works even on environments where ``ctypes`` is not installed. + `#522 `_ +- When collecting attributes using ``@attr.s(auto_attribs=True)``, attributes with a default of ``None`` are now deleted too. + `#523 `_, + `#556 `_ +- Fixed ``attr.validators.deep_iterable()`` and ``attr.validators.deep_mapping()`` type stubs. + `#533 `_ +- ``attr.validators.is_callable()`` validator now raises an exception ``attr.exceptions.NotCallableError``, a subclass of ``TypeError``, informing the received value. + `#536 `_ +- ``@attr.s(auto_exc=True)`` now generates classes that are hashable by ID, as the documentation always claimed it would. + `#543 `_, + `#563 `_ +- Added ``attr.validators.matches_re()`` that checks string attributes whether they match a regular expression. + `#552 `_ +- Keyword-only attributes (``kw_only=True``) and attributes that are excluded from the ``attrs``'s ``__init__`` (``init=False``) now can appear before mandatory attributes. + `#559 `_ +- The fake filename for generated methods is now more stable. + It won't change when you restart the process. + `#560 `_ +- The value passed to ``@attr.ib(repr=…)`` can now be either a boolean (as before) or a callable. + That callable must return a string and is then used for formatting the attribute by the generated ``__repr__()`` method. + `#568 `_ +- Added ``attr.__version_info__`` that can be used to reliably check the version of ``attrs`` and write forward- and backward-compatible code. + Please check out the `section on deprecated APIs `_ on how to use it. + `#580 `_ + + .. _`#425`: https://github.com/python-attrs/attrs/issues/425 + + +---- + + +19.1.0 (2019-03-03) +------------------- + +Backward-incompatible Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Fixed a bug where deserialized objects with ``cache_hash=True`` could have incorrect hash code values. + This change breaks classes with ``cache_hash=True`` when a custom ``__setstate__`` is present. + An exception will be thrown when applying the ``attrs`` annotation to such a class. + This limitation is tracked in issue `#494 `_. + `#482 `_ + + +Changes +^^^^^^^ + +- Add ``is_callable``, ``deep_iterable``, and ``deep_mapping`` validators. + + * ``is_callable``: validates that a value is callable + * ``deep_iterable``: Allows recursion down into an iterable, + applying another validator to every member in the iterable + as well as applying an optional validator to the iterable itself. + * ``deep_mapping``: Allows recursion down into the items in a mapping object, + applying a key validator and a value validator to the key and value in every item. + Also applies an optional validator to the mapping object itself. + + You can find them in the ``attr.validators`` package. + `#425`_ +- Fixed stub files to prevent errors raised by mypy's ``disallow_any_generics = True`` option. + `#443 `_ +- Attributes with ``init=False`` now can follow after ``kw_only=True`` attributes. + `#450 `_ +- ``attrs`` now has first class support for defining exception classes. + + If you define a class using ``@attr.s(auto_exc=True)`` and subclass an exception, the class will behave like a well-behaved exception class including an appropriate ``__str__`` method, and all attributes additionally available in an ``args`` attribute. + `#500 `_ +- Clarified documentation for hashing to warn that hashable objects should be deeply immutable (in their usage, even if this is not enforced). + `#503 `_ + + +---- + + +18.2.0 (2018-09-01) +------------------- + +Deprecations +^^^^^^^^^^^^ + +- Comparing subclasses using ``<``, ``>``, ``<=``, and ``>=`` is now deprecated. + The docs always claimed that instances are only compared if the types are identical, so this is a first step to conform to the docs. + + Equality operators (``==`` and ``!=``) were always strict in this regard. + `#394 `_ + + +Changes +^^^^^^^ + +- ``attrs`` now ships its own `PEP 484 `_ type hints. + Together with `mypy `_'s ``attrs`` plugin, you've got all you need for writing statically typed code in both Python 2 and 3! + + At that occasion, we've also added `narrative docs `_ about type annotations in ``attrs``. + `#238 `_ +- Added *kw_only* arguments to ``attr.ib`` and ``attr.s``, and a corresponding *kw_only* attribute to ``attr.Attribute``. + This change makes it possible to have a generated ``__init__`` with keyword-only arguments on Python 3, relaxing the required ordering of default and non-default valued attributes. + `#281 `_, + `#411 `_ +- The test suite now runs with ``hypothesis.HealthCheck.too_slow`` disabled to prevent CI breakage on slower computers. + `#364 `_, + `#396 `_ +- ``attr.validators.in_()`` now raises a ``ValueError`` with a useful message even if the options are a string and the value is not a string. + `#383 `_ +- ``attr.asdict()`` now properly handles deeply nested lists and dictionaries. + `#395 `_ +- Added ``attr.converters.default_if_none()`` that allows to replace ``None`` values in attributes. + For example ``attr.ib(converter=default_if_none(""))`` replaces ``None`` by empty strings. + `#400 `_, + `#414 `_ +- Fixed a reference leak where the original class would remain live after being replaced when ``slots=True`` is set. + `#407 `_ +- Slotted classes can now be made weakly referenceable by passing ``@attr.s(weakref_slot=True)``. + `#420 `_ +- Added *cache_hash* option to ``@attr.s`` which causes the hash code to be computed once and stored on the object. + `#426 `_ +- Attributes can be named ``property`` and ``itemgetter`` now. + `#430 `_ +- It is now possible to override a base class' class variable using only class annotations. + `#431 `_ + + +---- + + +18.1.0 (2018-05-03) +------------------- + +Changes +^^^^^^^ + +- ``x=X(); x.cycle = x; repr(x)`` will no longer raise a ``RecursionError``, and will instead show as ``X(x=...)``. + + `#95 `_ +- ``attr.ib(factory=f)`` is now syntactic sugar for the common case of ``attr.ib(default=attr.Factory(f))``. + + `#178 `_, + `#356 `_ +- Added ``attr.field_dict()`` to return an ordered dictionary of ``attrs`` attributes for a class, whose keys are the attribute names. + + `#290 `_, + `#349 `_ +- The order of attributes that are passed into ``attr.make_class()`` or the *these* argument of ``@attr.s()`` is now retained if the dictionary is ordered (i.e. ``dict`` on Python 3.6 and later, ``collections.OrderedDict`` otherwise). + + Before, the order was always determined by the order in which the attributes have been defined which may not be desirable when creating classes programatically. + + `#300 `_, + `#339 `_, + `#343 `_ +- In slotted classes, ``__getstate__`` and ``__setstate__`` now ignore the ``__weakref__`` attribute. + + `#311 `_, + `#326 `_ +- Setting the cell type is now completely best effort. + This fixes ``attrs`` on Jython. + + We cannot make any guarantees regarding Jython though, because our test suite cannot run due to dependency incompatabilities. + + `#321 `_, + `#334 `_ +- If ``attr.s`` is passed a *these* argument, it will no longer attempt to remove attributes with the same name from the class body. + + `#322 `_, + `#323 `_ +- The hash of ``attr.NOTHING`` is now vegan and faster on 32bit Python builds. + + `#331 `_, + `#332 `_ +- The overhead of instantiating frozen dict classes is virtually eliminated. + `#336 `_ +- Generated ``__init__`` methods now have an ``__annotations__`` attribute derived from the types of the fields. + + `#363 `_ +- We have restructured the documentation a bit to account for ``attrs``' growth in scope. + Instead of putting everything into the `examples `_ page, we have started to extract narrative chapters. + + So far, we've added chapters on `initialization `_ and `hashing `_. + + Expect more to come! + + `#369 `_, + `#370 `_ + + +---- + + +17.4.0 (2017-12-30) +------------------- + +Backward-incompatible Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- The traversal of MROs when using multiple inheritance was backward: + If you defined a class ``C`` that subclasses ``A`` and ``B`` like ``C(A, B)``, ``attrs`` would have collected the attributes from ``B`` *before* those of ``A``. + + This is now fixed and means that in classes that employ multiple inheritance, the output of ``__repr__`` and the order of positional arguments in ``__init__`` changes. + Because of the nature of this bug, a proper deprecation cycle was unfortunately impossible. + + Generally speaking, it's advisable to prefer ``kwargs``-based initialization anyways – *especially* if you employ multiple inheritance and diamond-shaped hierarchies. + + `#298 `_, + `#299 `_, + `#304 `_ +- The ``__repr__`` set by ``attrs`` no longer produces an ``AttributeError`` when the instance is missing some of the specified attributes (either through deleting or after using ``init=False`` on some attributes). + + This can break code that relied on ``repr(attr_cls_instance)`` raising ``AttributeError`` to check if any ``attrs``-specified members were unset. + + If you were using this, you can implement a custom method for checking this:: + + def has_unset_members(self): + for field in attr.fields(type(self)): + try: + getattr(self, field.name) + except AttributeError: + return True + return False + + `#308 `_ + + +Deprecations +^^^^^^^^^^^^ + +- The ``attr.ib(convert=callable)`` option is now deprecated in favor of ``attr.ib(converter=callable)``. + + This is done to achieve consistency with other noun-based arguments like *validator*. + + *convert* will keep working until at least January 2019 while raising a ``DeprecationWarning``. + + `#307 `_ + + +Changes +^^^^^^^ + +- Generated ``__hash__`` methods now hash the class type along with the attribute values. + Until now the hashes of two classes with the same values were identical which was a bug. + + The generated method is also *much* faster now. + + `#261 `_, + `#295 `_, + `#296 `_ +- ``attr.ib``\ ’s *metadata* argument now defaults to a unique empty ``dict`` instance instead of sharing a common empty ``dict`` for all. + The singleton empty ``dict`` is still enforced. + + `#280 `_ +- ``ctypes`` is optional now however if it's missing, a bare ``super()`` will not work in slotted classes. + This should only happen in special environments like Google App Engine. + + `#284 `_, + `#286 `_ +- The attribute redefinition feature introduced in 17.3.0 now takes into account if an attribute is redefined via multiple inheritance. + In that case, the definition that is closer to the base of the class hierarchy wins. + + `#285 `_, + `#287 `_ +- Subclasses of ``auto_attribs=True`` can be empty now. + + `#291 `_, + `#292 `_ +- Equality tests are *much* faster now. + + `#306 `_ +- All generated methods now have correct ``__module__``, ``__name__``, and (on Python 3) ``__qualname__`` attributes. + + `#309 `_ + + +---- + + +17.3.0 (2017-11-08) +------------------- + +Backward-incompatible Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Attributes are no longer defined on the class body. + + This means that if you define a class ``C`` with an attribute ``x``, the class will *not* have an attribute ``x`` for introspection. + Instead of ``C.x``, use ``attr.fields(C).x`` or look at ``C.__attrs_attrs__``. + The old behavior has been deprecated since version 16.1. + (`#253 `_) + + +Changes +^^^^^^^ + +- ``super()`` and ``__class__`` now work with slotted classes on Python 3. + (`#102 `_, `#226 `_, `#269 `_, `#270 `_, `#272 `_) +- Added *type* argument to ``attr.ib()`` and corresponding ``type`` attribute to ``attr.Attribute``. + + This change paves the way for automatic type checking and serialization (though as of this release ``attrs`` does not make use of it). + In Python 3.6 or higher, the value of ``attr.Attribute.type`` can alternately be set using variable type annotations + (see `PEP 526 `_). + (`#151 `_, `#214 `_, `#215 `_, `#239 `_) +- The combination of ``str=True`` and ``slots=True`` now works on Python 2. + (`#198 `_) +- ``attr.Factory`` is hashable again. + (`#204 `_) +- Subclasses now can overwrite attribute definitions of their base classes. + + That means that you can -- for example -- change the default value for an attribute by redefining it. + (`#221 `_, `#229 `_) +- Added new option *auto_attribs* to ``@attr.s`` that allows to collect annotated fields without setting them to ``attr.ib()``. + + Setting a field to an ``attr.ib()`` is still possible to supply options like validators. + Setting it to any other value is treated like it was passed as ``attr.ib(default=value)`` -- passing an instance of ``attr.Factory`` also works as expected. + (`#262 `_, `#277 `_) +- Instances of classes created using ``attr.make_class()`` can now be pickled. + (`#282 `_) + + +---- + + +17.2.0 (2017-05-24) +------------------- + + +Changes: +^^^^^^^^ + +- Validators are hashable again. + Note that validators may become frozen in the future, pending availability of no-overhead frozen classes. + `#192 `_ + + +---- + + +17.1.0 (2017-05-16) +------------------- + +To encourage more participation, the project has also been moved into a `dedicated GitHub organization `_ and everyone is most welcome to join! + +``attrs`` also has a logo now! + +.. image:: https://www.attrs.org/en/latest/_static/attrs_logo.png + :alt: attrs logo + + +Backward-incompatible Changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- ``attrs`` will set the ``__hash__()`` method to ``None`` by default now. + The way hashes were handled before was in conflict with `Python's specification `_. + This *may* break some software although this breakage is most likely just surfacing of latent bugs. + You can always make ``attrs`` create the ``__hash__()`` method using ``@attr.s(hash=True)``. + See `#136`_ for the rationale of this change. + + .. warning:: + + Please *do not* upgrade blindly and *do* test your software! + *Especially* if you use instances as dict keys or put them into sets! + +- Correspondingly, ``attr.ib``'s *hash* argument is ``None`` by default too and mirrors the *cmp* argument as it should. + + +Deprecations: +^^^^^^^^^^^^^ + +- ``attr.assoc()`` is now deprecated in favor of ``attr.evolve()`` and will stop working in 2018. + + +Changes: +^^^^^^^^ + +- Fix default hashing behavior. + Now *hash* mirrors the value of *cmp* and classes are unhashable by default. + `#136`_ + `#142 `_ +- Added ``attr.evolve()`` that, given an instance of an ``attrs`` class and field changes as keyword arguments, will instantiate a copy of the given instance with the changes applied. + ``evolve()`` replaces ``assoc()``, which is now deprecated. + ``evolve()`` is significantly faster than ``assoc()``, and requires the class have an initializer that can take the field values as keyword arguments (like ``attrs`` itself can generate). + `#116 `_ + `#124 `_ + `#135 `_ +- ``FrozenInstanceError`` is now raised when trying to delete an attribute from a frozen class. + `#118 `_ +- Frozen-ness of classes is now inherited. + `#128 `_ +- ``__attrs_post_init__()`` is now run if validation is disabled. + `#130 `_ +- Added ``attr.validators.in_(options)`` that, given the allowed ``options``, checks whether the attribute value is in it. + This can be used to check constants, enums, mappings, etc. + `#181 `_ +- Added ``attr.validators.and_()`` that composes multiple validators into one. + `#161 `_ +- For convenience, the *validator* argument of ``@attr.s`` now can take a list of validators that are wrapped using ``and_()``. + `#138 `_ +- Accordingly, ``attr.validators.optional()`` now can take a list of validators too. + `#161 `_ +- Validators can now be defined conveniently inline by using the attribute as a decorator. + Check out the `validator examples `_ to see it in action! + `#143 `_ +- ``attr.Factory()`` now has a *takes_self* argument that makes the initializer to pass the partially initialized instance into the factory. + In other words you can define attribute defaults based on other attributes. + `#165`_ + `#189 `_ +- Default factories can now also be defined inline using decorators. + They are *always* passed the partially initialized instance. + `#165`_ +- Conversion can now be made optional using ``attr.converters.optional()``. + `#105 `_ + `#173 `_ +- ``attr.make_class()`` now accepts the keyword argument ``bases`` which allows for subclassing. + `#152 `_ +- Metaclasses are now preserved with ``slots=True``. + `#155 `_ + +.. _`#136`: https://github.com/python-attrs/attrs/issues/136 +.. _`#165`: https://github.com/python-attrs/attrs/issues/165 + + +---- + + +16.3.0 (2016-11-24) +------------------- + +Changes: +^^^^^^^^ + +- Attributes now can have user-defined metadata which greatly improves ``attrs``'s extensibility. + `#96 `_ +- Allow for a ``__attrs_post_init__()`` method that -- if defined -- will get called at the end of the ``attrs``-generated ``__init__()`` method. + `#111 `_ +- Added ``@attr.s(str=True)`` that will optionally create a ``__str__()`` method that is identical to ``__repr__()``. + This is mainly useful with ``Exception``\ s and other classes that rely on a useful ``__str__()`` implementation but overwrite the default one through a poor own one. + Default Python class behavior is to use ``__repr__()`` as ``__str__()`` anyways. + + If you tried using ``attrs`` with ``Exception``\ s and were puzzled by the tracebacks: this option is for you. +- ``__name__`` is no longer overwritten with ``__qualname__`` for ``attr.s(slots=True)`` classes. + `#99 `_ + + +---- + + +16.2.0 (2016-09-17) +------------------- + +Changes: +^^^^^^^^ + +- Added ``attr.astuple()`` that -- similarly to ``attr.asdict()`` -- returns the instance as a tuple. + `#77 `_ +- Converters now work with frozen classes. + `#76 `_ +- Instantiation of ``attrs`` classes with converters is now significantly faster. + `#80 `_ +- Pickling now works with slotted classes. + `#81 `_ +- ``attr.assoc()`` now works with slotted classes. + `#84 `_ +- The tuple returned by ``attr.fields()`` now also allows to access the ``Attribute`` instances by name. + Yes, we've subclassed ``tuple`` so you don't have to! + Therefore ``attr.fields(C).x`` is equivalent to the deprecated ``C.x`` and works with slotted classes. + `#88 `_ + + +---- + + +16.1.0 (2016-08-30) +------------------- + +Backward-incompatible Changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- All instances where function arguments were called ``cl`` have been changed to the more Pythonic ``cls``. + Since it was always the first argument, it's doubtful anyone ever called those function with in the keyword form. + If so, sorry for any breakage but there's no practical deprecation path to solve this ugly wart. + + +Deprecations: +^^^^^^^^^^^^^ + +- Accessing ``Attribute`` instances on class objects is now deprecated and will stop working in 2017. + If you need introspection please use the ``__attrs_attrs__`` attribute or the ``attr.fields()`` function that carry them too. + In the future, the attributes that are defined on the class body and are usually overwritten in your ``__init__`` method are simply removed after ``@attr.s`` has been applied. + + This will remove the confusing error message if you write your own ``__init__`` and forget to initialize some attribute. + Instead you will get a straightforward ``AttributeError``. + In other words: decorated classes will work more like plain Python classes which was always ``attrs``'s goal. +- The serious business aliases ``attr.attributes`` and ``attr.attr`` have been deprecated in favor of ``attr.attrs`` and ``attr.attrib`` which are much more consistent and frankly obvious in hindsight. + They will be purged from documentation immediately but there are no plans to actually remove them. + + +Changes: +^^^^^^^^ + +- ``attr.asdict()``\ 's ``dict_factory`` arguments is now propagated on recursion. + `#45 `_ +- ``attr.asdict()``, ``attr.has()`` and ``attr.fields()`` are significantly faster. + `#48 `_ + `#51 `_ +- Add ``attr.attrs`` and ``attr.attrib`` as a more consistent aliases for ``attr.s`` and ``attr.ib``. +- Add *frozen* option to ``attr.s`` that will make instances best-effort immutable. + `#60 `_ +- ``attr.asdict()`` now takes ``retain_collection_types`` as an argument. + If ``True``, it does not convert attributes of type ``tuple`` or ``set`` to ``list``. + `#69 `_ + + +---- + + +16.0.0 (2016-05-23) +------------------- + +Backward-incompatible Changes: +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +- Python 3.3 and 2.6 are no longer supported. + They may work by chance but any effort to keep them working has ceased. + + The last Python 2.6 release was on October 29, 2013 and is no longer supported by the CPython core team. + Major Python packages like Django and Twisted dropped Python 2.6 a while ago already. + + Python 3.3 never had a significant user base and wasn't part of any distribution's LTS release. + +Changes: +^^^^^^^^ + +- ``__slots__`` have arrived! + Classes now can automatically be `slotted `_-style (and save your precious memory) just by passing ``slots=True``. + `#35 `_ +- Allow the case of initializing attributes that are set to ``init=False``. + This allows for clean initializer parameter lists while being able to initialize attributes to default values. + `#32 `_ +- ``attr.asdict()`` can now produce arbitrary mappings instead of Python ``dict``\ s when provided with a ``dict_factory`` argument. + `#40 `_ +- Multiple performance improvements. + + +---- + + +15.2.0 (2015-12-08) +------------------- + +Changes: +^^^^^^^^ + +- Added a ``convert`` argument to ``attr.ib``, which allows specifying a function to run on arguments. + This allows for simple type conversions, e.g. with ``attr.ib(convert=int)``. + `#26 `_ +- Speed up object creation when attribute validators are used. + `#28 `_ + + +---- + + +15.1.0 (2015-08-20) +------------------- + +Changes: +^^^^^^^^ + +- Added ``attr.validators.optional()`` that wraps other validators allowing attributes to be ``None``. + `#16 `_ +- Multi-level inheritance now works. + `#24 `_ +- ``__repr__()`` now works with non-redecorated subclasses. + `#20 `_ + + +---- + + +15.0.0 (2015-04-15) +------------------- + +Changes: +^^^^^^^^ + +Initial release. diff --git a/testing/web-platform/tests/tools/third_party/attrs/LICENSE b/testing/web-platform/tests/tools/third_party/attrs/LICENSE new file mode 100644 index 0000000000..7ae3df9309 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Hynek Schlawack + +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/testing/web-platform/tests/tools/third_party/attrs/MANIFEST.in b/testing/web-platform/tests/tools/third_party/attrs/MANIFEST.in new file mode 100644 index 0000000000..3d68bf9c5d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/MANIFEST.in @@ -0,0 +1,24 @@ +include LICENSE *.rst *.toml *.yml *.yaml *.ini +graft .github + +# Stubs +recursive-include src *.pyi +recursive-include src py.typed + +# Tests +include tox.ini conftest.py +recursive-include tests *.py +recursive-include tests *.yml + +# Documentation +include docs/Makefile docs/docutils.conf +recursive-include docs *.png +recursive-include docs *.svg +recursive-include docs *.py +recursive-include docs *.rst +prune docs/_build + +# Just to keep check-manifest happy; on releases those files are gone. +# Last rule wins! +exclude changelog.d/*.rst +include changelog.d/towncrier_template.rst diff --git a/testing/web-platform/tests/tools/third_party/attrs/README.rst b/testing/web-platform/tests/tools/third_party/attrs/README.rst new file mode 100644 index 0000000000..709bba83d7 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/README.rst @@ -0,0 +1,135 @@ +.. raw:: html + +

+ + attrs + +

+

+ + Documentation + + + License: MIT + + + + +

+ +.. teaser-begin + +``attrs`` is the Python package that will bring back the **joy** of **writing classes** by relieving you from the drudgery of implementing object protocols (aka `dunder methods `_). +`Trusted by NASA `_ for Mars missions since 2020! + +Its main goal is to help you to write **concise** and **correct** software without slowing down your code. + +.. teaser-end + +For that, it gives you a class decorator and a way to declaratively define the attributes on that class: + +.. -code-begin- + +.. code-block:: pycon + + >>> from attrs import asdict, define, make_class, Factory + + >>> @define + ... class SomeClass: + ... a_number: int = 42 + ... list_of_numbers: list[int] = Factory(list) + ... + ... def hard_math(self, another_number): + ... return self.a_number + sum(self.list_of_numbers) * another_number + + + >>> sc = SomeClass(1, [1, 2, 3]) + >>> sc + SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) + + >>> sc.hard_math(3) + 19 + >>> sc == SomeClass(1, [1, 2, 3]) + True + >>> sc != SomeClass(2, [3, 2, 1]) + True + + >>> asdict(sc) + {'a_number': 1, 'list_of_numbers': [1, 2, 3]} + + >>> SomeClass() + SomeClass(a_number=42, list_of_numbers=[]) + + >>> C = make_class("C", ["a", "b"]) + >>> C("foo", "bar") + C(a='foo', b='bar') + + +After *declaring* your attributes ``attrs`` gives you: + +- a concise and explicit overview of the class's attributes, +- a nice human-readable ``__repr__``, +- a equality-checking methods, +- an initializer, +- and much more, + +*without* writing dull boilerplate code again and again and *without* runtime performance penalties. + +**Hate type annotations**!? +No problem! +Types are entirely **optional** with ``attrs``. +Simply assign ``attrs.field()`` to the attributes instead of annotating them with types. + +---- + +This example uses ``attrs``'s modern APIs that have been introduced in version 20.1.0, and the ``attrs`` package import name that has been added in version 21.3.0. +The classic APIs (``@attr.s``, ``attr.ib``, plus their serious business aliases) and the ``attr`` package import name will remain **indefinitely**. + +Please check out `On The Core API Names `_ for a more in-depth explanation. + + +Data Classes +============ + +On the tin, ``attrs`` might remind you of ``dataclasses`` (and indeed, ``dataclasses`` are a descendant of ``attrs``). +In practice it does a lot more and is more flexible. +For instance it allows you to define `special handling of NumPy arrays for equality checks `_, or allows more ways to `plug into the initialization process `_. + +For more details, please refer to our `comparison page `_. + + +.. -getting-help- + +Getting Help +============ + +Please use the ``python-attrs`` tag on `Stack Overflow `_ to get help. + +Answering questions of your fellow developers is also a great way to help the project! + + +.. -project-information- + +Project Information +=================== + +``attrs`` is released under the `MIT `_ license, +its documentation lives at `Read the Docs `_, +the code on `GitHub `_, +and the latest release on `PyPI `_. +It’s rigorously tested on Python 2.7, 3.5+, and PyPy. + +We collect information on **third-party extensions** in our `wiki `_. +Feel free to browse and add your own! + +If you'd like to contribute to ``attrs`` you're most welcome and we've written `a little guide `_ to get you started! + + +``attrs`` for Enterprise +------------------------ + +Available as part of the Tidelift Subscription. + +The maintainers of ``attrs`` and thousands of other packages are working with Tidelift to deliver commercial support and maintenance for the open source packages you use to build your applications. +Save time, reduce risk, and improve code health, while paying the maintainers of the exact packages you use. +`Learn more. `_ diff --git a/testing/web-platform/tests/tools/third_party/attrs/changelog.d/.gitignore b/testing/web-platform/tests/tools/third_party/attrs/changelog.d/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/web-platform/tests/tools/third_party/attrs/changelog.d/towncrier_template.rst b/testing/web-platform/tests/tools/third_party/attrs/changelog.d/towncrier_template.rst new file mode 100644 index 0000000000..29ca74c4e8 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/changelog.d/towncrier_template.rst @@ -0,0 +1,35 @@ +{% for section, _ in sections.items() %} +{% set underline = underlines[0] %}{% if section %}{{section}} +{{ underline * section|length }}{% set underline = underlines[1] %} + +{% endif %} + +{% if sections[section] %} +{% for category, val in definitions.items() if category in sections[section]%} +{{ definitions[category]['name'] }} +{{ underline * definitions[category]['name']|length }} + +{% if definitions[category]['showcontent'] %} +{% for text, values in sections[section][category].items() %} +- {{ text }} + {{ values|join(',\n ') }} +{% endfor %} + +{% else %} +- {{ sections[section][category]['']|join(', ') }} + +{% endif %} +{% if sections[section][category]|length == 0 %} +No significant changes. + +{% else %} +{% endif %} + +{% endfor %} +{% else %} +No significant changes. + + +{% endif %} +{% endfor %} +---- diff --git a/testing/web-platform/tests/tools/third_party/attrs/conftest.py b/testing/web-platform/tests/tools/third_party/attrs/conftest.py new file mode 100644 index 0000000000..0d539a115c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/conftest.py @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + +from hypothesis import HealthCheck, settings + +from attr._compat import PY36, PY310 + + +def pytest_configure(config): + # HealthCheck.too_slow causes more trouble than good -- especially in CIs. + settings.register_profile( + "patience", settings(suppress_health_check=[HealthCheck.too_slow]) + ) + settings.load_profile("patience") + + +collect_ignore = [] +if not PY36: + collect_ignore.extend( + [ + "tests/test_annotations.py", + "tests/test_hooks.py", + "tests/test_init_subclass.py", + "tests/test_next_gen.py", + ] + ) +if not PY310: + collect_ignore.extend(["tests/test_pattern_matching.py"]) diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/Makefile b/testing/web-platform/tests/tools/third_party/attrs/docs/Makefile new file mode 100644 index 0000000000..3143891daf --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/attrs.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/attrs.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/attrs" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/attrs" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo.png b/testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo.png new file mode 100644 index 0000000000..11b6e6fe3f Binary files /dev/null and b/testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo.png differ diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo.svg b/testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo.svg new file mode 100644 index 0000000000..b02ae6c025 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo_white.svg b/testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo_white.svg new file mode 100644 index 0000000000..daad798da0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/_static/attrs_logo_white.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/api.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/api.rst new file mode 100644 index 0000000000..02aed52ad5 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/api.rst @@ -0,0 +1,826 @@ +API Reference +============= + +.. currentmodule:: attr + +``attrs`` works by decorating a class using `attrs.define` or `attr.s` and then optionally defining attributes on the class using `attrs.field`, `attr.ib`, or a type annotation. + +If you're confused by the many names, please check out `names` for clarification. + +What follows is the API explanation, if you'd like a more hands-on introduction, have a look at `examples`. + +As of version 21.3.0, ``attrs`` consists of **two** to-level package names: + +- The classic ``attr`` that powered the venerable `attr.s` and `attr.ib` +- The modern ``attrs`` that only contains most modern APIs and relies on `attrs.define` and `attrs.field` to define your classes. + Additionally it offers some ``attr`` APIs with nicer defaults (e.g. `attrs.asdict`). + Using this namespace requires Python 3.6 or later. + +The ``attrs`` namespace is built *on top of* ``attr`` which will *never* go away. + + +Core +---- + +.. note:: + + Please note that the ``attrs`` namespace has been added in version 21.3.0. + Most of the objects are simply re-imported from ``attr``. + Therefore if a class, method, or function claims that it has been added in an older version, it is only available in the ``attr`` namespace. + +.. autodata:: attrs.NOTHING + +.. autofunction:: attrs.define + +.. function:: attrs.mutable(same_as_define) + + Alias for `attrs.define`. + + .. versionadded:: 20.1.0 + +.. function:: attrs.frozen(same_as_define) + + Behaves the same as `attrs.define` but sets *frozen=True* and *on_setattr=None*. + + .. versionadded:: 20.1.0 + +.. autofunction:: attrs.field + +.. function:: define + + Old import path for `attrs.define`. + +.. function:: mutable + + Old import path for `attrs.mutable`. + +.. function:: frozen + + Old import path for `attrs.frozen`. + +.. function:: field + + Old import path for `attrs.field`. + +.. autoclass:: attrs.Attribute + :members: evolve + + For example: + + .. doctest:: + + >>> import attr + >>> @attr.s + ... class C(object): + ... x = attr.ib() + >>> attr.fields(C).x + Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None) + + +.. autofunction:: attrs.make_class + + This is handy if you want to programmatically create classes. + + For example: + + .. doctest:: + + >>> C1 = attr.make_class("C1", ["x", "y"]) + >>> C1(1, 2) + C1(x=1, y=2) + >>> C2 = attr.make_class("C2", {"x": attr.ib(default=42), + ... "y": attr.ib(default=attr.Factory(list))}) + >>> C2() + C2(x=42, y=[]) + + +.. autoclass:: attrs.Factory + + For example: + + .. doctest:: + + >>> @attr.s + ... class C(object): + ... x = attr.ib(default=attr.Factory(list)) + ... y = attr.ib(default=attr.Factory( + ... lambda self: set(self.x), + ... takes_self=True) + ... ) + >>> C() + C(x=[], y=set()) + >>> C([1, 2, 3]) + C(x=[1, 2, 3], y={1, 2, 3}) + + +Classic +~~~~~~~ + +.. data:: attr.NOTHING + + Same as `attrs.NOTHING`. + +.. autofunction:: attr.s(these=None, repr_ns=None, repr=None, cmp=None, hash=None, init=None, slots=False, frozen=False, weakref_slot=True, str=False, auto_attribs=False, kw_only=False, cache_hash=False, auto_exc=False, eq=None, order=None, auto_detect=False, collect_by_mro=False, getstate_setstate=None, on_setattr=None, field_transformer=None, match_args=True) + + .. note:: + + ``attrs`` also comes with a serious business alias ``attr.attrs``. + + For example: + + .. doctest:: + + >>> import attr + >>> @attr.s + ... class C(object): + ... _private = attr.ib() + >>> C(private=42) + C(_private=42) + >>> class D(object): + ... def __init__(self, x): + ... self.x = x + >>> D(1) + + >>> D = attr.s(these={"x": attr.ib()}, init=False)(D) + >>> D(1) + D(x=1) + >>> @attr.s(auto_exc=True) + ... class Error(Exception): + ... x = attr.ib() + ... y = attr.ib(default=42, init=False) + >>> Error("foo") + Error(x='foo', y=42) + >>> raise Error("foo") + Traceback (most recent call last): + ... + Error: ('foo', 42) + >>> raise ValueError("foo", 42) # for comparison + Traceback (most recent call last): + ... + ValueError: ('foo', 42) + + +.. autofunction:: attr.ib + + .. note:: + + ``attrs`` also comes with a serious business alias ``attr.attrib``. + + The object returned by `attr.ib` also allows for setting the default and the validator using decorators: + + .. doctest:: + + >>> @attr.s + ... class C(object): + ... x = attr.ib() + ... y = attr.ib() + ... @x.validator + ... def _any_name_except_a_name_of_an_attribute(self, attribute, value): + ... if value < 0: + ... raise ValueError("x must be positive") + ... @y.default + ... def _any_name_except_a_name_of_an_attribute(self): + ... return self.x + 1 + >>> C(1) + C(x=1, y=2) + >>> C(-1) + Traceback (most recent call last): + ... + ValueError: x must be positive + + + +Exceptions +---------- + +All exceptions are available from both ``attr.exceptions`` and ``attrs.exceptions`` and are the same thing. +That means that it doesn't matter from from which namespace they've been raised and/or caught: + +.. doctest:: + + >>> import attrs, attr + >>> try: + ... raise attrs.exceptions.FrozenError() + ... except attr.exceptions.FrozenError: + ... print("this works!") + this works! + +.. autoexception:: attrs.exceptions.PythonTooOldError +.. autoexception:: attrs.exceptions.FrozenError +.. autoexception:: attrs.exceptions.FrozenInstanceError +.. autoexception:: attrs.exceptions.FrozenAttributeError +.. autoexception:: attrs.exceptions.AttrsAttributeNotFoundError +.. autoexception:: attrs.exceptions.NotAnAttrsClassError +.. autoexception:: attrs.exceptions.DefaultAlreadySetError +.. autoexception:: attrs.exceptions.UnannotatedAttributeError +.. autoexception:: attrs.exceptions.NotCallableError + + For example:: + + @attr.s(auto_attribs=True) + class C: + x: int + y = attr.ib() # <- ERROR! + + +.. _helpers: + +Helpers +------- + +``attrs`` comes with a bunch of helper methods that make working with it easier: + +.. autofunction:: attrs.cmp_using +.. function:: attr.cmp_using + + Same as `attrs.cmp_using`. + +.. autofunction:: attrs.fields + + For example: + + .. doctest:: + + >>> @attr.s + ... class C(object): + ... x = attr.ib() + ... y = attr.ib() + >>> attrs.fields(C) + (Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None)) + >>> attrs.fields(C)[1] + Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None) + >>> attrs.fields(C).y is attrs.fields(C)[1] + True + +.. function:: attr.fields + + Same as `attrs.fields`. + +.. autofunction:: attrs.fields_dict + + For example: + + .. doctest:: + + >>> @attr.s + ... class C(object): + ... x = attr.ib() + ... y = attr.ib() + >>> attrs.fields_dict(C) + {'x': Attribute(name='x', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None), 'y': Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None)} + >>> attr.fields_dict(C)['y'] + Attribute(name='y', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False, inherited=False, on_setattr=None) + >>> attrs.fields_dict(C)['y'] is attrs.fields(C).y + True + +.. function:: attr.fields_dict + + Same as `attrs.fields_dict`. + +.. autofunction:: attrs.has + + For example: + + .. doctest:: + + >>> @attr.s + ... class C(object): + ... pass + >>> attr.has(C) + True + >>> attr.has(object) + False + +.. function:: attr.has + + Same as `attrs.has`. + +.. autofunction:: attrs.resolve_types + + For example: + + .. doctest:: + + >>> import typing + >>> @attrs.define + ... class A: + ... a: typing.List['A'] + ... b: 'B' + ... + >>> @attrs.define + ... class B: + ... a: A + ... + >>> attrs.fields(A).a.type + typing.List[ForwardRef('A')] + >>> attrs.fields(A).b.type + 'B' + >>> attrs.resolve_types(A, globals(), locals()) + + >>> attrs.fields(A).a.type + typing.List[A] + >>> attrs.fields(A).b.type + + +.. function:: attr.resolve_types + + Same as `attrs.resolve_types`. + +.. autofunction:: attrs.asdict + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x: int + ... y: int + >>> attrs.asdict(C(1, C(2, 3))) + {'x': 1, 'y': {'x': 2, 'y': 3}} + +.. autofunction:: attr.asdict + +.. autofunction:: attrs.astuple + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x = attr.field() + ... y = attr.field() + >>> attrs.astuple(C(1,2)) + (1, 2) + +.. autofunction:: attr.astuple + + +``attrs`` includes some handy helpers for filtering the attributes in `attrs.asdict` and `attrs.astuple`: + +.. autofunction:: attrs.filters.include + +.. autofunction:: attrs.filters.exclude + +.. function:: attr.filters.include + + Same as `attrs.filters.include`. + +.. function:: attr.filters.exclude + + Same as `attrs.filters.exclude`. + +See :func:`attrs.asdict` for examples. + +All objects from ``attrs.filters`` are also available from ``attr.filters``. + +---- + +.. autofunction:: attrs.evolve + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x: int + ... y: int + >>> i1 = C(1, 2) + >>> i1 + C(x=1, y=2) + >>> i2 = attrs.evolve(i1, y=3) + >>> i2 + C(x=1, y=3) + >>> i1 == i2 + False + + ``evolve`` creates a new instance using ``__init__``. + This fact has several implications: + + * private attributes should be specified without the leading underscore, just like in ``__init__``. + * attributes with ``init=False`` can't be set with ``evolve``. + * the usual ``__init__`` validators will validate the new values. + +.. function:: attr.evolve + + Same as `attrs.evolve`. + +.. autofunction:: attrs.validate + + For example: + + .. doctest:: + + >>> @attrs.define(on_setattr=attrs.setters.NO_OP) + ... class C: + ... x = attrs.field(validator=attrs.validators.instance_of(int)) + >>> i = C(1) + >>> i.x = "1" + >>> attrs.validate(i) + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got '1' that is a ).", ...) + +.. function:: attr.validate + + Same as `attrs.validate`. + + +Validators can be globally disabled if you want to run them only in development and tests but not in production because you fear their performance impact: + +.. autofunction:: set_run_validators + +.. autofunction:: get_run_validators + + +.. _api_validators: + +Validators +---------- + +``attrs`` comes with some common validators in the ``attrs.validators`` module. +All objects from ``attrs.converters`` are also available from ``attr.converters``. + + +.. autofunction:: attrs.validators.lt + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x = attrs.field(validator=attrs.validators.lt(42)) + >>> C(41) + C(x=41) + >>> C(42) + Traceback (most recent call last): + ... + ValueError: ("'x' must be < 42: 42") + +.. autofunction:: attrs.validators.le + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C(object): + ... x = attrs.field(validator=attr.validators.le(42)) + >>> C(42) + C(x=42) + >>> C(43) + Traceback (most recent call last): + ... + ValueError: ("'x' must be <= 42: 43") + +.. autofunction:: attrs.validators.ge + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x = attrs.field(validator=attrs.validators.ge(42)) + >>> C(42) + C(x=42) + >>> C(41) + Traceback (most recent call last): + ... + ValueError: ("'x' must be => 42: 41") + +.. autofunction:: attrs.validators.gt + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x = attr.field(validator=attrs.validators.gt(42)) + >>> C(43) + C(x=43) + >>> C(42) + Traceback (most recent call last): + ... + ValueError: ("'x' must be > 42: 42") + +.. autofunction:: attrs.validators.max_len + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x = attrs.field(validator=attrs.validators.max_len(4)) + >>> C("spam") + C(x='spam') + >>> C("bacon") + Traceback (most recent call last): + ... + ValueError: ("Length of 'x' must be <= 4: 5") + +.. autofunction:: attrs.validators.instance_of + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x = attrs.field(validator=attrs.validators.instance_of(int)) + >>> C(42) + C(x=42) + >>> C("42") + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got '42' that is a ).", Attribute(name='x', default=NOTHING, validator=>, type=None, kw_only=False), , '42') + >>> C(None) + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got None that is a ).", Attribute(name='x', default=NOTHING, validator=>, repr=True, cmp=True, hash=None, init=True, type=None, kw_only=False), , None) + +.. autofunction:: attrs.validators.in_ + + For example: + + .. doctest:: + + >>> import enum + >>> class State(enum.Enum): + ... ON = "on" + ... OFF = "off" + >>> @attrs.define + ... class C: + ... state = attrs.field(validator=attrs.validators.in_(State)) + ... val = attrs.field(validator=attrs.validators.in_([1, 2, 3])) + >>> C(State.ON, 1) + C(state=, val=1) + >>> C("on", 1) + Traceback (most recent call last): + ... + ValueError: 'state' must be in (got 'on') + >>> C(State.ON, 4) + Traceback (most recent call last): + ... + ValueError: 'val' must be in [1, 2, 3] (got 4) + +.. autofunction:: attrs.validators.provides + +.. autofunction:: attrs.validators.and_ + + For convenience, it's also possible to pass a list to `attrs.field`'s validator argument. + + Thus the following two statements are equivalent:: + + x = attrs.field(validator=attrs.validators.and_(v1, v2, v3)) + x = attrs.field(validator=[v1, v2, v3]) + +.. autofunction:: attrs.validators.optional + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x = attrs.field(validator=attrs.validators.optional(attr.validators.instance_of(int))) + >>> C(42) + C(x=42) + >>> C("42") + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got '42' that is a ).", Attribute(name='x', default=NOTHING, validator=>, type=None, kw_only=False), , '42') + >>> C(None) + C(x=None) + + +.. autofunction:: attrs.validators.is_callable + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x = attrs.field(validator=attrs.validators.is_callable()) + >>> C(isinstance) + C(x=) + >>> C("not a callable") + Traceback (most recent call last): + ... + attr.exceptions.NotCallableError: 'x' must be callable (got 'not a callable' that is a ). + + +.. autofunction:: attrs.validators.matches_re + + For example: + + .. doctest:: + + >>> @attrs.define + ... class User: + ... email = attrs.field(validator=attrs.validators.matches_re( + ... "(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)")) + >>> User(email="user@example.com") + User(email='user@example.com') + >>> User(email="user@example.com@test.com") + Traceback (most recent call last): + ... + ValueError: ("'email' must match regex '(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\\\.[a-zA-Z0-9-.]+$)' ('user@example.com@test.com' doesn't)", Attribute(name='email', default=NOTHING, validator=, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), re.compile('(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$)'), 'user@example.com@test.com') + + +.. autofunction:: attrs.validators.deep_iterable + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x = attrs.field(validator=attrs.validators.deep_iterable( + ... member_validator=attrs.validators.instance_of(int), + ... iterable_validator=attrs.validators.instance_of(list) + ... )) + >>> C(x=[1, 2, 3]) + C(x=[1, 2, 3]) + >>> C(x=set([1, 2, 3])) + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got {1, 2, 3} that is a ).", Attribute(name='x', default=NOTHING, validator=> iterables of >>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), , {1, 2, 3}) + >>> C(x=[1, 2, "3"]) + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got '3' that is a ).", Attribute(name='x', default=NOTHING, validator=> iterables of >>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), , '3') + + +.. autofunction:: attrs.validators.deep_mapping + + For example: + + .. doctest:: + + >>> @attrs.define + ... class C: + ... x = attrs.field(validator=attrs.validators.deep_mapping( + ... key_validator=attrs.validators.instance_of(str), + ... value_validator=attrs.validators.instance_of(int), + ... mapping_validator=attrs.validators.instance_of(dict) + ... )) + >>> C(x={"a": 1, "b": 2}) + C(x={'a': 1, 'b': 2}) + >>> C(x=None) + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got None that is a ).", Attribute(name='x', default=NOTHING, validator=> to >>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), , None) + >>> C(x={"a": 1.0, "b": 2}) + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got 1.0 that is a ).", Attribute(name='x', default=NOTHING, validator=> to >>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), , 1.0) + >>> C(x={"a": 1, 7: 2}) + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got 7 that is a ).", Attribute(name='x', default=NOTHING, validator=> to >>, repr=True, cmp=True, hash=None, init=True, metadata=mappingproxy({}), type=None, converter=None, kw_only=False), , 7) + +Validators can be both globally and locally disabled: + +.. autofunction:: attrs.validators.set_disabled + +.. autofunction:: attrs.validators.get_disabled + +.. autofunction:: attrs.validators.disabled + + +Converters +---------- + +All objects from ``attrs.converters`` are also available from ``attr.converters``. + +.. autofunction:: attrs.converters.pipe + + For convenience, it's also possible to pass a list to `attr.ib`'s converter argument. + + Thus the following two statements are equivalent:: + + x = attr.ib(converter=attr.converter.pipe(c1, c2, c3)) + x = attr.ib(converter=[c1, c2, c3]) + +.. autofunction:: attrs.converters.optional + + For example: + + .. doctest:: + + >>> @attr.s + ... class C(object): + ... x = attr.ib(converter=attr.converters.optional(int)) + >>> C(None) + C(x=None) + >>> C(42) + C(x=42) + + +.. autofunction:: attrs.converters.default_if_none + + For example: + + .. doctest:: + + >>> @attr.s + ... class C(object): + ... x = attr.ib( + ... converter=attr.converters.default_if_none("") + ... ) + >>> C(None) + C(x='') + + +.. autofunction:: attrs.converters.to_bool + + For example: + + .. doctest:: + + >>> @attr.s + ... class C(object): + ... x = attr.ib( + ... converter=attr.converters.to_bool + ... ) + >>> C("yes") + C(x=True) + >>> C(0) + C(x=False) + >>> C("foo") + Traceback (most recent call last): + File "", line 1, in + ValueError: Cannot convert value to bool: foo + + + +.. _api_setters: + +Setters +------- + +These are helpers that you can use together with `attrs.define`'s and `attrs.fields`'s ``on_setattr`` arguments. +All setters in ``attrs.setters`` are also available from ``attr.setters``. + +.. autofunction:: attrs.setters.frozen +.. autofunction:: attrs.setters.validate +.. autofunction:: attrs.setters.convert +.. autofunction:: attrs.setters.pipe +.. autodata:: attrs.setters.NO_OP + + For example, only ``x`` is frozen here: + + .. doctest:: + + >>> @attrs.define(on_setattr=attr.setters.frozen) + ... class C: + ... x = attr.field() + ... y = attr.field(on_setattr=attr.setters.NO_OP) + >>> c = C(1, 2) + >>> c.y = 3 + >>> c.y + 3 + >>> c.x = 4 + Traceback (most recent call last): + ... + attrs.exceptions.FrozenAttributeError: () + + N.B. Please use `attrs.define`'s *frozen* argument (or `attrs.frozen`) to freeze whole classes; it is more efficient. + + +Deprecated APIs +--------------- + +.. _version-info: + +To help you write backward compatible code that doesn't throw warnings on modern releases, the ``attr`` module has an ``__version_info__`` attribute as of version 19.2.0. +It behaves similarly to `sys.version_info` and is an instance of `VersionInfo`: + +.. autoclass:: VersionInfo + + With its help you can write code like this: + + >>> if getattr(attr, "__version_info__", (0,)) >= (19, 2): + ... cmp_off = {"eq": False} + ... else: + ... cmp_off = {"cmp": False} + >>> cmp_off == {"eq": False} + True + >>> @attr.s(**cmp_off) + ... class C(object): + ... pass + + +---- + +The serious business aliases used to be called ``attr.attributes`` and ``attr.attr``. +There are no plans to remove them but they shouldn't be used in new code. + +.. autofunction:: assoc diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/changelog.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/changelog.rst new file mode 100644 index 0000000000..565b0521d0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/changelog.rst @@ -0,0 +1 @@ +.. include:: ../CHANGELOG.rst diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/comparison.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/comparison.rst new file mode 100644 index 0000000000..760124ca3b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/comparison.rst @@ -0,0 +1,66 @@ +Comparison +========== + +By default, two instances of ``attrs`` classes are equal if all their fields are equal. +For that, ``attrs`` writes ``__eq__`` and ``__ne__`` methods for you. + +Additionally, if you pass ``order=True`` (which is the default if you use the `attr.s` decorator), ``attrs`` will also create a full set of ordering methods that are based on the defined fields: ``__le__``, ``__lt__``, ``__ge__``, and ``__gt__``. + + +.. _custom-comparison: + +Customization +------------- + +As with other features, you can exclude fields from being involved in comparison operations: + +.. doctest:: + + >>> from attr import define, field + + >>> @define + ... class C: + ... x: int + ... y: int = field(eq=False) + + >>> C(1, 2) == C(1, 3) + True + +Additionally you can also pass a *callable* instead of a bool to both *eq* and *order*. +It is then used as a key function like you may know from `sorted`: + +.. doctest:: + + >>> from attr import define, field + + >>> @define + ... class S: + ... x: str = field(eq=str.lower) + + >>> S("foo") == S("FOO") + True + + >>> @define(order=True) + ... class C: + ... x: str = field(order=int) + + >>> C("10") > C("2") + True + +This is especially useful when you have fields with objects that have atypical comparison properties. +Common examples of such objects are `NumPy arrays `_. + +To save you unnecessary boilerplate, ``attrs`` comes with the `attr.cmp_using` helper to create such functions. +For NumPy arrays it would look like this:: + + import numpy + + @define(order=False) + class C: + an_array = field(eq=attr.cmp_using(eq=numpy.array_equal)) + + +.. warning:: + + Please note that *eq* and *order* are set *independently*, because *order* is `False` by default in `attrs.define` (but not in `attr.s`). + You can set both at once by using the *cmp* argument that we've undeprecated just for this use-case. diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/conf.py b/testing/web-platform/tests/tools/third_party/attrs/docs/conf.py new file mode 100644 index 0000000000..0cc80be6a6 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/conf.py @@ -0,0 +1,155 @@ +# SPDX-License-Identifier: MIT + +from importlib import metadata + + +# -- General configuration ------------------------------------------------ + +doctest_global_setup = """ +from attr import define, frozen, field, validators, Factory +""" + +linkcheck_ignore = [ + # We run into GitHub's rate limits. + r"https://github.com/.*/(issues|pull)/\d+", + # It never finds the anchor even though it's there. + "https://github.com/microsoft/pyright/blob/main/specs/" + "dataclass_transforms.md#attrs", +] + +# In nitpick mode (-n), still ignore any of the following "broken" references +# to non-types. +nitpick_ignore = [ + ("py:class", "Any value"), + ("py:class", "callable"), + ("py:class", "callables"), + ("py:class", "tuple of types"), +] + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.intersphinx", + "sphinx.ext.todo", + "notfound.extension", +] + + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# The suffix of source filenames. +source_suffix = ".rst" + +# The master toctree document. +master_doc = "index" + +# General information about the project. +project = "attrs" +author = "Hynek Schlawack" +copyright = f"2015, {author}" + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +# The full version, including alpha/beta/rc tags. +release = metadata.version("attrs") +# The short X.Y version. +version = release.rsplit(".", 1)[0] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ["_build"] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +default_role = "any" + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +html_theme = "furo" +html_theme_options = { + "sidebar_hide_name": True, + "light_logo": "attrs_logo.svg", + "dark_logo": "attrs_logo_white.svg", +} + + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +# html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# If false, no module index is generated. +html_domain_indices = True + +# If false, no index is generated. +html_use_index = True + +# If true, the index is split into individual pages for each letter. +html_split_index = False + +# If true, links to the reST sources are added to the pages. +html_show_sourcelink = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +# html_use_opensearch = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = "attrsdoc" + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [("index", "attrs", "attrs Documentation", ["Hynek Schlawack"], 1)] + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + "index", + "attrs", + "attrs Documentation", + "Hynek Schlawack", + "attrs", + "Python Clases Without Boilerplate", + "Miscellaneous", + ) +] + +epub_description = "Python Clases Without Boilerplate" + +intersphinx_mapping = { + "https://docs.python.org/3": None, +} + +# Allow non-local URIs so we can have images in CHANGELOG etc. +suppress_warnings = ["image.nonlocal_uri"] diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/docutils.conf b/testing/web-platform/tests/tools/third_party/attrs/docs/docutils.conf new file mode 100644 index 0000000000..db8ca82c74 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/docutils.conf @@ -0,0 +1,3 @@ +[parsers] +[restructuredtext parser] +smart_quotes=yes diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/examples.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/examples.rst new file mode 100644 index 0000000000..ba5343d4ad --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/examples.rst @@ -0,0 +1,709 @@ +``attrs`` by Example +==================== + + +Basics +------ + +The simplest possible usage is: + +.. doctest:: + + >>> from attrs import define + >>> @define + ... class Empty: + ... pass + >>> Empty() + Empty() + >>> Empty() == Empty() + True + >>> Empty() is Empty() + False + +So in other words: ``attrs`` is useful even without actual attributes! + +But you'll usually want some data on your classes, so let's add some: + +.. doctest:: + + >>> @define + ... class Coordinates: + ... x: int + ... y: int + +By default, all features are added, so you immediately have a fully functional data class with a nice ``repr`` string and comparison methods. + +.. doctest:: + + >>> c1 = Coordinates(1, 2) + >>> c1 + Coordinates(x=1, y=2) + >>> c2 = Coordinates(x=2, y=1) + >>> c2 + Coordinates(x=2, y=1) + >>> c1 == c2 + False + +As shown, the generated ``__init__`` method allows for both positional and keyword arguments. + +For private attributes, ``attrs`` will strip the leading underscores for keyword arguments: + +.. doctest:: + + >>> @define + ... class C: + ... _x: int + >>> C(x=1) + C(_x=1) + +If you want to initialize your private attributes yourself, you can do that too: + +.. doctest:: + + >>> @define + ... class C: + ... _x: int = field(init=False, default=42) + >>> C() + C(_x=42) + >>> C(23) + Traceback (most recent call last): + ... + TypeError: __init__() takes exactly 1 argument (2 given) + +An additional way of defining attributes is supported too. +This is useful in times when you want to enhance classes that are not yours (nice ``__repr__`` for Django models anyone?): + +.. doctest:: + + >>> class SomethingFromSomeoneElse: + ... def __init__(self, x): + ... self.x = x + >>> SomethingFromSomeoneElse = define( + ... these={ + ... "x": field() + ... }, init=False)(SomethingFromSomeoneElse) + >>> SomethingFromSomeoneElse(1) + SomethingFromSomeoneElse(x=1) + + +`Subclassing is bad for you `_, but ``attrs`` will still do what you'd hope for: + +.. doctest:: + + >>> @define(slots=False) + ... class A: + ... a: int + ... def get_a(self): + ... return self.a + >>> @define(slots=False) + ... class B: + ... b: int + >>> @define(slots=False) + ... class C(B, A): + ... c: int + >>> i = C(1, 2, 3) + >>> i + C(a=1, b=2, c=3) + >>> i == C(1, 2, 3) + True + >>> i.get_a() + 1 + +:term:`Slotted classes `, which are the default for the new APIs, don't play well with multiple inheritance so we don't use them in the example. + +The order of the attributes is defined by the `MRO `_. + +Keyword-only Attributes +~~~~~~~~~~~~~~~~~~~~~~~ + +You can also add `keyword-only `_ attributes: + +.. doctest:: + + >>> @define + ... class A: + ... a: int = field(kw_only=True) + >>> A() + Traceback (most recent call last): + ... + TypeError: A() missing 1 required keyword-only argument: 'a' + >>> A(a=1) + A(a=1) + +``kw_only`` may also be specified at via ``define``, and will apply to all attributes: + +.. doctest:: + + >>> @define(kw_only=True) + ... class A: + ... a: int + ... b: int + >>> A(1, 2) + Traceback (most recent call last): + ... + TypeError: __init__() takes 1 positional argument but 3 were given + >>> A(a=1, b=2) + A(a=1, b=2) + + + +If you create an attribute with ``init=False``, the ``kw_only`` argument is ignored. + +Keyword-only attributes allow subclasses to add attributes without default values, even if the base class defines attributes with default values: + +.. doctest:: + + >>> @define + ... class A: + ... a: int = 0 + >>> @define + ... class B(A): + ... b: int = field(kw_only=True) + >>> B(b=1) + B(a=0, b=1) + >>> B() + Traceback (most recent call last): + ... + TypeError: B() missing 1 required keyword-only argument: 'b' + +If you don't set ``kw_only=True``, then there's is no valid attribute ordering and you'll get an error: + +.. doctest:: + + >>> @define + ... class A: + ... a: int = 0 + >>> @define + ... class B(A): + ... b: int + Traceback (most recent call last): + ... + ValueError: No mandatory attributes allowed after an attribute with a default value or factory. Attribute in question: Attribute(name='b', default=NOTHING, validator=None, repr=True, cmp=True, hash=None, init=True, converter=None, metadata=mappingproxy({}), type=int, kw_only=False) + +.. _asdict: + +Converting to Collections Types +------------------------------- + +When you have a class with data, it often is very convenient to transform that class into a `dict` (for example if you want to serialize it to JSON): + +.. doctest:: + + >>> from attrs import asdict + + >>> asdict(Coordinates(x=1, y=2)) + {'x': 1, 'y': 2} + +Some fields cannot or should not be transformed. +For that, `attrs.asdict` offers a callback that decides whether an attribute should be included: + +.. doctest:: + + >>> @define + ... class User(object): + ... email: str + ... password: str + + >>> @define + ... class UserList: + ... users: list[User] + + >>> asdict(UserList([User("jane@doe.invalid", "s33kred"), + ... User("joe@doe.invalid", "p4ssw0rd")]), + ... filter=lambda attr, value: attr.name != "password") + {'users': [{'email': 'jane@doe.invalid'}, {'email': 'joe@doe.invalid'}]} + +For the common case where you want to `include ` or `exclude ` certain types or attributes, ``attrs`` ships with a few helpers: + +.. doctest:: + + >>> from attrs import asdict, filters, fields + + >>> @define + ... class User: + ... login: str + ... password: str + ... id: int + + >>> asdict( + ... User("jane", "s33kred", 42), + ... filter=filters.exclude(fields(User).password, int)) + {'login': 'jane'} + + >>> @define + ... class C: + ... x: str + ... y: str + ... z: int + + >>> asdict(C("foo", "2", 3), + ... filter=filters.include(int, fields(C).x)) + {'x': 'foo', 'z': 3} + +Other times, all you want is a tuple and ``attrs`` won't let you down: + +.. doctest:: + + >>> import sqlite3 + >>> from attrs import astuple + + >>> @define + ... class Foo: + ... a: int + ... b: int + + >>> foo = Foo(2, 3) + >>> with sqlite3.connect(":memory:") as conn: + ... c = conn.cursor() + ... c.execute("CREATE TABLE foo (x INTEGER PRIMARY KEY ASC, y)") #doctest: +ELLIPSIS + ... c.execute("INSERT INTO foo VALUES (?, ?)", astuple(foo)) #doctest: +ELLIPSIS + ... foo2 = Foo(*c.execute("SELECT x, y FROM foo").fetchone()) + + + >>> foo == foo2 + True + +For more advanced transformations and conversions, we recommend you look at a companion library (such as `cattrs `_). + +Defaults +-------- + +Sometimes you want to have default values for your initializer. +And sometimes you even want mutable objects as default values (ever accidentally used ``def f(arg=[])``?). +``attrs`` has you covered in both cases: + +.. doctest:: + + >>> import collections + + >>> @define + ... class Connection: + ... socket: int + ... @classmethod + ... def connect(cls, db_string): + ... # ... connect somehow to db_string ... + ... return cls(socket=42) + + >>> @define + ... class ConnectionPool: + ... db_string: str + ... pool: collections.deque = Factory(collections.deque) + ... debug: bool = False + ... def get_connection(self): + ... try: + ... return self.pool.pop() + ... except IndexError: + ... if self.debug: + ... print("New connection!") + ... return Connection.connect(self.db_string) + ... def free_connection(self, conn): + ... if self.debug: + ... print("Connection returned!") + ... self.pool.appendleft(conn) + ... + >>> cp = ConnectionPool("postgres://localhost") + >>> cp + ConnectionPool(db_string='postgres://localhost', pool=deque([]), debug=False) + >>> conn = cp.get_connection() + >>> conn + Connection(socket=42) + >>> cp.free_connection(conn) + >>> cp + ConnectionPool(db_string='postgres://localhost', pool=deque([Connection(socket=42)]), debug=False) + +More information on why class methods for constructing objects are awesome can be found in this insightful `blog post `_. + +Default factories can also be set using the ``factory`` argument to ``field``, and using a decorator. +The method receives the partially initialized instance which enables you to base a default value on other attributes: + +.. doctest:: + + >>> @define + ... class C: + ... x: int = 1 + ... y: int = field() + ... @y.default + ... def _any_name_except_a_name_of_an_attribute(self): + ... return self.x + 1 + ... z: list = field(factory=list) + >>> C() + C(x=1, y=2, z=[]) + + +.. _examples_validators: + +Validators +---------- + +Although your initializers should do as little as possible (ideally: just initialize your instance according to the arguments!), it can come in handy to do some kind of validation on the arguments. + +``attrs`` offers two ways to define validators for each attribute and it's up to you to choose which one suits your style and project better. + +You can use a decorator: + +.. doctest:: + + >>> @define + ... class C: + ... x: int = field() + ... @x.validator + ... def check(self, attribute, value): + ... if value > 42: + ... raise ValueError("x must be smaller or equal to 42") + >>> C(42) + C(x=42) + >>> C(43) + Traceback (most recent call last): + ... + ValueError: x must be smaller or equal to 42 + +...or a callable... + +.. doctest:: + + >>> from attrs import validators + + >>> def x_smaller_than_y(instance, attribute, value): + ... if value >= instance.y: + ... raise ValueError("'x' has to be smaller than 'y'!") + >>> @define + ... class C: + ... x: int = field(validator=[validators.instance_of(int), + ... x_smaller_than_y]) + ... y: int + >>> C(x=3, y=4) + C(x=3, y=4) + >>> C(x=4, y=3) + Traceback (most recent call last): + ... + ValueError: 'x' has to be smaller than 'y'! + +...or both at once: + +.. doctest:: + + >>> @define + ... class C: + ... x: int = field(validator=validators.instance_of(int)) + ... @x.validator + ... def fits_byte(self, attribute, value): + ... if not 0 <= value < 256: + ... raise ValueError("value out of bounds") + >>> C(128) + C(x=128) + >>> C("128") + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got '128' that is a ).", Attribute(name='x', default=NOTHING, validator=[>, ], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=int, converter=None, kw_only=False), , '128') + >>> C(256) + Traceback (most recent call last): + ... + ValueError: value out of bounds + +Please note that the decorator approach only works if -- and only if! -- the attribute in question has a ``field`` assigned. +Therefore if you use ``@default``, it is *not* enough to annotate said attribute with a type. + +``attrs`` ships with a bunch of validators, make sure to `check them out ` before writing your own: + +.. doctest:: + + >>> @define + ... class C: + ... x: int = field(validator=validators.instance_of(int)) + >>> C(42) + C(x=42) + >>> C("42") + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got '42' that is a ).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=>, type=None, kw_only=False), , '42') + +Please note that if you use `attr.s` (and not `attrs.define`) to define your class, validators only run on initialization by default. +This behavior can be changed using the ``on_setattr`` argument. + +Check out `validators` for more details. + + +Conversion +---------- + +Attributes can have a ``converter`` function specified, which will be called with the attribute's passed-in value to get a new value to use. +This can be useful for doing type-conversions on values that you don't want to force your callers to do. + +.. doctest:: + + >>> @define + ... class C: + ... x: int = field(converter=int) + >>> o = C("1") + >>> o.x + 1 + +Please note that converters only run on initialization. + +Check out `converters` for more details. + + +.. _metadata: + +Metadata +-------- + +All ``attrs`` attributes may include arbitrary metadata in the form of a read-only dictionary. + +.. doctest:: + + >>> from attrs import fields + + >>> @define + ... class C: + ... x = field(metadata={'my_metadata': 1}) + >>> fields(C).x.metadata + mappingproxy({'my_metadata': 1}) + >>> fields(C).x.metadata['my_metadata'] + 1 + +Metadata is not used by ``attrs``, and is meant to enable rich functionality in third-party libraries. +The metadata dictionary follows the normal dictionary rules: keys need to be hashable, and both keys and values are recommended to be immutable. + +If you're the author of a third-party library with ``attrs`` integration, please see `Extending Metadata `. + + +Types +----- + +``attrs`` also allows you to associate a type with an attribute using either the *type* argument to `attr.ib` or -- as of Python 3.6 -- using `PEP 526 `_-annotations: + + +.. doctest:: + + >>> from attrs import fields + + >>> @define + ... class C: + ... x: int + >>> fields(C).x.type + + + >>> import attr + >>> @attr.s + ... class C(object): + ... x = attr.ib(type=int) + >>> fields(C).x.type + + +If you don't mind annotating *all* attributes, you can even drop the `attrs.field` and assign default values instead: + +.. doctest:: + + >>> import typing + >>> from attrs import fields + + >>> @define + ... class AutoC: + ... cls_var: typing.ClassVar[int] = 5 # this one is ignored + ... l: list[int] = Factory(list) + ... x: int = 1 + ... foo: str = "every attrib needs a type if auto_attribs=True" + ... bar: typing.Any = None + >>> fields(AutoC).l.type + list[int] + >>> fields(AutoC).x.type + + >>> fields(AutoC).foo.type + + >>> fields(AutoC).bar.type + typing.Any + >>> AutoC() + AutoC(l=[], x=1, foo='every attrib needs a type if auto_attribs=True', bar=None) + >>> AutoC.cls_var + 5 + +The generated ``__init__`` method will have an attribute called ``__annotations__`` that contains this type information. + +If your annotations contain strings (e.g. forward references), +you can resolve these after all references have been defined by using :func:`attrs.resolve_types`. +This will replace the *type* attribute in the respective fields. + +.. doctest:: + + >>> from attrs import fields, resolve_types + + >>> @define + ... class A: + ... a: 'list[A]' + ... b: 'B' + ... + >>> @define + ... class B: + ... a: A + ... + >>> fields(A).a.type + 'list[A]' + >>> fields(A).b.type + 'B' + >>> resolve_types(A, globals(), locals()) + + >>> fields(A).a.type + list[A] + >>> fields(A).b.type + + +.. note:: + + If you find yourself using string type annotations to handle forward references, wrap the entire type annotation in quotes instead of only the type you need a forward reference to (so ``'list[A]'`` instead of ``list['A']``). + This is a limitation of the Python typing system. + +.. warning:: + + ``attrs`` itself doesn't have any features that work on top of type metadata *yet*. + However it's useful for writing your own validators or serialization frameworks. + + +Slots +----- + +:term:`Slotted classes ` have several advantages on CPython. +Defining ``__slots__`` by hand is tedious, in ``attrs`` it's just a matter of using `attrs.define` or passing ``slots=True`` to `attr.s`: + +.. doctest:: + + >>> import attr + + >>> @attr.s(slots=True) + ... class Coordinates: + ... x: int + ... y: int + + +Immutability +------------ + +Sometimes you have instances that shouldn't be changed after instantiation. +Immutability is especially popular in functional programming and is generally a very good thing. +If you'd like to enforce it, ``attrs`` will try to help: + +.. doctest:: + + >>> @frozen + ... class C: + ... x: int + >>> i = C(1) + >>> i.x = 2 + Traceback (most recent call last): + ... + attr.exceptions.FrozenInstanceError: can't set attribute + >>> i.x + 1 + +Please note that true immutability is impossible in Python but it will `get ` you 99% there. +By themselves, immutable classes are useful for long-lived objects that should never change; like configurations for example. + +In order to use them in regular program flow, you'll need a way to easily create new instances with changed attributes. +In Clojure that function is called `assoc `_ and ``attrs`` shamelessly imitates it: `attr.evolve`: + +.. doctest:: + + >>> from attrs import evolve + + >>> @frozen + ... class C: + ... x: int + ... y: int + >>> i1 = C(1, 2) + >>> i1 + C(x=1, y=2) + >>> i2 = evolve(i1, y=3) + >>> i2 + C(x=1, y=3) + >>> i1 == i2 + False + + +Other Goodies +------------- + +Sometimes you may want to create a class programmatically. +``attrs`` won't let you down and gives you `attrs.make_class` : + +.. doctest:: + + >>> from attrs import fields, make_class + >>> @define + ... class C1: + ... x = field() + ... y = field() + >>> C2 = make_class("C2", ["x", "y"]) + >>> fields(C1) == fields(C2) + True + +You can still have power over the attributes if you pass a dictionary of name: ``field`` mappings and can pass arguments to ``@attr.s``: + +.. doctest:: + + >>> from attrs import make_class + + >>> C = make_class("C", {"x": field(default=42), + ... "y": field(default=Factory(list))}, + ... repr=False) + >>> i = C() + >>> i # no repr added! + <__main__.C object at ...> + >>> i.x + 42 + >>> i.y + [] + +If you need to dynamically make a class with `attrs.make_class` and it needs to be a subclass of something else than ``object``, use the ``bases`` argument: + +.. doctest:: + + >>> from attrs import make_class + + >>> class D: + ... def __eq__(self, other): + ... return True # arbitrary example + >>> C = make_class("C", {}, bases=(D,), cmp=False) + >>> isinstance(C(), D) + True + +Sometimes, you want to have your class's ``__init__`` method do more than just +the initialization, validation, etc. that gets done for you automatically when +using ``@define``. +To do this, just define a ``__attrs_post_init__`` method in your class. +It will get called at the end of the generated ``__init__`` method. + +.. doctest:: + + >>> @define + ... class C: + ... x: int + ... y: int + ... z: int = field(init=False) + ... + ... def __attrs_post_init__(self): + ... self.z = self.x + self.y + >>> obj = C(x=1, y=2) + >>> obj + C(x=1, y=2, z=3) + +You can exclude single attributes from certain methods: + +.. doctest:: + + >>> @define + ... class C: + ... user: str + ... password: str = field(repr=False) + >>> C("me", "s3kr3t") + C(user='me') + +Alternatively, to influence how the generated ``__repr__()`` method formats a specific attribute, specify a custom callable to be used instead of the ``repr()`` built-in function: + +.. doctest:: + + >>> @define + ... class C: + ... user: str + ... password: str = field(repr=lambda value: '***') + >>> C("me", "s3kr3t") + C(user='me', password=***) diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/extending.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/extending.rst new file mode 100644 index 0000000000..faf71afd91 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/extending.rst @@ -0,0 +1,313 @@ +Extending +========= + +Each ``attrs``-decorated class has a ``__attrs_attrs__`` class attribute. +It's a tuple of `attrs.Attribute` carrying metadata about each attribute. + +So it is fairly simple to build your own decorators on top of ``attrs``: + +.. doctest:: + + >>> from attr import define + >>> def print_attrs(cls): + ... print(cls.__attrs_attrs__) + ... return cls + >>> @print_attrs + ... @define + ... class C: + ... a: int + (Attribute(name='a', default=NOTHING, validator=None, repr=True, eq=True, eq_key=None, order=True, order_key=None, hash=None, init=True, metadata=mappingproxy({}), type=, converter=None, kw_only=False, inherited=False, on_setattr=None),) + + +.. warning:: + + The `attrs.define`/`attr.s` decorator **must** be applied first because it puts ``__attrs_attrs__`` in place! + That means that is has to come *after* your decorator because:: + + @a + @b + def f(): + pass + + is just `syntactic sugar `_ for:: + + def original_f(): + pass + + f = a(b(original_f)) + + +Wrapping the Decorator +---------------------- + +A more elegant way can be to wrap ``attrs`` altogether and build a class `DSL `_ on top of it. + +An example for that is the package `environ-config `_ that uses ``attrs`` under the hood to define environment-based configurations declaratively without exposing ``attrs`` APIs at all. + +Another common use case is to overwrite ``attrs``'s defaults. + +Mypy +^^^^ + +Unfortunately, decorator wrapping currently `confuses `_ mypy's ``attrs`` plugin. +At the moment, the best workaround is to hold your nose, write a fake mypy plugin, and mutate a bunch of global variables:: + + from mypy.plugin import Plugin + from mypy.plugins.attrs import ( + attr_attrib_makers, + attr_class_makers, + attr_dataclass_makers, + ) + + # These work just like `attr.dataclass`. + attr_dataclass_makers.add("my_module.method_looks_like_attr_dataclass") + + # This works just like `attr.s`. + attr_class_makers.add("my_module.method_looks_like_attr_s") + + # These are our `attr.ib` makers. + attr_attrib_makers.add("my_module.method_looks_like_attrib") + + class MyPlugin(Plugin): + # Our plugin does nothing but it has to exist so this file gets loaded. + pass + + + def plugin(version): + return MyPlugin + + +Then tell mypy about your plugin using your project's ``mypy.ini``: + +.. code:: ini + + [mypy] + plugins= + + +.. warning:: + Please note that it is currently *impossible* to let mypy know that you've changed defaults like *eq* or *order*. + You can only use this trick to tell mypy that a class is actually an ``attrs`` class. + +Pyright +^^^^^^^ + +Generic decorator wrapping is supported in `pyright `_ via their dataclass_transform_ specification. + +For a custom wrapping of the form:: + + def custom_define(f): + return attr.define(f) + +This is implemented via a ``__dataclass_transform__`` type decorator in the custom extension's ``.pyi`` of the form:: + + def __dataclass_transform__( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()), + ) -> Callable[[_T], _T]: ... + + @__dataclass_transform__(field_descriptors=(attr.attrib, attr.field)) + def custom_define(f): ... + +.. warning:: + + ``dataclass_transform`` is supported **provisionally** as of ``pyright`` 1.1.135. + + Both the ``pyright`` dataclass_transform_ specification and ``attrs`` implementation may change in future versions. + + +Types +----- + +``attrs`` offers two ways of attaching type information to attributes: + +- `PEP 526 `_ annotations on Python 3.6 and later, +- and the *type* argument to `attr.ib`. + +This information is available to you: + +.. doctest:: + + >>> from attr import attrib, define, field, fields + >>> @define + ... class C: + ... x: int = field() + ... y = attrib(type=str) + >>> fields(C).x.type + + >>> fields(C).y.type + + +Currently, ``attrs`` doesn't do anything with this information but it's very useful if you'd like to write your own validators or serializers! + + +.. _extending_metadata: + +Metadata +-------- + +If you're the author of a third-party library with ``attrs`` integration, you may want to take advantage of attribute metadata. + +Here are some tips for effective use of metadata: + +- Try making your metadata keys and values immutable. + This keeps the entire ``Attribute`` instances immutable too. + +- To avoid metadata key collisions, consider exposing your metadata keys from your modules.:: + + from mylib import MY_METADATA_KEY + + @define + class C: + x = field(metadata={MY_METADATA_KEY: 1}) + + Metadata should be composable, so consider supporting this approach even if you decide implementing your metadata in one of the following ways. + +- Expose ``field`` wrappers for your specific metadata. + This is a more graceful approach if your users don't require metadata from other libraries. + + .. doctest:: + + >>> from attr import fields, NOTHING + >>> MY_TYPE_METADATA = '__my_type_metadata' + >>> + >>> def typed( + ... cls, default=NOTHING, validator=None, repr=True, + ... eq=True, order=None, hash=None, init=True, metadata={}, + ... converter=None + ... ): + ... metadata = dict() if not metadata else metadata + ... metadata[MY_TYPE_METADATA] = cls + ... return field( + ... default=default, validator=validator, repr=repr, + ... eq=eq, order=order, hash=hash, init=init, + ... metadata=metadata, converter=converter + ... ) + >>> + >>> @define + ... class C: + ... x: int = typed(int, default=1, init=False) + >>> fields(C).x.metadata[MY_TYPE_METADATA] + + + +.. _transform-fields: + +Automatic Field Transformation and Modification +----------------------------------------------- + +``attrs`` allows you to automatically modify or transform the class' fields while the class is being created. +You do this by passing a *field_transformer* hook to `attr.define` (and its friends). +Its main purpose is to automatically add converters to attributes based on their type to aid the development of API clients and other typed data loaders. + +This hook must have the following signature: + +.. function:: your_hook(cls: type, fields: list[attrs.Attribute]) -> list[attrs.Attribute] + :noindex: + +- *cls* is your class right *before* it is being converted into an attrs class. + This means it does not yet have the ``__attrs_attrs__`` attribute. + +- *fields* is a list of all `attrs.Attribute` instances that will later be set to ``__attrs_attrs__``. + You can modify these attributes any way you want: + You can add converters, change types, and even remove attributes completely or create new ones! + +For example, let's assume that you really don't like floats: + +.. doctest:: + + >>> def drop_floats(cls, fields): + ... return [f for f in fields if f.type not in {float, 'float'}] + ... + >>> @frozen(field_transformer=drop_floats) + ... class Data: + ... a: int + ... b: float + ... c: str + ... + >>> Data(42, "spam") + Data(a=42, c='spam') + +A more realistic example would be to automatically convert data that you, e.g., load from JSON: + +.. doctest:: + + >>> from datetime import datetime + >>> + >>> def auto_convert(cls, fields): + ... results = [] + ... for field in fields: + ... if field.converter is not None: + ... results.append(field) + ... continue + ... if field.type in {datetime, 'datetime'}: + ... converter = (lambda d: datetime.fromisoformat(d) if isinstance(d, str) else d) + ... else: + ... converter = None + ... results.append(field.evolve(converter=converter)) + ... return results + ... + >>> @frozen(field_transformer=auto_convert) + ... class Data: + ... a: int + ... b: str + ... c: datetime + ... + >>> from_json = {"a": 3, "b": "spam", "c": "2020-05-04T13:37:00"} + >>> Data(**from_json) # **** + Data(a=3, b='spam', c=datetime.datetime(2020, 5, 4, 13, 37)) + + +Customize Value Serialization in ``asdict()`` +--------------------------------------------- + +``attrs`` allows you to serialize instances of ``attrs`` classes to dicts using the `attrs.asdict` function. +However, the result can not always be serialized since most data types will remain as they are: + +.. doctest:: + + >>> import json + >>> import datetime + >>> from attrs import asdict + >>> + >>> @frozen + ... class Data: + ... dt: datetime.datetime + ... + >>> data = asdict(Data(datetime.datetime(2020, 5, 4, 13, 37))) + >>> data + {'dt': datetime.datetime(2020, 5, 4, 13, 37)} + >>> json.dumps(data) + Traceback (most recent call last): + ... + TypeError: Object of type datetime is not JSON serializable + +To help you with this, `attr.asdict` allows you to pass a *value_serializer* hook. +It has the signature + +.. function:: your_hook(inst: type, field: attrs.Attribute, value: typing.Any) -> typing.Any + :noindex: + +.. doctest:: + + >>> from attr import asdict + >>> def serialize(inst, field, value): + ... if isinstance(value, datetime.datetime): + ... return value.isoformat() + ... return value + ... + >>> data = asdict( + ... Data(datetime.datetime(2020, 5, 4, 13, 37)), + ... value_serializer=serialize, + ... ) + >>> data + {'dt': '2020-05-04T13:37:00'} + >>> json.dumps(data) + '{"dt": "2020-05-04T13:37:00"}' + +***** + +.. _dataclass_transform: https://github.com/microsoft/pyright/blob/master/specs/dataclass_transforms.md diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/glossary.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/glossary.rst new file mode 100644 index 0000000000..5fd01f4fb1 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/glossary.rst @@ -0,0 +1,104 @@ +Glossary +======== + +.. glossary:: + + dunder methods + "Dunder" is a contraction of "double underscore". + + It's methods like ``__init__`` or ``__eq__`` that are sometimes also called *magic methods* or it's said that they implement an *object protocol*. + + In spoken form, you'd call ``__init__`` just "dunder init". + + Its first documented use is a `mailing list posting `_ by Mark Jackson from 2002. + + dict classes + A regular class whose attributes are stored in the `object.__dict__` attribute of every single instance. + This is quite wasteful especially for objects with very few data attributes and the space consumption can become significant when creating large numbers of instances. + + This is the type of class you get by default both with and without ``attrs`` (except with the next APIs `attr.define`, `attr.mutable`, and `attr.frozen`). + + slotted classes + A class whose instances have no `object.__dict__` attribute and `define `_ their attributes in a `object.__slots__` attribute instead. + In ``attrs``, they are created by passing ``slots=True`` to ``@attr.s`` (and are on by default in `attr.define`/`attr.mutable`/`attr.frozen`). + + + Their main advantage is that they use less memory on CPython [#pypy]_ and are slightly faster. + + However they also come with several possibly surprising gotchas: + + - Slotted classes don't allow for any other attribute to be set except for those defined in one of the class' hierarchies ``__slots__``: + + .. doctest:: + + >>> from attr import define + >>> @define + ... class Coordinates: + ... x: int + ... y: int + ... + >>> c = Coordinates(x=1, y=2) + >>> c.z = 3 + Traceback (most recent call last): + ... + AttributeError: 'Coordinates' object has no attribute 'z' + + - Slotted classes can inherit from other classes just like non-slotted classes, but some of the benefits of slotted classes are lost if you do that. + If you must inherit from other classes, try to inherit only from other slotted classes. + + - However, `it's not possible `_ to inherit from more than one class that has attributes in ``__slots__`` (you will get an ``TypeError: multiple bases have instance lay-out conflict``). + + - It's not possible to monkeypatch methods on slotted classes. + This can feel limiting in test code, however the need to monkeypatch your own classes is usually a design smell. + + If you really need to monkeypatch an instance in your tests, but don't want to give up on the advantages of slotted classes in production code, you can always subclass a slotted class as a dict class with no further changes and all the limitations go away: + + .. doctest:: + + >>> import attr, unittest.mock + >>> @define + ... class Slotted: + ... x: int + ... + ... def method(self): + ... return self.x + >>> s = Slotted(42) + >>> s.method() + 42 + >>> with unittest.mock.patch.object(s, "method", return_value=23): + ... pass + Traceback (most recent call last): + ... + AttributeError: 'Slotted' object attribute 'method' is read-only + >>> @define(slots=False) + ... class Dicted(Slotted): + ... pass + >>> d = Dicted(42) + >>> d.method() + 42 + >>> with unittest.mock.patch.object(d, "method", return_value=23): + ... assert 23 == d.method() + + - Slotted classes must implement :meth:`__getstate__ ` and :meth:`__setstate__ ` to be serializable with `pickle` protocol 0 and 1. + Therefore, ``attrs`` creates these methods automatically for ``slots=True`` classes (Python 2 uses protocol 0 by default). + + .. note:: + + If the ``@attr.s(slots=True)`` decorated class already implements the :meth:`__getstate__ ` and :meth:`__setstate__ ` methods, they will be *overwritten* by ``attrs`` autogenerated implementation by default. + + This can be avoided by setting ``@attr.s(getstate_setstate=False)`` or by setting ``@attr.s(auto_detect=True)``. + + Also, `think twice `_ before using `pickle`. + + - Slotted classes are weak-referenceable by default. + This can be disabled in CPython by passing ``weakref_slot=False`` to ``@attr.s`` [#pypyweakref]_. + + - Since it's currently impossible to make a class slotted after it's been created, ``attrs`` has to replace your class with a new one. + While it tries to do that as graciously as possible, certain metaclass features like `object.__init_subclass__` do not work with slotted classes. + + - The `class.__subclasses__` attribute needs a garbage collection run (which can be manually triggered using `gc.collect`), for the original class to be removed. + See issue `#407 `_ for more details. + + +.. [#pypy] On PyPy, there is no memory advantage in using slotted classes. +.. [#pypyweakref] On PyPy, slotted classes are naturally weak-referenceable so ``weakref_slot=False`` has no effect. diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/hashing.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/hashing.rst new file mode 100644 index 0000000000..30888f97bb --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/hashing.rst @@ -0,0 +1,86 @@ +Hashing +======= + +Hash Method Generation +---------------------- + +.. warning:: + + The overarching theme is to never set the ``@attr.s(hash=X)`` parameter yourself. + Leave it at ``None`` which means that ``attrs`` will do the right thing for you, depending on the other parameters: + + - If you want to make objects hashable by value: use ``@attr.s(frozen=True)``. + - If you want hashing and equality by object identity: use ``@attr.s(eq=False)`` + + Setting ``hash`` yourself can have unexpected consequences so we recommend to tinker with it only if you know exactly what you're doing. + +Under certain circumstances, it's necessary for objects to be *hashable*. +For example if you want to put them into a `set` or if you want to use them as keys in a `dict`. + +The *hash* of an object is an integer that represents the contents of an object. +It can be obtained by calling `hash` on an object and is implemented by writing a ``__hash__`` method for your class. + +``attrs`` will happily write a ``__hash__`` method for you [#fn1]_, however it will *not* do so by default. +Because according to the definition_ from the official Python docs, the returned hash has to fulfill certain constraints: + +#. Two objects that are equal, **must** have the same hash. + This means that if ``x == y``, it *must* follow that ``hash(x) == hash(y)``. + + By default, Python classes are compared *and* hashed by their `id`. + That means that every instance of a class has a different hash, no matter what attributes it carries. + + It follows that the moment you (or ``attrs``) change the way equality is handled by implementing ``__eq__`` which is based on attribute values, this constraint is broken. + For that reason Python 3 will make a class that has customized equality unhashable. + Python 2 on the other hand will happily let you shoot your foot off. + Unfortunately ``attrs`` currently mimics Python 2's behavior for backward compatibility reasons if you set ``hash=False``. + + The *correct way* to achieve hashing by id is to set ``@attr.s(eq=False)``. + Setting ``@attr.s(hash=False)`` (which implies ``eq=True``) is almost certainly a *bug*. + + .. warning:: + + Be careful when subclassing! + Setting ``eq=False`` on a class whose base class has a non-default ``__hash__`` method will *not* make ``attrs`` remove that ``__hash__`` for you. + + It is part of ``attrs``'s philosophy to only *add* to classes so you have the freedom to customize your classes as you wish. + So if you want to *get rid* of methods, you'll have to do it by hand. + + The easiest way to reset ``__hash__`` on a class is adding ``__hash__ = object.__hash__`` in the class body. + +#. If two object are not equal, their hash **should** be different. + + While this isn't a requirement from a standpoint of correctness, sets and dicts become less effective if there are a lot of identical hashes. + The worst case is when all objects have the same hash which turns a set into a list. + +#. The hash of an object **must not** change. + + If you create a class with ``@attr.s(frozen=True)`` this is fullfilled by definition, therefore ``attrs`` will write a ``__hash__`` function for you automatically. + You can also force it to write one with ``hash=True`` but then it's *your* responsibility to make sure that the object is not mutated. + + This point is the reason why mutable structures like lists, dictionaries, or sets aren't hashable while immutable ones like tuples or frozensets are: + point 1 and 2 require that the hash changes with the contents but point 3 forbids it. + +For a more thorough explanation of this topic, please refer to this blog post: `Python Hashes and Equality`_. + + +Hashing and Mutability +---------------------- + +Changing any field involved in hash code computation after the first call to ``__hash__`` (typically this would be after its insertion into a hash-based collection) can result in silent bugs. +Therefore, it is strongly recommended that hashable classes be ``frozen``. +Beware, however, that this is not a complete guarantee of safety: +if a field points to an object and that object is mutated, the hash code may change, but ``frozen`` will not protect you. + + +Hash Code Caching +----------------- + +Some objects have hash codes which are expensive to compute. +If such objects are to be stored in hash-based collections, it can be useful to compute the hash codes only once and then store the result on the object to make future hash code requests fast. +To enable caching of hash codes, pass ``cache_hash=True`` to ``@attrs``. +This may only be done if ``attrs`` is already generating a hash function for the object. + +.. [#fn1] The hash is computed by hashing a tuple that consists of an unique id for the class plus all attribute values. + +.. _definition: https://docs.python.org/3/glossary.html#term-hashable +.. _`Python Hashes and Equality`: https://hynek.me/articles/hashes-and-equality/ diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/how-does-it-work.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/how-does-it-work.rst new file mode 100644 index 0000000000..f899740542 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/how-does-it-work.rst @@ -0,0 +1,109 @@ +.. _how: + +How Does It Work? +================= + + +Boilerplate +----------- + +``attrs`` certainly isn't the first library that aims to simplify class definition in Python. +But its **declarative** approach combined with **no runtime overhead** lets it stand out. + +Once you apply the ``@attrs.define`` (or ``@attr.s``) decorator to a class, ``attrs`` searches the class object for instances of ``attr.ib``\ s. +Internally they're a representation of the data passed into ``attr.ib`` along with a counter to preserve the order of the attributes. +Alternatively, it's possible to define them using :doc:`types`. + +In order to ensure that subclassing works as you'd expect it to work, ``attrs`` also walks the class hierarchy and collects the attributes of all base classes. +Please note that ``attrs`` does *not* call ``super()`` *ever*. +It will write :term:`dunder methods` to work on *all* of those attributes which also has performance benefits due to fewer function calls. + +Once ``attrs`` knows what attributes it has to work on, it writes the requested :term:`dunder methods` and -- depending on whether you wish to have a :term:`dict ` or :term:`slotted ` class -- creates a new class for you (``slots=True``) or attaches them to the original class (``slots=False``). +While creating new classes is more elegant, we've run into several edge cases surrounding metaclasses that make it impossible to go this route unconditionally. + +To be very clear: if you define a class with a single attribute without a default value, the generated ``__init__`` will look *exactly* how you'd expect: + +.. doctest:: + + >>> import inspect + >>> from attr import define + >>> @define + ... class C: + ... x: int + >>> print(inspect.getsource(C.__init__)) + def __init__(self, x): + self.x = x + + +No magic, no meta programming, no expensive introspection at runtime. + +**** + +Everything until this point happens exactly *once* when the class is defined. +As soon as a class is done, it's done. +And it's just a regular Python class like any other, except for a single ``__attrs_attrs__`` attribute that ``attrs`` uses internally. +Much of the information is accessible via `attrs.fields` and other functions which can be used for introspection or for writing your own tools and decorators on top of ``attrs`` (like `attrs.asdict`). + +And once you start instantiating your classes, ``attrs`` is out of your way completely. + +This **static** approach was very much a design goal of ``attrs`` and what I strongly believe makes it distinct. + + +.. _how-frozen: + +Immutability +------------ + +In order to give you immutability, ``attrs`` will attach a ``__setattr__`` method to your class that raises an `attrs.exceptions.FrozenInstanceError` whenever anyone tries to set an attribute. + +The same is true if you choose to freeze individual attributes using the `attrs.setters.frozen` *on_setattr* hook -- except that the exception becomes `attrs.exceptions.FrozenAttributeError`. + +Both errors subclass `attrs.exceptions.FrozenError`. + +----- + +Depending on whether a class is a dict class or a slotted class, ``attrs`` uses a different technique to circumvent that limitation in the ``__init__`` method. + +Once constructed, frozen instances don't differ in any way from regular ones except that you cannot change its attributes. + + +Dict Classes +++++++++++++ + +Dict classes -- i.e. regular classes -- simply assign the value directly into the class' eponymous ``__dict__`` (and there's nothing we can do to stop the user to do the same). + +The performance impact is negligible. + + +Slotted Classes ++++++++++++++++ + +Slotted classes are more complicated. +Here it uses (an aggressively cached) :meth:`object.__setattr__` to set your attributes. +This is (still) slower than a plain assignment: + +.. code-block:: none + + $ pyperf timeit --rigorous \ + -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True)" \ + "C(1, 2, 3)" + ........................................ + Median +- std dev: 378 ns +- 12 ns + + $ pyperf timeit --rigorous \ + -s "import attr; C = attr.make_class('C', ['x', 'y', 'z'], slots=True, frozen=True)" \ + "C(1, 2, 3)" + ........................................ + Median +- std dev: 676 ns +- 16 ns + +So on a laptop computer the difference is about 300 nanoseconds (1 second is 1,000,000,000 nanoseconds). +It's certainly something you'll feel in a hot loop but shouldn't matter in normal code. +Pick what's more important to you. + + +Summary ++++++++ + +You should avoid instantiating lots of frozen slotted classes (i.e. ``@frozen``) in performance-critical code. + +Frozen dict classes have barely a performance impact, unfrozen slotted classes are even *faster* than unfrozen dict classes (i.e. regular classes). diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/index.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/index.rst new file mode 100644 index 0000000000..ff65a6738c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/index.rst @@ -0,0 +1,100 @@ +.. module:: attr +.. module:: attrs + +====================================== +``attrs``: Classes Without Boilerplate +====================================== + +Release v\ |release| (`What's new? `). + +.. include:: ../README.rst + :start-after: teaser-begin + :end-before: teaser-end + + +Getting Started +=============== + +``attrs`` is a Python-only package `hosted on PyPI `_. +The recommended installation method is `pip `_-installing into a `virtualenv `_: + +.. code-block:: console + + $ python -m pip install attrs + +The next three steps should bring you up and running in no time: + +- `overview` will show you a simple example of ``attrs`` in action and introduce you to its philosophy. + Afterwards, you can start writing your own classes and understand what drives ``attrs``'s design. +- `examples` will give you a comprehensive tour of ``attrs``'s features. + After reading, you will know about our advanced features and how to use them. +- If you're confused by all the ``attr.s``, ``attr.ib``, ``attrs``, ``attrib``, ``define``, ``frozen``, and ``field``, head over to `names` for a very short explanation, and optionally a quick history lesson. +- Finally `why` gives you a rundown of potential alternatives and why we think ``attrs`` is superior. + Yes, we've heard about ``namedtuple``\ s and Data Classes! +- If at any point you get confused by some terminology, please check out our `glossary`. + + +If you need any help while getting started, feel free to use the ``python-attrs`` tag on `Stack Overflow `_ and someone will surely help you out! + + +Day-to-Day Usage +================ + +- `types` help you to write *correct* and *self-documenting* code. + ``attrs`` has first class support for them, yet keeps them optional if you’re not convinced! +- Instance initialization is one of ``attrs`` key feature areas. + Our goal is to relieve you from writing as much code as possible. + `init` gives you an overview what ``attrs`` has to offer and explains some related philosophies we believe in. +- Comparing and ordering objects is a common task. + `comparison` shows you how ``attrs`` helps you with that and how you can customize it. +- If you want to put objects into sets or use them as keys in dictionaries, they have to be hashable. + The simplest way to do that is to use frozen classes, but the topic is more complex than it seems and `hashing` will give you a primer on what to look out for. +- Once you're comfortable with the concepts, our `api` contains all information you need to use ``attrs`` to its fullest. +- ``attrs`` is built for extension from the ground up. + `extending` will show you the affordances it offers and how to make it a building block of your own projects. + + +.. include:: ../README.rst + :start-after: -getting-help- + :end-before: -project-information- + + +---- + + +Full Table of Contents +====================== + +.. toctree:: + :maxdepth: 2 + + overview + why + examples + types + init + comparison + hashing + api + extending + how-does-it-work + names + glossary + + +.. include:: ../README.rst + :start-after: -project-information- + +.. toctree:: + :maxdepth: 1 + + license + python-2 + changelog + + +Indices and tables +================== + +* `genindex` +* `search` diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/init.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/init.rst new file mode 100644 index 0000000000..fb276ded8a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/init.rst @@ -0,0 +1,489 @@ +Initialization +============== + +In Python, instance initialization happens in the ``__init__`` method. +Generally speaking, you should keep as little logic as possible in it, and you should think about what the class needs and not how it is going to be instantiated. + +Passing complex objects into ``__init__`` and then using them to derive data for the class unnecessarily couples your new class with the old class which makes it harder to test and also will cause problems later. + +So assuming you use an ORM and want to extract 2D points from a row object, do not write code like this:: + + class Point(object): + def __init__(self, database_row): + self.x = database_row.x + self.y = database_row.y + + pt = Point(row) + +Instead, write a `classmethod` that will extract it for you:: + + @define + class Point: + x: float + y: float + + @classmethod + def from_row(cls, row): + return cls(row.x, row.y) + + pt = Point.from_row(row) + +Now you can instantiate ``Point``\ s without creating fake row objects in your tests and you can have as many smart creation helpers as you want, in case more data sources appear. + +For similar reasons, we strongly discourage from patterns like:: + + pt = Point(**row.attributes) + +which couples your classes to the database data model. +Try to design your classes in a way that is clean and convenient to use -- not based on your database format. +The database format can change anytime and you're stuck with a bad class design that is hard to change. +Embrace functions and classmethods as a filter between reality and what's best for you to work with. + +If you look for object serialization, there's a bunch of projects listed on our ``attrs`` extensions `Wiki page`_. +Some of them even support nested schemas. + + +Private Attributes +------------------ + +One thing people tend to find confusing is the treatment of private attributes that start with an underscore. +``attrs`` follows the doctrine that `there is no such thing as a private argument`_ and strips the underscores from the name when writing the ``__init__`` method signature: + +.. doctest:: + + >>> import inspect, attr, attrs + >>> from attr import define + >>> @define + ... class C: + ... _x: int + >>> inspect.signature(C.__init__) + None> + +There really isn't a right or wrong, it's a matter of taste. +But it's important to be aware of it because it can lead to surprising syntax errors: + +.. doctest:: + + >>> @define + ... class C: + ... _1: int + Traceback (most recent call last): + ... + SyntaxError: invalid syntax + +In this case a valid attribute name ``_1`` got transformed into an invalid argument name ``1``. + + +Defaults +-------- + +Sometimes you don't want to pass all attribute values to a class. +And sometimes, certain attributes aren't even intended to be passed but you want to allow for customization anyways for easier testing. + +This is when default values come into play: + +.. doctest:: + + >>> from attr import define, field, Factory + + >>> @define + ... class C: + ... a: int = 42 + ... b: list = field(factory=list) + ... c: list = Factory(list) # syntactic sugar for above + ... d: dict = field() + ... @d.default + ... def _any_name_except_a_name_of_an_attribute(self): + ... return {} + >>> C() + C(a=42, b=[], c=[], d={}) + +It's important that the decorated method -- or any other method or property! -- doesn't have the same name as the attribute, otherwise it would overwrite the attribute definition. + +Please note that as with function and method signatures, ``default=[]`` will *not* do what you may think it might do: + +.. doctest:: + + >>> @define + ... class C: + ... x = [] + >>> i = C() + >>> k = C() + >>> i.x.append(42) + >>> k.x + [42] + + +This is why ``attrs`` comes with factory options. + +.. warning:: + + Please note that the decorator based defaults have one gotcha: + they are executed when the attribute is set, that means depending on the order of attributes, the ``self`` object may not be fully initialized when they're called. + + Therefore you should use ``self`` as little as possible. + + Even the smartest of us can `get confused`_ by what happens if you pass partially initialized objects around. + + +.. _validators: + +Validators +---------- + +Another thing that definitely *does* belong in ``__init__`` is checking the resulting instance for invariants. +This is why ``attrs`` has the concept of validators. + + +Decorator +~~~~~~~~~ + +The most straightforward way is using the attribute's ``validator`` method as a decorator. + +The method has to accept three arguments: + +#. the *instance* that's being validated (aka ``self``), +#. the *attribute* that it's validating, and finally +#. the *value* that is passed for it. + +If the value does not pass the validator's standards, it just raises an appropriate exception. + + >>> @define + ... class C: + ... x: int = field() + ... @x.validator + ... def _check_x(self, attribute, value): + ... if value > 42: + ... raise ValueError("x must be smaller or equal to 42") + >>> C(42) + C(x=42) + >>> C(43) + Traceback (most recent call last): + ... + ValueError: x must be smaller or equal to 42 + +Again, it's important that the decorated method doesn't have the same name as the attribute and that the `attrs.field()` helper is used. + + +Callables +~~~~~~~~~ + +If you want to re-use your validators, you should have a look at the ``validator`` argument to `attrs.field`. + +It takes either a callable or a list of callables (usually functions) and treats them as validators that receive the same arguments as with the decorator approach. + +Since the validators run *after* the instance is initialized, you can refer to other attributes while validating: + +.. doctest:: + + >>> def x_smaller_than_y(instance, attribute, value): + ... if value >= instance.y: + ... raise ValueError("'x' has to be smaller than 'y'!") + >>> @define + ... class C: + ... x = field(validator=[attrs.validators.instance_of(int), + ... x_smaller_than_y]) + ... y = field() + >>> C(x=3, y=4) + C(x=3, y=4) + >>> C(x=4, y=3) + Traceback (most recent call last): + ... + ValueError: 'x' has to be smaller than 'y'! + +This example also shows of some syntactic sugar for using the `attrs.validators.and_` validator: if you pass a list, all validators have to pass. + +``attrs`` won't intercept your changes to those attributes but you can always call `attrs.validate` on any instance to verify that it's still valid: +When using `attrs.define` or `attrs.frozen`, ``attrs`` will run the validators even when setting the attribute. + +.. doctest:: + + >>> i = C(4, 5) + >>> i.x = 5 + Traceback (most recent call last): + ... + ValueError: 'x' has to be smaller than 'y'! + +``attrs`` ships with a bunch of validators, make sure to `check them out ` before writing your own: + +.. doctest:: + + >>> @define + ... class C: + ... x = field(validator=attrs.validators.instance_of(int)) + >>> C(42) + C(x=42) + >>> C("42") + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got '42' that is a ).", Attribute(name='x', default=NOTHING, factory=NOTHING, validator=>, type=None), , '42') + +Of course you can mix and match the two approaches at your convenience. +If you define validators both ways for an attribute, they are both ran: + +.. doctest:: + + >>> @define + ... class C: + ... x = field(validator=attrs.validators.instance_of(int)) + ... @x.validator + ... def fits_byte(self, attribute, value): + ... if not 0 <= value < 256: + ... raise ValueError("value out of bounds") + >>> C(128) + C(x=128) + >>> C("128") + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got '128' that is a ).", Attribute(name='x', default=NOTHING, validator=[>, ], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=one), , '128') + >>> C(256) + Traceback (most recent call last): + ... + ValueError: value out of bounds + +And finally you can disable validators globally: + + >>> attrs.validators.set_disabled(True) + >>> C("128") + C(x='128') + >>> attrs.validators.set_disabled(False) + >>> C("128") + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got '128' that is a ).", Attribute(name='x', default=NOTHING, validator=[>, ], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=None), , '128') + +You can achieve the same by using the context manager: + + >>> with attrs.validators.disabled(): + ... C("128") + C(x='128') + >>> C("128") + Traceback (most recent call last): + ... + TypeError: ("'x' must be (got '128' that is a ).", Attribute(name='x', default=NOTHING, validator=[>, ], repr=True, cmp=True, hash=True, init=True, metadata=mappingproxy({}), type=None, converter=None), , '128') + + +.. _converters: + +Converters +---------- + +Finally, sometimes you may want to normalize the values coming in. +For that ``attrs`` comes with converters. + +Attributes can have a ``converter`` function specified, which will be called with the attribute's passed-in value to get a new value to use. +This can be useful for doing type-conversions on values that you don't want to force your callers to do. + +.. doctest:: + + >>> @define + ... class C: + ... x = field(converter=int) + >>> o = C("1") + >>> o.x + 1 + +Converters are run *before* validators, so you can use validators to check the final form of the value. + +.. doctest:: + + >>> def validate_x(instance, attribute, value): + ... if value < 0: + ... raise ValueError("x must be at least 0.") + >>> @define + ... class C: + ... x = field(converter=int, validator=validate_x) + >>> o = C("0") + >>> o.x + 0 + >>> C("-1") + Traceback (most recent call last): + ... + ValueError: x must be at least 0. + + +Arguably, you can abuse converters as one-argument validators: + +.. doctest:: + + >>> C("x") + Traceback (most recent call last): + ... + ValueError: invalid literal for int() with base 10: 'x' + + +If a converter's first argument has a type annotation, that type will appear in the signature for ``__init__``. +A converter will override an explicit type annotation or ``type`` argument. + +.. doctest:: + + >>> def str2int(x: str) -> int: + ... return int(x) + >>> @define + ... class C: + ... x = field(converter=str2int) + >>> C.__init__.__annotations__ + {'return': None, 'x': } + + +Hooking Yourself Into Initialization +------------------------------------ + +Generally speaking, the moment you think that you need finer control over how your class is instantiated than what ``attrs`` offers, it's usually best to use a classmethod factory or to apply the `builder pattern `_. + +However, sometimes you need to do that one quick thing before or after your class is initialized. +And for that ``attrs`` offers three means: + +- ``__attrs_pre_init__`` is automatically detected and run *before* ``attrs`` starts initializing. + This is useful if you need to inject a call to ``super().__init__()``. +- ``__attrs_post_init__`` is automatically detected and run *after* ``attrs`` is done initializing your instance. + This is useful if you want to derive some attribute from others or perform some kind of validation over the whole instance. +- ``__attrs_init__`` is written and attached to your class *instead* of ``__init__``, if ``attrs`` is told to not write one (i.e. ``init=False`` or a combination of ``auto_detect=True`` and a custom ``__init__``). + This is useful if you want full control over the initialization process, but don't want to set the attributes by hand. + + +Pre Init +~~~~~~~~ + +The sole reason for the existance of ``__attrs_pre_init__`` is to give users the chance to call ``super().__init__()``, because some subclassing-based APIs require that. + +.. doctest:: + + >>> @define + ... class C: + ... x: int + ... def __attrs_pre_init__(self): + ... super().__init__() + >>> C(42) + C(x=42) + +If you need more control, use the custom init approach described next. + + +Custom Init +~~~~~~~~~~~ + +If you tell ``attrs`` to not write an ``__init__``, it will write an ``__attrs_init__`` instead, with the same code that it would have used for ``__init__``. +You have full control over the initialization, but also have to type out the types of your arguments etc. +Here's an example of a manual default value: + +.. doctest:: + + >>> from typing import Optional + + >>> @define + ... class C: + ... x: int + ... + ... def __init__(self, x: int = 42): + ... self.__attrs_init__(x) + >>> C() + C(x=42) + + +Post Init +~~~~~~~~~ + +.. doctest:: + + >>> @define + ... class C: + ... x: int + ... y: int = field(init=False) + ... def __attrs_post_init__(self): + ... self.y = self.x + 1 + >>> C(1) + C(x=1, y=2) + +Please note that you can't directly set attributes on frozen classes: + +.. doctest:: + + >>> @frozen + ... class FrozenBroken: + ... x: int + ... y: int = field(init=False) + ... def __attrs_post_init__(self): + ... self.y = self.x + 1 + >>> FrozenBroken(1) + Traceback (most recent call last): + ... + attrs.exceptions.FrozenInstanceError: can't set attribute + +If you need to set attributes on a frozen class, you'll have to resort to the `same trick ` as ``attrs`` and use :meth:`object.__setattr__`: + +.. doctest:: + + >>> @define + ... class Frozen: + ... x: int + ... y: int = field(init=False) + ... def __attrs_post_init__(self): + ... object.__setattr__(self, "y", self.x + 1) + >>> Frozen(1) + Frozen(x=1, y=2) + +Note that you *must not* access the hash code of the object in ``__attrs_post_init__`` if ``cache_hash=True``. + + +Order of Execution +------------------ + +If present, the hooks are executed in the following order: + +1. ``__attrs_pre_init__`` (if present on *current* class) +2. For each attribute, in the order it was declared: + + a. default factory + b. converter + +3. *all* validators +4. ``__attrs_post_init__`` (if present on *current* class) + +Notably this means, that you can access all attributes from within your validators, but your converters have to deal with invalid values and have to return a valid value. + + +Derived Attributes +------------------ + +One of the most common ``attrs`` questions on *Stack Overflow* is how to have attributes that depend on other attributes. +For example if you have an API token and want to instantiate a web client that uses it for authentication. +Based on the previous sections, there's two approaches. + +The simpler one is using ``__attrs_post_init__``:: + + @define + class APIClient: + token: str + client: WebClient = field(init=False) + + def __attrs_post_init__(self): + self.client = WebClient(self.token) + +The second one is using a decorator-based default:: + + @define + class APIClient: + token: str + client: WebClient = field() # needed! attr.ib works too + + @client.default + def _client_factory(self): + return WebClient(self.token) + +That said, and as pointed out in the beginning of the chapter, a better approach would be to have a factory class method:: + + @define + class APIClient: + client: WebClient + + @classmethod + def from_token(cls, token: str) -> SomeClass: + return cls(client=WebClient(token)) + +This makes the class more testable. + + +.. _`Wiki page`: https://github.com/python-attrs/attrs/wiki/Extensions-to-attrs +.. _`get confused`: https://github.com/python-attrs/attrs/issues/289 +.. _`there is no such thing as a private argument`: https://github.com/hynek/characteristic/issues/6 diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/license.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/license.rst new file mode 100644 index 0000000000..a341a31eb9 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/license.rst @@ -0,0 +1,8 @@ +=================== +License and Credits +=================== + +``attrs`` is licensed under the `MIT `_ license. +The full license text can be also found in the `source code repository `_. + +.. include:: ../AUTHORS.rst diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/names.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/names.rst new file mode 100644 index 0000000000..0fe953e6a5 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/names.rst @@ -0,0 +1,122 @@ +On The Core API Names +===================== + +You may be surprised seeing ``attrs`` classes being created using `attrs.define` and with type annotated fields, instead of `attr.s` and `attr.ib()`. + +Or, you wonder why the web and talks are full of this weird `attr.s` and `attr.ib` -- including people having strong opinions about it and using ``attr.attrs`` and ``attr.attrib`` instead. + +And what even is ``attr.dataclass`` that's not documented but commonly used!? + + +TL;DR +----- + +We recommend our modern APIs for new code: + +- `attrs.define()` to define a new class, +- `attrs.mutable()` is an alias for `attrs.define()`, +- `attrs.frozen()` is an alias for ``define(frozen=True)`` +- and `attrs.field()` to define an attribute. + +They have been added in ``attrs`` 20.1.0, they are expressive, and they have modern defaults like slots and type annotation awareness switched on by default. +They are only available in Python 3.6 and later. +Sometimes they're referred to as *next-generation* or *NG* APIs. +As of ``attrs`` 21.3.0 you can also import them from the ``attrs`` package namespace. + +The traditional APIs `attr.s` / `attr.ib`, their serious business aliases ``attr.attrs`` / ``attr.attrib``, and the never-documented, but popular ``attr.dataclass`` easter egg will stay **forever**. + +``attrs`` will **never** force you to use type annotations. + + +A Short History Lesson +---------------------- + +At this point, ``attrs`` is an old project. +It had its first release in April 2015 -- back when most Python code was on Python 2.7 and Python 3.4 was the first Python 3 release that showed promise. +``attrs`` was always Python 3-first, but `type annotations `_ came only into Python 3.5 that was released in September 2015 and were largely ignored until years later. + +At this time, if you didn't want to implement all the :term:`dunder methods`, the most common way to create a class with some attributes on it was to subclass a `collections.namedtuple`, or one of the many hacks that allowed you to access dictionary keys using attribute lookup. + +But ``attrs`` history goes even a bit further back, to the now-forgotten `characteristic `_ that came out in May 2014 and already used a class decorator, but was overall too unergonomic. + +In the wake of all of that, `glyph `_ and `Hynek `_ came together on IRC and brainstormed how to take the good ideas of ``characteristic``, but make them easier to use and read. +At this point the plan was not to make ``attrs`` what it is now -- a flexible class building kit. +All we wanted was an ergonomic little library to succinctly define classes with attributes. + +Under the impression of of the unwieldy ``characteristic`` name, we went to the other side and decided to make the package name part of the API, and keep the API functions very short. +This led to the infamous `attr.s` and `attr.ib` which some found confusing and pronounced it as "attr dot s" or used a singular ``@s`` as the decorator. +But it was really just a way to say ``attrs`` and ``attrib``\ [#attr]_. + +Some people hated this cutey API from day one, which is why we added aliases for them that we called *serious business*: ``@attr.attrs`` and ``attr.attrib()``. +Fans of them usually imported the names and didn't use the package name in the first place. +Unfortunately, the ``attr`` package name started creaking the moment we added ``attr.Factory``, since it couldn’t be morphed into something meaningful in any way. +A problem that grew worse over time, as more APIs and even modules were added. + +But overall, ``attrs`` in this shape was a **huge** success -- especially after glyph's blog post `The One Python Library Everyone Needs `_ in August 2016 and `pytest `_ adopting it. + +Being able to just write:: + + @attr.s + class Point(object): + x = attr.ib() + y = attr.ib() + +was a big step for those who wanted to write small, focused classes. + + +Dataclasses Enter The Arena +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A big change happened in May 2017 when Hynek sat down with `Guido van Rossum `_ and `Eric V. Smith `_ at PyCon US 2017. + +Type annotations for class attributes have `just landed `_ in Python 3.6 and Guido felt like it would be a good mechanic to introduce something similar to ``attrs`` to the Python standard library. +The result, of course, was `PEP 557 `_\ [#stdlib]_ which eventually became the `dataclasses` module in Python 3.7. + +``attrs`` at this point was lucky to have several people on board who were also very excited about type annotations and helped implementing it; including a `Mypy plugin `_. +And so it happened that ``attrs`` `shipped `_ the new method of defining classes more than half a year before Python 3.7 -- and thus `dataclasses` -- were released. + +----- + +Due to backward-compatibility concerns, this feature is off by default in the `attr.s` decorator and has to be activated using ``@attr.s(auto_attribs=True)``, though. +As a little easter egg and to save ourselves some typing, we've also `added `_ an alias called ``attr.dataclasses`` that just set ``auto_attribs=True``. +It was never documented, but people found it and used it and loved it. + +Over the next months and years it became clear that type annotations have become the popular way to define classes and their attributes. +However, it has also become clear that some people viscerally hate type annotations. +We're determined to serve both. + + +``attrs`` TNG +^^^^^^^^^^^^^ + +Over its existence, ``attrs`` never stood still. +But since we also greatly care about backward compatibility and not breaking our users's code, many features and niceties have to be manually activated. + +That is not only annoying, it also leads to the problem that many of ``attrs``'s users don't even know what it can do for them. +We've spent years alone explaining that defining attributes using type annotations is in no way unique to `dataclasses`. + +Finally we've decided to take the `Go route `_: +instead of fiddling with the old APIs -- whose names felt anachronistic anyway -- we'd define new ones, with better defaults. +So in July 2018, we `looked for better names `_ and came up with `attr.define`, `attr.field`, and friends. +Then in January 2019, we `started looking for inconvenient defaults `_ that we now could fix without any repercussions. + +These APIs proved to be very popular, so we've finally changed the documentation to them in November of 2021. + +All of this took way too long, of course. +One reason is the COVID-19 pandemic, but also our fear to fumble this historic chance to fix our APIs. + +Finally, in December 2021, we've added the ``attrs`` package namespace. + +We hope you like the result:: + + from attrs import define + + @define + class Point: + x: int + y: int + + +.. [#attr] We considered calling the PyPI package just ``attr`` too, but the name was already taken by an *ostensibly* inactive `package on PyPI `_. +.. [#stdlib] The highly readable PEP also explains why ``attrs`` wasn't just added to the standard library. + Don't believe the myths and rumors. diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/overview.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/overview.rst new file mode 100644 index 0000000000..b35f66f2dd --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/overview.rst @@ -0,0 +1,58 @@ +======== +Overview +======== + +In order to fulfill its ambitious goal of bringing back the joy to writing classes, it gives you a class decorator and a way to declaratively define the attributes on that class: + +.. include:: ../README.rst + :start-after: -code-begin- + :end-before: -getting-help- + + +.. _philosophy: + +Philosophy +========== + +**It's about regular classes.** + ``attrs`` is for creating well-behaved classes with a type, attributes, methods, and everything that comes with a class. + It can be used for data-only containers like ``namedtuple``\ s or ``types.SimpleNamespace`` but they're just a sub-genre of what ``attrs`` is good for. + +**The class belongs to the users.** + You define a class and ``attrs`` adds static methods to that class based on the attributes you declare. + The end. + It doesn't add metaclasses. + It doesn't add classes you've never heard of to your inheritance tree. + An ``attrs`` class in runtime is indistinguishable from a regular class: because it *is* a regular class with a few boilerplate-y methods attached. + +**Be light on API impact.** + As convenient as it seems at first, ``attrs`` will *not* tack on any methods to your classes except for the :term:`dunder ones `. + Hence all the useful `tools ` that come with ``attrs`` live in functions that operate on top of instances. + Since they take an ``attrs`` instance as their first argument, you can attach them to your classes with one line of code. + +**Performance matters.** + ``attrs`` runtime impact is very close to zero because all the work is done when the class is defined. + Once you're instantiating it, ``attrs`` is out of the picture completely. + +**No surprises.** + ``attrs`` creates classes that arguably work the way a Python beginner would reasonably expect them to work. + It doesn't try to guess what you mean because explicit is better than implicit. + It doesn't try to be clever because software shouldn't be clever. + +Check out `how-does-it-work` if you'd like to know how it achieves all of the above. + + +What ``attrs`` Is Not +===================== + +``attrs`` does *not* invent some kind of magic system that pulls classes out of its hat using meta classes, runtime introspection, and shaky interdependencies. + +All ``attrs`` does is: + +1. take your declaration, +2. write :term:`dunder methods` based on that information, +3. and attach them to your class. + +It does *nothing* dynamic at runtime, hence zero runtime overhead. +It's still *your* class. +Do with it as you please. diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/python-2.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/python-2.rst new file mode 100644 index 0000000000..7ec9e5112c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/python-2.rst @@ -0,0 +1,25 @@ +Python 2 Statement +================== + +While ``attrs`` has always been a Python 3-first package, we the maintainers are aware that Python 2 has not magically disappeared in 2020. +We are also aware that ``attrs`` is an important building block in many people's systems and livelihoods. + +As such, we do **not** have any immediate plans to drop Python 2 support in ``attrs``. +We intend to support is as long as it will be technically feasible for us. + +Feasibility in this case means: + +1. Possibility to run the tests on our development computers, +2. and **free** CI options. + +This can mean that we will have to run our tests on PyPy, whose maintainters have unequivocally declared that they do not intend to stop the development and maintenance of their Python 2-compatible line at all. +And this can mean that at some point, a sponsor will have to step up and pay for bespoke CI setups. + +**However**: there is no promise of new features coming to ``attrs`` running under Python 2. +It is up to our discretion alone, to decide whether the introduced complexity or awkwardness are worth it, or whether we choose to make a feature available on modern platforms only. + + +Summary +------- + +We will do our best to support existing users, but nobody is entitled to the latest and greatest features on a platform that is officially end of life. diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/types.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/types.rst new file mode 100644 index 0000000000..fbb90a7e93 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/types.rst @@ -0,0 +1,108 @@ +Type Annotations +================ + +``attrs`` comes with first class support for type annotations for both Python 3.6 (:pep:`526`) and legacy syntax. + +However they will forever remain *optional*, therefore the example from the README could also be written as: + +.. doctest:: + + >>> from attrs import define, field + + >>> @define + ... class SomeClass: + ... a_number = field(default=42) + ... list_of_numbers = field(factory=list) + + >>> sc = SomeClass(1, [1, 2, 3]) + >>> sc + SomeClass(a_number=1, list_of_numbers=[1, 2, 3]) + +You can choose freely between the approaches, but please remember that if you choose to use type annotations, you **must** annotate **all** attributes! + +---- + +Even when going all-in an type annotations, you will need `attr.field` for some advanced features though. + +One of those features are the decorator-based features like defaults. +It's important to remember that ``attrs`` doesn't do any magic behind your back. +All the decorators are implemented using an object that is returned by the call to `attrs.field`. + +Attributes that only carry a class annotation do not have that object so trying to call a method on it will inevitably fail. + +***** + +Please note that types -- however added -- are *only metadata* that can be queried from the class and they aren't used for anything out of the box! + +Because Python does not allow references to a class object before the class is defined, +types may be defined as string literals, so-called *forward references* (:pep:`526`). +You can enable this automatically for a whole module by using ``from __future__ import annotations`` (:pep:`563`) as of Python 3.7. +In this case ``attrs`` simply puts these string literals into the ``type`` attributes. +If you need to resolve these to real types, you can call `attrs.resolve_types` which will update the attribute in place. + +In practice though, types show their biggest usefulness in combination with tools like mypy_, pytype_, or pyright_ that have dedicated support for ``attrs`` classes. + +The addition of static types is certainly one of the most exciting features in the Python ecosystem and helps you writing *correct* and *verified self-documenting* code. + +If you don't know where to start, Carl Meyer gave a great talk on `Type-checked Python in the Real World `_ at PyCon US 2018 that will help you to get started in no time. + + +mypy +---- + +While having a nice syntax for type metadata is great, it's even greater that mypy_ as of 0.570 ships with a dedicated ``attrs`` plugin which allows you to statically check your code. + +Imagine you add another line that tries to instantiate the defined class using ``SomeClass("23")``. +Mypy will catch that error for you: + +.. code-block:: console + + $ mypy t.py + t.py:12: error: Argument 1 to "SomeClass" has incompatible type "str"; expected "int" + +This happens *without* running your code! + +And it also works with *both* Python 2-style annotation styles. +To mypy, this code is equivalent to the one above: + +.. code-block:: python + + @attr.s + class SomeClass(object): + a_number = attr.ib(default=42) # type: int + list_of_numbers = attr.ib(factory=list, type=list[int]) + + +pyright +------- + +``attrs`` provides support for pyright_ though the dataclass_transform_ specification. +This provides static type inference for a subset of ``attrs`` equivalent to standard-library ``dataclasses``, +and requires explicit type annotations using the `attrs.define` or ``@attr.s(auto_attribs=True)`` API. + +Given the following definition, ``pyright`` will generate static type signatures for ``SomeClass`` attribute access, ``__init__``, ``__eq__``, and comparison methods:: + + @attr.define + class SomeClass: + a_number: int = 42 + list_of_numbers: list[int] = attr.field(factory=list) + +.. warning:: + + The ``pyright`` inferred types are a subset of those supported by ``mypy``, including: + + - The generated ``__init__`` signature only includes the attribute type annotations. + It currently does not include attribute ``converter`` types. + + - The ``attr.frozen`` decorator is not typed with frozen attributes, which are properly typed via ``attr.define(frozen=True)``. + + A `full list `_ of limitations and incompatibilities can be found in pyright's repository. + + Your constructive feedback is welcome in both `attrs#795 `_ and `pyright#1782 `_. + Generally speaking, the decision on improving ``attrs`` support in pyright is entirely Microsoft's prerogative though. + + +.. _mypy: http://mypy-lang.org +.. _pytype: https://google.github.io/pytype/ +.. _pyright: https://github.com/microsoft/pyright +.. _dataclass_transform: https://github.com/microsoft/pyright/blob/main/specs/dataclass_transforms.md diff --git a/testing/web-platform/tests/tools/third_party/attrs/docs/why.rst b/testing/web-platform/tests/tools/third_party/attrs/docs/why.rst new file mode 100644 index 0000000000..2c0ca4cd66 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/docs/why.rst @@ -0,0 +1,290 @@ +Why not… +======== + + +If you'd like third party's account why ``attrs`` is great, have a look at Glyph's `The One Python Library Everyone Needs `_! + + +…Data Classes? +-------------- + +:pep:`557` added Data Classes to `Python 3.7 `_ that resemble ``attrs`` in many ways. + +They are the result of the Python community's `wish `_ to have an easier way to write classes in the standard library that doesn't carry the problems of ``namedtuple``\ s. +To that end, ``attrs`` and its developers were involved in the PEP process and while we may disagree with some minor decisions that have been made, it's a fine library and if it stops you from abusing ``namedtuple``\ s, they are a huge win. + +Nevertheless, there are still reasons to prefer ``attrs`` over Data Classes. +Whether they're relevant to *you* depends on your circumstances: + +- Data Classes are *intentionally* less powerful than ``attrs``. + There is a long list of features that were sacrificed for the sake of simplicity and while the most obvious ones are validators, converters, :ref:`equality customization `, or :doc:`extensibility ` in general, it permeates throughout all APIs. + + On the other hand, Data Classes currently do not offer any significant feature that ``attrs`` doesn't already have. +- ``attrs`` supports all mainstream Python versions, including CPython 2.7 and PyPy. +- ``attrs`` doesn't force type annotations on you if you don't like them. +- But since it **also** supports typing, it's the best way to embrace type hints *gradually*, too. +- While Data Classes are implementing features from ``attrs`` every now and then, their presence is dependent on the Python version, not the package version. + For example, support for ``__slots__`` has only been added in Python 3.10. + That is especially painful for PyPI packages that support multiple Python versions. + This includes possible implementation bugs. +- ``attrs`` can and will move faster. + We are not bound to any release schedules and we have a clear deprecation policy. + + One of the `reasons `_ to not vendor ``attrs`` in the standard library was to not impede ``attrs``'s future development. + +One way to think about ``attrs`` vs Data Classes is that ``attrs`` is a fully-fledged toolkit to write powerful classes while Data Classes are an easy way to get a class with some attributes. +Basically what ``attrs`` was in 2015. + + +…pydantic? +---------- + +*pydantic* is first an foremost a *data validation library*. +As such, it is a capable complement to class building libraries like ``attrs`` (or Data Classes!) for parsing and validating untrusted data. + +However, as convenient as it might be, using it for your business or data layer `is problematic in several ways `_: +Is it really necessary to re-validate all your objects while reading them from a trusted database? +In the parlance of `Form, Command, and Model Validation `_, *pydantic* is the right tool for *Commands*. + +`Separation of concerns `_ feels tedious at times, but it's one of those things that you get to appreciate once you've shot your own foot often enough. + + +…namedtuples? +------------- + +`collections.namedtuple`\ s are tuples with names, not classes. [#history]_ +Since writing classes is tiresome in Python, every now and then someone discovers all the typing they could save and gets really excited. +However, that convenience comes at a price. + +The most obvious difference between ``namedtuple``\ s and ``attrs``-based classes is that the latter are type-sensitive: + +.. doctest:: + + >>> import attr + >>> C1 = attr.make_class("C1", ["a"]) + >>> C2 = attr.make_class("C2", ["a"]) + >>> i1 = C1(1) + >>> i2 = C2(1) + >>> i1.a == i2.a + True + >>> i1 == i2 + False + +…while a ``namedtuple`` is *intentionally* `behaving like a tuple`_ which means the type of a tuple is *ignored*: + +.. doctest:: + + >>> from collections import namedtuple + >>> NT1 = namedtuple("NT1", "a") + >>> NT2 = namedtuple("NT2", "b") + >>> t1 = NT1(1) + >>> t2 = NT2(1) + >>> t1 == t2 == (1,) + True + +Other often surprising behaviors include: + +- Since they are a subclass of tuples, ``namedtuple``\ s have a length and are both iterable and indexable. + That's not what you'd expect from a class and is likely to shadow subtle typo bugs. +- Iterability also implies that it's easy to accidentally unpack a ``namedtuple`` which leads to hard-to-find bugs. [#iter]_ +- ``namedtuple``\ s have their methods *on your instances* whether you like it or not. [#pollution]_ +- ``namedtuple``\ s are *always* immutable. + Not only does that mean that you can't decide for yourself whether your instances should be immutable or not, it also means that if you want to influence your class' initialization (validation? default values?), you have to implement :meth:`__new__() ` which is a particularly hacky and error-prone requirement for a very common problem. [#immutable]_ +- To attach methods to a ``namedtuple`` you have to subclass it. + And if you follow the standard library documentation's recommendation of:: + + class Point(namedtuple('Point', ['x', 'y'])): + # ... + + you end up with a class that has *two* ``Point``\ s in its :attr:`__mro__ `: ``[, , , ]``. + + That's not only confusing, it also has very practical consequences: + for example if you create documentation that includes class hierarchies like `Sphinx's autodoc `_ with ``show-inheritance``. + Again: common problem, hacky solution with confusing fallout. + +All these things make ``namedtuple``\ s a particularly poor choice for public APIs because all your objects are irrevocably tainted. +With ``attrs`` your users won't notice a difference because it creates regular, well-behaved classes. + +.. admonition:: Summary + + If you want a *tuple with names*, by all means: go for a ``namedtuple``. [#perf]_ + But if you want a class with methods, you're doing yourself a disservice by relying on a pile of hacks that requires you to employ even more hacks as your requirements expand. + + Other than that, ``attrs`` also adds nifty features like validators, converters, and (mutable!) default values. + + +.. rubric:: Footnotes + +.. [#history] The word is that ``namedtuple``\ s were added to the Python standard library as a way to make tuples in return values more readable. + And indeed that is something you see throughout the standard library. + + Looking at what the makers of ``namedtuple``\ s use it for themselves is a good guideline for deciding on your own use cases. +.. [#pollution] ``attrs`` only adds a single attribute: ``__attrs_attrs__`` for introspection. + All helpers are functions in the ``attr`` package. + Since they take the instance as first argument, you can easily attach them to your classes under a name of your own choice. +.. [#iter] `attr.astuple` can be used to get that behavior in ``attrs`` on *explicit demand*. +.. [#immutable] ``attrs`` offers *optional* immutability through the ``frozen`` keyword. +.. [#perf] Although ``attrs`` would serve you just as well! + Since both employ the same method of writing and compiling Python code for you, the performance penalty is negligible at worst and in some cases ``attrs`` is even faster if you use ``slots=True`` (which is generally a good idea anyway). + +.. _behaving like a tuple: https://docs.python.org/3/tutorial/datastructures.html#tuples-and-sequences + + +…tuples? +-------- + +Readability +^^^^^^^^^^^ + +What makes more sense while debugging:: + + Point(x=1, y=2) + +or:: + + (1, 2) + +? + +Let's add even more ambiguity:: + + Customer(id=42, reseller=23, first_name="Jane", last_name="John") + +or:: + + (42, 23, "Jane", "John") + +? + +Why would you want to write ``customer[2]`` instead of ``customer.first_name``? + +Don't get me started when you add nesting. +If you've never run into mysterious tuples you had no idea what the hell they meant while debugging, you're much smarter than yours truly. + +Using proper classes with names and types makes program code much more readable and comprehensible_. +Especially when trying to grok a new piece of software or returning to old code after several months. + +.. _comprehensible: https://arxiv.org/pdf/1304.5257.pdf + + +Extendability +^^^^^^^^^^^^^ + +Imagine you have a function that takes or returns a tuple. +Especially if you use tuple unpacking (eg. ``x, y = get_point()``), adding additional data means that you have to change the invocation of that function *everywhere*. + +Adding an attribute to a class concerns only those who actually care about that attribute. + + +…dicts? +------- + +Dictionaries are not for fixed fields. + +If you have a dict, it maps something to something else. +You should be able to add and remove values. + +``attrs`` lets you be specific about those expectations; a dictionary does not. +It gives you a named entity (the class) in your code, which lets you explain in other places whether you take a parameter of that class or return a value of that class. + +In other words: if your dict has a fixed and known set of keys, it is an object, not a hash. +So if you never iterate over the keys of a dict, you should use a proper class. + + +…hand-written classes? +---------------------- + +While we're fans of all things artisanal, writing the same nine methods again and again doesn't qualify. +I usually manage to get some typos inside and there's simply more code that can break and thus has to be tested. + +To bring it into perspective, the equivalent of + +.. doctest:: + + >>> @attr.s + ... class SmartClass(object): + ... a = attr.ib() + ... b = attr.ib() + >>> SmartClass(1, 2) + SmartClass(a=1, b=2) + +is roughly + +.. doctest:: + + >>> class ArtisanalClass(object): + ... def __init__(self, a, b): + ... self.a = a + ... self.b = b + ... + ... def __repr__(self): + ... return "ArtisanalClass(a={}, b={})".format(self.a, self.b) + ... + ... def __eq__(self, other): + ... if other.__class__ is self.__class__: + ... return (self.a, self.b) == (other.a, other.b) + ... else: + ... return NotImplemented + ... + ... def __ne__(self, other): + ... result = self.__eq__(other) + ... if result is NotImplemented: + ... return NotImplemented + ... else: + ... return not result + ... + ... def __lt__(self, other): + ... if other.__class__ is self.__class__: + ... return (self.a, self.b) < (other.a, other.b) + ... else: + ... return NotImplemented + ... + ... def __le__(self, other): + ... if other.__class__ is self.__class__: + ... return (self.a, self.b) <= (other.a, other.b) + ... else: + ... return NotImplemented + ... + ... def __gt__(self, other): + ... if other.__class__ is self.__class__: + ... return (self.a, self.b) > (other.a, other.b) + ... else: + ... return NotImplemented + ... + ... def __ge__(self, other): + ... if other.__class__ is self.__class__: + ... return (self.a, self.b) >= (other.a, other.b) + ... else: + ... return NotImplemented + ... + ... def __hash__(self): + ... return hash((self.__class__, self.a, self.b)) + >>> ArtisanalClass(a=1, b=2) + ArtisanalClass(a=1, b=2) + +which is quite a mouthful and it doesn't even use any of ``attrs``'s more advanced features like validators or defaults values. +Also: no tests whatsoever. +And who will guarantee you, that you don't accidentally flip the ``<`` in your tenth implementation of ``__gt__``? + +It also should be noted that ``attrs`` is not an all-or-nothing solution. +You can freely choose which features you want and disable those that you want more control over: + +.. doctest:: + + >>> @attr.s(repr=False) + ... class SmartClass(object): + ... a = attr.ib() + ... b = attr.ib() + ... + ... def __repr__(self): + ... return "" % (self.a,) + >>> SmartClass(1, 2) + + +.. admonition:: Summary + + If you don't care and like typing, we're not gonna stop you. + + However it takes a lot of bias and determined rationalization to claim that ``attrs`` raises the mental burden on a project given how difficult it is to find the important bits in a hand-written class and how annoying it is to ensure you've copy-pasted your code correctly over all your classes. + + In any case, if you ever get sick of the repetitiveness and drowning important code in a sea of boilerplate, ``attrs`` will be waiting for you. diff --git a/testing/web-platform/tests/tools/third_party/attrs/mypy.ini b/testing/web-platform/tests/tools/third_party/attrs/mypy.ini new file mode 100644 index 0000000000..685c02599f --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/mypy.ini @@ -0,0 +1,3 @@ +[mypy] +disallow_untyped_defs = True +check_untyped_defs = True diff --git a/testing/web-platform/tests/tools/third_party/attrs/pyproject.toml b/testing/web-platform/tests/tools/third_party/attrs/pyproject.toml new file mode 100644 index 0000000000..52c0e49ec2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/pyproject.toml @@ -0,0 +1,71 @@ +[build-system] +requires = ["setuptools>=40.6.0", "wheel"] +build-backend = "setuptools.build_meta" + + +[tool.coverage.run] +parallel = true +branch = true +source = ["attr", "attrs"] + +[tool.coverage.paths] +source = ["src", ".tox/*/site-packages"] + +[tool.coverage.report] +show_missing = true +exclude_lines = [ + "pragma: no cover", + # PyPy is unacceptably slow under coverage. + "if PYPY:", +] + + +[tool.black] +line-length = 79 +extend-exclude = ''' +# Exclude pattern matching test till black gains Python 3.10 support +.*test_pattern_matching.* +''' + + +[tool.interrogate] +verbose = 2 +fail-under = 100 +whitelist-regex = ["test_.*"] + + +[tool.check-wheel-contents] +toplevel = ["attr", "attrs"] + + +[tool.isort] +profile = "attrs" + + +[tool.towncrier] + package = "attr" + package_dir = "src" + filename = "CHANGELOG.rst" + template = "changelog.d/towncrier_template.rst" + issue_format = "`#{issue} `_" + directory = "changelog.d" + title_format = "{version} ({project_date})" + underlines = ["-", "^"] + + [[tool.towncrier.section]] + path = "" + + [[tool.towncrier.type]] + directory = "breaking" + name = "Backward-incompatible Changes" + showcontent = true + + [[tool.towncrier.type]] + directory = "deprecation" + name = "Deprecations" + showcontent = true + + [[tool.towncrier.type]] + directory = "change" + name = "Changes" + showcontent = true diff --git a/testing/web-platform/tests/tools/third_party/attrs/setup.py b/testing/web-platform/tests/tools/third_party/attrs/setup.py new file mode 100644 index 0000000000..00e7b012ae --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/setup.py @@ -0,0 +1,151 @@ +# SPDX-License-Identifier: MIT + +import codecs +import os +import platform +import re +import sys + +from setuptools import find_packages, setup + + +############################################################################### + +NAME = "attrs" +PACKAGES = find_packages(where="src") +META_PATH = os.path.join("src", "attr", "__init__.py") +KEYWORDS = ["class", "attribute", "boilerplate"] +PROJECT_URLS = { + "Documentation": "https://www.attrs.org/", + "Changelog": "https://www.attrs.org/en/stable/changelog.html", + "Bug Tracker": "https://github.com/python-attrs/attrs/issues", + "Source Code": "https://github.com/python-attrs/attrs", + "Funding": "https://github.com/sponsors/hynek", + "Tidelift": "https://tidelift.com/subscription/pkg/pypi-attrs?" + "utm_source=pypi-attrs&utm_medium=pypi", + "Ko-fi": "https://ko-fi.com/the_hynek", +} +CLASSIFIERS = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Natural Language :: English", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Software Development :: Libraries :: Python Modules", +] +INSTALL_REQUIRES = [] +EXTRAS_REQUIRE = { + "docs": ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"], + "tests_no_zope": [ + # For regression test to ensure cloudpickle compat doesn't break. + 'cloudpickle; python_implementation == "CPython"', + # 5.0 introduced toml; parallel was broken until 5.0.2 + "coverage[toml]>=5.0.2", + "hypothesis", + "pympler", + "pytest>=4.3.0", # 4.3.0 dropped last use of `convert` + "six", + ], +} +if ( + sys.version_info[:2] >= (3, 6) + and platform.python_implementation() != "PyPy" +): + EXTRAS_REQUIRE["tests_no_zope"].extend(["mypy", "pytest-mypy-plugins"]) + +EXTRAS_REQUIRE["tests"] = EXTRAS_REQUIRE["tests_no_zope"] + ["zope.interface"] +EXTRAS_REQUIRE["dev"] = ( + EXTRAS_REQUIRE["tests"] + EXTRAS_REQUIRE["docs"] + ["pre-commit"] +) + +############################################################################### + +HERE = os.path.abspath(os.path.dirname(__file__)) + + +def read(*parts): + """ + Build an absolute path from *parts* and return the contents of the + resulting file. Assume UTF-8 encoding. + """ + with codecs.open(os.path.join(HERE, *parts), "rb", "utf-8") as f: + return f.read() + + +META_FILE = read(META_PATH) + + +def find_meta(meta): + """ + Extract __*meta*__ from META_FILE. + """ + meta_match = re.search( + r"^__{meta}__ = ['\"]([^'\"]*)['\"]".format(meta=meta), META_FILE, re.M + ) + if meta_match: + return meta_match.group(1) + raise RuntimeError("Unable to find __{meta}__ string.".format(meta=meta)) + + +LOGO = """ +.. image:: https://www.attrs.org/en/stable/_static/attrs_logo.png + :alt: attrs logo + :align: center +""" # noqa + +VERSION = find_meta("version") +URL = find_meta("url") +LONG = ( + LOGO + + read("README.rst").split(".. teaser-begin")[1] + + "\n\n" + + "Release Information\n" + + "===================\n\n" + + re.search( + r"(\d+.\d.\d \(.*?\)\r?\n.*?)\r?\n\r?\n\r?\n----\r?\n\r?\n\r?\n", + read("CHANGELOG.rst"), + re.S, + ).group(1) + + "\n\n`Full changelog " + + "<{url}en/stable/changelog.html>`_.\n\n".format(url=URL) + + read("AUTHORS.rst") +) + + +if __name__ == "__main__": + setup( + name=NAME, + description=find_meta("description"), + license=find_meta("license"), + url=URL, + project_urls=PROJECT_URLS, + version=VERSION, + author=find_meta("author"), + author_email=find_meta("email"), + maintainer=find_meta("author"), + maintainer_email=find_meta("email"), + keywords=KEYWORDS, + long_description=LONG, + long_description_content_type="text/x-rst", + packages=PACKAGES, + package_dir={"": "src"}, + python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*", + zip_safe=False, + classifiers=CLASSIFIERS, + install_requires=INSTALL_REQUIRES, + extras_require=EXTRAS_REQUIRE, + include_package_data=True, + options={"bdist_wheel": {"universal": "1"}}, + ) diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/__init__.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/__init__.py new file mode 100644 index 0000000000..f95c96dd57 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/__init__.py @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + +import sys + +from functools import partial + +from . import converters, exceptions, filters, setters, validators +from ._cmp import cmp_using +from ._config import get_run_validators, set_run_validators +from ._funcs import asdict, assoc, astuple, evolve, has, resolve_types +from ._make import ( + NOTHING, + Attribute, + Factory, + attrib, + attrs, + fields, + fields_dict, + make_class, + validate, +) +from ._version_info import VersionInfo + + +__version__ = "21.4.0" +__version_info__ = VersionInfo._from_version_string(__version__) + +__title__ = "attrs" +__description__ = "Classes Without Boilerplate" +__url__ = "https://www.attrs.org/" +__uri__ = __url__ +__doc__ = __description__ + " <" + __uri__ + ">" + +__author__ = "Hynek Schlawack" +__email__ = "hs@ox.cx" + +__license__ = "MIT" +__copyright__ = "Copyright (c) 2015 Hynek Schlawack" + + +s = attributes = attrs +ib = attr = attrib +dataclass = partial(attrs, auto_attribs=True) # happy Easter ;) + +__all__ = [ + "Attribute", + "Factory", + "NOTHING", + "asdict", + "assoc", + "astuple", + "attr", + "attrib", + "attributes", + "attrs", + "cmp_using", + "converters", + "evolve", + "exceptions", + "fields", + "fields_dict", + "filters", + "get_run_validators", + "has", + "ib", + "make_class", + "resolve_types", + "s", + "set_run_validators", + "setters", + "validate", + "validators", +] + +if sys.version_info[:2] >= (3, 6): + from ._next_gen import define, field, frozen, mutable # noqa: F401 + + __all__.extend(("define", "field", "frozen", "mutable")) diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/__init__.pyi b/testing/web-platform/tests/tools/third_party/attrs/src/attr/__init__.pyi new file mode 100644 index 0000000000..c0a2126503 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/__init__.pyi @@ -0,0 +1,484 @@ +import sys + +from typing import ( + Any, + Callable, + Dict, + Generic, + List, + Mapping, + Optional, + Sequence, + Tuple, + Type, + TypeVar, + Union, + overload, +) + +# `import X as X` is required to make these public +from . import converters as converters +from . import exceptions as exceptions +from . import filters as filters +from . import setters as setters +from . import validators as validators +from ._version_info import VersionInfo + +__version__: str +__version_info__: VersionInfo +__title__: str +__description__: str +__url__: str +__uri__: str +__author__: str +__email__: str +__license__: str +__copyright__: str + +_T = TypeVar("_T") +_C = TypeVar("_C", bound=type) + +_EqOrderType = Union[bool, Callable[[Any], Any]] +_ValidatorType = Callable[[Any, Attribute[_T], _T], Any] +_ConverterType = Callable[[Any], Any] +_FilterType = Callable[[Attribute[_T], _T], bool] +_ReprType = Callable[[Any], str] +_ReprArgType = Union[bool, _ReprType] +_OnSetAttrType = Callable[[Any, Attribute[Any], Any], Any] +_OnSetAttrArgType = Union[ + _OnSetAttrType, List[_OnSetAttrType], setters._NoOpType +] +_FieldTransformer = Callable[ + [type, List[Attribute[Any]]], List[Attribute[Any]] +] +_CompareWithType = Callable[[Any, Any], bool] +# FIXME: in reality, if multiple validators are passed they must be in a list +# or tuple, but those are invariant and so would prevent subtypes of +# _ValidatorType from working when passed in a list or tuple. +_ValidatorArgType = Union[_ValidatorType[_T], Sequence[_ValidatorType[_T]]] + +# _make -- + +NOTHING: object + +# NOTE: Factory lies about its return type to make this possible: +# `x: List[int] # = Factory(list)` +# Work around mypy issue #4554 in the common case by using an overload. +if sys.version_info >= (3, 8): + from typing import Literal + @overload + def Factory(factory: Callable[[], _T]) -> _T: ... + @overload + def Factory( + factory: Callable[[Any], _T], + takes_self: Literal[True], + ) -> _T: ... + @overload + def Factory( + factory: Callable[[], _T], + takes_self: Literal[False], + ) -> _T: ... + +else: + @overload + def Factory(factory: Callable[[], _T]) -> _T: ... + @overload + def Factory( + factory: Union[Callable[[Any], _T], Callable[[], _T]], + takes_self: bool = ..., + ) -> _T: ... + +# Static type inference support via __dataclass_transform__ implemented as per: +# https://github.com/microsoft/pyright/blob/1.1.135/specs/dataclass_transforms.md +# This annotation must be applied to all overloads of "define" and "attrs" +# +# NOTE: This is a typing construct and does not exist at runtime. Extensions +# wrapping attrs decorators should declare a separate __dataclass_transform__ +# signature in the extension module using the specification linked above to +# provide pyright support. +def __dataclass_transform__( + *, + eq_default: bool = True, + order_default: bool = False, + kw_only_default: bool = False, + field_descriptors: Tuple[Union[type, Callable[..., Any]], ...] = (()), +) -> Callable[[_T], _T]: ... + +class Attribute(Generic[_T]): + name: str + default: Optional[_T] + validator: Optional[_ValidatorType[_T]] + repr: _ReprArgType + cmp: _EqOrderType + eq: _EqOrderType + order: _EqOrderType + hash: Optional[bool] + init: bool + converter: Optional[_ConverterType] + metadata: Dict[Any, Any] + type: Optional[Type[_T]] + kw_only: bool + on_setattr: _OnSetAttrType + def evolve(self, **changes: Any) -> "Attribute[Any]": ... + +# NOTE: We had several choices for the annotation to use for type arg: +# 1) Type[_T] +# - Pros: Handles simple cases correctly +# - Cons: Might produce less informative errors in the case of conflicting +# TypeVars e.g. `attr.ib(default='bad', type=int)` +# 2) Callable[..., _T] +# - Pros: Better error messages than #1 for conflicting TypeVars +# - Cons: Terrible error messages for validator checks. +# e.g. attr.ib(type=int, validator=validate_str) +# -> error: Cannot infer function type argument +# 3) type (and do all of the work in the mypy plugin) +# - Pros: Simple here, and we could customize the plugin with our own errors. +# - Cons: Would need to write mypy plugin code to handle all the cases. +# We chose option #1. + +# `attr` lies about its return type to make the following possible: +# attr() -> Any +# attr(8) -> int +# attr(validator=) -> Whatever the callable expects. +# This makes this type of assignments possible: +# x: int = attr(8) +# +# This form catches explicit None or no default but with no other arguments +# returns Any. +@overload +def attrib( + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: None = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def attrib( + default: None = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: Optional[Type[_T]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def attrib( + default: _T, + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: Optional[Type[_T]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def attrib( + default: Optional[_T] = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + type: object = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... +@overload +def field( + *, + default: None = ..., + validator: None = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: None = ..., + factory: None = ..., + kw_only: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... + +# This form catches an explicit None or no default and infers the type from the +# other arguments. +@overload +def field( + *, + default: None = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form catches an explicit default argument. +@overload +def field( + *, + default: _T, + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> _T: ... + +# This form covers type=non-Type: e.g. forward references (str), Any +@overload +def field( + *, + default: Optional[_T] = ..., + validator: Optional[_ValidatorArgType[_T]] = ..., + repr: _ReprArgType = ..., + hash: Optional[bool] = ..., + init: bool = ..., + metadata: Optional[Mapping[Any, Any]] = ..., + converter: Optional[_ConverterType] = ..., + factory: Optional[Callable[[], _T]] = ..., + kw_only: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., +) -> Any: ... +@overload +@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) +def attrs( + maybe_cls: _C, + these: Optional[Dict[str, Any]] = ..., + repr_ns: Optional[str] = ..., + repr: bool = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., + match_args: bool = ..., +) -> _C: ... +@overload +@__dataclass_transform__(order_default=True, field_descriptors=(attrib, field)) +def attrs( + maybe_cls: None = ..., + these: Optional[Dict[str, Any]] = ..., + repr_ns: Optional[str] = ..., + repr: bool = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + auto_detect: bool = ..., + collect_by_mro: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., + match_args: bool = ..., +) -> Callable[[_C], _C]: ... +@overload +@__dataclass_transform__(field_descriptors=(attrib, field)) +def define( + maybe_cls: _C, + *, + these: Optional[Dict[str, Any]] = ..., + repr: bool = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., + match_args: bool = ..., +) -> _C: ... +@overload +@__dataclass_transform__(field_descriptors=(attrib, field)) +def define( + maybe_cls: None = ..., + *, + these: Optional[Dict[str, Any]] = ..., + repr: bool = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[bool] = ..., + order: Optional[bool] = ..., + auto_detect: bool = ..., + getstate_setstate: Optional[bool] = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., + match_args: bool = ..., +) -> Callable[[_C], _C]: ... + +mutable = define +frozen = define # they differ only in their defaults + +# TODO: add support for returning NamedTuple from the mypy plugin +class _Fields(Tuple[Attribute[Any], ...]): + def __getattr__(self, name: str) -> Attribute[Any]: ... + +def fields(cls: type) -> _Fields: ... +def fields_dict(cls: type) -> Dict[str, Attribute[Any]]: ... +def validate(inst: Any) -> None: ... +def resolve_types( + cls: _C, + globalns: Optional[Dict[str, Any]] = ..., + localns: Optional[Dict[str, Any]] = ..., + attribs: Optional[List[Attribute[Any]]] = ..., +) -> _C: ... + +# TODO: add support for returning a proper attrs class from the mypy plugin +# we use Any instead of _CountingAttr so that e.g. `make_class('Foo', +# [attr.ib()])` is valid +def make_class( + name: str, + attrs: Union[List[str], Tuple[str, ...], Dict[str, Any]], + bases: Tuple[type, ...] = ..., + repr_ns: Optional[str] = ..., + repr: bool = ..., + cmp: Optional[_EqOrderType] = ..., + hash: Optional[bool] = ..., + init: bool = ..., + slots: bool = ..., + frozen: bool = ..., + weakref_slot: bool = ..., + str: bool = ..., + auto_attribs: bool = ..., + kw_only: bool = ..., + cache_hash: bool = ..., + auto_exc: bool = ..., + eq: Optional[_EqOrderType] = ..., + order: Optional[_EqOrderType] = ..., + collect_by_mro: bool = ..., + on_setattr: Optional[_OnSetAttrArgType] = ..., + field_transformer: Optional[_FieldTransformer] = ..., +) -> type: ... + +# _funcs -- + +# TODO: add support for returning TypedDict from the mypy plugin +# FIXME: asdict/astuple do not honor their factory args. Waiting on one of +# these: +# https://github.com/python/mypy/issues/4236 +# https://github.com/python/typing/issues/253 +# XXX: remember to fix attrs.asdict/astuple too! +def asdict( + inst: Any, + recurse: bool = ..., + filter: Optional[_FilterType[Any]] = ..., + dict_factory: Type[Mapping[Any, Any]] = ..., + retain_collection_types: bool = ..., + value_serializer: Optional[ + Callable[[type, Attribute[Any], Any], Any] + ] = ..., + tuple_keys: Optional[bool] = ..., +) -> Dict[str, Any]: ... + +# TODO: add support for returning NamedTuple from the mypy plugin +def astuple( + inst: Any, + recurse: bool = ..., + filter: Optional[_FilterType[Any]] = ..., + tuple_factory: Type[Sequence[Any]] = ..., + retain_collection_types: bool = ..., +) -> Tuple[Any, ...]: ... +def has(cls: type) -> bool: ... +def assoc(inst: _T, **changes: Any) -> _T: ... +def evolve(inst: _T, **changes: Any) -> _T: ... + +# _config -- + +def set_run_validators(run: bool) -> None: ... +def get_run_validators() -> bool: ... + +# aliases -- + +s = attributes = attrs +ib = attr = attrib +dataclass = attrs # Technically, partial(attrs, auto_attribs=True) ;) diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/_cmp.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_cmp.py new file mode 100644 index 0000000000..6cffa4dbab --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_cmp.py @@ -0,0 +1,154 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + +import functools + +from ._compat import new_class +from ._make import _make_ne + + +_operation_names = {"eq": "==", "lt": "<", "le": "<=", "gt": ">", "ge": ">="} + + +def cmp_using( + eq=None, + lt=None, + le=None, + gt=None, + ge=None, + require_same_type=True, + class_name="Comparable", +): + """ + Create a class that can be passed into `attr.ib`'s ``eq``, ``order``, and + ``cmp`` arguments to customize field comparison. + + The resulting class will have a full set of ordering methods if + at least one of ``{lt, le, gt, ge}`` and ``eq`` are provided. + + :param Optional[callable] eq: `callable` used to evaluate equality + of two objects. + :param Optional[callable] lt: `callable` used to evaluate whether + one object is less than another object. + :param Optional[callable] le: `callable` used to evaluate whether + one object is less than or equal to another object. + :param Optional[callable] gt: `callable` used to evaluate whether + one object is greater than another object. + :param Optional[callable] ge: `callable` used to evaluate whether + one object is greater than or equal to another object. + + :param bool require_same_type: When `True`, equality and ordering methods + will return `NotImplemented` if objects are not of the same type. + + :param Optional[str] class_name: Name of class. Defaults to 'Comparable'. + + See `comparison` for more details. + + .. versionadded:: 21.1.0 + """ + + body = { + "__slots__": ["value"], + "__init__": _make_init(), + "_requirements": [], + "_is_comparable_to": _is_comparable_to, + } + + # Add operations. + num_order_functions = 0 + has_eq_function = False + + if eq is not None: + has_eq_function = True + body["__eq__"] = _make_operator("eq", eq) + body["__ne__"] = _make_ne() + + if lt is not None: + num_order_functions += 1 + body["__lt__"] = _make_operator("lt", lt) + + if le is not None: + num_order_functions += 1 + body["__le__"] = _make_operator("le", le) + + if gt is not None: + num_order_functions += 1 + body["__gt__"] = _make_operator("gt", gt) + + if ge is not None: + num_order_functions += 1 + body["__ge__"] = _make_operator("ge", ge) + + type_ = new_class(class_name, (object,), {}, lambda ns: ns.update(body)) + + # Add same type requirement. + if require_same_type: + type_._requirements.append(_check_same_type) + + # Add total ordering if at least one operation was defined. + if 0 < num_order_functions < 4: + if not has_eq_function: + # functools.total_ordering requires __eq__ to be defined, + # so raise early error here to keep a nice stack. + raise ValueError( + "eq must be define is order to complete ordering from " + "lt, le, gt, ge." + ) + type_ = functools.total_ordering(type_) + + return type_ + + +def _make_init(): + """ + Create __init__ method. + """ + + def __init__(self, value): + """ + Initialize object with *value*. + """ + self.value = value + + return __init__ + + +def _make_operator(name, func): + """ + Create operator method. + """ + + def method(self, other): + if not self._is_comparable_to(other): + return NotImplemented + + result = func(self.value, other.value) + if result is NotImplemented: + return NotImplemented + + return result + + method.__name__ = "__%s__" % (name,) + method.__doc__ = "Return a %s b. Computed by attrs." % ( + _operation_names[name], + ) + + return method + + +def _is_comparable_to(self, other): + """ + Check whether `other` is comparable to `self`. + """ + for func in self._requirements: + if not func(self, other): + return False + return True + + +def _check_same_type(self, other): + """ + Return True if *self* and *other* are of the same type, False otherwise. + """ + return other.value.__class__ is self.value.__class__ diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/_cmp.pyi b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_cmp.pyi new file mode 100644 index 0000000000..e71aaff7a1 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_cmp.pyi @@ -0,0 +1,13 @@ +from typing import Type + +from . import _CompareWithType + +def cmp_using( + eq: Optional[_CompareWithType], + lt: Optional[_CompareWithType], + le: Optional[_CompareWithType], + gt: Optional[_CompareWithType], + ge: Optional[_CompareWithType], + require_same_type: bool, + class_name: str, +) -> Type: ... diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/_compat.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_compat.py new file mode 100644 index 0000000000..dc0cb02b64 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_compat.py @@ -0,0 +1,261 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + +import platform +import sys +import threading +import types +import warnings + + +PY2 = sys.version_info[0] == 2 +PYPY = platform.python_implementation() == "PyPy" +PY36 = sys.version_info[:2] >= (3, 6) +HAS_F_STRINGS = PY36 +PY310 = sys.version_info[:2] >= (3, 10) + + +if PYPY or PY36: + ordered_dict = dict +else: + from collections import OrderedDict + + ordered_dict = OrderedDict + + +if PY2: + from collections import Mapping, Sequence + + from UserDict import IterableUserDict + + # We 'bundle' isclass instead of using inspect as importing inspect is + # fairly expensive (order of 10-15 ms for a modern machine in 2016) + def isclass(klass): + return isinstance(klass, (type, types.ClassType)) + + def new_class(name, bases, kwds, exec_body): + """ + A minimal stub of types.new_class that we need for make_class. + """ + ns = {} + exec_body(ns) + + return type(name, bases, ns) + + # TYPE is used in exceptions, repr(int) is different on Python 2 and 3. + TYPE = "type" + + def iteritems(d): + return d.iteritems() + + # Python 2 is bereft of a read-only dict proxy, so we make one! + class ReadOnlyDict(IterableUserDict): + """ + Best-effort read-only dict wrapper. + """ + + def __setitem__(self, key, val): + # We gently pretend we're a Python 3 mappingproxy. + raise TypeError( + "'mappingproxy' object does not support item assignment" + ) + + def update(self, _): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError( + "'mappingproxy' object has no attribute 'update'" + ) + + def __delitem__(self, _): + # We gently pretend we're a Python 3 mappingproxy. + raise TypeError( + "'mappingproxy' object does not support item deletion" + ) + + def clear(self): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError( + "'mappingproxy' object has no attribute 'clear'" + ) + + def pop(self, key, default=None): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError( + "'mappingproxy' object has no attribute 'pop'" + ) + + def popitem(self): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError( + "'mappingproxy' object has no attribute 'popitem'" + ) + + def setdefault(self, key, default=None): + # We gently pretend we're a Python 3 mappingproxy. + raise AttributeError( + "'mappingproxy' object has no attribute 'setdefault'" + ) + + def __repr__(self): + # Override to be identical to the Python 3 version. + return "mappingproxy(" + repr(self.data) + ")" + + def metadata_proxy(d): + res = ReadOnlyDict() + res.data.update(d) # We blocked update, so we have to do it like this. + return res + + def just_warn(*args, **kw): # pragma: no cover + """ + We only warn on Python 3 because we are not aware of any concrete + consequences of not setting the cell on Python 2. + """ + +else: # Python 3 and later. + from collections.abc import Mapping, Sequence # noqa + + def just_warn(*args, **kw): + """ + We only warn on Python 3 because we are not aware of any concrete + consequences of not setting the cell on Python 2. + """ + warnings.warn( + "Running interpreter doesn't sufficiently support code object " + "introspection. Some features like bare super() or accessing " + "__class__ will not work with slotted classes.", + RuntimeWarning, + stacklevel=2, + ) + + def isclass(klass): + return isinstance(klass, type) + + TYPE = "class" + + def iteritems(d): + return d.items() + + new_class = types.new_class + + def metadata_proxy(d): + return types.MappingProxyType(dict(d)) + + +def make_set_closure_cell(): + """Return a function of two arguments (cell, value) which sets + the value stored in the closure cell `cell` to `value`. + """ + # pypy makes this easy. (It also supports the logic below, but + # why not do the easy/fast thing?) + if PYPY: + + def set_closure_cell(cell, value): + cell.__setstate__((value,)) + + return set_closure_cell + + # Otherwise gotta do it the hard way. + + # Create a function that will set its first cellvar to `value`. + def set_first_cellvar_to(value): + x = value + return + + # This function will be eliminated as dead code, but + # not before its reference to `x` forces `x` to be + # represented as a closure cell rather than a local. + def force_x_to_be_a_cell(): # pragma: no cover + return x + + try: + # Extract the code object and make sure our assumptions about + # the closure behavior are correct. + if PY2: + co = set_first_cellvar_to.func_code + else: + co = set_first_cellvar_to.__code__ + if co.co_cellvars != ("x",) or co.co_freevars != (): + raise AssertionError # pragma: no cover + + # Convert this code object to a code object that sets the + # function's first _freevar_ (not cellvar) to the argument. + if sys.version_info >= (3, 8): + # CPython 3.8+ has an incompatible CodeType signature + # (added a posonlyargcount argument) but also added + # CodeType.replace() to do this without counting parameters. + set_first_freevar_code = co.replace( + co_cellvars=co.co_freevars, co_freevars=co.co_cellvars + ) + else: + args = [co.co_argcount] + if not PY2: + args.append(co.co_kwonlyargcount) + args.extend( + [ + co.co_nlocals, + co.co_stacksize, + co.co_flags, + co.co_code, + co.co_consts, + co.co_names, + co.co_varnames, + co.co_filename, + co.co_name, + co.co_firstlineno, + co.co_lnotab, + # These two arguments are reversed: + co.co_cellvars, + co.co_freevars, + ] + ) + set_first_freevar_code = types.CodeType(*args) + + def set_closure_cell(cell, value): + # Create a function using the set_first_freevar_code, + # whose first closure cell is `cell`. Calling it will + # change the value of that cell. + setter = types.FunctionType( + set_first_freevar_code, {}, "setter", (), (cell,) + ) + # And call it to set the cell. + setter(value) + + # Make sure it works on this interpreter: + def make_func_with_cell(): + x = None + + def func(): + return x # pragma: no cover + + return func + + if PY2: + cell = make_func_with_cell().func_closure[0] + else: + cell = make_func_with_cell().__closure__[0] + set_closure_cell(cell, 100) + if cell.cell_contents != 100: + raise AssertionError # pragma: no cover + + except Exception: + return just_warn + else: + return set_closure_cell + + +set_closure_cell = make_set_closure_cell() + +# Thread-local global to track attrs instances which are already being repr'd. +# This is needed because there is no other (thread-safe) way to pass info +# about the instances that are already being repr'd through the call stack +# in order to ensure we don't perform infinite recursion. +# +# For instance, if an instance contains a dict which contains that instance, +# we need to know that we're already repr'ing the outside instance from within +# the dict's repr() call. +# +# This lives here rather than in _make.py so that the functions in _make.py +# don't have a direct reference to the thread-local in their globals dict. +# If they have such a reference, it breaks cloudpickle. +repr_context = threading.local() diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/_config.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_config.py new file mode 100644 index 0000000000..fc9be29d00 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_config.py @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + + +__all__ = ["set_run_validators", "get_run_validators"] + +_run_validators = True + + +def set_run_validators(run): + """ + Set whether or not validators are run. By default, they are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.set_disabled()` + instead. + """ + if not isinstance(run, bool): + raise TypeError("'run' must be bool.") + global _run_validators + _run_validators = run + + +def get_run_validators(): + """ + Return whether or not validators are run. + + .. deprecated:: 21.3.0 It will not be removed, but it also will not be + moved to new ``attrs`` namespace. Use `attrs.validators.get_disabled()` + instead. + """ + return _run_validators diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/_funcs.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_funcs.py new file mode 100644 index 0000000000..4c90085a40 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_funcs.py @@ -0,0 +1,422 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + +import copy + +from ._compat import iteritems +from ._make import NOTHING, _obj_setattr, fields +from .exceptions import AttrsAttributeNotFoundError + + +def asdict( + inst, + recurse=True, + filter=None, + dict_factory=dict, + retain_collection_types=False, + value_serializer=None, +): + """ + Return the ``attrs`` attribute values of *inst* as a dict. + + Optionally recurse into other ``attrs``-decorated classes. + + :param inst: Instance of an ``attrs``-decorated class. + :param bool recurse: Recurse into classes that are also + ``attrs``-decorated. + :param callable filter: A callable whose return code determines whether an + attribute or element is included (``True``) or dropped (``False``). Is + called with the `attrs.Attribute` as the first argument and the + value as the second argument. + :param callable dict_factory: A callable to produce dictionaries from. For + example, to produce ordered dictionaries instead of normal Python + dictionaries, pass in ``collections.OrderedDict``. + :param bool retain_collection_types: Do not convert to ``list`` when + encountering an attribute whose type is ``tuple`` or ``set``. Only + meaningful if ``recurse`` is ``True``. + :param Optional[callable] value_serializer: A hook that is called for every + attribute or dict key/value. It receives the current instance, field + and value and must return the (updated) value. The hook is run *after* + the optional *filter* has been applied. + + :rtype: return type of *dict_factory* + + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 16.0.0 *dict_factory* + .. versionadded:: 16.1.0 *retain_collection_types* + .. versionadded:: 20.3.0 *value_serializer* + .. versionadded:: 21.3.0 If a dict has a collection for a key, it is + serialized as a tuple. + """ + attrs = fields(inst.__class__) + rv = dict_factory() + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + + if value_serializer is not None: + v = value_serializer(inst, a, v) + + if recurse is True: + if has(v.__class__): + rv[a.name] = asdict( + v, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + elif isinstance(v, (tuple, list, set, frozenset)): + cf = v.__class__ if retain_collection_types is True else list + rv[a.name] = cf( + [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in v + ] + ) + elif isinstance(v, dict): + df = dict_factory + rv[a.name] = df( + ( + _asdict_anything( + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + _asdict_anything( + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + ) + for kk, vv in iteritems(v) + ) + else: + rv[a.name] = v + else: + rv[a.name] = v + return rv + + +def _asdict_anything( + val, + is_key, + filter, + dict_factory, + retain_collection_types, + value_serializer, +): + """ + ``asdict`` only works on attrs instances, this works on anything. + """ + if getattr(val.__class__, "__attrs_attrs__", None) is not None: + # Attrs class. + rv = asdict( + val, + recurse=True, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + elif isinstance(val, (tuple, list, set, frozenset)): + if retain_collection_types is True: + cf = val.__class__ + elif is_key: + cf = tuple + else: + cf = list + + rv = cf( + [ + _asdict_anything( + i, + is_key=False, + filter=filter, + dict_factory=dict_factory, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ) + for i in val + ] + ) + elif isinstance(val, dict): + df = dict_factory + rv = df( + ( + _asdict_anything( + kk, + is_key=True, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + _asdict_anything( + vv, + is_key=False, + filter=filter, + dict_factory=df, + retain_collection_types=retain_collection_types, + value_serializer=value_serializer, + ), + ) + for kk, vv in iteritems(val) + ) + else: + rv = val + if value_serializer is not None: + rv = value_serializer(None, None, rv) + + return rv + + +def astuple( + inst, + recurse=True, + filter=None, + tuple_factory=tuple, + retain_collection_types=False, +): + """ + Return the ``attrs`` attribute values of *inst* as a tuple. + + Optionally recurse into other ``attrs``-decorated classes. + + :param inst: Instance of an ``attrs``-decorated class. + :param bool recurse: Recurse into classes that are also + ``attrs``-decorated. + :param callable filter: A callable whose return code determines whether an + attribute or element is included (``True``) or dropped (``False``). Is + called with the `attrs.Attribute` as the first argument and the + value as the second argument. + :param callable tuple_factory: A callable to produce tuples from. For + example, to produce lists instead of tuples. + :param bool retain_collection_types: Do not convert to ``list`` + or ``dict`` when encountering an attribute which type is + ``tuple``, ``dict`` or ``set``. Only meaningful if ``recurse`` is + ``True``. + + :rtype: return type of *tuple_factory* + + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 16.2.0 + """ + attrs = fields(inst.__class__) + rv = [] + retain = retain_collection_types # Very long. :/ + for a in attrs: + v = getattr(inst, a.name) + if filter is not None and not filter(a, v): + continue + if recurse is True: + if has(v.__class__): + rv.append( + astuple( + v, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + ) + elif isinstance(v, (tuple, list, set, frozenset)): + cf = v.__class__ if retain is True else list + rv.append( + cf( + [ + astuple( + j, + recurse=True, + filter=filter, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(j.__class__) + else j + for j in v + ] + ) + ) + elif isinstance(v, dict): + df = v.__class__ if retain is True else dict + rv.append( + df( + ( + astuple( + kk, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(kk.__class__) + else kk, + astuple( + vv, + tuple_factory=tuple_factory, + retain_collection_types=retain, + ) + if has(vv.__class__) + else vv, + ) + for kk, vv in iteritems(v) + ) + ) + else: + rv.append(v) + else: + rv.append(v) + + return rv if tuple_factory is list else tuple_factory(rv) + + +def has(cls): + """ + Check whether *cls* is a class with ``attrs`` attributes. + + :param type cls: Class to introspect. + :raise TypeError: If *cls* is not a class. + + :rtype: bool + """ + return getattr(cls, "__attrs_attrs__", None) is not None + + +def assoc(inst, **changes): + """ + Copy *inst* and apply *changes*. + + :param inst: Instance of a class with ``attrs`` attributes. + :param changes: Keyword changes in the new copy. + + :return: A copy of inst with *changes* incorporated. + + :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't + be found on *cls*. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. deprecated:: 17.1.0 + Use `attrs.evolve` instead if you can. + This function will not be removed du to the slightly different approach + compared to `attrs.evolve`. + """ + import warnings + + warnings.warn( + "assoc is deprecated and will be removed after 2018/01.", + DeprecationWarning, + stacklevel=2, + ) + new = copy.copy(inst) + attrs = fields(inst.__class__) + for k, v in iteritems(changes): + a = getattr(attrs, k, NOTHING) + if a is NOTHING: + raise AttrsAttributeNotFoundError( + "{k} is not an attrs attribute on {cl}.".format( + k=k, cl=new.__class__ + ) + ) + _obj_setattr(new, k, v) + return new + + +def evolve(inst, **changes): + """ + Create a new instance, based on *inst* with *changes* applied. + + :param inst: Instance of a class with ``attrs`` attributes. + :param changes: Keyword changes in the new copy. + + :return: A copy of inst with *changes* incorporated. + + :raise TypeError: If *attr_name* couldn't be found in the class + ``__init__``. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + .. versionadded:: 17.1.0 + """ + cls = inst.__class__ + attrs = fields(cls) + for a in attrs: + if not a.init: + continue + attr_name = a.name # To deal with private attributes. + init_name = attr_name if attr_name[0] != "_" else attr_name[1:] + if init_name not in changes: + changes[init_name] = getattr(inst, attr_name) + + return cls(**changes) + + +def resolve_types(cls, globalns=None, localns=None, attribs=None): + """ + Resolve any strings and forward annotations in type annotations. + + This is only required if you need concrete types in `Attribute`'s *type* + field. In other words, you don't need to resolve your types if you only + use them for static type checking. + + With no arguments, names will be looked up in the module in which the class + was created. If this is not what you want, e.g. if the name only exists + inside a method, you may pass *globalns* or *localns* to specify other + dictionaries in which to look up these names. See the docs of + `typing.get_type_hints` for more details. + + :param type cls: Class to resolve. + :param Optional[dict] globalns: Dictionary containing global variables. + :param Optional[dict] localns: Dictionary containing local variables. + :param Optional[list] attribs: List of attribs for the given class. + This is necessary when calling from inside a ``field_transformer`` + since *cls* is not an ``attrs`` class yet. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class and you didn't pass any attribs. + :raise NameError: If types cannot be resolved because of missing variables. + + :returns: *cls* so you can use this function also as a class decorator. + Please note that you have to apply it **after** `attrs.define`. That + means the decorator has to come in the line **before** `attrs.define`. + + .. versionadded:: 20.1.0 + .. versionadded:: 21.1.0 *attribs* + + """ + # Since calling get_type_hints is expensive we cache whether we've + # done it already. + if getattr(cls, "__attrs_types_resolved__", None) != cls: + import typing + + hints = typing.get_type_hints(cls, globalns=globalns, localns=localns) + for field in fields(cls) if attribs is None else attribs: + if field.name in hints: + # Since fields have been frozen we must work around it. + _obj_setattr(field, "type", hints[field.name]) + # We store the class we resolved so that subclasses know they haven't + # been resolved. + cls.__attrs_types_resolved__ = cls + + # Return the class so you can use it as a decorator too. + return cls diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/_make.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_make.py new file mode 100644 index 0000000000..d46f8a3e7a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_make.py @@ -0,0 +1,3173 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + +import copy +import inspect +import linecache +import sys +import warnings + +from operator import itemgetter + +# We need to import _compat itself in addition to the _compat members to avoid +# having the thread-local in the globals here. +from . import _compat, _config, setters +from ._compat import ( + HAS_F_STRINGS, + PY2, + PY310, + PYPY, + isclass, + iteritems, + metadata_proxy, + new_class, + ordered_dict, + set_closure_cell, +) +from .exceptions import ( + DefaultAlreadySetError, + FrozenInstanceError, + NotAnAttrsClassError, + PythonTooOldError, + UnannotatedAttributeError, +) + + +if not PY2: + import typing + + +# This is used at least twice, so cache it here. +_obj_setattr = object.__setattr__ +_init_converter_pat = "__attr_converter_%s" +_init_factory_pat = "__attr_factory_{}" +_tuple_property_pat = ( + " {attr_name} = _attrs_property(_attrs_itemgetter({index}))" +) +_classvar_prefixes = ( + "typing.ClassVar", + "t.ClassVar", + "ClassVar", + "typing_extensions.ClassVar", +) +# we don't use a double-underscore prefix because that triggers +# name mangling when trying to create a slot for the field +# (when slots=True) +_hash_cache_field = "_attrs_cached_hash" + +_empty_metadata_singleton = metadata_proxy({}) + +# Unique object for unequivocal getattr() defaults. +_sentinel = object() + +_ng_default_on_setattr = setters.pipe(setters.convert, setters.validate) + + +class _Nothing(object): + """ + Sentinel class to indicate the lack of a value when ``None`` is ambiguous. + + ``_Nothing`` is a singleton. There is only ever one of it. + + .. versionchanged:: 21.1.0 ``bool(NOTHING)`` is now False. + """ + + _singleton = None + + def __new__(cls): + if _Nothing._singleton is None: + _Nothing._singleton = super(_Nothing, cls).__new__(cls) + return _Nothing._singleton + + def __repr__(self): + return "NOTHING" + + def __bool__(self): + return False + + def __len__(self): + return 0 # __bool__ for Python 2 + + +NOTHING = _Nothing() +""" +Sentinel to indicate the lack of a value when ``None`` is ambiguous. +""" + + +class _CacheHashWrapper(int): + """ + An integer subclass that pickles / copies as None + + This is used for non-slots classes with ``cache_hash=True``, to avoid + serializing a potentially (even likely) invalid hash value. Since ``None`` + is the default value for uncalculated hashes, whenever this is copied, + the copy's value for the hash should automatically reset. + + See GH #613 for more details. + """ + + if PY2: + # For some reason `type(None)` isn't callable in Python 2, but we don't + # actually need a constructor for None objects, we just need any + # available function that returns None. + def __reduce__(self, _none_constructor=getattr, _args=(0, "", None)): + return _none_constructor, _args + + else: + + def __reduce__(self, _none_constructor=type(None), _args=()): + return _none_constructor, _args + + +def attrib( + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=None, + init=True, + metadata=None, + type=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, +): + """ + Create a new attribute on a class. + + .. warning:: + + Does *not* do anything unless the class is also decorated with + `attr.s`! + + :param default: A value that is used if an ``attrs``-generated ``__init__`` + is used and no value is passed while instantiating or the attribute is + excluded using ``init=False``. + + If the value is an instance of `attrs.Factory`, its callable will be + used to construct a new value (useful for mutable data types like lists + or dicts). + + If a default is not set (or set manually to `attrs.NOTHING`), a value + *must* be supplied when instantiating; otherwise a `TypeError` + will be raised. + + The default can also be set using decorator notation as shown below. + + :type default: Any value + + :param callable factory: Syntactic sugar for + ``default=attr.Factory(factory)``. + + :param validator: `callable` that is called by ``attrs``-generated + ``__init__`` methods after the instance has been initialized. They + receive the initialized instance, the :func:`~attrs.Attribute`, and the + passed value. + + The return value is *not* inspected so the validator has to throw an + exception itself. + + If a `list` is passed, its items are treated as validators and must + all pass. + + Validators can be globally disabled and re-enabled using + `get_run_validators`. + + The validator can also be set using decorator notation as shown below. + + :type validator: `callable` or a `list` of `callable`\\ s. + + :param repr: Include this attribute in the generated ``__repr__`` + method. If ``True``, include the attribute; if ``False``, omit it. By + default, the built-in ``repr()`` function is used. To override how the + attribute value is formatted, pass a ``callable`` that takes a single + value and returns a string. Note that the resulting string is used + as-is, i.e. it will be used directly *instead* of calling ``repr()`` + (the default). + :type repr: a `bool` or a `callable` to use a custom function. + + :param eq: If ``True`` (default), include this attribute in the + generated ``__eq__`` and ``__ne__`` methods that check two instances + for equality. To override how the attribute value is compared, + pass a ``callable`` that takes a single value and returns the value + to be compared. + :type eq: a `bool` or a `callable`. + + :param order: If ``True`` (default), include this attributes in the + generated ``__lt__``, ``__le__``, ``__gt__`` and ``__ge__`` methods. + To override how the attribute value is ordered, + pass a ``callable`` that takes a single value and returns the value + to be ordered. + :type order: a `bool` or a `callable`. + + :param cmp: Setting *cmp* is equivalent to setting *eq* and *order* to the + same value. Must not be mixed with *eq* or *order*. + :type cmp: a `bool` or a `callable`. + + :param Optional[bool] hash: Include this attribute in the generated + ``__hash__`` method. If ``None`` (default), mirror *eq*'s value. This + is the correct behavior according the Python spec. Setting this value + to anything else than ``None`` is *discouraged*. + :param bool init: Include this attribute in the generated ``__init__`` + method. It is possible to set this to ``False`` and set a default + value. In that case this attributed is unconditionally initialized + with the specified default value or factory. + :param callable converter: `callable` that is called by + ``attrs``-generated ``__init__`` methods to convert attribute's value + to the desired format. It is given the passed-in value, and the + returned value will be used as the new value of the attribute. The + value is converted before being passed to the validator, if any. + :param metadata: An arbitrary mapping, to be used by third-party + components. See `extending_metadata`. + :param type: The type of the attribute. In Python 3.6 or greater, the + preferred method to specify the type is using a variable annotation + (see `PEP 526 `_). + This argument is provided for backward compatibility. + Regardless of the approach used, the type will be stored on + ``Attribute.type``. + + Please note that ``attrs`` doesn't do anything with this metadata by + itself. You can use it as part of your own code or for + `static type checking `. + :param kw_only: Make this attribute keyword-only (Python 3+) + in the generated ``__init__`` (if ``init`` is ``False``, this + parameter is ignored). + :param on_setattr: Allows to overwrite the *on_setattr* setting from + `attr.s`. If left `None`, the *on_setattr* value from `attr.s` is used. + Set to `attrs.setters.NO_OP` to run **no** `setattr` hooks for this + attribute -- regardless of the setting in `attr.s`. + :type on_setattr: `callable`, or a list of callables, or `None`, or + `attrs.setters.NO_OP` + + .. versionadded:: 15.2.0 *convert* + .. versionadded:: 16.3.0 *metadata* + .. versionchanged:: 17.1.0 *validator* can be a ``list`` now. + .. versionchanged:: 17.1.0 + *hash* is ``None`` and therefore mirrors *eq* by default. + .. versionadded:: 17.3.0 *type* + .. deprecated:: 17.4.0 *convert* + .. versionadded:: 17.4.0 *converter* as a replacement for the deprecated + *convert* to achieve consistency with other noun-based arguments. + .. versionadded:: 18.1.0 + ``factory=f`` is syntactic sugar for ``default=attr.Factory(f)``. + .. versionadded:: 18.2.0 *kw_only* + .. versionchanged:: 19.2.0 *convert* keyword argument removed. + .. versionchanged:: 19.2.0 *repr* also accepts a custom callable. + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.3.0 *kw_only* backported to Python 2 + .. versionchanged:: 21.1.0 + *eq*, *order*, and *cmp* also accept a custom callable + .. versionchanged:: 21.1.0 *cmp* undeprecated + """ + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq, order, True + ) + + if hash is not None and hash is not True and hash is not False: + raise TypeError( + "Invalid value for hash. Must be True, False, or None." + ) + + if factory is not None: + if default is not NOTHING: + raise ValueError( + "The `default` and `factory` arguments are mutually " + "exclusive." + ) + if not callable(factory): + raise ValueError("The `factory` argument must be a callable.") + default = Factory(factory) + + if metadata is None: + metadata = {} + + # Apply syntactic sugar by auto-wrapping. + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + if validator and isinstance(validator, (list, tuple)): + validator = and_(*validator) + + if converter and isinstance(converter, (list, tuple)): + converter = pipe(*converter) + + return _CountingAttr( + default=default, + validator=validator, + repr=repr, + cmp=None, + hash=hash, + init=init, + converter=converter, + metadata=metadata, + type=type, + kw_only=kw_only, + eq=eq, + eq_key=eq_key, + order=order, + order_key=order_key, + on_setattr=on_setattr, + ) + + +def _compile_and_eval(script, globs, locs=None, filename=""): + """ + "Exec" the script with the given global (globs) and local (locs) variables. + """ + bytecode = compile(script, filename, "exec") + eval(bytecode, globs, locs) + + +def _make_method(name, script, filename, globs=None): + """ + Create the method with the script given and return the method object. + """ + locs = {} + if globs is None: + globs = {} + + # In order of debuggers like PDB being able to step through the code, + # we add a fake linecache entry. + count = 1 + base_filename = filename + while True: + linecache_tuple = ( + len(script), + None, + script.splitlines(True), + filename, + ) + old_val = linecache.cache.setdefault(filename, linecache_tuple) + if old_val == linecache_tuple: + break + else: + filename = "{}-{}>".format(base_filename[:-1], count) + count += 1 + + _compile_and_eval(script, globs, locs, filename) + + return locs[name] + + +def _make_attr_tuple_class(cls_name, attr_names): + """ + Create a tuple subclass to hold `Attribute`s for an `attrs` class. + + The subclass is a bare tuple with properties for names. + + class MyClassAttributes(tuple): + __slots__ = () + x = property(itemgetter(0)) + """ + attr_class_name = "{}Attributes".format(cls_name) + attr_class_template = [ + "class {}(tuple):".format(attr_class_name), + " __slots__ = ()", + ] + if attr_names: + for i, attr_name in enumerate(attr_names): + attr_class_template.append( + _tuple_property_pat.format(index=i, attr_name=attr_name) + ) + else: + attr_class_template.append(" pass") + globs = {"_attrs_itemgetter": itemgetter, "_attrs_property": property} + _compile_and_eval("\n".join(attr_class_template), globs) + return globs[attr_class_name] + + +# Tuple class for extracted attributes from a class definition. +# `base_attrs` is a subset of `attrs`. +_Attributes = _make_attr_tuple_class( + "_Attributes", + [ + # all attributes to build dunder methods for + "attrs", + # attributes that have been inherited + "base_attrs", + # map inherited attributes to their originating classes + "base_attrs_map", + ], +) + + +def _is_class_var(annot): + """ + Check whether *annot* is a typing.ClassVar. + + The string comparison hack is used to avoid evaluating all string + annotations which would put attrs-based classes at a performance + disadvantage compared to plain old classes. + """ + annot = str(annot) + + # Annotation can be quoted. + if annot.startswith(("'", '"')) and annot.endswith(("'", '"')): + annot = annot[1:-1] + + return annot.startswith(_classvar_prefixes) + + +def _has_own_attribute(cls, attrib_name): + """ + Check whether *cls* defines *attrib_name* (and doesn't just inherit it). + + Requires Python 3. + """ + attr = getattr(cls, attrib_name, _sentinel) + if attr is _sentinel: + return False + + for base_cls in cls.__mro__[1:]: + a = getattr(base_cls, attrib_name, None) + if attr is a: + return False + + return True + + +def _get_annotations(cls): + """ + Get annotations for *cls*. + """ + if _has_own_attribute(cls, "__annotations__"): + return cls.__annotations__ + + return {} + + +def _counter_getter(e): + """ + Key function for sorting to avoid re-creating a lambda for every class. + """ + return e[1].counter + + +def _collect_base_attrs(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in reversed(cls.__mro__[1:-1]): + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.inherited or a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + # For each name, only keep the freshest definition i.e. the furthest at the + # back. base_attr_map is fine because it gets overwritten with every new + # instance. + filtered = [] + seen = set() + for a in reversed(base_attrs): + if a.name in seen: + continue + filtered.insert(0, a) + seen.add(a.name) + + return filtered, base_attr_map + + +def _collect_base_attrs_broken(cls, taken_attr_names): + """ + Collect attr.ibs from base classes of *cls*, except *taken_attr_names*. + + N.B. *taken_attr_names* will be mutated. + + Adhere to the old incorrect behavior. + + Notably it collects from the front and considers inherited attributes which + leads to the buggy behavior reported in #428. + """ + base_attrs = [] + base_attr_map = {} # A dictionary of base attrs to their classes. + + # Traverse the MRO and collect attributes. + for base_cls in cls.__mro__[1:-1]: + for a in getattr(base_cls, "__attrs_attrs__", []): + if a.name in taken_attr_names: + continue + + a = a.evolve(inherited=True) + taken_attr_names.add(a.name) + base_attrs.append(a) + base_attr_map[a.name] = base_cls + + return base_attrs, base_attr_map + + +def _transform_attrs( + cls, these, auto_attribs, kw_only, collect_by_mro, field_transformer +): + """ + Transform all `_CountingAttr`s on a class into `Attribute`s. + + If *these* is passed, use that and don't look for them on the class. + + *collect_by_mro* is True, collect them in the correct MRO order, otherwise + use the old -- incorrect -- order. See #428. + + Return an `_Attributes`. + """ + cd = cls.__dict__ + anns = _get_annotations(cls) + + if these is not None: + ca_list = [(name, ca) for name, ca in iteritems(these)] + + if not isinstance(these, ordered_dict): + ca_list.sort(key=_counter_getter) + elif auto_attribs is True: + ca_names = { + name + for name, attr in cd.items() + if isinstance(attr, _CountingAttr) + } + ca_list = [] + annot_names = set() + for attr_name, type in anns.items(): + if _is_class_var(type): + continue + annot_names.add(attr_name) + a = cd.get(attr_name, NOTHING) + + if not isinstance(a, _CountingAttr): + if a is NOTHING: + a = attrib() + else: + a = attrib(default=a) + ca_list.append((attr_name, a)) + + unannotated = ca_names - annot_names + if len(unannotated) > 0: + raise UnannotatedAttributeError( + "The following `attr.ib`s lack a type annotation: " + + ", ".join( + sorted(unannotated, key=lambda n: cd.get(n).counter) + ) + + "." + ) + else: + ca_list = sorted( + ( + (name, attr) + for name, attr in cd.items() + if isinstance(attr, _CountingAttr) + ), + key=lambda e: e[1].counter, + ) + + own_attrs = [ + Attribute.from_counting_attr( + name=attr_name, ca=ca, type=anns.get(attr_name) + ) + for attr_name, ca in ca_list + ] + + if collect_by_mro: + base_attrs, base_attr_map = _collect_base_attrs( + cls, {a.name for a in own_attrs} + ) + else: + base_attrs, base_attr_map = _collect_base_attrs_broken( + cls, {a.name for a in own_attrs} + ) + + if kw_only: + own_attrs = [a.evolve(kw_only=True) for a in own_attrs] + base_attrs = [a.evolve(kw_only=True) for a in base_attrs] + + attrs = base_attrs + own_attrs + + # Mandatory vs non-mandatory attr order only matters when they are part of + # the __init__ signature and when they aren't kw_only (which are moved to + # the end and can be mandatory or non-mandatory in any order, as they will + # be specified as keyword args anyway). Check the order of those attrs: + had_default = False + for a in (a for a in attrs if a.init is not False and a.kw_only is False): + if had_default is True and a.default is NOTHING: + raise ValueError( + "No mandatory attributes allowed after an attribute with a " + "default value or factory. Attribute in question: %r" % (a,) + ) + + if had_default is False and a.default is not NOTHING: + had_default = True + + if field_transformer is not None: + attrs = field_transformer(cls, attrs) + + # Create AttrsClass *after* applying the field_transformer since it may + # add or remove attributes! + attr_names = [a.name for a in attrs] + AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names) + + return _Attributes((AttrsClass(attrs), base_attrs, base_attr_map)) + + +if PYPY: + + def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + if isinstance(self, BaseException) and name in ( + "__cause__", + "__context__", + ): + BaseException.__setattr__(self, name, value) + return + + raise FrozenInstanceError() + +else: + + def _frozen_setattrs(self, name, value): + """ + Attached to frozen classes as __setattr__. + """ + raise FrozenInstanceError() + + +def _frozen_delattrs(self, name): + """ + Attached to frozen classes as __delattr__. + """ + raise FrozenInstanceError() + + +class _ClassBuilder(object): + """ + Iteratively build *one* class. + """ + + __slots__ = ( + "_attr_names", + "_attrs", + "_base_attr_map", + "_base_names", + "_cache_hash", + "_cls", + "_cls_dict", + "_delete_attribs", + "_frozen", + "_has_pre_init", + "_has_post_init", + "_is_exc", + "_on_setattr", + "_slots", + "_weakref_slot", + "_wrote_own_setattr", + "_has_custom_setattr", + ) + + def __init__( + self, + cls, + these, + slots, + frozen, + weakref_slot, + getstate_setstate, + auto_attribs, + kw_only, + cache_hash, + is_exc, + collect_by_mro, + on_setattr, + has_custom_setattr, + field_transformer, + ): + attrs, base_attrs, base_map = _transform_attrs( + cls, + these, + auto_attribs, + kw_only, + collect_by_mro, + field_transformer, + ) + + self._cls = cls + self._cls_dict = dict(cls.__dict__) if slots else {} + self._attrs = attrs + self._base_names = set(a.name for a in base_attrs) + self._base_attr_map = base_map + self._attr_names = tuple(a.name for a in attrs) + self._slots = slots + self._frozen = frozen + self._weakref_slot = weakref_slot + self._cache_hash = cache_hash + self._has_pre_init = bool(getattr(cls, "__attrs_pre_init__", False)) + self._has_post_init = bool(getattr(cls, "__attrs_post_init__", False)) + self._delete_attribs = not bool(these) + self._is_exc = is_exc + self._on_setattr = on_setattr + + self._has_custom_setattr = has_custom_setattr + self._wrote_own_setattr = False + + self._cls_dict["__attrs_attrs__"] = self._attrs + + if frozen: + self._cls_dict["__setattr__"] = _frozen_setattrs + self._cls_dict["__delattr__"] = _frozen_delattrs + + self._wrote_own_setattr = True + elif on_setattr in ( + _ng_default_on_setattr, + setters.validate, + setters.convert, + ): + has_validator = has_converter = False + for a in attrs: + if a.validator is not None: + has_validator = True + if a.converter is not None: + has_converter = True + + if has_validator and has_converter: + break + if ( + ( + on_setattr == _ng_default_on_setattr + and not (has_validator or has_converter) + ) + or (on_setattr == setters.validate and not has_validator) + or (on_setattr == setters.convert and not has_converter) + ): + # If class-level on_setattr is set to convert + validate, but + # there's no field to convert or validate, pretend like there's + # no on_setattr. + self._on_setattr = None + + if getstate_setstate: + ( + self._cls_dict["__getstate__"], + self._cls_dict["__setstate__"], + ) = self._make_getstate_setstate() + + def __repr__(self): + return "<_ClassBuilder(cls={cls})>".format(cls=self._cls.__name__) + + def build_class(self): + """ + Finalize class based on the accumulated configuration. + + Builder cannot be used after calling this method. + """ + if self._slots is True: + return self._create_slots_class() + else: + return self._patch_original_class() + + def _patch_original_class(self): + """ + Apply accumulated methods and return the class. + """ + cls = self._cls + base_names = self._base_names + + # Clean class of attribute definitions (`attr.ib()`s). + if self._delete_attribs: + for name in self._attr_names: + if ( + name not in base_names + and getattr(cls, name, _sentinel) is not _sentinel + ): + try: + delattr(cls, name) + except AttributeError: + # This can happen if a base class defines a class + # variable and we want to set an attribute with the + # same name by using only a type annotation. + pass + + # Attach our dunder methods. + for name, value in self._cls_dict.items(): + setattr(cls, name, value) + + # If we've inherited an attrs __setattr__ and don't write our own, + # reset it to object's. + if not self._wrote_own_setattr and getattr( + cls, "__attrs_own_setattr__", False + ): + cls.__attrs_own_setattr__ = False + + if not self._has_custom_setattr: + cls.__setattr__ = object.__setattr__ + + return cls + + def _create_slots_class(self): + """ + Build and return a new class with a `__slots__` attribute. + """ + cd = { + k: v + for k, v in iteritems(self._cls_dict) + if k not in tuple(self._attr_names) + ("__dict__", "__weakref__") + } + + # If our class doesn't have its own implementation of __setattr__ + # (either from the user or by us), check the bases, if one of them has + # an attrs-made __setattr__, that needs to be reset. We don't walk the + # MRO because we only care about our immediate base classes. + # XXX: This can be confused by subclassing a slotted attrs class with + # XXX: a non-attrs class and subclass the resulting class with an attrs + # XXX: class. See `test_slotted_confused` for details. For now that's + # XXX: OK with us. + if not self._wrote_own_setattr: + cd["__attrs_own_setattr__"] = False + + if not self._has_custom_setattr: + for base_cls in self._cls.__bases__: + if base_cls.__dict__.get("__attrs_own_setattr__", False): + cd["__setattr__"] = object.__setattr__ + break + + # Traverse the MRO to collect existing slots + # and check for an existing __weakref__. + existing_slots = dict() + weakref_inherited = False + for base_cls in self._cls.__mro__[1:-1]: + if base_cls.__dict__.get("__weakref__", None) is not None: + weakref_inherited = True + existing_slots.update( + { + name: getattr(base_cls, name) + for name in getattr(base_cls, "__slots__", []) + } + ) + + base_names = set(self._base_names) + + names = self._attr_names + if ( + self._weakref_slot + and "__weakref__" not in getattr(self._cls, "__slots__", ()) + and "__weakref__" not in names + and not weakref_inherited + ): + names += ("__weakref__",) + + # We only add the names of attributes that aren't inherited. + # Setting __slots__ to inherited attributes wastes memory. + slot_names = [name for name in names if name not in base_names] + # There are slots for attributes from current class + # that are defined in parent classes. + # As their descriptors may be overriden by a child class, + # we collect them here and update the class dict + reused_slots = { + slot: slot_descriptor + for slot, slot_descriptor in iteritems(existing_slots) + if slot in slot_names + } + slot_names = [name for name in slot_names if name not in reused_slots] + cd.update(reused_slots) + if self._cache_hash: + slot_names.append(_hash_cache_field) + cd["__slots__"] = tuple(slot_names) + + qualname = getattr(self._cls, "__qualname__", None) + if qualname is not None: + cd["__qualname__"] = qualname + + # Create new class based on old class and our methods. + cls = type(self._cls)(self._cls.__name__, self._cls.__bases__, cd) + + # The following is a fix for + # . On Python 3, + # if a method mentions `__class__` or uses the no-arg super(), the + # compiler will bake a reference to the class in the method itself + # as `method.__closure__`. Since we replace the class with a + # clone, we rewrite these references so it keeps working. + for item in cls.__dict__.values(): + if isinstance(item, (classmethod, staticmethod)): + # Class- and staticmethods hide their functions inside. + # These might need to be rewritten as well. + closure_cells = getattr(item.__func__, "__closure__", None) + elif isinstance(item, property): + # Workaround for property `super()` shortcut (PY3-only). + # There is no universal way for other descriptors. + closure_cells = getattr(item.fget, "__closure__", None) + else: + closure_cells = getattr(item, "__closure__", None) + + if not closure_cells: # Catch None or the empty list. + continue + for cell in closure_cells: + try: + match = cell.cell_contents is self._cls + except ValueError: # ValueError: Cell is empty + pass + else: + if match: + set_closure_cell(cell, cls) + + return cls + + def add_repr(self, ns): + self._cls_dict["__repr__"] = self._add_method_dunders( + _make_repr(self._attrs, ns, self._cls) + ) + return self + + def add_str(self): + repr = self._cls_dict.get("__repr__") + if repr is None: + raise ValueError( + "__str__ can only be generated if a __repr__ exists." + ) + + def __str__(self): + return self.__repr__() + + self._cls_dict["__str__"] = self._add_method_dunders(__str__) + return self + + def _make_getstate_setstate(self): + """ + Create custom __setstate__ and __getstate__ methods. + """ + # __weakref__ is not writable. + state_attr_names = tuple( + an for an in self._attr_names if an != "__weakref__" + ) + + def slots_getstate(self): + """ + Automatically created by attrs. + """ + return tuple(getattr(self, name) for name in state_attr_names) + + hash_caching_enabled = self._cache_hash + + def slots_setstate(self, state): + """ + Automatically created by attrs. + """ + __bound_setattr = _obj_setattr.__get__(self, Attribute) + for name, value in zip(state_attr_names, state): + __bound_setattr(name, value) + + # The hash code cache is not included when the object is + # serialized, but it still needs to be initialized to None to + # indicate that the first call to __hash__ should be a cache + # miss. + if hash_caching_enabled: + __bound_setattr(_hash_cache_field, None) + + return slots_getstate, slots_setstate + + def make_unhashable(self): + self._cls_dict["__hash__"] = None + return self + + def add_hash(self): + self._cls_dict["__hash__"] = self._add_method_dunders( + _make_hash( + self._cls, + self._attrs, + frozen=self._frozen, + cache_hash=self._cache_hash, + ) + ) + + return self + + def add_init(self): + self._cls_dict["__init__"] = self._add_method_dunders( + _make_init( + self._cls, + self._attrs, + self._has_pre_init, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=False, + ) + ) + + return self + + def add_match_args(self): + self._cls_dict["__match_args__"] = tuple( + field.name + for field in self._attrs + if field.init and not field.kw_only + ) + + def add_attrs_init(self): + self._cls_dict["__attrs_init__"] = self._add_method_dunders( + _make_init( + self._cls, + self._attrs, + self._has_pre_init, + self._has_post_init, + self._frozen, + self._slots, + self._cache_hash, + self._base_attr_map, + self._is_exc, + self._on_setattr, + attrs_init=True, + ) + ) + + return self + + def add_eq(self): + cd = self._cls_dict + + cd["__eq__"] = self._add_method_dunders( + _make_eq(self._cls, self._attrs) + ) + cd["__ne__"] = self._add_method_dunders(_make_ne()) + + return self + + def add_order(self): + cd = self._cls_dict + + cd["__lt__"], cd["__le__"], cd["__gt__"], cd["__ge__"] = ( + self._add_method_dunders(meth) + for meth in _make_order(self._cls, self._attrs) + ) + + return self + + def add_setattr(self): + if self._frozen: + return self + + sa_attrs = {} + for a in self._attrs: + on_setattr = a.on_setattr or self._on_setattr + if on_setattr and on_setattr is not setters.NO_OP: + sa_attrs[a.name] = a, on_setattr + + if not sa_attrs: + return self + + if self._has_custom_setattr: + # We need to write a __setattr__ but there already is one! + raise ValueError( + "Can't combine custom __setattr__ with on_setattr hooks." + ) + + # docstring comes from _add_method_dunders + def __setattr__(self, name, val): + try: + a, hook = sa_attrs[name] + except KeyError: + nval = val + else: + nval = hook(self, a, val) + + _obj_setattr(self, name, nval) + + self._cls_dict["__attrs_own_setattr__"] = True + self._cls_dict["__setattr__"] = self._add_method_dunders(__setattr__) + self._wrote_own_setattr = True + + return self + + def _add_method_dunders(self, method): + """ + Add __module__ and __qualname__ to a *method* if possible. + """ + try: + method.__module__ = self._cls.__module__ + except AttributeError: + pass + + try: + method.__qualname__ = ".".join( + (self._cls.__qualname__, method.__name__) + ) + except AttributeError: + pass + + try: + method.__doc__ = "Method generated by attrs for class %s." % ( + self._cls.__qualname__, + ) + except AttributeError: + pass + + return method + + +_CMP_DEPRECATION = ( + "The usage of `cmp` is deprecated and will be removed on or after " + "2021-06-01. Please use `eq` and `order` instead." +) + + +def _determine_attrs_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + raise ValueError("Don't mix `cmp` with `eq' and `order`.") + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + return cmp, cmp + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq = default_eq + + if order is None: + order = eq + + if eq is False and order is True: + raise ValueError("`order` can only be True if `eq` is True too.") + + return eq, order + + +def _determine_attrib_eq_order(cmp, eq, order, default_eq): + """ + Validate the combination of *cmp*, *eq*, and *order*. Derive the effective + values of eq and order. If *eq* is None, set it to *default_eq*. + """ + if cmp is not None and any((eq is not None, order is not None)): + raise ValueError("Don't mix `cmp` with `eq' and `order`.") + + def decide_callable_or_boolean(value): + """ + Decide whether a key function is used. + """ + if callable(value): + value, key = True, value + else: + key = None + return value, key + + # cmp takes precedence due to bw-compatibility. + if cmp is not None: + cmp, cmp_key = decide_callable_or_boolean(cmp) + return cmp, cmp_key, cmp, cmp_key + + # If left None, equality is set to the specified default and ordering + # mirrors equality. + if eq is None: + eq, eq_key = default_eq, None + else: + eq, eq_key = decide_callable_or_boolean(eq) + + if order is None: + order, order_key = eq, eq_key + else: + order, order_key = decide_callable_or_boolean(order) + + if eq is False and order is True: + raise ValueError("`order` can only be True if `eq` is True too.") + + return eq, eq_key, order, order_key + + +def _determine_whether_to_implement( + cls, flag, auto_detect, dunders, default=True +): + """ + Check whether we should implement a set of methods for *cls*. + + *flag* is the argument passed into @attr.s like 'init', *auto_detect* the + same as passed into @attr.s and *dunders* is a tuple of attribute names + whose presence signal that the user has implemented it themselves. + + Return *default* if no reason for either for or against is found. + + auto_detect must be False on Python 2. + """ + if flag is True or flag is False: + return flag + + if flag is None and auto_detect is False: + return default + + # Logically, flag is None and auto_detect is True here. + for dunder in dunders: + if _has_own_attribute(cls, dunder): + return False + + return default + + +def attrs( + maybe_cls=None, + these=None, + repr_ns=None, + repr=None, + cmp=None, + hash=None, + init=None, + slots=False, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=False, + kw_only=False, + cache_hash=False, + auto_exc=False, + eq=None, + order=None, + auto_detect=False, + collect_by_mro=False, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, + match_args=True, +): + r""" + A class decorator that adds `dunder + `_\ -methods according to the + specified attributes using `attr.ib` or the *these* argument. + + :param these: A dictionary of name to `attr.ib` mappings. This is + useful to avoid the definition of your attributes within the class body + because you can't (e.g. if you want to add ``__repr__`` methods to + Django models) or don't want to. + + If *these* is not ``None``, ``attrs`` will *not* search the class body + for attributes and will *not* remove any attributes from it. + + If *these* is an ordered dict (`dict` on Python 3.6+, + `collections.OrderedDict` otherwise), the order is deduced from + the order of the attributes inside *these*. Otherwise the order + of the definition of the attributes is used. + + :type these: `dict` of `str` to `attr.ib` + + :param str repr_ns: When using nested classes, there's no way in Python 2 + to automatically detect that. Therefore it's possible to set the + namespace explicitly for a more meaningful ``repr`` output. + :param bool auto_detect: Instead of setting the *init*, *repr*, *eq*, + *order*, and *hash* arguments explicitly, assume they are set to + ``True`` **unless any** of the involved methods for one of the + arguments is implemented in the *current* class (i.e. it is *not* + inherited from some base class). + + So for example by implementing ``__eq__`` on a class yourself, + ``attrs`` will deduce ``eq=False`` and will create *neither* + ``__eq__`` *nor* ``__ne__`` (but Python classes come with a sensible + ``__ne__`` by default, so it *should* be enough to only implement + ``__eq__`` in most cases). + + .. warning:: + + If you prevent ``attrs`` from creating the ordering methods for you + (``order=False``, e.g. by implementing ``__le__``), it becomes + *your* responsibility to make sure its ordering is sound. The best + way is to use the `functools.total_ordering` decorator. + + + Passing ``True`` or ``False`` to *init*, *repr*, *eq*, *order*, + *cmp*, or *hash* overrides whatever *auto_detect* would determine. + + *auto_detect* requires Python 3. Setting it ``True`` on Python 2 raises + an `attrs.exceptions.PythonTooOldError`. + + :param bool repr: Create a ``__repr__`` method with a human readable + representation of ``attrs`` attributes.. + :param bool str: Create a ``__str__`` method that is identical to + ``__repr__``. This is usually not necessary except for + `Exception`\ s. + :param Optional[bool] eq: If ``True`` or ``None`` (default), add ``__eq__`` + and ``__ne__`` methods that check two instances for equality. + + They compare the instances as if they were tuples of their ``attrs`` + attributes if and only if the types of both classes are *identical*! + :param Optional[bool] order: If ``True``, add ``__lt__``, ``__le__``, + ``__gt__``, and ``__ge__`` methods that behave like *eq* above and + allow instances to be ordered. If ``None`` (default) mirror value of + *eq*. + :param Optional[bool] cmp: Setting *cmp* is equivalent to setting *eq* + and *order* to the same value. Must not be mixed with *eq* or *order*. + :param Optional[bool] hash: If ``None`` (default), the ``__hash__`` method + is generated according how *eq* and *frozen* are set. + + 1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you. + 2. If *eq* is True and *frozen* is False, ``__hash__`` will be set to + None, marking it unhashable (which it is). + 3. If *eq* is False, ``__hash__`` will be left untouched meaning the + ``__hash__`` method of the base class will be used (if base class is + ``object``, this means it will fall back to id-based hashing.). + + Although not recommended, you can decide for yourself and force + ``attrs`` to create one (e.g. if the class is immutable even though you + didn't freeze it programmatically) by passing ``True`` or not. Both of + these cases are rather special and should be used carefully. + + See our documentation on `hashing`, Python's documentation on + `object.__hash__`, and the `GitHub issue that led to the default \ + behavior `_ for more + details. + :param bool init: Create a ``__init__`` method that initializes the + ``attrs`` attributes. Leading underscores are stripped for the argument + name. If a ``__attrs_pre_init__`` method exists on the class, it will + be called before the class is initialized. If a ``__attrs_post_init__`` + method exists on the class, it will be called after the class is fully + initialized. + + If ``init`` is ``False``, an ``__attrs_init__`` method will be + injected instead. This allows you to define a custom ``__init__`` + method that can do pre-init work such as ``super().__init__()``, + and then call ``__attrs_init__()`` and ``__attrs_post_init__()``. + :param bool slots: Create a `slotted class ` that's more + memory-efficient. Slotted classes are generally superior to the default + dict classes, but have some gotchas you should know about, so we + encourage you to read the `glossary entry `. + :param bool frozen: Make instances immutable after initialization. If + someone attempts to modify a frozen instance, + `attr.exceptions.FrozenInstanceError` is raised. + + .. note:: + + 1. This is achieved by installing a custom ``__setattr__`` method + on your class, so you can't implement your own. + + 2. True immutability is impossible in Python. + + 3. This *does* have a minor a runtime performance `impact + ` when initializing new instances. In other words: + ``__init__`` is slightly slower with ``frozen=True``. + + 4. If a class is frozen, you cannot modify ``self`` in + ``__attrs_post_init__`` or a self-written ``__init__``. You can + circumvent that limitation by using + ``object.__setattr__(self, "attribute_name", value)``. + + 5. Subclasses of a frozen class are frozen too. + + :param bool weakref_slot: Make instances weak-referenceable. This has no + effect unless ``slots`` is also enabled. + :param bool auto_attribs: If ``True``, collect `PEP 526`_-annotated + attributes (Python 3.6 and later only) from the class body. + + In this case, you **must** annotate every field. If ``attrs`` + encounters a field that is set to an `attr.ib` but lacks a type + annotation, an `attr.exceptions.UnannotatedAttributeError` is + raised. Use ``field_name: typing.Any = attr.ib(...)`` if you don't + want to set a type. + + If you assign a value to those attributes (e.g. ``x: int = 42``), that + value becomes the default value like if it were passed using + ``attr.ib(default=42)``. Passing an instance of `attrs.Factory` also + works as expected in most cases (see warning below). + + Attributes annotated as `typing.ClassVar`, and attributes that are + neither annotated nor set to an `attr.ib` are **ignored**. + + .. warning:: + For features that use the attribute name to create decorators (e.g. + `validators `), you still *must* assign `attr.ib` to + them. Otherwise Python will either not find the name or try to use + the default value to call e.g. ``validator`` on it. + + These errors can be quite confusing and probably the most common bug + report on our bug tracker. + + .. _`PEP 526`: https://www.python.org/dev/peps/pep-0526/ + :param bool kw_only: Make all attributes keyword-only (Python 3+) + in the generated ``__init__`` (if ``init`` is ``False``, this + parameter is ignored). + :param bool cache_hash: Ensure that the object's hash code is computed + only once and stored on the object. If this is set to ``True``, + hashing must be either explicitly or implicitly enabled for this + class. If the hash code is cached, avoid any reassignments of + fields involved in hash code computation or mutations of the objects + those fields point to after object creation. If such changes occur, + the behavior of the object's hash code is undefined. + :param bool auto_exc: If the class subclasses `BaseException` + (which implicitly includes any subclass of any exception), the + following happens to behave like a well-behaved Python exceptions + class: + + - the values for *eq*, *order*, and *hash* are ignored and the + instances compare and hash by the instance's ids (N.B. ``attrs`` will + *not* remove existing implementations of ``__hash__`` or the equality + methods. It just won't add own ones.), + - all attributes that are either passed into ``__init__`` or have a + default value are additionally available as a tuple in the ``args`` + attribute, + - the value of *str* is ignored leaving ``__str__`` to base classes. + :param bool collect_by_mro: Setting this to `True` fixes the way ``attrs`` + collects attributes from base classes. The default behavior is + incorrect in certain cases of multiple inheritance. It should be on by + default but is kept off for backward-compatibility. + + See issue `#428 `_ for + more details. + + :param Optional[bool] getstate_setstate: + .. note:: + This is usually only interesting for slotted classes and you should + probably just set *auto_detect* to `True`. + + If `True`, ``__getstate__`` and + ``__setstate__`` are generated and attached to the class. This is + necessary for slotted classes to be pickleable. If left `None`, it's + `True` by default for slotted classes and ``False`` for dict classes. + + If *auto_detect* is `True`, and *getstate_setstate* is left `None`, + and **either** ``__getstate__`` or ``__setstate__`` is detected directly + on the class (i.e. not inherited), it is set to `False` (this is usually + what you want). + + :param on_setattr: A callable that is run whenever the user attempts to set + an attribute (either by assignment like ``i.x = 42`` or by using + `setattr` like ``setattr(i, "x", 42)``). It receives the same arguments + as validators: the instance, the attribute that is being modified, and + the new value. + + If no exception is raised, the attribute is set to the return value of + the callable. + + If a list of callables is passed, they're automatically wrapped in an + `attrs.setters.pipe`. + + :param Optional[callable] field_transformer: + A function that is called with the original class object and all + fields right before ``attrs`` finalizes the class. You can use + this, e.g., to automatically add converters or validators to + fields based on their types. See `transform-fields` for more details. + + :param bool match_args: + If `True` (default), set ``__match_args__`` on the class to support + `PEP 634 `_ (Structural + Pattern Matching). It is a tuple of all positional-only ``__init__`` + parameter names on Python 3.10 and later. Ignored on older Python + versions. + + .. versionadded:: 16.0.0 *slots* + .. versionadded:: 16.1.0 *frozen* + .. versionadded:: 16.3.0 *str* + .. versionadded:: 16.3.0 Support for ``__attrs_post_init__``. + .. versionchanged:: 17.1.0 + *hash* supports ``None`` as value which is also the default now. + .. versionadded:: 17.3.0 *auto_attribs* + .. versionchanged:: 18.1.0 + If *these* is passed, no attributes are deleted from the class body. + .. versionchanged:: 18.1.0 If *these* is ordered, the order is retained. + .. versionadded:: 18.2.0 *weakref_slot* + .. deprecated:: 18.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now raise a + `DeprecationWarning` if the classes compared are subclasses of + each other. ``__eq`` and ``__ne__`` never tried to compared subclasses + to each other. + .. versionchanged:: 19.2.0 + ``__lt__``, ``__le__``, ``__gt__``, and ``__ge__`` now do not consider + subclasses comparable anymore. + .. versionadded:: 18.2.0 *kw_only* + .. versionadded:: 18.2.0 *cache_hash* + .. versionadded:: 19.1.0 *auto_exc* + .. deprecated:: 19.2.0 *cmp* Removal on or after 2021-06-01. + .. versionadded:: 19.2.0 *eq* and *order* + .. versionadded:: 20.1.0 *auto_detect* + .. versionadded:: 20.1.0 *collect_by_mro* + .. versionadded:: 20.1.0 *getstate_setstate* + .. versionadded:: 20.1.0 *on_setattr* + .. versionadded:: 20.3.0 *field_transformer* + .. versionchanged:: 21.1.0 + ``init=False`` injects ``__attrs_init__`` + .. versionchanged:: 21.1.0 Support for ``__attrs_pre_init__`` + .. versionchanged:: 21.1.0 *cmp* undeprecated + .. versionadded:: 21.3.0 *match_args* + """ + if auto_detect and PY2: + raise PythonTooOldError( + "auto_detect only works on Python 3 and later." + ) + + eq_, order_ = _determine_attrs_eq_order(cmp, eq, order, None) + hash_ = hash # work around the lack of nonlocal + + if isinstance(on_setattr, (list, tuple)): + on_setattr = setters.pipe(*on_setattr) + + def wrap(cls): + + if getattr(cls, "__class__", None) is None: + raise TypeError("attrs only works with new-style classes.") + + is_frozen = frozen or _has_frozen_base_class(cls) + is_exc = auto_exc is True and issubclass(cls, BaseException) + has_own_setattr = auto_detect and _has_own_attribute( + cls, "__setattr__" + ) + + if has_own_setattr and is_frozen: + raise ValueError("Can't freeze a class with a custom __setattr__.") + + builder = _ClassBuilder( + cls, + these, + slots, + is_frozen, + weakref_slot, + _determine_whether_to_implement( + cls, + getstate_setstate, + auto_detect, + ("__getstate__", "__setstate__"), + default=slots, + ), + auto_attribs, + kw_only, + cache_hash, + is_exc, + collect_by_mro, + on_setattr, + has_own_setattr, + field_transformer, + ) + if _determine_whether_to_implement( + cls, repr, auto_detect, ("__repr__",) + ): + builder.add_repr(repr_ns) + if str is True: + builder.add_str() + + eq = _determine_whether_to_implement( + cls, eq_, auto_detect, ("__eq__", "__ne__") + ) + if not is_exc and eq is True: + builder.add_eq() + if not is_exc and _determine_whether_to_implement( + cls, order_, auto_detect, ("__lt__", "__le__", "__gt__", "__ge__") + ): + builder.add_order() + + builder.add_setattr() + + if ( + hash_ is None + and auto_detect is True + and _has_own_attribute(cls, "__hash__") + ): + hash = False + else: + hash = hash_ + if hash is not True and hash is not False and hash is not None: + # Can't use `hash in` because 1 == True for example. + raise TypeError( + "Invalid value for hash. Must be True, False, or None." + ) + elif hash is False or (hash is None and eq is False) or is_exc: + # Don't do anything. Should fall back to __object__'s __hash__ + # which is by id. + if cache_hash: + raise TypeError( + "Invalid value for cache_hash. To use hash caching," + " hashing must be either explicitly or implicitly " + "enabled." + ) + elif hash is True or ( + hash is None and eq is True and is_frozen is True + ): + # Build a __hash__ if told so, or if it's safe. + builder.add_hash() + else: + # Raise TypeError on attempts to hash. + if cache_hash: + raise TypeError( + "Invalid value for cache_hash. To use hash caching," + " hashing must be either explicitly or implicitly " + "enabled." + ) + builder.make_unhashable() + + if _determine_whether_to_implement( + cls, init, auto_detect, ("__init__",) + ): + builder.add_init() + else: + builder.add_attrs_init() + if cache_hash: + raise TypeError( + "Invalid value for cache_hash. To use hash caching," + " init must be True." + ) + + if ( + PY310 + and match_args + and not _has_own_attribute(cls, "__match_args__") + ): + builder.add_match_args() + + return builder.build_class() + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but ``None`` if used as `@attrs()`. + if maybe_cls is None: + return wrap + else: + return wrap(maybe_cls) + + +_attrs = attrs +""" +Internal alias so we can use it in functions that take an argument called +*attrs*. +""" + + +if PY2: + + def _has_frozen_base_class(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return ( + getattr(cls.__setattr__, "__module__", None) + == _frozen_setattrs.__module__ + and cls.__setattr__.__name__ == _frozen_setattrs.__name__ + ) + +else: + + def _has_frozen_base_class(cls): + """ + Check whether *cls* has a frozen ancestor by looking at its + __setattr__. + """ + return cls.__setattr__ == _frozen_setattrs + + +def _generate_unique_filename(cls, func_name): + """ + Create a "filename" suitable for a function being generated. + """ + unique_filename = "".format( + func_name, + cls.__module__, + getattr(cls, "__qualname__", cls.__name__), + ) + return unique_filename + + +def _make_hash(cls, attrs, frozen, cache_hash): + attrs = tuple( + a for a in attrs if a.hash is True or (a.hash is None and a.eq is True) + ) + + tab = " " + + unique_filename = _generate_unique_filename(cls, "hash") + type_hash = hash(unique_filename) + + hash_def = "def __hash__(self" + hash_func = "hash((" + closing_braces = "))" + if not cache_hash: + hash_def += "):" + else: + if not PY2: + hash_def += ", *" + + hash_def += ( + ", _cache_wrapper=" + + "__import__('attr._make')._make._CacheHashWrapper):" + ) + hash_func = "_cache_wrapper(" + hash_func + closing_braces += ")" + + method_lines = [hash_def] + + def append_hash_computation_lines(prefix, indent): + """ + Generate the code for actually computing the hash code. + Below this will either be returned directly or used to compute + a value which is then cached, depending on the value of cache_hash + """ + + method_lines.extend( + [ + indent + prefix + hash_func, + indent + " %d," % (type_hash,), + ] + ) + + for a in attrs: + method_lines.append(indent + " self.%s," % a.name) + + method_lines.append(indent + " " + closing_braces) + + if cache_hash: + method_lines.append(tab + "if self.%s is None:" % _hash_cache_field) + if frozen: + append_hash_computation_lines( + "object.__setattr__(self, '%s', " % _hash_cache_field, tab * 2 + ) + method_lines.append(tab * 2 + ")") # close __setattr__ + else: + append_hash_computation_lines( + "self.%s = " % _hash_cache_field, tab * 2 + ) + method_lines.append(tab + "return self.%s" % _hash_cache_field) + else: + append_hash_computation_lines("return ", tab) + + script = "\n".join(method_lines) + return _make_method("__hash__", script, unique_filename) + + +def _add_hash(cls, attrs): + """ + Add a hash method to *cls*. + """ + cls.__hash__ = _make_hash(cls, attrs, frozen=False, cache_hash=False) + return cls + + +def _make_ne(): + """ + Create __ne__ method. + """ + + def __ne__(self, other): + """ + Check equality and either forward a NotImplemented or + return the result negated. + """ + result = self.__eq__(other) + if result is NotImplemented: + return NotImplemented + + return not result + + return __ne__ + + +def _make_eq(cls, attrs): + """ + Create __eq__ method for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.eq] + + unique_filename = _generate_unique_filename(cls, "eq") + lines = [ + "def __eq__(self, other):", + " if other.__class__ is not self.__class__:", + " return NotImplemented", + ] + + # We can't just do a big self.x = other.x and... clause due to + # irregularities like nan == nan is false but (nan,) == (nan,) is true. + globs = {} + if attrs: + lines.append(" return (") + others = [" ) == ("] + for a in attrs: + if a.eq_key: + cmp_name = "_%s_key" % (a.name,) + # Add the key function to the global namespace + # of the evaluated function. + globs[cmp_name] = a.eq_key + lines.append( + " %s(self.%s)," + % ( + cmp_name, + a.name, + ) + ) + others.append( + " %s(other.%s)," + % ( + cmp_name, + a.name, + ) + ) + else: + lines.append(" self.%s," % (a.name,)) + others.append(" other.%s," % (a.name,)) + + lines += others + [" )"] + else: + lines.append(" return True") + + script = "\n".join(lines) + + return _make_method("__eq__", script, unique_filename, globs) + + +def _make_order(cls, attrs): + """ + Create ordering methods for *cls* with *attrs*. + """ + attrs = [a for a in attrs if a.order] + + def attrs_to_tuple(obj): + """ + Save us some typing. + """ + return tuple( + key(value) if key else value + for value, key in ( + (getattr(obj, a.name), a.order_key) for a in attrs + ) + ) + + def __lt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) < attrs_to_tuple(other) + + return NotImplemented + + def __le__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) <= attrs_to_tuple(other) + + return NotImplemented + + def __gt__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) > attrs_to_tuple(other) + + return NotImplemented + + def __ge__(self, other): + """ + Automatically created by attrs. + """ + if other.__class__ is self.__class__: + return attrs_to_tuple(self) >= attrs_to_tuple(other) + + return NotImplemented + + return __lt__, __le__, __gt__, __ge__ + + +def _add_eq(cls, attrs=None): + """ + Add equality methods to *cls* with *attrs*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + cls.__eq__ = _make_eq(cls, attrs) + cls.__ne__ = _make_ne() + + return cls + + +if HAS_F_STRINGS: + + def _make_repr(attrs, ns, cls): + unique_filename = _generate_unique_filename(cls, "repr") + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom + # callable. + attr_names_with_reprs = tuple( + (a.name, (repr if a.repr is True else a.repr), a.init) + for a in attrs + if a.repr is not False + ) + globs = { + name + "_repr": r + for name, r, _ in attr_names_with_reprs + if r != repr + } + globs["_compat"] = _compat + globs["AttributeError"] = AttributeError + globs["NOTHING"] = NOTHING + attribute_fragments = [] + for name, r, i in attr_names_with_reprs: + accessor = ( + "self." + name + if i + else 'getattr(self, "' + name + '", NOTHING)' + ) + fragment = ( + "%s={%s!r}" % (name, accessor) + if r == repr + else "%s={%s_repr(%s)}" % (name, name, accessor) + ) + attribute_fragments.append(fragment) + repr_fragment = ", ".join(attribute_fragments) + + if ns is None: + cls_name_fragment = ( + '{self.__class__.__qualname__.rsplit(">.", 1)[-1]}' + ) + else: + cls_name_fragment = ns + ".{self.__class__.__name__}" + + lines = [ + "def __repr__(self):", + " try:", + " already_repring = _compat.repr_context.already_repring", + " except AttributeError:", + " already_repring = {id(self),}", + " _compat.repr_context.already_repring = already_repring", + " else:", + " if id(self) in already_repring:", + " return '...'", + " else:", + " already_repring.add(id(self))", + " try:", + " return f'%s(%s)'" % (cls_name_fragment, repr_fragment), + " finally:", + " already_repring.remove(id(self))", + ] + + return _make_method( + "__repr__", "\n".join(lines), unique_filename, globs=globs + ) + +else: + + def _make_repr(attrs, ns, _): + """ + Make a repr method that includes relevant *attrs*, adding *ns* to the + full name. + """ + + # Figure out which attributes to include, and which function to use to + # format them. The a.repr value can be either bool or a custom + # callable. + attr_names_with_reprs = tuple( + (a.name, repr if a.repr is True else a.repr) + for a in attrs + if a.repr is not False + ) + + def __repr__(self): + """ + Automatically created by attrs. + """ + try: + already_repring = _compat.repr_context.already_repring + except AttributeError: + already_repring = set() + _compat.repr_context.already_repring = already_repring + + if id(self) in already_repring: + return "..." + real_cls = self.__class__ + if ns is None: + qualname = getattr(real_cls, "__qualname__", None) + if qualname is not None: # pragma: no cover + # This case only happens on Python 3.5 and 3.6. We exclude + # it from coverage, because we don't want to slow down our + # test suite by running them under coverage too for this + # one line. + class_name = qualname.rsplit(">.", 1)[-1] + else: + class_name = real_cls.__name__ + else: + class_name = ns + "." + real_cls.__name__ + + # Since 'self' remains on the stack (i.e.: strongly referenced) + # for the duration of this call, it's safe to depend on id(...) + # stability, and not need to track the instance and therefore + # worry about properties like weakref- or hash-ability. + already_repring.add(id(self)) + try: + result = [class_name, "("] + first = True + for name, attr_repr in attr_names_with_reprs: + if first: + first = False + else: + result.append(", ") + result.extend( + (name, "=", attr_repr(getattr(self, name, NOTHING))) + ) + return "".join(result) + ")" + finally: + already_repring.remove(id(self)) + + return __repr__ + + +def _add_repr(cls, ns=None, attrs=None): + """ + Add a repr method to *cls*. + """ + if attrs is None: + attrs = cls.__attrs_attrs__ + + cls.__repr__ = _make_repr(attrs, ns, cls) + return cls + + +def fields(cls): + """ + Return the tuple of ``attrs`` attributes for a class. + + The tuple also allows accessing the fields by their names (see below for + examples). + + :param type cls: Class to introspect. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + :rtype: tuple (with name accessors) of `attrs.Attribute` + + .. versionchanged:: 16.2.0 Returned tuple allows accessing the fields + by name. + """ + if not isclass(cls): + raise TypeError("Passed object must be a class.") + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + raise NotAnAttrsClassError( + "{cls!r} is not an attrs-decorated class.".format(cls=cls) + ) + return attrs + + +def fields_dict(cls): + """ + Return an ordered dictionary of ``attrs`` attributes for a class, whose + keys are the attribute names. + + :param type cls: Class to introspect. + + :raise TypeError: If *cls* is not a class. + :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs`` + class. + + :rtype: an ordered dict where keys are attribute names and values are + `attrs.Attribute`\\ s. This will be a `dict` if it's + naturally ordered like on Python 3.6+ or an + :class:`~collections.OrderedDict` otherwise. + + .. versionadded:: 18.1.0 + """ + if not isclass(cls): + raise TypeError("Passed object must be a class.") + attrs = getattr(cls, "__attrs_attrs__", None) + if attrs is None: + raise NotAnAttrsClassError( + "{cls!r} is not an attrs-decorated class.".format(cls=cls) + ) + return ordered_dict(((a.name, a) for a in attrs)) + + +def validate(inst): + """ + Validate all attributes on *inst* that have a validator. + + Leaves all exceptions through. + + :param inst: Instance of a class with ``attrs`` attributes. + """ + if _config._run_validators is False: + return + + for a in fields(inst.__class__): + v = a.validator + if v is not None: + v(inst, a, getattr(inst, a.name)) + + +def _is_slot_cls(cls): + return "__slots__" in cls.__dict__ + + +def _is_slot_attr(a_name, base_attr_map): + """ + Check if the attribute name comes from a slot class. + """ + return a_name in base_attr_map and _is_slot_cls(base_attr_map[a_name]) + + +def _make_init( + cls, + attrs, + pre_init, + post_init, + frozen, + slots, + cache_hash, + base_attr_map, + is_exc, + cls_on_setattr, + attrs_init, +): + has_cls_on_setattr = ( + cls_on_setattr is not None and cls_on_setattr is not setters.NO_OP + ) + + if frozen and has_cls_on_setattr: + raise ValueError("Frozen classes can't use on_setattr.") + + needs_cached_setattr = cache_hash or frozen + filtered_attrs = [] + attr_dict = {} + for a in attrs: + if not a.init and a.default is NOTHING: + continue + + filtered_attrs.append(a) + attr_dict[a.name] = a + + if a.on_setattr is not None: + if frozen is True: + raise ValueError("Frozen classes can't use on_setattr.") + + needs_cached_setattr = True + elif has_cls_on_setattr and a.on_setattr is not setters.NO_OP: + needs_cached_setattr = True + + unique_filename = _generate_unique_filename(cls, "init") + + script, globs, annotations = _attrs_to_init_script( + filtered_attrs, + frozen, + slots, + pre_init, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_cls_on_setattr, + attrs_init, + ) + if cls.__module__ in sys.modules: + # This makes typing.get_type_hints(CLS.__init__) resolve string types. + globs.update(sys.modules[cls.__module__].__dict__) + + globs.update({"NOTHING": NOTHING, "attr_dict": attr_dict}) + + if needs_cached_setattr: + # Save the lookup overhead in __init__ if we need to circumvent + # setattr hooks. + globs["_cached_setattr"] = _obj_setattr + + init = _make_method( + "__attrs_init__" if attrs_init else "__init__", + script, + unique_filename, + globs, + ) + init.__annotations__ = annotations + + return init + + +def _setattr(attr_name, value_var, has_on_setattr): + """ + Use the cached object.setattr to set *attr_name* to *value_var*. + """ + return "_setattr('%s', %s)" % (attr_name, value_var) + + +def _setattr_with_converter(attr_name, value_var, has_on_setattr): + """ + Use the cached object.setattr to set *attr_name* to *value_var*, but run + its converter first. + """ + return "_setattr('%s', %s(%s))" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + +def _assign(attr_name, value, has_on_setattr): + """ + Unless *attr_name* has an on_setattr hook, use normal assignment. Otherwise + relegate to _setattr. + """ + if has_on_setattr: + return _setattr(attr_name, value, True) + + return "self.%s = %s" % (attr_name, value) + + +def _assign_with_converter(attr_name, value_var, has_on_setattr): + """ + Unless *attr_name* has an on_setattr hook, use normal assignment after + conversion. Otherwise relegate to _setattr_with_converter. + """ + if has_on_setattr: + return _setattr_with_converter(attr_name, value_var, True) + + return "self.%s = %s(%s)" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + +if PY2: + + def _unpack_kw_only_py2(attr_name, default=None): + """ + Unpack *attr_name* from _kw_only dict. + """ + if default is not None: + arg_default = ", %s" % default + else: + arg_default = "" + return "%s = _kw_only.pop('%s'%s)" % ( + attr_name, + attr_name, + arg_default, + ) + + def _unpack_kw_only_lines_py2(kw_only_args): + """ + Unpack all *kw_only_args* from _kw_only dict and handle errors. + + Given a list of strings "{attr_name}" and "{attr_name}={default}" + generates list of lines of code that pop attrs from _kw_only dict and + raise TypeError similar to builtin if required attr is missing or + extra key is passed. + + >>> print("\n".join(_unpack_kw_only_lines_py2(["a", "b=42"]))) + try: + a = _kw_only.pop('a') + b = _kw_only.pop('b', 42) + except KeyError as _key_error: + raise TypeError( + ... + if _kw_only: + raise TypeError( + ... + """ + lines = ["try:"] + lines.extend( + " " + _unpack_kw_only_py2(*arg.split("=")) + for arg in kw_only_args + ) + lines += """\ +except KeyError as _key_error: + raise TypeError( + '__init__() missing required keyword-only argument: %s' % _key_error + ) +if _kw_only: + raise TypeError( + '__init__() got an unexpected keyword argument %r' + % next(iter(_kw_only)) + ) +""".split( + "\n" + ) + return lines + + +def _attrs_to_init_script( + attrs, + frozen, + slots, + pre_init, + post_init, + cache_hash, + base_attr_map, + is_exc, + needs_cached_setattr, + has_cls_on_setattr, + attrs_init, +): + """ + Return a script of an initializer for *attrs* and a dict of globals. + + The globals are expected by the generated script. + + If *frozen* is True, we cannot set the attributes directly so we use + a cached ``object.__setattr__``. + """ + lines = [] + if pre_init: + lines.append("self.__attrs_pre_init__()") + + if needs_cached_setattr: + lines.append( + # Circumvent the __setattr__ descriptor to save one lookup per + # assignment. + # Note _setattr will be used again below if cache_hash is True + "_setattr = _cached_setattr.__get__(self, self.__class__)" + ) + + if frozen is True: + if slots is True: + fmt_setter = _setattr + fmt_setter_with_converter = _setattr_with_converter + else: + # Dict frozen classes assign directly to __dict__. + # But only if the attribute doesn't come from an ancestor slot + # class. + # Note _inst_dict will be used again below if cache_hash is True + lines.append("_inst_dict = self.__dict__") + + def fmt_setter(attr_name, value_var, has_on_setattr): + if _is_slot_attr(attr_name, base_attr_map): + return _setattr(attr_name, value_var, has_on_setattr) + + return "_inst_dict['%s'] = %s" % (attr_name, value_var) + + def fmt_setter_with_converter( + attr_name, value_var, has_on_setattr + ): + if has_on_setattr or _is_slot_attr(attr_name, base_attr_map): + return _setattr_with_converter( + attr_name, value_var, has_on_setattr + ) + + return "_inst_dict['%s'] = %s(%s)" % ( + attr_name, + _init_converter_pat % (attr_name,), + value_var, + ) + + else: + # Not frozen. + fmt_setter = _assign + fmt_setter_with_converter = _assign_with_converter + + args = [] + kw_only_args = [] + attrs_to_validate = [] + + # This is a dictionary of names to validator and converter callables. + # Injecting this into __init__ globals lets us avoid lookups. + names_for_globals = {} + annotations = {"return": None} + + for a in attrs: + if a.validator: + attrs_to_validate.append(a) + + attr_name = a.name + has_on_setattr = a.on_setattr is not None or ( + a.on_setattr is not setters.NO_OP and has_cls_on_setattr + ) + arg_name = a.name.lstrip("_") + + has_factory = isinstance(a.default, Factory) + if has_factory and a.default.takes_self: + maybe_self = "self" + else: + maybe_self = "" + + if a.init is False: + if has_factory: + init_factory_name = _init_factory_pat.format(a.name) + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + init_factory_name + "(%s)" % (maybe_self,), + has_on_setattr, + ) + ) + conv_name = _init_converter_pat % (a.name,) + names_for_globals[conv_name] = a.converter + else: + lines.append( + fmt_setter( + attr_name, + init_factory_name + "(%s)" % (maybe_self,), + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, + "attr_dict['%s'].default" % (attr_name,), + has_on_setattr, + ) + ) + conv_name = _init_converter_pat % (a.name,) + names_for_globals[conv_name] = a.converter + else: + lines.append( + fmt_setter( + attr_name, + "attr_dict['%s'].default" % (attr_name,), + has_on_setattr, + ) + ) + elif a.default is not NOTHING and not has_factory: + arg = "%s=attr_dict['%s'].default" % (arg_name, attr_name) + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) + names_for_globals[ + _init_converter_pat % (a.name,) + ] = a.converter + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + elif has_factory: + arg = "%s=NOTHING" % (arg_name,) + if a.kw_only: + kw_only_args.append(arg) + else: + args.append(arg) + lines.append("if %s is not NOTHING:" % (arg_name,)) + + init_factory_name = _init_factory_pat.format(a.name) + if a.converter is not None: + lines.append( + " " + + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter_with_converter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + ) + ) + names_for_globals[ + _init_converter_pat % (a.name,) + ] = a.converter + else: + lines.append( + " " + fmt_setter(attr_name, arg_name, has_on_setattr) + ) + lines.append("else:") + lines.append( + " " + + fmt_setter( + attr_name, + init_factory_name + "(" + maybe_self + ")", + has_on_setattr, + ) + ) + names_for_globals[init_factory_name] = a.default.factory + else: + if a.kw_only: + kw_only_args.append(arg_name) + else: + args.append(arg_name) + + if a.converter is not None: + lines.append( + fmt_setter_with_converter( + attr_name, arg_name, has_on_setattr + ) + ) + names_for_globals[ + _init_converter_pat % (a.name,) + ] = a.converter + else: + lines.append(fmt_setter(attr_name, arg_name, has_on_setattr)) + + if a.init is True: + if a.type is not None and a.converter is None: + annotations[arg_name] = a.type + elif a.converter is not None and not PY2: + # Try to get the type from the converter. + sig = None + try: + sig = inspect.signature(a.converter) + except (ValueError, TypeError): # inspect failed + pass + if sig: + sig_params = list(sig.parameters.values()) + if ( + sig_params + and sig_params[0].annotation + is not inspect.Parameter.empty + ): + annotations[arg_name] = sig_params[0].annotation + + if attrs_to_validate: # we can skip this if there are no validators. + names_for_globals["_config"] = _config + lines.append("if _config._run_validators is True:") + for a in attrs_to_validate: + val_name = "__attr_validator_" + a.name + attr_name = "__attr_" + a.name + lines.append( + " %s(self, %s, self.%s)" % (val_name, attr_name, a.name) + ) + names_for_globals[val_name] = a.validator + names_for_globals[attr_name] = a + + if post_init: + lines.append("self.__attrs_post_init__()") + + # because this is set only after __attrs_post_init is called, a crash + # will result if post-init tries to access the hash code. This seemed + # preferable to setting this beforehand, in which case alteration to + # field values during post-init combined with post-init accessing the + # hash code would result in silent bugs. + if cache_hash: + if frozen: + if slots: + # if frozen and slots, then _setattr defined above + init_hash_cache = "_setattr('%s', %s)" + else: + # if frozen and not slots, then _inst_dict defined above + init_hash_cache = "_inst_dict['%s'] = %s" + else: + init_hash_cache = "self.%s = %s" + lines.append(init_hash_cache % (_hash_cache_field, "None")) + + # For exceptions we rely on BaseException.__init__ for proper + # initialization. + if is_exc: + vals = ",".join("self." + a.name for a in attrs if a.init) + + lines.append("BaseException.__init__(self, %s)" % (vals,)) + + args = ", ".join(args) + if kw_only_args: + if PY2: + lines = _unpack_kw_only_lines_py2(kw_only_args) + lines + + args += "%s**_kw_only" % (", " if args else "",) # leading comma + else: + args += "%s*, %s" % ( + ", " if args else "", # leading comma + ", ".join(kw_only_args), # kw_only args + ) + return ( + """\ +def {init_name}(self, {args}): + {lines} +""".format( + init_name=("__attrs_init__" if attrs_init else "__init__"), + args=args, + lines="\n ".join(lines) if lines else "pass", + ), + names_for_globals, + annotations, + ) + + +class Attribute(object): + """ + *Read-only* representation of an attribute. + + The class has *all* arguments of `attr.ib` (except for ``factory`` + which is only syntactic sugar for ``default=Factory(...)`` plus the + following: + + - ``name`` (`str`): The name of the attribute. + - ``inherited`` (`bool`): Whether or not that attribute has been inherited + from a base class. + - ``eq_key`` and ``order_key`` (`typing.Callable` or `None`): The callables + that are used for comparing and ordering objects by this attribute, + respectively. These are set by passing a callable to `attr.ib`'s ``eq``, + ``order``, or ``cmp`` arguments. See also :ref:`comparison customization + `. + + Instances of this class are frequently used for introspection purposes + like: + + - `fields` returns a tuple of them. + - Validators get them passed as the first argument. + - The :ref:`field transformer ` hook receives a list of + them. + + .. versionadded:: 20.1.0 *inherited* + .. versionadded:: 20.1.0 *on_setattr* + .. versionchanged:: 20.2.0 *inherited* is not taken into account for + equality checks and hashing anymore. + .. versionadded:: 21.1.0 *eq_key* and *order_key* + + For the full version history of the fields, see `attr.ib`. + """ + + __slots__ = ( + "name", + "default", + "validator", + "repr", + "eq", + "eq_key", + "order", + "order_key", + "hash", + "init", + "metadata", + "type", + "converter", + "kw_only", + "inherited", + "on_setattr", + ) + + def __init__( + self, + name, + default, + validator, + repr, + cmp, # XXX: unused, remove along with other cmp code. + hash, + init, + inherited, + metadata=None, + type=None, + converter=None, + kw_only=False, + eq=None, + eq_key=None, + order=None, + order_key=None, + on_setattr=None, + ): + eq, eq_key, order, order_key = _determine_attrib_eq_order( + cmp, eq_key or eq, order_key or order, True + ) + + # Cache this descriptor here to speed things up later. + bound_setattr = _obj_setattr.__get__(self, Attribute) + + # Despite the big red warning, people *do* instantiate `Attribute` + # themselves. + bound_setattr("name", name) + bound_setattr("default", default) + bound_setattr("validator", validator) + bound_setattr("repr", repr) + bound_setattr("eq", eq) + bound_setattr("eq_key", eq_key) + bound_setattr("order", order) + bound_setattr("order_key", order_key) + bound_setattr("hash", hash) + bound_setattr("init", init) + bound_setattr("converter", converter) + bound_setattr( + "metadata", + ( + metadata_proxy(metadata) + if metadata + else _empty_metadata_singleton + ), + ) + bound_setattr("type", type) + bound_setattr("kw_only", kw_only) + bound_setattr("inherited", inherited) + bound_setattr("on_setattr", on_setattr) + + def __setattr__(self, name, value): + raise FrozenInstanceError() + + @classmethod + def from_counting_attr(cls, name, ca, type=None): + # type holds the annotated value. deal with conflicts: + if type is None: + type = ca.type + elif ca.type is not None: + raise ValueError( + "Type annotation and type argument cannot both be present" + ) + inst_dict = { + k: getattr(ca, k) + for k in Attribute.__slots__ + if k + not in ( + "name", + "validator", + "default", + "type", + "inherited", + ) # exclude methods and deprecated alias + } + return cls( + name=name, + validator=ca._validator, + default=ca._default, + type=type, + cmp=None, + inherited=False, + **inst_dict + ) + + @property + def cmp(self): + """ + Simulate the presence of a cmp attribute and warn. + """ + warnings.warn(_CMP_DEPRECATION, DeprecationWarning, stacklevel=2) + + return self.eq and self.order + + # Don't use attr.evolve since fields(Attribute) doesn't work + def evolve(self, **changes): + """ + Copy *self* and apply *changes*. + + This works similarly to `attr.evolve` but that function does not work + with ``Attribute``. + + It is mainly meant to be used for `transform-fields`. + + .. versionadded:: 20.3.0 + """ + new = copy.copy(self) + + new._setattrs(changes.items()) + + return new + + # Don't use _add_pickle since fields(Attribute) doesn't work + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple( + getattr(self, name) if name != "metadata" else dict(self.metadata) + for name in self.__slots__ + ) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + self._setattrs(zip(self.__slots__, state)) + + def _setattrs(self, name_values_pairs): + bound_setattr = _obj_setattr.__get__(self, Attribute) + for name, value in name_values_pairs: + if name != "metadata": + bound_setattr(name, value) + else: + bound_setattr( + name, + metadata_proxy(value) + if value + else _empty_metadata_singleton, + ) + + +_a = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=(name != "metadata"), + init=True, + inherited=False, + ) + for name in Attribute.__slots__ +] + +Attribute = _add_hash( + _add_eq( + _add_repr(Attribute, attrs=_a), + attrs=[a for a in _a if a.name != "inherited"], + ), + attrs=[a for a in _a if a.hash and a.name != "inherited"], +) + + +class _CountingAttr(object): + """ + Intermediate representation of attributes that uses a counter to preserve + the order in which the attributes have been defined. + + *Internal* data structure of the attrs library. Running into is most + likely the result of a bug like a forgotten `@attr.s` decorator. + """ + + __slots__ = ( + "counter", + "_default", + "repr", + "eq", + "eq_key", + "order", + "order_key", + "hash", + "init", + "metadata", + "_validator", + "converter", + "type", + "kw_only", + "on_setattr", + ) + __attrs_attrs__ = tuple( + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + hash=True, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ) + for name in ( + "counter", + "_default", + "repr", + "eq", + "order", + "hash", + "init", + "on_setattr", + ) + ) + ( + Attribute( + name="metadata", + default=None, + validator=None, + repr=True, + cmp=None, + hash=False, + init=True, + kw_only=False, + eq=True, + eq_key=None, + order=False, + order_key=None, + inherited=False, + on_setattr=None, + ), + ) + cls_counter = 0 + + def __init__( + self, + default, + validator, + repr, + cmp, + hash, + init, + converter, + metadata, + type, + kw_only, + eq, + eq_key, + order, + order_key, + on_setattr, + ): + _CountingAttr.cls_counter += 1 + self.counter = _CountingAttr.cls_counter + self._default = default + self._validator = validator + self.converter = converter + self.repr = repr + self.eq = eq + self.eq_key = eq_key + self.order = order + self.order_key = order_key + self.hash = hash + self.init = init + self.metadata = metadata + self.type = type + self.kw_only = kw_only + self.on_setattr = on_setattr + + def validator(self, meth): + """ + Decorator that adds *meth* to the list of validators. + + Returns *meth* unchanged. + + .. versionadded:: 17.1.0 + """ + if self._validator is None: + self._validator = meth + else: + self._validator = and_(self._validator, meth) + return meth + + def default(self, meth): + """ + Decorator that allows to set the default for an attribute. + + Returns *meth* unchanged. + + :raises DefaultAlreadySetError: If default has been set before. + + .. versionadded:: 17.1.0 + """ + if self._default is not NOTHING: + raise DefaultAlreadySetError() + + self._default = Factory(meth, takes_self=True) + + return meth + + +_CountingAttr = _add_eq(_add_repr(_CountingAttr)) + + +class Factory(object): + """ + Stores a factory callable. + + If passed as the default value to `attrs.field`, the factory is used to + generate a new value. + + :param callable factory: A callable that takes either none or exactly one + mandatory positional argument depending on *takes_self*. + :param bool takes_self: Pass the partially initialized instance that is + being initialized as a positional argument. + + .. versionadded:: 17.1.0 *takes_self* + """ + + __slots__ = ("factory", "takes_self") + + def __init__(self, factory, takes_self=False): + """ + `Factory` is part of the default machinery so if we want a default + value here, we have to implement it ourselves. + """ + self.factory = factory + self.takes_self = takes_self + + def __getstate__(self): + """ + Play nice with pickle. + """ + return tuple(getattr(self, name) for name in self.__slots__) + + def __setstate__(self, state): + """ + Play nice with pickle. + """ + for name, value in zip(self.__slots__, state): + setattr(self, name, value) + + +_f = [ + Attribute( + name=name, + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=False, + hash=True, + init=True, + inherited=False, + ) + for name in Factory.__slots__ +] + +Factory = _add_hash(_add_eq(_add_repr(Factory, attrs=_f), attrs=_f), attrs=_f) + + +def make_class(name, attrs, bases=(object,), **attributes_arguments): + """ + A quick way to create a new class called *name* with *attrs*. + + :param str name: The name for the new class. + + :param attrs: A list of names or a dictionary of mappings of names to + attributes. + + If *attrs* is a list or an ordered dict (`dict` on Python 3.6+, + `collections.OrderedDict` otherwise), the order is deduced from + the order of the names or attributes inside *attrs*. Otherwise the + order of the definition of the attributes is used. + :type attrs: `list` or `dict` + + :param tuple bases: Classes that the new class will subclass. + + :param attributes_arguments: Passed unmodified to `attr.s`. + + :return: A new class with *attrs*. + :rtype: type + + .. versionadded:: 17.1.0 *bases* + .. versionchanged:: 18.1.0 If *attrs* is ordered, the order is retained. + """ + if isinstance(attrs, dict): + cls_dict = attrs + elif isinstance(attrs, (list, tuple)): + cls_dict = dict((a, attrib()) for a in attrs) + else: + raise TypeError("attrs argument must be a dict or a list.") + + pre_init = cls_dict.pop("__attrs_pre_init__", None) + post_init = cls_dict.pop("__attrs_post_init__", None) + user_init = cls_dict.pop("__init__", None) + + body = {} + if pre_init is not None: + body["__attrs_pre_init__"] = pre_init + if post_init is not None: + body["__attrs_post_init__"] = post_init + if user_init is not None: + body["__init__"] = user_init + + type_ = new_class(name, bases, {}, lambda ns: ns.update(body)) + + # For pickling to work, the __module__ variable needs to be set to the + # frame where the class is created. Bypass this step in environments where + # sys._getframe is not defined (Jython for example) or sys._getframe is not + # defined for arguments greater than 0 (IronPython). + try: + type_.__module__ = sys._getframe(1).f_globals.get( + "__name__", "__main__" + ) + except (AttributeError, ValueError): + pass + + # We do it here for proper warnings with meaningful stacklevel. + cmp = attributes_arguments.pop("cmp", None) + ( + attributes_arguments["eq"], + attributes_arguments["order"], + ) = _determine_attrs_eq_order( + cmp, + attributes_arguments.get("eq"), + attributes_arguments.get("order"), + True, + ) + + return _attrs(these=cls_dict, **attributes_arguments)(type_) + + +# These are required by within this module so we define them here and merely +# import into .validators / .converters. + + +@attrs(slots=True, hash=True) +class _AndValidator(object): + """ + Compose many validators to a single one. + """ + + _validators = attrib() + + def __call__(self, inst, attr, value): + for v in self._validators: + v(inst, attr, value) + + +def and_(*validators): + """ + A validator that composes multiple validators into one. + + When called on a value, it runs all wrapped validators. + + :param callables validators: Arbitrary number of validators. + + .. versionadded:: 17.1.0 + """ + vals = [] + for validator in validators: + vals.extend( + validator._validators + if isinstance(validator, _AndValidator) + else [validator] + ) + + return _AndValidator(tuple(vals)) + + +def pipe(*converters): + """ + A converter that composes multiple converters into one. + + When called on a value, it runs all wrapped converters, returning the + *last* value. + + Type annotations will be inferred from the wrapped converters', if + they have any. + + :param callables converters: Arbitrary number of converters. + + .. versionadded:: 20.1.0 + """ + + def pipe_converter(val): + for converter in converters: + val = converter(val) + + return val + + if not PY2: + if not converters: + # If the converter list is empty, pipe_converter is the identity. + A = typing.TypeVar("A") + pipe_converter.__annotations__ = {"val": A, "return": A} + else: + # Get parameter type. + sig = None + try: + sig = inspect.signature(converters[0]) + except (ValueError, TypeError): # inspect failed + pass + if sig: + params = list(sig.parameters.values()) + if ( + params + and params[0].annotation is not inspect.Parameter.empty + ): + pipe_converter.__annotations__["val"] = params[ + 0 + ].annotation + # Get return type. + sig = None + try: + sig = inspect.signature(converters[-1]) + except (ValueError, TypeError): # inspect failed + pass + if sig and sig.return_annotation is not inspect.Signature().empty: + pipe_converter.__annotations__[ + "return" + ] = sig.return_annotation + + return pipe_converter diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/_next_gen.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_next_gen.py new file mode 100644 index 0000000000..068253688c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_next_gen.py @@ -0,0 +1,216 @@ +# SPDX-License-Identifier: MIT + +""" +These are Python 3.6+-only and keyword-only APIs that call `attr.s` and +`attr.ib` with different default values. +""" + + +from functools import partial + +from . import setters +from ._funcs import asdict as _asdict +from ._funcs import astuple as _astuple +from ._make import ( + NOTHING, + _frozen_setattrs, + _ng_default_on_setattr, + attrib, + attrs, +) +from .exceptions import UnannotatedAttributeError + + +def define( + maybe_cls=None, + *, + these=None, + repr=None, + hash=None, + init=None, + slots=True, + frozen=False, + weakref_slot=True, + str=False, + auto_attribs=None, + kw_only=False, + cache_hash=False, + auto_exc=True, + eq=None, + order=False, + auto_detect=True, + getstate_setstate=None, + on_setattr=None, + field_transformer=None, + match_args=True, +): + r""" + Define an ``attrs`` class. + + Differences to the classic `attr.s` that it uses underneath: + + - Automatically detect whether or not *auto_attribs* should be `True` + (c.f. *auto_attribs* parameter). + - If *frozen* is `False`, run converters and validators when setting an + attribute by default. + - *slots=True* (see :term:`slotted classes` for potentially surprising + behaviors) + - *auto_exc=True* + - *auto_detect=True* + - *order=False* + - *match_args=True* + - Some options that were only relevant on Python 2 or were kept around for + backwards-compatibility have been removed. + + Please note that these are all defaults and you can change them as you + wish. + + :param Optional[bool] auto_attribs: If set to `True` or `False`, it behaves + exactly like `attr.s`. If left `None`, `attr.s` will try to guess: + + 1. If any attributes are annotated and no unannotated `attrs.fields`\ s + are found, it assumes *auto_attribs=True*. + 2. Otherwise it assumes *auto_attribs=False* and tries to collect + `attrs.fields`\ s. + + For now, please refer to `attr.s` for the rest of the parameters. + + .. versionadded:: 20.1.0 + .. versionchanged:: 21.3.0 Converters are also run ``on_setattr``. + """ + + def do_it(cls, auto_attribs): + return attrs( + maybe_cls=cls, + these=these, + repr=repr, + hash=hash, + init=init, + slots=slots, + frozen=frozen, + weakref_slot=weakref_slot, + str=str, + auto_attribs=auto_attribs, + kw_only=kw_only, + cache_hash=cache_hash, + auto_exc=auto_exc, + eq=eq, + order=order, + auto_detect=auto_detect, + collect_by_mro=True, + getstate_setstate=getstate_setstate, + on_setattr=on_setattr, + field_transformer=field_transformer, + match_args=match_args, + ) + + def wrap(cls): + """ + Making this a wrapper ensures this code runs during class creation. + + We also ensure that frozen-ness of classes is inherited. + """ + nonlocal frozen, on_setattr + + had_on_setattr = on_setattr not in (None, setters.NO_OP) + + # By default, mutable classes convert & validate on setattr. + if frozen is False and on_setattr is None: + on_setattr = _ng_default_on_setattr + + # However, if we subclass a frozen class, we inherit the immutability + # and disable on_setattr. + for base_cls in cls.__bases__: + if base_cls.__setattr__ is _frozen_setattrs: + if had_on_setattr: + raise ValueError( + "Frozen classes can't use on_setattr " + "(frozen-ness was inherited)." + ) + + on_setattr = setters.NO_OP + break + + if auto_attribs is not None: + return do_it(cls, auto_attribs) + + try: + return do_it(cls, True) + except UnannotatedAttributeError: + return do_it(cls, False) + + # maybe_cls's type depends on the usage of the decorator. It's a class + # if it's used as `@attrs` but ``None`` if used as `@attrs()`. + if maybe_cls is None: + return wrap + else: + return wrap(maybe_cls) + + +mutable = define +frozen = partial(define, frozen=True, on_setattr=None) + + +def field( + *, + default=NOTHING, + validator=None, + repr=True, + hash=None, + init=True, + metadata=None, + converter=None, + factory=None, + kw_only=False, + eq=None, + order=None, + on_setattr=None, +): + """ + Identical to `attr.ib`, except keyword-only and with some arguments + removed. + + .. versionadded:: 20.1.0 + """ + return attrib( + default=default, + validator=validator, + repr=repr, + hash=hash, + init=init, + metadata=metadata, + converter=converter, + factory=factory, + kw_only=kw_only, + eq=eq, + order=order, + on_setattr=on_setattr, + ) + + +def asdict(inst, *, recurse=True, filter=None, value_serializer=None): + """ + Same as `attr.asdict`, except that collections types are always retained + and dict is always used as *dict_factory*. + + .. versionadded:: 21.3.0 + """ + return _asdict( + inst=inst, + recurse=recurse, + filter=filter, + value_serializer=value_serializer, + retain_collection_types=True, + ) + + +def astuple(inst, *, recurse=True, filter=None): + """ + Same as `attr.astuple`, except that collections types are always retained + and `tuple` is always used as the *tuple_factory*. + + .. versionadded:: 21.3.0 + """ + return _astuple( + inst=inst, recurse=recurse, filter=filter, retain_collection_types=True + ) diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/_version_info.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_version_info.py new file mode 100644 index 0000000000..cdaeec37a1 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_version_info.py @@ -0,0 +1,87 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + +from functools import total_ordering + +from ._funcs import astuple +from ._make import attrib, attrs + + +@total_ordering +@attrs(eq=False, order=False, slots=True, frozen=True) +class VersionInfo(object): + """ + A version object that can be compared to tuple of length 1--4: + + >>> attr.VersionInfo(19, 1, 0, "final") <= (19, 2) + True + >>> attr.VersionInfo(19, 1, 0, "final") < (19, 1, 1) + True + >>> vi = attr.VersionInfo(19, 2, 0, "final") + >>> vi < (19, 1, 1) + False + >>> vi < (19,) + False + >>> vi == (19, 2,) + True + >>> vi == (19, 2, 1) + False + + .. versionadded:: 19.2 + """ + + year = attrib(type=int) + minor = attrib(type=int) + micro = attrib(type=int) + releaselevel = attrib(type=str) + + @classmethod + def _from_version_string(cls, s): + """ + Parse *s* and return a _VersionInfo. + """ + v = s.split(".") + if len(v) == 3: + v.append("final") + + return cls( + year=int(v[0]), minor=int(v[1]), micro=int(v[2]), releaselevel=v[3] + ) + + def _ensure_tuple(self, other): + """ + Ensure *other* is a tuple of a valid length. + + Returns a possibly transformed *other* and ourselves as a tuple of + the same length as *other*. + """ + + if self.__class__ is other.__class__: + other = astuple(other) + + if not isinstance(other, tuple): + raise NotImplementedError + + if not (1 <= len(other) <= 4): + raise NotImplementedError + + return astuple(self)[: len(other)], other + + def __eq__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + return us == them + + def __lt__(self, other): + try: + us, them = self._ensure_tuple(other) + except NotImplementedError: + return NotImplemented + + # Since alphabetically "dev0" < "final" < "post1" < "post2", we don't + # have to do anything special with releaselevel for now. + return us < them diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/_version_info.pyi b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_version_info.pyi new file mode 100644 index 0000000000..45ced08633 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/_version_info.pyi @@ -0,0 +1,9 @@ +class VersionInfo: + @property + def year(self) -> int: ... + @property + def minor(self) -> int: ... + @property + def micro(self) -> int: ... + @property + def releaselevel(self) -> str: ... diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/converters.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/converters.py new file mode 100644 index 0000000000..1fb6c05d7b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/converters.py @@ -0,0 +1,155 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful converters. +""" + +from __future__ import absolute_import, division, print_function + +from ._compat import PY2 +from ._make import NOTHING, Factory, pipe + + +if not PY2: + import inspect + import typing + + +__all__ = [ + "default_if_none", + "optional", + "pipe", + "to_bool", +] + + +def optional(converter): + """ + A converter that allows an attribute to be optional. An optional attribute + is one which can be set to ``None``. + + Type annotations will be inferred from the wrapped converter's, if it + has any. + + :param callable converter: the converter that is used for non-``None`` + values. + + .. versionadded:: 17.1.0 + """ + + def optional_converter(val): + if val is None: + return None + return converter(val) + + if not PY2: + sig = None + try: + sig = inspect.signature(converter) + except (ValueError, TypeError): # inspect failed + pass + if sig: + params = list(sig.parameters.values()) + if params and params[0].annotation is not inspect.Parameter.empty: + optional_converter.__annotations__["val"] = typing.Optional[ + params[0].annotation + ] + if sig.return_annotation is not inspect.Signature.empty: + optional_converter.__annotations__["return"] = typing.Optional[ + sig.return_annotation + ] + + return optional_converter + + +def default_if_none(default=NOTHING, factory=None): + """ + A converter that allows to replace ``None`` values by *default* or the + result of *factory*. + + :param default: Value to be used if ``None`` is passed. Passing an instance + of `attrs.Factory` is supported, however the ``takes_self`` option + is *not*. + :param callable factory: A callable that takes no parameters whose result + is used if ``None`` is passed. + + :raises TypeError: If **neither** *default* or *factory* is passed. + :raises TypeError: If **both** *default* and *factory* are passed. + :raises ValueError: If an instance of `attrs.Factory` is passed with + ``takes_self=True``. + + .. versionadded:: 18.2.0 + """ + if default is NOTHING and factory is None: + raise TypeError("Must pass either `default` or `factory`.") + + if default is not NOTHING and factory is not None: + raise TypeError( + "Must pass either `default` or `factory` but not both." + ) + + if factory is not None: + default = Factory(factory) + + if isinstance(default, Factory): + if default.takes_self: + raise ValueError( + "`takes_self` is not supported by default_if_none." + ) + + def default_if_none_converter(val): + if val is not None: + return val + + return default.factory() + + else: + + def default_if_none_converter(val): + if val is not None: + return val + + return default + + return default_if_none_converter + + +def to_bool(val): + """ + Convert "boolean" strings (e.g., from env. vars.) to real booleans. + + Values mapping to :code:`True`: + + - :code:`True` + - :code:`"true"` / :code:`"t"` + - :code:`"yes"` / :code:`"y"` + - :code:`"on"` + - :code:`"1"` + - :code:`1` + + Values mapping to :code:`False`: + + - :code:`False` + - :code:`"false"` / :code:`"f"` + - :code:`"no"` / :code:`"n"` + - :code:`"off"` + - :code:`"0"` + - :code:`0` + + :raises ValueError: for any other value. + + .. versionadded:: 21.3.0 + """ + if isinstance(val, str): + val = val.lower() + truthy = {True, "true", "t", "yes", "y", "on", "1", 1} + falsy = {False, "false", "f", "no", "n", "off", "0", 0} + try: + if val in truthy: + return True + if val in falsy: + return False + except TypeError: + # Raised when "val" is not hashable (e.g., lists) + pass + raise ValueError("Cannot convert value to bool: {}".format(val)) diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/converters.pyi b/testing/web-platform/tests/tools/third_party/attrs/src/attr/converters.pyi new file mode 100644 index 0000000000..0f58088a37 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/converters.pyi @@ -0,0 +1,13 @@ +from typing import Callable, Optional, TypeVar, overload + +from . import _ConverterType + +_T = TypeVar("_T") + +def pipe(*validators: _ConverterType) -> _ConverterType: ... +def optional(converter: _ConverterType) -> _ConverterType: ... +@overload +def default_if_none(default: _T) -> _ConverterType: ... +@overload +def default_if_none(*, factory: Callable[[], _T]) -> _ConverterType: ... +def to_bool(val: str) -> bool: ... diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/exceptions.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/exceptions.py new file mode 100644 index 0000000000..b2f1edc32a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/exceptions.py @@ -0,0 +1,94 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + + +class FrozenError(AttributeError): + """ + A frozen/immutable instance or attribute have been attempted to be + modified. + + It mirrors the behavior of ``namedtuples`` by using the same error message + and subclassing `AttributeError`. + + .. versionadded:: 20.1.0 + """ + + msg = "can't set attribute" + args = [msg] + + +class FrozenInstanceError(FrozenError): + """ + A frozen instance has been attempted to be modified. + + .. versionadded:: 16.1.0 + """ + + +class FrozenAttributeError(FrozenError): + """ + A frozen attribute has been attempted to be modified. + + .. versionadded:: 20.1.0 + """ + + +class AttrsAttributeNotFoundError(ValueError): + """ + An ``attrs`` function couldn't find an attribute that the user asked for. + + .. versionadded:: 16.2.0 + """ + + +class NotAnAttrsClassError(ValueError): + """ + A non-``attrs`` class has been passed into an ``attrs`` function. + + .. versionadded:: 16.2.0 + """ + + +class DefaultAlreadySetError(RuntimeError): + """ + A default has been set using ``attr.ib()`` and is attempted to be reset + using the decorator. + + .. versionadded:: 17.1.0 + """ + + +class UnannotatedAttributeError(RuntimeError): + """ + A class with ``auto_attribs=True`` has an ``attr.ib()`` without a type + annotation. + + .. versionadded:: 17.3.0 + """ + + +class PythonTooOldError(RuntimeError): + """ + It was attempted to use an ``attrs`` feature that requires a newer Python + version. + + .. versionadded:: 18.2.0 + """ + + +class NotCallableError(TypeError): + """ + A ``attr.ib()`` requiring a callable has been set with a value + that is not callable. + + .. versionadded:: 19.2.0 + """ + + def __init__(self, msg, value): + super(TypeError, self).__init__(msg, value) + self.msg = msg + self.value = value + + def __str__(self): + return str(self.msg) diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/exceptions.pyi b/testing/web-platform/tests/tools/third_party/attrs/src/attr/exceptions.pyi new file mode 100644 index 0000000000..f2680118b4 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/exceptions.pyi @@ -0,0 +1,17 @@ +from typing import Any + +class FrozenError(AttributeError): + msg: str = ... + +class FrozenInstanceError(FrozenError): ... +class FrozenAttributeError(FrozenError): ... +class AttrsAttributeNotFoundError(ValueError): ... +class NotAnAttrsClassError(ValueError): ... +class DefaultAlreadySetError(RuntimeError): ... +class UnannotatedAttributeError(RuntimeError): ... +class PythonTooOldError(RuntimeError): ... + +class NotCallableError(TypeError): + msg: str = ... + value: Any = ... + def __init__(self, msg: str, value: Any) -> None: ... diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/filters.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/filters.py new file mode 100644 index 0000000000..a1978a8775 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/filters.py @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful filters for `attr.asdict`. +""" + +from __future__ import absolute_import, division, print_function + +from ._compat import isclass +from ._make import Attribute + + +def _split_what(what): + """ + Returns a tuple of `frozenset`s of classes and attributes. + """ + return ( + frozenset(cls for cls in what if isclass(cls)), + frozenset(cls for cls in what if isinstance(cls, Attribute)), + ) + + +def include(*what): + """ + Include *what*. + + :param what: What to include. + :type what: `list` of `type` or `attrs.Attribute`\\ s + + :rtype: `callable` + """ + cls, attrs = _split_what(what) + + def include_(attribute, value): + return value.__class__ in cls or attribute in attrs + + return include_ + + +def exclude(*what): + """ + Exclude *what*. + + :param what: What to exclude. + :type what: `list` of classes or `attrs.Attribute`\\ s. + + :rtype: `callable` + """ + cls, attrs = _split_what(what) + + def exclude_(attribute, value): + return value.__class__ not in cls and attribute not in attrs + + return exclude_ diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/filters.pyi b/testing/web-platform/tests/tools/third_party/attrs/src/attr/filters.pyi new file mode 100644 index 0000000000..993866865e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/filters.pyi @@ -0,0 +1,6 @@ +from typing import Any, Union + +from . import Attribute, _FilterType + +def include(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... +def exclude(*what: Union[type, Attribute[Any]]) -> _FilterType[Any]: ... diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/py.typed b/testing/web-platform/tests/tools/third_party/attrs/src/attr/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/setters.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/setters.py new file mode 100644 index 0000000000..b1cbb5d83e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/setters.py @@ -0,0 +1,79 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly used hooks for on_setattr. +""" + +from __future__ import absolute_import, division, print_function + +from . import _config +from .exceptions import FrozenAttributeError + + +def pipe(*setters): + """ + Run all *setters* and return the return value of the last one. + + .. versionadded:: 20.1.0 + """ + + def wrapped_pipe(instance, attrib, new_value): + rv = new_value + + for setter in setters: + rv = setter(instance, attrib, rv) + + return rv + + return wrapped_pipe + + +def frozen(_, __, ___): + """ + Prevent an attribute to be modified. + + .. versionadded:: 20.1.0 + """ + raise FrozenAttributeError() + + +def validate(instance, attrib, new_value): + """ + Run *attrib*'s validator on *new_value* if it has one. + + .. versionadded:: 20.1.0 + """ + if _config._run_validators is False: + return new_value + + v = attrib.validator + if not v: + return new_value + + v(instance, attrib, new_value) + + return new_value + + +def convert(instance, attrib, new_value): + """ + Run *attrib*'s converter -- if it has one -- on *new_value* and return the + result. + + .. versionadded:: 20.1.0 + """ + c = attrib.converter + if c: + return c(new_value) + + return new_value + + +NO_OP = object() +""" +Sentinel for disabling class-wide *on_setattr* hooks for certain attributes. + +Does not work in `pipe` or within lists. + +.. versionadded:: 20.1.0 +""" diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/setters.pyi b/testing/web-platform/tests/tools/third_party/attrs/src/attr/setters.pyi new file mode 100644 index 0000000000..3f5603c2b0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/setters.pyi @@ -0,0 +1,19 @@ +from typing import Any, NewType, NoReturn, TypeVar, cast + +from . import Attribute, _OnSetAttrType + +_T = TypeVar("_T") + +def frozen( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> NoReturn: ... +def pipe(*setters: _OnSetAttrType) -> _OnSetAttrType: ... +def validate(instance: Any, attribute: Attribute[_T], new_value: _T) -> _T: ... + +# convert is allowed to return Any, because they can be chained using pipe. +def convert( + instance: Any, attribute: Attribute[Any], new_value: Any +) -> Any: ... + +_NoOpType = NewType("_NoOpType", object) +NO_OP: _NoOpType diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/validators.py b/testing/web-platform/tests/tools/third_party/attrs/src/attr/validators.py new file mode 100644 index 0000000000..0b0c8342f2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/validators.py @@ -0,0 +1,561 @@ +# SPDX-License-Identifier: MIT + +""" +Commonly useful validators. +""" + +from __future__ import absolute_import, division, print_function + +import operator +import re + +from contextlib import contextmanager + +from ._config import get_run_validators, set_run_validators +from ._make import _AndValidator, and_, attrib, attrs +from .exceptions import NotCallableError + + +try: + Pattern = re.Pattern +except AttributeError: # Python <3.7 lacks a Pattern type. + Pattern = type(re.compile("")) + + +__all__ = [ + "and_", + "deep_iterable", + "deep_mapping", + "disabled", + "ge", + "get_disabled", + "gt", + "in_", + "instance_of", + "is_callable", + "le", + "lt", + "matches_re", + "max_len", + "optional", + "provides", + "set_disabled", +] + + +def set_disabled(disabled): + """ + Globally disable or enable running validators. + + By default, they are run. + + :param disabled: If ``True``, disable running all validators. + :type disabled: bool + + .. warning:: + + This function is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(not disabled) + + +def get_disabled(): + """ + Return a bool indicating whether validators are currently disabled or not. + + :return: ``True`` if validators are currently disabled. + :rtype: bool + + .. versionadded:: 21.3.0 + """ + return not get_run_validators() + + +@contextmanager +def disabled(): + """ + Context manager that disables running validators within its context. + + .. warning:: + + This context manager is not thread-safe! + + .. versionadded:: 21.3.0 + """ + set_run_validators(False) + try: + yield + finally: + set_run_validators(True) + + +@attrs(repr=False, slots=True, hash=True) +class _InstanceOfValidator(object): + type = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not isinstance(value, self.type): + raise TypeError( + "'{name}' must be {type!r} (got {value!r} that is a " + "{actual!r}).".format( + name=attr.name, + type=self.type, + actual=value.__class__, + value=value, + ), + attr, + self.type, + value, + ) + + def __repr__(self): + return "".format( + type=self.type + ) + + +def instance_of(type): + """ + A validator that raises a `TypeError` if the initializer is called + with a wrong type for this particular attribute (checks are performed using + `isinstance` therefore it's also valid to pass a tuple of types). + + :param type: The type to check for. + :type type: type or tuple of types + + :raises TypeError: With a human readable error message, the attribute + (of type `attrs.Attribute`), the expected type, and the value it + got. + """ + return _InstanceOfValidator(type) + + +@attrs(repr=False, frozen=True, slots=True) +class _MatchesReValidator(object): + pattern = attrib() + match_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.match_func(value): + raise ValueError( + "'{name}' must match regex {pattern!r}" + " ({value!r} doesn't)".format( + name=attr.name, pattern=self.pattern.pattern, value=value + ), + attr, + self.pattern, + value, + ) + + def __repr__(self): + return "".format( + pattern=self.pattern + ) + + +def matches_re(regex, flags=0, func=None): + r""" + A validator that raises `ValueError` if the initializer is called + with a string that doesn't match *regex*. + + :param regex: a regex string or precompiled pattern to match against + :param int flags: flags that will be passed to the underlying re function + (default 0) + :param callable func: which underlying `re` function to call (options + are `re.fullmatch`, `re.search`, `re.match`, default + is ``None`` which means either `re.fullmatch` or an emulation of + it on Python 2). For performance reasons, they won't be used directly + but on a pre-`re.compile`\ ed pattern. + + .. versionadded:: 19.2.0 + .. versionchanged:: 21.3.0 *regex* can be a pre-compiled pattern. + """ + fullmatch = getattr(re, "fullmatch", None) + valid_funcs = (fullmatch, None, re.search, re.match) + if func not in valid_funcs: + raise ValueError( + "'func' must be one of {}.".format( + ", ".join( + sorted( + e and e.__name__ or "None" for e in set(valid_funcs) + ) + ) + ) + ) + + if isinstance(regex, Pattern): + if flags: + raise TypeError( + "'flags' can only be used with a string pattern; " + "pass flags to re.compile() instead" + ) + pattern = regex + else: + pattern = re.compile(regex, flags) + + if func is re.match: + match_func = pattern.match + elif func is re.search: + match_func = pattern.search + elif fullmatch: + match_func = pattern.fullmatch + else: # Python 2 fullmatch emulation (https://bugs.python.org/issue16203) + pattern = re.compile( + r"(?:{})\Z".format(pattern.pattern), pattern.flags + ) + match_func = pattern.match + + return _MatchesReValidator(pattern, match_func) + + +@attrs(repr=False, slots=True, hash=True) +class _ProvidesValidator(object): + interface = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.interface.providedBy(value): + raise TypeError( + "'{name}' must provide {interface!r} which {value!r} " + "doesn't.".format( + name=attr.name, interface=self.interface, value=value + ), + attr, + self.interface, + value, + ) + + def __repr__(self): + return "".format( + interface=self.interface + ) + + +def provides(interface): + """ + A validator that raises a `TypeError` if the initializer is called + with an object that does not provide the requested *interface* (checks are + performed using ``interface.providedBy(value)`` (see `zope.interface + `_). + + :param interface: The interface to check for. + :type interface: ``zope.interface.Interface`` + + :raises TypeError: With a human readable error message, the attribute + (of type `attrs.Attribute`), the expected interface, and the + value it got. + """ + return _ProvidesValidator(interface) + + +@attrs(repr=False, slots=True, hash=True) +class _OptionalValidator(object): + validator = attrib() + + def __call__(self, inst, attr, value): + if value is None: + return + + self.validator(inst, attr, value) + + def __repr__(self): + return "".format( + what=repr(self.validator) + ) + + +def optional(validator): + """ + A validator that makes an attribute optional. An optional attribute is one + which can be set to ``None`` in addition to satisfying the requirements of + the sub-validator. + + :param validator: A validator (or a list of validators) that is used for + non-``None`` values. + :type validator: callable or `list` of callables. + + .. versionadded:: 15.1.0 + .. versionchanged:: 17.1.0 *validator* can be a list of validators. + """ + if isinstance(validator, list): + return _OptionalValidator(_AndValidator(validator)) + return _OptionalValidator(validator) + + +@attrs(repr=False, slots=True, hash=True) +class _InValidator(object): + options = attrib() + + def __call__(self, inst, attr, value): + try: + in_options = value in self.options + except TypeError: # e.g. `1 in "abc"` + in_options = False + + if not in_options: + raise ValueError( + "'{name}' must be in {options!r} (got {value!r})".format( + name=attr.name, options=self.options, value=value + ) + ) + + def __repr__(self): + return "".format( + options=self.options + ) + + +def in_(options): + """ + A validator that raises a `ValueError` if the initializer is called + with a value that does not belong in the options provided. The check is + performed using ``value in options``. + + :param options: Allowed options. + :type options: list, tuple, `enum.Enum`, ... + + :raises ValueError: With a human readable error message, the attribute (of + type `attrs.Attribute`), the expected options, and the value it + got. + + .. versionadded:: 17.1.0 + """ + return _InValidator(options) + + +@attrs(repr=False, slots=False, hash=True) +class _IsCallableValidator(object): + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not callable(value): + message = ( + "'{name}' must be callable " + "(got {value!r} that is a {actual!r})." + ) + raise NotCallableError( + msg=message.format( + name=attr.name, value=value, actual=value.__class__ + ), + value=value, + ) + + def __repr__(self): + return "" + + +def is_callable(): + """ + A validator that raises a `attr.exceptions.NotCallableError` if the + initializer is called with a value for this particular attribute + that is not callable. + + .. versionadded:: 19.1.0 + + :raises `attr.exceptions.NotCallableError`: With a human readable error + message containing the attribute (`attrs.Attribute`) name, + and the value it got. + """ + return _IsCallableValidator() + + +@attrs(repr=False, slots=True, hash=True) +class _DeepIterable(object): + member_validator = attrib(validator=is_callable()) + iterable_validator = attrib( + default=None, validator=optional(is_callable()) + ) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.iterable_validator is not None: + self.iterable_validator(inst, attr, value) + + for member in value: + self.member_validator(inst, attr, member) + + def __repr__(self): + iterable_identifier = ( + "" + if self.iterable_validator is None + else " {iterable!r}".format(iterable=self.iterable_validator) + ) + return ( + "" + ).format( + iterable_identifier=iterable_identifier, + member=self.member_validator, + ) + + +def deep_iterable(member_validator, iterable_validator=None): + """ + A validator that performs deep validation of an iterable. + + :param member_validator: Validator to apply to iterable members + :param iterable_validator: Validator to apply to iterable itself + (optional) + + .. versionadded:: 19.1.0 + + :raises TypeError: if any sub-validators fail + """ + return _DeepIterable(member_validator, iterable_validator) + + +@attrs(repr=False, slots=True, hash=True) +class _DeepMapping(object): + key_validator = attrib(validator=is_callable()) + value_validator = attrib(validator=is_callable()) + mapping_validator = attrib(default=None, validator=optional(is_callable())) + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if self.mapping_validator is not None: + self.mapping_validator(inst, attr, value) + + for key in value: + self.key_validator(inst, attr, key) + self.value_validator(inst, attr, value[key]) + + def __repr__(self): + return ( + "" + ).format(key=self.key_validator, value=self.value_validator) + + +def deep_mapping(key_validator, value_validator, mapping_validator=None): + """ + A validator that performs deep validation of a dictionary. + + :param key_validator: Validator to apply to dictionary keys + :param value_validator: Validator to apply to dictionary values + :param mapping_validator: Validator to apply to top-level mapping + attribute (optional) + + .. versionadded:: 19.1.0 + + :raises TypeError: if any sub-validators fail + """ + return _DeepMapping(key_validator, value_validator, mapping_validator) + + +@attrs(repr=False, frozen=True, slots=True) +class _NumberValidator(object): + bound = attrib() + compare_op = attrib() + compare_func = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if not self.compare_func(value, self.bound): + raise ValueError( + "'{name}' must be {op} {bound}: {value}".format( + name=attr.name, + op=self.compare_op, + bound=self.bound, + value=value, + ) + ) + + def __repr__(self): + return "".format( + op=self.compare_op, bound=self.bound + ) + + +def lt(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number larger or equal to *val*. + + :param val: Exclusive upper bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<", operator.lt) + + +def le(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number greater than *val*. + + :param val: Inclusive upper bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, "<=", operator.le) + + +def ge(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number smaller than *val*. + + :param val: Inclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">=", operator.ge) + + +def gt(val): + """ + A validator that raises `ValueError` if the initializer is called + with a number smaller or equal to *val*. + + :param val: Exclusive lower bound for values + + .. versionadded:: 21.3.0 + """ + return _NumberValidator(val, ">", operator.gt) + + +@attrs(repr=False, frozen=True, slots=True) +class _MaxLengthValidator(object): + max_length = attrib() + + def __call__(self, inst, attr, value): + """ + We use a callable class to be able to change the ``__repr__``. + """ + if len(value) > self.max_length: + raise ValueError( + "Length of '{name}' must be <= {max}: {len}".format( + name=attr.name, max=self.max_length, len=len(value) + ) + ) + + def __repr__(self): + return "".format(max=self.max_length) + + +def max_len(length): + """ + A validator that raises `ValueError` if the initializer is called + with a string or iterable that is longer than *length*. + + :param int length: Maximum length of the string or iterable + + .. versionadded:: 21.3.0 + """ + return _MaxLengthValidator(length) diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attr/validators.pyi b/testing/web-platform/tests/tools/third_party/attrs/src/attr/validators.pyi new file mode 100644 index 0000000000..5e00b85433 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attr/validators.pyi @@ -0,0 +1,78 @@ +from typing import ( + Any, + AnyStr, + Callable, + Container, + ContextManager, + Iterable, + List, + Mapping, + Match, + Optional, + Pattern, + Tuple, + Type, + TypeVar, + Union, + overload, +) + +from . import _ValidatorType + +_T = TypeVar("_T") +_T1 = TypeVar("_T1") +_T2 = TypeVar("_T2") +_T3 = TypeVar("_T3") +_I = TypeVar("_I", bound=Iterable) +_K = TypeVar("_K") +_V = TypeVar("_V") +_M = TypeVar("_M", bound=Mapping) + +def set_disabled(run: bool) -> None: ... +def get_disabled() -> bool: ... +def disabled() -> ContextManager[None]: ... + +# To be more precise on instance_of use some overloads. +# If there are more than 3 items in the tuple then we fall back to Any +@overload +def instance_of(type: Type[_T]) -> _ValidatorType[_T]: ... +@overload +def instance_of(type: Tuple[Type[_T]]) -> _ValidatorType[_T]: ... +@overload +def instance_of( + type: Tuple[Type[_T1], Type[_T2]] +) -> _ValidatorType[Union[_T1, _T2]]: ... +@overload +def instance_of( + type: Tuple[Type[_T1], Type[_T2], Type[_T3]] +) -> _ValidatorType[Union[_T1, _T2, _T3]]: ... +@overload +def instance_of(type: Tuple[type, ...]) -> _ValidatorType[Any]: ... +def provides(interface: Any) -> _ValidatorType[Any]: ... +def optional( + validator: Union[_ValidatorType[_T], List[_ValidatorType[_T]]] +) -> _ValidatorType[Optional[_T]]: ... +def in_(options: Container[_T]) -> _ValidatorType[_T]: ... +def and_(*validators: _ValidatorType[_T]) -> _ValidatorType[_T]: ... +def matches_re( + regex: Union[Pattern[AnyStr], AnyStr], + flags: int = ..., + func: Optional[ + Callable[[AnyStr, AnyStr, int], Optional[Match[AnyStr]]] + ] = ..., +) -> _ValidatorType[AnyStr]: ... +def deep_iterable( + member_validator: _ValidatorType[_T], + iterable_validator: Optional[_ValidatorType[_I]] = ..., +) -> _ValidatorType[_I]: ... +def deep_mapping( + key_validator: _ValidatorType[_K], + value_validator: _ValidatorType[_V], + mapping_validator: Optional[_ValidatorType[_M]] = ..., +) -> _ValidatorType[_M]: ... +def is_callable() -> _ValidatorType[_T]: ... +def lt(val: _T) -> _ValidatorType[_T]: ... +def le(val: _T) -> _ValidatorType[_T]: ... +def ge(val: _T) -> _ValidatorType[_T]: ... +def gt(val: _T) -> _ValidatorType[_T]: ... +def max_len(length: int) -> _ValidatorType[_T]: ... diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attrs/__init__.py b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/__init__.py new file mode 100644 index 0000000000..a704b8b56b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/__init__.py @@ -0,0 +1,70 @@ +# SPDX-License-Identifier: MIT + +from attr import ( + NOTHING, + Attribute, + Factory, + __author__, + __copyright__, + __description__, + __doc__, + __email__, + __license__, + __title__, + __url__, + __version__, + __version_info__, + assoc, + cmp_using, + define, + evolve, + field, + fields, + fields_dict, + frozen, + has, + make_class, + mutable, + resolve_types, + validate, +) +from attr._next_gen import asdict, astuple + +from . import converters, exceptions, filters, setters, validators + + +__all__ = [ + "__author__", + "__copyright__", + "__description__", + "__doc__", + "__email__", + "__license__", + "__title__", + "__url__", + "__version__", + "__version_info__", + "asdict", + "assoc", + "astuple", + "Attribute", + "cmp_using", + "converters", + "define", + "evolve", + "exceptions", + "Factory", + "field", + "fields_dict", + "fields", + "filters", + "frozen", + "has", + "make_class", + "mutable", + "NOTHING", + "resolve_types", + "setters", + "validate", + "validators", +] diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attrs/__init__.pyi b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/__init__.pyi new file mode 100644 index 0000000000..7426fa5ddb --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/__init__.pyi @@ -0,0 +1,63 @@ +from typing import ( + Any, + Callable, + Dict, + Mapping, + Optional, + Sequence, + Tuple, + Type, +) + +# Because we need to type our own stuff, we have to make everything from +# attr explicitly public too. +from attr import __author__ as __author__ +from attr import __copyright__ as __copyright__ +from attr import __description__ as __description__ +from attr import __email__ as __email__ +from attr import __license__ as __license__ +from attr import __title__ as __title__ +from attr import __url__ as __url__ +from attr import __version__ as __version__ +from attr import __version_info__ as __version_info__ +from attr import _FilterType +from attr import assoc as assoc +from attr import Attribute as Attribute +from attr import define as define +from attr import evolve as evolve +from attr import Factory as Factory +from attr import exceptions as exceptions +from attr import field as field +from attr import fields as fields +from attr import fields_dict as fields_dict +from attr import frozen as frozen +from attr import has as has +from attr import make_class as make_class +from attr import mutable as mutable +from attr import NOTHING as NOTHING +from attr import resolve_types as resolve_types +from attr import setters as setters +from attr import validate as validate +from attr import validators as validators + +# TODO: see definition of attr.asdict/astuple +def asdict( + inst: Any, + recurse: bool = ..., + filter: Optional[_FilterType[Any]] = ..., + dict_factory: Type[Mapping[Any, Any]] = ..., + retain_collection_types: bool = ..., + value_serializer: Optional[ + Callable[[type, Attribute[Any], Any], Any] + ] = ..., + tuple_keys: bool = ..., +) -> Dict[str, Any]: ... + +# TODO: add support for returning NamedTuple from the mypy plugin +def astuple( + inst: Any, + recurse: bool = ..., + filter: Optional[_FilterType[Any]] = ..., + tuple_factory: Type[Sequence[Any]] = ..., + retain_collection_types: bool = ..., +) -> Tuple[Any, ...]: ... diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attrs/converters.py b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/converters.py new file mode 100644 index 0000000000..edfa8d3c16 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/converters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.converters import * # noqa diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attrs/exceptions.py b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/exceptions.py new file mode 100644 index 0000000000..bd9efed202 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/exceptions.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.exceptions import * # noqa diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attrs/filters.py b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/filters.py new file mode 100644 index 0000000000..52959005b0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/filters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.filters import * # noqa diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attrs/py.typed b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/py.typed new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attrs/setters.py b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/setters.py new file mode 100644 index 0000000000..9b50770804 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/setters.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.setters import * # noqa diff --git a/testing/web-platform/tests/tools/third_party/attrs/src/attrs/validators.py b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/validators.py new file mode 100644 index 0000000000..ab2c9b3024 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/src/attrs/validators.py @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: MIT + +from attr.validators import * # noqa diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/__init__.py b/testing/web-platform/tests/tools/third_party/attrs/tests/__init__.py new file mode 100644 index 0000000000..548d2d447d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/__init__.py @@ -0,0 +1 @@ +# SPDX-License-Identifier: MIT diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/attr_import_star.py b/testing/web-platform/tests/tools/third_party/attrs/tests/attr_import_star.py new file mode 100644 index 0000000000..eaec321bac --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/attr_import_star.py @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import + +from attr import * # noqa: F401,F403 + + +# This is imported by test_import::test_from_attr_import_star; this must +# be done indirectly because importing * is only allowed on module level, +# so can't be done inside a test. diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/dataclass_transform_example.py b/testing/web-platform/tests/tools/third_party/attrs/tests/dataclass_transform_example.py new file mode 100644 index 0000000000..49e09061a8 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/dataclass_transform_example.py @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: MIT + +import attr + + +@attr.define() +class Define: + a: str + b: int + + +reveal_type(Define.__init__) # noqa + + +@attr.define() +class DefineConverter: + # mypy plugin adapts the "int" method signature, pyright does not + with_converter: int = attr.field(converter=int) + + +reveal_type(DefineConverter.__init__) # noqa + + +# mypy plugin supports attr.frozen, pyright does not +@attr.frozen() +class Frozen: + a: str + + +d = Frozen("a") +d.a = "new" + +reveal_type(d.a) # noqa + + +# but pyright supports attr.define(frozen) +@attr.define(frozen=True) +class FrozenDefine: + a: str + + +d2 = FrozenDefine("a") +d2.a = "new" + +reveal_type(d2.a) # noqa diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/strategies.py b/testing/web-platform/tests/tools/third_party/attrs/tests/strategies.py new file mode 100644 index 0000000000..99f9f48536 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/strategies.py @@ -0,0 +1,198 @@ +# SPDX-License-Identifier: MIT + +""" +Testing strategies for Hypothesis-based tests. +""" + +import keyword +import string + +from collections import OrderedDict + +from hypothesis import strategies as st + +import attr + +from .utils import make_class + + +optional_bool = st.one_of(st.none(), st.booleans()) + + +def gen_attr_names(): + """ + Generate names for attributes, 'a'...'z', then 'aa'...'zz'. + + ~702 different attribute names should be enough in practice. + + Some short strings (such as 'as') are keywords, so we skip them. + """ + lc = string.ascii_lowercase + for c in lc: + yield c + for outer in lc: + for inner in lc: + res = outer + inner + if keyword.iskeyword(res): + continue + yield outer + inner + + +def maybe_underscore_prefix(source): + """ + A generator to sometimes prepend an underscore. + """ + to_underscore = False + for val in source: + yield val if not to_underscore else "_" + val + to_underscore = not to_underscore + + +@st.composite +def _create_hyp_nested_strategy(draw, simple_class_strategy): + """ + Create a recursive attrs class. + + Given a strategy for building (simpler) classes, create and return + a strategy for building classes that have as an attribute: either just + the simpler class, a list of simpler classes, a tuple of simpler classes, + an ordered dict or a dict mapping the string "cls" to a simpler class. + """ + cls = draw(simple_class_strategy) + factories = [ + cls, + lambda: [cls()], + lambda: (cls(),), + lambda: {"cls": cls()}, + lambda: OrderedDict([("cls", cls())]), + ] + factory = draw(st.sampled_from(factories)) + attrs = draw(list_of_attrs) + [attr.ib(default=attr.Factory(factory))] + return make_class("HypClass", dict(zip(gen_attr_names(), attrs))) + + +bare_attrs = st.builds(attr.ib, default=st.none()) +int_attrs = st.integers().map(lambda i: attr.ib(default=i)) +str_attrs = st.text().map(lambda s: attr.ib(default=s)) +float_attrs = st.floats().map(lambda f: attr.ib(default=f)) +dict_attrs = st.dictionaries(keys=st.text(), values=st.integers()).map( + lambda d: attr.ib(default=d) +) + +simple_attrs_without_metadata = ( + bare_attrs | int_attrs | str_attrs | float_attrs | dict_attrs +) + + +@st.composite +def simple_attrs_with_metadata(draw): + """ + Create a simple attribute with arbitrary metadata. + """ + c_attr = draw(simple_attrs) + keys = st.booleans() | st.binary() | st.integers() | st.text() + vals = st.booleans() | st.binary() | st.integers() | st.text() + metadata = draw( + st.dictionaries(keys=keys, values=vals, min_size=1, max_size=3) + ) + + return attr.ib( + default=c_attr._default, + validator=c_attr._validator, + repr=c_attr.repr, + eq=c_attr.eq, + order=c_attr.order, + hash=c_attr.hash, + init=c_attr.init, + metadata=metadata, + type=None, + converter=c_attr.converter, + ) + + +simple_attrs = simple_attrs_without_metadata | simple_attrs_with_metadata() + +# Python functions support up to 255 arguments. +list_of_attrs = st.lists(simple_attrs, max_size=3) + + +@st.composite +def simple_classes( + draw, slots=None, frozen=None, weakref_slot=None, private_attrs=None +): + """ + A strategy that generates classes with default non-attr attributes. + + For example, this strategy might generate a class such as: + + @attr.s(slots=True, frozen=True, weakref_slot=True) + class HypClass: + a = attr.ib(default=1) + _b = attr.ib(default=None) + c = attr.ib(default='text') + _d = attr.ib(default=1.0) + c = attr.ib(default={'t': 1}) + + By default, all combinations of slots, frozen, and weakref_slot classes + will be generated. If `slots=True` is passed in, only slotted classes will + be generated, and if `slots=False` is passed in, no slotted classes will be + generated. The same applies to `frozen` and `weakref_slot`. + + By default, some attributes will be private (i.e. prefixed with an + underscore). If `private_attrs=True` is passed in, all attributes will be + private, and if `private_attrs=False`, no attributes will be private. + """ + attrs = draw(list_of_attrs) + frozen_flag = draw(st.booleans()) + slots_flag = draw(st.booleans()) + weakref_flag = draw(st.booleans()) + + if private_attrs is None: + attr_names = maybe_underscore_prefix(gen_attr_names()) + elif private_attrs is True: + attr_names = ("_" + n for n in gen_attr_names()) + elif private_attrs is False: + attr_names = gen_attr_names() + + cls_dict = dict(zip(attr_names, attrs)) + pre_init_flag = draw(st.booleans()) + post_init_flag = draw(st.booleans()) + init_flag = draw(st.booleans()) + + if pre_init_flag: + + def pre_init(self): + pass + + cls_dict["__attrs_pre_init__"] = pre_init + + if post_init_flag: + + def post_init(self): + pass + + cls_dict["__attrs_post_init__"] = post_init + + if not init_flag: + + def init(self, *args, **kwargs): + self.__attrs_init__(*args, **kwargs) + + cls_dict["__init__"] = init + + return make_class( + "HypClass", + cls_dict, + slots=slots_flag if slots is None else slots, + frozen=frozen_flag if frozen is None else frozen, + weakref_slot=weakref_flag if weakref_slot is None else weakref_slot, + init=init_flag, + ) + + +# st.recursive works by taking a base strategy (in this case, simple_classes) +# and a special function. This function receives a strategy, and returns +# another strategy (building on top of the base strategy). +nested_classes = st.recursive( + simple_classes(), _create_hyp_nested_strategy, max_leaves=3 +) diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_3rd_party.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_3rd_party.py new file mode 100644 index 0000000000..8866d7f6ef --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_3rd_party.py @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for compatibility against other Python modules. +""" + +import pytest + +from hypothesis import given + +from .strategies import simple_classes + + +cloudpickle = pytest.importorskip("cloudpickle") + + +class TestCloudpickleCompat(object): + """ + Tests for compatibility with ``cloudpickle``. + """ + + @given(simple_classes()) + def test_repr(self, cls): + """ + attrs instances can be pickled and un-pickled with cloudpickle. + """ + inst = cls() + # Exact values aren't a concern so long as neither direction + # raises an exception. + pkl = cloudpickle.dumps(inst) + cloudpickle.loads(pkl) diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_annotations.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_annotations.py new file mode 100644 index 0000000000..a201ebf7fa --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_annotations.py @@ -0,0 +1,671 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for PEP-526 type annotations. + +Python 3.6+ only. +""" + +import sys +import types +import typing + +import pytest + +import attr + +from attr._make import _is_class_var +from attr.exceptions import UnannotatedAttributeError + + +def assert_init_annotations(cls, **annotations): + """ + Assert cls.__init__ has the correct annotations. + """ + __tracebackhide__ = True + + annotations["return"] = type(None) + + assert annotations == typing.get_type_hints(cls.__init__) + + +class TestAnnotations: + """ + Tests for types derived from variable annotations (PEP-526). + """ + + def test_basic_annotations(self): + """ + Sets the `Attribute.type` attr from basic type annotations. + """ + + @attr.resolve_types + @attr.s + class C: + x: int = attr.ib() + y = attr.ib(type=str) + z = attr.ib() + + assert int is attr.fields(C).x.type + assert str is attr.fields(C).y.type + assert None is attr.fields(C).z.type + assert_init_annotations(C, x=int, y=str) + + def test_catches_basic_type_conflict(self): + """ + Raises ValueError if type is specified both ways. + """ + with pytest.raises(ValueError) as e: + + @attr.s + class C: + x: int = attr.ib(type=int) + + assert ( + "Type annotation and type argument cannot both be present", + ) == e.value.args + + def test_typing_annotations(self): + """ + Sets the `Attribute.type` attr from typing annotations. + """ + + @attr.resolve_types + @attr.s + class C: + x: typing.List[int] = attr.ib() + y = attr.ib(type=typing.Optional[str]) + + assert typing.List[int] is attr.fields(C).x.type + assert typing.Optional[str] is attr.fields(C).y.type + assert_init_annotations(C, x=typing.List[int], y=typing.Optional[str]) + + def test_only_attrs_annotations_collected(self): + """ + Annotations that aren't set to an attr.ib are ignored. + """ + + @attr.resolve_types + @attr.s + class C: + x: typing.List[int] = attr.ib() + y: int + + assert 1 == len(attr.fields(C)) + assert_init_annotations(C, x=typing.List[int]) + + @pytest.mark.parametrize("slots", [True, False]) + def test_auto_attribs(self, slots): + """ + If *auto_attribs* is True, bare annotations are collected too. + Defaults work and class variables are ignored. + """ + + @attr.s(auto_attribs=True, slots=slots) + class C: + cls_var: typing.ClassVar[int] = 23 + a: int + x: typing.List[int] = attr.Factory(list) + y: int = 2 + z: int = attr.ib(default=3) + foo: typing.Any = None + + i = C(42) + assert "C(a=42, x=[], y=2, z=3, foo=None)" == repr(i) + + attr_names = set(a.name for a in C.__attrs_attrs__) + assert "a" in attr_names # just double check that the set works + assert "cls_var" not in attr_names + + attr.resolve_types(C) + + assert int == attr.fields(C).a.type + + assert attr.Factory(list) == attr.fields(C).x.default + assert typing.List[int] == attr.fields(C).x.type + + assert int == attr.fields(C).y.type + assert 2 == attr.fields(C).y.default + + assert int == attr.fields(C).z.type + + assert typing.Any == attr.fields(C).foo.type + + # Class body is clean. + if slots is False: + with pytest.raises(AttributeError): + C.y + + assert 2 == i.y + else: + assert isinstance(C.y, types.MemberDescriptorType) + + i.y = 23 + assert 23 == i.y + + assert_init_annotations( + C, + a=int, + x=typing.List[int], + y=int, + z=int, + foo=typing.Optional[typing.Any], + ) + + @pytest.mark.parametrize("slots", [True, False]) + def test_auto_attribs_unannotated(self, slots): + """ + Unannotated `attr.ib`s raise an error. + """ + with pytest.raises(UnannotatedAttributeError) as e: + + @attr.s(slots=slots, auto_attribs=True) + class C: + v = attr.ib() + x: int + y = attr.ib() + z: str + + assert ( + "The following `attr.ib`s lack a type annotation: v, y.", + ) == e.value.args + + @pytest.mark.parametrize("slots", [True, False]) + def test_auto_attribs_subclassing(self, slots): + """ + Attributes from base classes are inherited, it doesn't matter if the + subclass has annotations or not. + + Ref #291 + """ + + @attr.resolve_types + @attr.s(slots=slots, auto_attribs=True) + class A: + a: int = 1 + + @attr.resolve_types + @attr.s(slots=slots, auto_attribs=True) + class B(A): + b: int = 2 + + @attr.resolve_types + @attr.s(slots=slots, auto_attribs=True) + class C(A): + pass + + assert "B(a=1, b=2)" == repr(B()) + assert "C(a=1)" == repr(C()) + assert_init_annotations(A, a=int) + assert_init_annotations(B, a=int, b=int) + assert_init_annotations(C, a=int) + + def test_converter_annotations(self): + """ + An unannotated attribute with an annotated converter gets its + annotation from the converter. + """ + + def int2str(x: int) -> str: + return str(x) + + @attr.s + class A: + a = attr.ib(converter=int2str) + + assert_init_annotations(A, a=int) + + def int2str_(x: int, y: str = ""): + return str(x) + + @attr.s + class A: + a = attr.ib(converter=int2str_) + + assert_init_annotations(A, a=int) + + def test_converter_attrib_annotations(self): + """ + If a converter is provided, an explicit type annotation has no + effect on an attribute's type annotation. + """ + + def int2str(x: int) -> str: + return str(x) + + @attr.s + class A: + a: str = attr.ib(converter=int2str) + b = attr.ib(converter=int2str, type=str) + + assert_init_annotations(A, a=int, b=int) + + def test_non_introspectable_converter(self): + """ + A non-introspectable converter doesn't cause a crash. + """ + + @attr.s + class A: + a = attr.ib(converter=print) + + def test_nullary_converter(self): + """ + A coverter with no arguments doesn't cause a crash. + """ + + def noop(): + pass + + @attr.s + class A: + a = attr.ib(converter=noop) + + assert A.__init__.__annotations__ == {"return": None} + + def test_pipe(self): + """ + pipe() uses the input annotation of its first argument and the + output annotation of its last argument. + """ + + def int2str(x: int) -> str: + return str(x) + + def strlen(y: str) -> int: + return len(y) + + def identity(z): + return z + + assert attr.converters.pipe(int2str).__annotations__ == { + "val": int, + "return": str, + } + assert attr.converters.pipe(int2str, strlen).__annotations__ == { + "val": int, + "return": int, + } + assert attr.converters.pipe(identity, strlen).__annotations__ == { + "return": int + } + assert attr.converters.pipe(int2str, identity).__annotations__ == { + "val": int + } + + def int2str_(x: int, y: int = 0) -> str: + return str(x) + + assert attr.converters.pipe(int2str_).__annotations__ == { + "val": int, + "return": str, + } + + def test_pipe_empty(self): + """ + pipe() with no converters is annotated like the identity. + """ + + p = attr.converters.pipe() + assert "val" in p.__annotations__ + t = p.__annotations__["val"] + assert isinstance(t, typing.TypeVar) + assert p.__annotations__ == {"val": t, "return": t} + + def test_pipe_non_introspectable(self): + """ + pipe() doesn't crash when passed a non-introspectable converter. + """ + + assert attr.converters.pipe(print).__annotations__ == {} + + def test_pipe_nullary(self): + """ + pipe() doesn't crash when passed a nullary converter. + """ + + def noop(): + pass + + assert attr.converters.pipe(noop).__annotations__ == {} + + def test_optional(self): + """ + optional() uses the annotations of the converter it wraps. + """ + + def int2str(x: int) -> str: + return str(x) + + def int_identity(x: int): + return x + + def strify(x) -> str: + return str(x) + + def identity(x): + return x + + assert attr.converters.optional(int2str).__annotations__ == { + "val": typing.Optional[int], + "return": typing.Optional[str], + } + assert attr.converters.optional(int_identity).__annotations__ == { + "val": typing.Optional[int] + } + assert attr.converters.optional(strify).__annotations__ == { + "return": typing.Optional[str] + } + assert attr.converters.optional(identity).__annotations__ == {} + + def int2str_(x: int, y: int = 0) -> str: + return str(x) + + assert attr.converters.optional(int2str_).__annotations__ == { + "val": typing.Optional[int], + "return": typing.Optional[str], + } + + def test_optional_non_introspectable(self): + """ + optional() doesn't crash when passed a non-introspectable + converter. + """ + + assert attr.converters.optional(print).__annotations__ == {} + + def test_optional_nullary(self): + """ + optional() doesn't crash when passed a nullary converter. + """ + + def noop(): + pass + + assert attr.converters.optional(noop).__annotations__ == {} + + @pytest.mark.xfail( + sys.version_info[:2] == (3, 6), reason="Does not work on 3.6." + ) + @pytest.mark.parametrize("slots", [True, False]) + def test_annotations_strings(self, slots): + """ + String annotations are passed into __init__ as is. + + It fails on 3.6 due to a bug in Python. + """ + import typing as t + + from typing import ClassVar + + @attr.s(auto_attribs=True, slots=slots) + class C: + cls_var1: "typing.ClassVar[int]" = 23 + cls_var2: "ClassVar[int]" = 23 + cls_var3: "t.ClassVar[int]" = 23 + a: "int" + x: "typing.List[int]" = attr.Factory(list) + y: "int" = 2 + z: "int" = attr.ib(default=3) + foo: "typing.Any" = None + + attr.resolve_types(C, locals(), globals()) + + assert_init_annotations( + C, + a=int, + x=typing.List[int], + y=int, + z=int, + foo=typing.Optional[typing.Any], + ) + + @pytest.mark.parametrize("slots", [True, False]) + def test_typing_extensions_classvar(self, slots): + """ + If ClassVar is coming from typing_extensions, it is recognized too. + """ + + @attr.s(auto_attribs=True, slots=slots) + class C: + cls_var: "typing_extensions.ClassVar" = 23 # noqa + + assert_init_annotations(C) + + def test_keyword_only_auto_attribs(self): + """ + `kw_only` propagates to attributes defined via `auto_attribs`. + """ + + @attr.s(auto_attribs=True, kw_only=True) + class C: + x: int + y: int + + with pytest.raises(TypeError): + C(0, 1) + + with pytest.raises(TypeError): + C(x=0) + + c = C(x=0, y=1) + + assert c.x == 0 + assert c.y == 1 + + def test_base_class_variable(self): + """ + Base class' class variables can be overridden with an attribute + without resorting to using an explicit `attr.ib()`. + """ + + class Base: + x: int = 42 + + @attr.s(auto_attribs=True) + class C(Base): + x: int + + assert 1 == C(1).x + + def test_removes_none_too(self): + """ + Regression test for #523: make sure defaults that are set to None are + removed too. + """ + + @attr.s(auto_attribs=True) + class C: + x: int = 42 + y: typing.Any = None + + with pytest.raises(AttributeError): + C.x + + with pytest.raises(AttributeError): + C.y + + def test_non_comparable_defaults(self): + """ + Regression test for #585: objects that are not directly comparable + (for example numpy arrays) would cause a crash when used as + default values of an attrs auto-attrib class. + """ + + class NonComparable: + def __eq__(self, other): + raise ValueError + + @attr.s(auto_attribs=True) + class C: + x: typing.Any = NonComparable() + + def test_basic_resolve(self): + """ + Resolve the `Attribute.type` attr from basic type annotations. + Unannotated types are ignored. + """ + + @attr.s + class C: + x: "int" = attr.ib() + y = attr.ib(type=str) + z = attr.ib() + + attr.resolve_types(C) + + assert int is attr.fields(C).x.type + assert str is attr.fields(C).y.type + assert None is attr.fields(C).z.type + + @pytest.mark.parametrize("slots", [True, False]) + def test_resolve_types_auto_attrib(self, slots): + """ + Types can be resolved even when strings are involved. + """ + + @attr.s(slots=slots, auto_attribs=True) + class A: + a: typing.List[int] + b: typing.List["int"] + c: "typing.List[int]" + + # Note: I don't have to pass globals and locals here because + # int is a builtin and will be available in any scope. + attr.resolve_types(A) + + assert typing.List[int] == attr.fields(A).a.type + assert typing.List[int] == attr.fields(A).b.type + assert typing.List[int] == attr.fields(A).c.type + + @pytest.mark.parametrize("slots", [True, False]) + def test_resolve_types_decorator(self, slots): + """ + Types can be resolved using it as a decorator. + """ + + @attr.resolve_types + @attr.s(slots=slots, auto_attribs=True) + class A: + a: typing.List[int] + b: typing.List["int"] + c: "typing.List[int]" + + assert typing.List[int] == attr.fields(A).a.type + assert typing.List[int] == attr.fields(A).b.type + assert typing.List[int] == attr.fields(A).c.type + + @pytest.mark.parametrize("slots", [True, False]) + def test_self_reference(self, slots): + """ + References to self class using quotes can be resolved. + """ + + @attr.s(slots=slots, auto_attribs=True) + class A: + a: "A" + b: typing.Optional["A"] # noqa: will resolve below + + attr.resolve_types(A, globals(), locals()) + + assert A == attr.fields(A).a.type + assert typing.Optional[A] == attr.fields(A).b.type + + @pytest.mark.parametrize("slots", [True, False]) + def test_forward_reference(self, slots): + """ + Forward references can be resolved. + """ + + @attr.s(slots=slots, auto_attribs=True) + class A: + a: typing.List["B"] # noqa: will resolve below + + @attr.s(slots=slots, auto_attribs=True) + class B: + a: A + + attr.resolve_types(A, globals(), locals()) + attr.resolve_types(B, globals(), locals()) + + assert typing.List[B] == attr.fields(A).a.type + assert A == attr.fields(B).a.type + + assert typing.List[B] == attr.fields(A).a.type + assert A == attr.fields(B).a.type + + def test_init_type_hints(self): + """ + Forward references in __init__ can be automatically resolved. + """ + + @attr.s + class C: + x = attr.ib(type="typing.List[int]") + + assert_init_annotations(C, x=typing.List[int]) + + def test_init_type_hints_fake_module(self): + """ + If you somehow set the __module__ to something that doesn't exist + you'll lose __init__ resolution. + """ + + class C: + x = attr.ib(type="typing.List[int]") + + C.__module__ = "totally fake" + C = attr.s(C) + + with pytest.raises(NameError): + typing.get_type_hints(C.__init__) + + def test_inheritance(self): + """ + Subclasses can be resolved after the parent is resolved. + """ + + @attr.define() + class A: + n: "int" + + @attr.define() + class B(A): + pass + + attr.resolve_types(A) + attr.resolve_types(B) + + assert int == attr.fields(A).n.type + assert int == attr.fields(B).n.type + + def test_resolve_twice(self): + """ + You can call resolve_types as many times as you like. + This test is here mostly for coverage. + """ + + @attr.define() + class A: + n: "int" + + attr.resolve_types(A) + assert int == attr.fields(A).n.type + attr.resolve_types(A) + assert int == attr.fields(A).n.type + + +@pytest.mark.parametrize( + "annot", + [ + typing.ClassVar, + "typing.ClassVar", + "'typing.ClassVar[dict]'", + "t.ClassVar[int]", + ], +) +def test_is_class_var(annot): + """ + ClassVars are detected, even if they're a string or quoted. + """ + assert _is_class_var(annot) diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_cmp.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_cmp.py new file mode 100644 index 0000000000..ec2c687489 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_cmp.py @@ -0,0 +1,510 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for methods from `attrib._cmp`. +""" + +from __future__ import absolute_import, division, print_function + +import pytest + +from attr._cmp import cmp_using +from attr._compat import PY2 + + +# Test parameters. +EqCSameType = cmp_using(eq=lambda a, b: a == b, class_name="EqCSameType") +PartialOrderCSameType = cmp_using( + eq=lambda a, b: a == b, + lt=lambda a, b: a < b, + class_name="PartialOrderCSameType", +) +FullOrderCSameType = cmp_using( + eq=lambda a, b: a == b, + lt=lambda a, b: a < b, + le=lambda a, b: a <= b, + gt=lambda a, b: a > b, + ge=lambda a, b: a >= b, + class_name="FullOrderCSameType", +) + +EqCAnyType = cmp_using( + eq=lambda a, b: a == b, require_same_type=False, class_name="EqCAnyType" +) +PartialOrderCAnyType = cmp_using( + eq=lambda a, b: a == b, + lt=lambda a, b: a < b, + require_same_type=False, + class_name="PartialOrderCAnyType", +) + + +eq_data = [ + (EqCSameType, True), + (EqCAnyType, False), +] + +order_data = [ + (PartialOrderCSameType, True), + (PartialOrderCAnyType, False), + (FullOrderCSameType, True), +] + +eq_ids = [c[0].__name__ for c in eq_data] +order_ids = [c[0].__name__ for c in order_data] + +cmp_data = eq_data + order_data +cmp_ids = eq_ids + order_ids + + +class TestEqOrder(object): + """ + Tests for eq and order related methods. + """ + + ######### + # eq + ######### + @pytest.mark.parametrize("cls, requires_same_type", cmp_data, ids=cmp_ids) + def test_equal_same_type(self, cls, requires_same_type): + """ + Equal objects are detected as equal. + """ + assert cls(1) == cls(1) + assert not (cls(1) != cls(1)) + + @pytest.mark.parametrize("cls, requires_same_type", cmp_data, ids=cmp_ids) + def test_unequal_same_type(self, cls, requires_same_type): + """ + Unequal objects of correct type are detected as unequal. + """ + assert cls(1) != cls(2) + assert not (cls(1) == cls(2)) + + @pytest.mark.parametrize("cls, requires_same_type", cmp_data, ids=cmp_ids) + def test_equal_different_type(self, cls, requires_same_type): + """ + Equal values of different types are detected appropriately. + """ + assert (cls(1) == cls(1.0)) == (not requires_same_type) + assert not (cls(1) != cls(1.0)) == (not requires_same_type) + + ######### + # lt + ######### + @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") + @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) + def test_lt_unorderable(self, cls, requires_same_type): + """ + TypeError is raised if class does not implement __lt__. + """ + with pytest.raises(TypeError): + cls(1) < cls(2) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_lt_same_type(self, cls, requires_same_type): + """ + Less-than objects are detected appropriately. + """ + assert cls(1) < cls(2) + assert not (cls(2) < cls(1)) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_not_lt_same_type(self, cls, requires_same_type): + """ + Not less-than objects are detected appropriately. + """ + assert cls(2) >= cls(1) + assert not (cls(1) >= cls(2)) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_lt_different_type(self, cls, requires_same_type): + """ + Less-than values of different types are detected appropriately. + """ + if requires_same_type: + # Unlike __eq__, NotImplemented will cause an exception to be + # raised from __lt__. + if not PY2: + with pytest.raises(TypeError): + cls(1) < cls(2.0) + else: + assert cls(1) < cls(2.0) + assert not (cls(2) < cls(1.0)) + + ######### + # le + ######### + @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") + @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) + def test_le_unorderable(self, cls, requires_same_type): + """ + TypeError is raised if class does not implement __le__. + """ + with pytest.raises(TypeError): + cls(1) <= cls(2) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_le_same_type(self, cls, requires_same_type): + """ + Less-than-or-equal objects are detected appropriately. + """ + assert cls(1) <= cls(1) + assert cls(1) <= cls(2) + assert not (cls(2) <= cls(1)) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_not_le_same_type(self, cls, requires_same_type): + """ + Not less-than-or-equal objects are detected appropriately. + """ + assert cls(2) > cls(1) + assert not (cls(1) > cls(1)) + assert not (cls(1) > cls(2)) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_le_different_type(self, cls, requires_same_type): + """ + Less-than-or-equal values of diff. types are detected appropriately. + """ + if requires_same_type: + # Unlike __eq__, NotImplemented will cause an exception to be + # raised from __le__. + if not PY2: + with pytest.raises(TypeError): + cls(1) <= cls(2.0) + else: + assert cls(1) <= cls(2.0) + assert cls(1) <= cls(1.0) + assert not (cls(2) <= cls(1.0)) + + ######### + # gt + ######### + @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") + @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) + def test_gt_unorderable(self, cls, requires_same_type): + """ + TypeError is raised if class does not implement __gt__. + """ + with pytest.raises(TypeError): + cls(2) > cls(1) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_gt_same_type(self, cls, requires_same_type): + """ + Greater-than objects are detected appropriately. + """ + assert cls(2) > cls(1) + assert not (cls(1) > cls(2)) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_not_gt_same_type(self, cls, requires_same_type): + """ + Not greater-than objects are detected appropriately. + """ + assert cls(1) <= cls(2) + assert not (cls(2) <= cls(1)) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_gt_different_type(self, cls, requires_same_type): + """ + Greater-than values of different types are detected appropriately. + """ + if requires_same_type: + # Unlike __eq__, NotImplemented will cause an exception to be + # raised from __gt__. + if not PY2: + with pytest.raises(TypeError): + cls(2) > cls(1.0) + else: + assert cls(2) > cls(1.0) + assert not (cls(1) > cls(2.0)) + + ######### + # ge + ######### + @pytest.mark.skipif(PY2, reason="PY2 does not raise TypeError") + @pytest.mark.parametrize("cls, requires_same_type", eq_data, ids=eq_ids) + def test_ge_unorderable(self, cls, requires_same_type): + """ + TypeError is raised if class does not implement __ge__. + """ + with pytest.raises(TypeError): + cls(2) >= cls(1) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_ge_same_type(self, cls, requires_same_type): + """ + Greater-than-or-equal objects are detected appropriately. + """ + assert cls(1) >= cls(1) + assert cls(2) >= cls(1) + assert not (cls(1) >= cls(2)) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_not_ge_same_type(self, cls, requires_same_type): + """ + Not greater-than-or-equal objects are detected appropriately. + """ + assert cls(1) < cls(2) + assert not (cls(1) < cls(1)) + assert not (cls(2) < cls(1)) + + @pytest.mark.parametrize( + "cls, requires_same_type", order_data, ids=order_ids + ) + def test_ge_different_type(self, cls, requires_same_type): + """ + Greater-than-or-equal values of diff. types are detected appropriately. + """ + if requires_same_type: + # Unlike __eq__, NotImplemented will cause an exception to be + # raised from __ge__. + if not PY2: + with pytest.raises(TypeError): + cls(2) >= cls(1.0) + else: + assert cls(2) >= cls(2.0) + assert cls(2) >= cls(1.0) + assert not (cls(1) >= cls(2.0)) + + +class TestDundersUnnamedClass(object): + """ + Tests for dunder attributes of unnamed classes. + """ + + cls = cmp_using(eq=lambda a, b: a == b) + + def test_class(self): + """ + Class name and qualified name should be well behaved. + """ + assert self.cls.__name__ == "Comparable" + if not PY2: + assert self.cls.__qualname__ == "Comparable" + + def test_eq(self): + """ + __eq__ docstring and qualified name should be well behaved. + """ + method = self.cls.__eq__ + assert method.__doc__.strip() == "Return a == b. Computed by attrs." + assert method.__name__ == "__eq__" + + def test_ne(self): + """ + __ne__ docstring and qualified name should be well behaved. + """ + method = self.cls.__ne__ + assert method.__doc__.strip() == ( + "Check equality and either forward a NotImplemented or\n" + " return the result negated." + ) + assert method.__name__ == "__ne__" + + +class TestTotalOrderingException(object): + """ + Test for exceptions related to total ordering. + """ + + def test_eq_must_specified(self): + """ + `total_ordering` requires `__eq__` to be specified. + """ + with pytest.raises(ValueError) as ei: + cmp_using(lt=lambda a, b: a < b) + + assert ei.value.args[0] == ( + "eq must be define is order to complete ordering from " + "lt, le, gt, ge." + ) + + +class TestNotImplementedIsPropagated(object): + """ + Test related to functions that return NotImplemented. + """ + + def test_not_implemented_is_propagated(self): + """ + If the comparison function returns NotImplemented, + the dunder method should too. + """ + C = cmp_using(eq=lambda a, b: NotImplemented if a == 1 else a == b) + + assert C(2) == C(2) + assert C(1) != C(1) + + +class TestDundersPartialOrdering(object): + """ + Tests for dunder attributes of classes with partial ordering. + """ + + cls = PartialOrderCSameType + + def test_class(self): + """ + Class name and qualified name should be well behaved. + """ + assert self.cls.__name__ == "PartialOrderCSameType" + if not PY2: + assert self.cls.__qualname__ == "PartialOrderCSameType" + + def test_eq(self): + """ + __eq__ docstring and qualified name should be well behaved. + """ + method = self.cls.__eq__ + assert method.__doc__.strip() == "Return a == b. Computed by attrs." + assert method.__name__ == "__eq__" + + def test_ne(self): + """ + __ne__ docstring and qualified name should be well behaved. + """ + method = self.cls.__ne__ + assert method.__doc__.strip() == ( + "Check equality and either forward a NotImplemented or\n" + " return the result negated." + ) + assert method.__name__ == "__ne__" + + def test_lt(self): + """ + __lt__ docstring and qualified name should be well behaved. + """ + method = self.cls.__lt__ + assert method.__doc__.strip() == "Return a < b. Computed by attrs." + assert method.__name__ == "__lt__" + + def test_le(self): + """ + __le__ docstring and qualified name should be well behaved. + """ + method = self.cls.__le__ + if PY2: + assert method.__doc__ == "x.__le__(y) <==> x<=y" + else: + assert method.__doc__.strip().startswith( + "Return a <= b. Computed by @total_ordering from" + ) + assert method.__name__ == "__le__" + + def test_gt(self): + """ + __gt__ docstring and qualified name should be well behaved. + """ + method = self.cls.__gt__ + if PY2: + assert method.__doc__ == "x.__gt__(y) <==> x>y" + else: + assert method.__doc__.strip().startswith( + "Return a > b. Computed by @total_ordering from" + ) + assert method.__name__ == "__gt__" + + def test_ge(self): + """ + __ge__ docstring and qualified name should be well behaved. + """ + method = self.cls.__ge__ + if PY2: + assert method.__doc__ == "x.__ge__(y) <==> x>=y" + else: + assert method.__doc__.strip().startswith( + "Return a >= b. Computed by @total_ordering from" + ) + assert method.__name__ == "__ge__" + + +class TestDundersFullOrdering(object): + """ + Tests for dunder attributes of classes with full ordering. + """ + + cls = FullOrderCSameType + + def test_class(self): + """ + Class name and qualified name should be well behaved. + """ + assert self.cls.__name__ == "FullOrderCSameType" + if not PY2: + assert self.cls.__qualname__ == "FullOrderCSameType" + + def test_eq(self): + """ + __eq__ docstring and qualified name should be well behaved. + """ + method = self.cls.__eq__ + assert method.__doc__.strip() == "Return a == b. Computed by attrs." + assert method.__name__ == "__eq__" + + def test_ne(self): + """ + __ne__ docstring and qualified name should be well behaved. + """ + method = self.cls.__ne__ + assert method.__doc__.strip() == ( + "Check equality and either forward a NotImplemented or\n" + " return the result negated." + ) + assert method.__name__ == "__ne__" + + def test_lt(self): + """ + __lt__ docstring and qualified name should be well behaved. + """ + method = self.cls.__lt__ + assert method.__doc__.strip() == "Return a < b. Computed by attrs." + assert method.__name__ == "__lt__" + + def test_le(self): + """ + __le__ docstring and qualified name should be well behaved. + """ + method = self.cls.__le__ + assert method.__doc__.strip() == "Return a <= b. Computed by attrs." + assert method.__name__ == "__le__" + + def test_gt(self): + """ + __gt__ docstring and qualified name should be well behaved. + """ + method = self.cls.__gt__ + assert method.__doc__.strip() == "Return a > b. Computed by attrs." + assert method.__name__ == "__gt__" + + def test_ge(self): + """ + __ge__ docstring and qualified name should be well behaved. + """ + method = self.cls.__ge__ + assert method.__doc__.strip() == "Return a >= b. Computed by attrs." + assert method.__name__ == "__ge__" diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_compat.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_compat.py new file mode 100644 index 0000000000..464b492f0f --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_compat.py @@ -0,0 +1,52 @@ +# SPDX-License-Identifier: MIT + +import pytest + +from attr._compat import metadata_proxy + + +@pytest.fixture(name="mp") +def _mp(): + return metadata_proxy({"x": 42, "y": "foo"}) + + +class TestMetadataProxy: + """ + Ensure properties of metadata_proxy independently of hypothesis strategies. + """ + + def test_repr(self, mp): + """ + repr makes sense and is consistent across Python versions. + """ + assert any( + [ + "mappingproxy({'x': 42, 'y': 'foo'})" == repr(mp), + "mappingproxy({'y': 'foo', 'x': 42})" == repr(mp), + ] + ) + + def test_immutable(self, mp): + """ + All mutating methods raise errors. + """ + with pytest.raises(TypeError, match="not support item assignment"): + mp["z"] = 23 + + with pytest.raises(TypeError, match="not support item deletion"): + del mp["x"] + + with pytest.raises(AttributeError, match="no attribute 'update'"): + mp.update({}) + + with pytest.raises(AttributeError, match="no attribute 'clear'"): + mp.clear() + + with pytest.raises(AttributeError, match="no attribute 'pop'"): + mp.pop("x") + + with pytest.raises(AttributeError, match="no attribute 'popitem'"): + mp.popitem() + + with pytest.raises(AttributeError, match="no attribute 'setdefault'"): + mp.setdefault("x") diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_config.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_config.py new file mode 100644 index 0000000000..bbf6756406 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_config.py @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for `attr._config`. +""" + +from __future__ import absolute_import, division, print_function + +import pytest + +from attr import _config + + +class TestConfig(object): + def test_default(self): + """ + Run validators by default. + """ + assert True is _config._run_validators + + def test_set_run_validators(self): + """ + Sets `_run_validators`. + """ + _config.set_run_validators(False) + assert False is _config._run_validators + _config.set_run_validators(True) + assert True is _config._run_validators + + def test_get_run_validators(self): + """ + Returns `_run_validators`. + """ + _config._run_validators = False + assert _config._run_validators is _config.get_run_validators() + _config._run_validators = True + assert _config._run_validators is _config.get_run_validators() + + def test_wrong_type(self): + """ + Passing anything else than a boolean raises TypeError. + """ + with pytest.raises(TypeError) as e: + _config.set_run_validators("False") + assert "'run' must be bool." == e.value.args[0] diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_converters.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_converters.py new file mode 100644 index 0000000000..d0fc723eb1 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_converters.py @@ -0,0 +1,163 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for `attr.converters`. +""" + +from __future__ import absolute_import + +import pytest + +import attr + +from attr import Factory, attrib +from attr.converters import default_if_none, optional, pipe, to_bool + + +class TestOptional(object): + """ + Tests for `optional`. + """ + + def test_success_with_type(self): + """ + Wrapped converter is used as usual if value is not None. + """ + c = optional(int) + + assert c("42") == 42 + + def test_success_with_none(self): + """ + Nothing happens if None. + """ + c = optional(int) + + assert c(None) is None + + def test_fail(self): + """ + Propagates the underlying conversion error when conversion fails. + """ + c = optional(int) + + with pytest.raises(ValueError): + c("not_an_int") + + +class TestDefaultIfNone(object): + def test_missing_default(self): + """ + Raises TypeError if neither default nor factory have been passed. + """ + with pytest.raises(TypeError, match="Must pass either"): + default_if_none() + + def test_too_many_defaults(self): + """ + Raises TypeError if both default and factory are passed. + """ + with pytest.raises(TypeError, match="but not both"): + default_if_none(True, lambda: 42) + + def test_factory_takes_self(self): + """ + Raises ValueError if passed Factory has takes_self=True. + """ + with pytest.raises(ValueError, match="takes_self"): + default_if_none(Factory(list, takes_self=True)) + + @pytest.mark.parametrize("val", [1, 0, True, False, "foo", "", object()]) + def test_not_none(self, val): + """ + If a non-None value is passed, it's handed down. + """ + c = default_if_none("nope") + + assert val == c(val) + + c = default_if_none(factory=list) + + assert val == c(val) + + def test_none_value(self): + """ + Default values are returned when a None is passed. + """ + c = default_if_none(42) + + assert 42 == c(None) + + def test_none_factory(self): + """ + Factories are used if None is passed. + """ + c = default_if_none(factory=list) + + assert [] == c(None) + + c = default_if_none(default=Factory(list)) + + assert [] == c(None) + + +class TestPipe(object): + def test_success(self): + """ + Succeeds if all wrapped converters succeed. + """ + c = pipe(str, to_bool, bool) + + assert True is c("True") is c(True) + + def test_fail(self): + """ + Fails if any wrapped converter fails. + """ + c = pipe(str, to_bool) + + # First wrapped converter fails: + with pytest.raises(ValueError): + c(33) + + # Last wrapped converter fails: + with pytest.raises(ValueError): + c("33") + + def test_sugar(self): + """ + `pipe(c1, c2, c3)` and `[c1, c2, c3]` are equivalent. + """ + + @attr.s + class C(object): + a1 = attrib(default="True", converter=pipe(str, to_bool, bool)) + a2 = attrib(default=True, converter=[str, to_bool, bool]) + + c = C() + assert True is c.a1 is c.a2 + + +class TestToBool(object): + def test_unhashable(self): + """ + Fails if value is unhashable. + """ + with pytest.raises(ValueError, match="Cannot convert value to bool"): + to_bool([]) + + def test_truthy(self): + """ + Fails if truthy values are incorrectly converted. + """ + assert to_bool("t") + assert to_bool("yes") + assert to_bool("on") + + def test_falsy(self): + """ + Fails if falsy values are incorrectly converted. + """ + assert not to_bool("f") + assert not to_bool("no") + assert not to_bool("off") diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_dunders.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_dunders.py new file mode 100644 index 0000000000..186762eb0d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_dunders.py @@ -0,0 +1,1008 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for dunder methods from `attrib._make`. +""" + +from __future__ import absolute_import, division, print_function + +import copy +import pickle + +import pytest + +from hypothesis import given +from hypothesis.strategies import booleans + +import attr + +from attr._make import ( + NOTHING, + Factory, + _add_repr, + _is_slot_cls, + _make_init, + _Nothing, + fields, + make_class, +) +from attr.validators import instance_of + +from .utils import simple_attr, simple_class + + +EqC = simple_class(eq=True) +EqCSlots = simple_class(eq=True, slots=True) +OrderC = simple_class(order=True) +OrderCSlots = simple_class(order=True, slots=True) +ReprC = simple_class(repr=True) +ReprCSlots = simple_class(repr=True, slots=True) + + +@attr.s(eq=True) +class EqCallableC(object): + a = attr.ib(eq=str.lower, order=False) + b = attr.ib(eq=True) + + +@attr.s(eq=True, slots=True) +class EqCallableCSlots(object): + a = attr.ib(eq=str.lower, order=False) + b = attr.ib(eq=True) + + +@attr.s(order=True) +class OrderCallableC(object): + a = attr.ib(eq=True, order=str.lower) + b = attr.ib(order=True) + + +@attr.s(order=True, slots=True) +class OrderCallableCSlots(object): + a = attr.ib(eq=True, order=str.lower) + b = attr.ib(order=True) + + +# HashC is hashable by explicit definition while HashCSlots is hashable +# implicitly. The "Cached" versions are the same, except with hash code +# caching enabled +HashC = simple_class(hash=True) +HashCSlots = simple_class(hash=None, eq=True, frozen=True, slots=True) +HashCCached = simple_class(hash=True, cache_hash=True) +HashCSlotsCached = simple_class( + hash=None, eq=True, frozen=True, slots=True, cache_hash=True +) +# the cached hash code is stored slightly differently in this case +# so it needs to be tested separately +HashCFrozenNotSlotsCached = simple_class( + frozen=True, slots=False, hash=True, cache_hash=True +) + + +def _add_init(cls, frozen): + """ + Add a __init__ method to *cls*. If *frozen* is True, make it immutable. + + This function used to be part of _make. It wasn't used anymore however + the tests for it are still useful to test the behavior of _make_init. + """ + cls.__init__ = _make_init( + cls, + cls.__attrs_attrs__, + getattr(cls, "__attrs_pre_init__", False), + getattr(cls, "__attrs_post_init__", False), + frozen, + _is_slot_cls(cls), + cache_hash=False, + base_attr_map={}, + is_exc=False, + cls_on_setattr=None, + attrs_init=False, + ) + return cls + + +class InitC(object): + __attrs_attrs__ = [simple_attr("a"), simple_attr("b")] + + +InitC = _add_init(InitC, False) + + +class TestEqOrder(object): + """ + Tests for eq and order related methods. + """ + + @given(booleans()) + def test_eq_ignore_attrib(self, slots): + """ + If `eq` is False for an attribute, ignore that attribute. + """ + C = make_class( + "C", {"a": attr.ib(eq=False), "b": attr.ib()}, slots=slots + ) + + assert C(1, 2) == C(2, 2) + + @pytest.mark.parametrize("cls", [EqC, EqCSlots]) + def test_equal(self, cls): + """ + Equal objects are detected as equal. + """ + assert cls(1, 2) == cls(1, 2) + assert not (cls(1, 2) != cls(1, 2)) + + @pytest.mark.parametrize("cls", [EqCallableC, EqCallableCSlots]) + def test_equal_callable(self, cls): + """ + Equal objects are detected as equal. + """ + assert cls("Test", 1) == cls("test", 1) + assert cls("Test", 1) != cls("test", 2) + assert not (cls("Test", 1) != cls("test", 1)) + assert not (cls("Test", 1) == cls("test", 2)) + + @pytest.mark.parametrize("cls", [EqC, EqCSlots]) + def test_unequal_same_class(self, cls): + """ + Unequal objects of correct type are detected as unequal. + """ + assert cls(1, 2) != cls(2, 1) + assert not (cls(1, 2) == cls(2, 1)) + + @pytest.mark.parametrize("cls", [EqCallableC, EqCallableCSlots]) + def test_unequal_same_class_callable(self, cls): + """ + Unequal objects of correct type are detected as unequal. + """ + assert cls("Test", 1) != cls("foo", 2) + assert not (cls("Test", 1) == cls("foo", 2)) + + @pytest.mark.parametrize( + "cls", [EqC, EqCSlots, EqCallableC, EqCallableCSlots] + ) + def test_unequal_different_class(self, cls): + """ + Unequal objects of different type are detected even if their attributes + match. + """ + + class NotEqC(object): + a = 1 + b = 2 + + assert cls(1, 2) != NotEqC() + assert not (cls(1, 2) == NotEqC()) + + @pytest.mark.parametrize("cls", [OrderC, OrderCSlots]) + def test_lt(self, cls): + """ + __lt__ compares objects as tuples of attribute values. + """ + for a, b in [ + ((1, 2), (2, 1)), + ((1, 2), (1, 3)), + (("a", "b"), ("b", "a")), + ]: + assert cls(*a) < cls(*b) + + @pytest.mark.parametrize("cls", [OrderCallableC, OrderCallableCSlots]) + def test_lt_callable(self, cls): + """ + __lt__ compares objects as tuples of attribute values. + """ + # Note: "A" < "a" + for a, b in [ + (("test1", 1), ("Test1", 2)), + (("test0", 1), ("Test1", 1)), + ]: + assert cls(*a) < cls(*b) + + @pytest.mark.parametrize( + "cls", [OrderC, OrderCSlots, OrderCallableC, OrderCallableCSlots] + ) + def test_lt_unordable(self, cls): + """ + __lt__ returns NotImplemented if classes differ. + """ + assert NotImplemented == (cls(1, 2).__lt__(42)) + + @pytest.mark.parametrize("cls", [OrderC, OrderCSlots]) + def test_le(self, cls): + """ + __le__ compares objects as tuples of attribute values. + """ + for a, b in [ + ((1, 2), (2, 1)), + ((1, 2), (1, 3)), + ((1, 1), (1, 1)), + (("a", "b"), ("b", "a")), + (("a", "b"), ("a", "b")), + ]: + assert cls(*a) <= cls(*b) + + @pytest.mark.parametrize("cls", [OrderCallableC, OrderCallableCSlots]) + def test_le_callable(self, cls): + """ + __le__ compares objects as tuples of attribute values. + """ + # Note: "A" < "a" + for a, b in [ + (("test1", 1), ("Test1", 1)), + (("test1", 1), ("Test1", 2)), + (("test0", 1), ("Test1", 1)), + (("test0", 2), ("Test1", 1)), + ]: + assert cls(*a) <= cls(*b) + + @pytest.mark.parametrize( + "cls", [OrderC, OrderCSlots, OrderCallableC, OrderCallableCSlots] + ) + def test_le_unordable(self, cls): + """ + __le__ returns NotImplemented if classes differ. + """ + assert NotImplemented == (cls(1, 2).__le__(42)) + + @pytest.mark.parametrize("cls", [OrderC, OrderCSlots]) + def test_gt(self, cls): + """ + __gt__ compares objects as tuples of attribute values. + """ + for a, b in [ + ((2, 1), (1, 2)), + ((1, 3), (1, 2)), + (("b", "a"), ("a", "b")), + ]: + assert cls(*a) > cls(*b) + + @pytest.mark.parametrize("cls", [OrderCallableC, OrderCallableCSlots]) + def test_gt_callable(self, cls): + """ + __gt__ compares objects as tuples of attribute values. + """ + # Note: "A" < "a" + for a, b in [ + (("Test1", 2), ("test1", 1)), + (("Test1", 1), ("test0", 1)), + ]: + assert cls(*a) > cls(*b) + + @pytest.mark.parametrize( + "cls", [OrderC, OrderCSlots, OrderCallableC, OrderCallableCSlots] + ) + def test_gt_unordable(self, cls): + """ + __gt__ returns NotImplemented if classes differ. + """ + assert NotImplemented == (cls(1, 2).__gt__(42)) + + @pytest.mark.parametrize("cls", [OrderC, OrderCSlots]) + def test_ge(self, cls): + """ + __ge__ compares objects as tuples of attribute values. + """ + for a, b in [ + ((2, 1), (1, 2)), + ((1, 3), (1, 2)), + ((1, 1), (1, 1)), + (("b", "a"), ("a", "b")), + (("a", "b"), ("a", "b")), + ]: + assert cls(*a) >= cls(*b) + + @pytest.mark.parametrize("cls", [OrderCallableC, OrderCallableCSlots]) + def test_ge_callable(self, cls): + """ + __ge__ compares objects as tuples of attribute values. + """ + # Note: "A" < "a" + for a, b in [ + (("Test1", 1), ("test1", 1)), + (("Test1", 2), ("test1", 1)), + (("Test1", 1), ("test0", 1)), + (("Test1", 1), ("test0", 2)), + ]: + assert cls(*a) >= cls(*b) + + @pytest.mark.parametrize( + "cls", [OrderC, OrderCSlots, OrderCallableC, OrderCallableCSlots] + ) + def test_ge_unordable(self, cls): + """ + __ge__ returns NotImplemented if classes differ. + """ + assert NotImplemented == (cls(1, 2).__ge__(42)) + + +class TestAddRepr(object): + """ + Tests for `_add_repr`. + """ + + @pytest.mark.parametrize("slots", [True, False]) + def test_repr(self, slots): + """ + If `repr` is False, ignore that attribute. + """ + C = make_class( + "C", {"a": attr.ib(repr=False), "b": attr.ib()}, slots=slots + ) + + assert "C(b=2)" == repr(C(1, 2)) + + @pytest.mark.parametrize("cls", [ReprC, ReprCSlots]) + def test_repr_works(self, cls): + """ + repr returns a sensible value. + """ + assert "C(a=1, b=2)" == repr(cls(1, 2)) + + def test_custom_repr_works(self): + """ + repr returns a sensible value for attributes with a custom repr + callable. + """ + + def custom_repr(value): + return "foo:" + str(value) + + @attr.s + class C(object): + a = attr.ib(repr=custom_repr) + + assert "C(a=foo:1)" == repr(C(1)) + + def test_infinite_recursion(self): + """ + In the presence of a cyclic graph, repr will emit an ellipsis and not + raise an exception. + """ + + @attr.s + class Cycle(object): + value = attr.ib(default=7) + cycle = attr.ib(default=None) + + cycle = Cycle() + cycle.cycle = cycle + assert "Cycle(value=7, cycle=...)" == repr(cycle) + + def test_infinite_recursion_long_cycle(self): + """ + A cyclic graph can pass through other non-attrs objects, and repr will + still emit an ellipsis and not raise an exception. + """ + + @attr.s + class LongCycle(object): + value = attr.ib(default=14) + cycle = attr.ib(default=None) + + cycle = LongCycle() + # Ensure that the reference cycle passes through a non-attrs object. + # This demonstrates the need for a thread-local "global" ID tracker. + cycle.cycle = {"cycle": [cycle]} + assert "LongCycle(value=14, cycle={'cycle': [...]})" == repr(cycle) + + def test_underscores(self): + """ + repr does not strip underscores. + """ + + class C(object): + __attrs_attrs__ = [simple_attr("_x")] + + C = _add_repr(C) + i = C() + i._x = 42 + + assert "C(_x=42)" == repr(i) + + def test_repr_uninitialized_member(self): + """ + repr signals unset attributes + """ + C = make_class("C", {"a": attr.ib(init=False)}) + + assert "C(a=NOTHING)" == repr(C()) + + @given(add_str=booleans(), slots=booleans()) + def test_str(self, add_str, slots): + """ + If str is True, it returns the same as repr. + + This only makes sense when subclassing a class with an poor __str__ + (like Exceptions). + """ + + @attr.s(str=add_str, slots=slots) + class Error(Exception): + x = attr.ib() + + e = Error(42) + + assert (str(e) == repr(e)) is add_str + + def test_str_no_repr(self): + """ + Raises a ValueError if repr=False and str=True. + """ + with pytest.raises(ValueError) as e: + simple_class(repr=False, str=True) + + assert ( + "__str__ can only be generated if a __repr__ exists." + ) == e.value.args[0] + + +# these are for use in TestAddHash.test_cache_hash_serialization +# they need to be out here so they can be un-pickled +@attr.attrs(hash=True, cache_hash=False) +class HashCacheSerializationTestUncached(object): + foo_value = attr.ib() + + +@attr.attrs(hash=True, cache_hash=True) +class HashCacheSerializationTestCached(object): + foo_value = attr.ib() + + +@attr.attrs(slots=True, hash=True, cache_hash=True) +class HashCacheSerializationTestCachedSlots(object): + foo_value = attr.ib() + + +class IncrementingHasher(object): + def __init__(self): + self.hash_value = 100 + + def __hash__(self): + rv = self.hash_value + self.hash_value += 1 + return rv + + +class TestAddHash(object): + """ + Tests for `_add_hash`. + """ + + def test_enforces_type(self): + """ + The `hash` argument to both attrs and attrib must be None, True, or + False. + """ + exc_args = ("Invalid value for hash. Must be True, False, or None.",) + + with pytest.raises(TypeError) as e: + make_class("C", {}, hash=1), + + assert exc_args == e.value.args + + with pytest.raises(TypeError) as e: + make_class("C", {"a": attr.ib(hash=1)}), + + assert exc_args == e.value.args + + def test_enforce_no_cache_hash_without_hash(self): + """ + Ensure exception is thrown if caching the hash code is requested + but attrs is not requested to generate `__hash__`. + """ + exc_args = ( + "Invalid value for cache_hash. To use hash caching," + " hashing must be either explicitly or implicitly " + "enabled.", + ) + with pytest.raises(TypeError) as e: + make_class("C", {}, hash=False, cache_hash=True) + assert exc_args == e.value.args + + # unhashable case + with pytest.raises(TypeError) as e: + make_class( + "C", {}, hash=None, eq=True, frozen=False, cache_hash=True + ) + assert exc_args == e.value.args + + def test_enforce_no_cached_hash_without_init(self): + """ + Ensure exception is thrown if caching the hash code is requested + but attrs is not requested to generate `__init__`. + """ + exc_args = ( + "Invalid value for cache_hash. To use hash caching," + " init must be True.", + ) + with pytest.raises(TypeError) as e: + make_class("C", {}, init=False, hash=True, cache_hash=True) + assert exc_args == e.value.args + + @given(booleans(), booleans()) + def test_hash_attribute(self, slots, cache_hash): + """ + If `hash` is False on an attribute, ignore that attribute. + """ + C = make_class( + "C", + {"a": attr.ib(hash=False), "b": attr.ib()}, + slots=slots, + hash=True, + cache_hash=cache_hash, + ) + + assert hash(C(1, 2)) == hash(C(2, 2)) + + @given(booleans()) + def test_hash_attribute_mirrors_eq(self, eq): + """ + If `hash` is None, the hash generation mirrors `eq`. + """ + C = make_class("C", {"a": attr.ib(eq=eq)}, eq=True, frozen=True) + + if eq: + assert C(1) != C(2) + assert hash(C(1)) != hash(C(2)) + assert hash(C(1)) == hash(C(1)) + else: + assert C(1) == C(2) + assert hash(C(1)) == hash(C(2)) + + @given(booleans()) + def test_hash_mirrors_eq(self, eq): + """ + If `hash` is None, the hash generation mirrors `eq`. + """ + C = make_class("C", {"a": attr.ib()}, eq=eq, frozen=True) + + i = C(1) + + assert i == i + assert hash(i) == hash(i) + + if eq: + assert C(1) == C(1) + assert hash(C(1)) == hash(C(1)) + else: + assert C(1) != C(1) + assert hash(C(1)) != hash(C(1)) + + @pytest.mark.parametrize( + "cls", + [ + HashC, + HashCSlots, + HashCCached, + HashCSlotsCached, + HashCFrozenNotSlotsCached, + ], + ) + def test_hash_works(self, cls): + """ + __hash__ returns different hashes for different values. + """ + a = cls(1, 2) + b = cls(1, 1) + assert hash(a) != hash(b) + # perform the test again to test the pre-cached path through + # __hash__ for the cached-hash versions + assert hash(a) != hash(b) + + def test_hash_default(self): + """ + Classes are not hashable by default. + """ + C = make_class("C", {}) + + with pytest.raises(TypeError) as e: + hash(C()) + + assert e.value.args[0] in ( + "'C' objects are unhashable", # PyPy + "unhashable type: 'C'", # CPython + ) + + def test_cache_hashing(self): + """ + Ensure that hash computation if cached if and only if requested + """ + + class HashCounter: + """ + A class for testing which counts how many times its hash + has been requested + """ + + def __init__(self): + self.times_hash_called = 0 + + def __hash__(self): + self.times_hash_called += 1 + return 12345 + + Uncached = make_class( + "Uncached", + {"hash_counter": attr.ib(factory=HashCounter)}, + hash=True, + cache_hash=False, + ) + Cached = make_class( + "Cached", + {"hash_counter": attr.ib(factory=HashCounter)}, + hash=True, + cache_hash=True, + ) + + uncached_instance = Uncached() + cached_instance = Cached() + + hash(uncached_instance) + hash(uncached_instance) + hash(cached_instance) + hash(cached_instance) + + assert 2 == uncached_instance.hash_counter.times_hash_called + assert 1 == cached_instance.hash_counter.times_hash_called + + @pytest.mark.parametrize("cache_hash", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + @pytest.mark.parametrize("slots", [True, False]) + def test_copy_hash_cleared(self, cache_hash, frozen, slots): + """ + Test that the default hash is recalculated after a copy operation. + """ + + kwargs = dict(frozen=frozen, slots=slots, cache_hash=cache_hash) + + # Give it an explicit hash if we don't have an implicit one + if not frozen: + kwargs["hash"] = True + + @attr.s(**kwargs) + class C(object): + x = attr.ib() + + a = C(IncrementingHasher()) + # Ensure that any hash cache would be calculated before copy + orig_hash = hash(a) + b = copy.deepcopy(a) + + if kwargs["cache_hash"]: + # For cache_hash classes, this call is cached + assert orig_hash == hash(a) + + assert orig_hash != hash(b) + + @pytest.mark.parametrize( + "klass,cached", + [ + (HashCacheSerializationTestUncached, False), + (HashCacheSerializationTestCached, True), + (HashCacheSerializationTestCachedSlots, True), + ], + ) + def test_cache_hash_serialization_hash_cleared(self, klass, cached): + """ + Tests that the hash cache is cleared on deserialization to fix + https://github.com/python-attrs/attrs/issues/482 . + + This test is intended to guard against a stale hash code surviving + across serialization (which may cause problems when the hash value + is different in different interpreters). + """ + + obj = klass(IncrementingHasher()) + original_hash = hash(obj) + obj_rt = self._roundtrip_pickle(obj) + + if cached: + assert original_hash == hash(obj) + + assert original_hash != hash(obj_rt) + + @pytest.mark.parametrize("frozen", [True, False]) + def test_copy_two_arg_reduce(self, frozen): + """ + If __getstate__ returns None, the tuple returned by object.__reduce__ + won't contain the state dictionary; this test ensures that the custom + __reduce__ generated when cache_hash=True works in that case. + """ + + @attr.s(frozen=frozen, cache_hash=True, hash=True) + class C(object): + x = attr.ib() + + def __getstate__(self): + return None + + # By the nature of this test it doesn't really create an object that's + # in a valid state - it basically does the equivalent of + # `object.__new__(C)`, so it doesn't make much sense to assert anything + # about the result of the copy. This test will just check that it + # doesn't raise an *error*. + copy.deepcopy(C(1)) + + def _roundtrip_pickle(self, obj): + pickle_str = pickle.dumps(obj) + return pickle.loads(pickle_str) + + +class TestAddInit(object): + """ + Tests for `_add_init`. + """ + + @given(booleans(), booleans()) + def test_init(self, slots, frozen): + """ + If `init` is False, ignore that attribute. + """ + C = make_class( + "C", + {"a": attr.ib(init=False), "b": attr.ib()}, + slots=slots, + frozen=frozen, + ) + with pytest.raises(TypeError) as e: + C(a=1, b=2) + + assert e.value.args[0].endswith( + "__init__() got an unexpected keyword argument 'a'" + ) + + @given(booleans(), booleans()) + def test_no_init_default(self, slots, frozen): + """ + If `init` is False but a Factory is specified, don't allow passing that + argument but initialize it anyway. + """ + C = make_class( + "C", + { + "_a": attr.ib(init=False, default=42), + "_b": attr.ib(init=False, default=Factory(list)), + "c": attr.ib(), + }, + slots=slots, + frozen=frozen, + ) + with pytest.raises(TypeError): + C(a=1, c=2) + with pytest.raises(TypeError): + C(b=1, c=2) + + i = C(23) + assert (42, [], 23) == (i._a, i._b, i.c) + + @given(booleans(), booleans()) + def test_no_init_order(self, slots, frozen): + """ + If an attribute is `init=False`, it's legal to come after a mandatory + attribute. + """ + make_class( + "C", + {"a": attr.ib(default=Factory(list)), "b": attr.ib(init=False)}, + slots=slots, + frozen=frozen, + ) + + def test_sets_attributes(self): + """ + The attributes are initialized using the passed keywords. + """ + obj = InitC(a=1, b=2) + assert 1 == obj.a + assert 2 == obj.b + + def test_default(self): + """ + If a default value is present, it's used as fallback. + """ + + class C(object): + __attrs_attrs__ = [ + simple_attr(name="a", default=2), + simple_attr(name="b", default="hallo"), + simple_attr(name="c", default=None), + ] + + C = _add_init(C, False) + i = C() + assert 2 == i.a + assert "hallo" == i.b + assert None is i.c + + def test_factory(self): + """ + If a default factory is present, it's used as fallback. + """ + + class D(object): + pass + + class C(object): + __attrs_attrs__ = [ + simple_attr(name="a", default=Factory(list)), + simple_attr(name="b", default=Factory(D)), + ] + + C = _add_init(C, False) + i = C() + + assert [] == i.a + assert isinstance(i.b, D) + + def test_validator(self): + """ + If a validator is passed, call it with the preliminary instance, the + Attribute, and the argument. + """ + + class VException(Exception): + pass + + def raiser(*args): + raise VException(*args) + + C = make_class("C", {"a": attr.ib("a", validator=raiser)}) + with pytest.raises(VException) as e: + C(42) + + assert (fields(C).a, 42) == e.value.args[1:] + assert isinstance(e.value.args[0], C) + + def test_validator_slots(self): + """ + If a validator is passed, call it with the preliminary instance, the + Attribute, and the argument. + """ + + class VException(Exception): + pass + + def raiser(*args): + raise VException(*args) + + C = make_class("C", {"a": attr.ib("a", validator=raiser)}, slots=True) + with pytest.raises(VException) as e: + C(42) + + assert (fields(C)[0], 42) == e.value.args[1:] + assert isinstance(e.value.args[0], C) + + @given(booleans()) + def test_validator_others(self, slots): + """ + Does not interfere when setting non-attrs attributes. + """ + C = make_class( + "C", {"a": attr.ib("a", validator=instance_of(int))}, slots=slots + ) + i = C(1) + + assert 1 == i.a + + if not slots: + i.b = "foo" + assert "foo" == i.b + else: + with pytest.raises(AttributeError): + i.b = "foo" + + def test_underscores(self): + """ + The argument names in `__init__` are without leading and trailing + underscores. + """ + + class C(object): + __attrs_attrs__ = [simple_attr("_private")] + + C = _add_init(C, False) + i = C(private=42) + assert 42 == i._private + + +class TestNothing(object): + """ + Tests for `_Nothing`. + """ + + def test_copy(self): + """ + __copy__ returns the same object. + """ + n = _Nothing() + assert n is copy.copy(n) + + def test_deepcopy(self): + """ + __deepcopy__ returns the same object. + """ + n = _Nothing() + assert n is copy.deepcopy(n) + + def test_eq(self): + """ + All instances are equal. + """ + assert _Nothing() == _Nothing() == NOTHING + assert not (_Nothing() != _Nothing()) + assert 1 != _Nothing() + + def test_false(self): + """ + NOTHING evaluates as falsey. + """ + assert not NOTHING + assert False is bool(NOTHING) + + +@attr.s(hash=True, order=True) +class C(object): + pass + + +# Store this class so that we recreate it. +OriginalC = C + + +@attr.s(hash=True, order=True) +class C(object): + pass + + +CopyC = C + + +@attr.s(hash=True, order=True) +class C(object): + """A different class, to generate different methods.""" + + a = attr.ib() + + +class TestFilenames(object): + def test_filenames(self): + """ + The created dunder methods have a "consistent" filename. + """ + assert ( + OriginalC.__init__.__code__.co_filename + == "" + ) + assert ( + OriginalC.__eq__.__code__.co_filename + == "" + ) + assert ( + OriginalC.__hash__.__code__.co_filename + == "" + ) + assert ( + CopyC.__init__.__code__.co_filename + == "" + ) + assert ( + CopyC.__eq__.__code__.co_filename + == "" + ) + assert ( + CopyC.__hash__.__code__.co_filename + == "" + ) + assert ( + C.__init__.__code__.co_filename + == "" + ) + assert ( + C.__eq__.__code__.co_filename + == "" + ) + assert ( + C.__hash__.__code__.co_filename + == "" + ) diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_filters.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_filters.py new file mode 100644 index 0000000000..d1ec24dc6c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_filters.py @@ -0,0 +1,111 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for `attr.filters`. +""" + +from __future__ import absolute_import, division, print_function + +import pytest + +import attr + +from attr import fields +from attr.filters import _split_what, exclude, include + + +@attr.s +class C(object): + a = attr.ib() + b = attr.ib() + + +class TestSplitWhat(object): + """ + Tests for `_split_what`. + """ + + def test_splits(self): + """ + Splits correctly. + """ + assert ( + frozenset((int, str)), + frozenset((fields(C).a,)), + ) == _split_what((str, fields(C).a, int)) + + +class TestInclude(object): + """ + Tests for `include`. + """ + + @pytest.mark.parametrize( + "incl,value", + [ + ((int,), 42), + ((str,), "hello"), + ((str, fields(C).a), 42), + ((str, fields(C).b), "hello"), + ], + ) + def test_allow(self, incl, value): + """ + Return True if a class or attribute is included. + """ + i = include(*incl) + assert i(fields(C).a, value) is True + + @pytest.mark.parametrize( + "incl,value", + [ + ((str,), 42), + ((int,), "hello"), + ((str, fields(C).b), 42), + ((int, fields(C).b), "hello"), + ], + ) + def test_drop_class(self, incl, value): + """ + Return False on non-included classes and attributes. + """ + i = include(*incl) + assert i(fields(C).a, value) is False + + +class TestExclude(object): + """ + Tests for `exclude`. + """ + + @pytest.mark.parametrize( + "excl,value", + [ + ((str,), 42), + ((int,), "hello"), + ((str, fields(C).b), 42), + ((int, fields(C).b), "hello"), + ], + ) + def test_allow(self, excl, value): + """ + Return True if class or attribute is not excluded. + """ + e = exclude(*excl) + assert e(fields(C).a, value) is True + + @pytest.mark.parametrize( + "excl,value", + [ + ((int,), 42), + ((str,), "hello"), + ((str, fields(C).a), 42), + ((str, fields(C).b), "hello"), + ], + ) + def test_drop_class(self, excl, value): + """ + Return True on non-excluded classes and attributes. + """ + e = exclude(*excl) + assert e(fields(C).a, value) is False diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_funcs.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_funcs.py new file mode 100644 index 0000000000..4490ed815a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_funcs.py @@ -0,0 +1,680 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for `attr._funcs`. +""" + +from __future__ import absolute_import, division, print_function + +from collections import OrderedDict + +import pytest + +from hypothesis import assume, given +from hypothesis import strategies as st + +import attr + +from attr import asdict, assoc, astuple, evolve, fields, has +from attr._compat import TYPE, Mapping, Sequence, ordered_dict +from attr.exceptions import AttrsAttributeNotFoundError +from attr.validators import instance_of + +from .strategies import nested_classes, simple_classes + + +MAPPING_TYPES = (dict, OrderedDict) +SEQUENCE_TYPES = (list, tuple) + + +@pytest.fixture(scope="session", name="C") +def _C(): + """ + Return a simple but fully featured attrs class with an x and a y attribute. + """ + import attr + + @attr.s + class C(object): + x = attr.ib() + y = attr.ib() + + return C + + +class TestAsDict(object): + """ + Tests for `asdict`. + """ + + @given(st.sampled_from(MAPPING_TYPES)) + def test_shallow(self, C, dict_factory): + """ + Shallow asdict returns correct dict. + """ + assert {"x": 1, "y": 2} == asdict( + C(x=1, y=2), False, dict_factory=dict_factory + ) + + @given(st.sampled_from(MAPPING_TYPES)) + def test_recurse(self, C, dict_class): + """ + Deep asdict returns correct dict. + """ + assert {"x": {"x": 1, "y": 2}, "y": {"x": 3, "y": 4}} == asdict( + C(C(1, 2), C(3, 4)), dict_factory=dict_class + ) + + def test_nested_lists(self, C): + """ + Test unstructuring deeply nested lists. + """ + inner = C(1, 2) + outer = C([[inner]], None) + + assert {"x": [[{"x": 1, "y": 2}]], "y": None} == asdict(outer) + + def test_nested_dicts(self, C): + """ + Test unstructuring deeply nested dictionaries. + """ + inner = C(1, 2) + outer = C({1: {2: inner}}, None) + + assert {"x": {1: {2: {"x": 1, "y": 2}}}, "y": None} == asdict(outer) + + @given(nested_classes, st.sampled_from(MAPPING_TYPES)) + def test_recurse_property(self, cls, dict_class): + """ + Property tests for recursive asdict. + """ + obj = cls() + obj_dict = asdict(obj, dict_factory=dict_class) + + def assert_proper_dict_class(obj, obj_dict): + assert isinstance(obj_dict, dict_class) + + for field in fields(obj.__class__): + field_val = getattr(obj, field.name) + if has(field_val.__class__): + # This field holds a class, recurse the assertions. + assert_proper_dict_class(field_val, obj_dict[field.name]) + elif isinstance(field_val, Sequence): + dict_val = obj_dict[field.name] + for item, item_dict in zip(field_val, dict_val): + if has(item.__class__): + assert_proper_dict_class(item, item_dict) + elif isinstance(field_val, Mapping): + # This field holds a dictionary. + assert isinstance(obj_dict[field.name], dict_class) + + for key, val in field_val.items(): + if has(val.__class__): + assert_proper_dict_class( + val, obj_dict[field.name][key] + ) + + assert_proper_dict_class(obj, obj_dict) + + @given(st.sampled_from(MAPPING_TYPES)) + def test_filter(self, C, dict_factory): + """ + Attributes that are supposed to be skipped are skipped. + """ + assert {"x": {"x": 1}} == asdict( + C(C(1, 2), C(3, 4)), + filter=lambda a, v: a.name != "y", + dict_factory=dict_factory, + ) + + @given(container=st.sampled_from(SEQUENCE_TYPES)) + def test_lists_tuples(self, container, C): + """ + If recurse is True, also recurse into lists. + """ + assert { + "x": 1, + "y": [{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"], + } == asdict(C(1, container([C(2, 3), C(4, 5), "a"]))) + + @given(container=st.sampled_from(SEQUENCE_TYPES)) + def test_lists_tuples_retain_type(self, container, C): + """ + If recurse and retain_collection_types are True, also recurse + into lists and do not convert them into list. + """ + assert { + "x": 1, + "y": container([{"x": 2, "y": 3}, {"x": 4, "y": 5}, "a"]), + } == asdict( + C(1, container([C(2, 3), C(4, 5), "a"])), + retain_collection_types=True, + ) + + @given(set_type=st.sampled_from((set, frozenset))) + def test_sets_no_retain(self, C, set_type): + """ + Set types are converted to lists if retain_collection_types=False. + """ + d = asdict( + C(1, set_type((1, 2, 3))), + retain_collection_types=False, + recurse=True, + ) + + assert {"x": 1, "y": [1, 2, 3]} == d + + @given(st.sampled_from(MAPPING_TYPES)) + def test_dicts(self, C, dict_factory): + """ + If recurse is True, also recurse into dicts. + """ + res = asdict(C(1, {"a": C(4, 5)}), dict_factory=dict_factory) + + assert {"x": 1, "y": {"a": {"x": 4, "y": 5}}} == res + assert isinstance(res, dict_factory) + + @given(simple_classes(private_attrs=False), st.sampled_from(MAPPING_TYPES)) + def test_roundtrip(self, cls, dict_class): + """ + Test dumping to dicts and back for Hypothesis-generated classes. + + Private attributes don't round-trip (the attribute name is different + than the initializer argument). + """ + instance = cls() + dict_instance = asdict(instance, dict_factory=dict_class) + + assert isinstance(dict_instance, dict_class) + + roundtrip_instance = cls(**dict_instance) + + assert instance == roundtrip_instance + + @given(simple_classes()) + def test_asdict_preserve_order(self, cls): + """ + Field order should be preserved when dumping to an ordered_dict. + """ + instance = cls() + dict_instance = asdict(instance, dict_factory=ordered_dict) + + assert [a.name for a in fields(cls)] == list(dict_instance.keys()) + + def test_retain_keys_are_tuples(self): + """ + retain_collect_types also retains keys. + """ + + @attr.s + class A(object): + a = attr.ib() + + instance = A({(1,): 1}) + + assert {"a": {(1,): 1}} == attr.asdict( + instance, retain_collection_types=True + ) + + def test_tuple_keys(self): + """ + If a key is collection type, retain_collection_types is False, + the key is serialized as a tuple. + + See #646 + """ + + @attr.s + class A(object): + a = attr.ib() + + instance = A({(1,): 1}) + + assert {"a": {(1,): 1}} == attr.asdict(instance) + + +class TestAsTuple(object): + """ + Tests for `astuple`. + """ + + @given(st.sampled_from(SEQUENCE_TYPES)) + def test_shallow(self, C, tuple_factory): + """ + Shallow astuple returns correct dict. + """ + assert tuple_factory([1, 2]) == astuple( + C(x=1, y=2), False, tuple_factory=tuple_factory + ) + + @given(st.sampled_from(SEQUENCE_TYPES)) + def test_recurse(self, C, tuple_factory): + """ + Deep astuple returns correct tuple. + """ + assert tuple_factory( + [tuple_factory([1, 2]), tuple_factory([3, 4])] + ) == astuple(C(C(1, 2), C(3, 4)), tuple_factory=tuple_factory) + + @given(nested_classes, st.sampled_from(SEQUENCE_TYPES)) + def test_recurse_property(self, cls, tuple_class): + """ + Property tests for recursive astuple. + """ + obj = cls() + obj_tuple = astuple(obj, tuple_factory=tuple_class) + + def assert_proper_tuple_class(obj, obj_tuple): + assert isinstance(obj_tuple, tuple_class) + for index, field in enumerate(fields(obj.__class__)): + field_val = getattr(obj, field.name) + if has(field_val.__class__): + # This field holds a class, recurse the assertions. + assert_proper_tuple_class(field_val, obj_tuple[index]) + + assert_proper_tuple_class(obj, obj_tuple) + + @given(nested_classes, st.sampled_from(SEQUENCE_TYPES)) + def test_recurse_retain(self, cls, tuple_class): + """ + Property tests for asserting collection types are retained. + """ + obj = cls() + obj_tuple = astuple( + obj, tuple_factory=tuple_class, retain_collection_types=True + ) + + def assert_proper_col_class(obj, obj_tuple): + # Iterate over all attributes, and if they are lists or mappings + # in the original, assert they are the same class in the dumped. + for index, field in enumerate(fields(obj.__class__)): + field_val = getattr(obj, field.name) + if has(field_val.__class__): + # This field holds a class, recurse the assertions. + assert_proper_col_class(field_val, obj_tuple[index]) + elif isinstance(field_val, (list, tuple)): + # This field holds a sequence of something. + expected_type = type(obj_tuple[index]) + assert type(field_val) is expected_type + for obj_e, obj_tuple_e in zip(field_val, obj_tuple[index]): + if has(obj_e.__class__): + assert_proper_col_class(obj_e, obj_tuple_e) + elif isinstance(field_val, dict): + orig = field_val + tupled = obj_tuple[index] + assert type(orig) is type(tupled) + for obj_e, obj_tuple_e in zip( + orig.items(), tupled.items() + ): + if has(obj_e[0].__class__): # Dict key + assert_proper_col_class(obj_e[0], obj_tuple_e[0]) + if has(obj_e[1].__class__): # Dict value + assert_proper_col_class(obj_e[1], obj_tuple_e[1]) + + assert_proper_col_class(obj, obj_tuple) + + @given(st.sampled_from(SEQUENCE_TYPES)) + def test_filter(self, C, tuple_factory): + """ + Attributes that are supposed to be skipped are skipped. + """ + assert tuple_factory([tuple_factory([1])]) == astuple( + C(C(1, 2), C(3, 4)), + filter=lambda a, v: a.name != "y", + tuple_factory=tuple_factory, + ) + + @given(container=st.sampled_from(SEQUENCE_TYPES)) + def test_lists_tuples(self, container, C): + """ + If recurse is True, also recurse into lists. + """ + assert (1, [(2, 3), (4, 5), "a"]) == astuple( + C(1, container([C(2, 3), C(4, 5), "a"])) + ) + + @given(st.sampled_from(SEQUENCE_TYPES)) + def test_dicts(self, C, tuple_factory): + """ + If recurse is True, also recurse into dicts. + """ + res = astuple(C(1, {"a": C(4, 5)}), tuple_factory=tuple_factory) + assert tuple_factory([1, {"a": tuple_factory([4, 5])}]) == res + assert isinstance(res, tuple_factory) + + @given(container=st.sampled_from(SEQUENCE_TYPES)) + def test_lists_tuples_retain_type(self, container, C): + """ + If recurse and retain_collection_types are True, also recurse + into lists and do not convert them into list. + """ + assert (1, container([(2, 3), (4, 5), "a"])) == astuple( + C(1, container([C(2, 3), C(4, 5), "a"])), + retain_collection_types=True, + ) + + @given(container=st.sampled_from(MAPPING_TYPES)) + def test_dicts_retain_type(self, container, C): + """ + If recurse and retain_collection_types are True, also recurse + into lists and do not convert them into list. + """ + assert (1, container({"a": (4, 5)})) == astuple( + C(1, container({"a": C(4, 5)})), retain_collection_types=True + ) + + @given(simple_classes(), st.sampled_from(SEQUENCE_TYPES)) + def test_roundtrip(self, cls, tuple_class): + """ + Test dumping to tuple and back for Hypothesis-generated classes. + """ + instance = cls() + tuple_instance = astuple(instance, tuple_factory=tuple_class) + + assert isinstance(tuple_instance, tuple_class) + + roundtrip_instance = cls(*tuple_instance) + + assert instance == roundtrip_instance + + @given(set_type=st.sampled_from((set, frozenset))) + def test_sets_no_retain(self, C, set_type): + """ + Set types are converted to lists if retain_collection_types=False. + """ + d = astuple( + C(1, set_type((1, 2, 3))), + retain_collection_types=False, + recurse=True, + ) + + assert (1, [1, 2, 3]) == d + + +class TestHas(object): + """ + Tests for `has`. + """ + + def test_positive(self, C): + """ + Returns `True` on decorated classes. + """ + assert has(C) + + def test_positive_empty(self): + """ + Returns `True` on decorated classes even if there are no attributes. + """ + + @attr.s + class D(object): + pass + + assert has(D) + + def test_negative(self): + """ + Returns `False` on non-decorated classes. + """ + assert not has(object) + + +class TestAssoc(object): + """ + Tests for `assoc`. + """ + + @given(slots=st.booleans(), frozen=st.booleans()) + def test_empty(self, slots, frozen): + """ + Empty classes without changes get copied. + """ + + @attr.s(slots=slots, frozen=frozen) + class C(object): + pass + + i1 = C() + with pytest.deprecated_call(): + i2 = assoc(i1) + + assert i1 is not i2 + assert i1 == i2 + + @given(simple_classes()) + def test_no_changes(self, C): + """ + No changes means a verbatim copy. + """ + i1 = C() + with pytest.deprecated_call(): + i2 = assoc(i1) + + assert i1 is not i2 + assert i1 == i2 + + @given(simple_classes(), st.data()) + def test_change(self, C, data): + """ + Changes work. + """ + # Take the first attribute, and change it. + assume(fields(C)) # Skip classes with no attributes. + field_names = [a.name for a in fields(C)] + original = C() + chosen_names = data.draw(st.sets(st.sampled_from(field_names))) + change_dict = {name: data.draw(st.integers()) for name in chosen_names} + + with pytest.deprecated_call(): + changed = assoc(original, **change_dict) + + for k, v in change_dict.items(): + assert getattr(changed, k) == v + + @given(simple_classes()) + def test_unknown(self, C): + """ + Wanting to change an unknown attribute raises an + AttrsAttributeNotFoundError. + """ + # No generated class will have a four letter attribute. + with pytest.raises( + AttrsAttributeNotFoundError + ) as e, pytest.deprecated_call(): + assoc(C(), aaaa=2) + + assert ( + "aaaa is not an attrs attribute on {cls!r}.".format(cls=C), + ) == e.value.args + + def test_frozen(self): + """ + Works on frozen classes. + """ + + @attr.s(frozen=True) + class C(object): + x = attr.ib() + y = attr.ib() + + with pytest.deprecated_call(): + assert C(3, 2) == assoc(C(1, 2), x=3) + + def test_warning(self): + """ + DeprecationWarning points to the correct file. + """ + + @attr.s + class C(object): + x = attr.ib() + + with pytest.warns(DeprecationWarning) as wi: + assert C(2) == assoc(C(1), x=2) + + assert __file__ == wi.list[0].filename + + +class TestEvolve(object): + """ + Tests for `evolve`. + """ + + @given(slots=st.booleans(), frozen=st.booleans()) + def test_empty(self, slots, frozen): + """ + Empty classes without changes get copied. + """ + + @attr.s(slots=slots, frozen=frozen) + class C(object): + pass + + i1 = C() + i2 = evolve(i1) + + assert i1 is not i2 + assert i1 == i2 + + @given(simple_classes()) + def test_no_changes(self, C): + """ + No changes means a verbatim copy. + """ + i1 = C() + i2 = evolve(i1) + + assert i1 is not i2 + assert i1 == i2 + + @given(simple_classes(), st.data()) + def test_change(self, C, data): + """ + Changes work. + """ + # Take the first attribute, and change it. + assume(fields(C)) # Skip classes with no attributes. + field_names = [a.name for a in fields(C)] + original = C() + chosen_names = data.draw(st.sets(st.sampled_from(field_names))) + # We pay special attention to private attributes, they should behave + # like in `__init__`. + change_dict = { + name.replace("_", ""): data.draw(st.integers()) + for name in chosen_names + } + changed = evolve(original, **change_dict) + for name in chosen_names: + assert getattr(changed, name) == change_dict[name.replace("_", "")] + + @given(simple_classes()) + def test_unknown(self, C): + """ + Wanting to change an unknown attribute raises an + AttrsAttributeNotFoundError. + """ + # No generated class will have a four letter attribute. + with pytest.raises(TypeError) as e: + evolve(C(), aaaa=2) + + if hasattr(C, "__attrs_init__"): + expected = ( + "__attrs_init__() got an unexpected keyword argument 'aaaa'" + ) + else: + expected = "__init__() got an unexpected keyword argument 'aaaa'" + + assert e.value.args[0].endswith(expected) + + def test_validator_failure(self): + """ + TypeError isn't swallowed when validation fails within evolve. + """ + + @attr.s + class C(object): + a = attr.ib(validator=instance_of(int)) + + with pytest.raises(TypeError) as e: + evolve(C(a=1), a="some string") + m = e.value.args[0] + + assert m.startswith("'a' must be <{type} 'int'>".format(type=TYPE)) + + def test_private(self): + """ + evolve() acts as `__init__` with regards to private attributes. + """ + + @attr.s + class C(object): + _a = attr.ib() + + assert evolve(C(1), a=2)._a == 2 + + with pytest.raises(TypeError): + evolve(C(1), _a=2) + + with pytest.raises(TypeError): + evolve(C(1), a=3, _a=2) + + def test_non_init_attrs(self): + """ + evolve() handles `init=False` attributes. + """ + + @attr.s + class C(object): + a = attr.ib() + b = attr.ib(init=False, default=0) + + assert evolve(C(1), a=2).a == 2 + + def test_regression_attrs_classes(self): + """ + evolve() can evolve fields that are instances of attrs classes. + + Regression test for #804 + """ + + @attr.s + class Cls1(object): + param1 = attr.ib() + + @attr.s + class Cls2(object): + param2 = attr.ib() + + obj2a = Cls2(param2="a") + obj2b = Cls2(param2="b") + + obj1a = Cls1(param1=obj2a) + + assert Cls1(param1=Cls2(param2="b")) == attr.evolve( + obj1a, param1=obj2b + ) + + def test_dicts(self): + """ + evolve() can replace an attrs class instance with a dict. + + See #806 + """ + + @attr.s + class Cls1(object): + param1 = attr.ib() + + @attr.s + class Cls2(object): + param2 = attr.ib() + + obj2a = Cls2(param2="a") + obj2b = {"foo": 42, "param2": 42} + + obj1a = Cls1(param1=obj2a) + + assert Cls1({"foo": 42, "param2": 42}) == attr.evolve( + obj1a, param1=obj2b + ) diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_functional.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_functional.py new file mode 100644 index 0000000000..9b6a27e2f4 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_functional.py @@ -0,0 +1,790 @@ +# SPDX-License-Identifier: MIT + +""" +End-to-end tests. +""" + +from __future__ import absolute_import, division, print_function + +import inspect +import pickle + +from copy import deepcopy + +import pytest +import six + +from hypothesis import assume, given +from hypothesis.strategies import booleans + +import attr + +from attr._compat import PY2, PY36, TYPE +from attr._make import NOTHING, Attribute +from attr.exceptions import FrozenInstanceError + +from .strategies import optional_bool + + +@attr.s +class C1(object): + x = attr.ib(validator=attr.validators.instance_of(int)) + y = attr.ib() + + +@attr.s(slots=True) +class C1Slots(object): + x = attr.ib(validator=attr.validators.instance_of(int)) + y = attr.ib() + + +foo = None + + +@attr.s() +class C2(object): + x = attr.ib(default=foo) + y = attr.ib(default=attr.Factory(list)) + + +@attr.s(slots=True) +class C2Slots(object): + x = attr.ib(default=foo) + y = attr.ib(default=attr.Factory(list)) + + +@attr.s +class Base(object): + x = attr.ib() + + def meth(self): + return self.x + + +@attr.s(slots=True) +class BaseSlots(object): + x = attr.ib() + + def meth(self): + return self.x + + +@attr.s +class Sub(Base): + y = attr.ib() + + +@attr.s(slots=True) +class SubSlots(BaseSlots): + y = attr.ib() + + +@attr.s(frozen=True, slots=True) +class Frozen(object): + x = attr.ib() + + +@attr.s +class SubFrozen(Frozen): + y = attr.ib() + + +@attr.s(frozen=True, slots=False) +class FrozenNoSlots(object): + x = attr.ib() + + +class Meta(type): + pass + + +@attr.s +@six.add_metaclass(Meta) +class WithMeta(object): + pass + + +@attr.s(slots=True) +@six.add_metaclass(Meta) +class WithMetaSlots(object): + pass + + +FromMakeClass = attr.make_class("FromMakeClass", ["x"]) + + +class TestFunctional(object): + """ + Functional tests. + """ + + @pytest.mark.parametrize("cls", [C2, C2Slots]) + def test_fields(self, cls): + """ + `attr.fields` works. + """ + assert ( + Attribute( + name="x", + default=foo, + validator=None, + repr=True, + cmp=None, + eq=True, + order=True, + hash=None, + init=True, + inherited=False, + ), + Attribute( + name="y", + default=attr.Factory(list), + validator=None, + repr=True, + cmp=None, + eq=True, + order=True, + hash=None, + init=True, + inherited=False, + ), + ) == attr.fields(cls) + + @pytest.mark.parametrize("cls", [C1, C1Slots]) + def test_asdict(self, cls): + """ + `attr.asdict` works. + """ + assert {"x": 1, "y": 2} == attr.asdict(cls(x=1, y=2)) + + @pytest.mark.parametrize("cls", [C1, C1Slots]) + def test_validator(self, cls): + """ + `instance_of` raises `TypeError` on type mismatch. + """ + with pytest.raises(TypeError) as e: + cls("1", 2) + + # Using C1 explicitly, since slotted classes don't support this. + assert ( + "'x' must be <{type} 'int'> (got '1' that is a <{type} " + "'str'>).".format(type=TYPE), + attr.fields(C1).x, + int, + "1", + ) == e.value.args + + @given(booleans()) + def test_renaming(self, slots): + """ + Private members are renamed but only in `__init__`. + """ + + @attr.s(slots=slots) + class C3(object): + _x = attr.ib() + + assert "C3(_x=1)" == repr(C3(x=1)) + + @given(booleans(), booleans()) + def test_programmatic(self, slots, frozen): + """ + `attr.make_class` works. + """ + PC = attr.make_class("PC", ["a", "b"], slots=slots, frozen=frozen) + + assert ( + Attribute( + name="a", + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=True, + hash=None, + init=True, + inherited=False, + ), + Attribute( + name="b", + default=NOTHING, + validator=None, + repr=True, + cmp=None, + eq=True, + order=True, + hash=None, + init=True, + inherited=False, + ), + ) == attr.fields(PC) + + @pytest.mark.parametrize("cls", [Sub, SubSlots]) + def test_subclassing_with_extra_attrs(self, cls): + """ + Subclassing (where the subclass has extra attrs) does what you'd hope + for. + """ + obj = object() + i = cls(x=obj, y=2) + assert i.x is i.meth() is obj + assert i.y == 2 + if cls is Sub: + assert "Sub(x={obj}, y=2)".format(obj=obj) == repr(i) + else: + assert "SubSlots(x={obj}, y=2)".format(obj=obj) == repr(i) + + @pytest.mark.parametrize("base", [Base, BaseSlots]) + def test_subclass_without_extra_attrs(self, base): + """ + Subclassing (where the subclass does not have extra attrs) still + behaves the same as a subclass with extra attrs. + """ + + class Sub2(base): + pass + + obj = object() + i = Sub2(x=obj) + assert i.x is i.meth() is obj + assert "Sub2(x={obj})".format(obj=obj) == repr(i) + + @pytest.mark.parametrize( + "frozen_class", + [ + Frozen, # has slots=True + attr.make_class("FrozenToo", ["x"], slots=False, frozen=True), + ], + ) + def test_frozen_instance(self, frozen_class): + """ + Frozen instances can't be modified (easily). + """ + frozen = frozen_class(1) + + with pytest.raises(FrozenInstanceError) as e: + frozen.x = 2 + + with pytest.raises(FrozenInstanceError) as e: + del frozen.x + + assert e.value.args[0] == "can't set attribute" + assert 1 == frozen.x + + @pytest.mark.parametrize( + "cls", + [ + C1, + C1Slots, + C2, + C2Slots, + Base, + BaseSlots, + Sub, + SubSlots, + Frozen, + FrozenNoSlots, + FromMakeClass, + ], + ) + @pytest.mark.parametrize("protocol", range(2, pickle.HIGHEST_PROTOCOL + 1)) + def test_pickle_attributes(self, cls, protocol): + """ + Pickling/un-pickling of Attribute instances works. + """ + for attribute in attr.fields(cls): + assert attribute == pickle.loads(pickle.dumps(attribute, protocol)) + + @pytest.mark.parametrize( + "cls", + [ + C1, + C1Slots, + C2, + C2Slots, + Base, + BaseSlots, + Sub, + SubSlots, + Frozen, + FrozenNoSlots, + FromMakeClass, + ], + ) + @pytest.mark.parametrize("protocol", range(2, pickle.HIGHEST_PROTOCOL + 1)) + def test_pickle_object(self, cls, protocol): + """ + Pickle object serialization works on all kinds of attrs classes. + """ + if len(attr.fields(cls)) == 2: + obj = cls(123, 456) + else: + obj = cls(123) + + assert repr(obj) == repr(pickle.loads(pickle.dumps(obj, protocol))) + + def test_subclassing_frozen_gives_frozen(self): + """ + The frozen-ness of classes is inherited. Subclasses of frozen classes + are also frozen and can be instantiated. + """ + i = SubFrozen("foo", "bar") + + assert i.x == "foo" + assert i.y == "bar" + + with pytest.raises(FrozenInstanceError): + i.x = "baz" + + @pytest.mark.parametrize("cls", [WithMeta, WithMetaSlots]) + def test_metaclass_preserved(self, cls): + """ + Metaclass data is preserved. + """ + assert Meta == type(cls) + + def test_default_decorator(self): + """ + Default decorator sets the default and the respective method gets + called. + """ + + @attr.s + class C(object): + x = attr.ib(default=1) + y = attr.ib() + + @y.default + def compute(self): + return self.x + 1 + + assert C(1, 2) == C() + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + @pytest.mark.parametrize("weakref_slot", [True, False]) + def test_attrib_overwrite(self, slots, frozen, weakref_slot): + """ + Subclasses can overwrite attributes of their base class. + """ + + @attr.s(slots=slots, frozen=frozen, weakref_slot=weakref_slot) + class SubOverwrite(Base): + x = attr.ib(default=attr.Factory(list)) + + assert SubOverwrite([]) == SubOverwrite() + + def test_dict_patch_class(self): + """ + dict-classes are never replaced. + """ + + class C(object): + x = attr.ib() + + C_new = attr.s(C) + + assert C_new is C + + def test_hash_by_id(self): + """ + With dict classes, hashing by ID is active for hash=False even on + Python 3. This is incorrect behavior but we have to retain it for + backward compatibility. + """ + + @attr.s(hash=False) + class HashByIDBackwardCompat(object): + x = attr.ib() + + assert hash(HashByIDBackwardCompat(1)) != hash( + HashByIDBackwardCompat(1) + ) + + @attr.s(hash=False, eq=False) + class HashByID(object): + x = attr.ib() + + assert hash(HashByID(1)) != hash(HashByID(1)) + + @attr.s(hash=True) + class HashByValues(object): + x = attr.ib() + + assert hash(HashByValues(1)) == hash(HashByValues(1)) + + def test_handles_different_defaults(self): + """ + Unhashable defaults + subclassing values work. + """ + + @attr.s + class Unhashable(object): + pass + + @attr.s + class C(object): + x = attr.ib(default=Unhashable()) + + @attr.s + class D(C): + pass + + @pytest.mark.parametrize("slots", [True, False]) + def test_hash_false_eq_false(self, slots): + """ + hash=False and eq=False make a class hashable by ID. + """ + + @attr.s(hash=False, eq=False, slots=slots) + class C(object): + pass + + assert hash(C()) != hash(C()) + + @pytest.mark.parametrize("slots", [True, False]) + def test_eq_false(self, slots): + """ + eq=False makes a class hashable by ID. + """ + + @attr.s(eq=False, slots=slots) + class C(object): + pass + + # Ensure both objects live long enough such that their ids/hashes + # can't be recycled. Thanks to Ask Hjorth Larsen for pointing that + # out. + c1 = C() + c2 = C() + + assert hash(c1) != hash(c2) + + def test_overwrite_base(self): + """ + Base classes can overwrite each other and the attributes are added + in the order they are defined. + """ + + @attr.s + class C(object): + c = attr.ib(default=100) + x = attr.ib(default=1) + b = attr.ib(default=23) + + @attr.s + class D(C): + a = attr.ib(default=42) + x = attr.ib(default=2) + d = attr.ib(default=3.14) + + @attr.s + class E(D): + y = attr.ib(default=3) + z = attr.ib(default=4) + + assert "E(c=100, b=23, a=42, x=2, d=3.14, y=3, z=4)" == repr(E()) + + @pytest.mark.parametrize("base_slots", [True, False]) + @pytest.mark.parametrize("sub_slots", [True, False]) + @pytest.mark.parametrize("base_frozen", [True, False]) + @pytest.mark.parametrize("sub_frozen", [True, False]) + @pytest.mark.parametrize("base_weakref_slot", [True, False]) + @pytest.mark.parametrize("sub_weakref_slot", [True, False]) + @pytest.mark.parametrize("base_converter", [True, False]) + @pytest.mark.parametrize("sub_converter", [True, False]) + def test_frozen_slots_combo( + self, + base_slots, + sub_slots, + base_frozen, + sub_frozen, + base_weakref_slot, + sub_weakref_slot, + base_converter, + sub_converter, + ): + """ + A class with a single attribute, inheriting from another class + with a single attribute. + """ + + @attr.s( + frozen=base_frozen, + slots=base_slots, + weakref_slot=base_weakref_slot, + ) + class Base(object): + a = attr.ib(converter=int if base_converter else None) + + @attr.s( + frozen=sub_frozen, slots=sub_slots, weakref_slot=sub_weakref_slot + ) + class Sub(Base): + b = attr.ib(converter=int if sub_converter else None) + + i = Sub("1", "2") + + assert i.a == (1 if base_converter else "1") + assert i.b == (2 if sub_converter else "2") + + if base_frozen or sub_frozen: + with pytest.raises(FrozenInstanceError): + i.a = "2" + + with pytest.raises(FrozenInstanceError): + i.b = "3" + + def test_tuple_class_aliasing(self): + """ + itemgetter and property are legal attribute names. + """ + + @attr.s + class C(object): + property = attr.ib() + itemgetter = attr.ib() + x = attr.ib() + + assert "property" == attr.fields(C).property.name + assert "itemgetter" == attr.fields(C).itemgetter.name + assert "x" == attr.fields(C).x.name + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_auto_exc(self, slots, frozen): + """ + Classes with auto_exc=True have a Exception-style __str__, compare and + hash by id, and store the fields additionally in self.args. + """ + + @attr.s(auto_exc=True, slots=slots, frozen=frozen) + class FooError(Exception): + x = attr.ib() + y = attr.ib(init=False, default=42) + z = attr.ib(init=False) + a = attr.ib() + + FooErrorMade = attr.make_class( + "FooErrorMade", + bases=(Exception,), + attrs={ + "x": attr.ib(), + "y": attr.ib(init=False, default=42), + "z": attr.ib(init=False), + "a": attr.ib(), + }, + auto_exc=True, + slots=slots, + frozen=frozen, + ) + + assert FooError(1, "foo") != FooError(1, "foo") + assert FooErrorMade(1, "foo") != FooErrorMade(1, "foo") + + for cls in (FooError, FooErrorMade): + with pytest.raises(cls) as ei1: + raise cls(1, "foo") + + with pytest.raises(cls) as ei2: + raise cls(1, "foo") + + e1 = ei1.value + e2 = ei2.value + + assert e1 is e1 + assert e1 == e1 + assert e2 == e2 + assert e1 != e2 + assert "(1, 'foo')" == str(e1) == str(e2) + assert (1, "foo") == e1.args == e2.args + + hash(e1) == hash(e1) + hash(e2) == hash(e2) + + if not frozen: + deepcopy(e1) + deepcopy(e2) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_auto_exc_one_attrib(self, slots, frozen): + """ + Having one attribute works with auto_exc=True. + + Easy to get wrong with tuple literals. + """ + + @attr.s(auto_exc=True, slots=slots, frozen=frozen) + class FooError(Exception): + x = attr.ib() + + FooError(1) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_eq_only(self, slots, frozen): + """ + Classes with order=False cannot be ordered. + + Python 3 throws a TypeError, in Python2 we have to check for the + absence. + """ + + @attr.s(eq=True, order=False, slots=slots, frozen=frozen) + class C(object): + x = attr.ib() + + if not PY2: + possible_errors = ( + "unorderable types: C() < C()", + "'<' not supported between instances of 'C' and 'C'", + "unorderable types: C < C", # old PyPy 3 + ) + + with pytest.raises(TypeError) as ei: + C(5) < C(6) + + assert ei.value.args[0] in possible_errors + else: + i = C(42) + for m in ("lt", "le", "gt", "ge"): + assert None is getattr(i, "__%s__" % (m,), None) + + @given(cmp=optional_bool, eq=optional_bool, order=optional_bool) + def test_cmp_deprecated_attribute(self, cmp, eq, order): + """ + Accessing Attribute.cmp raises a deprecation warning but returns True + if cmp is True, or eq and order are *both* effectively True. + """ + # These cases are invalid and raise a ValueError. + assume(cmp is None or (eq is None and order is None)) + assume(not (eq is False and order is True)) + + if cmp is not None: + rv = cmp + elif eq is True or eq is None: + rv = order is None or order is True + elif cmp is None and eq is None and order is None: + rv = True + elif cmp is None or eq is None: + rv = False + else: + pytest.fail( + "Unexpected state: cmp=%r eq=%r order=%r" % (cmp, eq, order) + ) + + with pytest.deprecated_call() as dc: + + @attr.s + class C(object): + x = attr.ib(cmp=cmp, eq=eq, order=order) + + assert rv == attr.fields(C).x.cmp + + (w,) = dc.list + + assert ( + "The usage of `cmp` is deprecated and will be removed on or after " + "2021-06-01. Please use `eq` and `order` instead." + == w.message.args[0] + ) + + @pytest.mark.parametrize("slots", [True, False]) + def test_no_setattr_if_validate_without_validators(self, slots): + """ + If a class has on_setattr=attr.setters.validate (former default in NG + APIs) but sets no validators, don't use the (slower) setattr in + __init__. + + Regression test for #816. + """ + + @attr.s(on_setattr=attr.setters.validate) + class C(object): + x = attr.ib() + + @attr.s(on_setattr=attr.setters.validate) + class D(C): + y = attr.ib() + + src = inspect.getsource(D.__init__) + + assert "setattr" not in src + assert "self.x = x" in src + assert "self.y = y" in src + assert object.__setattr__ == D.__setattr__ + + @pytest.mark.parametrize("slots", [True, False]) + def test_no_setattr_if_convert_without_converters(self, slots): + """ + If a class has on_setattr=attr.setters.convert but sets no validators, + don't use the (slower) setattr in __init__. + """ + + @attr.s(on_setattr=attr.setters.convert) + class C(object): + x = attr.ib() + + @attr.s(on_setattr=attr.setters.convert) + class D(C): + y = attr.ib() + + src = inspect.getsource(D.__init__) + + assert "setattr" not in src + assert "self.x = x" in src + assert "self.y = y" in src + assert object.__setattr__ == D.__setattr__ + + @pytest.mark.skipif(not PY36, reason="NG APIs are 3.6+") + @pytest.mark.parametrize("slots", [True, False]) + def test_no_setattr_with_ng_defaults(self, slots): + """ + If a class has the NG default on_setattr=[convert, validate] but sets + no validators or converters, don't use the (slower) setattr in + __init__. + """ + + @attr.define + class C(object): + x = attr.ib() + + src = inspect.getsource(C.__init__) + + assert "setattr" not in src + assert "self.x = x" in src + assert object.__setattr__ == C.__setattr__ + + @attr.define + class D(C): + y = attr.ib() + + src = inspect.getsource(D.__init__) + + assert "setattr" not in src + assert "self.x = x" in src + assert "self.y = y" in src + assert object.__setattr__ == D.__setattr__ + + def test_on_setattr_detect_inherited_validators(self): + """ + _make_init detects the presence of a validator even if the field is + inherited. + """ + + @attr.s(on_setattr=attr.setters.validate) + class C(object): + x = attr.ib(validator=42) + + @attr.s(on_setattr=attr.setters.validate) + class D(C): + y = attr.ib() + + src = inspect.getsource(D.__init__) + + assert "_setattr = _cached_setattr" in src + assert "_setattr('x', x)" in src + assert "_setattr('y', y)" in src + assert object.__setattr__ != D.__setattr__ diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_hooks.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_hooks.py new file mode 100644 index 0000000000..92fc2dcaab --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_hooks.py @@ -0,0 +1,209 @@ +# SPDX-License-Identifier: MIT + +from datetime import datetime +from typing import Dict, List + +import attr + + +class TestTransformHook: + """ + Tests for `attrs(tranform_value_serializer=func)` + """ + + def test_hook_applied(self): + """ + The transform hook is applied to all attributes. Types can be missing, + explicitly set, or annotated. + """ + results = [] + + def hook(cls, attribs): + attr.resolve_types(cls, attribs=attribs) + results[:] = [(a.name, a.type) for a in attribs] + return attribs + + @attr.s(field_transformer=hook) + class C: + x = attr.ib() + y = attr.ib(type=int) + z: float = attr.ib() + + assert results == [("x", None), ("y", int), ("z", float)] + + def test_hook_applied_auto_attrib(self): + """ + The transform hook is applied to all attributes and type annotations + are detected. + """ + results = [] + + def hook(cls, attribs): + attr.resolve_types(cls, attribs=attribs) + results[:] = [(a.name, a.type) for a in attribs] + return attribs + + @attr.s(auto_attribs=True, field_transformer=hook) + class C: + x: int + y: str = attr.ib() + + assert results == [("x", int), ("y", str)] + + def test_hook_applied_modify_attrib(self): + """ + The transform hook can modify attributes. + """ + + def hook(cls, attribs): + attr.resolve_types(cls, attribs=attribs) + return [a.evolve(converter=a.type) for a in attribs] + + @attr.s(auto_attribs=True, field_transformer=hook) + class C: + x: int = attr.ib(converter=int) + y: float + + c = C(x="3", y="3.14") + assert c == C(x=3, y=3.14) + + def test_hook_remove_field(self): + """ + It is possible to remove fields via the hook. + """ + + def hook(cls, attribs): + attr.resolve_types(cls, attribs=attribs) + return [a for a in attribs if a.type is not int] + + @attr.s(auto_attribs=True, field_transformer=hook) + class C: + x: int + y: float + + assert attr.asdict(C(2.7)) == {"y": 2.7} + + def test_hook_add_field(self): + """ + It is possible to add fields via the hook. + """ + + def hook(cls, attribs): + a1 = attribs[0] + a2 = a1.evolve(name="new") + return [a1, a2] + + @attr.s(auto_attribs=True, field_transformer=hook) + class C: + x: int + + assert attr.asdict(C(1, 2)) == {"x": 1, "new": 2} + + def test_hook_with_inheritance(self): + """ + The hook receives all fields from base classes. + """ + + def hook(cls, attribs): + assert [a.name for a in attribs] == ["x", "y"] + # Remove Base' "x" + return attribs[1:] + + @attr.s(auto_attribs=True) + class Base: + x: int + + @attr.s(auto_attribs=True, field_transformer=hook) + class Sub(Base): + y: int + + assert attr.asdict(Sub(2)) == {"y": 2} + + def test_attrs_attrclass(self): + """ + The list of attrs returned by a field_transformer is converted to + "AttrsClass" again. + + Regression test for #821. + """ + + @attr.s(auto_attribs=True, field_transformer=lambda c, a: list(a)) + class C: + x: int + + fields_type = type(attr.fields(C)) + assert fields_type.__name__ == "CAttributes" + assert issubclass(fields_type, tuple) + + +class TestAsDictHook: + def test_asdict(self): + """ + asdict() calls the hooks in attrs classes and in other datastructures + like lists or dicts. + """ + + def hook(inst, a, v): + if isinstance(v, datetime): + return v.isoformat() + return v + + @attr.dataclass + class Child: + x: datetime + y: List[datetime] + + @attr.dataclass + class Parent: + a: Child + b: List[Child] + c: Dict[str, Child] + d: Dict[str, datetime] + + inst = Parent( + a=Child(1, [datetime(2020, 7, 1)]), + b=[Child(2, [datetime(2020, 7, 2)])], + c={"spam": Child(3, [datetime(2020, 7, 3)])}, + d={"eggs": datetime(2020, 7, 4)}, + ) + + result = attr.asdict(inst, value_serializer=hook) + assert result == { + "a": {"x": 1, "y": ["2020-07-01T00:00:00"]}, + "b": [{"x": 2, "y": ["2020-07-02T00:00:00"]}], + "c": {"spam": {"x": 3, "y": ["2020-07-03T00:00:00"]}}, + "d": {"eggs": "2020-07-04T00:00:00"}, + } + + def test_asdict_calls(self): + """ + The correct instances and attribute names are passed to the hook. + """ + calls = [] + + def hook(inst, a, v): + calls.append((inst, a.name if a else a, v)) + return v + + @attr.dataclass + class Child: + x: int + + @attr.dataclass + class Parent: + a: Child + b: List[Child] + c: Dict[str, Child] + + inst = Parent(a=Child(1), b=[Child(2)], c={"spam": Child(3)}) + + attr.asdict(inst, value_serializer=hook) + assert calls == [ + (inst, "a", inst.a), + (inst.a, "x", inst.a.x), + (inst, "b", inst.b), + (inst.b[0], "x", inst.b[0].x), + (inst, "c", inst.c), + (None, None, "spam"), + (inst.c["spam"], "x", inst.c["spam"].x), + ] diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_import.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_import.py new file mode 100644 index 0000000000..423124319c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_import.py @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: MIT + + +class TestImportStar(object): + def test_from_attr_import_star(self): + """ + import * from attr + """ + # attr_import_star contains `from attr import *`, which cannot + # be done here because *-imports are only allowed on module level. + from . import attr_import_star # noqa: F401 diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_init_subclass.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_init_subclass.py new file mode 100644 index 0000000000..863e794377 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_init_subclass.py @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for `__init_subclass__` related tests. + +Python 3.6+ only. +""" + +import pytest + +import attr + + +@pytest.mark.parametrize("slots", [True, False]) +def test_init_subclass_vanilla(slots): + """ + `super().__init_subclass__` can be used if the subclass is not an attrs + class both with dict and slotted classes. + """ + + @attr.s(slots=slots) + class Base: + def __init_subclass__(cls, param, **kw): + super().__init_subclass__(**kw) + cls.param = param + + class Vanilla(Base, param="foo"): + pass + + assert "foo" == Vanilla().param + + +def test_init_subclass_attrs(): + """ + `__init_subclass__` works with attrs classes as long as slots=False. + """ + + @attr.s(slots=False) + class Base: + def __init_subclass__(cls, param, **kw): + super().__init_subclass__(**kw) + cls.param = param + + @attr.s + class Attrs(Base, param="foo"): + pass + + assert "foo" == Attrs().param diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_make.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_make.py new file mode 100644 index 0000000000..729d3a71f0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_make.py @@ -0,0 +1,2462 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for `attr._make`. +""" + +from __future__ import absolute_import, division, print_function + +import copy +import functools +import gc +import inspect +import itertools +import sys + +from operator import attrgetter + +import pytest + +from hypothesis import assume, given +from hypothesis.strategies import booleans, integers, lists, sampled_from, text + +import attr + +from attr import _config +from attr._compat import PY2, PY310, ordered_dict +from attr._make import ( + Attribute, + Factory, + _AndValidator, + _Attributes, + _ClassBuilder, + _CountingAttr, + _determine_attrib_eq_order, + _determine_attrs_eq_order, + _determine_whether_to_implement, + _transform_attrs, + and_, + fields, + fields_dict, + make_class, + validate, +) +from attr.exceptions import ( + DefaultAlreadySetError, + NotAnAttrsClassError, + PythonTooOldError, +) + +from .strategies import ( + gen_attr_names, + list_of_attrs, + optional_bool, + simple_attrs, + simple_attrs_with_metadata, + simple_attrs_without_metadata, + simple_classes, +) +from .utils import simple_attr + + +attrs_st = simple_attrs.map(lambda c: Attribute.from_counting_attr("name", c)) + + +class TestCountingAttr(object): + """ + Tests for `attr`. + """ + + def test_returns_Attr(self): + """ + Returns an instance of _CountingAttr. + """ + a = attr.ib() + + assert isinstance(a, _CountingAttr) + + def test_validators_lists_to_wrapped_tuples(self): + """ + If a list is passed as validator, it's just converted to a tuple. + """ + + def v1(_, __): + pass + + def v2(_, __): + pass + + a = attr.ib(validator=[v1, v2]) + + assert _AndValidator((v1, v2)) == a._validator + + def test_validator_decorator_single(self): + """ + If _CountingAttr.validator is used as a decorator and there is no + decorator set, the decorated method is used as the validator. + """ + a = attr.ib() + + @a.validator + def v(): + pass + + assert v == a._validator + + @pytest.mark.parametrize( + "wrap", [lambda v: v, lambda v: [v], lambda v: and_(v)] + ) + def test_validator_decorator(self, wrap): + """ + If _CountingAttr.validator is used as a decorator and there is already + a decorator set, the decorators are composed using `and_`. + """ + + def v(_, __): + pass + + a = attr.ib(validator=wrap(v)) + + @a.validator + def v2(self, _, __): + pass + + assert _AndValidator((v, v2)) == a._validator + + def test_default_decorator_already_set(self): + """ + Raise DefaultAlreadySetError if the decorator is used after a default + has been set. + """ + a = attr.ib(default=42) + + with pytest.raises(DefaultAlreadySetError): + + @a.default + def f(self): + pass + + def test_default_decorator_sets(self): + """ + Decorator wraps the method in a Factory with pass_self=True and sets + the default. + """ + a = attr.ib() + + @a.default + def f(self): + pass + + assert Factory(f, True) == a._default + + +def make_tc(): + class TransformC(object): + z = attr.ib() + y = attr.ib() + x = attr.ib() + a = 42 + + return TransformC + + +class TestTransformAttrs(object): + """ + Tests for `_transform_attrs`. + """ + + def test_no_modifications(self): + """ + Does not attach __attrs_attrs__ to the class. + """ + C = make_tc() + _transform_attrs(C, None, False, False, True, None) + + assert None is getattr(C, "__attrs_attrs__", None) + + def test_normal(self): + """ + Transforms every `_CountingAttr` and leaves others (a) be. + """ + C = make_tc() + attrs, _, _ = _transform_attrs(C, None, False, False, True, None) + + assert ["z", "y", "x"] == [a.name for a in attrs] + + def test_empty(self): + """ + No attributes works as expected. + """ + + @attr.s + class C(object): + pass + + assert _Attributes(((), [], {})) == _transform_attrs( + C, None, False, False, True, None + ) + + def test_transforms_to_attribute(self): + """ + All `_CountingAttr`s are transformed into `Attribute`s. + """ + C = make_tc() + attrs, base_attrs, _ = _transform_attrs( + C, None, False, False, True, None + ) + + assert [] == base_attrs + assert 3 == len(attrs) + assert all(isinstance(a, Attribute) for a in attrs) + + def test_conflicting_defaults(self): + """ + Raises `ValueError` if attributes with defaults are followed by + mandatory attributes. + """ + + class C(object): + x = attr.ib(default=None) + y = attr.ib() + + with pytest.raises(ValueError) as e: + _transform_attrs(C, None, False, False, True, None) + assert ( + "No mandatory attributes allowed after an attribute with a " + "default value or factory. Attribute in question: Attribute" + "(name='y', default=NOTHING, validator=None, repr=True, " + "eq=True, eq_key=None, order=True, order_key=None, " + "hash=None, init=True, " + "metadata=mappingproxy({}), type=None, converter=None, " + "kw_only=False, inherited=False, on_setattr=None)", + ) == e.value.args + + def test_kw_only(self): + """ + Converts all attributes, including base class' attributes, if `kw_only` + is provided. Therefore, `kw_only` allows attributes with defaults to + preceed mandatory attributes. + + Updates in the subclass *don't* affect the base class attributes. + """ + + @attr.s + class B(object): + b = attr.ib() + + for b_a in B.__attrs_attrs__: + assert b_a.kw_only is False + + class C(B): + x = attr.ib(default=None) + y = attr.ib() + + attrs, base_attrs, _ = _transform_attrs( + C, None, False, True, True, None + ) + + assert len(attrs) == 3 + assert len(base_attrs) == 1 + + for a in attrs: + assert a.kw_only is True + + for b_a in B.__attrs_attrs__: + assert b_a.kw_only is False + + def test_these(self): + """ + If these is passed, use it and ignore body and base classes. + """ + + class Base(object): + z = attr.ib() + + class C(Base): + y = attr.ib() + + attrs, base_attrs, _ = _transform_attrs( + C, {"x": attr.ib()}, False, False, True, None + ) + + assert [] == base_attrs + assert (simple_attr("x"),) == attrs + + def test_these_leave_body(self): + """ + If these is passed, no attributes are removed from the body. + """ + + @attr.s(init=False, these={"x": attr.ib()}) + class C(object): + x = 5 + + assert 5 == C().x + assert "C(x=5)" == repr(C()) + + def test_these_ordered(self): + """ + If these is passed ordered attrs, their order respect instead of the + counter. + """ + b = attr.ib(default=2) + a = attr.ib(default=1) + + @attr.s(these=ordered_dict([("a", a), ("b", b)])) + class C(object): + pass + + assert "C(a=1, b=2)" == repr(C()) + + def test_multiple_inheritance_old(self): + """ + Old multiple inheritance attributre collection behavior is retained. + + See #285 + """ + + @attr.s + class A(object): + a1 = attr.ib(default="a1") + a2 = attr.ib(default="a2") + + @attr.s + class B(A): + b1 = attr.ib(default="b1") + b2 = attr.ib(default="b2") + + @attr.s + class C(B, A): + c1 = attr.ib(default="c1") + c2 = attr.ib(default="c2") + + @attr.s + class D(A): + d1 = attr.ib(default="d1") + d2 = attr.ib(default="d2") + + @attr.s + class E(C, D): + e1 = attr.ib(default="e1") + e2 = attr.ib(default="e2") + + assert ( + "E(a1='a1', a2='a2', b1='b1', b2='b2', c1='c1', c2='c2', d1='d1', " + "d2='d2', e1='e1', e2='e2')" + ) == repr(E()) + + def test_overwrite_proper_mro(self): + """ + The proper MRO path works single overwrites too. + """ + + @attr.s(collect_by_mro=True) + class C(object): + x = attr.ib(default=1) + + @attr.s(collect_by_mro=True) + class D(C): + x = attr.ib(default=2) + + assert "D(x=2)" == repr(D()) + + def test_multiple_inheritance_proper_mro(self): + """ + Attributes are collected according to the MRO. + + See #428 + """ + + @attr.s + class A(object): + a1 = attr.ib(default="a1") + a2 = attr.ib(default="a2") + + @attr.s + class B(A): + b1 = attr.ib(default="b1") + b2 = attr.ib(default="b2") + + @attr.s + class C(B, A): + c1 = attr.ib(default="c1") + c2 = attr.ib(default="c2") + + @attr.s + class D(A): + d1 = attr.ib(default="d1") + d2 = attr.ib(default="d2") + + @attr.s(collect_by_mro=True) + class E(C, D): + e1 = attr.ib(default="e1") + e2 = attr.ib(default="e2") + + assert ( + "E(a1='a1', a2='a2', d1='d1', d2='d2', b1='b1', b2='b2', c1='c1', " + "c2='c2', e1='e1', e2='e2')" + ) == repr(E()) + + def test_mro(self): + """ + Attributes and methods are looked up the same way. + + See #428 + """ + + @attr.s(collect_by_mro=True) + class A(object): + + x = attr.ib(10) + + def xx(self): + return 10 + + @attr.s(collect_by_mro=True) + class B(A): + y = attr.ib(20) + + @attr.s(collect_by_mro=True) + class C(A): + x = attr.ib(50) + + def xx(self): + return 50 + + @attr.s(collect_by_mro=True) + class D(B, C): + pass + + d = D() + + assert d.x == d.xx() + + def test_inherited(self): + """ + Inherited Attributes have `.inherited` True, otherwise False. + """ + + @attr.s + class A(object): + a = attr.ib() + + @attr.s + class B(A): + b = attr.ib() + + @attr.s + class C(B): + a = attr.ib() + c = attr.ib() + + f = attr.fields + + assert False is f(A).a.inherited + + assert True is f(B).a.inherited + assert False is f(B).b.inherited + + assert False is f(C).a.inherited + assert True is f(C).b.inherited + assert False is f(C).c.inherited + + +class TestAttributes(object): + """ + Tests for the `attrs`/`attr.s` class decorator. + """ + + @pytest.mark.skipif(not PY2, reason="No old-style classes in Py3") + def test_catches_old_style(self): + """ + Raises TypeError on old-style classes. + """ + with pytest.raises(TypeError) as e: + + @attr.s + class C: + pass + + assert ("attrs only works with new-style classes.",) == e.value.args + + def test_sets_attrs(self): + """ + Sets the `__attrs_attrs__` class attribute with a list of `Attribute`s. + """ + + @attr.s + class C(object): + x = attr.ib() + + assert "x" == C.__attrs_attrs__[0].name + assert all(isinstance(a, Attribute) for a in C.__attrs_attrs__) + + def test_empty(self): + """ + No attributes, no problems. + """ + + @attr.s + class C3(object): + pass + + assert "C3()" == repr(C3()) + assert C3() == C3() + + @given(attr=attrs_st, attr_name=sampled_from(Attribute.__slots__)) + def test_immutable(self, attr, attr_name): + """ + Attribute instances are immutable. + """ + with pytest.raises(AttributeError): + setattr(attr, attr_name, 1) + + @pytest.mark.parametrize( + "method_name", ["__repr__", "__eq__", "__hash__", "__init__"] + ) + def test_adds_all_by_default(self, method_name): + """ + If no further arguments are supplied, all add_XXX functions except + add_hash are applied. __hash__ is set to None. + """ + # Set the method name to a sentinel and check whether it has been + # overwritten afterwards. + sentinel = object() + + class C(object): + x = attr.ib() + + setattr(C, method_name, sentinel) + + C = attr.s(C) + meth = getattr(C, method_name) + + assert sentinel != meth + if method_name == "__hash__": + assert meth is None + + @pytest.mark.parametrize( + "arg_name, method_name", + [ + ("repr", "__repr__"), + ("eq", "__eq__"), + ("order", "__le__"), + ("hash", "__hash__"), + ("init", "__init__"), + ], + ) + def test_respects_add_arguments(self, arg_name, method_name): + """ + If a certain `XXX` is `False`, `__XXX__` is not added to the class. + """ + # Set the method name to a sentinel and check whether it has been + # overwritten afterwards. + sentinel = object() + + am_args = { + "repr": True, + "eq": True, + "order": True, + "hash": True, + "init": True, + } + am_args[arg_name] = False + if arg_name == "eq": + am_args["order"] = False + + class C(object): + x = attr.ib() + + setattr(C, method_name, sentinel) + + C = attr.s(**am_args)(C) + + assert sentinel == getattr(C, method_name) + + @pytest.mark.parametrize("init", [True, False]) + def test_respects_init_attrs_init(self, init): + """ + If init=False, adds __attrs_init__ to the class. + Otherwise, it does not. + """ + + class C(object): + x = attr.ib() + + C = attr.s(init=init)(C) + assert hasattr(C, "__attrs_init__") != init + + @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.") + @given(slots_outer=booleans(), slots_inner=booleans()) + def test_repr_qualname(self, slots_outer, slots_inner): + """ + On Python 3, the name in repr is the __qualname__. + """ + + @attr.s(slots=slots_outer) + class C(object): + @attr.s(slots=slots_inner) + class D(object): + pass + + assert "C.D()" == repr(C.D()) + assert "GC.D()" == repr(GC.D()) + + @given(slots_outer=booleans(), slots_inner=booleans()) + def test_repr_fake_qualname(self, slots_outer, slots_inner): + """ + Setting repr_ns overrides a potentially guessed namespace. + """ + + @attr.s(slots=slots_outer) + class C(object): + @attr.s(repr_ns="C", slots=slots_inner) + class D(object): + pass + + assert "C.D()" == repr(C.D()) + + @pytest.mark.skipif(PY2, reason="__qualname__ is PY3-only.") + @given(slots_outer=booleans(), slots_inner=booleans()) + def test_name_not_overridden(self, slots_outer, slots_inner): + """ + On Python 3, __name__ is different from __qualname__. + """ + + @attr.s(slots=slots_outer) + class C(object): + @attr.s(slots=slots_inner) + class D(object): + pass + + assert C.D.__name__ == "D" + assert C.D.__qualname__ == C.__qualname__ + ".D" + + @pytest.mark.parametrize("with_validation", [True, False]) + def test_pre_init(self, with_validation, monkeypatch): + """ + Verify that __attrs_pre_init__ gets called if defined. + """ + monkeypatch.setattr(_config, "_run_validators", with_validation) + + @attr.s + class C(object): + def __attrs_pre_init__(self2): + self2.z = 30 + + c = C() + + assert 30 == getattr(c, "z", None) + + @pytest.mark.parametrize("with_validation", [True, False]) + def test_post_init(self, with_validation, monkeypatch): + """ + Verify that __attrs_post_init__ gets called if defined. + """ + monkeypatch.setattr(_config, "_run_validators", with_validation) + + @attr.s + class C(object): + x = attr.ib() + y = attr.ib() + + def __attrs_post_init__(self2): + self2.z = self2.x + self2.y + + c = C(x=10, y=20) + + assert 30 == getattr(c, "z", None) + + @pytest.mark.parametrize("with_validation", [True, False]) + def test_pre_post_init_order(self, with_validation, monkeypatch): + """ + Verify that __attrs_post_init__ gets called if defined. + """ + monkeypatch.setattr(_config, "_run_validators", with_validation) + + @attr.s + class C(object): + x = attr.ib() + + def __attrs_pre_init__(self2): + self2.z = 30 + + def __attrs_post_init__(self2): + self2.z += self2.x + + c = C(x=10) + + assert 40 == getattr(c, "z", None) + + def test_types(self): + """ + Sets the `Attribute.type` attr from type argument. + """ + + @attr.s + class C(object): + x = attr.ib(type=int) + y = attr.ib(type=str) + z = attr.ib() + + assert int is fields(C).x.type + assert str is fields(C).y.type + assert None is fields(C).z.type + + @pytest.mark.parametrize("slots", [True, False]) + def test_clean_class(self, slots): + """ + Attribute definitions do not appear on the class body after @attr.s. + """ + + @attr.s(slots=slots) + class C(object): + x = attr.ib() + + x = getattr(C, "x", None) + + assert not isinstance(x, _CountingAttr) + + def test_factory_sugar(self): + """ + Passing factory=f is syntactic sugar for passing default=Factory(f). + """ + + @attr.s + class C(object): + x = attr.ib(factory=list) + + assert Factory(list) == attr.fields(C).x.default + + def test_sugar_factory_mutex(self): + """ + Passing both default and factory raises ValueError. + """ + with pytest.raises(ValueError, match="mutually exclusive"): + + @attr.s + class C(object): + x = attr.ib(factory=list, default=Factory(list)) + + def test_sugar_callable(self): + """ + Factory has to be a callable to prevent people from passing Factory + into it. + """ + with pytest.raises(ValueError, match="must be a callable"): + + @attr.s + class C(object): + x = attr.ib(factory=Factory(list)) + + def test_inherited_does_not_affect_hashing_and_equality(self): + """ + Whether or not an Attribute has been inherited doesn't affect how it's + hashed and compared. + """ + + @attr.s + class BaseClass(object): + x = attr.ib() + + @attr.s + class SubClass(BaseClass): + pass + + ba = attr.fields(BaseClass)[0] + sa = attr.fields(SubClass)[0] + + assert ba == sa + assert hash(ba) == hash(sa) + + +class TestKeywordOnlyAttributes(object): + """ + Tests for keyword-only attributes. + """ + + def test_adds_keyword_only_arguments(self): + """ + Attributes can be added as keyword-only. + """ + + @attr.s + class C(object): + a = attr.ib() + b = attr.ib(default=2, kw_only=True) + c = attr.ib(kw_only=True) + d = attr.ib(default=attr.Factory(lambda: 4), kw_only=True) + + c = C(1, c=3) + + assert c.a == 1 + assert c.b == 2 + assert c.c == 3 + assert c.d == 4 + + def test_ignores_kw_only_when_init_is_false(self): + """ + Specifying ``kw_only=True`` when ``init=False`` is essentially a no-op. + """ + + @attr.s + class C(object): + x = attr.ib(init=False, default=0, kw_only=True) + y = attr.ib() + + c = C(1) + + assert c.x == 0 + assert c.y == 1 + + def test_keyword_only_attributes_presence(self): + """ + Raises `TypeError` when keyword-only arguments are + not specified. + """ + + @attr.s + class C(object): + x = attr.ib(kw_only=True) + + with pytest.raises(TypeError) as e: + C() + + if PY2: + assert ( + "missing required keyword-only argument: 'x'" + ) in e.value.args[0] + else: + assert ( + "missing 1 required keyword-only argument: 'x'" + ) in e.value.args[0] + + def test_keyword_only_attributes_unexpected(self): + """ + Raises `TypeError` when unexpected keyword argument passed. + """ + + @attr.s + class C(object): + x = attr.ib(kw_only=True) + + with pytest.raises(TypeError) as e: + C(x=5, y=10) + + assert "got an unexpected keyword argument 'y'" in e.value.args[0] + + def test_keyword_only_attributes_can_come_in_any_order(self): + """ + Mandatory vs non-mandatory attr order only matters when they are part + of the __init__ signature and when they aren't kw_only (which are + moved to the end and can be mandatory or non-mandatory in any order, + as they will be specified as keyword args anyway). + """ + + @attr.s + class C(object): + a = attr.ib(kw_only=True) + b = attr.ib(kw_only=True, default="b") + c = attr.ib(kw_only=True) + d = attr.ib() + e = attr.ib(default="e") + f = attr.ib(kw_only=True) + g = attr.ib(kw_only=True, default="g") + h = attr.ib(kw_only=True) + i = attr.ib(init=False) + + c = C("d", a="a", c="c", f="f", h="h") + + assert c.a == "a" + assert c.b == "b" + assert c.c == "c" + assert c.d == "d" + assert c.e == "e" + assert c.f == "f" + assert c.g == "g" + assert c.h == "h" + + def test_keyword_only_attributes_allow_subclassing(self): + """ + Subclass can define keyword-only attributed without defaults, + when the base class has attributes with defaults. + """ + + @attr.s + class Base(object): + x = attr.ib(default=0) + + @attr.s + class C(Base): + y = attr.ib(kw_only=True) + + c = C(y=1) + + assert c.x == 0 + assert c.y == 1 + + def test_keyword_only_class_level(self): + """ + `kw_only` can be provided at the attr.s level, converting all + attributes to `kw_only.` + """ + + @attr.s(kw_only=True) + class C(object): + x = attr.ib() + y = attr.ib(kw_only=True) + + with pytest.raises(TypeError): + C(0, y=1) + + c = C(x=0, y=1) + + assert c.x == 0 + assert c.y == 1 + + def test_keyword_only_class_level_subclassing(self): + """ + Subclass `kw_only` propagates to attrs inherited from the base, + allowing non-default following default. + """ + + @attr.s + class Base(object): + x = attr.ib(default=0) + + @attr.s(kw_only=True) + class C(Base): + y = attr.ib() + + with pytest.raises(TypeError): + C(1) + + c = C(x=0, y=1) + + assert c.x == 0 + assert c.y == 1 + + def test_init_false_attribute_after_keyword_attribute(self): + """ + A positional attribute cannot follow a `kw_only` attribute, + but an `init=False` attribute can because it won't appear + in `__init__` + """ + + @attr.s + class KwArgBeforeInitFalse(object): + kwarg = attr.ib(kw_only=True) + non_init_function_default = attr.ib(init=False) + non_init_keyword_default = attr.ib( + init=False, default="default-by-keyword" + ) + + @non_init_function_default.default + def _init_to_init(self): + return self.kwarg + "b" + + c = KwArgBeforeInitFalse(kwarg="a") + + assert c.kwarg == "a" + assert c.non_init_function_default == "ab" + assert c.non_init_keyword_default == "default-by-keyword" + + def test_init_false_attribute_after_keyword_attribute_with_inheritance( + self, + ): + """ + A positional attribute cannot follow a `kw_only` attribute, + but an `init=False` attribute can because it won't appear + in `__init__`. This test checks that we allow this + even when the `kw_only` attribute appears in a parent class + """ + + @attr.s + class KwArgBeforeInitFalseParent(object): + kwarg = attr.ib(kw_only=True) + + @attr.s + class KwArgBeforeInitFalseChild(KwArgBeforeInitFalseParent): + non_init_function_default = attr.ib(init=False) + non_init_keyword_default = attr.ib( + init=False, default="default-by-keyword" + ) + + @non_init_function_default.default + def _init_to_init(self): + return self.kwarg + "b" + + c = KwArgBeforeInitFalseChild(kwarg="a") + + assert c.kwarg == "a" + assert c.non_init_function_default == "ab" + assert c.non_init_keyword_default == "default-by-keyword" + + +@pytest.mark.skipif(not PY2, reason="PY2-specific keyword-only error behavior") +class TestKeywordOnlyAttributesOnPy2(object): + """ + Tests for keyword-only attribute behavior on py2. + """ + + def test_no_init(self): + """ + Keyworld-only is a no-op, not any error, if ``init=false``. + """ + + @attr.s(kw_only=True, init=False) + class ClassLevel(object): + a = attr.ib() + + @attr.s(init=False) + class AttrLevel(object): + a = attr.ib(kw_only=True) + + +@attr.s +class GC(object): + @attr.s + class D(object): + pass + + +class TestMakeClass(object): + """ + Tests for `make_class`. + """ + + @pytest.mark.parametrize("ls", [list, tuple]) + def test_simple(self, ls): + """ + Passing a list of strings creates attributes with default args. + """ + C1 = make_class("C1", ls(["a", "b"])) + + @attr.s + class C2(object): + a = attr.ib() + b = attr.ib() + + assert C1.__attrs_attrs__ == C2.__attrs_attrs__ + + def test_dict(self): + """ + Passing a dict of name: _CountingAttr creates an equivalent class. + """ + C1 = make_class( + "C1", {"a": attr.ib(default=42), "b": attr.ib(default=None)} + ) + + @attr.s + class C2(object): + a = attr.ib(default=42) + b = attr.ib(default=None) + + assert C1.__attrs_attrs__ == C2.__attrs_attrs__ + + def test_attr_args(self): + """ + attributes_arguments are passed to attributes + """ + C = make_class("C", ["x"], repr=False) + + assert repr(C(1)).startswith(" 0 + + for cls_a, raw_a in zip(fields(C), list_of_attrs): + assert cls_a.metadata != {} + assert cls_a.metadata == raw_a.metadata + + def test_metadata(self): + """ + If metadata that is not None is passed, it is used. + + This is necessary for coverage because the previous test is + hypothesis-based. + """ + md = {} + a = attr.ib(metadata=md) + + assert md is a.metadata + + +class TestClassBuilder(object): + """ + Tests for `_ClassBuilder`. + """ + + def test_repr_str(self): + """ + Trying to add a `__str__` without having a `__repr__` raises a + ValueError. + """ + with pytest.raises(ValueError) as ei: + make_class("C", {}, repr=False, str=True) + + assert ( + "__str__ can only be generated if a __repr__ exists.", + ) == ei.value.args + + def test_repr(self): + """ + repr of builder itself makes sense. + """ + + class C(object): + pass + + b = _ClassBuilder( + C, + None, + True, + True, + False, + False, + False, + False, + False, + False, + True, + None, + False, + None, + ) + + assert "<_ClassBuilder(cls=C)>" == repr(b) + + def test_returns_self(self): + """ + All methods return the builder for chaining. + """ + + class C(object): + x = attr.ib() + + b = _ClassBuilder( + C, + None, + True, + True, + False, + False, + False, + False, + False, + False, + True, + None, + False, + None, + ) + + cls = ( + b.add_eq() + .add_order() + .add_hash() + .add_init() + .add_attrs_init() + .add_repr("ns") + .add_str() + .build_class() + ) + + assert "ns.C(x=1)" == repr(cls(1)) + + @pytest.mark.parametrize( + "meth_name", + [ + "__init__", + "__hash__", + "__repr__", + "__str__", + "__eq__", + "__ne__", + "__lt__", + "__le__", + "__gt__", + "__ge__", + ], + ) + def test_attaches_meta_dunders(self, meth_name): + """ + Generated methods have correct __module__, __name__, and __qualname__ + attributes. + """ + + @attr.s(hash=True, str=True) + class C(object): + def organic(self): + pass + + @attr.s(hash=True, str=True) + class D(object): + pass + + meth_C = getattr(C, meth_name) + meth_D = getattr(D, meth_name) + + assert meth_name == meth_C.__name__ == meth_D.__name__ + assert C.organic.__module__ == meth_C.__module__ == meth_D.__module__ + if not PY2: + # This is assertion that would fail if a single __ne__ instance + # was reused across multiple _make_eq calls. + organic_prefix = C.organic.__qualname__.rsplit(".", 1)[0] + assert organic_prefix + "." + meth_name == meth_C.__qualname__ + + def test_handles_missing_meta_on_class(self): + """ + If the class hasn't a __module__ or __qualname__, the method hasn't + either. + """ + + class C(object): + pass + + b = _ClassBuilder( + C, + these=None, + slots=False, + frozen=False, + weakref_slot=True, + getstate_setstate=False, + auto_attribs=False, + is_exc=False, + kw_only=False, + cache_hash=False, + collect_by_mro=True, + on_setattr=None, + has_custom_setattr=False, + field_transformer=None, + ) + b._cls = {} # no __module__; no __qualname__ + + def fake_meth(self): + pass + + fake_meth.__module__ = "42" + fake_meth.__qualname__ = "23" + + rv = b._add_method_dunders(fake_meth) + + assert "42" == rv.__module__ == fake_meth.__module__ + assert "23" == rv.__qualname__ == fake_meth.__qualname__ + + def test_weakref_setstate(self): + """ + __weakref__ is not set on in setstate because it's not writable in + slotted classes. + """ + + @attr.s(slots=True) + class C(object): + __weakref__ = attr.ib( + init=False, hash=False, repr=False, eq=False, order=False + ) + + assert C() == copy.deepcopy(C()) + + def test_no_references_to_original(self): + """ + When subclassing a slotted class, there are no stray references to the + original class. + """ + + @attr.s(slots=True) + class C(object): + pass + + @attr.s(slots=True) + class C2(C): + pass + + # The original C2 is in a reference cycle, so force a collect: + gc.collect() + + assert [C2] == C.__subclasses__() + + def _get_copy_kwargs(include_slots=True): + """ + Generate a list of compatible attr.s arguments for the `copy` tests. + """ + options = ["frozen", "hash", "cache_hash"] + + if include_slots: + options.extend(["slots", "weakref_slot"]) + + out_kwargs = [] + for args in itertools.product([True, False], repeat=len(options)): + kwargs = dict(zip(options, args)) + + kwargs["hash"] = kwargs["hash"] or None + + if kwargs["cache_hash"] and not ( + kwargs["frozen"] or kwargs["hash"] + ): + continue + + out_kwargs.append(kwargs) + + return out_kwargs + + @pytest.mark.parametrize("kwargs", _get_copy_kwargs()) + def test_copy(self, kwargs): + """ + Ensure that an attrs class can be copied successfully. + """ + + @attr.s(eq=True, **kwargs) + class C(object): + x = attr.ib() + + a = C(1) + b = copy.deepcopy(a) + + assert a == b + + @pytest.mark.parametrize("kwargs", _get_copy_kwargs(include_slots=False)) + def test_copy_custom_setstate(self, kwargs): + """ + Ensure that non-slots classes respect a custom __setstate__. + """ + + @attr.s(eq=True, **kwargs) + class C(object): + x = attr.ib() + + def __getstate__(self): + return self.__dict__ + + def __setstate__(self, state): + state["x"] *= 5 + self.__dict__.update(state) + + expected = C(25) + actual = copy.copy(C(5)) + + assert actual == expected + + +class TestMakeOrder: + """ + Tests for _make_order(). + """ + + def test_subclasses_cannot_be_compared(self): + """ + Calling comparison methods on subclasses raises a TypeError. + + We use the actual operation so we get an error raised on Python 3. + """ + + @attr.s + class A(object): + a = attr.ib() + + @attr.s + class B(A): + pass + + a = A(42) + b = B(42) + + assert a <= a + assert a >= a + assert not a < a + assert not a > a + + assert ( + NotImplemented + == a.__lt__(b) + == a.__le__(b) + == a.__gt__(b) + == a.__ge__(b) + ) + + if not PY2: + with pytest.raises(TypeError): + a <= b + + with pytest.raises(TypeError): + a >= b + + with pytest.raises(TypeError): + a < b + + with pytest.raises(TypeError): + a > b + + +class TestDetermineAttrsEqOrder(object): + def test_default(self): + """ + If all are set to None, set both eq and order to the passed default. + """ + assert (42, 42) == _determine_attrs_eq_order(None, None, None, 42) + + @pytest.mark.parametrize("eq", [True, False]) + def test_order_mirrors_eq_by_default(self, eq): + """ + If order is None, it mirrors eq. + """ + assert (eq, eq) == _determine_attrs_eq_order(None, eq, None, True) + + def test_order_without_eq(self): + """ + eq=False, order=True raises a meaningful ValueError. + """ + with pytest.raises( + ValueError, match="`order` can only be True if `eq` is True too." + ): + _determine_attrs_eq_order(None, False, True, True) + + @given(cmp=booleans(), eq=optional_bool, order=optional_bool) + def test_mix(self, cmp, eq, order): + """ + If cmp is not None, eq and order must be None and vice versa. + """ + assume(eq is not None or order is not None) + + with pytest.raises( + ValueError, match="Don't mix `cmp` with `eq' and `order`." + ): + _determine_attrs_eq_order(cmp, eq, order, True) + + +class TestDetermineAttribEqOrder(object): + def test_default(self): + """ + If all are set to None, set both eq and order to the passed default. + """ + assert (42, None, 42, None) == _determine_attrib_eq_order( + None, None, None, 42 + ) + + def test_eq_callable_order_boolean(self): + """ + eq=callable or order=callable need to transformed into eq/eq_key + or order/order_key. + """ + assert (True, str.lower, False, None) == _determine_attrib_eq_order( + None, str.lower, False, True + ) + + def test_eq_callable_order_callable(self): + """ + eq=callable or order=callable need to transformed into eq/eq_key + or order/order_key. + """ + assert (True, str.lower, True, abs) == _determine_attrib_eq_order( + None, str.lower, abs, True + ) + + def test_eq_boolean_order_callable(self): + """ + eq=callable or order=callable need to transformed into eq/eq_key + or order/order_key. + """ + assert (True, None, True, str.lower) == _determine_attrib_eq_order( + None, True, str.lower, True + ) + + @pytest.mark.parametrize("eq", [True, False]) + def test_order_mirrors_eq_by_default(self, eq): + """ + If order is None, it mirrors eq. + """ + assert (eq, None, eq, None) == _determine_attrib_eq_order( + None, eq, None, True + ) + + def test_order_without_eq(self): + """ + eq=False, order=True raises a meaningful ValueError. + """ + with pytest.raises( + ValueError, match="`order` can only be True if `eq` is True too." + ): + _determine_attrib_eq_order(None, False, True, True) + + @given(cmp=booleans(), eq=optional_bool, order=optional_bool) + def test_mix(self, cmp, eq, order): + """ + If cmp is not None, eq and order must be None and vice versa. + """ + assume(eq is not None or order is not None) + + with pytest.raises( + ValueError, match="Don't mix `cmp` with `eq' and `order`." + ): + _determine_attrib_eq_order(cmp, eq, order, True) + + +class TestDocs: + @pytest.mark.parametrize( + "meth_name", + [ + "__init__", + "__repr__", + "__eq__", + "__ne__", + "__lt__", + "__le__", + "__gt__", + "__ge__", + ], + ) + def test_docs(self, meth_name): + """ + Tests the presence and correctness of the documentation + for the generated methods + """ + + @attr.s + class A(object): + pass + + if hasattr(A, "__qualname__"): + method = getattr(A, meth_name) + expected = "Method generated by attrs for class {}.".format( + A.__qualname__ + ) + assert expected == method.__doc__ + + +@pytest.mark.skipif(not PY2, reason="Needs to be only caught on Python 2.") +def test_auto_detect_raises_on_py2(): + """ + Trying to pass auto_detect=True to attr.s raises PythonTooOldError. + """ + with pytest.raises(PythonTooOldError): + attr.s(auto_detect=True) + + +class BareC(object): + pass + + +class BareSlottedC(object): + __slots__ = () + + +@pytest.mark.skipif(PY2, reason="Auto-detection is Python 3-only.") +class TestAutoDetect: + @pytest.mark.parametrize("C", (BareC, BareSlottedC)) + def test_determine_detects_non_presence_correctly(self, C): + """ + On an empty class, nothing should be detected. + """ + assert True is _determine_whether_to_implement( + C, None, True, ("__init__",) + ) + assert True is _determine_whether_to_implement( + C, None, True, ("__repr__",) + ) + assert True is _determine_whether_to_implement( + C, None, True, ("__eq__", "__ne__") + ) + assert True is _determine_whether_to_implement( + C, None, True, ("__le__", "__lt__", "__ge__", "__gt__") + ) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_make_all_by_default(self, slots, frozen): + """ + If nothing is there to be detected, imply init=True, repr=True, + hash=None, eq=True, order=True. + """ + + @attr.s(auto_detect=True, slots=slots, frozen=frozen) + class C(object): + x = attr.ib() + + i = C(1) + o = object() + + assert i.__init__ is not o.__init__ + assert i.__repr__ is not o.__repr__ + assert i.__eq__ is not o.__eq__ + assert i.__ne__ is not o.__ne__ + assert i.__le__ is not o.__le__ + assert i.__lt__ is not o.__lt__ + assert i.__ge__ is not o.__ge__ + assert i.__gt__ is not o.__gt__ + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_detect_auto_init(self, slots, frozen): + """ + If auto_detect=True and an __init__ exists, don't write one. + """ + + @attr.s(auto_detect=True, slots=slots, frozen=frozen) + class CI(object): + x = attr.ib() + + def __init__(self): + object.__setattr__(self, "x", 42) + + assert 42 == CI().x + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_detect_auto_repr(self, slots, frozen): + """ + If auto_detect=True and an __repr__ exists, don't write one. + """ + + @attr.s(auto_detect=True, slots=slots, frozen=frozen) + class C(object): + x = attr.ib() + + def __repr__(self): + return "hi" + + assert "hi" == repr(C(42)) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_detect_auto_hash(self, slots, frozen): + """ + If auto_detect=True and an __hash__ exists, don't write one. + """ + + @attr.s(auto_detect=True, slots=slots, frozen=frozen) + class C(object): + x = attr.ib() + + def __hash__(self): + return 0xC0FFEE + + assert 0xC0FFEE == hash(C(42)) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_detect_auto_eq(self, slots, frozen): + """ + If auto_detect=True and an __eq__ or an __ne__, exist, don't write one. + """ + + @attr.s(auto_detect=True, slots=slots, frozen=frozen) + class C(object): + x = attr.ib() + + def __eq__(self, o): + raise ValueError("worked") + + with pytest.raises(ValueError, match="worked"): + C(1) == C(1) + + @attr.s(auto_detect=True, slots=slots, frozen=frozen) + class D(object): + x = attr.ib() + + def __ne__(self, o): + raise ValueError("worked") + + with pytest.raises(ValueError, match="worked"): + D(1) != D(1) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_detect_auto_order(self, slots, frozen): + """ + If auto_detect=True and an __ge__, __gt__, __le__, or and __lt__ exist, + don't write one. + + It's surprisingly difficult to test this programmatically, so we do it + by hand. + """ + + def assert_not_set(cls, ex, meth_name): + __tracebackhide__ = True + + a = getattr(cls, meth_name) + if meth_name == ex: + assert a == 42 + else: + assert a is getattr(object, meth_name) + + def assert_none_set(cls, ex): + __tracebackhide__ = True + + for m in ("le", "lt", "ge", "gt"): + assert_not_set(cls, ex, "__" + m + "__") + + @attr.s(auto_detect=True, slots=slots, frozen=frozen) + class LE(object): + __le__ = 42 + + @attr.s(auto_detect=True, slots=slots, frozen=frozen) + class LT(object): + __lt__ = 42 + + @attr.s(auto_detect=True, slots=slots, frozen=frozen) + class GE(object): + __ge__ = 42 + + @attr.s(auto_detect=True, slots=slots, frozen=frozen) + class GT(object): + __gt__ = 42 + + assert_none_set(LE, "__le__") + assert_none_set(LT, "__lt__") + assert_none_set(GE, "__ge__") + assert_none_set(GT, "__gt__") + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_override_init(self, slots, frozen): + """ + If init=True is passed, ignore __init__. + """ + + @attr.s(init=True, auto_detect=True, slots=slots, frozen=frozen) + class C(object): + x = attr.ib() + + def __init__(self): + pytest.fail("should not be called") + + assert C(1) == C(1) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_override_repr(self, slots, frozen): + """ + If repr=True is passed, ignore __repr__. + """ + + @attr.s(repr=True, auto_detect=True, slots=slots, frozen=frozen) + class C(object): + x = attr.ib() + + def __repr__(self): + pytest.fail("should not be called") + + assert "C(x=1)" == repr(C(1)) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_override_hash(self, slots, frozen): + """ + If hash=True is passed, ignore __hash__. + """ + + @attr.s(hash=True, auto_detect=True, slots=slots, frozen=frozen) + class C(object): + x = attr.ib() + + def __hash__(self): + pytest.fail("should not be called") + + assert hash(C(1)) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + def test_override_eq(self, slots, frozen): + """ + If eq=True is passed, ignore __eq__ and __ne__. + """ + + @attr.s(eq=True, auto_detect=True, slots=slots, frozen=frozen) + class C(object): + x = attr.ib() + + def __eq__(self, o): + pytest.fail("should not be called") + + def __ne__(self, o): + pytest.fail("should not be called") + + assert C(1) == C(1) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("frozen", [True, False]) + @pytest.mark.parametrize( + "eq,order,cmp", + [ + (True, None, None), + (True, True, None), + (None, True, None), + (None, None, True), + ], + ) + def test_override_order(self, slots, frozen, eq, order, cmp): + """ + If order=True is passed, ignore __le__, __lt__, __gt__, __ge__. + + eq=True and cmp=True both imply order=True so test it too. + """ + + def meth(self, o): + pytest.fail("should not be called") + + @attr.s( + cmp=cmp, + order=order, + eq=eq, + auto_detect=True, + slots=slots, + frozen=frozen, + ) + class C(object): + x = attr.ib() + __le__ = __lt__ = __gt__ = __ge__ = meth + + assert C(1) < C(2) + assert C(1) <= C(2) + assert C(2) > C(1) + assert C(2) >= C(1) + + @pytest.mark.parametrize("slots", [True, False]) + @pytest.mark.parametrize("first", [True, False]) + def test_total_ordering(self, slots, first): + """ + functools.total_ordering works as expected if an order method and an eq + method are detected. + + Ensure the order doesn't matter. + """ + + class C(object): + x = attr.ib() + own_eq_called = attr.ib(default=False) + own_le_called = attr.ib(default=False) + + def __eq__(self, o): + self.own_eq_called = True + return self.x == o.x + + def __le__(self, o): + self.own_le_called = True + return self.x <= o.x + + if first: + C = functools.total_ordering( + attr.s(auto_detect=True, slots=slots)(C) + ) + else: + C = attr.s(auto_detect=True, slots=slots)( + functools.total_ordering(C) + ) + + c1, c2 = C(1), C(2) + + assert c1 < c2 + assert c1.own_le_called + + c1, c2 = C(1), C(2) + + assert c2 > c1 + assert c2.own_le_called + + c1, c2 = C(1), C(2) + + assert c2 != c1 + assert c1 == c1 + + assert c1.own_eq_called + + @pytest.mark.parametrize("slots", [True, False]) + def test_detects_setstate_getstate(self, slots): + """ + __getstate__ and __setstate__ are not overwritten if either is present. + """ + + @attr.s(slots=slots, auto_detect=True) + class C(object): + def __getstate__(self): + return ("hi",) + + assert None is getattr(C(), "__setstate__", None) + + @attr.s(slots=slots, auto_detect=True) + class C(object): + called = attr.ib(False) + + def __setstate__(self, state): + self.called = True + + i = C() + + assert False is i.called + + i.__setstate__(()) + + assert True is i.called + assert None is getattr(C(), "__getstate__", None) + + @pytest.mark.skipif(PY310, reason="Pre-3.10 only.") + def test_match_args_pre_310(self): + """ + __match_args__ is not created on Python versions older than 3.10. + """ + + @attr.s + class C(object): + a = attr.ib() + + assert None is getattr(C, "__match_args__", None) + + +@pytest.mark.skipif(not PY310, reason="Structural pattern matching is 3.10+") +class TestMatchArgs(object): + """ + Tests for match_args and __match_args__ generation. + """ + + def test_match_args(self): + """ + __match_args__ is created by default on Python 3.10. + """ + + @attr.define + class C: + a = attr.field() + + assert ("a",) == C.__match_args__ + + def test_explicit_match_args(self): + """ + A custom __match_args__ set is not overwritten. + """ + + ma = () + + @attr.define + class C: + a = attr.field() + __match_args__ = ma + + assert C(42).__match_args__ is ma + + @pytest.mark.parametrize("match_args", [True, False]) + def test_match_args_attr_set(self, match_args): + """ + __match_args__ is set depending on match_args. + """ + + @attr.define(match_args=match_args) + class C: + a = attr.field() + + if match_args: + assert hasattr(C, "__match_args__") + else: + assert not hasattr(C, "__match_args__") + + def test_match_args_kw_only(self): + """ + kw_only classes don't generate __match_args__. + kw_only fields are not included in __match_args__. + """ + + @attr.define + class C: + a = attr.field(kw_only=True) + b = attr.field() + + assert C.__match_args__ == ("b",) + + @attr.define(kw_only=True) + class C: + a = attr.field() + b = attr.field() + + assert C.__match_args__ == () + + def test_match_args_argument(self): + """ + match_args being False with inheritance. + """ + + @attr.define(match_args=False) + class X: + a = attr.field() + + assert "__match_args__" not in X.__dict__ + + @attr.define(match_args=False) + class Y: + a = attr.field() + __match_args__ = ("b",) + + assert Y.__match_args__ == ("b",) + + @attr.define(match_args=False) + class Z(Y): + z = attr.field() + + assert Z.__match_args__ == ("b",) + + @attr.define + class A: + a = attr.field() + z = attr.field() + + @attr.define(match_args=False) + class B(A): + b = attr.field() + + assert B.__match_args__ == ("a", "z") + + def test_make_class(self): + """ + match_args generation with make_class. + """ + + C1 = make_class("C1", ["a", "b"]) + assert ("a", "b") == C1.__match_args__ + + C1 = make_class("C1", ["a", "b"], match_args=False) + assert not hasattr(C1, "__match_args__") + + C1 = make_class("C1", ["a", "b"], kw_only=True) + assert () == C1.__match_args__ + + C1 = make_class("C1", {"a": attr.ib(kw_only=True), "b": attr.ib()}) + assert ("b",) == C1.__match_args__ diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_mypy.yml b/testing/web-platform/tests/tools/third_party/attrs/tests/test_mypy.yml new file mode 100644 index 0000000000..ca17b0a662 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_mypy.yml @@ -0,0 +1,1395 @@ +- case: attr_s_with_type_argument + parametrized: + - val: 'a = attr.ib(type=int)' + - val: 'a: int = attr.ib()' + main: | + import attr + @attr.s + class C: + {{ val }} + C() # E: Missing positional argument "a" in call to "C" + C(1) + C(a=1) + C(a="hi") # E: Argument "a" to "C" has incompatible type "str"; expected "int" +- case: attr_s_with_type_annotations + main : | + import attr + @attr.s + class C: + a: int = attr.ib() + C() # E: Missing positional argument "a" in call to "C" + C(1) + C(a=1) + C(a="hi") # E: Argument "a" to "C" has incompatible type "str"; expected "int" + +- case: testAttrsSimple + main: | + import attr + @attr.s + class A: + a = attr.ib() + _b = attr.ib() + c = attr.ib(18) + _d = attr.ib(validator=None, default=18) + E = 18 + + def foo(self): + return self.a + reveal_type(A) # N: Revealed type is "def (a: Any, b: Any, c: Any =, d: Any =) -> main.A" + A(1, [2]) + A(1, [2], '3', 4) + A(1, 2, 3, 4) + A(1, [2], '3', 4, 5) # E: Too many arguments for "A" + +- case: testAttrsAnnotated + main: | + import attr + from typing import List, ClassVar + @attr.s + class A: + a: int = attr.ib() + _b: List[int] = attr.ib() + c: str = attr.ib('18') + _d: int = attr.ib(validator=None, default=18) + E = 7 + F: ClassVar[int] = 22 + reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> main.A" + A(1, [2]) + A(1, [2], '3', 4) + A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" + A(1, [2], '3', 4, 5) # E: Too many arguments for "A" + +- case: testAttrsPython2Annotations + main: | + import attr + from typing import List, ClassVar + @attr.s + class A: + a = attr.ib() # type: int + _b = attr.ib() # type: List[int] + c = attr.ib('18') # type: str + _d = attr.ib(validator=None, default=18) # type: int + E = 7 + F: ClassVar[int] = 22 + reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> main.A" + A(1, [2]) + A(1, [2], '3', 4) + A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" + A(1, [2], '3', 4, 5) # E: Too many arguments for "A" + +- case: testAttrsAutoAttribs + main: | + import attr + from typing import List, ClassVar + @attr.s(auto_attribs=True) + class A: + a: int + _b: List[int] + c: str = '18' + _d: int = attr.ib(validator=None, default=18) + E = 7 + F: ClassVar[int] = 22 + reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.int], c: builtins.str =, d: builtins.int =) -> main.A" + A(1, [2]) + A(1, [2], '3', 4) + A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" # E: Argument 3 to "A" has incompatible type "int"; expected "str" + A(1, [2], '3', 4, 5) # E: Too many arguments for "A" + +- case: testAttrsUntypedNoUntypedDefs + mypy_config: | + disallow_untyped_defs = True + main: | + import attr + @attr.s + class A: + a = attr.ib() # E: Need type annotation for "a" + _b = attr.ib() # E: Need type annotation for "_b" + c = attr.ib(18) # E: Need type annotation for "c" + _d = attr.ib(validator=None, default=18) # E: Need type annotation for "_d" + E = 18 + +- case: testAttrsWrongReturnValue + main: | + import attr + @attr.s + class A: + x: int = attr.ib(8) + def foo(self) -> str: + return self.x # E: Incompatible return value type (got "int", expected "str") + @attr.s + class B: + x = attr.ib(8) # type: int + def foo(self) -> str: + return self.x # E: Incompatible return value type (got "int", expected "str") + @attr.dataclass + class C: + x: int = 8 + def foo(self) -> str: + return self.x # E: Incompatible return value type (got "int", expected "str") + @attr.s + class D: + x = attr.ib(8, type=int) + def foo(self) -> str: + return self.x # E: Incompatible return value type (got "int", expected "str") + +- case: testAttrsSeriousNames + main: | + from attr import attrib, attrs + from typing import List + @attrs(init=True) + class A: + a = attrib() + _b: List[int] = attrib() + c = attrib(18) + _d = attrib(validator=None, default=18) + CLASS_VAR = 18 + reveal_type(A) # N: Revealed type is "def (a: Any, b: builtins.list[builtins.int], c: Any =, d: Any =) -> main.A" + A(1, [2]) + A(1, [2], '3', 4) + A(1, 2, 3, 4) # E: Argument 2 to "A" has incompatible type "int"; expected "List[int]" + A(1, [2], '3', 4, 5) # E: Too many arguments for "A" + +- case: testAttrsDefaultErrors + main: | + import attr + @attr.s + class A: + x = attr.ib(default=17) + y = attr.ib() # E: Non-default attributes not allowed after default attributes. + @attr.s(auto_attribs=True) + class B: + x: int = 17 + y: int # E: Non-default attributes not allowed after default attributes. + @attr.s(auto_attribs=True) + class C: + x: int = attr.ib(default=17) + y: int # E: Non-default attributes not allowed after default attributes. + @attr.s + class D: + x = attr.ib() + y = attr.ib() # E: Non-default attributes not allowed after default attributes. + + @x.default + def foo(self): + return 17 + +- case: testAttrsNotBooleans + main: | + import attr + x = True + @attr.s(cmp=x) # E: "cmp" argument must be True or False. + class A: + a = attr.ib(init=x) # E: "init" argument must be True or False. + +- case: testAttrsInitFalse + main: | + from attr import attrib, attrs + @attrs(auto_attribs=True, init=False) + class A: + a: int + _b: int + c: int = 18 + _d: int = attrib(validator=None, default=18) + reveal_type(A) # N: Revealed type is "def () -> main.A" + A() + A(1, [2]) # E: Too many arguments for "A" + A(1, [2], '3', 4) # E: Too many arguments for "A" + +- case: testAttrsInitAttribFalse + main: | + from attr import attrib, attrs + @attrs + class A: + a = attrib(init=False) + b = attrib() + reveal_type(A) # N: Revealed type is "def (b: Any) -> main.A" + +- case: testAttrsCmpTrue + main: | + from attr import attrib, attrs + @attrs(auto_attribs=True) + class A: + a: int + reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> main.A" + reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" + reveal_type(A.__le__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" + reveal_type(A.__gt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" + reveal_type(A.__ge__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" + + A(1) < A(2) + A(1) <= A(2) + A(1) > A(2) + A(1) >= A(2) + A(1) == A(2) + A(1) != A(2) + + A(1) < 1 # E: Unsupported operand types for < ("A" and "int") + A(1) <= 1 # E: Unsupported operand types for <= ("A" and "int") + A(1) > 1 # E: Unsupported operand types for > ("A" and "int") + A(1) >= 1 # E: Unsupported operand types for >= ("A" and "int") + A(1) == 1 + A(1) != 1 + + 1 < A(1) # E: Unsupported operand types for < ("int" and "A") + 1 <= A(1) # E: Unsupported operand types for <= ("int" and "A") + 1 > A(1) # E: Unsupported operand types for > ("int" and "A") + 1 >= A(1) # E: Unsupported operand types for >= ("int" and "A") + 1 == A(1) + 1 != A(1) + +- case: testAttrsEqFalse + main: | + from attr import attrib, attrs + @attrs(auto_attribs=True, eq=False) + class A: + a: int + reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> main.A" + reveal_type(A.__eq__) # N: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" + reveal_type(A.__ne__) # N: Revealed type is "def (builtins.object, builtins.object) -> builtins.bool" + + A(1) < A(2) # E: Unsupported left operand type for < ("A") + A(1) <= A(2) # E: Unsupported left operand type for <= ("A") + A(1) > A(2) # E: Unsupported left operand type for > ("A") + A(1) >= A(2) # E: Unsupported left operand type for >= ("A") + A(1) == A(2) + A(1) != A(2) + + A(1) < 1 # E: Unsupported operand types for > ("int" and "A") + A(1) <= 1 # E: Unsupported operand types for >= ("int" and "A") + A(1) > 1 # E: Unsupported operand types for < ("int" and "A") + A(1) >= 1 # E: Unsupported operand types for <= ("int" and "A") + A(1) == 1 + A(1) != 1 + + 1 < A(1) # E: Unsupported operand types for < ("int" and "A") + 1 <= A(1) # E: Unsupported operand types for <= ("int" and "A") + 1 > A(1) # E: Unsupported operand types for > ("int" and "A") + 1 >= A(1) # E: Unsupported operand types for >= ("int" and "A") + 1 == A(1) + 1 != A(1) + +- case: testAttrsOrderFalse + main: | + from attr import attrib, attrs + @attrs(auto_attribs=True, order=False) + class A: + a: int + reveal_type(A) # N: Revealed type is "def (a: builtins.int) -> main.A" + + A(1) < A(2) # E: Unsupported left operand type for < ("A") + A(1) <= A(2) # E: Unsupported left operand type for <= ("A") + A(1) > A(2) # E: Unsupported left operand type for > ("A") + A(1) >= A(2) # E: Unsupported left operand type for >= ("A") + A(1) == A(2) + A(1) != A(2) + + A(1) < 1 # E: Unsupported operand types for > ("int" and "A") + A(1) <= 1 # E: Unsupported operand types for >= ("int" and "A") + A(1) > 1 # E: Unsupported operand types for < ("int" and "A") + A(1) >= 1 # E: Unsupported operand types for <= ("int" and "A") + A(1) == 1 + A(1) != 1 + + 1 < A(1) # E: Unsupported operand types for < ("int" and "A") + 1 <= A(1) # E: Unsupported operand types for <= ("int" and "A") + 1 > A(1) # E: Unsupported operand types for > ("int" and "A") + 1 >= A(1) # E: Unsupported operand types for >= ("int" and "A") + 1 == A(1) + 1 != A(1) + +- case: testAttrsCmpEqOrderValues + main: | + from attr import attrib, attrs + @attrs(cmp=True) + class DeprecatedTrue: + ... + + @attrs(cmp=False) + class DeprecatedFalse: + ... + + @attrs(cmp=False, eq=True) # E: Don't mix "cmp" with "eq" and "order" + class Mixed: + ... + + @attrs(order=True, eq=False) # E: eq must be True if order is True + class Confused: + ... + + +- case: testAttrsInheritance + main: | + import attr + @attr.s + class A: + a: int = attr.ib() + @attr.s + class B: + b: str = attr.ib() + @attr.s + class C(A, B): + c: bool = attr.ib() + reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.str, c: builtins.bool) -> main.C" + +- case: testAttrsNestedInClasses + main: | + import attr + @attr.s + class C: + y = attr.ib() + @attr.s + class D: + x: int = attr.ib() + reveal_type(C) # N: Revealed type is "def (y: Any) -> main.C" + reveal_type(C.D) # N: Revealed type is "def (x: builtins.int) -> main.C.D" + +- case: testAttrsInheritanceOverride + main: | + import attr + + @attr.s + class A: + a: int = attr.ib() + x: int = attr.ib() + + @attr.s + class B(A): + b: str = attr.ib() + x: int = attr.ib(default=22) + + @attr.s + class C(B): + c: bool = attr.ib() # No error here because the x below overwrites the x above. + x: int = attr.ib() + + reveal_type(A) # N: Revealed type is "def (a: builtins.int, x: builtins.int) -> main.A" + reveal_type(B) # N: Revealed type is "def (a: builtins.int, b: builtins.str, x: builtins.int =) -> main.B" + reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.str, c: builtins.bool, x: builtins.int) -> main.C" + +- case: testAttrsTypeEquals + main: | + import attr + + @attr.s + class A: + a = attr.ib(type=int) + b = attr.ib(18, type=int) + reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.int =) -> main.A" + +- case: testAttrsFrozen + main: | + import attr + + @attr.s(frozen=True) + class A: + a = attr.ib() + + a = A(5) + a.a = 16 # E: Property "a" defined in "A" is read-only +- case: testAttrsNextGenFrozen + main: | + from attr import frozen, field + + @frozen + class A: + a = field() + + a = A(5) + a.a = 16 # E: Property "a" defined in "A" is read-only + +- case: testAttrsNextGenDetect + main: | + from attr import define, field + + @define + class A: + a = field() + + @define + class B: + a: int + + @define + class C: + a: int = field() + b = field() + + @define + class D: + a: int + b = field() + + # TODO: Next Gen hasn't shipped with mypy yet so the following are wrong + reveal_type(A) # N: Revealed type is "def (a: Any) -> main.A" + reveal_type(B) # N: Revealed type is "def (a: builtins.int) -> main.B" + reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: Any) -> main.C" + reveal_type(D) # N: Revealed type is "def (b: Any) -> main.D" + +- case: testAttrsDataClass + main: | + import attr + from typing import List, ClassVar + @attr.dataclass + class A: + a: int + _b: List[str] + c: str = '18' + _d: int = attr.ib(validator=None, default=18) + E = 7 + F: ClassVar[int] = 22 + reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.list[builtins.str], c: builtins.str =, d: builtins.int =) -> main.A" + A(1, ['2']) + +- case: testAttrsTypeAlias + main: | + from typing import List + import attr + Alias = List[int] + @attr.s(auto_attribs=True) + class A: + Alias2 = List[str] + x: Alias + y: Alias2 = attr.ib() + reveal_type(A) # N: Revealed type is "def (x: builtins.list[builtins.int], y: builtins.list[builtins.str]) -> main.A" + +- case: testAttrsGeneric + main: | + from typing import TypeVar, Generic, List + import attr + T = TypeVar('T') + @attr.s(auto_attribs=True) + class A(Generic[T]): + x: List[T] + y: T = attr.ib() + def foo(self) -> List[T]: + return [self.y] + def bar(self) -> T: + return self.x[0] + def problem(self) -> T: + return self.x # E: Incompatible return value type (got "List[T]", expected "T") + reveal_type(A) # N: Revealed type is "def [T] (x: builtins.list[T`1], y: T`1) -> main.A[T`1]" + a = A([1], 2) + reveal_type(a) # N: Revealed type is "main.A[builtins.int*]" + reveal_type(a.x) # N: Revealed type is "builtins.list[builtins.int*]" + reveal_type(a.y) # N: Revealed type is "builtins.int*" + + A(['str'], 7) # E: Cannot infer type argument 1 of "A" + A([1], '2') # E: Cannot infer type argument 1 of "A" + + +- case: testAttrsUntypedGenericInheritance + main: | + from typing import Generic, TypeVar + import attr + + T = TypeVar("T") + + @attr.s(auto_attribs=True) + class Base(Generic[T]): + attr: T + + @attr.s(auto_attribs=True) + class Sub(Base): + pass + + sub = Sub(attr=1) + reveal_type(sub) # N: Revealed type is "main.Sub" + reveal_type(sub.attr) # N: Revealed type is "Any" + skip: True # Need to investigate why this is broken + +- case: testAttrsGenericInheritance + main: | + from typing import Generic, TypeVar + import attr + + S = TypeVar("S") + T = TypeVar("T") + + @attr.s(auto_attribs=True) + class Base(Generic[T]): + attr: T + + @attr.s(auto_attribs=True) + class Sub(Base[S]): + pass + + sub_int = Sub[int](attr=1) + reveal_type(sub_int) # N: Revealed type is "main.Sub[builtins.int*]" + reveal_type(sub_int.attr) # N: Revealed type is "builtins.int*" + + sub_str = Sub[str](attr='ok') + reveal_type(sub_str) # N: Revealed type is "main.Sub[builtins.str*]" + reveal_type(sub_str.attr) # N: Revealed type is "builtins.str*" + +- case: testAttrsGenericInheritance2 + main: | + from typing import Generic, TypeVar + import attr + + T1 = TypeVar("T1") + T2 = TypeVar("T2") + T3 = TypeVar("T3") + + @attr.s(auto_attribs=True) + class Base(Generic[T1, T2, T3]): + one: T1 + two: T2 + three: T3 + + @attr.s(auto_attribs=True) + class Sub(Base[int, str, float]): + pass + + sub = Sub(one=1, two='ok', three=3.14) + reveal_type(sub) # N: Revealed type is "main.Sub" + reveal_type(sub.one) # N: Revealed type is "builtins.int*" + reveal_type(sub.two) # N: Revealed type is "builtins.str*" + reveal_type(sub.three) # N: Revealed type is "builtins.float*" + skip: True # Need to investigate why this is broken + +- case: testAttrsMultiGenericInheritance + main: | + from typing import Generic, TypeVar + import attr + + T = TypeVar("T") + + @attr.s(auto_attribs=True, eq=False) + class Base(Generic[T]): + base_attr: T + + S = TypeVar("S") + + @attr.s(auto_attribs=True, eq=False) + class Middle(Base[int], Generic[S]): + middle_attr: S + + @attr.s(auto_attribs=True, eq=False) + class Sub(Middle[str]): + pass + + reveal_type(Sub.__init__) + + sub = Sub(base_attr=1, middle_attr='ok') + reveal_type(sub) # N: Revealed type is "main.Sub" + reveal_type(sub.base_attr) # N: Revealed type is "builtins.int*" + reveal_type(sub.middle_attr) # N: Revealed type is "builtins.str*" + skip: True # Need to investigate why this is broken + +- case: testAttrsGenericClassmethod + main: | + from typing import TypeVar, Generic, Optional + import attr + T = TypeVar('T') + @attr.s(auto_attribs=True) + class A(Generic[T]): + x: Optional[T] + @classmethod + def clsmeth(cls) -> None: + reveal_type(cls) # N: Revealed type is "Type[main.A[T`1]]" + +- case: testAttrsForwardReference + main: | + from typing import Optional + import attr + @attr.s(auto_attribs=True) + class A: + parent: 'B' + + @attr.s(auto_attribs=True) + class B: + parent: Optional[A] + + reveal_type(A) # N: Revealed type is "def (parent: main.B) -> main.A" + reveal_type(B) # N: Revealed type is "def (parent: Union[main.A, None]) -> main.B" + A(B(None)) + +- case: testAttrsForwardReferenceInClass + main: | + from typing import Optional + import attr + @attr.s(auto_attribs=True) + class A: + parent: A.B + + @attr.s(auto_attribs=True) + class B: + parent: Optional[A] + + reveal_type(A) # N: Revealed type is "def (parent: main.A.B) -> main.A" + reveal_type(A.B) # N: Revealed type is "def (parent: Union[main.A, None]) -> main.A.B" + A(A.B(None)) + +- case: testAttrsImporting + main: | + from helper import A + reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> helper.A" + files: + - path: helper.py + content: | + import attr + @attr.s(auto_attribs=True) + class A: + a: int + b: str = attr.ib() + +- case: testAttrsOtherMethods + main: | + import attr + @attr.s(auto_attribs=True) + class A: + a: int + b: str = attr.ib() + @classmethod + def new(cls) -> A: + reveal_type(cls) # N: Revealed type is "Type[main.A]" + return cls(6, 'hello') + @classmethod + def bad(cls) -> A: + return cls(17) # E: Missing positional argument "b" in call to "A" + def foo(self) -> int: + return self.a + reveal_type(A) # N: Revealed type is "def (a: builtins.int, b: builtins.str) -> main.A" + a = A.new() + reveal_type(a.foo) # N: Revealed type is "def () -> builtins.int" + +- case: testAttrsOtherOverloads + main: | + import attr + from typing import overload, Union + + @attr.s + class A: + a = attr.ib() + b = attr.ib(default=3) + + @classmethod + def other(cls) -> str: + return "..." + + @overload + @classmethod + def foo(cls, x: int) -> int: ... + + @overload + @classmethod + def foo(cls, x: str) -> str: ... + + @classmethod + def foo(cls, x: Union[int, str]) -> Union[int, str]: + reveal_type(cls) # N: Revealed type is "Type[main.A]" + reveal_type(cls.other()) # N: Revealed type is "builtins.str" + return x + + reveal_type(A.foo(3)) # N: Revealed type is "builtins.int" + reveal_type(A.foo("foo")) # N: Revealed type is "builtins.str" + +- case: testAttrsDefaultDecorator + main: | + import attr + @attr.s + class C(object): + x: int = attr.ib(default=1) + y: int = attr.ib() + @y.default + def name_does_not_matter(self): + return self.x + 1 + C() + +- case: testAttrsValidatorDecorator + main: | + import attr + @attr.s + class C(object): + x = attr.ib() + @x.validator + def check(self, attribute, value): + if value > 42: + raise ValueError("x must be smaller or equal to 42") + C(42) + C(43) + +- case: testAttrsLocalVariablesInClassMethod + main: | + import attr + @attr.s(auto_attribs=True) + class A: + a: int + b: int = attr.ib() + @classmethod + def new(cls, foo: int) -> A: + a = foo + b = a + return cls(a, b) + +- case: testAttrsUnionForward + main: | + import attr + from typing import Union, List + + @attr.s(auto_attribs=True) + class A: + frob: List['AOrB'] + + class B: + pass + + AOrB = Union[A, B] + + reveal_type(A) # N: Revealed type is "def (frob: builtins.list[Union[main.A, main.B]]) -> main.A" + reveal_type(B) # N: Revealed type is "def () -> main.B" + + A([B()]) + +- case: testAttrsUsingConverter + main: | + import attr + import helper + + def converter2(s:int) -> str: + return 'hello' + + @attr.s + class C: + x: str = attr.ib(converter=helper.converter) + y: str = attr.ib(converter=converter2) + + # Because of the converter the __init__ takes an int, but the variable is a str. + reveal_type(C) # N: Revealed type is "def (x: builtins.int, y: builtins.int) -> main.C" + reveal_type(C(15, 16).x) # N: Revealed type is "builtins.str" + files: + - path: helper.py + content: | + def converter(s:int) -> str: + return 'hello' + +- case: testAttrsUsingBadConverter + mypy_config: + strict_optional = False + main: | + import attr + from typing import overload + @overload + def bad_overloaded_converter(x: int, y: int) -> int: + ... + @overload + def bad_overloaded_converter(x: str, y: str) -> str: + ... + def bad_overloaded_converter(x, y=7): + return x + def bad_converter() -> str: + return '' + @attr.dataclass + class A: + bad: str = attr.ib(converter=bad_converter) + bad_overloaded: int = attr.ib(converter=bad_overloaded_converter) + reveal_type(A) + out: | + main:15: error: Cannot determine __init__ type from converter + main:15: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any]" + main:16: error: Cannot determine __init__ type from converter + main:16: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any]" + main:17: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A" + +- case: testAttrsUsingBadConverterReprocess + mypy_config: + strict_optional = False + main: | + import attr + from typing import overload + forward: 'A' + @overload + def bad_overloaded_converter(x: int, y: int) -> int: + ... + @overload + def bad_overloaded_converter(x: str, y: str) -> str: + ... + def bad_overloaded_converter(x, y=7): + return x + def bad_converter() -> str: + return '' + @attr.dataclass + class A: + bad: str = attr.ib(converter=bad_converter) + bad_overloaded: int = attr.ib(converter=bad_overloaded_converter) + reveal_type(A) + out: | + main:16: error: Cannot determine __init__ type from converter + main:16: error: Argument "converter" has incompatible type "Callable[[], str]"; expected "Callable[[Any], Any]" + main:17: error: Cannot determine __init__ type from converter + main:17: error: Argument "converter" has incompatible type overloaded function; expected "Callable[[Any], Any]" + main:18: note: Revealed type is "def (bad: Any, bad_overloaded: Any) -> main.A" + +- case: testAttrsUsingUnsupportedConverter + main: | + import attr + class Thing: + def do_it(self, int) -> str: + ... + thing = Thing() + def factory(default: int): + ... + @attr.s + class C: + x: str = attr.ib(converter=thing.do_it) # E: Unsupported converter, only named functions and types are currently supported + y: str = attr.ib(converter=lambda x: x) # E: Unsupported converter, only named functions and types are currently supported + z: str = attr.ib(converter=factory(8)) # E: Unsupported converter, only named functions and types are currently supported + reveal_type(C) # N: Revealed type is "def (x: Any, y: Any, z: Any) -> main.C" + +- case: testAttrsUsingConverterAndSubclass + main: | + import attr + + def converter(s:int) -> str: + return 'hello' + + @attr.s + class C: + x: str = attr.ib(converter=converter) + + @attr.s + class A(C): + pass + + # Because of the convert the __init__ takes an int, but the variable is a str. + reveal_type(A) # N: Revealed type is "def (x: builtins.int) -> main.A" + reveal_type(A(15).x) # N: Revealed type is "builtins.str" + +- case: testAttrsUsingConverterWithTypes + main: | + from typing import overload + import attr + + @attr.dataclass + class A: + x: str + + @attr.s + class C: + x: complex = attr.ib(converter=complex) + y: int = attr.ib(converter=int) + z: A = attr.ib(converter=A) + + o = C("1", "2", "3") + o = C(1, 2, "3") + +- case: testAttrsCmpWithSubclasses + main: | + import attr + @attr.s + class A: pass + @attr.s + class B: pass + @attr.s + class C(A, B): pass + @attr.s + class D(A): pass + + reveal_type(A.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" + reveal_type(B.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" + reveal_type(C.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" + reveal_type(D.__lt__) # N: Revealed type is "def [_AT] (self: _AT`-1, other: _AT`-1) -> builtins.bool" + + A() < A() + B() < B() + A() < B() # E: Unsupported operand types for < ("A" and "B") + + C() > A() + C() > B() + C() > C() + C() > D() # E: Unsupported operand types for > ("C" and "D") + + D() >= A() + D() >= B() # E: Unsupported operand types for >= ("D" and "B") + D() >= C() # E: Unsupported operand types for >= ("D" and "C") + D() >= D() + + A() <= 1 # E: Unsupported operand types for <= ("A" and "int") + B() <= 1 # E: Unsupported operand types for <= ("B" and "int") + C() <= 1 # E: Unsupported operand types for <= ("C" and "int") + D() <= 1 # E: Unsupported operand types for <= ("D" and "int") + +- case: testAttrsComplexSuperclass + main: | + import attr + @attr.s + class C: + x: int = attr.ib(default=1) + y: int = attr.ib() + @y.default + def name_does_not_matter(self): + return self.x + 1 + @attr.s + class A(C): + z: int = attr.ib(default=18) + reveal_type(C) # N: Revealed type is "def (x: builtins.int =, y: builtins.int =) -> main.C" + reveal_type(A) # N: Revealed type is "def (x: builtins.int =, y: builtins.int =, z: builtins.int =) -> main.A" + +- case: testAttrsMultiAssign + main: | + import attr + @attr.s + class A: + x, y, z = attr.ib(), attr.ib(type=int), attr.ib(default=17) + reveal_type(A) # N: Revealed type is "def (x: Any, y: builtins.int, z: Any =) -> main.A" + +- case: testAttrsMultiAssign2 + main: | + import attr + @attr.s + class A: + x = y = z = attr.ib() # E: Too many names for one attribute + +- case: testAttrsPrivateInit + main: | + import attr + @attr.s + class C(object): + _x = attr.ib(init=False, default=42) + C() + C(_x=42) # E: Unexpected keyword argument "_x" for "C" + +- case: testAttrsAutoMustBeAll + main: | + import attr + @attr.s(auto_attribs=True) + class A: + a: int + b = 17 + # The following forms are not allowed with auto_attribs=True + c = attr.ib() # E: Need type annotation for "c" + d, e = attr.ib(), attr.ib() # E: Need type annotation for "d" # E: Need type annotation for "e" + f = g = attr.ib() # E: Need type annotation for "f" # E: Need type annotation for "g" + +- case: testAttrsRepeatedName + main: | + import attr + @attr.s + class A: + a = attr.ib(default=8) + b = attr.ib() + a = attr.ib() + reveal_type(A) # N: Revealed type is "def (b: Any, a: Any) -> main.A" + @attr.s + class B: + a: int = attr.ib(default=8) + b: int = attr.ib() + a: int = attr.ib() # E: Name "a" already defined on line 10 + reveal_type(B) # N: Revealed type is "def (b: builtins.int, a: builtins.int) -> main.B" + @attr.s(auto_attribs=True) + class C: + a: int = 8 + b: int + a: int = attr.ib() # E: Name "a" already defined on line 16 + reveal_type(C) # N: Revealed type is "def (a: builtins.int, b: builtins.int) -> main.C" + +- case: testAttrsNewStyleClassPy2 + mypy_config: + python_version = 2.7 + main: | + import attr + @attr.s + class Good(object): + pass + @attr.s + class Bad: # E: attrs only works with new-style classes + pass + skip: True # https://github.com/typeddjango/pytest-mypy-plugins/issues/47 + +- case: testAttrsAutoAttribsPy2 + mypy_config: | + python_version = 2.7 + main: | + import attr + @attr.s(auto_attribs=True) # E: auto_attribs is not supported in Python 2 + class A(object): + x = attr.ib() + skip: True # https://github.com/typeddjango/pytest-mypy-plugins/issues/47 + +- case: testAttrsFrozenSubclass + main: | + import attr + + @attr.dataclass + class NonFrozenBase: + a: int + + @attr.dataclass(frozen=True) + class FrozenBase: + a: int + + @attr.dataclass(frozen=True) + class FrozenNonFrozen(NonFrozenBase): + b: int + + @attr.dataclass(frozen=True) + class FrozenFrozen(FrozenBase): + b: int + + @attr.dataclass + class NonFrozenFrozen(FrozenBase): + b: int + + # Make sure these are untouched + non_frozen_base = NonFrozenBase(1) + non_frozen_base.a = 17 + frozen_base = FrozenBase(1) + frozen_base.a = 17 # E: Property "a" defined in "FrozenBase" is read-only + + a = FrozenNonFrozen(1, 2) + a.a = 17 # E: Property "a" defined in "FrozenNonFrozen" is read-only + a.b = 17 # E: Property "b" defined in "FrozenNonFrozen" is read-only + + b = FrozenFrozen(1, 2) + b.a = 17 # E: Property "a" defined in "FrozenFrozen" is read-only + b.b = 17 # E: Property "b" defined in "FrozenFrozen" is read-only + + c = NonFrozenFrozen(1, 2) + c.a = 17 # E: Property "a" defined in "NonFrozenFrozen" is read-only + c.b = 17 # E: Property "b" defined in "NonFrozenFrozen" is read-only +- case: testAttrsCallableAttributes + main: | + from typing import Callable + import attr + def blah(a: int, b: int) -> bool: + return True + + @attr.s(auto_attribs=True) + class F: + _cb: Callable[[int, int], bool] = blah + def foo(self) -> bool: + return self._cb(5, 6) + + @attr.s + class G: + _cb: Callable[[int, int], bool] = attr.ib(blah) + def foo(self) -> bool: + return self._cb(5, 6) + + @attr.s(auto_attribs=True, frozen=True) + class FFrozen(F): + def bar(self) -> bool: + return self._cb(5, 6) + +- case: testAttrsWithFactory + main: | + from typing import List + import attr + def my_factory() -> int: + return 7 + @attr.s + class A: + x: List[int] = attr.ib(factory=list) + y: int = attr.ib(factory=my_factory) + A() + +- case: testAttrsFactoryAndDefault + main: | + import attr + @attr.s + class A: + x: int = attr.ib(factory=int, default=7) # E: Can't pass both "default" and "factory". + +- case: testAttrsFactoryBadReturn + main: | + import attr + def my_factory() -> int: + return 7 + @attr.s + class A: + x: int = attr.ib(factory=list) # E: Incompatible types in assignment (expression has type "List[_T]", variable has type "int") + y: str = attr.ib(factory=my_factory) # E: Incompatible types in assignment (expression has type "int", variable has type "str") + +- case: testAttrsDefaultAndInit + main: | + import attr + + @attr.s + class C: + a = attr.ib(init=False, default=42) + b = attr.ib() # Ok because previous attribute is init=False + c = attr.ib(default=44) + d = attr.ib(init=False) # Ok because this attribute is init=False + e = attr.ib() # E: Non-default attributes not allowed after default attributes. + +- case: testAttrsOptionalConverter + main: | + # flags: --strict-optional + import attr + from attr.converters import optional + from typing import Optional + + def converter(s:int) -> str: + return 'hello' + + + @attr.s + class A: + y: Optional[int] = attr.ib(converter=optional(int)) + z: Optional[str] = attr.ib(converter=optional(converter)) + + + A(None, None) + +- case: testAttrsTypeVarNoCollision + main: | + from typing import TypeVar, Generic + import attr + + T = TypeVar("T", bytes, str) + + # Make sure the generated __le__ (and friends) don't use T for their arguments. + @attr.s(auto_attribs=True) + class A(Generic[T]): + v: T + +- case: testAttrsKwOnlyAttrib + main: | + import attr + @attr.s + class A: + a = attr.ib(kw_only=True) + A() # E: Missing named argument "a" for "A" + A(15) # E: Too many positional arguments for "A" + A(a=15) + +- case: testAttrsKwOnlyClass + main: | + import attr + @attr.s(kw_only=True, auto_attribs=True) + class A: + a: int + b: bool + A() # E: Missing named argument "a" for "A" # E: Missing named argument "b" for "A" + A(b=True, a=15) + +- case: testAttrsKwOnlyClassNoInit + main: | + import attr + @attr.s(kw_only=True) + class B: + a = attr.ib(init=False) + b = attr.ib() + B(b=True) + +- case: testAttrsKwOnlyWithDefault + main: | + import attr + @attr.s + class C: + a = attr.ib(0) + b = attr.ib(kw_only=True) + c = attr.ib(16, kw_only=True) + C(b=17) + +- case: testAttrsKwOnlyClassWithMixedDefaults + main: | + import attr + @attr.s(kw_only=True) + class D: + a = attr.ib(10) + b = attr.ib() + c = attr.ib(15) + D(b=17) + + +- case: testAttrsKwOnlySubclass + main: | + import attr + @attr.s + class A2: + a = attr.ib(default=0) + @attr.s + class B2(A2): + b = attr.ib(kw_only=True) + B2(b=1) + +- case: testAttrsNonKwOnlyAfterKwOnly + main: | + import attr + @attr.s(kw_only=True) + class A: + a = attr.ib(default=0) + @attr.s + class B(A): + b = attr.ib() + @attr.s + class C: + a = attr.ib(kw_only=True) + b = attr.ib(15) + +- case: testAttrsKwOnlyPy2 + mypy_config: + python_version=2.7 + main: | + import attr + @attr.s(kw_only=True) # E: kw_only is not supported in Python 2 + class A(object): + x = attr.ib() + @attr.s + class B(object): + x = attr.ib(kw_only=True) # E: kw_only is not supported in Python 2 + skip: True # https://github.com/typeddjango/pytest-mypy-plugins/issues/47 + +- case: testAttrsDisallowUntypedWorksForward + main: | + # flags: --disallow-untyped-defs + import attr + from typing import List + + @attr.s + class B: + x: C = attr.ib() + + class C(List[C]): + pass + + reveal_type(B) # N: Revealed type is "def (x: main.C) -> main.B" + +- case: testDisallowUntypedWorksForwardBad + mypy_config: + disallow_untyped_defs = True + main: | + import attr + + @attr.s + class B: + x = attr.ib() # E: Need type annotation for "x" + + reveal_type(B) # N: Revealed type is "def (x: Any) -> main.B" + +- case: testAttrsDefaultDecoratorDeferred + main: | + defer: Yes + + import attr + @attr.s + class C(object): + x: int = attr.ib(default=1) + y: int = attr.ib() + @y.default + def inc(self): + return self.x + 1 + + class Yes: ... + +- case: testAttrsValidatorDecoratorDeferred + main: | + defer: Yes + + import attr + @attr.s + class C(object): + x = attr.ib() + @x.validator + def check(self, attribute, value): + if value > 42: + raise ValueError("x must be smaller or equal to 42") + C(42) + C(43) + + class Yes: ... + +- case: testTypeInAttrUndefined + main: | + import attr + + @attr.s + class C: + total = attr.ib(type=Bad) # E: Name "Bad" is not defined + +- case: testTypeInAttrForwardInRuntime + main: | + import attr + + @attr.s + class C: + total = attr.ib(type=Forward) + + reveal_type(C.total) # N: Revealed type is "main.Forward" + C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "Forward" + class Forward: ... + +- case: testDefaultInAttrForward + main: | + import attr + + @attr.s + class C: + total = attr.ib(default=func()) + + def func() -> int: ... + + C() + C(1) + C(1, 2) # E: Too many arguments for "C" + +- case: testTypeInAttrUndefinedFrozen + main: | + import attr + + @attr.s(frozen=True) + class C: + total = attr.ib(type=Bad) # E: Name "Bad" is not defined + + C(0).total = 1 # E: Property "total" defined in "C" is read-only + +- case: testTypeInAttrDeferredStar + main: | + import lib + files: + - path: lib.py + content: | + import attr + MYPY = False + if MYPY: # Force deferral + from other import * + + @attr.s + class C: + total = attr.ib(type=int) + + C() # E: Missing positional argument "total" in call to "C" + C('no') # E: Argument 1 to "C" has incompatible type "str"; expected "int" + - path: other.py + content: | + import lib + +- case: testAttrsDefaultsMroOtherFile + main: | + import a + files: + - path: a.py + content: | + import attr + from b import A1, A2 + + @attr.s + class Asdf(A1, A2): # E: Non-default attributes not allowed after default attributes. + pass + - path: b.py + content: | + import attr + + @attr.s + class A1: + a: str = attr.ib('test') + + @attr.s + class A2: + b: int = attr.ib() + +- case: testAttrsInheritanceNoAnnotation + main: | + import attr + + @attr.s + class A: + foo = attr.ib() # type: int + + x = 0 + @attr.s + class B(A): + foo = x + + reveal_type(B) # N: Revealed type is "def (foo: builtins.int) -> main.B" diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_next_gen.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_next_gen.py new file mode 100644 index 0000000000..8395f9c028 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_next_gen.py @@ -0,0 +1,440 @@ +# SPDX-License-Identifier: MIT + +""" +Python 3-only integration tests for provisional next generation APIs. +""" + +import re + +from functools import partial + +import pytest + +import attr as _attr # don't use it by accident +import attrs + + +@attrs.define +class C: + x: str + y: int + + +class TestNextGen: + def test_simple(self): + """ + Instantiation works. + """ + C("1", 2) + + def test_no_slots(self): + """ + slots can be deactivated. + """ + + @attrs.define(slots=False) + class NoSlots: + x: int + + ns = NoSlots(1) + + assert {"x": 1} == getattr(ns, "__dict__") + + def test_validates(self): + """ + Validators at __init__ and __setattr__ work. + """ + + @attrs.define + class Validated: + x: int = attrs.field(validator=attrs.validators.instance_of(int)) + + v = Validated(1) + + with pytest.raises(TypeError): + Validated(None) + + with pytest.raises(TypeError): + v.x = "1" + + def test_no_order(self): + """ + Order is off by default but can be added. + """ + with pytest.raises(TypeError): + C("1", 2) < C("2", 3) + + @attrs.define(order=True) + class Ordered: + x: int + + assert Ordered(1) < Ordered(2) + + def test_override_auto_attribs_true(self): + """ + Don't guess if auto_attrib is set explicitly. + + Having an unannotated attrs.ib/attrs.field fails. + """ + with pytest.raises(attrs.exceptions.UnannotatedAttributeError): + + @attrs.define(auto_attribs=True) + class ThisFails: + x = attrs.field() + y: int + + def test_override_auto_attribs_false(self): + """ + Don't guess if auto_attrib is set explicitly. + + Annotated fields that don't carry an attrs.ib are ignored. + """ + + @attrs.define(auto_attribs=False) + class NoFields: + x: int + y: int + + assert NoFields() == NoFields() + + def test_auto_attribs_detect(self): + """ + define correctly detects if a class lacks type annotations. + """ + + @attrs.define + class OldSchool: + x = attrs.field() + + assert OldSchool(1) == OldSchool(1) + + # Test with maybe_cls = None + @attrs.define() + class OldSchool2: + x = attrs.field() + + assert OldSchool2(1) == OldSchool2(1) + + def test_auto_attribs_detect_fields_and_annotations(self): + """ + define infers auto_attribs=True if fields have type annotations + """ + + @attrs.define + class NewSchool: + x: int + y: list = attrs.field() + + @y.validator + def _validate_y(self, attribute, value): + if value < 0: + raise ValueError("y must be positive") + + assert NewSchool(1, 1) == NewSchool(1, 1) + with pytest.raises(ValueError): + NewSchool(1, -1) + assert list(attrs.fields_dict(NewSchool).keys()) == ["x", "y"] + + def test_auto_attribs_partially_annotated(self): + """ + define infers auto_attribs=True if any type annotations are found + """ + + @attrs.define + class NewSchool: + x: int + y: list + z = 10 + + # fields are defined for any annotated attributes + assert NewSchool(1, []) == NewSchool(1, []) + assert list(attrs.fields_dict(NewSchool).keys()) == ["x", "y"] + + # while the unannotated attributes are left as class vars + assert NewSchool.z == 10 + assert "z" in NewSchool.__dict__ + + def test_auto_attribs_detect_annotations(self): + """ + define correctly detects if a class has type annotations. + """ + + @attrs.define + class NewSchool: + x: int + + assert NewSchool(1) == NewSchool(1) + + # Test with maybe_cls = None + @attrs.define() + class NewSchool2: + x: int + + assert NewSchool2(1) == NewSchool2(1) + + def test_exception(self): + """ + Exceptions are detected and correctly handled. + """ + + @attrs.define + class E(Exception): + msg: str + other: int + + with pytest.raises(E) as ei: + raise E("yolo", 42) + + e = ei.value + + assert ("yolo", 42) == e.args + assert "yolo" == e.msg + assert 42 == e.other + + def test_frozen(self): + """ + attrs.frozen freezes classes. + """ + + @attrs.frozen + class F: + x: str + + f = F(1) + + with pytest.raises(attrs.exceptions.FrozenInstanceError): + f.x = 2 + + def test_auto_detect_eq(self): + """ + auto_detect=True works for eq. + + Regression test for #670. + """ + + @attrs.define + class C: + def __eq__(self, o): + raise ValueError() + + with pytest.raises(ValueError): + C() == C() + + def test_subclass_frozen(self): + """ + It's possible to subclass an `attrs.frozen` class and the frozen-ness + is inherited. + """ + + @attrs.frozen + class A: + a: int + + @attrs.frozen + class B(A): + b: int + + @attrs.define(on_setattr=attrs.setters.NO_OP) + class C(B): + c: int + + assert B(1, 2) == B(1, 2) + assert C(1, 2, 3) == C(1, 2, 3) + + with pytest.raises(attrs.exceptions.FrozenInstanceError): + A(1).a = 1 + + with pytest.raises(attrs.exceptions.FrozenInstanceError): + B(1, 2).a = 1 + + with pytest.raises(attrs.exceptions.FrozenInstanceError): + B(1, 2).b = 2 + + with pytest.raises(attrs.exceptions.FrozenInstanceError): + C(1, 2, 3).c = 3 + + def test_catches_frozen_on_setattr(self): + """ + Passing frozen=True and on_setattr hooks is caught, even if the + immutability is inherited. + """ + + @attrs.define(frozen=True) + class A: + pass + + with pytest.raises( + ValueError, match="Frozen classes can't use on_setattr." + ): + + @attrs.define(frozen=True, on_setattr=attrs.setters.validate) + class B: + pass + + with pytest.raises( + ValueError, + match=re.escape( + "Frozen classes can't use on_setattr " + "(frozen-ness was inherited)." + ), + ): + + @attrs.define(on_setattr=attrs.setters.validate) + class C(A): + pass + + @pytest.mark.parametrize( + "decorator", + [ + partial(_attr.s, frozen=True, slots=True, auto_exc=True), + attrs.frozen, + attrs.define, + attrs.mutable, + ], + ) + def test_discard_context(self, decorator): + """ + raise from None works. + + Regression test for #703. + """ + + @decorator + class MyException(Exception): + x: str = attrs.field() + + with pytest.raises(MyException) as ei: + try: + raise ValueError() + except ValueError: + raise MyException("foo") from None + + assert "foo" == ei.value.x + assert ei.value.__cause__ is None + + def test_converts_and_validates_by_default(self): + """ + If no on_setattr is set, assume setters.convert, setters.validate. + """ + + @attrs.define + class C: + x: int = attrs.field(converter=int) + + @x.validator + def _v(self, _, value): + if value < 10: + raise ValueError("must be >=10") + + inst = C(10) + + # Converts + inst.x = "11" + + assert 11 == inst.x + + # Validates + with pytest.raises(ValueError, match="must be >=10"): + inst.x = "9" + + def test_mro_ng(self): + """ + Attributes and methods are looked up the same way in NG by default. + + See #428 + """ + + @attrs.define + class A: + + x: int = 10 + + def xx(self): + return 10 + + @attrs.define + class B(A): + y: int = 20 + + @attrs.define + class C(A): + x: int = 50 + + def xx(self): + return 50 + + @attrs.define + class D(B, C): + pass + + d = D() + + assert d.x == d.xx() + + +class TestAsTuple: + def test_smoke(self): + """ + `attrs.astuple` only changes defaults, so we just call it and compare. + """ + inst = C("foo", 42) + + assert attrs.astuple(inst) == _attr.astuple(inst) + + +class TestAsDict: + def test_smoke(self): + """ + `attrs.asdict` only changes defaults, so we just call it and compare. + """ + inst = C("foo", {(1,): 42}) + + assert attrs.asdict(inst) == _attr.asdict( + inst, retain_collection_types=True + ) + + +class TestImports: + """ + Verify our re-imports and mirroring works. + """ + + def test_converters(self): + """ + Importing from attrs.converters works. + """ + from attrs.converters import optional + + assert optional is _attr.converters.optional + + def test_exceptions(self): + """ + Importing from attrs.exceptions works. + """ + from attrs.exceptions import FrozenError + + assert FrozenError is _attr.exceptions.FrozenError + + def test_filters(self): + """ + Importing from attrs.filters works. + """ + from attrs.filters import include + + assert include is _attr.filters.include + + def test_setters(self): + """ + Importing from attrs.setters works. + """ + from attrs.setters import pipe + + assert pipe is _attr.setters.pipe + + def test_validators(self): + """ + Importing from attrs.validators works. + """ + from attrs.validators import and_ + + assert and_ is _attr.validators.and_ diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_pattern_matching.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_pattern_matching.py new file mode 100644 index 0000000000..590804a8a7 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_pattern_matching.py @@ -0,0 +1,101 @@ +# SPDX-License-Identifier: MIT + +# Keep this file SHORT, until Black can handle it. +import pytest + +import attr + + +class TestPatternMatching: + """ + Pattern matching syntax test cases. + """ + + @pytest.mark.parametrize("dec", [attr.s, attr.define, attr.frozen]) + def test_simple_match_case(self, dec): + """ + Simple match case statement works as expected with all class + decorators. + """ + + @dec + class C(object): + a = attr.ib() + + assert ("a",) == C.__match_args__ + + matched = False + c = C(a=1) + match c: + case C(a): + matched = True + + assert matched + assert 1 == a + + def test_explicit_match_args(self): + """ + Does not overwrite a manually set empty __match_args__. + """ + + ma = () + + @attr.define + class C: + a = attr.field() + __match_args__ = ma + + c = C(a=1) + + msg = r"C\(\) accepts 0 positional sub-patterns \(1 given\)" + with pytest.raises(TypeError, match=msg): + match c: + case C(_): + pass + + def test_match_args_kw_only(self): + """ + kw_only classes don't generate __match_args__. + kw_only fields are not included in __match_args__. + """ + + @attr.define + class C: + a = attr.field(kw_only=True) + b = attr.field() + + assert ("b",) == C.__match_args__ + + c = C(a=1, b=1) + msg = r"C\(\) accepts 1 positional sub-pattern \(2 given\)" + with pytest.raises(TypeError, match=msg): + match c: + case C(a, b): + pass + + found = False + match c: + case C(b, a=a): + found = True + + assert found + + @attr.define(kw_only=True) + class C: + a = attr.field() + b = attr.field() + + c = C(a=1, b=1) + msg = r"C\(\) accepts 0 positional sub-patterns \(2 given\)" + with pytest.raises(TypeError, match=msg): + match c: + case C(a, b): + pass + + found = False + match c: + case C(a=a, b=b): + found = True + + assert found + assert (1, 1) == (a, b) diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_pyright.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_pyright.py new file mode 100644 index 0000000000..c30dcc5cb1 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_pyright.py @@ -0,0 +1,71 @@ +# SPDX-License-Identifier: MIT + +import json +import os.path +import shutil +import subprocess +import sys + +import pytest + +import attr + + +if sys.version_info < (3, 6): + _found_pyright = False +else: + _found_pyright = shutil.which("pyright") + + +@attr.s(frozen=True) +class PyrightDiagnostic(object): + severity = attr.ib() + message = attr.ib() + + +@pytest.mark.skipif(not _found_pyright, reason="Requires pyright.") +def test_pyright_baseline(): + """The __dataclass_transform__ decorator allows pyright to determine + attrs decorated class types. + """ + + test_file = os.path.dirname(__file__) + "/dataclass_transform_example.py" + + pyright = subprocess.run( + ["pyright", "--outputjson", str(test_file)], capture_output=True + ) + pyright_result = json.loads(pyright.stdout) + + diagnostics = set( + PyrightDiagnostic(d["severity"], d["message"]) + for d in pyright_result["generalDiagnostics"] + ) + + # Expected diagnostics as per pyright 1.1.135 + expected_diagnostics = { + PyrightDiagnostic( + severity="information", + message='Type of "Define.__init__" is' + ' "(self: Define, a: str, b: int) -> None"', + ), + PyrightDiagnostic( + severity="information", + message='Type of "DefineConverter.__init__" is ' + '"(self: DefineConverter, with_converter: int) -> None"', + ), + PyrightDiagnostic( + severity="information", + message='Type of "d.a" is "Literal[\'new\']"', + ), + PyrightDiagnostic( + severity="error", + message='Cannot assign member "a" for type ' + '"FrozenDefine"\n\xa0\xa0"FrozenDefine" is frozen', + ), + PyrightDiagnostic( + severity="information", + message='Type of "d2.a" is "Literal[\'new\']"', + ), + } + + assert diagnostics == expected_diagnostics diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_setattr.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_setattr.py new file mode 100644 index 0000000000..aaedde5746 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_setattr.py @@ -0,0 +1,437 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + +import pickle + +import pytest + +import attr + +from attr import setters +from attr._compat import PY2 +from attr.exceptions import FrozenAttributeError +from attr.validators import instance_of, matches_re + + +@attr.s(frozen=True) +class Frozen(object): + x = attr.ib() + + +@attr.s +class WithOnSetAttrHook(object): + x = attr.ib(on_setattr=lambda *args: None) + + +class TestSetAttr(object): + def test_change(self): + """ + The return value of a hook overwrites the value. But they are not run + on __init__. + """ + + def hook(*a, **kw): + return "hooked!" + + @attr.s + class Hooked(object): + x = attr.ib(on_setattr=hook) + y = attr.ib() + + h = Hooked("x", "y") + + assert "x" == h.x + assert "y" == h.y + + h.x = "xxx" + h.y = "yyy" + + assert "yyy" == h.y + assert "hooked!" == h.x + + def test_frozen_attribute(self): + """ + Frozen attributes raise FrozenAttributeError, others are not affected. + """ + + @attr.s + class PartiallyFrozen(object): + x = attr.ib(on_setattr=setters.frozen) + y = attr.ib() + + pf = PartiallyFrozen("x", "y") + + pf.y = "yyy" + + assert "yyy" == pf.y + + with pytest.raises(FrozenAttributeError): + pf.x = "xxx" + + assert "x" == pf.x + + @pytest.mark.parametrize( + "on_setattr", + [setters.validate, [setters.validate], setters.pipe(setters.validate)], + ) + def test_validator(self, on_setattr): + """ + Validators are run and they don't alter the value. + """ + + @attr.s(on_setattr=on_setattr) + class ValidatedAttribute(object): + x = attr.ib() + y = attr.ib(validator=[instance_of(str), matches_re("foo.*qux")]) + + va = ValidatedAttribute(42, "foobarqux") + + with pytest.raises(TypeError) as ei: + va.y = 42 + + assert "foobarqux" == va.y + + assert ei.value.args[0].startswith("'y' must be <") + + with pytest.raises(ValueError) as ei: + va.y = "quxbarfoo" + + assert ei.value.args[0].startswith("'y' must match regex '") + + assert "foobarqux" == va.y + + va.y = "foobazqux" + + assert "foobazqux" == va.y + + def test_pipe(self): + """ + Multiple hooks are possible, in that case the last return value is + used. They can be supplied using the pipe functions or by passing a + list to on_setattr. + """ + + s = [setters.convert, lambda _, __, nv: nv + 1] + + @attr.s + class Piped(object): + x1 = attr.ib(converter=int, on_setattr=setters.pipe(*s)) + x2 = attr.ib(converter=int, on_setattr=s) + + p = Piped("41", "22") + + assert 41 == p.x1 + assert 22 == p.x2 + + p.x1 = "41" + p.x2 = "22" + + assert 42 == p.x1 + assert 23 == p.x2 + + def test_make_class(self): + """ + on_setattr of make_class gets forwarded. + """ + C = attr.make_class("C", {"x": attr.ib()}, on_setattr=setters.frozen) + + c = C(1) + + with pytest.raises(FrozenAttributeError): + c.x = 2 + + def test_no_validator_no_converter(self): + """ + validate and convert tolerate missing validators and converters. + """ + + @attr.s(on_setattr=[setters.convert, setters.validate]) + class C(object): + x = attr.ib() + + c = C(1) + + c.x = 2 + + assert 2 == c.x + + def test_validate_respects_run_validators_config(self): + """ + If run validators is off, validate doesn't run them. + """ + + @attr.s(on_setattr=setters.validate) + class C(object): + x = attr.ib(validator=attr.validators.instance_of(int)) + + c = C(1) + + attr.set_run_validators(False) + + c.x = "1" + + assert "1" == c.x + + attr.set_run_validators(True) + + with pytest.raises(TypeError) as ei: + c.x = "1" + + assert ei.value.args[0].startswith("'x' must be <") + + def test_frozen_on_setattr_class_is_caught(self): + """ + @attr.s(on_setattr=X, frozen=True) raises an ValueError. + """ + with pytest.raises(ValueError) as ei: + + @attr.s(frozen=True, on_setattr=setters.validate) + class C(object): + x = attr.ib() + + assert "Frozen classes can't use on_setattr." == ei.value.args[0] + + def test_frozen_on_setattr_attribute_is_caught(self): + """ + attr.ib(on_setattr=X) on a frozen class raises an ValueError. + """ + + with pytest.raises(ValueError) as ei: + + @attr.s(frozen=True) + class C(object): + x = attr.ib(on_setattr=setters.validate) + + assert "Frozen classes can't use on_setattr." == ei.value.args[0] + + @pytest.mark.parametrize("slots", [True, False]) + def test_setattr_reset_if_no_custom_setattr(self, slots): + """ + If a class with an active setattr is subclassed and no new setattr + is generated, the __setattr__ is set to object.__setattr__. + + We do the double test because of Python 2. + """ + + def boom(*args): + pytest.fail("Must not be called.") + + @attr.s + class Hooked(object): + x = attr.ib(on_setattr=boom) + + @attr.s(slots=slots) + class NoHook(WithOnSetAttrHook): + x = attr.ib() + + if not PY2: + assert NoHook.__setattr__ == object.__setattr__ + + assert 1 == NoHook(1).x + assert Hooked.__attrs_own_setattr__ + assert not NoHook.__attrs_own_setattr__ + assert WithOnSetAttrHook.__attrs_own_setattr__ + + @pytest.mark.parametrize("slots", [True, False]) + def test_setattr_inherited_do_not_reset(self, slots): + """ + If we inherit a __setattr__ that has been written by the user, we must + not reset it unless necessary. + """ + + class A(object): + """ + Not an attrs class on purpose to prevent accidental resets that + would render the asserts meaningless. + """ + + def __setattr__(self, *args): + pass + + @attr.s(slots=slots) + class B(A): + pass + + assert B.__setattr__ == A.__setattr__ + + @attr.s(slots=slots) + class C(B): + pass + + assert C.__setattr__ == A.__setattr__ + + @pytest.mark.parametrize("slots", [True, False]) + def test_pickling_retains_attrs_own(self, slots): + """ + Pickling/Unpickling does not lose ownership information about + __setattr__. + """ + i = WithOnSetAttrHook(1) + + assert True is i.__attrs_own_setattr__ + + i2 = pickle.loads(pickle.dumps(i)) + + assert True is i2.__attrs_own_setattr__ + + WOSAH = pickle.loads(pickle.dumps(WithOnSetAttrHook)) + + assert True is WOSAH.__attrs_own_setattr__ + + def test_slotted_class_can_have_custom_setattr(self): + """ + A slotted class can define a custom setattr and it doesn't get + overwritten. + + Regression test for #680. + """ + + @attr.s(slots=True) + class A(object): + def __setattr__(self, key, value): + raise SystemError + + with pytest.raises(SystemError): + A().x = 1 + + @pytest.mark.xfail(raises=attr.exceptions.FrozenAttributeError) + def test_slotted_confused(self): + """ + If we have a in-between non-attrs class, setattr reset detection + should still work, but currently doesn't. + + It works with dict classes because we can look the finished class and + patch it. With slotted classes we have to deduce it ourselves. + """ + + @attr.s(slots=True) + class A(object): + x = attr.ib(on_setattr=setters.frozen) + + class B(A): + pass + + @attr.s(slots=True) + class C(B): + x = attr.ib() + + C(1).x = 2 + + +@pytest.mark.skipif(PY2, reason="Python 3-only.") +class TestSetAttrNoPy2(object): + """ + __setattr__ tests for Py3+ to avoid the skip repetition. + """ + + @pytest.mark.parametrize("slots", [True, False]) + def test_setattr_auto_detect_if_no_custom_setattr(self, slots): + """ + It's possible to remove the on_setattr hook from an attribute and + therefore write a custom __setattr__. + """ + assert 1 == WithOnSetAttrHook(1).x + + @attr.s(auto_detect=True, slots=slots) + class RemoveNeedForOurSetAttr(WithOnSetAttrHook): + x = attr.ib() + + def __setattr__(self, name, val): + object.__setattr__(self, name, val * 2) + + i = RemoveNeedForOurSetAttr(1) + + assert not RemoveNeedForOurSetAttr.__attrs_own_setattr__ + assert 2 == i.x + + @pytest.mark.parametrize("slots", [True, False]) + def test_setattr_restore_respects_auto_detect(self, slots): + """ + If __setattr__ should be restored but the user supplied its own and + set auto_detect, leave is alone. + """ + + @attr.s(auto_detect=True, slots=slots) + class CustomSetAttr: + def __setattr__(self, _, __): + pass + + assert CustomSetAttr.__setattr__ != object.__setattr__ + + @pytest.mark.parametrize("slots", [True, False]) + def test_setattr_auto_detect_frozen(self, slots): + """ + frozen=True together with a detected custom __setattr__ are rejected. + """ + with pytest.raises( + ValueError, match="Can't freeze a class with a custom __setattr__." + ): + + @attr.s(auto_detect=True, slots=slots, frozen=True) + class CustomSetAttr(Frozen): + def __setattr__(self, _, __): + pass + + @pytest.mark.parametrize("slots", [True, False]) + def test_setattr_auto_detect_on_setattr(self, slots): + """ + on_setattr attributes together with a detected custom __setattr__ are + rejected. + """ + with pytest.raises( + ValueError, + match="Can't combine custom __setattr__ with on_setattr hooks.", + ): + + @attr.s(auto_detect=True, slots=slots) + class HookAndCustomSetAttr(object): + x = attr.ib(on_setattr=lambda *args: None) + + def __setattr__(self, _, __): + pass + + @pytest.mark.parametrize("a_slots", [True, False]) + @pytest.mark.parametrize("b_slots", [True, False]) + @pytest.mark.parametrize("c_slots", [True, False]) + def test_setattr_inherited_do_not_reset_intermediate( + self, a_slots, b_slots, c_slots + ): + """ + A user-provided intermediate __setattr__ is not reset to + object.__setattr__. + + This only can work on Python 3+ with auto_detect activated, such that + attrs can know that there is a user-provided __setattr__. + """ + + @attr.s(slots=a_slots) + class A(object): + x = attr.ib(on_setattr=setters.frozen) + + @attr.s(slots=b_slots, auto_detect=True) + class B(A): + x = attr.ib(on_setattr=setters.NO_OP) + + def __setattr__(self, key, value): + raise SystemError + + @attr.s(slots=c_slots) + class C(B): + pass + + assert getattr(A, "__attrs_own_setattr__", False) is True + assert getattr(B, "__attrs_own_setattr__", False) is False + assert getattr(C, "__attrs_own_setattr__", False) is False + + with pytest.raises(SystemError): + C(1).x = 3 + + def test_docstring(self): + """ + Generated __setattr__ has a useful docstring. + """ + assert ( + "Method generated by attrs for class WithOnSetAttrHook." + == WithOnSetAttrHook.__setattr__.__doc__ + ) diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_slots.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_slots.py new file mode 100644 index 0000000000..baf9a40ddb --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_slots.py @@ -0,0 +1,740 @@ +# SPDX-License-Identifier: MIT + +""" +Unit tests for slots-related functionality. +""" + +import pickle +import sys +import types +import weakref + +import pytest + +import attr + +from attr._compat import PY2, PYPY, just_warn, make_set_closure_cell + + +# Pympler doesn't work on PyPy. +try: + from pympler.asizeof import asizeof + + has_pympler = True +except BaseException: # Won't be an import error. + has_pympler = False + + +@attr.s +class C1(object): + x = attr.ib(validator=attr.validators.instance_of(int)) + y = attr.ib() + + def method(self): + return self.x + + @classmethod + def classmethod(cls): + return "clsmethod" + + @staticmethod + def staticmethod(): + return "staticmethod" + + if not PY2: + + def my_class(self): + return __class__ + + def my_super(self): + """Just to test out the no-arg super.""" + return super().__repr__() + + +@attr.s(slots=True, hash=True) +class C1Slots(object): + x = attr.ib(validator=attr.validators.instance_of(int)) + y = attr.ib() + + def method(self): + return self.x + + @classmethod + def classmethod(cls): + return "clsmethod" + + @staticmethod + def staticmethod(): + return "staticmethod" + + if not PY2: + + def my_class(self): + return __class__ + + def my_super(self): + """Just to test out the no-arg super.""" + return super().__repr__() + + +def test_slots_being_used(): + """ + The class is really using __slots__. + """ + non_slot_instance = C1(x=1, y="test") + slot_instance = C1Slots(x=1, y="test") + + assert "__dict__" not in dir(slot_instance) + assert "__slots__" in dir(slot_instance) + + assert "__dict__" in dir(non_slot_instance) + assert "__slots__" not in dir(non_slot_instance) + + assert set(["__weakref__", "x", "y"]) == set(slot_instance.__slots__) + + if has_pympler: + assert asizeof(slot_instance) < asizeof(non_slot_instance) + + non_slot_instance.t = "test" + with pytest.raises(AttributeError): + slot_instance.t = "test" + + assert 1 == non_slot_instance.method() + assert 1 == slot_instance.method() + + assert attr.fields(C1Slots) == attr.fields(C1) + assert attr.asdict(slot_instance) == attr.asdict(non_slot_instance) + + +def test_basic_attr_funcs(): + """ + Comparison, `__eq__`, `__hash__`, `__repr__`, `attrs.asdict` work. + """ + a = C1Slots(x=1, y=2) + b = C1Slots(x=1, y=3) + a_ = C1Slots(x=1, y=2) + + # Comparison. + assert b > a + + assert a_ == a + + # Hashing. + hash(b) # Just to assert it doesn't raise. + + # Repr. + assert "C1Slots(x=1, y=2)" == repr(a) + + assert {"x": 1, "y": 2} == attr.asdict(a) + + +def test_inheritance_from_nonslots(): + """ + Inheritance from a non-slotted class works. + + Note that a slotted class inheriting from an ordinary class loses most of + the benefits of slotted classes, but it should still work. + """ + + @attr.s(slots=True, hash=True) + class C2Slots(C1): + z = attr.ib() + + c2 = C2Slots(x=1, y=2, z="test") + + assert 1 == c2.x + assert 2 == c2.y + assert "test" == c2.z + + c2.t = "test" # This will work, using the base class. + + assert "test" == c2.t + + assert 1 == c2.method() + assert "clsmethod" == c2.classmethod() + assert "staticmethod" == c2.staticmethod() + + assert set(["z"]) == set(C2Slots.__slots__) + + c3 = C2Slots(x=1, y=3, z="test") + + assert c3 > c2 + + c2_ = C2Slots(x=1, y=2, z="test") + + assert c2 == c2_ + + assert "C2Slots(x=1, y=2, z='test')" == repr(c2) + + hash(c2) # Just to assert it doesn't raise. + + assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2) + + +def test_nonslots_these(): + """ + Enhancing a dict class using 'these' works. + + This will actually *replace* the class with another one, using slots. + """ + + class SimpleOrdinaryClass(object): + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + + def method(self): + return self.x + + @classmethod + def classmethod(cls): + return "clsmethod" + + @staticmethod + def staticmethod(): + return "staticmethod" + + C2Slots = attr.s( + these={"x": attr.ib(), "y": attr.ib(), "z": attr.ib()}, + init=False, + slots=True, + hash=True, + )(SimpleOrdinaryClass) + + c2 = C2Slots(x=1, y=2, z="test") + assert 1 == c2.x + assert 2 == c2.y + assert "test" == c2.z + with pytest.raises(AttributeError): + c2.t = "test" # We have slots now. + + assert 1 == c2.method() + assert "clsmethod" == c2.classmethod() + assert "staticmethod" == c2.staticmethod() + + assert set(["__weakref__", "x", "y", "z"]) == set(C2Slots.__slots__) + + c3 = C2Slots(x=1, y=3, z="test") + assert c3 > c2 + c2_ = C2Slots(x=1, y=2, z="test") + assert c2 == c2_ + + assert "SimpleOrdinaryClass(x=1, y=2, z='test')" == repr(c2) + + hash(c2) # Just to assert it doesn't raise. + + assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2) + + +def test_inheritance_from_slots(): + """ + Inheriting from an attrs slotted class works. + """ + + @attr.s(slots=True, hash=True) + class C2Slots(C1Slots): + z = attr.ib() + + @attr.s(slots=True, hash=True) + class C2(C1): + z = attr.ib() + + c2 = C2Slots(x=1, y=2, z="test") + assert 1 == c2.x + assert 2 == c2.y + assert "test" == c2.z + + assert set(["z"]) == set(C2Slots.__slots__) + + assert 1 == c2.method() + assert "clsmethod" == c2.classmethod() + assert "staticmethod" == c2.staticmethod() + + with pytest.raises(AttributeError): + c2.t = "test" + + non_slot_instance = C2(x=1, y=2, z="test") + if has_pympler: + assert asizeof(c2) < asizeof(non_slot_instance) + + c3 = C2Slots(x=1, y=3, z="test") + assert c3 > c2 + c2_ = C2Slots(x=1, y=2, z="test") + assert c2 == c2_ + + assert "C2Slots(x=1, y=2, z='test')" == repr(c2) + + hash(c2) # Just to assert it doesn't raise. + + assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2) + + +def test_inheritance_from_slots_with_attribute_override(): + """ + Inheriting from a slotted class doesn't re-create existing slots + """ + + class HasXSlot(object): + __slots__ = ("x",) + + @attr.s(slots=True, hash=True) + class C2Slots(C1Slots): + # y re-defined here but it shouldn't get a slot + y = attr.ib() + z = attr.ib() + + @attr.s(slots=True, hash=True) + class NonAttrsChild(HasXSlot): + # Parent class has slot for "x" already, so we skip it + x = attr.ib() + y = attr.ib() + z = attr.ib() + + c2 = C2Slots(1, 2, "test") + assert 1 == c2.x + assert 2 == c2.y + assert "test" == c2.z + + assert {"z"} == set(C2Slots.__slots__) + + na = NonAttrsChild(1, 2, "test") + assert 1 == na.x + assert 2 == na.y + assert "test" == na.z + + assert {"__weakref__", "y", "z"} == set(NonAttrsChild.__slots__) + + +def test_inherited_slot_reuses_slot_descriptor(): + """ + We reuse slot descriptor for an attr.ib defined in a slotted attr.s + """ + + class HasXSlot(object): + __slots__ = ("x",) + + class OverridesX(HasXSlot): + @property + def x(self): + return None + + @attr.s(slots=True) + class Child(OverridesX): + x = attr.ib() + + assert Child.x is not OverridesX.x + assert Child.x is HasXSlot.x + + c = Child(1) + assert 1 == c.x + assert set() == set(Child.__slots__) + + ox = OverridesX() + assert ox.x is None + + +def test_bare_inheritance_from_slots(): + """ + Inheriting from a bare attrs slotted class works. + """ + + @attr.s( + init=False, eq=False, order=False, hash=False, repr=False, slots=True + ) + class C1BareSlots(object): + x = attr.ib(validator=attr.validators.instance_of(int)) + y = attr.ib() + + def method(self): + return self.x + + @classmethod + def classmethod(cls): + return "clsmethod" + + @staticmethod + def staticmethod(): + return "staticmethod" + + @attr.s(init=False, eq=False, order=False, hash=False, repr=False) + class C1Bare(object): + x = attr.ib(validator=attr.validators.instance_of(int)) + y = attr.ib() + + def method(self): + return self.x + + @classmethod + def classmethod(cls): + return "clsmethod" + + @staticmethod + def staticmethod(): + return "staticmethod" + + @attr.s(slots=True, hash=True) + class C2Slots(C1BareSlots): + z = attr.ib() + + @attr.s(slots=True, hash=True) + class C2(C1Bare): + z = attr.ib() + + c2 = C2Slots(x=1, y=2, z="test") + assert 1 == c2.x + assert 2 == c2.y + assert "test" == c2.z + + assert 1 == c2.method() + assert "clsmethod" == c2.classmethod() + assert "staticmethod" == c2.staticmethod() + + with pytest.raises(AttributeError): + c2.t = "test" + + non_slot_instance = C2(x=1, y=2, z="test") + if has_pympler: + assert asizeof(c2) < asizeof(non_slot_instance) + + c3 = C2Slots(x=1, y=3, z="test") + assert c3 > c2 + c2_ = C2Slots(x=1, y=2, z="test") + assert c2 == c2_ + + assert "C2Slots(x=1, y=2, z='test')" == repr(c2) + + hash(c2) # Just to assert it doesn't raise. + + assert {"x": 1, "y": 2, "z": "test"} == attr.asdict(c2) + + +@pytest.mark.skipif(PY2, reason="closure cell rewriting is PY3-only.") +class TestClosureCellRewriting(object): + def test_closure_cell_rewriting(self): + """ + Slotted classes support proper closure cell rewriting. + + This affects features like `__class__` and the no-arg super(). + """ + non_slot_instance = C1(x=1, y="test") + slot_instance = C1Slots(x=1, y="test") + + assert non_slot_instance.my_class() is C1 + assert slot_instance.my_class() is C1Slots + + # Just assert they return something, and not an exception. + assert non_slot_instance.my_super() + assert slot_instance.my_super() + + def test_inheritance(self): + """ + Slotted classes support proper closure cell rewriting when inheriting. + + This affects features like `__class__` and the no-arg super(). + """ + + @attr.s + class C2(C1): + def my_subclass(self): + return __class__ + + @attr.s + class C2Slots(C1Slots): + def my_subclass(self): + return __class__ + + non_slot_instance = C2(x=1, y="test") + slot_instance = C2Slots(x=1, y="test") + + assert non_slot_instance.my_class() is C1 + assert slot_instance.my_class() is C1Slots + + # Just assert they return something, and not an exception. + assert non_slot_instance.my_super() + assert slot_instance.my_super() + + assert non_slot_instance.my_subclass() is C2 + assert slot_instance.my_subclass() is C2Slots + + @pytest.mark.parametrize("slots", [True, False]) + def test_cls_static(self, slots): + """ + Slotted classes support proper closure cell rewriting for class- and + static methods. + """ + # Python can reuse closure cells, so we create new classes just for + # this test. + + @attr.s(slots=slots) + class C: + @classmethod + def clsmethod(cls): + return __class__ + + assert C.clsmethod() is C + + @attr.s(slots=slots) + class D: + @staticmethod + def statmethod(): + return __class__ + + assert D.statmethod() is D + + @pytest.mark.skipif(PYPY, reason="set_closure_cell always works on PyPy") + @pytest.mark.skipif( + sys.version_info >= (3, 8), + reason="can't break CodeType.replace() via monkeypatch", + ) + def test_code_hack_failure(self, monkeypatch): + """ + Keeps working if function/code object introspection doesn't work + on this (nonstandard) interpeter. + + A warning is emitted that points to the actual code. + """ + # This is a pretty good approximation of the behavior of + # the actual types.CodeType on Brython. + monkeypatch.setattr(types, "CodeType", lambda: None) + func = make_set_closure_cell() + + with pytest.warns(RuntimeWarning) as wr: + func() + + w = wr.pop() + assert __file__ == w.filename + assert ( + "Running interpreter doesn't sufficiently support code object " + "introspection. Some features like bare super() or accessing " + "__class__ will not work with slotted classes.", + ) == w.message.args + + assert just_warn is func + + +@pytest.mark.skipif(PYPY, reason="__slots__ only block weakref on CPython") +def test_not_weakrefable(): + """ + Instance is not weak-referenceable when `weakref_slot=False` in CPython. + """ + + @attr.s(slots=True, weakref_slot=False) + class C(object): + pass + + c = C() + + with pytest.raises(TypeError): + weakref.ref(c) + + +@pytest.mark.skipif( + not PYPY, reason="slots without weakref_slot should only work on PyPy" +) +def test_implicitly_weakrefable(): + """ + Instance is weak-referenceable even when `weakref_slot=False` in PyPy. + """ + + @attr.s(slots=True, weakref_slot=False) + class C(object): + pass + + c = C() + w = weakref.ref(c) + + assert c is w() + + +def test_weakrefable(): + """ + Instance is weak-referenceable when `weakref_slot=True`. + """ + + @attr.s(slots=True, weakref_slot=True) + class C(object): + pass + + c = C() + w = weakref.ref(c) + + assert c is w() + + +def test_weakref_does_not_add_a_field(): + """ + `weakref_slot=True` does not add a field to the class. + """ + + @attr.s(slots=True, weakref_slot=True) + class C(object): + field = attr.ib() + + assert [f.name for f in attr.fields(C)] == ["field"] + + +def tests_weakref_does_not_add_when_inheriting_with_weakref(): + """ + `weakref_slot=True` does not add a new __weakref__ slot when inheriting + one. + """ + + @attr.s(slots=True, weakref_slot=True) + class C(object): + pass + + @attr.s(slots=True, weakref_slot=True) + class D(C): + pass + + d = D() + w = weakref.ref(d) + + assert d is w() + + +def tests_weakref_does_not_add_with_weakref_attribute(): + """ + `weakref_slot=True` does not add a new __weakref__ slot when an attribute + of that name exists. + """ + + @attr.s(slots=True, weakref_slot=True) + class C(object): + __weakref__ = attr.ib( + init=False, hash=False, repr=False, eq=False, order=False + ) + + c = C() + w = weakref.ref(c) + + assert c is w() + + +def test_slots_empty_cell(): + """ + Tests that no `ValueError: Cell is empty` exception is raised when + closure cells are present with no contents in a `slots=True` class. + (issue https://github.com/python-attrs/attrs/issues/589) + + On Python 3, if a method mentions `__class__` or uses the no-arg `super()`, + the compiler will bake a reference to the class in the method itself as + `method.__closure__`. Since `attrs` replaces the class with a clone, + `_ClassBuilder._create_slots_class(self)` will rewrite these references so + it keeps working. This method was not properly covering the edge case where + the closure cell was empty, we fixed it and this is the non-regression + test. + """ + + @attr.s(slots=True) + class C(object): + field = attr.ib() + + def f(self, a): + super(C, self).__init__() + + C(field=1) + + +@attr.s(getstate_setstate=True) +class C2(object): + x = attr.ib() + + +@attr.s(slots=True, getstate_setstate=True) +class C2Slots(object): + x = attr.ib() + + +class TestPickle(object): + @pytest.mark.parametrize("protocol", range(pickle.HIGHEST_PROTOCOL)) + def test_pickleable_by_default(self, protocol): + """ + If nothing else is passed, slotted classes can be pickled and + unpickled with all supported protocols. + """ + i1 = C1Slots(1, 2) + i2 = pickle.loads(pickle.dumps(i1, protocol)) + + assert i1 == i2 + assert i1 is not i2 + + def test_no_getstate_setstate_for_dict_classes(self): + """ + As long as getstate_setstate is None, nothing is done to dict + classes. + """ + i = C1(1, 2) + + assert None is getattr(i, "__getstate__", None) + assert None is getattr(i, "__setstate__", None) + + def test_no_getstate_setstate_if_option_false(self): + """ + Don't add getstate/setstate if getstate_setstate is False. + """ + + @attr.s(slots=True, getstate_setstate=False) + class C(object): + x = attr.ib() + + i = C(42) + + assert None is getattr(i, "__getstate__", None) + assert None is getattr(i, "__setstate__", None) + + @pytest.mark.parametrize("cls", [C2(1), C2Slots(1)]) + def test_getstate_set_state_force_true(self, cls): + """ + If getstate_setstate is True, add them unconditionally. + """ + assert None is not getattr(cls, "__getstate__", None) + assert None is not getattr(cls, "__setstate__", None) + + +def test_slots_super_property_get(): + """ + On Python 2/3: the `super(self.__class__, self)` works. + """ + + @attr.s(slots=True) + class A(object): + x = attr.ib() + + @property + def f(self): + return self.x + + @attr.s(slots=True) + class B(A): + @property + def f(self): + return super(B, self).f ** 2 + + assert B(11).f == 121 + assert B(17).f == 289 + + +@pytest.mark.skipif(PY2, reason="shortcut super() is PY3-only.") +def test_slots_super_property_get_shurtcut(): + """ + On Python 3, the `super()` shortcut is allowed. + """ + + @attr.s(slots=True) + class A(object): + x = attr.ib() + + @property + def f(self): + return self.x + + @attr.s(slots=True) + class B(A): + @property + def f(self): + return super().f ** 2 + + assert B(11).f == 121 + assert B(17).f == 289 diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_validators.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_validators.py new file mode 100644 index 0000000000..d7c6de8bad --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_validators.py @@ -0,0 +1,952 @@ +# SPDX-License-Identifier: MIT + +""" +Tests for `attr.validators`. +""" + +from __future__ import absolute_import, division, print_function + +import re + +import pytest + +import attr + +from attr import _config, fields, has +from attr import validators as validator_module +from attr._compat import PY2, TYPE +from attr.validators import ( + and_, + deep_iterable, + deep_mapping, + ge, + gt, + in_, + instance_of, + is_callable, + le, + lt, + matches_re, + max_len, + optional, + provides, +) + +from .utils import simple_attr + + +@pytest.fixture(scope="module") +def zope_interface(): + """Provides ``zope.interface`` if available, skipping the test if not.""" + try: + import zope.interface + except ImportError: + raise pytest.skip( + "zope-related tests skipped when zope.interface is not installed" + ) + + return zope.interface + + +class TestDisableValidators(object): + @pytest.fixture(autouse=True) + def reset_default(self): + """ + Make sure validators are always enabled after a test. + """ + yield + _config._run_validators = True + + def test_default(self): + """ + Run validators by default. + """ + assert _config._run_validators is True + + @pytest.mark.parametrize("value, expected", [(True, False), (False, True)]) + def test_set_validators_diabled(self, value, expected): + """ + Sets `_run_validators`. + """ + validator_module.set_disabled(value) + + assert _config._run_validators is expected + + @pytest.mark.parametrize("value, expected", [(True, False), (False, True)]) + def test_disabled(self, value, expected): + """ + Returns `_run_validators`. + """ + _config._run_validators = value + + assert validator_module.get_disabled() is expected + + def test_disabled_ctx(self): + """ + The `disabled` context manager disables running validators, + but only within its context. + """ + assert _config._run_validators is True + + with validator_module.disabled(): + assert _config._run_validators is False + + assert _config._run_validators is True + + def test_disabled_ctx_with_errors(self): + """ + Running validators is re-enabled even if an error is raised. + """ + assert _config._run_validators is True + + with pytest.raises(ValueError): + with validator_module.disabled(): + assert _config._run_validators is False + + raise ValueError("haha!") + + assert _config._run_validators is True + + +class TestInstanceOf(object): + """ + Tests for `instance_of`. + """ + + def test_in_all(self): + """ + Verify that this validator is in ``__all__``. + """ + assert instance_of.__name__ in validator_module.__all__ + + def test_success(self): + """ + Nothing happens if types match. + """ + v = instance_of(int) + v(None, simple_attr("test"), 42) + + def test_subclass(self): + """ + Subclasses are accepted too. + """ + v = instance_of(int) + # yep, bools are a subclass of int :( + v(None, simple_attr("test"), True) + + def test_fail(self): + """ + Raises `TypeError` on wrong types. + """ + v = instance_of(int) + a = simple_attr("test") + with pytest.raises(TypeError) as e: + v(None, a, "42") + assert ( + "'test' must be <{type} 'int'> (got '42' that is a <{type} " + "'str'>).".format(type=TYPE), + a, + int, + "42", + ) == e.value.args + + def test_repr(self): + """ + Returned validator has a useful `__repr__`. + """ + v = instance_of(int) + assert ( + ">".format(type=TYPE) + ) == repr(v) + + +class TestMatchesRe(object): + """ + Tests for `matches_re`. + """ + + def test_in_all(self): + """ + validator is in ``__all__``. + """ + assert matches_re.__name__ in validator_module.__all__ + + def test_match(self): + """ + Silent on matches, raises ValueError on mismatches. + """ + + @attr.s + class ReTester(object): + str_match = attr.ib(validator=matches_re("a|ab")) + + ReTester("ab") # shouldn't raise exceptions + with pytest.raises(TypeError): + ReTester(1) + with pytest.raises(ValueError): + ReTester("1") + with pytest.raises(ValueError): + ReTester("a1") + + def test_flags(self): + """ + Flags are propagated to the match function. + """ + + @attr.s + class MatchTester(object): + val = attr.ib(validator=matches_re("a", re.IGNORECASE, re.match)) + + MatchTester("A1") # test flags and using re.match + + def test_precompiled_pattern(self): + """ + Pre-compiled patterns are accepted. + """ + pattern = re.compile("a") + + @attr.s + class RePatternTester(object): + val = attr.ib(validator=matches_re(pattern)) + + RePatternTester("a") + + def test_precompiled_pattern_no_flags(self): + """ + A pre-compiled pattern cannot be combined with a 'flags' argument. + """ + pattern = re.compile("") + + with pytest.raises( + TypeError, match="can only be used with a string pattern" + ): + matches_re(pattern, flags=re.IGNORECASE) + + def test_different_func(self): + """ + Changing the match functions works. + """ + + @attr.s + class SearchTester(object): + val = attr.ib(validator=matches_re("a", 0, re.search)) + + SearchTester("bab") # re.search will match + + def test_catches_invalid_func(self): + """ + Invalid match functions are caught. + """ + with pytest.raises(ValueError) as ei: + matches_re("a", 0, lambda: None) + + if not PY2: + assert ( + "'func' must be one of None, fullmatch, match, search." + == ei.value.args[0] + ) + else: + assert ( + "'func' must be one of None, match, search." + == ei.value.args[0] + ) + + @pytest.mark.parametrize( + "func", [None, getattr(re, "fullmatch", None), re.match, re.search] + ) + def test_accepts_all_valid_func(self, func): + """ + Every valid match function is accepted. + """ + matches_re("a", func=func) + + def test_repr(self): + """ + __repr__ is meaningful. + """ + assert repr(matches_re("a")).startswith( + "".format( + interface=ifoo + ) + ) == repr(v) + + +@pytest.mark.parametrize( + "validator", [instance_of(int), [always_pass, instance_of(int)]] +) +class TestOptional(object): + """ + Tests for `optional`. + """ + + def test_in_all(self, validator): + """ + Verify that this validator is in ``__all__``. + """ + assert optional.__name__ in validator_module.__all__ + + def test_success(self, validator): + """ + Nothing happens if validator succeeds. + """ + v = optional(validator) + v(None, simple_attr("test"), 42) + + def test_success_with_none(self, validator): + """ + Nothing happens if None. + """ + v = optional(validator) + v(None, simple_attr("test"), None) + + def test_fail(self, validator): + """ + Raises `TypeError` on wrong types. + """ + v = optional(validator) + a = simple_attr("test") + with pytest.raises(TypeError) as e: + v(None, a, "42") + assert ( + "'test' must be <{type} 'int'> (got '42' that is a <{type} " + "'str'>).".format(type=TYPE), + a, + int, + "42", + ) == e.value.args + + def test_repr(self, validator): + """ + Returned validator has a useful `__repr__`. + """ + v = optional(validator) + + if isinstance(validator, list): + repr_s = ( + ">]) or None>" + ).format(func=repr(always_pass), type=TYPE) + else: + repr_s = ( + "> or None>" + ).format(type=TYPE) + + assert repr_s == repr(v) + + +class TestIn_(object): + """ + Tests for `in_`. + """ + + def test_in_all(self): + """ + Verify that this validator is in ``__all__``. + """ + assert in_.__name__ in validator_module.__all__ + + def test_success_with_value(self): + """ + If the value is in our options, nothing happens. + """ + v = in_([1, 2, 3]) + a = simple_attr("test") + v(1, a, 3) + + def test_fail(self): + """ + Raise ValueError if the value is outside our options. + """ + v = in_([1, 2, 3]) + a = simple_attr("test") + with pytest.raises(ValueError) as e: + v(None, a, None) + assert ("'test' must be in [1, 2, 3] (got None)",) == e.value.args + + def test_fail_with_string(self): + """ + Raise ValueError if the value is outside our options when the + options are specified as a string and the value is not a string. + """ + v = in_("abc") + a = simple_attr("test") + with pytest.raises(ValueError) as e: + v(None, a, None) + assert ("'test' must be in 'abc' (got None)",) == e.value.args + + def test_repr(self): + """ + Returned validator has a useful `__repr__`. + """ + v = in_([3, 4, 5]) + assert (("")) == repr(v) + + +class TestDeepIterable(object): + """ + Tests for `deep_iterable`. + """ + + def test_in_all(self): + """ + Verify that this validator is in ``__all__``. + """ + assert deep_iterable.__name__ in validator_module.__all__ + + def test_success_member_only(self): + """ + If the member validator succeeds and the iterable validator is not set, + nothing happens. + """ + member_validator = instance_of(int) + v = deep_iterable(member_validator) + a = simple_attr("test") + v(None, a, [42]) + + def test_success_member_and_iterable(self): + """ + If both the member and iterable validators succeed, nothing happens. + """ + member_validator = instance_of(int) + iterable_validator = instance_of(list) + v = deep_iterable(member_validator, iterable_validator) + a = simple_attr("test") + v(None, a, [42]) + + @pytest.mark.parametrize( + "member_validator, iterable_validator", + ( + (instance_of(int), 42), + (42, instance_of(list)), + (42, 42), + (42, None), + ), + ) + def test_noncallable_validators( + self, member_validator, iterable_validator + ): + """ + Raise `TypeError` if any validators are not callable. + """ + with pytest.raises(TypeError) as e: + deep_iterable(member_validator, iterable_validator) + value = 42 + message = "must be callable (got {value} that is a {type_}).".format( + value=value, type_=value.__class__ + ) + + assert message in e.value.args[0] + assert value == e.value.args[1] + assert message in e.value.msg + assert value == e.value.value + + def test_fail_invalid_member(self): + """ + Raise member validator error if an invalid member is found. + """ + member_validator = instance_of(int) + v = deep_iterable(member_validator) + a = simple_attr("test") + with pytest.raises(TypeError): + v(None, a, [42, "42"]) + + def test_fail_invalid_iterable(self): + """ + Raise iterable validator error if an invalid iterable is found. + """ + member_validator = instance_of(int) + iterable_validator = instance_of(tuple) + v = deep_iterable(member_validator, iterable_validator) + a = simple_attr("test") + with pytest.raises(TypeError): + v(None, a, [42]) + + def test_fail_invalid_member_and_iterable(self): + """ + Raise iterable validator error if both the iterable + and a member are invalid. + """ + member_validator = instance_of(int) + iterable_validator = instance_of(tuple) + v = deep_iterable(member_validator, iterable_validator) + a = simple_attr("test") + with pytest.raises(TypeError): + v(None, a, [42, "42"]) + + def test_repr_member_only(self): + """ + Returned validator has a useful `__repr__` + when only member validator is set. + """ + member_validator = instance_of(int) + member_repr = ">".format( + type=TYPE + ) + v = deep_iterable(member_validator) + expected_repr = ( + "" + ).format(member_repr=member_repr) + assert ((expected_repr)) == repr(v) + + def test_repr_member_and_iterable(self): + """ + Returned validator has a useful `__repr__` when both member + and iterable validators are set. + """ + member_validator = instance_of(int) + member_repr = ">".format( + type=TYPE + ) + iterable_validator = instance_of(list) + iterable_repr = ( + ">" + ).format(type=TYPE) + v = deep_iterable(member_validator, iterable_validator) + expected_repr = ( + "" + ).format(iterable_repr=iterable_repr, member_repr=member_repr) + assert expected_repr == repr(v) + + +class TestDeepMapping(object): + """ + Tests for `deep_mapping`. + """ + + def test_in_all(self): + """ + Verify that this validator is in ``__all__``. + """ + assert deep_mapping.__name__ in validator_module.__all__ + + def test_success(self): + """ + If both the key and value validators succeed, nothing happens. + """ + key_validator = instance_of(str) + value_validator = instance_of(int) + v = deep_mapping(key_validator, value_validator) + a = simple_attr("test") + v(None, a, {"a": 6, "b": 7}) + + @pytest.mark.parametrize( + "key_validator, value_validator, mapping_validator", + ( + (42, instance_of(int), None), + (instance_of(str), 42, None), + (instance_of(str), instance_of(int), 42), + (42, 42, None), + (42, 42, 42), + ), + ) + def test_noncallable_validators( + self, key_validator, value_validator, mapping_validator + ): + """ + Raise `TypeError` if any validators are not callable. + """ + with pytest.raises(TypeError) as e: + deep_mapping(key_validator, value_validator, mapping_validator) + + value = 42 + message = "must be callable (got {value} that is a {type_}).".format( + value=value, type_=value.__class__ + ) + + assert message in e.value.args[0] + assert value == e.value.args[1] + assert message in e.value.msg + assert value == e.value.value + + def test_fail_invalid_mapping(self): + """ + Raise `TypeError` if mapping validator fails. + """ + key_validator = instance_of(str) + value_validator = instance_of(int) + mapping_validator = instance_of(dict) + v = deep_mapping(key_validator, value_validator, mapping_validator) + a = simple_attr("test") + with pytest.raises(TypeError): + v(None, a, None) + + def test_fail_invalid_key(self): + """ + Raise key validator error if an invalid key is found. + """ + key_validator = instance_of(str) + value_validator = instance_of(int) + v = deep_mapping(key_validator, value_validator) + a = simple_attr("test") + with pytest.raises(TypeError): + v(None, a, {"a": 6, 42: 7}) + + def test_fail_invalid_member(self): + """ + Raise key validator error if an invalid member value is found. + """ + key_validator = instance_of(str) + value_validator = instance_of(int) + v = deep_mapping(key_validator, value_validator) + a = simple_attr("test") + with pytest.raises(TypeError): + v(None, a, {"a": "6", "b": 7}) + + def test_repr(self): + """ + Returned validator has a useful `__repr__`. + """ + key_validator = instance_of(str) + key_repr = ">".format( + type=TYPE + ) + value_validator = instance_of(int) + value_repr = ">".format( + type=TYPE + ) + v = deep_mapping(key_validator, value_validator) + expected_repr = ( + "" + ).format(key_repr=key_repr, value_repr=value_repr) + assert expected_repr == repr(v) + + +class TestIsCallable(object): + """ + Tests for `is_callable`. + """ + + def test_in_all(self): + """ + Verify that this validator is in ``__all__``. + """ + assert is_callable.__name__ in validator_module.__all__ + + def test_success(self): + """ + If the value is callable, nothing happens. + """ + v = is_callable() + a = simple_attr("test") + v(None, a, isinstance) + + def test_fail(self): + """ + Raise TypeError if the value is not callable. + """ + v = is_callable() + a = simple_attr("test") + with pytest.raises(TypeError) as e: + v(None, a, None) + + value = None + message = "'test' must be callable (got {value} that is a {type_})." + expected_message = message.format(value=value, type_=value.__class__) + + assert expected_message == e.value.args[0] + assert value == e.value.args[1] + assert expected_message == e.value.msg + assert value == e.value.value + + def test_repr(self): + """ + Returned validator has a useful `__repr__`. + """ + v = is_callable() + assert "" == repr(v) + + def test_exception_repr(self): + """ + Verify that NotCallableError exception has a useful `__str__`. + """ + from attr.exceptions import NotCallableError + + instance = NotCallableError(msg="Some Message", value=42) + assert "Some Message" == str(instance) + + +def test_hashability(): + """ + Validator classes are hashable. + """ + for obj_name in dir(validator_module): + obj = getattr(validator_module, obj_name) + if not has(obj): + continue + hash_func = getattr(obj, "__hash__", None) + assert hash_func is not None + assert hash_func is not object.__hash__ + + +class TestLtLeGeGt: + """ + Tests for `max_len`. + """ + + BOUND = 4 + + def test_in_all(self): + """ + validator is in ``__all__``. + """ + assert all( + f.__name__ in validator_module.__all__ for f in [lt, le, ge, gt] + ) + + @pytest.mark.parametrize("v", [lt, le, ge, gt]) + def test_retrieve_bound(self, v): + """ + The configured bound for the comparison can be extracted from the + Attribute. + """ + + @attr.s + class Tester(object): + value = attr.ib(validator=v(self.BOUND)) + + assert fields(Tester).value.validator.bound == self.BOUND + + @pytest.mark.parametrize( + "v, value", + [ + (lt, 3), + (le, 3), + (le, 4), + (ge, 4), + (ge, 5), + (gt, 5), + ], + ) + def test_check_valid(self, v, value): + """Silent if value {op} bound.""" + + @attr.s + class Tester(object): + value = attr.ib(validator=v(self.BOUND)) + + Tester(value) # shouldn't raise exceptions + + @pytest.mark.parametrize( + "v, value", + [ + (lt, 4), + (le, 5), + (ge, 3), + (gt, 4), + ], + ) + def test_check_invalid(self, v, value): + """Raise ValueError if value {op} bound.""" + + @attr.s + class Tester(object): + value = attr.ib(validator=v(self.BOUND)) + + with pytest.raises(ValueError): + Tester(value) + + @pytest.mark.parametrize("v", [lt, le, ge, gt]) + def test_repr(self, v): + """ + __repr__ is meaningful. + """ + nv = v(23) + assert repr(nv) == "".format( + op=nv.compare_op, bound=23 + ) + + +class TestMaxLen: + """ + Tests for `max_len`. + """ + + MAX_LENGTH = 4 + + def test_in_all(self): + """ + validator is in ``__all__``. + """ + assert max_len.__name__ in validator_module.__all__ + + def test_retrieve_max_len(self): + """ + The configured max. length can be extracted from the Attribute + """ + + @attr.s + class Tester(object): + value = attr.ib(validator=max_len(self.MAX_LENGTH)) + + assert fields(Tester).value.validator.max_length == self.MAX_LENGTH + + @pytest.mark.parametrize( + "value", + [ + "", + "foo", + "spam", + [], + list(range(MAX_LENGTH)), + {"spam": 3, "eggs": 4}, + ], + ) + def test_check_valid(self, value): + """ + Silent if len(value) <= max_len. + Values can be strings and other iterables. + """ + + @attr.s + class Tester(object): + value = attr.ib(validator=max_len(self.MAX_LENGTH)) + + Tester(value) # shouldn't raise exceptions + + @pytest.mark.parametrize( + "value", + [ + "bacon", + list(range(6)), + ], + ) + def test_check_invalid(self, value): + """ + Raise ValueError if len(value) > max_len. + """ + + @attr.s + class Tester(object): + value = attr.ib(validator=max_len(self.MAX_LENGTH)) + + with pytest.raises(ValueError): + Tester(value) + + def test_repr(self): + """ + __repr__ is meaningful. + """ + assert repr(max_len(23)) == "" diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/test_version_info.py b/testing/web-platform/tests/tools/third_party/attrs/tests/test_version_info.py new file mode 100644 index 0000000000..41f75f47a6 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/test_version_info.py @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: MIT + +from __future__ import absolute_import, division, print_function + +import pytest + +from attr import VersionInfo +from attr._compat import PY2 + + +@pytest.fixture(name="vi") +def fixture_vi(): + return VersionInfo(19, 2, 0, "final") + + +class TestVersionInfo: + def test_from_string_no_releaselevel(self, vi): + """ + If there is no suffix, the releaselevel becomes "final" by default. + """ + assert vi == VersionInfo._from_version_string("19.2.0") + + def test_suffix_is_preserved(self): + """ + If there is a suffix, it's preserved. + """ + assert ( + "dev0" + == VersionInfo._from_version_string("19.2.0.dev0").releaselevel + ) + + @pytest.mark.skipif( + PY2, reason="Python 2 is too YOLO to care about comparability." + ) + @pytest.mark.parametrize("other", [(), (19, 2, 0, "final", "garbage")]) + def test_wrong_len(self, vi, other): + """ + Comparing with a tuple that has the wrong length raises an error. + """ + assert vi != other + + with pytest.raises(TypeError): + vi < other + + @pytest.mark.parametrize("other", [[19, 2, 0, "final"]]) + def test_wrong_type(self, vi, other): + """ + Only compare to other VersionInfos or tuples. + """ + assert vi != other + + def test_order(self, vi): + """ + Ordering works as expected. + """ + assert vi < (20,) + assert vi < (19, 2, 1) + assert vi > (0,) + assert vi <= (19, 2) + assert vi >= (19, 2) + assert vi > (19, 2, 0, "dev0") + assert vi < (19, 2, 0, "post1") diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/typing_example.py b/testing/web-platform/tests/tools/third_party/attrs/tests/typing_example.py new file mode 100644 index 0000000000..a85c768c10 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/typing_example.py @@ -0,0 +1,420 @@ +# SPDX-License-Identifier: MIT + +import re + +from typing import Any, Dict, List, Tuple, Union + +import attr +import attrs + + +# Typing via "type" Argument --- + + +@attr.s +class C: + a = attr.ib(type=int) + + +c = C(1) +C(a=1) + + +@attr.s +class D: + x = attr.ib(type=List[int]) + + +@attr.s +class E: + y = attr.ib(type="List[int]") + + +@attr.s +class F: + z = attr.ib(type=Any) + + +# Typing via Annotations --- + + +@attr.s +class CC: + a: int = attr.ib() + + +cc = CC(1) +CC(a=1) + + +@attr.s +class DD: + x: List[int] = attr.ib() + + +@attr.s +class EE: + y: "List[int]" = attr.ib() + + +@attr.s +class FF: + z: Any = attr.ib() + + +@attrs.define +class FFF: + z: int + + +FFF(1) + + +# Inheritance -- + + +@attr.s +class GG(DD): + y: str = attr.ib() + + +GG(x=[1], y="foo") + + +@attr.s +class HH(DD, EE): + z: float = attr.ib() + + +HH(x=[1], y=[], z=1.1) + + +# same class +c == cc + + +# Exceptions +@attr.s(auto_exc=True) +class Error(Exception): + x: int = attr.ib() + + +try: + raise Error(1) +except Error as e: + e.x + e.args + str(e) + + +@attrs.define +class Error2(Exception): + x: int + + +try: + raise Error2(1) +except Error as e: + e.x + e.args + str(e) + + +# Converters +# XXX: Currently converters can only be functions so none of this works +# although the stubs should be correct. + +# @attr.s +# class ConvCOptional: +# x: Optional[int] = attr.ib(converter=attr.converters.optional(int)) + + +# ConvCOptional(1) +# ConvCOptional(None) + + +# @attr.s +# class ConvCDefaultIfNone: +# x: int = attr.ib(converter=attr.converters.default_if_none(42)) + + +# ConvCDefaultIfNone(1) +# ConvCDefaultIfNone(None) + + +# @attr.s +# class ConvCToBool: +# x: int = attr.ib(converter=attr.converters.to_bool) + + +# ConvCToBool(1) +# ConvCToBool(True) +# ConvCToBool("on") +# ConvCToBool("yes") +# ConvCToBool(0) +# ConvCToBool(False) +# ConvCToBool("n") + + +# Validators +@attr.s +class Validated: + a = attr.ib( + type=List[C], + validator=attr.validators.deep_iterable( + attr.validators.instance_of(C), attr.validators.instance_of(list) + ), + ) + a = attr.ib( + type=Tuple[C], + validator=attr.validators.deep_iterable( + attr.validators.instance_of(C), attr.validators.instance_of(tuple) + ), + ) + b = attr.ib( + type=List[C], + validator=attr.validators.deep_iterable( + attr.validators.instance_of(C) + ), + ) + c = attr.ib( + type=Dict[C, D], + validator=attr.validators.deep_mapping( + attr.validators.instance_of(C), + attr.validators.instance_of(D), + attr.validators.instance_of(dict), + ), + ) + d = attr.ib( + type=Dict[C, D], + validator=attr.validators.deep_mapping( + attr.validators.instance_of(C), attr.validators.instance_of(D) + ), + ) + e: str = attr.ib(validator=attr.validators.matches_re(re.compile(r"foo"))) + f: str = attr.ib( + validator=attr.validators.matches_re(r"foo", flags=42, func=re.search) + ) + + # Test different forms of instance_of + g: int = attr.ib(validator=attr.validators.instance_of(int)) + h: int = attr.ib(validator=attr.validators.instance_of((int,))) + j: Union[int, str] = attr.ib( + validator=attr.validators.instance_of((int, str)) + ) + k: Union[int, str, C] = attr.ib( + validator=attrs.validators.instance_of((int, C, str)) + ) + + +@attr.define +class Validated2: + num: int = attr.field(validator=attr.validators.ge(0)) + + +@attrs.define +class Validated3: + num: int = attr.field(validator=attr.validators.ge(0)) + + +with attr.validators.disabled(): + Validated2(num=-1) + +with attrs.validators.disabled(): + Validated3(num=-1) + +try: + attr.validators.set_disabled(True) + Validated2(num=-1) +finally: + attr.validators.set_disabled(False) + + +# Custom repr() +@attr.s +class WithCustomRepr: + a: int = attr.ib(repr=True) + b: str = attr.ib(repr=False) + c: str = attr.ib(repr=lambda value: "c is for cookie") + d: bool = attr.ib(repr=str) + + +@attrs.define +class WithCustomRepr2: + a: int = attrs.field(repr=True) + b: str = attrs.field(repr=False) + c: str = attrs.field(repr=lambda value: "c is for cookie") + d: bool = attrs.field(repr=str) + + +# Check some of our own types +@attr.s(eq=True, order=False) +class OrderFlags: + a: int = attr.ib(eq=False, order=False) + b: int = attr.ib(eq=True, order=True) + + +# on_setattr hooks +@attr.s(on_setattr=attr.setters.validate) +class ValidatedSetter: + a: int + b: str = attr.ib(on_setattr=attr.setters.NO_OP) + c: bool = attr.ib(on_setattr=attr.setters.frozen) + d: int = attr.ib(on_setattr=[attr.setters.convert, attr.setters.validate]) + e: bool = attr.ib( + on_setattr=attr.setters.pipe( + attr.setters.convert, attr.setters.validate + ) + ) + + +@attrs.define(on_setattr=attr.setters.validate) +class ValidatedSetter2: + a: int + b: str = attrs.field(on_setattr=attrs.setters.NO_OP) + c: bool = attrs.field(on_setattr=attrs.setters.frozen) + d: int = attrs.field( + on_setattr=[attrs.setters.convert, attrs.setters.validate] + ) + e: bool = attrs.field( + on_setattr=attrs.setters.pipe( + attrs.setters.convert, attrs.setters.validate + ) + ) + + +# field_transformer +def ft_hook(cls: type, attribs: List[attr.Attribute]) -> List[attr.Attribute]: + return attribs + + +# field_transformer +def ft_hook2( + cls: type, attribs: List[attrs.Attribute] +) -> List[attrs.Attribute]: + return attribs + + +@attr.s(field_transformer=ft_hook) +class TransformedAttrs: + x: int + + +@attrs.define(field_transformer=ft_hook2) +class TransformedAttrs2: + x: int + + +# Auto-detect +@attr.s(auto_detect=True) +class AutoDetect: + x: int + + def __init__(self, x: int): + self.x = x + + +# Provisional APIs +@attr.define(order=True) +class NGClass: + x: int = attr.field(default=42) + + +ngc = NGClass(1) + + +@attr.mutable(slots=False) +class NGClass2: + x: int + + +ngc2 = NGClass2(1) + + +@attr.frozen(str=True) +class NGFrozen: + x: int + + +ngf = NGFrozen(1) + +attr.fields(NGFrozen).x.evolve(eq=False) +a = attr.fields(NGFrozen).x +a.evolve(repr=False) + + +attrs.fields(NGFrozen).x.evolve(eq=False) +a = attrs.fields(NGFrozen).x +a.evolve(repr=False) + + +@attr.s(collect_by_mro=True) +class MRO: + pass + + +@attr.s +class FactoryTest: + a: List[int] = attr.ib(default=attr.Factory(list)) + b: List[Any] = attr.ib(default=attr.Factory(list, False)) + c: List[int] = attr.ib(default=attr.Factory((lambda s: s.a), True)) + + +@attrs.define +class FactoryTest2: + a: List[int] = attrs.field(default=attrs.Factory(list)) + b: List[Any] = attrs.field(default=attrs.Factory(list, False)) + c: List[int] = attrs.field(default=attrs.Factory((lambda s: s.a), True)) + + +attrs.asdict(FactoryTest2()) +attr.asdict(FactoryTest(), tuple_keys=True) + + +# Check match_args stub +@attr.s(match_args=False) +class MatchArgs: + a: int = attr.ib() + b: int = attr.ib() + + +attr.asdict(FactoryTest()) +attr.asdict(FactoryTest(), retain_collection_types=False) + + +# Check match_args stub +@attrs.define(match_args=False) +class MatchArgs2: + a: int + b: int + + +# NG versions of asdict/astuple +attrs.asdict(MatchArgs2(1, 2)) +attrs.astuple(MatchArgs2(1, 2)) + + +def importing_from_attr() -> None: + """ + Use a function to keep the ns clean. + """ + from attr.converters import optional + from attr.exceptions import FrozenError + from attr.filters import include + from attr.setters import frozen + from attr.validators import and_ + + assert optional and FrozenError and include and frozen and and_ + + +def importing_from_attrs() -> None: + """ + Use a function to keep the ns clean. + """ + from attrs.converters import optional + from attrs.exceptions import FrozenError + from attrs.filters import include + from attrs.setters import frozen + from attrs.validators import and_ + + assert optional and FrozenError and include and frozen and and_ diff --git a/testing/web-platform/tests/tools/third_party/attrs/tests/utils.py b/testing/web-platform/tests/tools/third_party/attrs/tests/utils.py new file mode 100644 index 0000000000..a2fefbd606 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tests/utils.py @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: MIT + +""" +Common helper functions for tests. +""" + +from __future__ import absolute_import, division, print_function + +from attr import Attribute +from attr._make import NOTHING, make_class + + +def simple_class( + eq=False, + order=False, + repr=False, + hash=False, + str=False, + slots=False, + frozen=False, + cache_hash=False, +): + """ + Return a new simple class. + """ + return make_class( + "C", + ["a", "b"], + eq=eq or order, + order=order, + repr=repr, + hash=hash, + init=True, + slots=slots, + str=str, + frozen=frozen, + cache_hash=cache_hash, + ) + + +def simple_attr( + name, + default=NOTHING, + validator=None, + repr=True, + eq=True, + hash=None, + init=True, + converter=None, + kw_only=False, + inherited=False, +): + """ + Return an attribute with a name and no other bells and whistles. + """ + return Attribute( + name=name, + default=default, + validator=validator, + repr=repr, + cmp=None, + eq=eq, + hash=hash, + init=init, + converter=converter, + kw_only=kw_only, + inherited=inherited, + ) + + +class TestSimpleClass(object): + """ + Tests for the testing helper function `make_class`. + """ + + def test_returns_class(self): + """ + Returns a class object. + """ + assert type is simple_class().__class__ + + def returns_distinct_classes(self): + """ + Each call returns a completely new class. + """ + assert simple_class() is not simple_class() diff --git a/testing/web-platform/tests/tools/third_party/attrs/tox.ini b/testing/web-platform/tests/tools/third_party/attrs/tox.ini new file mode 100644 index 0000000000..ddcbc4dbbc --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/attrs/tox.ini @@ -0,0 +1,129 @@ +[pytest] +addopts = -ra +testpaths = tests +xfail_strict = true +filterwarnings = + once::Warning + ignore:::pympler[.*] + + +# Keep docs in sync with docs env and .readthedocs.yml. +[gh-actions] +python = + 2.7: py27 + 3.5: py35 + 3.6: py36 + 3.7: py37 + 3.8: py38, changelog + 3.9: py39, pyright + 3.10: py310, manifest, typing, docs + pypy-2: pypy + pypy-3: pypy3 + + +[tox] +envlist = typing,pre-commit,py27,py35,py36,py37,py38,py39,py310,pypy,pypy3,pyright,manifest,docs,pypi-description,changelog,coverage-report +isolated_build = True + + +[testenv:docs] +# Keep basepython in sync with gh-actions and .readthedocs.yml. +basepython = python3.10 +extras = docs +commands = + sphinx-build -n -T -W -b html -d {envtmpdir}/doctrees docs docs/_build/html + sphinx-build -n -T -W -b doctest -d {envtmpdir}/doctrees docs docs/_build/html + python -m doctest README.rst + + +[testenv] +extras = tests +commands = python -m pytest {posargs} + + +[testenv:py27] +extras = tests +commands = coverage run -m pytest {posargs} + + +[testenv:py37] +extras = tests +commands = coverage run -m pytest {posargs} + + +[testenv:py310] +# Python 3.6+ has a number of compile-time warnings on invalid string escapes. +# PYTHONWARNINGS=d and --no-compile below make them visible during the Tox run. +basepython = python3.10 +install_command = pip install --no-compile {opts} {packages} +setenv = + PYTHONWARNINGS=d +extras = tests +commands = coverage run -m pytest {posargs} + + +[testenv:coverage-report] +basepython = python3.10 +depends = py27,py37,py310 +skip_install = true +deps = coverage[toml]>=5.4 +commands = + coverage combine + coverage report + + +[testenv:pre-commit] +basepython = python3.10 +skip_install = true +deps = + pre-commit +passenv = HOMEPATH # needed on Windows +commands = + pre-commit run --all-files + + +[testenv:manifest] +basepython = python3.10 +deps = check-manifest +skip_install = true +commands = check-manifest + + +[testenv:pypi-description] +basepython = python3.8 +skip_install = true +deps = + twine + pip >= 18.0.0 +commands = + pip wheel -w {envtmpdir}/build --no-deps . + twine check {envtmpdir}/build/* + + +[testenv:changelog] +basepython = python3.8 +deps = towncrier<21.3 +skip_install = true +commands = towncrier --draft + + +[testenv:typing] +basepython = python3.10 +deps = mypy>=0.902 +commands = + mypy src/attr/__init__.pyi src/attr/_version_info.pyi src/attr/converters.pyi src/attr/exceptions.pyi src/attr/filters.pyi src/attr/setters.pyi src/attr/validators.pyi + mypy tests/typing_example.py + + +[testenv:pyright] +# Install and configure node and pyright +# This *could* be folded into a custom install_command +# Use nodeenv to configure node in the running tox virtual environment +# Seeing errors using "nodeenv -p" +# Use npm install -g to install "globally" into the virtual environment +basepython = python3.9 +deps = nodeenv +commands = + nodeenv --prebuilt --node=lts --force {envdir} + npm install -g --no-package-lock --no-save pyright + pytest tests/test_pyright.py -vv diff --git a/testing/web-platform/tests/tools/third_party/certifi/LICENSE b/testing/web-platform/tests/tools/third_party/certifi/LICENSE new file mode 100644 index 0000000000..802b53ff11 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/certifi/LICENSE @@ -0,0 +1,21 @@ +This packge 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: +http://mxr.mozilla.org/mozilla/source/security/nss/lib/ckfw/builtins/certdata.txt?raw=1# +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/testing/web-platform/tests/tools/third_party/certifi/MANIFEST.in b/testing/web-platform/tests/tools/third_party/certifi/MANIFEST.in new file mode 100644 index 0000000000..6077b5ff84 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/certifi/MANIFEST.in @@ -0,0 +1 @@ +include MANIFEST.in README.rst LICENSE certifi/cacert.pem diff --git a/testing/web-platform/tests/tools/third_party/certifi/PKG-INFO b/testing/web-platform/tests/tools/third_party/certifi/PKG-INFO new file mode 100644 index 0000000000..73f3643804 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/certifi/PKG-INFO @@ -0,0 +1,69 @@ +Metadata-Version: 1.1 +Name: certifi +Version: 2018.4.16 +Summary: Python package for providing Mozilla's CA Bundle. +Home-page: http://certifi.io/ +Author: Kenneth Reitz +Author-email: me@kennethreitz.com +License: MPL-2.0 +Description: Certifi: Python SSL Certificates + ================================ + + `Certifi`_ is a 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/python2.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. To assist in migrating old + code, the function ``certifi.old_where()`` continues to exist as an alias of + ``certifi.where()``. Please update your code to use ``certifi.where()`` + instead. ``certifi.old_where()`` will be removed in 2018. + + .. _`Certifi`: http://certifi.io/en/latest/ + .. _`Requests`: http://docs.python-requests.org/en/latest/ + +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 :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 diff --git a/testing/web-platform/tests/tools/third_party/certifi/README.rst b/testing/web-platform/tests/tools/third_party/certifi/README.rst new file mode 100644 index 0000000000..64b3e38e10 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/certifi/README.rst @@ -0,0 +1,46 @@ +Certifi: Python SSL Certificates +================================ + +`Certifi`_ is a 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/python2.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. To assist in migrating old +code, the function ``certifi.old_where()`` continues to exist as an alias of +``certifi.where()``. Please update your code to use ``certifi.where()`` +instead. ``certifi.old_where()`` will be removed in 2018. + +.. _`Certifi`: http://certifi.io/en/latest/ +.. _`Requests`: http://docs.python-requests.org/en/latest/ diff --git a/testing/web-platform/tests/tools/third_party/certifi/certifi/__init__.py b/testing/web-platform/tests/tools/third_party/certifi/certifi/__init__.py new file mode 100644 index 0000000000..0c4963ef60 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/certifi/certifi/__init__.py @@ -0,0 +1,3 @@ +from .core import where, old_where + +__version__ = "2018.04.16" diff --git a/testing/web-platform/tests/tools/third_party/certifi/certifi/__main__.py b/testing/web-platform/tests/tools/third_party/certifi/certifi/__main__.py new file mode 100644 index 0000000000..5f1da0dd0c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/certifi/certifi/__main__.py @@ -0,0 +1,2 @@ +from certifi import where +print(where()) diff --git a/testing/web-platform/tests/tools/third_party/certifi/certifi/cacert.pem b/testing/web-platform/tests/tools/third_party/certifi/certifi/cacert.pem new file mode 100644 index 0000000000..2713f541c4 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/certifi/certifi/cacert.pem @@ -0,0 +1,4400 @@ + +# 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=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R2 +# Label: "GlobalSign Root CA - R2" +# Serial: 4835703278459682885658125 +# MD5 Fingerprint: 94:14:77:7e:3e:5e:fd:8f:30:bd:41:b0:cf:e7:d0:30 +# SHA1 Fingerprint: 75:e0:ab:b6:13:85:12:27:1c:04:f8:5f:dd:de:38:e4:b7:24:2e:fe +# SHA256 Fingerprint: ca:42:dd:41:74:5f:d0:b8:1e:b9:02:36:2c:f9:d8:bf:71:9d:a1:bd:1b:1e:fc:94:6f:5b:4c:99:f4:2c:1b:9e +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgILBAAAAAABD4Ym5g0wDQYJKoZIhvcNAQEFBQAwTDEgMB4G +A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjIxEzARBgNVBAoTCkdsb2JhbFNp +Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDYxMjE1MDgwMDAwWhcNMjExMjE1 +MDgwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMjETMBEG +A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAKbPJA6+Lm8omUVCxKs+IVSbC9N/hHD6ErPL +v4dfxn+G07IwXNb9rfF73OX4YJYJkhD10FPe+3t+c4isUoh7SqbKSaZeqKeMWhG8 +eoLrvozps6yWJQeXSpkqBy+0Hne/ig+1AnwblrjFuTosvNYSuetZfeLQBoZfXklq +tTleiDTsvHgMCJiEbKjNS7SgfQx5TfC4LcshytVsW33hoCmEofnTlEnLJGKRILzd +C9XZzPnqJworc5HGnRusyMvo4KD0L5CLTfuwNhv2GXqF4G3yYROIXJ/gkwpRl4pa +zq+r1feqCapgvdzZX99yqWATXgAByUr6P6TqBwMhAo6CygPCm48CAwEAAaOBnDCB +mTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm+IH +V2ccHsBqBt5ZtJot39wZhi4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL2NybC5n +bG9iYWxzaWduLm5ldC9yb290LXIyLmNybDAfBgNVHSMEGDAWgBSb4gdXZxwewGoG +3lm0mi3f3BmGLjANBgkqhkiG9w0BAQUFAAOCAQEAmYFThxxol4aR7OBKuEQLq4Gs +J0/WwbgcQ3izDJr86iw8bmEbTUsp9Z8FHSbBuOmDAGJFtqkIk7mpM0sYmsL4h4hO +291xNBrBVNpGP+DTKqttVCL1OmLNIG+6KYnX3ZHu01yiPqFbQfXf5WRDLenVOavS +ot+3i9DAgBkcRcAtjOj4LaR0VknFBbVPFd5uRHg5h6h+u/N5GJG79G+dwfCMNYxd +AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 +TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only +# Label: "Verisign Class 3 Public Primary Certification Authority - G3" +# Serial: 206684696279472310254277870180966723415 +# MD5 Fingerprint: cd:68:b6:a7:c7:c4:ce:75:e0:1d:4f:57:44:61:92:09 +# SHA1 Fingerprint: 13:2d:0d:45:53:4b:69:97:cd:b2:d5:c3:39:e2:55:76:60:9b:5c:c6 +# SHA256 Fingerprint: eb:04:cf:5e:b1:f3:9a:fa:76:2f:2b:b1:20:f2:96:cb:a5:20:c1:b9:7d:b1:58:95:65:b8:1c:b9:a1:7b:72:44 +-----BEGIN CERTIFICATE----- +MIIEGjCCAwICEQCbfgZJoz5iudXukEhxKe9XMA0GCSqGSIb3DQEBBQUAMIHKMQsw +CQYDVQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZl +cmlTaWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWdu +LCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlT +aWduIENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgLSBHMzAeFw05OTEwMDEwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMIHKMQswCQYD +VQQGEwJVUzEXMBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlT +aWduIFRydXN0IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAxOTk5IFZlcmlTaWduLCBJ +bmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxRTBDBgNVBAMTPFZlcmlTaWdu +IENsYXNzIDMgUHVibGljIFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMu6nFL8eB8aHm8b +N3O9+MlrlBIwT/A2R/XQkQr1F8ilYcEWQE37imGQ5XYgwREGfassbqb1EUGO+i2t +KmFZpGcmTNDovFJbcCAEWNF6yaRpvIMXZK0Fi7zQWM6NjPXr8EJJC52XJ2cybuGu +kxUccLwgTS8Y3pKI6GyFVxEa6X7jJhFUokWWVYPKMIno3Nij7SqAP395ZVc+FSBm +CC+Vk7+qRy+oRpfwEuL+wgorUeZ25rdGt+INpsyow0xZVYnm6FNcHOqd8GIWC6fJ +Xwzw3sJ2zq/3avL6QaaiMxTJ5Xpj055iN9WFZZ4O5lMkdBteHRJTW8cs54NJOxWu +imi5V5cCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAERSWwauSCPc/L8my/uRan2Te +2yFPhpk0djZX3dAVL8WtfxUfN2JzPtTnX84XA9s1+ivbrmAJXx5fj267Cz3qWhMe +DGBvtcC1IyIuBwvLqXTLR7sdwdela8wv0kL9Sd2nic9TutoAWii/gt/4uhMdUIaC +/Y4wjylGsB49Ndo4YhYYSq3mtlFs3q9i6wHQHiT+eo8SGhJouPtmmRQURVyu565p +F4ErWjfJXir0xuKhXFSbplQAz/DxwceYMBo7Nhbbo27q/a2ywtrvAkcTisDxszGt +TxzhT5yvDwyd93gN2PQ1VoDat20Xj50egWTh/sVFuq1ruQp6Tk9LhO5L8X3dEQ== +-----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=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Subject: CN=AddTrust External CA Root O=AddTrust AB OU=AddTrust External TTP Network +# Label: "AddTrust External Root" +# Serial: 1 +# MD5 Fingerprint: 1d:35:54:04:85:78:b0:3f:42:42:4d:bf:20:73:0a:3f +# SHA1 Fingerprint: 02:fa:f3:e2:91:43:54:68:60:78:57:69:4d:f5:e4:5b:68:85:18:68 +# SHA256 Fingerprint: 68:7f:a4:51:38:22:78:ff:f0:c8:b1:1f:8d:43:d5:76:67:1c:6e:b2:bc:ea:b4:13:fb:83:d9:65:d0:6d:2f:f2 +-----BEGIN CERTIFICATE----- +MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU +MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs +IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290 +MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux +FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h +bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt +H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9 +uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX +mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX +a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN +E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0 +WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD +VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0 +Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU +cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx +IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN +AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH +YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5 +6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC +Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX +c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a +mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ= +-----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=GeoTrust Global CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Global CA O=GeoTrust Inc. +# Label: "GeoTrust Global CA" +# Serial: 144470 +# MD5 Fingerprint: f7:75:ab:29:fb:51:4e:b7:77:5e:ff:05:3c:99:8e:f5 +# SHA1 Fingerprint: de:28:f4:a4:ff:e5:b9:2f:a3:c5:03:d1:a3:49:a7:f9:96:2a:82:12 +# SHA256 Fingerprint: ff:85:6a:2d:25:1d:cd:88:d3:66:56:f4:50:12:67:98:cf:ab:aa:de:40:79:9c:72:2d:e4:d2:b5:db:36:a7:3a +-----BEGIN CERTIFICATE----- +MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT +MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i +YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG +EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg +R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9 +9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq +fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv +iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU +1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+ +bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW +MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA +ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l +uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn +Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS +tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF +PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un +hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV +5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw== +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA O=GeoTrust Inc. +# Label: "GeoTrust Universal CA" +# Serial: 1 +# MD5 Fingerprint: 92:65:58:8b:a2:1a:31:72:73:68:5c:b4:a5:7a:07:48 +# SHA1 Fingerprint: e6:21:f3:35:43:79:05:9a:4b:68:30:9d:8a:2f:74:22:15:87:ec:79 +# SHA256 Fingerprint: a0:45:9b:9f:63:b2:25:59:f5:fa:5d:4c:6d:b3:f9:f7:2f:f1:93:42:03:35:78:f0:73:bf:1d:1b:46:cb:b9:12 +-----BEGIN CERTIFICATE----- +MIIFaDCCA1CgAwIBAgIBATANBgkqhkiG9w0BAQUFADBFMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEeMBwGA1UEAxMVR2VvVHJ1c3QgVW5pdmVy +c2FsIENBMB4XDTA0MDMwNDA1MDAwMFoXDTI5MDMwNDA1MDAwMFowRTELMAkGA1UE +BhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xHjAcBgNVBAMTFUdlb1RydXN0 +IFVuaXZlcnNhbCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKYV +VaCjxuAfjJ0hUNfBvitbtaSeodlyWL0AG0y/YckUHUWCq8YdgNY96xCcOq9tJPi8 +cQGeBvV8Xx7BDlXKg5pZMK4ZyzBIle0iN430SppyZj6tlcDgFgDgEB8rMQ7XlFTT +QjOgNB0eRXbdT8oYN+yFFXoZCPzVx5zw8qkuEKmS5j1YPakWaDwvdSEYfyh3peFh +F7em6fgemdtzbvQKoiFs7tqqhZJmr/Z6a4LauiIINQ/PQvE1+mrufislzDoR5G2v +c7J2Ha3QsnhnGqQ5HFELZ1aD/ThdDc7d8Lsrlh/eezJS/R27tQahsiFepdaVaH/w +mZ7cRQg+59IJDTWU3YBOU5fXtQlEIGQWFwMCTFMNaN7VqnJNk22CDtucvc+081xd +VHppCZbW2xHBjXWotM85yM48vCR85mLK4b19p71XZQvk/iXttmkQ3CgaRr0BHdCX +teGYO8A3ZNY9lO4L4fUorgtWv3GLIylBjobFS1J72HGrH4oVpjuDWtdYAVHGTEHZ +f9hBZ3KiKN9gg6meyHv8U3NyWfWTehd2Ds735VzZC1U0oqpbtWpU5xPKV+yXbfRe +Bi9Fi1jUIxaS5BZuKGNZMN9QAZxjiRqf2xeUgnA3wySemkfWWspOqGmJch+RbNt+ +nhutxx9z3SxPGWX9f5NAEC7S8O08ni4oPmkmM8V7AgMBAAGjYzBhMA8GA1UdEwEB +/wQFMAMBAf8wHQYDVR0OBBYEFNq7LqqwDLiIJlF0XG0D08DYj3rWMB8GA1UdIwQY +MBaAFNq7LqqwDLiIJlF0XG0D08DYj3rWMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQUFAAOCAgEAMXjmx7XfuJRAyXHEqDXsRh3ChfMoWIawC/yOsjmPRFWrZIRc +aanQmjg8+uUfNeVE44B5lGiku8SfPeE0zTBGi1QrlaXv9z+ZhP015s8xxtxqv6fX +IwjhmF7DWgh2qaavdy+3YL1ERmrvl/9zlcGO6JP7/TG37FcREUWbMPEaiDnBTzyn +ANXH/KttgCJwpQzgXQQpAvvLoJHRfNbDflDVnVi+QTjruXU8FdmbyUqDWcDaU/0z +uzYYm4UPFd3uLax2k7nZAY1IEKj79TiG8dsKxr2EoyNB3tZ3b4XUhRxQ4K5RirqN +Pnbiucon8l+f725ZDQbYKxek0nxru18UGkiPGkzns0ccjkxFKyDuSN/n3QmOGKja +QI2SJhFTYXNd673nxE0pN2HrrDktZy4W1vUAg4WhzH92xH3kt0tm7wNFYGm2DFKW +koRepqO1pD4r2czYG0eq8kTaT/kD6PAUyz/zg97QwVTjt+gKN02LIFkDMBmhLMi9 +ER/frslKxfMnZmaGrGiR/9nmUxwPi1xpZQomyB40w11Re9epnAahNt3ViZS82eQt +DF4JbAiXfKM9fJP/P6EUp8+1Xevb2xzEdt+Iub1FBZUbrvxGakyvSOPOrg/Sfuvm +bJxPgWp6ZKy7PtXny3YuxadIwVyQD8vIP/rmMuGNG2+k5o7Y+SlIis5z/iw= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Subject: CN=GeoTrust Universal CA 2 O=GeoTrust Inc. +# Label: "GeoTrust Universal CA 2" +# Serial: 1 +# MD5 Fingerprint: 34:fc:b8:d0:36:db:9e:14:b3:c2:f2:db:8f:e4:94:c7 +# SHA1 Fingerprint: 37:9a:19:7b:41:85:45:35:0c:a6:03:69:f3:3c:2e:af:47:4f:20:79 +# SHA256 Fingerprint: a0:23:4f:3b:c8:52:7c:a5:62:8e:ec:81:ad:5d:69:89:5d:a5:68:0d:c9:1d:1c:b8:47:7f:33:f8:78:b9:5b:0b +-----BEGIN CERTIFICATE----- +MIIFbDCCA1SgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJVUzEW +MBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1c3QgVW5pdmVy +c2FsIENBIDIwHhcNMDQwMzA0MDUwMDAwWhcNMjkwMzA0MDUwMDAwWjBHMQswCQYD +VQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEgMB4GA1UEAxMXR2VvVHJ1 +c3QgVW5pdmVyc2FsIENBIDIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC +AQCzVFLByT7y2dyxUxpZKeexw0Uo5dfR7cXFS6GqdHtXr0om/Nj1XqduGdt0DE81 +WzILAePb63p3NeqqWuDW6KFXlPCQo3RWlEQwAx5cTiuFJnSCegx2oG9NzkEtoBUG +FF+3Qs17j1hhNNwqCPkuwwGmIkQcTAeC5lvO0Ep8BNMZcyfwqph/Lq9O64ceJHdq +XbboW0W63MOhBW9Wjo8QJqVJwy7XQYci4E+GymC16qFjwAGXEHm9ADwSbSsVsaxL +se4YuU6W3Nx2/zu+z18DwPw76L5GG//aQMJS9/7jOvdqdzXQ2o3rXhhqMcceujwb +KNZrVMaqW9eiLBsZzKIC9ptZvTdrhrVtgrrY6slWvKk2WP0+GfPtDCapkzj4T8Fd +IgbQl+rhrcZV4IErKIM6+vR7IVEAvlI4zs1meaj0gVbi0IMJR1FbUGrP20gaXT73 +y/Zl92zxlfgCOzJWgjl6W70viRu/obTo/3+NjN8D8WBOWBFM66M/ECuDmgFz2ZRt +hAAnZqzwcEAJQpKtT5MNYQlRJNiS1QuUYbKHsu3/mjX/hVTK7URDrBs8FmtISgoc +QIgfksILAAX/8sgCSqSqqcyZlpwvWOB94b67B9xfBHJcMTTD7F8t4D1kkCLm0ey4 +Lt1ZrtmhN79UNdxzMk+MBB4zsslG8dhcyFVQyWi9qLo2CQIDAQABo2MwYTAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAfBgNV +HSMEGDAWgBR281Xh+qQ2+/CfXGJx7Tz0RzgQKzAOBgNVHQ8BAf8EBAMCAYYwDQYJ +KoZIhvcNAQEFBQADggIBAGbBxiPz2eAubl/oz66wsCVNK/g7WJtAJDday6sWSf+z +dXkzoS9tcBc0kf5nfo/sm+VegqlVHy/c1FEHEv6sFj4sNcZj/NwQ6w2jqtB8zNHQ +L1EuxBRa3ugZ4T7GzKQp5y6EqgYweHZUcyiYWTjgAA1i00J9IZ+uPTqM1fp3DRgr +Fg5fNuH8KrUwJM/gYwx7WBr+mbpCErGR9Hxo4sjoryzqyX6uuyo9DRXcNJW2GHSo +ag/HtPQTxORb7QrSpJdMKu0vbBKJPfEncKpqA1Ihn0CoZ1Dy81of398j9tx4TuaY +T1U6U+Pv8vSfx3zYWK8pIpe44L2RLrB27FcRz+8pRPPphXpgY+RdM4kX2TGq2tbz +GDVyz4crL2MjhF2EjD9XoIj8mZEoJmmZ1I+XRL6O1UixpCgp8RW04eWe3fiPpm8m +1wk8OhwRDqZsN/etRIcsKMfYdIKz0G9KV7s1KSegi+ghp4dkNl3M2Basx7InQJJV +OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH +6aLcr34YEoP9VhdBLtUpgn2Z9DH2canPLAEnpQW5qrJITirvn5NSUZU8UnOOVkwX +QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS +-----END CERTIFICATE----- + +# Issuer: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association +# Subject: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association +# Label: "Visa eCommerce Root" +# Serial: 25952180776285836048024890241505565794 +# MD5 Fingerprint: fc:11:b8:d8:08:93:30:00:6d:23:f9:7e:eb:52:1e:02 +# SHA1 Fingerprint: 70:17:9b:86:8c:00:a4:fa:60:91:52:22:3f:9f:3e:32:bd:e0:05:62 +# SHA256 Fingerprint: 69:fa:c9:bd:55:fb:0a:c7:8d:53:bb:ee:5c:f1:d5:97:98:9f:d0:aa:ab:20:a2:51:51:bd:f1:73:3e:e7:d1:22 +-----BEGIN CERTIFICATE----- +MIIDojCCAoqgAwIBAgIQE4Y1TR0/BvLB+WUF1ZAcYjANBgkqhkiG9w0BAQUFADBr +MQswCQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRl +cm5hdGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNv +bW1lcmNlIFJvb3QwHhcNMDIwNjI2MDIxODM2WhcNMjIwNjI0MDAxNjEyWjBrMQsw +CQYDVQQGEwJVUzENMAsGA1UEChMEVklTQTEvMC0GA1UECxMmVmlzYSBJbnRlcm5h +dGlvbmFsIFNlcnZpY2UgQXNzb2NpYXRpb24xHDAaBgNVBAMTE1Zpc2EgZUNvbW1l +cmNlIFJvb3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvV95WHm6h +2mCxlCfLF9sHP4CFT8icttD0b0/Pmdjh28JIXDqsOTPHH2qLJj0rNfVIsZHBAk4E +lpF7sDPwsRROEW+1QK8bRaVK7362rPKgH1g/EkZgPI2h4H3PVz4zHvtH8aoVlwdV +ZqW1LS7YgFmypw23RuwhY/81q6UCzyr0TP579ZRdhE2o8mCP2w4lPJ9zcc+U30rq +299yOIzzlr3xF7zSujtFWsan9sYXiwGd/BmoKoMWuDpI/k4+oKsGGelT84ATB+0t +vz8KPFUgOSwsAGl0lUq8ILKpeeUYiZGo3BxN77t+Nwtd/jmliFKMAGzsGHxBvfaL +dXe6YJ2E5/4tAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQD +AgEGMB0GA1UdDgQWBBQVOIMPPyw/cDMezUb+B4wg4NfDtzANBgkqhkiG9w0BAQUF +AAOCAQEAX/FBfXxcCLkr4NWSR/pnXKUTwwMhmytMiUbPWU3J/qVAtmPN3XEolWcR +zCSs00Rsca4BIGsDoo8Ytyk6feUWYFN4PMCvFYP3j1IzJL1kk5fui/fbGKhtcbP3 +LBfQdCVp9/5rPJS+TUtBjE7ic9DjkCJzQ83z7+pzzkWKsKZJ/0x9nXGIxHYdkFsd +7v3M9+79YKWxehZx0RbQfBI8bGmX265fOZpwLwU8GUYEmSA20GBuYQa7FkKMcPcw +++DbZqMAAb3mLNqRX6BGi01qnD093QVG/na/oAo85ADmJ7f/hC3euiInlhBx6yLt +398znM/jra6O1I7mT1GvFpLgXPYHDw== +-----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 Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Subject: CN=QuoVadis Root Certification Authority O=QuoVadis Limited OU=Root Certification Authority +# Label: "QuoVadis Root CA" +# Serial: 985026699 +# MD5 Fingerprint: 27:de:36:fe:72:b7:00:03:00:9d:f4:f0:1e:6c:04:24 +# SHA1 Fingerprint: de:3f:40:bd:50:93:d3:9b:6c:60:f6:da:bc:07:62:01:00:89:76:c9 +# SHA256 Fingerprint: a4:5e:de:3b:bb:f0:9c:8a:e1:5c:72:ef:c0:72:68:d6:93:a2:1c:99:6f:d5:1e:67:ca:07:94:60:fd:6d:88:73 +-----BEGIN CERTIFICATE----- +MIIF0DCCBLigAwIBAgIEOrZQizANBgkqhkiG9w0BAQUFADB/MQswCQYDVQQGEwJC +TTEZMBcGA1UEChMQUXVvVmFkaXMgTGltaXRlZDElMCMGA1UECxMcUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTEuMCwGA1UEAxMlUXVvVmFkaXMgUm9vdCBDZXJ0 +aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wMTAzMTkxODMzMzNaFw0yMTAzMTcxODMz +MzNaMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1pdGVkMSUw +IwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYDVQQDEyVR +dW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv2G1lVO6V/z68mcLOhrfEYBklbTRvM16z/Yp +li4kVEAkOPcahdxYTMukJ0KX0J+DisPkBgNbAKVRHnAEdOLB1Dqr1607BxgFjv2D +rOpm2RgbaIr1VxqYuvXtdj182d6UajtLF8HVj71lODqV0D1VNk7feVcxKh7YWWVJ +WCCYfqtffp/p1k3sg3Spx2zY7ilKhSoGFPlU5tPaZQeLYzcS19Dsw3sgQUSj7cug +F+FxZc4dZjH3dgEZyH0DWLaVSR2mEiboxgx24ONmy+pdpibu5cxfvWenAScOospU +xbF6lR1xHkopigPcakXBpBlebzbNw6Kwt/5cOOJSvPhEQ+aQuwIDAQABo4ICUjCC +Ak4wPQYIKwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwczovL29jc3AucXVv +dmFkaXNvZmZzaG9yZS5jb20wDwYDVR0TAQH/BAUwAwEB/zCCARoGA1UdIASCAREw +ggENMIIBCQYJKwYBBAG+WAABMIH7MIHUBggrBgEFBQcCAjCBxxqBxFJlbGlhbmNl +IG9uIHRoZSBRdW9WYWRpcyBSb290IENlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh +c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJsZSBzdGFuZGFy +ZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2UsIGNlcnRpZmljYXRpb24gcHJh +Y3RpY2VzLCBhbmQgdGhlIFF1b1ZhZGlzIENlcnRpZmljYXRlIFBvbGljeS4wIgYI +KwYBBQUHAgEWFmh0dHA6Ly93d3cucXVvdmFkaXMuYm0wHQYDVR0OBBYEFItLbe3T +KbkGGew5Oanwl4Rqy+/fMIGuBgNVHSMEgaYwgaOAFItLbe3TKbkGGew5Oanwl4Rq +y+/foYGEpIGBMH8xCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBMaW1p +dGVkMSUwIwYDVQQLExxSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MS4wLAYD +VQQDEyVRdW9WYWRpcyBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5ggQ6tlCL +MA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOCAQEAitQUtf70mpKnGdSk +fnIYj9lofFIk3WdvOXrEql494liwTXCYhGHoG+NpGA7O+0dQoE7/8CQfvbLO9Sf8 +7C9TqnN7Az10buYWnuulLsS/VidQK2K6vkscPFVcQR0kvoIgR13VRH56FmjffU1R +cHhXHTMe/QKZnAzNCgVPx7uOpHX6Sm2xgI4JVrmcGmD+XcHXetwReNDWXcG31a0y +mQM6isxUJTkxgXsTIlG6Rmyhu576BGxJJnSP0nPrzDCi5upZIof4l/UO/erMkqQW +xFIY6iHOsfHmhIHluqmGKPJDWl0Snawe2ajlCmqnf6CHKc/yiU3U7MXi5nrQNiOK +SnQ2+Q== +-----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=Sonera Class2 CA O=Sonera +# Subject: CN=Sonera Class2 CA O=Sonera +# Label: "Sonera Class 2 Root CA" +# Serial: 29 +# MD5 Fingerprint: a3:ec:75:0f:2e:88:df:fa:48:01:4e:0b:5c:48:6f:fb +# SHA1 Fingerprint: 37:f7:6d:e6:07:7c:90:c5:b1:3e:93:1a:b7:41:10:b4:f2:e4:9a:27 +# SHA256 Fingerprint: 79:08:b4:03:14:c1:38:10:0b:51:8d:07:35:80:7f:fb:fc:f8:51:8a:00:95:33:71:05:ba:38:6b:15:3d:d9:27 +-----BEGIN CERTIFICATE----- +MIIDIDCCAgigAwIBAgIBHTANBgkqhkiG9w0BAQUFADA5MQswCQYDVQQGEwJGSTEP +MA0GA1UEChMGU29uZXJhMRkwFwYDVQQDExBTb25lcmEgQ2xhc3MyIENBMB4XDTAx +MDQwNjA3Mjk0MFoXDTIxMDQwNjA3Mjk0MFowOTELMAkGA1UEBhMCRkkxDzANBgNV +BAoTBlNvbmVyYTEZMBcGA1UEAxMQU29uZXJhIENsYXNzMiBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAJAXSjWdyvANlsdE+hY3/Ei9vX+ALTU74W+o +Z6m/AxxNjG8yR9VBaKQTBME1DJqEQ/xcHf+Js+gXGM2RX/uJ4+q/Tl18GybTdXnt +5oTjV+WtKcT0OijnpXuENmmz/V52vaMtmdOQTiMofRhj8VQ7Jp12W5dCsv+u8E7s +3TmVToMGf+dJQMjFAbJUWmYdPfz56TwKnoG4cPABi+QjVHzIrviQHgCWctRUz2Ej +vOr7nQKV0ba5cTppCD8PtOFCx4j1P5iop7oc4HFx71hXgVB6XGt0Rg6DA5jDjqhu +8nYybieDwnPz3BjotJPqdURrBGAgcVeHnfO+oJAjPYok4doh28MCAwEAAaMzMDEw +DwYDVR0TAQH/BAUwAwEB/zARBgNVHQ4ECgQISqCqWITTXjwwCwYDVR0PBAQDAgEG +MA0GCSqGSIb3DQEBBQUAA4IBAQBazof5FnIVV0sd2ZvnoiYw7JNn39Yt0jSv9zil +zqsWuasvfDXLrNAPtEwr/IDva4yRXzZ299uzGxnq9LIR/WFxRL8oszodv7ND6J+/ +3DEIcbCdjdY0RzKQxmUk96BKfARzjzlvF4xytb1LyHr4e4PDKE6cCepnP7JnBBvD +FNr450kkkdAdavphOe9r5yF1BgfYErQhIHBCcYHaPJo2vqZbDWpsmh+Re/n570K6 +Tk6ezAyNlNzZRZxe7EJQY670XcSxEtzKO6gunRRaBXW37Ndj4ro1tgQIkejanZz2 +ZrUYrAqmVCY0M9IbwdR/GjqOC6oybtv8TyWf2TLHllpwrN9M +-----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: O=Government Root Certification Authority +# Subject: O=Government Root Certification Authority +# Label: "Taiwan GRCA" +# Serial: 42023070807708724159991140556527066870 +# MD5 Fingerprint: 37:85:44:53:32:45:1f:20:f0:f3:95:e1:25:c4:43:4e +# SHA1 Fingerprint: f4:8b:11:bf:de:ab:be:94:54:20:71:e6:41:de:6b:be:88:2b:40:b9 +# SHA256 Fingerprint: 76:00:29:5e:ef:e8:5b:9e:1f:d6:24:db:76:06:2a:aa:ae:59:81:8a:54:d2:77:4c:d4:c0:b2:c0:11:31:e1:b3 +-----BEGIN CERTIFICATE----- +MIIFcjCCA1qgAwIBAgIQH51ZWtcvwgZEpYAIaeNe9jANBgkqhkiG9w0BAQUFADA/ +MQswCQYDVQQGEwJUVzEwMC4GA1UECgwnR292ZXJubWVudCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5MB4XDTAyMTIwNTEzMjMzM1oXDTMyMTIwNTEzMjMzM1ow +PzELMAkGA1UEBhMCVFcxMDAuBgNVBAoMJ0dvdmVybm1lbnQgUm9vdCBDZXJ0aWZp +Y2F0aW9uIEF1dGhvcml0eTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +AJoluOzMonWoe/fOW1mKydGGEghU7Jzy50b2iPN86aXfTEc2pBsBHH8eV4qNw8XR +IePaJD9IK/ufLqGU5ywck9G/GwGHU5nOp/UKIXZ3/6m3xnOUT0b3EEk3+qhZSV1q +gQdW8or5BtD3cCJNtLdBuTK4sfCxw5w/cP1T3YGq2GN49thTbqGsaoQkclSGxtKy +yhwOeYHWtXBiCAEuTk8O1RGvqa/lmr/czIdtJuTJV6L7lvnM4T9TjGxMfptTCAts +F/tnyMKtsc2AtJfcdgEWFelq16TheEfOhtX7MfP6Mb40qij7cEwdScevLJ1tZqa2 +jWR+tSBqnTuBto9AAGdLiYa4zGX+FVPpBMHWXx1E1wovJ5pGfaENda1UhhXcSTvx +ls4Pm6Dso3pdvtUqdULle96ltqqvKKyskKw4t9VoNSZ63Pc78/1Fm9G7Q3hub/FC +VGqY8A2tl+lSXunVanLeavcbYBT0peS2cWeqH+riTcFCQP5nRhc4L0c/cZyu5SHK +YS1tB6iEfC3uUSXxY5Ce/eFXiGvviiNtsea9P63RPZYLhY3Naye7twWb7LuRqQoH +EgKXTiCQ8P8NHuJBO9NAOueNXdpm5AKwB1KYXA6OM5zCppX7VRluTI6uSw+9wThN +Xo+EHWbNxWCWtFJaBYmOlXqYwZE8lSOyDvR5tMl8wUohAgMBAAGjajBoMB0GA1Ud +DgQWBBTMzO/MKWCkO7GStjz6MmKPrCUVOzAMBgNVHRMEBTADAQH/MDkGBGcqBwAE +MTAvMC0CAQAwCQYFKw4DAhoFADAHBgVnKgMAAAQUA5vwIhP/lSg209yewDL7MTqK +UWUwDQYJKoZIhvcNAQEFBQADggIBAECASvomyc5eMN1PhnR2WPWus4MzeKR6dBcZ +TulStbngCnRiqmjKeKBMmo4sIy7VahIkv9Ro04rQ2JyftB8M3jh+Vzj8jeJPXgyf +qzvS/3WXy6TjZwj/5cAWtUgBfen5Cv8b5Wppv3ghqMKnI6mGq3ZW6A4M9hPdKmaK +ZEk9GhiHkASfQlK3T8v+R0F2Ne//AHY2RTKbxkaFXeIksB7jSJaYV0eUVXoPQbFE +JPPB/hprv4j9wabak2BegUqZIJxIZhm1AHlUD7gsL0u8qV1bYH+Mh6XgUmMqvtg7 +hUAV/h62ZT/FS9p+tXo1KaMuephgIqP0fSdOLeq0dDzpD6QzDxARvBMB1uUO07+1 +EqLhRSPAzAhuYbeJq4PjJB7mXQfnHyA+z2fI56wwbSdLaG5LKlwCCDTb+HbkZ6Mm +nD+iMsJKxYEYMRBWqoTvLQr/uB930r+lWKBi5NdLkXWNiYCYfm3LU05er/ayl4WX +udpVBrkk7tfGOB5jGxI7leFYrPLfhNVfmS8NVVvmONsuP3LpSIXLuykTjx44Vbnz +ssQwmSNOXfJIoRIM3BKQCZBUkQM8R+XVyWXgt0t97EfTsws+rZ7QdAAO671RrcDe +LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl +pYYsfPQS +-----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=Class 2 Primary CA O=Certplus +# Subject: CN=Class 2 Primary CA O=Certplus +# Label: "Certplus Class 2 Primary CA" +# Serial: 177770208045934040241468760488327595043 +# MD5 Fingerprint: 88:2c:8c:52:b8:a2:3c:f3:f7:bb:03:ea:ae:ac:42:0b +# SHA1 Fingerprint: 74:20:74:41:72:9c:dd:92:ec:79:31:d8:23:10:8d:c2:81:92:e2:bb +# SHA256 Fingerprint: 0f:99:3c:8a:ef:97:ba:af:56:87:14:0e:d5:9a:d1:82:1b:b4:af:ac:f0:aa:9a:58:b5:d5:7a:33:8a:3a:fb:cb +-----BEGIN CERTIFICATE----- +MIIDkjCCAnqgAwIBAgIRAIW9S/PY2uNp9pTXX8OlRCMwDQYJKoZIhvcNAQEFBQAw +PTELMAkGA1UEBhMCRlIxETAPBgNVBAoTCENlcnRwbHVzMRswGQYDVQQDExJDbGFz +cyAyIFByaW1hcnkgQ0EwHhcNOTkwNzA3MTcwNTAwWhcNMTkwNzA2MjM1OTU5WjA9 +MQswCQYDVQQGEwJGUjERMA8GA1UEChMIQ2VydHBsdXMxGzAZBgNVBAMTEkNsYXNz +IDIgUHJpbWFyeSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxQ +ltAS+DXSCHh6tlJw/W/uz7kRy1134ezpfgSN1sxvc0NXYKwzCkTsA18cgCSR5aiR +VhKC9+Ar9NuuYS6JEI1rbLqzAr3VNsVINyPi8Fo3UjMXEuLRYE2+L0ER4/YXJQyL +kcAbmXuZVg2v7tK8R1fjeUl7NIknJITesezpWE7+Tt9avkGtrAjFGA7v0lPubNCd +EgETjdyAYveVqUSISnFOYFWe2yMZeVYHDD9jC1yw4r5+FfyUM1hBOHTE4Y+L3yas +H7WLO7dDWWuwJKZtkIvEcupdM5i3y95ee++U8Rs+yskhwcWYAqqi9lt3m/V+llU0 +HGdpwPFC40es/CgcZlUCAwEAAaOBjDCBiTAPBgNVHRMECDAGAQH/AgEKMAsGA1Ud +DwQEAwIBBjAdBgNVHQ4EFgQU43Mt38sOKAze3bOkynm4jrvoMIkwEQYJYIZIAYb4 +QgEBBAQDAgEGMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuY2VydHBsdXMu +Y29tL0NSTC9jbGFzczIuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQCnVM+IRBnL39R/ +AN9WM2K191EBkOvDP9GIROkkXe/nFL0gt5o8AP5tn9uQ3Nf0YtaLcF3n5QRIqWh8 +yfFC82x/xXp8HVGIutIKPidd3i1RTtMTZGnkLuPT55sJmabglZvOGtd/vjzOUrMR +FcEPF80Du5wlFbqidon8BvEY0JNLDnyCt6X09l/+7UCmnYR0ObncHoUW2ikbhiMA +ybuJfm6AiB4vFLQDJKgybwOaRywwvlbGp0ICcBvqQNi6BQNwB6SW//1IMwrh3KWB +kJtN3X3n57LNXMhqlfil9o3EXXgIvnsG1knPGTZQIy4I5p4FTUcY1Rbpsda2ENW7 +l7+ijrRU +-----END CERTIFICATE----- + +# Issuer: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Subject: CN=DST Root CA X3 O=Digital Signature Trust Co. +# Label: "DST Root CA X3" +# Serial: 91299735575339953335919266965803778155 +# MD5 Fingerprint: 41:03:52:dc:0f:f7:50:1b:16:f0:02:8e:ba:6f:45:c5 +# SHA1 Fingerprint: da:c9:02:4f:54:d8:f6:df:94:93:5f:b1:73:26:38:ca:6a:d7:7c:13 +# SHA256 Fingerprint: 06:87:26:03:31:a7:24:03:d9:09:f1:05:e6:9b:cf:0d:32:e1:bd:24:93:ff:c6:d9:20:6d:11:bc:d6:77:07:39 +-----BEGIN CERTIFICATE----- +MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow +PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD +Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O +rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq +OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b +xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw +7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD +aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG +SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69 +ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr +AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz +R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5 +JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo +Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ +-----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=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Subject: CN=GeoTrust Primary Certification Authority O=GeoTrust Inc. +# Label: "GeoTrust Primary Certification Authority" +# Serial: 32798226551256963324313806436981982369 +# MD5 Fingerprint: 02:26:c3:01:5e:08:30:37:43:a9:d0:7d:cf:37:e6:bf +# SHA1 Fingerprint: 32:3c:11:8e:1b:f7:b8:b6:52:54:e2:e2:10:0d:d6:02:90:37:f0:96 +# SHA256 Fingerprint: 37:d5:10:06:c5:12:ea:ab:62:64:21:f1:ec:8c:92:01:3f:c5:f8:2a:e9:8e:e5:33:eb:46:19:b8:de:b4:d0:6c +-----BEGIN CERTIFICATE----- +MIIDfDCCAmSgAwIBAgIQGKy1av1pthU6Y2yv2vrEoTANBgkqhkiG9w0BAQUFADBY +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjExMC8GA1UEAxMo +R2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEx +MjcwMDAwMDBaFw0zNjA3MTYyMzU5NTlaMFgxCzAJBgNVBAYTAlVTMRYwFAYDVQQK +Ew1HZW9UcnVzdCBJbmMuMTEwLwYDVQQDEyhHZW9UcnVzdCBQcmltYXJ5IENlcnRp +ZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEAvrgVe//UfH1nrYNke8hCUy3f9oQIIGHWAVlqnEQRr+92/ZV+zmEwu3qDXwK9 +AWbK7hWNb6EwnL2hhZ6UOvNWiAAxz9juapYC2e0DjPt1befquFUWBRaa9OBesYjA +ZIVcFU2Ix7e64HXprQU9nceJSOC7KMgD4TCTZF5SwFlwIjVXiIrxlQqD17wxcwE0 +7e9GceBrAqg1cmuXm2bgyxx5X9gaBGgeRwLmnWDiNpcB3841kt++Z8dtd1k7j53W +kBWUvEI0EME5+bEnPn7WinXFsq+W06Lem+SYvn3h6YGttm/81w7a4DSwDRp35+MI +mO9Y+pyEtzavwt+s0vQQBnBxNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4G +A1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQULNVQQZcVi/CPNmFbSvtr2ZnJM5IwDQYJ +KoZIhvcNAQEFBQADggEBAFpwfyzdtzRP9YZRqSa+S7iq8XEN3GHHoOo0Hnp3DwQ1 +6CePbJC/kRYkRj5KTs4rFtULUh38H2eiAkUxT87z+gOneZ1TatnaYzr4gNfTmeGl +4b7UVXGYNTq+k+qurUKykG/g/CFNNWMziUnWm07Kx+dOCQD32sfvmWKZd7aVIl6K +oKv0uHiYyjgZmclynnjNS6yvGaBzEi38wkG6gZHaFloxt/m0cYASSJlyc1pZU8Fj +UjPtp8nSOQJw+uCxQmYpqptR7TBUIhRf2asdweSU8Pj1K/fqynhG1riR/aYNKxoU +AT6A8EKglQdebc3MS6RFjasS6LPeWuWgfOgPIh1a6Vk= +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA O=thawte, Inc. OU=Certification Services Division/(c) 2006 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA" +# Serial: 69529181992039203566298953787712940909 +# MD5 Fingerprint: 8c:ca:dc:0b:22:ce:f5:be:72:ac:41:1a:11:a8:d8:12 +# SHA1 Fingerprint: 91:c6:d6:ee:3e:8a:c8:63:84:e5:48:c2:99:29:5c:75:6c:81:7b:81 +# SHA256 Fingerprint: 8d:72:2f:81:a9:c1:13:c0:79:1d:f1:36:a2:96:6d:b2:6c:95:0a:97:1d:b4:6b:41:99:f4:ea:54:b7:8b:fb:9f +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIQNE7VVyDV7exJ9C/ON9srbTANBgkqhkiG9w0BAQUFADCB +qTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxHzAdBgNV +BAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwHhcNMDYxMTE3MDAwMDAwWhcNMzYw +NzE2MjM1OTU5WjCBqTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5j +LjEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYG +A1UECxMvKGMpIDIwMDYgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNl +IG9ubHkxHzAdBgNVBAMTFnRoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCsoPD7gFnUnMekz52hWXMJEEUMDSxuaPFs +W0hoSVk3/AszGcJ3f8wQLZU0HObrTQmnHNK4yZc2AreJ1CRfBsDMRJSUjQJib+ta +3RGNKJpchJAQeg29dGYvajig4tVUROsdB58Hum/u6f1OCyn1PoSgAfGcq/gcfomk +6KHYcWUNo1F77rzSImANuVud37r8UVsLr5iy6S7pBOhih94ryNdOwUxkHt3Ph1i6 +Sk/KaAcdHJ1KxtUvkcx8cXIcxcBn6zL9yZJclNqFwJu/U30rCfSMnZEfl2pSy94J +NqR32HuHUETVPm4pafs5SSYeCaWAe0At6+gnhcn+Yf1+5nyXHdWdAgMBAAGjQjBA +MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBR7W0XP +r87Lev0xkhpqtvNG61dIUDANBgkqhkiG9w0BAQUFAAOCAQEAeRHAS7ORtvzw6WfU +DW5FvlXok9LOAz/t2iWwHVfLHjp2oEzsUHboZHIMpKnxuIvW1oeEuzLlQRHAd9mz +YJ3rG9XRbkREqaYB7FViHXe4XI5ISXycO1cRrK1zN44veFyQaEfZYGDm/Ac9IiAX +xPcW6cTYcvnIc3zfFi8VqT79aie2oetaupgf1eNNZAqdE8hhuvU5HIe6uL17In/2 +/qxAeeWsEG89jxt5dovEN7MhGITlNgDrYyCZuen+MwS7QcjBAvlEYyCegc5C09Y/ +LHbTY5xZ3Y+m4Q6gLkH3LpVHz7z9M/P2C2F+fpErgUfCJzDupxBdN49cOSvkBPB7 +jVaMaA== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G5 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2006 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G5" +# Serial: 33037644167568058970164719475676101450 +# MD5 Fingerprint: cb:17:e4:31:67:3e:e2:09:fe:45:57:93:f3:0a:fa:1c +# SHA1 Fingerprint: 4e:b6:d5:78:49:9b:1c:cf:5f:58:1e:ad:56:be:3d:9b:67:44:a5:e5 +# SHA256 Fingerprint: 9a:cf:ab:7e:43:c8:d8:80:d0:6b:26:2a:94:de:ee:e4:b4:65:99:89:c3:d0:ca:f1:9b:af:64:05:e4:1a:b7:df +-----BEGIN CERTIFICATE----- +MIIE0zCCA7ugAwIBAgIQGNrRniZ96LtKIVjNzGs7SjANBgkqhkiG9w0BAQUFADCB +yjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxW +ZXJpU2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0 +aG9yaXR5IC0gRzUwHhcNMDYxMTA4MDAwMDAwWhcNMzYwNzE2MjM1OTU5WjCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNiBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCvJAgIKXo1 +nmAMqudLO07cfLw8RRy7K+D+KQL5VwijZIUVJ/XxrcgxiV0i6CqqpkKzj/i5Vbex +t0uz/o9+B1fs70PbZmIVYc9gDaTY3vjgw2IIPVQT60nKWVSFJuUrjxuf6/WhkcIz +SdhDY2pSS9KP6HBRTdGJaXvHcPaz3BJ023tdS1bTlr8Vd6Gw9KIl8q8ckmcY5fQG +BO+QueQA5N06tRn/Arr0PO7gi+s3i+z016zy9vA9r911kTMZHRxAy3QkGSGT2RT+ +rCpSx4/VBEnkjWNHiDxpg8v+R70rfk/Fla4OndTRQ8Bnc+MUCH7lP59zuDMKz10/ +NIeWiu5T6CUVAgMBAAGjgbIwga8wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E +BAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJaW1hZ2UvZ2lmMCEwHzAH +BgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYjaHR0cDovL2xvZ28udmVy +aXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFH/TZafC3ey78DAJ80M5+gKv +MzEzMA0GCSqGSIb3DQEBBQUAA4IBAQCTJEowX2LP2BqYLz3q3JktvXf2pXkiOOzE +p6B4Eq1iDkVwZMXnl2YtmAl+X6/WzChl8gGqCBpH3vn5fJJaCGkgDdk+bW48DW7Y +5gaRQBi5+MHt39tBquCWIMnNZBU4gcmU7qKEKQsTb47bDN0lAtukixlE0kF6BWlK +WE9gyn6CagsCqiUXObXbf+eEZSqVir2G3l6BFoMtEMze/aiCKm0oHw0LxOXnGiYZ +4fQRbxC1lfznQgUy286dUV4otp6F01vvpX1FQHKOtw5rDgb7MzVIcbidJ4vEZV8N +hnacRHr2lVz2XTIIM6RUthg/aFzyQkqFOFSDX9HoLPKsEdao7WNq +-----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=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Subject: CN=Network Solutions Certificate Authority O=Network Solutions L.L.C. +# Label: "Network Solutions Certificate Authority" +# Serial: 116697915152937497490437556386812487904 +# MD5 Fingerprint: d3:f3:a6:16:c0:fa:6b:1d:59:b1:2d:96:4d:0e:11:2e +# SHA1 Fingerprint: 74:f8:a3:c3:ef:e7:b3:90:06:4b:83:90:3c:21:64:60:20:e5:df:ce +# SHA256 Fingerprint: 15:f0:ba:00:a3:ac:7a:f3:ac:88:4c:07:2b:10:11:a0:77:bd:77:c0:97:f4:01:64:b2:f8:59:8a:bd:83:86:0c +-----BEGIN CERTIFICATE----- +MIID5jCCAs6gAwIBAgIQV8szb8JcFuZHFhfjkDFo4DANBgkqhkiG9w0BAQUFADBi +MQswCQYDVQQGEwJVUzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMu +MTAwLgYDVQQDEydOZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3Jp +dHkwHhcNMDYxMjAxMDAwMDAwWhcNMjkxMjMxMjM1OTU5WjBiMQswCQYDVQQGEwJV +UzEhMB8GA1UEChMYTmV0d29yayBTb2x1dGlvbnMgTC5MLkMuMTAwLgYDVQQDEydO +ZXR3b3JrIFNvbHV0aW9ucyBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkvH6SMG3G2I4rC7xGzuAnlt7e+foS0zwz +c7MEL7xxjOWftiJgPl9dzgn/ggwbmlFQGiaJ3dVhXRncEg8tCqJDXRfQNJIg6nPP +OCwGJgl6cvf6UDL4wpPTaaIjzkGxzOTVHzbRijr4jGPiFFlp7Q3Tf2vouAPlT2rl +mGNpSAW+Lv8ztumXWWn4Zxmuk2GWRBXTcrA/vGp97Eh/jcOrqnErU2lBUzS1sLnF +BgrEsEX1QV1uiUV7PTsmjHTC5dLRfbIR1PtYMiKagMnc/Qzpf14Dl847ABSHJ3A4 +qY5usyd2mFHgBeMhqxrVhSI8KbWaFsWAqPS7azCPL0YCorEMIuDTAgMBAAGjgZcw +gZQwHQYDVR0OBBYEFCEwyfsA106Y2oeqKtCnLrFAMadMMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MFIGA1UdHwRLMEkwR6BFoEOGQWh0dHA6Ly9jcmwu +bmV0c29sc3NsLmNvbS9OZXR3b3JrU29sdXRpb25zQ2VydGlmaWNhdGVBdXRob3Jp +dHkuY3JsMA0GCSqGSIb3DQEBBQUAA4IBAQC7rkvnt1frf6ott3NHhWrB5KUd5Oc8 +6fRZZXe1eltajSU24HqXLjjAV2CDmAaDn7l2em5Q4LqILPxFzBiwmZVRDuwduIj/ +h1AcgsLj4DKAv6ALR8jDMe+ZZzKATxcheQxpXN5eNK4CtSbqUN9/GGUsyfJj4akH +/nxxH2szJGoeBfcFaMBqEssuXmHLrijTfsK0ZpEmXzwuJF/LWA/rKOyvEZbz3Htv +wKeI8lN3s2Berq4o2jUsbzRF0ybh3uxbTydrFny9RAQYgrOJeRcQcT16ohZO9QHN +pGxlaKFJdlxDydi8NmdspZS11My5vWo1ViHe2MPr+8ukYEywVaCge1ey +-----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=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Subject: CN=OISTE WISeKey Global Root GA CA O=WISeKey OU=Copyright (c) 2005/OISTE Foundation Endorsed +# Label: "OISTE WISeKey Global Root GA CA" +# Serial: 86718877871133159090080555911823548314 +# MD5 Fingerprint: bc:6c:51:33:a7:e9:d3:66:63:54:15:72:1b:21:92:93 +# SHA1 Fingerprint: 59:22:a1:e1:5a:ea:16:35:21:f8:98:39:6a:46:46:b0:44:1b:0f:a9 +# SHA256 Fingerprint: 41:c9:23:86:6a:b4:ca:d6:b7:ad:57:80:81:58:2e:02:07:97:a6:cb:df:4f:ff:78:ce:83:96:b3:89:37:d7:f5 +-----BEGIN CERTIFICATE----- +MIID8TCCAtmgAwIBAgIQQT1yx/RrH4FDffHSKFTfmjANBgkqhkiG9w0BAQUFADCB +ijELMAkGA1UEBhMCQ0gxEDAOBgNVBAoTB1dJU2VLZXkxGzAZBgNVBAsTEkNvcHly +aWdodCAoYykgMjAwNTEiMCAGA1UECxMZT0lTVEUgRm91bmRhdGlvbiBFbmRvcnNl +ZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwgUm9vdCBHQSBDQTAeFw0w +NTEyMTExNjAzNDRaFw0zNzEyMTExNjA5NTFaMIGKMQswCQYDVQQGEwJDSDEQMA4G +A1UEChMHV0lTZUtleTEbMBkGA1UECxMSQ29weXJpZ2h0IChjKSAyMDA1MSIwIAYD +VQQLExlPSVNURSBGb3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBX +SVNlS2V5IEdsb2JhbCBSb290IEdBIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAy0+zAJs9Nt350UlqaxBJH+zYK7LG+DKBKUOVTJoZIyEVRd7jyBxR +VVuuk+g3/ytr6dTqvirdqFEr12bDYVxgAsj1znJ7O7jyTmUIms2kahnBAbtzptf2 +w93NvKSLtZlhuAGio9RN1AU9ka34tAhxZK9w8RxrfvbDd50kc3vkDIzh2TbhmYsF +mQvtRTEJysIA2/dyoJaqlYfQjse2YXMNdmaM3Bu0Y6Kff5MTMPGhJ9vZ/yxViJGg +4E8HsChWjBgbl0SOid3gF27nKu+POQoxhILYQBRJLnpB5Kf+42TMwVlxSywhp1t9 +4B3RLoGbw9ho972WG6xwsRYUC9tguSYBBQIDAQABo1EwTzALBgNVHQ8EBAMCAYYw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUswN+rja8sHnR3JQmthG+IbJphpQw +EAYJKwYBBAGCNxUBBAMCAQAwDQYJKoZIhvcNAQEFBQADggEBAEuh/wuHbrP5wUOx +SPMowB0uyQlB+pQAHKSkq0lPjz0e701vvbyk9vImMMkQyh2I+3QZH4VFvbBsUfk2 +ftv1TDI6QU9bR8/oCy22xBmddMVHxjtqD6wU2zz0c5ypBd8A3HR4+vg1YFkCExh8 +vPtNsCBtQ7tgMHpnM1zFmdH4LTlSc/uMqpclXHLZCB6rTjzjgTGfA6b7wP4piFXa +hNVQA7bihKOmNqoROgHhGEvWRGizPflTdISzRpFGlgC3gCy24eMQ4tui5yiPAZZi +Fj4A4xylNoEYokxSdsARo27mHbrjWr42U8U+dY+GaSlYU7Wcu2+fXMUY7N0v4ZjJ +/L7fCg0= +-----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: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center +# Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center +# Label: "Deutsche Telekom Root CA 2" +# Serial: 38 +# MD5 Fingerprint: 74:01:4a:91:b1:08:c4:58:ce:47:cd:f0:dd:11:53:08 +# SHA1 Fingerprint: 85:a4:08:c0:9c:19:3e:5d:51:58:7d:cd:d6:13:30:fd:8c:de:37:bf +# SHA256 Fingerprint: b6:19:1a:50:d0:c3:97:7f:7d:a9:9b:cd:aa:c8:6a:22:7d:ae:b9:67:9e:c7:0b:a3:b0:c9:d9:22:71:c1:70:d3 +-----BEGIN CERTIFICATE----- +MIIDnzCCAoegAwIBAgIBJjANBgkqhkiG9w0BAQUFADBxMQswCQYDVQQGEwJERTEc +MBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxlU2Vj +IFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290IENB +IDIwHhcNOTkwNzA5MTIxMTAwWhcNMTkwNzA5MjM1OTAwWjBxMQswCQYDVQQGEwJE +RTEcMBoGA1UEChMTRGV1dHNjaGUgVGVsZWtvbSBBRzEfMB0GA1UECxMWVC1UZWxl +U2VjIFRydXN0IENlbnRlcjEjMCEGA1UEAxMaRGV1dHNjaGUgVGVsZWtvbSBSb290 +IENBIDIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrC6M14IspFLEU +ha88EOQ5bzVdSq7d6mGNlUn0b2SjGmBmpKlAIoTZ1KXleJMOaAGtuU1cOs7TuKhC +QN/Po7qCWWqSG6wcmtoIKyUn+WkjR/Hg6yx6m/UTAtB+NHzCnjwAWav12gz1Mjwr +rFDa1sPeg5TKqAyZMg4ISFZbavva4VhYAUlfckE8FQYBjl2tqriTtM2e66foai1S +NNs671x1Udrb8zH57nGYMsRUFUQM+ZtV7a3fGAigo4aKSe5TBY8ZTNXeWHmb0moc +QqvF1afPaA+W5OFhmHZhyJF81j4A4pFQh+GdCuatl9Idxjp9y7zaAzTVjlsB9WoH +txa2bkp/AgMBAAGjQjBAMB0GA1UdDgQWBBQxw3kbuvVT1xfgiXotF2wKsyudMzAP +BgNVHRMECDAGAQH/AgEFMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQUFAAOC +AQEAlGRZrTlk5ynrE/5aw4sTV8gEJPB0d8Bg42f76Ymmg7+Wgnxu1MM9756Abrsp +tJh6sTtU6zkXR34ajgv8HzFZMQSyzhfzLMdiNlXiItiJVbSYSKpk+tYcNthEeFpa +IzpXl/V6ME+un2pMSyuOoAPjPuCp1NJ70rOo4nI8rZ7/gFnkm0W09juwzTkZmDLl +6iFhkOQxIY40sfcvNUqFENrnijchvllj4PKFiDFT1FQUhXB59C4Gdyd1Lx+4ivn+ +xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU +Cm26OWMohpLzGITY+9HPBVZkVw== +-----END CERTIFICATE----- + +# Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc +# Subject: CN=Cybertrust Global Root O=Cybertrust, Inc +# Label: "Cybertrust Global Root" +# Serial: 4835703278459682877484360 +# MD5 Fingerprint: 72:e4:4a:87:e3:69:40:80:77:ea:bc:e3:f4:ff:f0:e1 +# SHA1 Fingerprint: 5f:43:e5:b1:bf:f8:78:8c:ac:1c:c7:ca:4a:9a:c6:22:2b:cc:34:c6 +# SHA256 Fingerprint: 96:0a:df:00:63:e9:63:56:75:0c:29:65:dd:0a:08:67:da:0b:9c:bd:6e:77:71:4a:ea:fb:23:49:ab:39:3d:a3 +-----BEGIN CERTIFICATE----- +MIIDoTCCAomgAwIBAgILBAAAAAABD4WqLUgwDQYJKoZIhvcNAQEFBQAwOzEYMBYG +A1UEChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2Jh +bCBSb290MB4XDTA2MTIxNTA4MDAwMFoXDTIxMTIxNTA4MDAwMFowOzEYMBYGA1UE +ChMPQ3liZXJ0cnVzdCwgSW5jMR8wHQYDVQQDExZDeWJlcnRydXN0IEdsb2JhbCBS +b290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+Mi8vRRQZhP/8NN5 +7CPytxrHjoXxEnOmGaoQ25yiZXRadz5RfVb23CO21O1fWLE3TdVJDm71aofW0ozS +J8bi/zafmGWgE07GKmSb1ZASzxQG9Dvj1Ci+6A74q05IlG2OlTEQXO2iLb3VOm2y +HLtgwEZLAfVJrn5GitB0jaEMAs7u/OePuGtm839EAL9mJRQr3RAwHQeWP032a7iP +t3sMpTjr3kfb1V05/Iin89cqdPHoWqI7n1C6poxFNcJQZZXcY4Lv3b93TZxiyWNz +FtApD0mpSPCzqrdsxacwOUBdrsTiXSZT8M4cIwhhqJQZugRiQOwfOHB3EgZxpzAY +XSUnpQIDAQABo4GlMIGiMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBS2CHsNesysIEyGVjJez6tuhS1wVzA/BgNVHR8EODA2MDSgMqAw +hi5odHRwOi8vd3d3Mi5wdWJsaWMtdHJ1c3QuY29tL2NybC9jdC9jdHJvb3QuY3Js +MB8GA1UdIwQYMBaAFLYIew16zKwgTIZWMl7Pq26FLXBXMA0GCSqGSIb3DQEBBQUA +A4IBAQBW7wojoFROlZfJ+InaRcHUowAl9B8Tq7ejhVhpwjCt2BWKLePJzYFa+HMj +Wqd8BfP9IjsO0QbE2zZMcwSO5bAi5MXzLqXZI+O4Tkogp24CJJ8iYGd7ix1yCcUx +XOl5n4BHPa2hCwcUPUf/A2kaDAtE52Mlp3+yybh2hO0j9n0Hq0V+09+zv+mKts2o +omcrUtW3ZfA5TGOgkXmTUg9U3YO7n9GPp1Nzw8v/MOx8BLjYRB+TX3EJIrduPuoc +A06dGiBh+4E37F78CkWr1+cXVdCg6mCbpvbjjFspwgZgFJ0tl0ypkxWdYcQBX0jW +WL1WMRJOEcgh4LMRkWXbtKaIOM5V +-----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=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G3 O=GeoTrust Inc. OU=(c) 2008 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G3" +# Serial: 28809105769928564313984085209975885599 +# MD5 Fingerprint: b5:e8:34:36:c9:10:44:58:48:70:6d:2e:83:d4:b8:05 +# SHA1 Fingerprint: 03:9e:ed:b8:0b:e7:a0:3c:69:53:89:3b:20:d2:d9:32:3a:4c:2a:fd +# SHA256 Fingerprint: b4:78:b8:12:25:0d:f8:78:63:5c:2a:a7:ec:7d:15:5e:aa:62:5e:e8:29:16:e2:cd:29:43:61:88:6c:d1:fb:d4 +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQFaxulBmyeUtB9iepwxgPHzANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsT +MChjKSAyMDA4IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25s +eTE2MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhv +cml0eSAtIEczMB4XDTA4MDQwMjAwMDAwMFoXDTM3MTIwMTIzNTk1OVowgZgxCzAJ +BgNVBAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykg +MjAwOCBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0 +BgNVBAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkg +LSBHMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANziXmJYHTNXOTIz ++uvLh4yn1ErdBojqZI4xmKU4kB6Yzy5jK/BGvESyiaHAKAxJcCGVn2TAppMSAmUm +hsalifD614SgcK9PGpc/BkTVyetyEH3kMSj7HGHmKAdEc5IiaacDiGydY8hS2pgn +5whMcD60yRLBxWeDXTPzAxHsatBT4tG6NmCUgLthY2xbF37fQJQeqw3CIShwiP/W +JmxsYAQlTlV+fe+/lEjetx3dcI0FX4ilm/LC7urRQEFtYjgdVgbFA0dRIBn8exAL +DmKudlW/X3e+PkkBUz2YJQN2JFodtNuJ6nnltrM7P7pMKEF/BqxqjsHQ9gUdfeZC +huOl1UcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +HQYDVR0OBBYEFMR5yo6hTgMdHNxr2zFblD4/MH8tMA0GCSqGSIb3DQEBCwUAA4IB +AQAtxRPPVoB7eni9n64smefv2t+UXglpp+duaIy9cr5HqQ6XErhK8WTTOd8lNNTB +zU6B8A8ExCSzNJbGpqow32hhc9f5joWJ7w5elShKKiePEI4ufIbEAp7aDHdlDkQN +kv39sxY2+hENHYwOB4lqKVb3cvTdFZx3NWZXqxNT2I7BQMXXExZacse3aQHEerGD +AWh9jUGhlBjBJVz88P6DAod8DQ3PLghcSkANPuyBYeYk28rgDi0Hsj5W3I31QYUH +SJsMC8tJP33st/3LjWeJGqvtux6jAAgIFyqCXDFdRootD4abdNlF+9RAsXqqaC2G +spki4cErx5z481+oghLrGREt +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G2 O=thawte, Inc. OU=(c) 2007 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G2" +# Serial: 71758320672825410020661621085256472406 +# MD5 Fingerprint: 74:9d:ea:60:24:c4:fd:22:53:3e:cc:3a:72:d9:29:4f +# SHA1 Fingerprint: aa:db:bc:22:23:8f:c4:01:a1:27:bb:38:dd:f4:1d:db:08:9e:f0:12 +# SHA256 Fingerprint: a4:31:0d:50:af:18:a6:44:71:90:37:2a:86:af:af:8b:95:1f:fb:43:1d:83:7f:1e:56:88:b4:59:71:ed:15:57 +-----BEGIN CERTIFICATE----- +MIICiDCCAg2gAwIBAgIQNfwmXNmET8k9Jj1Xm67XVjAKBggqhkjOPQQDAzCBhDEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjE4MDYGA1UECxMvKGMp +IDIwMDcgdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAi +BgNVBAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMjAeFw0wNzExMDUwMDAw +MDBaFw0zODAxMTgyMzU5NTlaMIGEMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhh +d3RlLCBJbmMuMTgwNgYDVQQLEy8oYykgMjAwNyB0aGF3dGUsIEluYy4gLSBGb3Ig +YXV0aG9yaXplZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9v +dCBDQSAtIEcyMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEotWcgnuVnfFSeIf+iha/ +BebfowJPDQfGAFG6DAJSLSKkQjnE/o/qycG+1E3/n3qe4rF8mq2nhglzh9HnmuN6 +papu+7qzcMBniKI11KOasf2twu8x+qi58/sIxpHR+ymVo0IwQDAPBgNVHRMBAf8E +BTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUmtgAMADna3+FGO6Lts6K +DPgR4bswCgYIKoZIzj0EAwMDaQAwZgIxAN344FdHW6fmCsO99YCKlzUNG4k8VIZ3 +KMqh9HneteY4sPBlcIx/AlTCv//YoT7ZzwIxAMSNlPzcU9LcnXgWHxUzI1NS41ox +XZ3Krr0TKUQNJ1uo52icEvdYPy5yAlejj6EULg== +-----END CERTIFICATE----- + +# Issuer: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Subject: CN=thawte Primary Root CA - G3 O=thawte, Inc. OU=Certification Services Division/(c) 2008 thawte, Inc. - For authorized use only +# Label: "thawte Primary Root CA - G3" +# Serial: 127614157056681299805556476275995414779 +# MD5 Fingerprint: fb:1b:5d:43:8a:94:cd:44:c6:76:f2:43:4b:47:e7:31 +# SHA1 Fingerprint: f1:8b:53:8d:1b:e9:03:b6:a6:f0:56:43:5b:17:15:89:ca:f3:6b:f2 +# SHA256 Fingerprint: 4b:03:f4:58:07:ad:70:f2:1b:fc:2c:ae:71:c9:fd:e4:60:4c:06:4c:f5:ff:b6:86:ba:e5:db:aa:d7:fd:d3:4c +-----BEGIN CERTIFICATE----- +MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB +rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf +Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw +MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV +BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa +Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl +LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u +MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl +ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm +gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8 +YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf +b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9 +9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S +zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk +OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV +HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA +2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW +oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu +t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c +KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM +m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu +MdRAGmI0Nj81Aa6sY6A= +-----END CERTIFICATE----- + +# Issuer: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Subject: CN=GeoTrust Primary Certification Authority - G2 O=GeoTrust Inc. OU=(c) 2007 GeoTrust Inc. - For authorized use only +# Label: "GeoTrust Primary Certification Authority - G2" +# Serial: 80682863203381065782177908751794619243 +# MD5 Fingerprint: 01:5e:d8:6b:bd:6f:3d:8e:a1:31:f8:12:e0:98:73:6a +# SHA1 Fingerprint: 8d:17:84:d5:37:f3:03:7d:ec:70:fe:57:8b:51:9a:99:e6:10:d7:b0 +# SHA256 Fingerprint: 5e:db:7a:c4:3b:82:a0:6a:87:61:e8:d7:be:49:79:eb:f2:61:1f:7d:d7:9b:f9:1c:1c:6b:56:6a:21:9e:d7:66 +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQPLL0SAoA4v7rJDteYD7DazAKBggqhkjOPQQDAzCBmDEL +MAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IEluYy4xOTA3BgNVBAsTMChj +KSAyMDA3IEdlb1RydXN0IEluYy4gLSBGb3IgYXV0aG9yaXplZCB1c2Ugb25seTE2 +MDQGA1UEAxMtR2VvVHJ1c3QgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0 +eSAtIEcyMB4XDTA3MTEwNTAwMDAwMFoXDTM4MDExODIzNTk1OVowgZgxCzAJBgNV +BAYTAlVTMRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMTkwNwYDVQQLEzAoYykgMjAw +NyBHZW9UcnVzdCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxNjA0BgNV +BAMTLUdlb1RydXN0IFByaW1hcnkgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkgLSBH +MjB2MBAGByqGSM49AgEGBSuBBAAiA2IABBWx6P0DFUPlrOuHNxFi79KDNlJ9RVcL +So17VDs6bl8VAsBQps8lL33KSLjHUGMcKiEIfJo22Av+0SbFWDEwKCXzXV2juLal +tJLtbCyf691DiaI8S0iRHVDsJt/WYC69IaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAO +BgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBVfNVdRVfslsq0DafwBo/q+EVXVMAoG +CCqGSM49BAMDA2cAMGQCMGSWWaboCd6LuvpaiIjwH5HTRqjySkwCY/tsXzjbLkGT +qQ7mndwxHLKgpxgceeHHNgIwOlavmnRs9vuD4DPTCF+hnMJbn0bWtsuRBmOiBucz +rD6ogRLQy7rQkgu2npaqBA+K +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Universal Root Certification Authority O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2008 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Universal Root Certification Authority" +# Serial: 85209574734084581917763752644031726877 +# MD5 Fingerprint: 8e:ad:b5:01:aa:4d:81:e4:8c:1d:d1:e1:14:00:95:19 +# SHA1 Fingerprint: 36:79:ca:35:66:87:72:30:4d:30:a5:fb:87:3b:0f:a7:7b:b7:0d:54 +# SHA256 Fingerprint: 23:99:56:11:27:a5:71:25:de:8c:ef:ea:61:0d:df:2f:a0:78:b5:c8:06:7f:4e:82:82:90:bf:b8:60:e8:4b:3c +-----BEGIN CERTIFICATE----- +MIIEuTCCA6GgAwIBAgIQQBrEZCGzEyEDDrvkEhrFHTANBgkqhkiG9w0BAQsFADCB +vTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL +ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwOCBWZXJp +U2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MTgwNgYDVQQDEy9W +ZXJpU2lnbiBVbml2ZXJzYWwgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAe +Fw0wODA0MDIwMDAwMDBaFw0zNzEyMDEyMzU5NTlaMIG9MQswCQYDVQQGEwJVUzEX +MBUGA1UEChMOVmVyaVNpZ24sIEluYy4xHzAdBgNVBAsTFlZlcmlTaWduIFRydXN0 +IE5ldHdvcmsxOjA4BgNVBAsTMShjKSAyMDA4IFZlcmlTaWduLCBJbmMuIC0gRm9y +IGF1dGhvcml6ZWQgdXNlIG9ubHkxODA2BgNVBAMTL1ZlcmlTaWduIFVuaXZlcnNh +bCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAx2E3XrEBNNti1xWb/1hajCMj1mCOkdeQmIN65lgZOIzF +9uVkhbSicfvtvbnazU0AtMgtc6XHaXGVHzk8skQHnOgO+k1KxCHfKWGPMiJhgsWH +H26MfF8WIFFE0XBPV+rjHOPMee5Y2A7Cs0WTwCznmhcrewA3ekEzeOEz4vMQGn+H +LL729fdC4uW/h2KJXwBL38Xd5HVEMkE6HnFuacsLdUYI0crSK5XQz/u5QGtkjFdN +/BMReYTtXlT2NJ8IAfMQJQYXStrxHXpma5hgZqTZ79IugvHw7wnqRMkVauIDbjPT +rJ9VAMf2CGqUuV/c4DPxhGD5WycRtPwW8rtWaoAljQIDAQABo4GyMIGvMA8GA1Ud +EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMG0GCCsGAQUFBwEMBGEwX6FdoFsw +WTBXMFUWCWltYWdlL2dpZjAhMB8wBwYFKw4DAhoEFI/l0xqGrI2Oa8PPgGrUSBgs +exkuMCUWI2h0dHA6Ly9sb2dvLnZlcmlzaWduLmNvbS92c2xvZ28uZ2lmMB0GA1Ud +DgQWBBS2d/ppSEefUxLVwuoHMnYH0ZcHGTANBgkqhkiG9w0BAQsFAAOCAQEASvj4 +sAPmLGd75JR3Y8xuTPl9Dg3cyLk1uXBPY/ok+myDjEedO2Pzmvl2MpWRsXe8rJq+ +seQxIcaBlVZaDrHC1LGmWazxY8u4TB1ZkErvkBYoH1quEPuBUDgMbMzxPcP1Y+Oz +4yHJJDnp/RVmRvQbEdBNc6N9Rvk97ahfYtTxP/jgdFcrGJ2BtMQo2pSXpXDrrB2+ +BxHw1dvd5Yzw1TKwg+ZX4o+/vqGqvz0dtdQ46tewXDpPaj+PwGZsY6rp2aQW9IHR +lRQOfc2VNNnSj3BzgXucfr2YYdhFh5iQxeuGMMY1v/D/w1WIg0vvBZIGcfK4mJO3 +7M2CYfE45k+XmCpajQ== +-----END CERTIFICATE----- + +# Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G4 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 2007 VeriSign, Inc. - For authorized use only +# Label: "VeriSign Class 3 Public Primary Certification Authority - G4" +# Serial: 63143484348153506665311985501458640051 +# MD5 Fingerprint: 3a:52:e1:e7:fd:6f:3a:e3:6f:f3:6f:99:1b:f9:22:41 +# SHA1 Fingerprint: 22:d5:d8:df:8f:02:31:d1:8d:f7:9d:b7:cf:8a:2d:64:c9:3f:6c:3a +# SHA256 Fingerprint: 69:dd:d7:ea:90:bb:57:c9:3e:13:5d:c8:5e:a6:fc:d5:48:0b:60:32:39:bd:c4:54:fc:75:8b:2a:26:cf:7f:79 +-----BEGIN CERTIFICATE----- +MIIDhDCCAwqgAwIBAgIQL4D+I4wOIg9IZxIokYesszAKBggqhkjOPQQDAzCByjEL +MAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZW +ZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2ln +biwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJp +U2lnbiBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9y +aXR5IC0gRzQwHhcNMDcxMTA1MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCByjELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQLExZWZXJp +U2lnbiBUcnVzdCBOZXR3b3JrMTowOAYDVQQLEzEoYykgMjAwNyBWZXJpU2lnbiwg +SW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5MUUwQwYDVQQDEzxWZXJpU2ln +biBDbGFzcyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +IC0gRzQwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASnVnp8Utpkmw4tXNherJI9/gHm +GUo9FANL+mAnINmDiWn6VMaaGF5VKmTeBvaNSjutEDxlPZCIBIngMGGzrl0Bp3ve +fLK+ymVhAIau2o970ImtTR1ZmkGxvEeA3J5iw/mjgbIwga8wDwYDVR0TAQH/BAUw +AwEB/zAOBgNVHQ8BAf8EBAMCAQYwbQYIKwYBBQUHAQwEYTBfoV2gWzBZMFcwVRYJ +aW1hZ2UvZ2lmMCEwHzAHBgUrDgMCGgQUj+XTGoasjY5rw8+AatRIGCx7GS4wJRYj +aHR0cDovL2xvZ28udmVyaXNpZ24uY29tL3ZzbG9nby5naWYwHQYDVR0OBBYEFLMW +kf3upm7ktS5Jj4d4gYDs5bG1MAoGCCqGSM49BAMDA2gAMGUCMGYhDBgmYFo4e1ZC +4Kf8NoRRkSAsdk1DPcQdhCPQrNZ8NQbOzWm9kA3bbEhCHQ6qQgIxAJw9SDkjOVga +FRJZap7v1VmyHVIsmXHNxynfGyphe3HR3vPA5Q06Sqotp9iGKt0uEA== +-----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=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G2 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G2" +# Serial: 10000012 +# MD5 Fingerprint: 7c:a5:0f:f8:5b:9a:7d:6d:30:ae:54:5a:e3:42:a2:8a +# SHA1 Fingerprint: 59:af:82:79:91:86:c7:b4:75:07:cb:cf:03:57:46:eb:04:dd:b7:16 +# SHA256 Fingerprint: 66:8c:83:94:7d:a6:3b:72:4b:ec:e1:74:3c:31:a0:e6:ae:d0:db:8e:c5:b3:1b:e3:77:bb:78:4f:91:b6:71:6f +-----BEGIN CERTIFICATE----- +MIIFyjCCA7KgAwIBAgIEAJiWjDANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEcyMB4XDTA4MDMyNjExMTgxN1oX +DTIwMDMyNTExMDMxMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMjCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMVZ5291 +qj5LnLW4rJ4L5PnZyqtdj7U5EILXr1HgO+EASGrP2uEGQxGZqhQlEq0i6ABtQ8Sp +uOUfiUtnvWFI7/3S4GCI5bkYYCjDdyutsDeqN95kWSpGV+RLufg3fNU254DBtvPU +Z5uW6M7XxgpT0GtJlvOjCwV3SPcl5XCsMBQgJeN/dVrlSPhOewMHBPqCYYdu8DvE +pMfQ9XQ+pV0aCPKbJdL2rAQmPlU6Yiile7Iwr/g3wtG61jj99O9JMDeZJiFIhQGp +5Rbn3JBV3w/oOM2ZNyFPXfUib2rFEhZgF1XyZWampzCROME4HYYEhLoaJXhena/M +UGDWE4dS7WMfbWV9whUYdMrhfmQpjHLYFhN9C0lK8SgbIHRrxT3dsKpICT0ugpTN +GmXZK4iambwYfp/ufWZ8Pr2UuIHOzZgweMFvZ9C+X+Bo7d7iscksWXiSqt8rYGPy +5V6548r6f1CGPqI0GAwJaCgRHOThuVw+R7oyPxjMW4T182t0xHJ04eOLoEq9jWYv +6q012iDTiIJh8BIitrzQ1aTsr1SIJSQ8p22xcik/Plemf1WvbibG/ufMQFxRRIEK +eN5KzlW/HdXZt1bv8Hb/C3m1r737qWmRRpdogBQ2HbN/uymYNqUg+oJgYjOk7Na6 +B6duxc8UpufWkjTYgfX8HV2qXB72o007uPc5AgMBAAGjgZcwgZQwDwYDVR0TAQH/ +BAUwAwEB/zBSBgNVHSAESzBJMEcGBFUdIAAwPzA9BggrBgEFBQcCARYxaHR0cDov +L3d3dy5wa2lvdmVyaGVpZC5ubC9wb2xpY2llcy9yb290LXBvbGljeS1HMjAOBgNV +HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFJFoMocVHYnitfGsNig0jQt8YojrMA0GCSqG +SIb3DQEBCwUAA4ICAQCoQUpnKpKBglBu4dfYszk78wIVCVBR7y29JHuIhjv5tLyS +CZa59sCrI2AGeYwRTlHSeYAz+51IvuxBQ4EffkdAHOV6CMqqi3WtFMTC6GY8ggen +5ieCWxjmD27ZUD6KQhgpxrRW/FYQoAUXvQwjf/ST7ZwaUb7dRUG/kSS0H4zpX897 +IZmflZ85OkYcbPnNe5yQzSipx6lVu6xiNGI1E0sUOlWDuYaNkqbG9AclVMwWVxJK +gnjIFNkXgiYtXSAfea7+1HAWFpWD2DU5/1JddRwWxRNVz0fMdWVSSt7wsKfkCpYL ++63C4iWEst3kvX5ZbJvw8NjnyvLplzh+ib7M+zkXYT9y2zqR2GUBGR2tUKRXCnxL +vJxxcypFURmFzI79R6d0lR2o0a9OF7FpJsKqeFdbxU2n5Z4FF5TKsl+gSRiNNOkm +bEgeqmiSBeGCc1qb3AdbCG19ndeNIdn8FCCqwkXfP+cAslHkwvgFuXkajDTznlvk +N1trSt8sV4pAWja63XVECDdCcAz+3F4hoKOKwJCcaNpQ5kUQR3i2TtJlycM33+FC +Y7BXN0Ute4qcvwXqZVUz9zkQxSgqIXobisQk+T8VyJoVIPVVYpbtbZNQvOSqeK3Z +ywplh6ZmwcSBo3c6WB4L7oOLnR7SUqTMHW+wmG2UMbX4cQrcufx9MmDm66+KAQ== +-----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=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Chambers of Commerce Root - 2008 O=AC Camerfirma S.A. +# Label: "Chambers of Commerce Root - 2008" +# Serial: 11806822484801597146 +# MD5 Fingerprint: 5e:80:9e:84:5a:0e:65:0b:17:02:f3:55:18:2a:3e:d7 +# SHA1 Fingerprint: 78:6a:74:ac:76:ab:14:7f:9c:6a:30:50:ba:9e:a8:7e:fe:9a:ce:3c +# SHA256 Fingerprint: 06:3e:4a:fa:c4:91:df:d3:32:f3:08:9b:85:42:e9:46:17:d8:93:d7:fe:94:4e:10:a7:93:7e:e2:9d:96:93:c0 +-----BEGIN CERTIFICATE----- +MIIHTzCCBTegAwIBAgIJAKPaQn6ksa7aMA0GCSqGSIb3DQEBBQUAMIGuMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xKTAnBgNVBAMTIENoYW1iZXJz +IG9mIENvbW1lcmNlIFJvb3QgLSAyMDA4MB4XDTA4MDgwMTEyMjk1MFoXDTM4MDcz +MTEyMjk1MFowga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpNYWRyaWQgKHNlZSBj +dXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29tL2FkZHJlc3MpMRIw +EAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVyZmlybWEgUy5BLjEp +MCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAtIDIwMDgwggIiMA0G +CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCvAMtwNyuAWko6bHiUfaN/Gh/2NdW9 +28sNRHI+JrKQUrpjOyhYb6WzbZSm891kDFX29ufyIiKAXuFixrYp4YFs8r/lfTJq +VKAyGVn+H4vXPWCGhSRv4xGzdz4gljUha7MI2XAuZPeEklPWDrCQiorjh40G072Q +DuKZoRuGDtqaCrsLYVAGUvGef3bsyw/QHg3PmTA9HMRFEFis1tPo1+XqxQEHd9ZR +5gN/ikilTWh1uem8nk4ZcfUyS5xtYBkL+8ydddy/Js2Pk3g5eXNeJQ7KXOt3EgfL +ZEFHcpOrUMPrCXZkNNI5t3YRCQ12RcSprj1qr7V9ZS+UWBDsXHyvfuK2GNnQm05a +Sd+pZgvMPMZ4fKecHePOjlO+Bd5gD2vlGts/4+EhySnB8esHnFIbAURRPHsl18Tl +UlRdJQfKFiC4reRB7noI/plvg6aRArBsNlVq5331lubKgdaX8ZSD6e2wsWsSaR6s ++12pxZjptFtYer49okQ6Y1nUCyXeG0+95QGezdIp1Z8XGQpvvwyQ0wlf2eOKNcx5 +Wk0ZN5K3xMGtr/R5JJqyAQuxr1yW84Ay+1w9mPGgP0revq+ULtlVmhduYJ1jbLhj +ya6BXBg14JC7vjxPNyK5fuvPnnchpj04gftI2jE9K+OJ9dC1vX7gUMQSibMjmhAx +hduub+84Mxh2EQIDAQABo4IBbDCCAWgwEgYDVR0TAQH/BAgwBgEB/wIBDDAdBgNV +HQ4EFgQU+SSsD7K1+HnA+mCIG8TZTQKeFxkwgeMGA1UdIwSB2zCB2IAU+SSsD7K1 ++HnA+mCIG8TZTQKeFxmhgbSkgbEwga4xCzAJBgNVBAYTAkVVMUMwQQYDVQQHEzpN +YWRyaWQgKHNlZSBjdXJyZW50IGFkZHJlc3MgYXQgd3d3LmNhbWVyZmlybWEuY29t +L2FkZHJlc3MpMRIwEAYDVQQFEwlBODI3NDMyODcxGzAZBgNVBAoTEkFDIENhbWVy +ZmlybWEgUy5BLjEpMCcGA1UEAxMgQ2hhbWJlcnMgb2YgQ29tbWVyY2UgUm9vdCAt +IDIwMDiCCQCj2kJ+pLGu2jAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRV +HSAAMCowKAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20w +DQYJKoZIhvcNAQEFBQADggIBAJASryI1wqM58C7e6bXpeHxIvj99RZJe6dqxGfwW +PJ+0W2aeaufDuV2I6A+tzyMP3iU6XsxPpcG1Lawk0lgH3qLPaYRgM+gQDROpI9CF +5Y57pp49chNyM/WqfcZjHwj0/gF/JM8rLFQJ3uIrbZLGOU8W6jx+ekbURWpGqOt1 +glanq6B8aBMz9p0w8G8nOSQjKpD9kCk18pPfNKXG9/jvjA9iSnyu0/VU+I22mlaH +FoI6M6taIgj3grrqLuBHmrS1RaMFO9ncLkVAO+rcf+g769HsJtg1pDDFOqxXnrN2 +pSB7+R5KBWIBpih1YJeSDW4+TTdDDZIVnBgizVGZoCkaPF+KMjNbMMeJL0eYD6MD +xvbxrN8y8NmBGuScvfaAFPDRLLmF9dijscilIeUcE5fuDr3fKanvNFNb0+RqE4QG +tjICxFKuItLcsiFCGtpA8CnJ7AoMXOLQusxI0zcKzBIKinmwPQN/aUv0NCB9szTq +jktk9T79syNnFQ0EuPAtwQlRPLJsFfClI9eDdOTlLsn+mCdCxqvGnrDQWzilm1De +fhiYtUU79nm06PcaewaD+9CL2rvHvRirCG88gGtAPxkZumWK5r7VXNM21+9AUiRg +OGcEMeyP84LG3rlV8zsxkVrctQgVrXYlCg17LofiDKYGvCYQbTed7N14jHyAxfDZ +d0jQ +-----END CERTIFICATE----- + +# Issuer: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Subject: CN=Global Chambersign Root - 2008 O=AC Camerfirma S.A. +# Label: "Global Chambersign Root - 2008" +# Serial: 14541511773111788494 +# MD5 Fingerprint: 9e:80:ff:78:01:0c:2e:c1:36:bd:fe:96:90:6e:08:f3 +# SHA1 Fingerprint: 4a:bd:ee:ec:95:0d:35:9c:89:ae:c7:52:a1:2c:5b:29:f6:d6:aa:0c +# SHA256 Fingerprint: 13:63:35:43:93:34:a7:69:80:16:a0:d3:24:de:72:28:4e:07:9d:7b:52:20:bb:8f:bd:74:78:16:ee:be:ba:ca +-----BEGIN CERTIFICATE----- +MIIHSTCCBTGgAwIBAgIJAMnN0+nVfSPOMA0GCSqGSIb3DQEBBQUAMIGsMQswCQYD +VQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3VycmVudCBhZGRyZXNzIGF0 +IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAGA1UEBRMJQTgyNzQzMjg3 +MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAlBgNVBAMTHkdsb2JhbCBD +aGFtYmVyc2lnbiBSb290IC0gMjAwODAeFw0wODA4MDExMjMxNDBaFw0zODA3MzEx +MjMxNDBaMIGsMQswCQYDVQQGEwJFVTFDMEEGA1UEBxM6TWFkcmlkIChzZWUgY3Vy +cmVudCBhZGRyZXNzIGF0IHd3dy5jYW1lcmZpcm1hLmNvbS9hZGRyZXNzKTESMBAG +A1UEBRMJQTgyNzQzMjg3MRswGQYDVQQKExJBQyBDYW1lcmZpcm1hIFMuQS4xJzAl +BgNVBAMTHkdsb2JhbCBDaGFtYmVyc2lnbiBSb290IC0gMjAwODCCAiIwDQYJKoZI +hvcNAQEBBQADggIPADCCAgoCggIBAMDfVtPkOpt2RbQT2//BthmLN0EYlVJH6xed +KYiONWwGMi5HYvNJBL99RDaxccy9Wglz1dmFRP+RVyXfXjaOcNFccUMd2drvXNL7 +G706tcuto8xEpw2uIRU/uXpbknXYpBI4iRmKt4DS4jJvVpyR1ogQC7N0ZJJ0YPP2 +zxhPYLIj0Mc7zmFLmY/CDNBAspjcDahOo7kKrmCgrUVSY7pmvWjg+b4aqIG7HkF4 +ddPB/gBVsIdU6CeQNR1MM62X/JcumIS/LMmjv9GYERTtY/jKmIhYF5ntRQOXfjyG +HoiMvvKRhI9lNNgATH23MRdaKXoKGCQwoze1eqkBfSbW+Q6OWfH9GzO1KTsXO0G2 +Id3UwD2ln58fQ1DJu7xsepeY7s2MH/ucUa6LcL0nn3HAa6x9kGbo1106DbDVwo3V +yJ2dwW3Q0L9R5OP4wzg2rtandeavhENdk5IMagfeOx2YItaswTXbo6Al/3K1dh3e +beksZixShNBFks4c5eUzHdwHU1SjqoI7mjcv3N2gZOnm3b2u/GSFHTynyQbehP9r +6GsaPMWis0L7iwk+XwhSx2LE1AVxv8Rk5Pihg+g+EpuoHtQ2TS9x9o0o9oOpE9Jh +wZG7SMA0j0GMS0zbaRL/UJScIINZc+18ofLx/d33SdNDWKBWY8o9PeU1VlnpDsog +zCtLkykPAgMBAAGjggFqMIIBZjASBgNVHRMBAf8ECDAGAQH/AgEMMB0GA1UdDgQW +BBS5CcqcHtvTbDprru1U8VuTBjUuXjCB4QYDVR0jBIHZMIHWgBS5CcqcHtvTbDpr +ru1U8VuTBjUuXqGBsqSBrzCBrDELMAkGA1UEBhMCRVUxQzBBBgNVBAcTOk1hZHJp +ZCAoc2VlIGN1cnJlbnQgYWRkcmVzcyBhdCB3d3cuY2FtZXJmaXJtYS5jb20vYWRk +cmVzcykxEjAQBgNVBAUTCUE4Mjc0MzI4NzEbMBkGA1UEChMSQUMgQ2FtZXJmaXJt +YSBTLkEuMScwJQYDVQQDEx5HbG9iYWwgQ2hhbWJlcnNpZ24gUm9vdCAtIDIwMDiC +CQDJzdPp1X0jzjAOBgNVHQ8BAf8EBAMCAQYwPQYDVR0gBDYwNDAyBgRVHSAAMCow +KAYIKwYBBQUHAgEWHGh0dHA6Ly9wb2xpY3kuY2FtZXJmaXJtYS5jb20wDQYJKoZI +hvcNAQEFBQADggIBAICIf3DekijZBZRG/5BXqfEv3xoNa/p8DhxJJHkn2EaqbylZ +UohwEurdPfWbU1Rv4WCiqAm57OtZfMY18dwY6fFn5a+6ReAJ3spED8IXDneRRXoz +X1+WLGiLwUePmJs9wOzL9dWCkoQ10b42OFZyMVtHLaoXpGNR6woBrX/sdZ7LoR/x +fxKxueRkf2fWIyr0uDldmOghp+G9PUIadJpwr2hsUF1Jz//7Dl3mLEfXgTpZALVz +a2Mg9jFFCDkO9HB+QHBaP9BrQql0PSgvAm11cpUJjUhjxsYjV5KTXjXBjfkK9yyd +Yhz2rXzdpjEetrHHfoUm+qRqtdpjMNHvkzeyZi99Bffnt0uYlDXA2TopwZ2yUDMd +SqlapskD7+3056huirRXhOukP9DuqqqHW2Pok+JrqNS4cnhrG+055F3Lm6qH1U9O +AP7Zap88MQ8oAgF9mOinsKJknnn4SPIVqczmyETrP3iZ8ntxPjzxmKfFGBI/5rso +M0LpRQp8bfKGeS/Fghl9CYl8slR2iK7ewfPM4W7bMdaTrpmg7yVqc5iJWzouE4ge +v8CSlDQb4ye3ix5vQv/n6TebUB0tovkC7stYWDpxvGjjqsGvHCgfotwjZT+B6q6Z +09gwzxMNTxXJhLynSC34MCN32EZLeW32jO06f2ARePTpm67VVMB0gNELQp/B +-----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=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Subject: CN=Hellenic Academic and Research Institutions RootCA 2011 O=Hellenic Academic and Research Institutions Cert. Authority +# Label: "Hellenic Academic and Research Institutions RootCA 2011" +# Serial: 0 +# MD5 Fingerprint: 73:9f:4c:4b:73:5b:79:e9:fa:ba:1c:ef:6e:cb:d5:c9 +# SHA1 Fingerprint: fe:45:65:9b:79:03:5b:98:a1:61:b5:51:2e:ac:da:58:09:48:22:4d +# SHA256 Fingerprint: bc:10:4f:15:a4:8b:e7:09:dc:a5:42:a7:e1:d4:b9:df:6f:05:45:27:e8:02:ea:a9:2d:59:54:44:25:8a:fe:71 +-----BEGIN CERTIFICATE----- +MIIEMTCCAxmgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBlTELMAkGA1UEBhMCR1Ix +RDBCBgNVBAoTO0hlbGxlbmljIEFjYWRlbWljIGFuZCBSZXNlYXJjaCBJbnN0aXR1 +dGlvbnMgQ2VydC4gQXV0aG9yaXR5MUAwPgYDVQQDEzdIZWxsZW5pYyBBY2FkZW1p +YyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIFJvb3RDQSAyMDExMB4XDTExMTIw +NjEzNDk1MloXDTMxMTIwMTEzNDk1MlowgZUxCzAJBgNVBAYTAkdSMUQwQgYDVQQK +EztIZWxsZW5pYyBBY2FkZW1pYyBhbmQgUmVzZWFyY2ggSW5zdGl0dXRpb25zIENl +cnQuIEF1dGhvcml0eTFAMD4GA1UEAxM3SGVsbGVuaWMgQWNhZGVtaWMgYW5kIFJl +c2VhcmNoIEluc3RpdHV0aW9ucyBSb290Q0EgMjAxMTCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAKlTAOMupvaO+mDYLZU++CwqVE7NuYRhlFhPjz2L5EPz +dYmNUeTDN9KKiE15HrcS3UN4SoqS5tdI1Q+kOilENbgH9mgdVc04UfCMJDGFr4PJ +fel3r+0ae50X+bOdOFAPplp5kYCvN66m0zH7tSYJnTxa71HFK9+WXesyHgLacEns +bgzImjeN9/E2YEsmLIKe0HjzDQ9jpFEw4fkrJxIH2Oq9GGKYsFk3fb7u8yBRQlqD +75O6aRXxYp2fmTmCobd0LovUxQt7L/DICto9eQqakxylKHJzkUOap9FNhYS5qXSP +FEDH3N6sQWRstBmbAmNtJGSPRLIl6s5ddAxjMlyNh+UCAwEAAaOBiTCBhjAPBgNV +HRMBAf8EBTADAQH/MAsGA1UdDwQEAwIBBjAdBgNVHQ4EFgQUppFC/RNhSiOeCKQp +5dgTBCPuQSUwRwYDVR0eBEAwPqA8MAWCAy5ncjAFggMuZXUwBoIELmVkdTAGggQu +b3JnMAWBAy5ncjAFgQMuZXUwBoEELmVkdTAGgQQub3JnMA0GCSqGSIb3DQEBBQUA +A4IBAQAf73lB4XtuP7KMhjdCSk4cNx6NZrokgclPEg8hwAOXhiVtXdMiKahsog2p +6z0GW5k6x8zDmjR/qw7IThzh+uTczQ2+vyT+bOdrwg3IBp5OjWEopmr95fZi6hg8 +TqBTnbI6nOulnJEWtk2C4AwFSKls9cz4y51JtPACpf1wA+2KIaWuE4ZJwzNzvoc7 +dIsXRSZMFpGD/md9zU1jZ/rzAxKWeAaNsWftjj++n08C9bMJL/NMh98qy5V8Acys +Nnq/onN694/BtZqhFLKPM58N7yLcZnuEvUUXBj08yrl3NI/K6s8/MT7jiOOASSXI +l7WdmplNsDz4SgCbZN2fOUvRJ9e4 +-----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: O=Trustis Limited OU=Trustis FPS Root CA +# Subject: O=Trustis Limited OU=Trustis FPS Root CA +# Label: "Trustis FPS Root CA" +# Serial: 36053640375399034304724988975563710553 +# MD5 Fingerprint: 30:c9:e7:1e:6b:e6:14:eb:65:b2:16:69:20:31:67:4d +# SHA1 Fingerprint: 3b:c0:38:0b:33:c3:f6:a6:0c:86:15:22:93:d9:df:f5:4b:81:c0:04 +# SHA256 Fingerprint: c1:b4:82:99:ab:a5:20:8f:e9:63:0a:ce:55:ca:68:a0:3e:da:5a:51:9c:88:02:a0:d3:a6:73:be:8f:8e:55:7d +-----BEGIN CERTIFICATE----- +MIIDZzCCAk+gAwIBAgIQGx+ttiD5JNM2a/fH8YygWTANBgkqhkiG9w0BAQUFADBF +MQswCQYDVQQGEwJHQjEYMBYGA1UEChMPVHJ1c3RpcyBMaW1pdGVkMRwwGgYDVQQL +ExNUcnVzdGlzIEZQUyBSb290IENBMB4XDTAzMTIyMzEyMTQwNloXDTI0MDEyMTEx +MzY1NFowRTELMAkGA1UEBhMCR0IxGDAWBgNVBAoTD1RydXN0aXMgTGltaXRlZDEc +MBoGA1UECxMTVHJ1c3RpcyBGUFMgUm9vdCBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMVQe547NdDfxIzNjpvto8A2mfRC6qc+gIMPpqdZh8mQRUN+ +AOqGeSoDvT03mYlmt+WKVoaTnGhLaASMk5MCPjDSNzoiYYkchU59j9WvezX2fihH +iTHcDnlkH5nSW7r+f2C/revnPDgpai/lkQtV/+xvWNUtyd5MZnGPDNcE2gfmHhjj +vSkCqPoc4Vu5g6hBSLwacY3nYuUtsuvffM/bq1rKMfFMIvMFE/eC+XN5DL7XSxzA +0RU8k0Fk0ea+IxciAIleH2ulrG6nS4zto3Lmr2NNL4XSFDWaLk6M6jKYKIahkQlB +OrTh4/L68MkKokHdqeMDx4gVOxzUGpTXn2RZEm0CAwEAAaNTMFEwDwYDVR0TAQH/ +BAUwAwEB/zAfBgNVHSMEGDAWgBS6+nEleYtXQSUhhgtx67JkDoshZzAdBgNVHQ4E +FgQUuvpxJXmLV0ElIYYLceuyZA6LIWcwDQYJKoZIhvcNAQEFBQADggEBAH5Y//01 +GX2cGE+esCu8jowU/yyg2kdbw++BLa8F6nRIW/M+TgfHbcWzk88iNVy2P3UnXwmW +zaD+vkAMXBJV+JOCyinpXj9WV4s4NvdFGkwozZ5BuO1WTISkQMi4sKUraXAEasP4 +1BIy+Q7DsdwyhEQsb8tGD+pmQQ9P8Vilpg0ND2HepZ5dfWWhPBfnqFVO76DH7cZE +f1T1o+CP8HxVIo8ptoGj4W1OLBuAZ+ytIJ8MYmHVl/9D7S3B2l0pKoU/rGXuhg8F +jZBf3+6f9L/uHfuY5H+QK4R4EA5sSVPvFVtlRkpdr7r7OnIdzfYliB6XzCGcKQEN +ZetX2fNXlrtIzYE= +-----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=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Subject: CN=EE Certification Centre Root CA O=AS Sertifitseerimiskeskus +# Label: "EE Certification Centre Root CA" +# Serial: 112324828676200291871926431888494945866 +# MD5 Fingerprint: 43:5e:88:d4:7d:1a:4a:7e:fd:84:2e:52:eb:01:d4:6f +# SHA1 Fingerprint: c9:a8:b9:e7:55:80:5e:58:e3:53:77:a7:25:eb:af:c3:7b:27:cc:d7 +# SHA256 Fingerprint: 3e:84:ba:43:42:90:85:16:e7:75:73:c0:99:2f:09:79:ca:08:4e:46:85:68:1f:f1:95:cc:ba:8a:22:9b:8a:76 +-----BEGIN CERTIFICATE----- +MIIEAzCCAuugAwIBAgIQVID5oHPtPwBMyonY43HmSjANBgkqhkiG9w0BAQUFADB1 +MQswCQYDVQQGEwJFRTEiMCAGA1UECgwZQVMgU2VydGlmaXRzZWVyaW1pc2tlc2t1 +czEoMCYGA1UEAwwfRUUgQ2VydGlmaWNhdGlvbiBDZW50cmUgUm9vdCBDQTEYMBYG +CSqGSIb3DQEJARYJcGtpQHNrLmVlMCIYDzIwMTAxMDMwMTAxMDMwWhgPMjAzMDEy +MTcyMzU5NTlaMHUxCzAJBgNVBAYTAkVFMSIwIAYDVQQKDBlBUyBTZXJ0aWZpdHNl +ZXJpbWlza2Vza3VzMSgwJgYDVQQDDB9FRSBDZXJ0aWZpY2F0aW9uIENlbnRyZSBS +b290IENBMRgwFgYJKoZIhvcNAQkBFglwa2lAc2suZWUwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQDIIMDs4MVLqwd4lfNE7vsLDP90jmG7sWLqI9iroWUy +euuOF0+W2Ap7kaJjbMeMTC55v6kF/GlclY1i+blw7cNRfdCT5mzrMEvhvH2/UpvO +bntl8jixwKIy72KyaOBhU8E2lf/slLo2rpwcpzIP5Xy0xm90/XsY6KxX7QYgSzIw +WFv9zajmofxwvI6Sc9uXp3whrj3B9UiHbCe9nyV0gVWw93X2PaRka9ZP585ArQ/d +MtO8ihJTmMmJ+xAdTX7Nfh9WDSFwhfYggx/2uh8Ej+p3iDXE/+pOoYtNP2MbRMNE +1CV2yreN1x5KZmTNXMWcg+HCCIia7E6j8T4cLNlsHaFLAgMBAAGjgYowgYcwDwYD +VR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFBLyWj7qVhy/ +zQas8fElyalL1BSZMEUGA1UdJQQ+MDwGCCsGAQUFBwMCBggrBgEFBQcDAQYIKwYB +BQUHAwMGCCsGAQUFBwMEBggrBgEFBQcDCAYIKwYBBQUHAwkwDQYJKoZIhvcNAQEF +BQADggEBAHv25MANqhlHt01Xo/6tu7Fq1Q+e2+RjxY6hUFaTlrg4wCQiZrxTFGGV +v9DHKpY5P30osxBAIWrEr7BSdxjhlthWXePdNl4dp1BUoMUq5KqMlIpPnTX/dqQG +E5Gion0ARD9V04I8GtVbvFZMIi5GQ4okQC3zErg7cBqklrkar4dBGmoYDQZPxz5u +uSlNDUmJEYcyW+ZLBMjkXOZ0c5RdFpgTlf7727FE5TpwrDdr5rMzcijJs1eg9gIW +iAYLtqZLICjU3j2LrTcFU3T+bsy8QxdxXvnFzBqpYe73dgzzcvRyrc9yAjYHR8/v +GVCJYMzpJJUPwssd8m92kMfMdcGWxZ0= +-----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 - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 14367148294922964480859022125800977897474 +# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e +# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb +# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----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=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G3" +# Serial: 10003001 +# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 +# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc +# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Label: "Staat der Nederlanden EV Root CA" +# Serial: 10000013 +# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba +# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb +# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y +MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg +TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS +M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC +UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d +Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p +rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l +pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb +j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC +KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS +/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X +cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH +1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP +px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 +MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u +2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS +v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC +wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy +CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e +vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 +Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa +Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL +eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 +FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc +7uzXLg== +-----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=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Label: "Certinomis - Root CA" +# Serial: 1 +# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f +# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 +# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 +-----BEGIN CERTIFICATE----- +MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET +MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb +BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz +MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx +FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g +Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 +fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl +LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV +WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF +TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb +5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc +CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri +wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ +wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG +m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 +F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng +WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 +2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF +AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ +0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw +F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS +g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj +qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN +h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ +ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V +btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj +Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ +8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW +gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= +-----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=Certplus Root CA G1 O=Certplus +# Subject: CN=Certplus Root CA G1 O=Certplus +# Label: "Certplus Root CA G1" +# Serial: 1491911565779898356709731176965615564637713 +# MD5 Fingerprint: 7f:09:9c:f7:d9:b9:5c:69:69:56:d5:37:3e:14:0d:42 +# SHA1 Fingerprint: 22:fd:d0:b7:fd:a2:4e:0d:ac:49:2c:a0:ac:a6:7b:6a:1f:e3:f7:66 +# SHA256 Fingerprint: 15:2a:40:2b:fc:df:2c:d5:48:05:4d:22:75:b3:9c:7f:ca:3e:c0:97:80:78:b0:f0:ea:76:e5:61:a6:c7:43:3e +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgISESBVg+QtPlRWhS2DN7cs3EYRMA0GCSqGSIb3DQEBDQUA +MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy +dHBsdXMgUm9vdCBDQSBHMTAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBa +MD4xCzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2Vy +dHBsdXMgUm9vdCBDQSBHMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB +ANpQh7bauKk+nWT6VjOaVj0W5QOVsjQcmm1iBdTYj+eJZJ+622SLZOZ5KmHNr49a +iZFluVj8tANfkT8tEBXgfs+8/H9DZ6itXjYj2JizTfNDnjl8KvzsiNWI7nC9hRYt +6kuJPKNxQv4c/dMcLRC4hlTqQ7jbxofaqK6AJc96Jh2qkbBIb6613p7Y1/oA/caP +0FG7Yn2ksYyy/yARujVjBYZHYEMzkPZHogNPlk2dT8Hq6pyi/jQu3rfKG3akt62f +6ajUeD94/vI4CTYd0hYCyOwqaK/1jpTvLRN6HkJKHRUxrgwEV/xhc/MxVoYxgKDE +EW4wduOU8F8ExKyHcomYxZ3MVwia9Az8fXoFOvpHgDm2z4QTd28n6v+WZxcIbekN +1iNQMLAVdBM+5S//Ds3EC0pd8NgAM0lm66EYfFkuPSi5YXHLtaW6uOrc4nBvCGrc +h2c0798wct3zyT8j/zXhviEpIDCB5BmlIOklynMxdCm+4kLV87ImZsdo/Rmz5yCT +mehd4F6H50boJZwKKSTUzViGUkAksnsPmBIgJPaQbEfIDbsYIC7Z/fyL8inqh3SV +4EJQeIQEQWGw9CEjjy3LKCHyamz0GqbFFLQ3ZU+V/YDI+HLlJWvEYLF7bY5KinPO +WftwenMGE9nTdDckQQoRb5fc5+R+ob0V8rqHDz1oihYHAgMBAAGjYzBhMA4GA1Ud +DwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSowcCbkahDFXxd +Bie0KlHYlwuBsTAfBgNVHSMEGDAWgBSowcCbkahDFXxdBie0KlHYlwuBsTANBgkq +hkiG9w0BAQ0FAAOCAgEAnFZvAX7RvUz1isbwJh/k4DgYzDLDKTudQSk0YcbX8ACh +66Ryj5QXvBMsdbRX7gp8CXrc1cqh0DQT+Hern+X+2B50ioUHj3/MeXrKls3N/U/7 +/SMNkPX0XtPGYX2eEeAC7gkE2Qfdpoq3DIMku4NQkv5gdRE+2J2winq14J2by5BS +S7CTKtQ+FjPlnsZlFT5kOwQ/2wyPX1wdaR+v8+khjPPvl/aatxm2hHSco1S1cE5j +2FddUyGbQJJD+tZ3VTNPZNX70Cxqjm0lpu+F6ALEUz65noe8zDUa3qHpimOHZR4R +Kttjd5cUvpoUmRGywO6wT/gUITJDT5+rosuoD6o7BlXGEilXCNQ314cnrUlZp5Gr +RHpejXDbl85IULFzk/bwg2D5zfHhMf1bfHEhYxQUqq/F3pN+aLHsIqKqkHWetUNy +6mSjhEv9DKgma3GX7lZjZuhCVPnHHd/Qj1vfyDBviP4NxDMcU6ij/UgQ8uQKTuEV +V/xuZDDCVRHc6qnNSlSsKWNEz0pAoNZoWRsz+e86i9sgktxChL8Bq4fA1SCC28a5 +g4VCXA9DO2pJNdWY9BW/+mGBDAkgGNLQFwzLSABQ6XaCjGTXOqAHVcweMcDvOrRl +++O/QmueD6i9a5jc2NvLi6Td11n0bt3+qsOR0C5CB8AMTVPNJLFMWx5R9N/pkvo= +-----END CERTIFICATE----- + +# Issuer: CN=Certplus Root CA G2 O=Certplus +# Subject: CN=Certplus Root CA G2 O=Certplus +# Label: "Certplus Root CA G2" +# Serial: 1492087096131536844209563509228951875861589 +# MD5 Fingerprint: a7:ee:c4:78:2d:1b:ee:2d:b9:29:ce:d6:a7:96:32:31 +# SHA1 Fingerprint: 4f:65:8e:1f:e9:06:d8:28:02:e9:54:47:41:c9:54:25:5d:69:cc:1a +# SHA256 Fingerprint: 6c:c0:50:41:e6:44:5e:74:69:6c:4c:fb:c9:f8:0f:54:3b:7e:ab:bb:44:b4:ce:6f:78:7c:6a:99:71:c4:2f:17 +-----BEGIN CERTIFICATE----- +MIICHDCCAaKgAwIBAgISESDZkc6uo+jF5//pAq/Pc7xVMAoGCCqGSM49BAMDMD4x +CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs +dXMgUm9vdCBDQSBHMjAeFw0xNDA1MjYwMDAwMDBaFw0zODAxMTUwMDAwMDBaMD4x +CzAJBgNVBAYTAkZSMREwDwYDVQQKDAhDZXJ0cGx1czEcMBoGA1UEAwwTQ2VydHBs +dXMgUm9vdCBDQSBHMjB2MBAGByqGSM49AgEGBSuBBAAiA2IABM0PW1aC3/BFGtat +93nwHcmsltaeTpwftEIRyoa/bfuFo8XlGVzX7qY/aWfYeOKmycTbLXku54uNAm8x +Ik0G42ByRZ0OQneezs/lf4WbGOT8zC5y0xaTTsqZY1yhBSpsBqNjMGEwDgYDVR0P +AQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNqDYwJ5jtpMxjwj +FNiPwyCrKGBZMB8GA1UdIwQYMBaAFNqDYwJ5jtpMxjwjFNiPwyCrKGBZMAoGCCqG +SM49BAMDA2gAMGUCMHD+sAvZ94OX7PNVHdTcswYO/jOYnYs5kGuUIe22113WTNch +p+e/IQ8rzfcq3IUHnQIxAIYUFuXcsGXCwI4Un78kFmjlvPl5adytRSv3tjFzzAal +U5ORGpOucGpnutee5WEaXw== +-----END CERTIFICATE----- + +# Issuer: CN=OpenTrust Root CA G1 O=OpenTrust +# Subject: CN=OpenTrust Root CA G1 O=OpenTrust +# Label: "OpenTrust Root CA G1" +# Serial: 1492036577811947013770400127034825178844775 +# MD5 Fingerprint: 76:00:cc:81:29:cd:55:5e:88:6a:7a:2e:f7:4d:39:da +# SHA1 Fingerprint: 79:91:e8:34:f7:e2:ee:dd:08:95:01:52:e9:55:2d:14:e9:58:d5:7e +# SHA256 Fingerprint: 56:c7:71:28:d9:8c:18:d9:1b:4c:fd:ff:bc:25:ee:91:03:d4:75:8e:a2:ab:ad:82:6a:90:f3:45:7d:46:0e:b4 +-----BEGIN CERTIFICATE----- +MIIFbzCCA1egAwIBAgISESCzkFU5fX82bWTCp59rY45nMA0GCSqGSIb3DQEBCwUA +MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w +ZW5UcnVzdCBSb290IENBIEcxMB4XDTE0MDUyNjA4NDU1MFoXDTM4MDExNTAwMDAw +MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU +T3BlblRydXN0IFJvb3QgQ0EgRzEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQD4eUbalsUwXopxAy1wpLuwxQjczeY1wICkES3d5oeuXT2R0odsN7faYp6b +wiTXj/HbpqbfRm9RpnHLPhsxZ2L3EVs0J9V5ToybWL0iEA1cJwzdMOWo010hOHQX +/uMftk87ay3bfWAfjH1MBcLrARYVmBSO0ZB3Ij/swjm4eTrwSSTilZHcYTSSjFR0 +77F9jAHiOH3BX2pfJLKOYheteSCtqx234LSWSE9mQxAGFiQD4eCcjsZGT44ameGP +uY4zbGneWK2gDqdkVBFpRGZPTBKnjix9xNRbxQA0MMHZmf4yzgeEtE7NCv82TWLx +p2NX5Ntqp66/K7nJ5rInieV+mhxNaMbBGN4zK1FGSxyO9z0M+Yo0FMT7MzUj8czx +Kselu7Cizv5Ta01BG2Yospb6p64KTrk5M0ScdMGTHPjgniQlQ/GbI4Kq3ywgsNw2 +TgOzfALU5nsaqocTvz6hdLubDuHAk5/XpGbKuxs74zD0M1mKB3IDVedzagMxbm+W +G+Oin6+Sx+31QrclTDsTBM8clq8cIqPQqwWyTBIjUtz9GVsnnB47ev1CI9sjgBPw +vFEVVJSmdz7QdFG9URQIOTfLHzSpMJ1ShC5VkLG631UAC9hWLbFJSXKAqWLXwPYY +EQRVzXR7z2FwefR7LFxckvzluFqrTJOVoSfupb7PcSNCupt2LQIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUl0YhVyE1 +2jZVx/PxN3DlCPaTKbYwHwYDVR0jBBgwFoAUl0YhVyE12jZVx/PxN3DlCPaTKbYw +DQYJKoZIhvcNAQELBQADggIBAB3dAmB84DWn5ph76kTOZ0BP8pNuZtQ5iSas000E +PLuHIT839HEl2ku6q5aCgZG27dmxpGWX4m9kWaSW7mDKHyP7Rbr/jyTwyqkxf3kf +gLMtMrpkZ2CvuVnN35pJ06iCsfmYlIrM4LvgBBuZYLFGZdwIorJGnkSI6pN+VxbS +FXJfLkur1J1juONI5f6ELlgKn0Md/rcYkoZDSw6cMoYsYPXpSOqV7XAp8dUv/TW0 +V8/bhUiZucJvbI/NeJWsZCj9VrDDb8O+WVLhX4SPgPL0DTatdrOjteFkdjpY3H1P +XlZs5VVZV6Xf8YpmMIzUUmI4d7S+KNfKNsSbBfD4Fdvb8e80nR14SohWZ25g/4/I +i+GOvUKpMwpZQhISKvqxnUOOBZuZ2mKtVzazHbYNeS2WuOvyDEsMpZTGMKcmGS3t +TAZQMPH9WD25SxdfGbRqhFS0OE85og2WaMMolP3tLR9Ka0OWLpABEPs4poEL0L91 +09S5zvE/bw4cHjdx5RiHdRk/ULlepEU0rbDK5uUTdg8xFKmOLZTW1YVNcxVPS/Ky +Pu1svf0OnWZzsD2097+o4BGkxK51CUpjAEggpsadCwmKtODmzj7HPiY46SvepghJ +AwSQiumPv+i2tCqjI40cHLI5kqiPAlxAOXXUc0ECd97N4EOH1uS6SsNsEn/+KuYj +1oxx +-----END CERTIFICATE----- + +# Issuer: CN=OpenTrust Root CA G2 O=OpenTrust +# Subject: CN=OpenTrust Root CA G2 O=OpenTrust +# Label: "OpenTrust Root CA G2" +# Serial: 1492012448042702096986875987676935573415441 +# MD5 Fingerprint: 57:24:b6:59:24:6b:ae:c8:fe:1c:0c:20:f2:c0:4e:eb +# SHA1 Fingerprint: 79:5f:88:60:c5:ab:7c:3d:92:e6:cb:f4:8d:e1:45:cd:11:ef:60:0b +# SHA256 Fingerprint: 27:99:58:29:fe:6a:75:15:c1:bf:e8:48:f9:c4:76:1d:b1:6c:22:59:29:25:7b:f4:0d:08:94:f2:9e:a8:ba:f2 +-----BEGIN CERTIFICATE----- +MIIFbzCCA1egAwIBAgISESChaRu/vbm9UpaPI+hIvyYRMA0GCSqGSIb3DQEBDQUA +MEAxCzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9w +ZW5UcnVzdCBSb290IENBIEcyMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAw +MFowQDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwU +T3BlblRydXN0IFJvb3QgQ0EgRzIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQDMtlelM5QQgTJT32F+D3Y5z1zCU3UdSXqWON2ic2rxb95eolq5cSG+Ntmh +/LzubKh8NBpxGuga2F8ORAbtp+Dz0mEL4DKiltE48MLaARf85KxP6O6JHnSrT78e +CbY2albz4e6WiWYkBuTNQjpK3eCasMSCRbP+yatcfD7J6xcvDH1urqWPyKwlCm/6 +1UWY0jUJ9gNDlP7ZvyCVeYCYitmJNbtRG6Q3ffyZO6v/v6wNj0OxmXsWEH4db0fE +FY8ElggGQgT4hNYdvJGmQr5J1WqIP7wtUdGejeBSzFfdNTVY27SPJIjki9/ca1TS +gSuyzpJLHB9G+h3Ykst2Z7UJmQnlrBcUVXDGPKBWCgOz3GIZ38i1MH/1PCZ1Eb3X +G7OHngevZXHloM8apwkQHZOJZlvoPGIytbU6bumFAYueQ4xncyhZW+vj3CzMpSZy +YhK05pyDRPZRpOLAeiRXyg6lPzq1O4vldu5w5pLeFlwoW5cZJ5L+epJUzpM5ChaH +vGOz9bGTXOBut9Dq+WIyiET7vycotjCVXRIouZW+j1MY5aIYFuJWpLIsEPUdN6b4 +t/bQWVyJ98LVtZR00dX+G7bw5tYee9I8y6jj9RjzIR9u701oBnstXW5DiabA+aC/ +gh7PU3+06yzbXfZqfUAkBXKJOAGTy3HCOV0GEfZvePg3DTmEJwIDAQABo2MwYTAO +BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUajn6QiL3 +5okATV59M4PLuG53hq8wHwYDVR0jBBgwFoAUajn6QiL35okATV59M4PLuG53hq8w +DQYJKoZIhvcNAQENBQADggIBAJjLq0A85TMCl38th6aP1F5Kr7ge57tx+4BkJamz +Gj5oXScmp7oq4fBXgwpkTx4idBvpkF/wrM//T2h6OKQQbA2xx6R3gBi2oihEdqc0 +nXGEL8pZ0keImUEiyTCYYW49qKgFbdEfwFFEVn8nNQLdXpgKQuswv42hm1GqO+qT +RmTFAHneIWv2V6CG1wZy7HBGS4tz3aAhdT7cHcCP009zHIXZ/n9iyJVvttN7jLpT +wm+bREx50B1ws9efAvSyB7DH5fitIw6mVskpEndI2S9G/Tvw/HRwkqWOOAgfZDC2 +t0v7NqwQjqBSM2OdAzVWxWm9xiNaJ5T2pBL4LTM8oValX9YZ6e18CL13zSdkzJTa +TkZQh+D5wVOAHrut+0dSixv9ovneDiK3PTNZbNTe9ZUGMg1RGUFcPk8G97krgCf2 +o6p6fAbhQ8MTOWIaNr3gKC6UAuQpLmBVrkA9sHSSXvAgZJY/X0VdiLWK2gKgW0VU +3jg9CcCoSmVGFvyqv1ROTVu+OEO3KMqLM6oaJbolXCkvW0pujOotnCr2BXbgd5eA +iN1nE28daCSLT7d0geX0YJ96Vdc+N9oWaz53rK4YcJUIeSkDiv7BO7M/Gg+kO14f +WKGVyasvc0rQLW6aWQ9VGHgtPFGml4vmu7JwqkwR3v98KzfUetF3NI/n+UL3PIEM +S1IK +-----END CERTIFICATE----- + +# Issuer: CN=OpenTrust Root CA G3 O=OpenTrust +# Subject: CN=OpenTrust Root CA G3 O=OpenTrust +# Label: "OpenTrust Root CA G3" +# Serial: 1492104908271485653071219941864171170455615 +# MD5 Fingerprint: 21:37:b4:17:16:92:7b:67:46:70:a9:96:d7:a8:13:24 +# SHA1 Fingerprint: 6e:26:64:f3:56:bf:34:55:bf:d1:93:3f:7c:01:de:d8:13:da:8a:a6 +# SHA256 Fingerprint: b7:c3:62:31:70:6e:81:07:8c:36:7c:b8:96:19:8f:1e:32:08:dd:92:69:49:dd:8f:57:09:a4:10:f7:5b:62:92 +-----BEGIN CERTIFICATE----- +MIICITCCAaagAwIBAgISESDm+Ez8JLC+BUCs2oMbNGA/MAoGCCqGSM49BAMDMEAx +CzAJBgNVBAYTAkZSMRIwEAYDVQQKDAlPcGVuVHJ1c3QxHTAbBgNVBAMMFE9wZW5U +cnVzdCBSb290IENBIEczMB4XDTE0MDUyNjAwMDAwMFoXDTM4MDExNTAwMDAwMFow +QDELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU9wZW5UcnVzdDEdMBsGA1UEAwwUT3Bl +blRydXN0IFJvb3QgQ0EgRzMwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAARK7liuTcpm +3gY6oxH84Bjwbhy6LTAMidnW7ptzg6kjFYwvWYpa3RTqnVkrQ7cG7DK2uu5Bta1d +oYXM6h0UZqNnfkbilPPntlahFVmhTzeXuSIevRHr9LIfXsMUmuXZl5mjYzBhMA4G +A1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRHd8MUi2I5 +DMlv4VBN0BBY3JWIbTAfBgNVHSMEGDAWgBRHd8MUi2I5DMlv4VBN0BBY3JWIbTAK +BggqhkjOPQQDAwNpADBmAjEAj6jcnboMBBf6Fek9LykBl7+BFjNAk2z8+e2AcG+q +j9uEwov1NcoG3GRvaBbhj5G5AjEA2Euly8LQCGzpGPta3U1fJAuwACEl74+nBCZx +4nxp5V2a+EEfOzmTk51V6s2N8fvB +-----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=LuxTrust Global Root 2 O=LuxTrust S.A. +# Subject: CN=LuxTrust Global Root 2 O=LuxTrust S.A. +# Label: "LuxTrust Global Root 2" +# Serial: 59914338225734147123941058376788110305822489521 +# MD5 Fingerprint: b2:e1:09:00:61:af:f7:f1:91:6f:c4:ad:8d:5e:3b:7c +# SHA1 Fingerprint: 1e:0e:56:19:0a:d1:8b:25:98:b2:04:44:ff:66:8a:04:17:99:5f:3f +# SHA256 Fingerprint: 54:45:5f:71:29:c2:0b:14:47:c4:18:f9:97:16:8f:24:c5:8f:c5:02:3b:f5:da:5b:e2:eb:6e:1d:d8:90:2e:d5 +-----BEGIN CERTIFICATE----- +MIIFwzCCA6ugAwIBAgIUCn6m30tEntpqJIWe5rgV0xZ/u7EwDQYJKoZIhvcNAQEL +BQAwRjELMAkGA1UEBhMCTFUxFjAUBgNVBAoMDUx1eFRydXN0IFMuQS4xHzAdBgNV +BAMMFkx1eFRydXN0IEdsb2JhbCBSb290IDIwHhcNMTUwMzA1MTMyMTU3WhcNMzUw +MzA1MTMyMTU3WjBGMQswCQYDVQQGEwJMVTEWMBQGA1UECgwNTHV4VHJ1c3QgUy5B +LjEfMB0GA1UEAwwWTHV4VHJ1c3QgR2xvYmFsIFJvb3QgMjCCAiIwDQYJKoZIhvcN +AQEBBQADggIPADCCAgoCggIBANeFl78RmOnwYoNMPIf5U2o3C/IPPIfOb9wmKb3F +ibrJgz337spbxm1Jc7TJRqMbNBM/wYlFV/TZsfs2ZUv7COJIcRHIbjuend+JZTem +hfY7RBi2xjcwYkSSl2l9QjAk5A0MiWtj3sXh306pFGxT4GHO9hcvHTy95iJMHZP1 +EMShduxq3sVs35a0VkBCwGKSMKEtFZSg0iAGCW5qbeXrt77U8PEVfIvmTroTzEsn +Xpk8F12PgX8zPU/TPxvsXD/wPEx1bvKm1Z3aLQdjAsZy6ZS8TEmVT4hSyNvoaYL4 +zDRbIvCGp4m9SAptZoFtyMhk+wHh9OHe2Z7d21vUKpkmFRseTJIpgp7VkoGSQXAZ +96Tlk0u8d2cx3Rz9MXANF5kM+Qw5GSoXtTBxVdUPrljhPS80m8+f9niFwpN6cj5m +j5wWEWCPnolvZ77gR1o7DJpni89Gxq44o/KnvObWhWszJHAiS8sIm7vI+AIpHb4g +DEa/a4ebsypmQjVGbKq6rfmYe+lQVRQxv7HaLe2ArWgk+2mr2HETMOZns4dA/Yl+ +8kPREd8vZS9kzl8UubG/Mb2HeFpZZYiq/FkySIbWTLkpS5XTdvN3JW1CHDiDTf2j +X5t/Lax5Gw5CMZdjpPuKadUiDTSQMC6otOBttpSsvItO13D8xTiOZCXhTTmQzsmH +hFhxAgMBAAGjgagwgaUwDwYDVR0TAQH/BAUwAwEB/zBCBgNVHSAEOzA5MDcGByuB +KwEBAQowLDAqBggrBgEFBQcCARYeaHR0cHM6Ly9yZXBvc2l0b3J5Lmx1eHRydXN0 +Lmx1MA4GA1UdDwEB/wQEAwIBBjAfBgNVHSMEGDAWgBT/GCh2+UgFLKGu8SsbK7JT ++Et8szAdBgNVHQ4EFgQU/xgodvlIBSyhrvErGyuyU/hLfLMwDQYJKoZIhvcNAQEL +BQADggIBAGoZFO1uecEsh9QNcH7X9njJCwROxLHOk3D+sFTAMs2ZMGQXvw/l4jP9 +BzZAcg4atmpZ1gDlaCDdLnINH2pkMSCEfUmmWjfrRcmF9dTHF5kH5ptV5AzoqbTO +jFu1EVzPig4N1qx3gf4ynCSecs5U89BvolbW7MM3LGVYvlcAGvI1+ut7MV3CwRI9 +loGIlonBWVx65n9wNOeD4rHh4bhY79SV5GCc8JaXcozrhAIuZY+kt9J/Z93I055c +qqmkoCUUBpvsT34tC38ddfEz2O3OuHVtPlu5mB0xDVbYQw8wkbIEa91WvpWAVWe+ +2M2D2RjuLg+GLZKecBPs3lHJQ3gCpU3I+V/EkVhGFndadKpAvAefMLmx9xIX3eP/ +JEAdemrRTxgKqpAd60Ae36EeRJIQmvKN4dFLRp7oRUKX6kWZ8+xm1QL68qZKJKre +zrnK+T+Tb/mjuuqlPpmt/f97mfVl7vBZKGfXkJWkE4SphMHozs51k2MavDzq1WQf +LSoSOcbDWjLtR5EWDrw4wVDej8oqkDQc7kGUnF4ZLvhFSZl0kbAEb+MEWrGrKqv+ +x9CWttrhSmQGbmBNvUJO/3jaJMobtNeWOWyu8Q6qp31IiyBMz2TWuJdGsE7RKlY6 +oJO9r4Ak4Ap+58rVyuiFVdw2KuGUaJPHZnJED4AhMmwlxyOAgwrr +-----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=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor RootCert CA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor RootCert CA-1" +# Serial: 15752444095811006489 +# MD5 Fingerprint: 6e:85:f1:dc:1a:00:d3:22:d5:b2:b2:ac:6b:37:05:45 +# SHA1 Fingerprint: ff:bd:cd:e7:82:c8:43:5e:3c:6f:26:86:5c:ca:a8:3a:45:5b:c3:0a +# SHA256 Fingerprint: d4:0e:9c:86:cd:8f:e4:68:c1:77:69:59:f4:9e:a7:74:fa:54:86:84:b6:c4:06:f3:90:92:61:f4:dc:e2:57:5c +-----BEGIN CERTIFICATE----- +MIIEMDCCAxigAwIBAgIJANqb7HHzA7AZMA0GCSqGSIb3DQEBCwUAMIGkMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRydXN0Q29y +IFJvb3RDZXJ0IENBLTEwHhcNMTYwMjA0MTIzMjE2WhcNMjkxMjMxMTcyMzE2WjCB +pDELMAkGA1UEBhMCUEExDzANBgNVBAgMBlBhbmFtYTEUMBIGA1UEBwwLUGFuYW1h +IENpdHkxJDAiBgNVBAoMG1RydXN0Q29yIFN5c3RlbXMgUy4gZGUgUi5MLjEnMCUG +A1UECwweVHJ1c3RDb3IgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MR8wHQYDVQQDDBZU +cnVzdENvciBSb290Q2VydCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAv463leLCJhJrMxnHQFgKq1mqjQCj/IDHUHuO1CAmujIS2CNUSSUQIpid +RtLByZ5OGy4sDjjzGiVoHKZaBeYei0i/mJZ0PmnK6bV4pQa81QBeCQryJ3pS/C3V +seq0iWEk8xoT26nPUu0MJLq5nux+AHT6k61sKZKuUbS701e/s/OojZz0JEsq1pme +9J7+wH5COucLlVPat2gOkEz7cD+PSiyU8ybdY2mplNgQTsVHCJCZGxdNuWxu72CV +EY4hgLW9oHPY0LJ3xEXqWib7ZnZ2+AYfYW0PVcWDtxBWcgYHpfOxGgMFZA6dWorW +hnAbJN7+KIor0Gqw/Hqi3LJ5DotlDwIDAQABo2MwYTAdBgNVHQ4EFgQU7mtJPHo/ +DeOxCbeKyKsZn3MzUOcwHwYDVR0jBBgwFoAU7mtJPHo/DeOxCbeKyKsZn3MzUOcw +DwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBACUY1JGPE+6PHh0RU9otRCkZoB5rMZ5NDp6tPVxBb5UrJKF5mDo4Nvu7Zp5I +/5CQ7z3UuJu0h3U/IJvOcs+hVcFNZKIZBqEHMwwLKeXx6quj7LUKdJDHfXLy11yf +ke+Ri7fc7Waiz45mO7yfOgLgJ90WmMCV1Aqk5IGadZQ1nJBfiDcGrVmVCrDRZ9MZ +yonnMlo2HD6CqFqTvsbQZJG2z9m2GM/bftJlo6bEjhcxwft+dtvTheNYsnd6djts +L1Ac59v2Z3kf9YKVmgenFK+P3CghZwnS1k1aHBkcjndcw5QkPTJrS37UeJSDvjdN +zl/HHk484IkzlQsPpTLWPFp5LBk= +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor RootCert CA-2 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor RootCert CA-2" +# Serial: 2711694510199101698 +# MD5 Fingerprint: a2:e1:f8:18:0b:ba:45:d5:c7:41:2a:bb:37:52:45:64 +# SHA1 Fingerprint: b8:be:6d:cb:56:f1:55:b9:63:d4:12:ca:4e:06:34:c7:94:b2:1c:c0 +# SHA256 Fingerprint: 07:53:e9:40:37:8c:1b:d5:e3:83:6e:39:5d:ae:a5:cb:83:9e:50:46:f1:bd:0e:ae:19:51:cf:10:fe:c7:c9:65 +-----BEGIN CERTIFICATE----- +MIIGLzCCBBegAwIBAgIIJaHfyjPLWQIwDQYJKoZIhvcNAQELBQAwgaQxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEfMB0GA1UEAwwWVHJ1c3RDb3Ig +Um9vdENlcnQgQ0EtMjAeFw0xNjAyMDQxMjMyMjNaFw0zNDEyMzExNzI2MzlaMIGk +MQswCQYDVQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEg +Q2l0eTEkMCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYD +VQQLDB5UcnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxHzAdBgNVBAMMFlRy +dXN0Q29yIFJvb3RDZXJ0IENBLTIwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCnIG7CKqJiJJWQdsg4foDSq8GbZQWU9MEKENUCrO2fk8eHyLAnK0IMPQo+ +QVqedd2NyuCb7GgypGmSaIwLgQ5WoD4a3SwlFIIvl9NkRvRUqdw6VC0xK5mC8tkq +1+9xALgxpL56JAfDQiDyitSSBBtlVkxs1Pu2YVpHI7TYabS3OtB0PAx1oYxOdqHp +2yqlO/rOsP9+aij9JxzIsekp8VduZLTQwRVtDr4uDkbIXvRR/u8OYzo7cbrPb1nK +DOObXUm4TOJXsZiKQlecdu/vvdFoqNL0Cbt3Nb4lggjEFixEIFapRBF37120Hape +az6LMvYHL1cEksr1/p3C6eizjkxLAjHZ5DxIgif3GIJ2SDpxsROhOdUuxTTCHWKF +3wP+TfSvPd9cW436cOGlfifHhi5qjxLGhF5DUVCcGZt45vz27Ud+ez1m7xMTiF88 +oWP7+ayHNZ/zgp6kPwqcMWmLmaSISo5uZk3vFsQPeSghYA2FFn3XVDjxklb9tTNM +g9zXEJ9L/cb4Qr26fHMC4P99zVvh1Kxhe1fVSntb1IVYJ12/+CtgrKAmrhQhJ8Z3 +mjOAPF5GP/fDsaOGM8boXg25NSyqRsGFAnWAoOsk+xWq5Gd/bnc/9ASKL3x74xdh +8N0JqSDIvgmk0H5Ew7IwSjiqqewYmgeCK9u4nBit2uBGF6zPXQIDAQABo2MwYTAd +BgNVHQ4EFgQU2f4hQG6UnrybPZx9mCAZ5YwwYrIwHwYDVR0jBBgwFoAU2f4hQG6U +nrybPZx9mCAZ5YwwYrIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggIBAJ5Fngw7tu/hOsh80QA9z+LqBrWyOrsGS2h60COX +dKcs8AjYeVrXWoSK2BKaG9l9XE1wxaX5q+WjiYndAfrs3fnpkpfbsEZC89NiqpX+ +MWcUaViQCqoL7jcjx1BRtPV+nuN79+TMQjItSQzL/0kMmx40/W5ulop5A7Zv2wnL +/V9lFDfhOPXzYRZY5LVtDQsEGz9QLX+zx3oaFoBg+Iof6Rsqxvm6ARppv9JYx1RX +CI/hOWB3S6xZhBqI8d3LT3jX5+EzLfzuQfogsL7L9ziUwOHQhQ+77Sxzq+3+knYa +ZH9bDTMJBzN7Bj8RpFxwPIXAz+OQqIN3+tvmxYxoZxBnpVIt8MSZj3+/0WvitUfW +2dCFmU2Umw9Lje4AWkcdEQOsQRivh7dvDDqPys/cA8GiCcjl/YBeyGBCARsaU1q7 +N6a3vLqE6R5sGtRk2tRD/pOLS/IseRYQ1JMLiI+h2IYURpFHmygk71dSTlxCnKr3 +Sewn6EAes6aJInKc9Q0ztFijMDvd1GpUk74aTfOTlPf8hAs/hCBcNANExdqtvArB +As8e5ZTZ845b2EzwnexhF7sUMlQMAimTHpKG9n/v55IFDlndmQguLvqcAFLTxWYp +5KeXRKQOKIETNcX2b2TmQcTVL8w0RSXPQQCWPUouwpaYT05KnJe32x+SMsj/D1Fu +1uwJ +-----END CERTIFICATE----- + +# Issuer: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Subject: CN=TrustCor ECA-1 O=TrustCor Systems S. de R.L. OU=TrustCor Certificate Authority +# Label: "TrustCor ECA-1" +# Serial: 9548242946988625984 +# MD5 Fingerprint: 27:92:23:1d:0a:f5:40:7c:e9:e6:6b:9d:d8:f5:e7:6c +# SHA1 Fingerprint: 58:d1:df:95:95:67:6b:63:c0:f0:5b:1c:17:4d:8b:84:0b:c8:78:bd +# SHA256 Fingerprint: 5a:88:5d:b1:9c:01:d9:12:c5:75:93:88:93:8c:af:bb:df:03:1a:b2:d4:8e:91:ee:15:58:9b:42:97:1d:03:9c +-----BEGIN CERTIFICATE----- +MIIEIDCCAwigAwIBAgIJAISCLF8cYtBAMA0GCSqGSIb3DQEBCwUAMIGcMQswCQYD +VQQGEwJQQTEPMA0GA1UECAwGUGFuYW1hMRQwEgYDVQQHDAtQYW5hbWEgQ2l0eTEk +MCIGA1UECgwbVHJ1c3RDb3IgU3lzdGVtcyBTLiBkZSBSLkwuMScwJQYDVQQLDB5U +cnVzdENvciBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxFzAVBgNVBAMMDlRydXN0Q29y +IEVDQS0xMB4XDTE2MDIwNDEyMzIzM1oXDTI5MTIzMTE3MjgwN1owgZwxCzAJBgNV +BAYTAlBBMQ8wDQYDVQQIDAZQYW5hbWExFDASBgNVBAcMC1BhbmFtYSBDaXR5MSQw +IgYDVQQKDBtUcnVzdENvciBTeXN0ZW1zIFMuIGRlIFIuTC4xJzAlBgNVBAsMHlRy +dXN0Q29yIENlcnRpZmljYXRlIEF1dGhvcml0eTEXMBUGA1UEAwwOVHJ1c3RDb3Ig +RUNBLTEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDPj+ARtZ+odnbb +3w9U73NjKYKtR8aja+3+XzP4Q1HpGjORMRegdMTUpwHmspI+ap3tDvl0mEDTPwOA +BoJA6LHip1GnHYMma6ve+heRK9jGrB6xnhkB1Zem6g23xFUfJ3zSCNV2HykVh0A5 +3ThFEXXQmqc04L/NyFIduUd+Dbi7xgz2c1cWWn5DkR9VOsZtRASqnKmcp0yJF4Ou +owReUoCLHhIlERnXDH19MURB6tuvsBzvgdAsxZohmz3tQjtQJvLsznFhBmIhVE5/ +wZ0+fyCMgMsq2JdiyIMzkX2woloPV+g7zPIlstR8L+xNxqE6FXrntl019fZISjZF +ZtS6mFjBAgMBAAGjYzBhMB0GA1UdDgQWBBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAf +BgNVHSMEGDAWgBREnkj1zG1I1KBLf/5ZJC+Dl5mahjAPBgNVHRMBAf8EBTADAQH/ +MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOCAQEABT41XBVwm8nHc2Fv +civUwo/yQ10CzsSUuZQRg2dd4mdsdXa/uwyqNsatR5Nj3B5+1t4u/ukZMjgDfxT2 +AHMsWbEhBuH7rBiVDKP/mZb3Kyeb1STMHd3BOuCYRLDE5D53sXOpZCz2HAF8P11F +hcCF5yWPldwX8zyfGm6wyuMdKulMY/okYWLW2n62HGz1Ah3UKt1VkOsqEUc8Ll50 +soIipX1TH0XsJ5F95yIW6MBoNtjG8U+ARDL54dHRHareqKucBK+tIA5kmE2la8BI +WJZpTdwHjFGTot+fDz2LYLSCjaoITmJF4PkL0uDgPFveXHEnJcLmA4GLEFPjx1Wi +tJ/X5g== +-----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----- diff --git a/testing/web-platform/tests/tools/third_party/certifi/certifi/core.py b/testing/web-platform/tests/tools/third_party/certifi/certifi/core.py new file mode 100644 index 0000000000..eab9d1d178 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/certifi/certifi/core.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +""" +certifi.py +~~~~~~~~~~ + +This module returns the installation location of cacert.pem. +""" +import os +import warnings + + +class DeprecatedBundleWarning(DeprecationWarning): + """ + The weak security bundle is being deprecated. Please bother your service + provider to get them to stop using cross-signed roots. + """ + + +def where(): + f = os.path.dirname(__file__) + + return os.path.join(f, 'cacert.pem') + + +def old_where(): + warnings.warn( + "The weak security bundle has been removed. certifi.old_where() is now an alias " + "of certifi.where(). Please update your code to use certifi.where() instead. " + "certifi.old_where() will be removed in 2018.", + DeprecatedBundleWarning + ) + return where() + +if __name__ == '__main__': + print(where()) diff --git a/testing/web-platform/tests/tools/third_party/certifi/setup.cfg b/testing/web-platform/tests/tools/third_party/certifi/setup.cfg new file mode 100644 index 0000000000..163eba3165 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/certifi/setup.cfg @@ -0,0 +1,11 @@ +[bdist_wheel] +universal = 1 + +[metadata] +license_file = LICENSE + +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff --git a/testing/web-platform/tests/tools/third_party/certifi/setup.py b/testing/web-platform/tests/tools/third_party/certifi/setup.py new file mode 100755 index 0000000000..2c20c269f6 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/certifi/setup.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import with_statement +import re +import os +import sys + +# While I generally consider it an antipattern to try and support both +# setuptools and distutils with a single setup.py, in this specific instance +# where certifi is a dependency of setuptools, it can create a circular +# dependency when projects attempt to unbundle stuff from setuptools and pip. +# Though we don't really support that, it makes things easier if we do this and +# should hopefully cause less issues for end users. +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + + +version_regex = r'__version__ = ["\']([^"\']*)["\']' +with open('certifi/__init__.py', 'r') as f: + text = f.read() + match = re.search(version_regex, text) + + if match: + VERSION = match.group(1) + else: + raise RuntimeError("No version number found!") + +if sys.argv[-1] == 'publish': + os.system('python setup.py sdist bdist_wheel upload') + sys.exit() + +required = [] +setup( + name='certifi', + version=VERSION, + description='Python package for providing Mozilla\'s CA Bundle.', + long_description=open('README.rst').read(), + author='Kenneth Reitz', + author_email='me@kennethreitz.com', + url='http://certifi.io/', + packages=[ + 'certifi', + ], + package_dir={'certifi': 'certifi'}, + package_data={'certifi': ['*.pem']}, + # data_files=[('certifi', ['certifi/cacert.pem'])], + include_package_data=True, + zip_safe=False, + license='MPL-2.0', + classifiers=( + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)', + 'Natural Language :: English', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + ), +) diff --git a/testing/web-platform/tests/tools/third_party/enum/MANIFEST.in b/testing/web-platform/tests/tools/third_party/enum/MANIFEST.in new file mode 100644 index 0000000000..98fe77f55a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/enum/MANIFEST.in @@ -0,0 +1,9 @@ +exclude enum/* +include setup.py +include README +include enum/__init__.py +include enum/test.py +include enum/LICENSE +include enum/README +include enum/doc/enum.pdf +include enum/doc/enum.rst diff --git a/testing/web-platform/tests/tools/third_party/enum/PKG-INFO b/testing/web-platform/tests/tools/third_party/enum/PKG-INFO new file mode 100644 index 0000000000..623171e38f --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/enum/PKG-INFO @@ -0,0 +1,60 @@ +Metadata-Version: 1.1 +Name: enum34 +Version: 1.1.10 +Summary: Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4 +Home-page: https://bitbucket.org/stoneleaf/enum34 +Author: Ethan Furman +Author-email: ethan@stoneleaf.us +License: BSD License +Description: enum --- support for enumerations + ======================================== + + An enumeration is a set of symbolic names (members) bound to unique, constant + values. Within an enumeration, the members can be compared by identity, and + the enumeration itself can be iterated over. + + from enum import Enum + + class Fruit(Enum): + apple = 1 + banana = 2 + orange = 3 + + list(Fruit) + # [, , ] + + len(Fruit) + # 3 + + Fruit.banana + # + + Fruit['banana'] + # + + Fruit(2) + # + + Fruit.banana is Fruit['banana'] is Fruit(2) + # True + + Fruit.banana.name + # 'banana' + + Fruit.banana.value + # 2 + + Repository and Issue Tracker at https://bitbucket.org/stoneleaf/enum34. + +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Programming Language :: Python +Classifier: Topic :: Software Development +Classifier: Programming Language :: Python :: 2.4 +Classifier: Programming Language :: Python :: 2.5 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3.3 +Provides: enum diff --git a/testing/web-platform/tests/tools/third_party/enum/README b/testing/web-platform/tests/tools/third_party/enum/README new file mode 100644 index 0000000000..aa2333d8df --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/enum/README @@ -0,0 +1,3 @@ +enum34 is the new Python stdlib enum module available in Python 3.4 +backported for previous versions of Python from 2.4 to 3.3. +tested on 2.6, 2.7, and 3.3+ diff --git a/testing/web-platform/tests/tools/third_party/enum/enum/LICENSE b/testing/web-platform/tests/tools/third_party/enum/enum/LICENSE new file mode 100644 index 0000000000..9003b8850e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/enum/enum/LICENSE @@ -0,0 +1,32 @@ +Copyright (c) 2013, Ethan Furman. +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 Ethan Furman nor the names of any + 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/testing/web-platform/tests/tools/third_party/enum/enum/README b/testing/web-platform/tests/tools/third_party/enum/enum/README new file mode 100644 index 0000000000..aa2333d8df --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/enum/enum/README @@ -0,0 +1,3 @@ +enum34 is the new Python stdlib enum module available in Python 3.4 +backported for previous versions of Python from 2.4 to 3.3. +tested on 2.6, 2.7, and 3.3+ diff --git a/testing/web-platform/tests/tools/third_party/enum/enum/__init__.py b/testing/web-platform/tests/tools/third_party/enum/enum/__init__.py new file mode 100644 index 0000000000..51f3cf2470 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/enum/enum/__init__.py @@ -0,0 +1,838 @@ +"""Python Enumerations""" + +import sys as _sys + +__all__ = ['Enum', 'IntEnum', 'unique'] + +version = 1, 1, 10 + +pyver = float('%s.%s' % _sys.version_info[:2]) + +try: + any +except NameError: + def any(iterable): + for element in iterable: + if element: + return True + return False + +try: + from collections import OrderedDict +except ImportError: + OrderedDict = None + +try: + basestring +except NameError: + # In Python 2 basestring is the ancestor of both str and unicode + # in Python 3 it's just str, but was missing in 3.1 + basestring = str + +try: + unicode +except NameError: + # In Python 3 unicode no longer exists (it's just str) + unicode = str + +class _RouteClassAttributeToGetattr(object): + """Route attribute access on a class to __getattr__. + + This is a descriptor, used to define attributes that act differently when + accessed through an instance and through a class. Instance access remains + normal, but access to an attribute through a class will be routed to the + class's __getattr__ method; this is done by raising AttributeError. + + """ + def __init__(self, fget=None): + self.fget = fget + + def __get__(self, instance, ownerclass=None): + if instance is None: + raise AttributeError() + return self.fget(instance) + + def __set__(self, instance, value): + raise AttributeError("can't set attribute") + + def __delete__(self, instance): + raise AttributeError("can't delete attribute") + + +def _is_descriptor(obj): + """Returns True if obj is a descriptor, False otherwise.""" + return ( + hasattr(obj, '__get__') or + hasattr(obj, '__set__') or + hasattr(obj, '__delete__')) + + +def _is_dunder(name): + """Returns True if a __dunder__ name, False otherwise.""" + return (name[:2] == name[-2:] == '__' and + name[2:3] != '_' and + name[-3:-2] != '_' and + len(name) > 4) + + +def _is_sunder(name): + """Returns True if a _sunder_ name, False otherwise.""" + return (name[0] == name[-1] == '_' and + name[1:2] != '_' and + name[-2:-1] != '_' and + len(name) > 2) + + +def _make_class_unpicklable(cls): + """Make the given class un-picklable.""" + def _break_on_call_reduce(self, protocol=None): + raise TypeError('%r cannot be pickled' % self) + cls.__reduce_ex__ = _break_on_call_reduce + cls.__module__ = '' + + +class _EnumDict(dict): + """Track enum member order and ensure member names are not reused. + + EnumMeta will use the names found in self._member_names as the + enumeration member names. + + """ + def __init__(self): + super(_EnumDict, self).__init__() + self._member_names = [] + + def __setitem__(self, key, value): + """Changes anything not dundered or not a descriptor. + + If a descriptor is added with the same name as an enum member, the name + is removed from _member_names (this may leave a hole in the numerical + sequence of values). + + If an enum member name is used twice, an error is raised; duplicate + values are not checked for. + + Single underscore (sunder) names are reserved. + + Note: in 3.x __order__ is simply discarded as a not necessary piece + leftover from 2.x + + """ + if pyver >= 3.0 and key in ('_order_', '__order__'): + return + elif key == '__order__': + key = '_order_' + if _is_sunder(key): + if key != '_order_': + raise ValueError('_names_ are reserved for future Enum use') + elif _is_dunder(key): + pass + elif key in self._member_names: + # descriptor overwriting an enum? + raise TypeError('Attempted to reuse key: %r' % key) + elif not _is_descriptor(value): + if key in self: + # enum overwriting a descriptor? + raise TypeError('Key already defined as: %r' % self[key]) + self._member_names.append(key) + super(_EnumDict, self).__setitem__(key, value) + + +# Dummy value for Enum as EnumMeta explicity checks for it, but of course until +# EnumMeta finishes running the first time the Enum class doesn't exist. This +# is also why there are checks in EnumMeta like `if Enum is not None` +Enum = None + + +class EnumMeta(type): + """Metaclass for Enum""" + @classmethod + def __prepare__(metacls, cls, bases): + return _EnumDict() + + def __new__(metacls, cls, bases, classdict): + # an Enum class is final once enumeration items have been defined; it + # cannot be mixed with other types (int, float, etc.) if it has an + # inherited __new__ unless a new __new__ is defined (or the resulting + # class will fail). + if type(classdict) is dict: + original_dict = classdict + classdict = _EnumDict() + for k, v in original_dict.items(): + classdict[k] = v + + member_type, first_enum = metacls._get_mixins_(bases) + __new__, save_new, use_args = metacls._find_new_(classdict, member_type, + first_enum) + # save enum items into separate mapping so they don't get baked into + # the new class + members = dict((k, classdict[k]) for k in classdict._member_names) + for name in classdict._member_names: + del classdict[name] + + # py2 support for definition order + _order_ = classdict.get('_order_') + if _order_ is None: + if pyver < 3.0: + try: + _order_ = [name for (name, value) in sorted(members.items(), key=lambda item: item[1])] + except TypeError: + _order_ = [name for name in sorted(members.keys())] + else: + _order_ = classdict._member_names + else: + del classdict['_order_'] + if pyver < 3.0: + if isinstance(_order_, basestring): + _order_ = _order_.replace(',', ' ').split() + aliases = [name for name in members if name not in _order_] + _order_ += aliases + + # check for illegal enum names (any others?) + invalid_names = set(members) & set(['mro']) + if invalid_names: + raise ValueError('Invalid enum member name(s): %s' % ( + ', '.join(invalid_names), )) + + # save attributes from super classes so we know if we can take + # the shortcut of storing members in the class dict + base_attributes = set([a for b in bases for a in b.__dict__]) + # create our new Enum type + enum_class = super(EnumMeta, metacls).__new__(metacls, cls, bases, classdict) + enum_class._member_names_ = [] # names in random order + if OrderedDict is not None: + enum_class._member_map_ = OrderedDict() + else: + enum_class._member_map_ = {} # name->value map + enum_class._member_type_ = member_type + + # Reverse value->name map for hashable values. + enum_class._value2member_map_ = {} + + # instantiate them, checking for duplicates as we go + # we instantiate first instead of checking for duplicates first in case + # a custom __new__ is doing something funky with the values -- such as + # auto-numbering ;) + if __new__ is None: + __new__ = enum_class.__new__ + for member_name in _order_: + value = members[member_name] + if not isinstance(value, tuple): + args = (value, ) + else: + args = value + if member_type is tuple: # special case for tuple enums + args = (args, ) # wrap it one more time + if not use_args or not args: + enum_member = __new__(enum_class) + if not hasattr(enum_member, '_value_'): + enum_member._value_ = value + else: + enum_member = __new__(enum_class, *args) + if not hasattr(enum_member, '_value_'): + enum_member._value_ = member_type(*args) + value = enum_member._value_ + enum_member._name_ = member_name + enum_member.__objclass__ = enum_class + enum_member.__init__(*args) + # If another member with the same value was already defined, the + # new member becomes an alias to the existing one. + for name, canonical_member in enum_class._member_map_.items(): + if canonical_member.value == enum_member._value_: + enum_member = canonical_member + break + else: + # Aliases don't appear in member names (only in __members__). + enum_class._member_names_.append(member_name) + # performance boost for any member that would not shadow + # a DynamicClassAttribute (aka _RouteClassAttributeToGetattr) + if member_name not in base_attributes: + setattr(enum_class, member_name, enum_member) + # now add to _member_map_ + enum_class._member_map_[member_name] = enum_member + try: + # This may fail if value is not hashable. We can't add the value + # to the map, and by-value lookups for this value will be + # linear. + enum_class._value2member_map_[value] = enum_member + except TypeError: + pass + + + # If a custom type is mixed into the Enum, and it does not know how + # to pickle itself, pickle.dumps will succeed but pickle.loads will + # fail. Rather than have the error show up later and possibly far + # from the source, sabotage the pickle protocol for this class so + # that pickle.dumps also fails. + # + # However, if the new class implements its own __reduce_ex__, do not + # sabotage -- it's on them to make sure it works correctly. We use + # __reduce_ex__ instead of any of the others as it is preferred by + # pickle over __reduce__, and it handles all pickle protocols. + unpicklable = False + if '__reduce_ex__' not in classdict: + if member_type is not object: + methods = ('__getnewargs_ex__', '__getnewargs__', + '__reduce_ex__', '__reduce__') + if not any(m in member_type.__dict__ for m in methods): + _make_class_unpicklable(enum_class) + unpicklable = True + + + # double check that repr and friends are not the mixin's or various + # things break (such as pickle) + for name in ('__repr__', '__str__', '__format__', '__reduce_ex__'): + class_method = getattr(enum_class, name) + obj_method = getattr(member_type, name, None) + enum_method = getattr(first_enum, name, None) + if name not in classdict and class_method is not enum_method: + if name == '__reduce_ex__' and unpicklable: + continue + setattr(enum_class, name, enum_method) + + # method resolution and int's are not playing nice + # Python's less than 2.6 use __cmp__ + + if pyver < 2.6: + + if issubclass(enum_class, int): + setattr(enum_class, '__cmp__', getattr(int, '__cmp__')) + + elif pyver < 3.0: + + if issubclass(enum_class, int): + for method in ( + '__le__', + '__lt__', + '__gt__', + '__ge__', + '__eq__', + '__ne__', + '__hash__', + ): + setattr(enum_class, method, getattr(int, method)) + + # replace any other __new__ with our own (as long as Enum is not None, + # anyway) -- again, this is to support pickle + if Enum is not None: + # if the user defined their own __new__, save it before it gets + # clobbered in case they subclass later + if save_new: + setattr(enum_class, '__member_new__', enum_class.__dict__['__new__']) + setattr(enum_class, '__new__', Enum.__dict__['__new__']) + return enum_class + + def __bool__(cls): + """ + classes/types should always be True. + """ + return True + + def __call__(cls, value, names=None, module=None, type=None, start=1): + """Either returns an existing member, or creates a new enum class. + + This method is used both when an enum class is given a value to match + to an enumeration member (i.e. Color(3)) and for the functional API + (i.e. Color = Enum('Color', names='red green blue')). + + When used for the functional API: `module`, if set, will be stored in + the new class' __module__ attribute; `type`, if set, will be mixed in + as the first base class. + + Note: if `module` is not set this routine will attempt to discover the + calling module by walking the frame stack; if this is unsuccessful + the resulting class will not be pickleable. + + """ + if names is None: # simple value lookup + return cls.__new__(cls, value) + # otherwise, functional API: we're creating a new Enum type + return cls._create_(value, names, module=module, type=type, start=start) + + def __contains__(cls, member): + return isinstance(member, cls) and member.name in cls._member_map_ + + def __delattr__(cls, attr): + # nicer error message when someone tries to delete an attribute + # (see issue19025). + if attr in cls._member_map_: + raise AttributeError( + "%s: cannot delete Enum member." % cls.__name__) + super(EnumMeta, cls).__delattr__(attr) + + def __dir__(self): + return (['__class__', '__doc__', '__members__', '__module__'] + + self._member_names_) + + @property + def __members__(cls): + """Returns a mapping of member name->value. + + This mapping lists all enum members, including aliases. Note that this + is a copy of the internal mapping. + + """ + return cls._member_map_.copy() + + def __getattr__(cls, name): + """Return the enum member matching `name` + + We use __getattr__ instead of descriptors or inserting into the enum + class' __dict__ in order to support `name` and `value` being both + properties for enum members (which live in the class' __dict__) and + enum members themselves. + + """ + if _is_dunder(name): + raise AttributeError(name) + try: + return cls._member_map_[name] + except KeyError: + raise AttributeError(name) + + def __getitem__(cls, name): + return cls._member_map_[name] + + def __iter__(cls): + return (cls._member_map_[name] for name in cls._member_names_) + + def __reversed__(cls): + return (cls._member_map_[name] for name in reversed(cls._member_names_)) + + def __len__(cls): + return len(cls._member_names_) + + __nonzero__ = __bool__ + + def __repr__(cls): + return "" % cls.__name__ + + def __setattr__(cls, name, value): + """Block attempts to reassign Enum members. + + A simple assignment to the class namespace only changes one of the + several possible ways to get an Enum member from the Enum class, + resulting in an inconsistent Enumeration. + + """ + member_map = cls.__dict__.get('_member_map_', {}) + if name in member_map: + raise AttributeError('Cannot reassign members.') + super(EnumMeta, cls).__setattr__(name, value) + + def _create_(cls, class_name, names=None, module=None, type=None, start=1): + """Convenience method to create a new Enum class. + + `names` can be: + + * A string containing member names, separated either with spaces or + commas. Values are auto-numbered from 1. + * An iterable of member names. Values are auto-numbered from 1. + * An iterable of (member name, value) pairs. + * A mapping of member name -> value. + + """ + if pyver < 3.0: + # if class_name is unicode, attempt a conversion to ASCII + if isinstance(class_name, unicode): + try: + class_name = class_name.encode('ascii') + except UnicodeEncodeError: + raise TypeError('%r is not representable in ASCII' % class_name) + metacls = cls.__class__ + if type is None: + bases = (cls, ) + else: + bases = (type, cls) + classdict = metacls.__prepare__(class_name, bases) + _order_ = [] + + # special processing needed for names? + if isinstance(names, basestring): + names = names.replace(',', ' ').split() + if isinstance(names, (tuple, list)) and isinstance(names[0], basestring): + names = [(e, i+start) for (i, e) in enumerate(names)] + + # Here, names is either an iterable of (name, value) or a mapping. + item = None # in case names is empty + for item in names: + if isinstance(item, basestring): + member_name, member_value = item, names[item] + else: + member_name, member_value = item + classdict[member_name] = member_value + _order_.append(member_name) + # only set _order_ in classdict if name/value was not from a mapping + if not isinstance(item, basestring): + classdict['_order_'] = _order_ + enum_class = metacls.__new__(metacls, class_name, bases, classdict) + + # TODO: replace the frame hack if a blessed way to know the calling + # module is ever developed + if module is None: + try: + module = _sys._getframe(2).f_globals['__name__'] + except (AttributeError, ValueError): + pass + if module is None: + _make_class_unpicklable(enum_class) + else: + enum_class.__module__ = module + + return enum_class + + @staticmethod + def _get_mixins_(bases): + """Returns the type for creating enum members, and the first inherited + enum class. + + bases: the tuple of bases that was given to __new__ + + """ + if not bases or Enum is None: + return object, Enum + + + # double check that we are not subclassing a class with existing + # enumeration members; while we're at it, see if any other data + # type has been mixed in so we can use the correct __new__ + member_type = first_enum = None + for base in bases: + if (base is not Enum and + issubclass(base, Enum) and + base._member_names_): + raise TypeError("Cannot extend enumerations") + # base is now the last base in bases + if not issubclass(base, Enum): + raise TypeError("new enumerations must be created as " + "`ClassName([mixin_type,] enum_type)`") + + # get correct mix-in type (either mix-in type of Enum subclass, or + # first base if last base is Enum) + if not issubclass(bases[0], Enum): + member_type = bases[0] # first data type + first_enum = bases[-1] # enum type + else: + for base in bases[0].__mro__: + # most common: (IntEnum, int, Enum, object) + # possible: (, , + # , , + # ) + if issubclass(base, Enum): + if first_enum is None: + first_enum = base + else: + if member_type is None: + member_type = base + + return member_type, first_enum + + if pyver < 3.0: + @staticmethod + def _find_new_(classdict, member_type, first_enum): + """Returns the __new__ to be used for creating the enum members. + + classdict: the class dictionary given to __new__ + member_type: the data type whose __new__ will be used by default + first_enum: enumeration to check for an overriding __new__ + + """ + # now find the correct __new__, checking to see of one was defined + # by the user; also check earlier enum classes in case a __new__ was + # saved as __member_new__ + __new__ = classdict.get('__new__', None) + if __new__: + return None, True, True # __new__, save_new, use_args + + N__new__ = getattr(None, '__new__') + O__new__ = getattr(object, '__new__') + if Enum is None: + E__new__ = N__new__ + else: + E__new__ = Enum.__dict__['__new__'] + # check all possibles for __member_new__ before falling back to + # __new__ + for method in ('__member_new__', '__new__'): + for possible in (member_type, first_enum): + try: + target = possible.__dict__[method] + except (AttributeError, KeyError): + target = getattr(possible, method, None) + if target not in [ + None, + N__new__, + O__new__, + E__new__, + ]: + if method == '__member_new__': + classdict['__new__'] = target + return None, False, True + if isinstance(target, staticmethod): + target = target.__get__(member_type) + __new__ = target + break + if __new__ is not None: + break + else: + __new__ = object.__new__ + + # if a non-object.__new__ is used then whatever value/tuple was + # assigned to the enum member name will be passed to __new__ and to the + # new enum member's __init__ + if __new__ is object.__new__: + use_args = False + else: + use_args = True + + return __new__, False, use_args + else: + @staticmethod + def _find_new_(classdict, member_type, first_enum): + """Returns the __new__ to be used for creating the enum members. + + classdict: the class dictionary given to __new__ + member_type: the data type whose __new__ will be used by default + first_enum: enumeration to check for an overriding __new__ + + """ + # now find the correct __new__, checking to see of one was defined + # by the user; also check earlier enum classes in case a __new__ was + # saved as __member_new__ + __new__ = classdict.get('__new__', None) + + # should __new__ be saved as __member_new__ later? + save_new = __new__ is not None + + if __new__ is None: + # check all possibles for __member_new__ before falling back to + # __new__ + for method in ('__member_new__', '__new__'): + for possible in (member_type, first_enum): + target = getattr(possible, method, None) + if target not in ( + None, + None.__new__, + object.__new__, + Enum.__new__, + ): + __new__ = target + break + if __new__ is not None: + break + else: + __new__ = object.__new__ + + # if a non-object.__new__ is used then whatever value/tuple was + # assigned to the enum member name will be passed to __new__ and to the + # new enum member's __init__ + if __new__ is object.__new__: + use_args = False + else: + use_args = True + + return __new__, save_new, use_args + + +######################################################## +# In order to support Python 2 and 3 with a single +# codebase we have to create the Enum methods separately +# and then use the `type(name, bases, dict)` method to +# create the class. +######################################################## +temp_enum_dict = {} +temp_enum_dict['__doc__'] = "Generic enumeration.\n\n Derive from this class to define new enumerations.\n\n" + +def __new__(cls, value): + # all enum instances are actually created during class construction + # without calling this method; this method is called by the metaclass' + # __call__ (i.e. Color(3) ), and by pickle + if type(value) is cls: + # For lookups like Color(Color.red) + value = value.value + #return value + # by-value search for a matching enum member + # see if it's in the reverse mapping (for hashable values) + try: + if value in cls._value2member_map_: + return cls._value2member_map_[value] + except TypeError: + # not there, now do long search -- O(n) behavior + for member in cls._member_map_.values(): + if member.value == value: + return member + raise ValueError("%s is not a valid %s" % (value, cls.__name__)) +temp_enum_dict['__new__'] = __new__ +del __new__ + +def __repr__(self): + return "<%s.%s: %r>" % ( + self.__class__.__name__, self._name_, self._value_) +temp_enum_dict['__repr__'] = __repr__ +del __repr__ + +def __str__(self): + return "%s.%s" % (self.__class__.__name__, self._name_) +temp_enum_dict['__str__'] = __str__ +del __str__ + +if pyver >= 3.0: + def __dir__(self): + added_behavior = [ + m + for cls in self.__class__.mro() + for m in cls.__dict__ + if m[0] != '_' and m not in self._member_map_ + ] + return (['__class__', '__doc__', '__module__', ] + added_behavior) + temp_enum_dict['__dir__'] = __dir__ + del __dir__ + +def __format__(self, format_spec): + # mixed-in Enums should use the mixed-in type's __format__, otherwise + # we can get strange results with the Enum name showing up instead of + # the value + + # pure Enum branch + if self._member_type_ is object: + cls = str + val = str(self) + # mix-in branch + else: + cls = self._member_type_ + val = self.value + return cls.__format__(val, format_spec) +temp_enum_dict['__format__'] = __format__ +del __format__ + + +#################################### +# Python's less than 2.6 use __cmp__ + +if pyver < 2.6: + + def __cmp__(self, other): + if type(other) is self.__class__: + if self is other: + return 0 + return -1 + return NotImplemented + raise TypeError("unorderable types: %s() and %s()" % (self.__class__.__name__, other.__class__.__name__)) + temp_enum_dict['__cmp__'] = __cmp__ + del __cmp__ + +else: + + def __le__(self, other): + raise TypeError("unorderable types: %s() <= %s()" % (self.__class__.__name__, other.__class__.__name__)) + temp_enum_dict['__le__'] = __le__ + del __le__ + + def __lt__(self, other): + raise TypeError("unorderable types: %s() < %s()" % (self.__class__.__name__, other.__class__.__name__)) + temp_enum_dict['__lt__'] = __lt__ + del __lt__ + + def __ge__(self, other): + raise TypeError("unorderable types: %s() >= %s()" % (self.__class__.__name__, other.__class__.__name__)) + temp_enum_dict['__ge__'] = __ge__ + del __ge__ + + def __gt__(self, other): + raise TypeError("unorderable types: %s() > %s()" % (self.__class__.__name__, other.__class__.__name__)) + temp_enum_dict['__gt__'] = __gt__ + del __gt__ + + +def __eq__(self, other): + if type(other) is self.__class__: + return self is other + return NotImplemented +temp_enum_dict['__eq__'] = __eq__ +del __eq__ + +def __ne__(self, other): + if type(other) is self.__class__: + return self is not other + return NotImplemented +temp_enum_dict['__ne__'] = __ne__ +del __ne__ + +def __hash__(self): + return hash(self._name_) +temp_enum_dict['__hash__'] = __hash__ +del __hash__ + +def __reduce_ex__(self, proto): + return self.__class__, (self._value_, ) +temp_enum_dict['__reduce_ex__'] = __reduce_ex__ +del __reduce_ex__ + +# _RouteClassAttributeToGetattr is used to provide access to the `name` +# and `value` properties of enum members while keeping some measure of +# protection from modification, while still allowing for an enumeration +# to have members named `name` and `value`. This works because enumeration +# members are not set directly on the enum class -- __getattr__ is +# used to look them up. + +@_RouteClassAttributeToGetattr +def name(self): + return self._name_ +temp_enum_dict['name'] = name +del name + +@_RouteClassAttributeToGetattr +def value(self): + return self._value_ +temp_enum_dict['value'] = value +del value + +@classmethod +def _convert(cls, name, module, filter, source=None): + """ + Create a new Enum subclass that replaces a collection of global constants + """ + # convert all constants from source (or module) that pass filter() to + # a new Enum called name, and export the enum and its members back to + # module; + # also, replace the __reduce_ex__ method so unpickling works in + # previous Python versions + module_globals = vars(_sys.modules[module]) + if source: + source = vars(source) + else: + source = module_globals + members = dict((name, value) for name, value in source.items() if filter(name)) + cls = cls(name, members, module=module) + cls.__reduce_ex__ = _reduce_ex_by_name + module_globals.update(cls.__members__) + module_globals[name] = cls + return cls +temp_enum_dict['_convert'] = _convert +del _convert + +Enum = EnumMeta('Enum', (object, ), temp_enum_dict) +del temp_enum_dict + +# Enum has now been created +########################### + +class IntEnum(int, Enum): + """Enum where members are also (and must be) ints""" + +def _reduce_ex_by_name(self, proto): + return self.name + +def unique(enumeration): + """Class decorator that ensures only unique members exist in an enumeration.""" + duplicates = [] + for name, member in enumeration.__members__.items(): + if name != member.name: + duplicates.append((name, member.name)) + if duplicates: + duplicate_names = ', '.join( + ["%s -> %s" % (alias, name) for (alias, name) in duplicates] + ) + raise ValueError('duplicate names found in %r: %s' % + (enumeration, duplicate_names) + ) + return enumeration diff --git a/testing/web-platform/tests/tools/third_party/enum/enum/doc/enum.rst b/testing/web-platform/tests/tools/third_party/enum/enum/doc/enum.rst new file mode 100644 index 0000000000..3afc238210 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/enum/enum/doc/enum.rst @@ -0,0 +1,735 @@ +``enum`` --- support for enumerations +======================================== + +.. :synopsis: enumerations are sets of symbolic names bound to unique, constant + values. +.. :moduleauthor:: Ethan Furman +.. :sectionauthor:: Barry Warsaw , +.. :sectionauthor:: Eli Bendersky , +.. :sectionauthor:: Ethan Furman + +---------------- + +An enumeration is a set of symbolic names (members) bound to unique, constant +values. Within an enumeration, the members can be compared by identity, and +the enumeration itself can be iterated over. + + +Module Contents +--------------- + +This module defines two enumeration classes that can be used to define unique +sets of names and values: ``Enum`` and ``IntEnum``. It also defines +one decorator, ``unique``. + +``Enum`` + +Base class for creating enumerated constants. See section `Functional API`_ +for an alternate construction syntax. + +``IntEnum`` + +Base class for creating enumerated constants that are also subclasses of ``int``. + +``unique`` + +Enum class decorator that ensures only one name is bound to any one value. + + +Creating an Enum +---------------- + +Enumerations are created using the ``class`` syntax, which makes them +easy to read and write. An alternative creation method is described in +`Functional API`_. To define an enumeration, subclass ``Enum`` as +follows:: + + >>> from enum import Enum + >>> class Color(Enum): + ... red = 1 + ... green = 2 + ... blue = 3 + +Note: Nomenclature + + - The class ``Color`` is an *enumeration* (or *enum*) + - The attributes ``Color.red``, ``Color.green``, etc., are + *enumeration members* (or *enum members*). + - The enum members have *names* and *values* (the name of + ``Color.red`` is ``red``, the value of ``Color.blue`` is + ``3``, etc.) + +Note: + + Even though we use the ``class`` syntax to create Enums, Enums + are not normal Python classes. See `How are Enums different?`_ for + more details. + +Enumeration members have human readable string representations:: + + >>> print(Color.red) + Color.red + +...while their ``repr`` has more information:: + + >>> print(repr(Color.red)) + + +The *type* of an enumeration member is the enumeration it belongs to:: + + >>> type(Color.red) + + >>> isinstance(Color.green, Color) + True + >>> + +Enum members also have a property that contains just their item name:: + + >>> print(Color.red.name) + red + +Enumerations support iteration. In Python 3.x definition order is used; in +Python 2.x the definition order is not available, but class attribute +``__order__`` is supported; otherwise, value order is used:: + + >>> class Shake(Enum): + ... __order__ = 'vanilla chocolate cookies mint' # only needed in 2.x + ... vanilla = 7 + ... chocolate = 4 + ... cookies = 9 + ... mint = 3 + ... + >>> for shake in Shake: + ... print(shake) + ... + Shake.vanilla + Shake.chocolate + Shake.cookies + Shake.mint + +The ``__order__`` attribute is always removed, and in 3.x it is also ignored +(order is definition order); however, in the stdlib version it will be ignored +but not removed. + +Enumeration members are hashable, so they can be used in dictionaries and sets:: + + >>> apples = {} + >>> apples[Color.red] = 'red delicious' + >>> apples[Color.green] = 'granny smith' + >>> apples == {Color.red: 'red delicious', Color.green: 'granny smith'} + True + + +Programmatic access to enumeration members and their attributes +--------------------------------------------------------------- + +Sometimes it's useful to access members in enumerations programmatically (i.e. +situations where ``Color.red`` won't do because the exact color is not known +at program-writing time). ``Enum`` allows such access:: + + >>> Color(1) + + >>> Color(3) + + +If you want to access enum members by *name*, use item access:: + + >>> Color['red'] + + >>> Color['green'] + + +If have an enum member and need its ``name`` or ``value``:: + + >>> member = Color.red + >>> member.name + 'red' + >>> member.value + 1 + + +Duplicating enum members and values +----------------------------------- + +Having two enum members (or any other attribute) with the same name is invalid; +in Python 3.x this would raise an error, but in Python 2.x the second member +simply overwrites the first:: + + >>> # python 2.x + >>> class Shape(Enum): + ... square = 2 + ... square = 3 + ... + >>> Shape.square + + + >>> # python 3.x + >>> class Shape(Enum): + ... square = 2 + ... square = 3 + Traceback (most recent call last): + ... + TypeError: Attempted to reuse key: 'square' + +However, two enum members are allowed to have the same value. Given two members +A and B with the same value (and A defined first), B is an alias to A. By-value +lookup of the value of A and B will return A. By-name lookup of B will also +return A:: + + >>> class Shape(Enum): + ... __order__ = 'square diamond circle alias_for_square' # only needed in 2.x + ... square = 2 + ... diamond = 1 + ... circle = 3 + ... alias_for_square = 2 + ... + >>> Shape.square + + >>> Shape.alias_for_square + + >>> Shape(2) + + + +Allowing aliases is not always desirable. ``unique`` can be used to ensure +that none exist in a particular enumeration:: + + >>> from enum import unique + >>> @unique + ... class Mistake(Enum): + ... __order__ = 'one two three four' # only needed in 2.x + ... one = 1 + ... two = 2 + ... three = 3 + ... four = 3 + Traceback (most recent call last): + ... + ValueError: duplicate names found in : four -> three + +Iterating over the members of an enum does not provide the aliases:: + + >>> list(Shape) + [, , ] + +The special attribute ``__members__`` is a dictionary mapping names to members. +It includes all names defined in the enumeration, including the aliases:: + + >>> for name, member in sorted(Shape.__members__.items()): + ... name, member + ... + ('alias_for_square', ) + ('circle', ) + ('diamond', ) + ('square', ) + +The ``__members__`` attribute can be used for detailed programmatic access to +the enumeration members. For example, finding all the aliases:: + + >>> [name for name, member in Shape.__members__.items() if member.name != name] + ['alias_for_square'] + +Comparisons +----------- + +Enumeration members are compared by identity:: + + >>> Color.red is Color.red + True + >>> Color.red is Color.blue + False + >>> Color.red is not Color.blue + True + +Ordered comparisons between enumeration values are *not* supported. Enum +members are not integers (but see `IntEnum`_ below):: + + >>> Color.red < Color.blue + Traceback (most recent call last): + File "", line 1, in + TypeError: unorderable types: Color() < Color() + +.. warning:: + + In Python 2 *everything* is ordered, even though the ordering may not + make sense. If you want your enumerations to have a sensible ordering + check out the `OrderedEnum`_ recipe below. + + +Equality comparisons are defined though:: + + >>> Color.blue == Color.red + False + >>> Color.blue != Color.red + True + >>> Color.blue == Color.blue + True + +Comparisons against non-enumeration values will always compare not equal +(again, ``IntEnum`` was explicitly designed to behave differently, see +below):: + + >>> Color.blue == 2 + False + + +Allowed members and attributes of enumerations +---------------------------------------------- + +The examples above use integers for enumeration values. Using integers is +short and handy (and provided by default by the `Functional API`_), but not +strictly enforced. In the vast majority of use-cases, one doesn't care what +the actual value of an enumeration is. But if the value *is* important, +enumerations can have arbitrary values. + +Enumerations are Python classes, and can have methods and special methods as +usual. If we have this enumeration:: + + >>> class Mood(Enum): + ... funky = 1 + ... happy = 3 + ... + ... def describe(self): + ... # self is the member here + ... return self.name, self.value + ... + ... def __str__(self): + ... return 'my custom str! {0}'.format(self.value) + ... + ... @classmethod + ... def favorite_mood(cls): + ... # cls here is the enumeration + ... return cls.happy + +Then:: + + >>> Mood.favorite_mood() + + >>> Mood.happy.describe() + ('happy', 3) + >>> str(Mood.funky) + 'my custom str! 1' + +The rules for what is allowed are as follows: _sunder_ names (starting and +ending with a single underscore) are reserved by enum and cannot be used; +all other attributes defined within an enumeration will become members of this +enumeration, with the exception of *__dunder__* names and descriptors (methods +are also descriptors). + +Note: + + If your enumeration defines ``__new__`` and/or ``__init__`` then + whatever value(s) were given to the enum member will be passed into + those methods. See `Planet`_ for an example. + + +Restricted subclassing of enumerations +-------------------------------------- + +Subclassing an enumeration is allowed only if the enumeration does not define +any members. So this is forbidden:: + + >>> class MoreColor(Color): + ... pink = 17 + Traceback (most recent call last): + ... + TypeError: Cannot extend enumerations + +But this is allowed:: + + >>> class Foo(Enum): + ... def some_behavior(self): + ... pass + ... + >>> class Bar(Foo): + ... happy = 1 + ... sad = 2 + ... + +Allowing subclassing of enums that define members would lead to a violation of +some important invariants of types and instances. On the other hand, it makes +sense to allow sharing some common behavior between a group of enumerations. +(See `OrderedEnum`_ for an example.) + + +Pickling +-------- + +Enumerations can be pickled and unpickled:: + + >>> from enum.test_enum import Fruit + >>> from pickle import dumps, loads + >>> Fruit.tomato is loads(dumps(Fruit.tomato, 2)) + True + +The usual restrictions for pickling apply: picklable enums must be defined in +the top level of a module, since unpickling requires them to be importable +from that module. + +Note: + + With pickle protocol version 4 (introduced in Python 3.4) it is possible + to easily pickle enums nested in other classes. + + + +Functional API +-------------- + +The ``Enum`` class is callable, providing the following functional API:: + + >>> Animal = Enum('Animal', 'ant bee cat dog') + >>> Animal + + >>> Animal.ant + + >>> Animal.ant.value + 1 + >>> list(Animal) + [, , , ] + +The semantics of this API resemble ``namedtuple``. The first argument +of the call to ``Enum`` is the name of the enumeration. + +The second argument is the *source* of enumeration member names. It can be a +whitespace-separated string of names, a sequence of names, a sequence of +2-tuples with key/value pairs, or a mapping (e.g. dictionary) of names to +values. The last two options enable assigning arbitrary values to +enumerations; the others auto-assign increasing integers starting with 1. A +new class derived from ``Enum`` is returned. In other words, the above +assignment to ``Animal`` is equivalent to:: + + >>> class Animals(Enum): + ... ant = 1 + ... bee = 2 + ... cat = 3 + ... dog = 4 + +Pickling enums created with the functional API can be tricky as frame stack +implementation details are used to try and figure out which module the +enumeration is being created in (e.g. it will fail if you use a utility +function in separate module, and also may not work on IronPython or Jython). +The solution is to specify the module name explicitly as follows:: + + >>> Animals = Enum('Animals', 'ant bee cat dog', module=__name__) + +Derived Enumerations +-------------------- + +IntEnum +^^^^^^^ + +A variation of ``Enum`` is provided which is also a subclass of +``int``. Members of an ``IntEnum`` can be compared to integers; +by extension, integer enumerations of different types can also be compared +to each other:: + + >>> from enum import IntEnum + >>> class Shape(IntEnum): + ... circle = 1 + ... square = 2 + ... + >>> class Request(IntEnum): + ... post = 1 + ... get = 2 + ... + >>> Shape == 1 + False + >>> Shape.circle == 1 + True + >>> Shape.circle == Request.post + True + +However, they still can't be compared to standard ``Enum`` enumerations:: + + >>> class Shape(IntEnum): + ... circle = 1 + ... square = 2 + ... + >>> class Color(Enum): + ... red = 1 + ... green = 2 + ... + >>> Shape.circle == Color.red + False + +``IntEnum`` values behave like integers in other ways you'd expect:: + + >>> int(Shape.circle) + 1 + >>> ['a', 'b', 'c'][Shape.circle] + 'b' + >>> [i for i in range(Shape.square)] + [0, 1] + +For the vast majority of code, ``Enum`` is strongly recommended, +since ``IntEnum`` breaks some semantic promises of an enumeration (by +being comparable to integers, and thus by transitivity to other +unrelated enumerations). It should be used only in special cases where +there's no other choice; for example, when integer constants are +replaced with enumerations and backwards compatibility is required with code +that still expects integers. + + +Others +^^^^^^ + +While ``IntEnum`` is part of the ``enum`` module, it would be very +simple to implement independently:: + + class IntEnum(int, Enum): + pass + +This demonstrates how similar derived enumerations can be defined; for example +a ``StrEnum`` that mixes in ``str`` instead of ``int``. + +Some rules: + +1. When subclassing ``Enum``, mix-in types must appear before + ``Enum`` itself in the sequence of bases, as in the ``IntEnum`` + example above. +2. While ``Enum`` can have members of any type, once you mix in an + additional type, all the members must have values of that type, e.g. + ``int`` above. This restriction does not apply to mix-ins which only + add methods and don't specify another data type such as ``int`` or + ``str``. +3. When another data type is mixed in, the ``value`` attribute is *not the + same* as the enum member itself, although it is equivalant and will compare + equal. +4. %-style formatting: ``%s`` and ``%r`` call ``Enum``'s ``__str__`` and + ``__repr__`` respectively; other codes (such as ``%i`` or ``%h`` for + IntEnum) treat the enum member as its mixed-in type. + + Note: Prior to Python 3.4 there is a bug in ``str``'s %-formatting: ``int`` + subclasses are printed as strings and not numbers when the ``%d``, ``%i``, + or ``%u`` codes are used. +5. ``str.__format__`` (or ``format``) will use the mixed-in + type's ``__format__``. If the ``Enum``'s ``str`` or + ``repr`` is desired use the ``!s`` or ``!r`` ``str`` format codes. + + +Decorators +---------- + +unique +^^^^^^ + +A ``class`` decorator specifically for enumerations. It searches an +enumeration's ``__members__`` gathering any aliases it finds; if any are +found ``ValueError`` is raised with the details:: + + >>> @unique + ... class NoDupes(Enum): + ... first = 'one' + ... second = 'two' + ... third = 'two' + Traceback (most recent call last): + ... + ValueError: duplicate names found in : third -> second + + +Interesting examples +-------------------- + +While ``Enum`` and ``IntEnum`` are expected to cover the majority of +use-cases, they cannot cover them all. Here are recipes for some different +types of enumerations that can be used directly, or as examples for creating +one's own. + + +AutoNumber +^^^^^^^^^^ + +Avoids having to specify the value for each enumeration member:: + + >>> class AutoNumber(Enum): + ... def __new__(cls): + ... value = len(cls.__members__) + 1 + ... obj = object.__new__(cls) + ... obj._value_ = value + ... return obj + ... + >>> class Color(AutoNumber): + ... __order__ = "red green blue" # only needed in 2.x + ... red = () + ... green = () + ... blue = () + ... + >>> Color.green.value == 2 + True + +Note: + + The `__new__` method, if defined, is used during creation of the Enum + members; it is then replaced by Enum's `__new__` which is used after + class creation for lookup of existing members. Due to the way Enums are + supposed to behave, there is no way to customize Enum's `__new__`. + + +UniqueEnum +^^^^^^^^^^ + +Raises an error if a duplicate member name is found instead of creating an +alias:: + + >>> class UniqueEnum(Enum): + ... def __init__(self, *args): + ... cls = self.__class__ + ... if any(self.value == e.value for e in cls): + ... a = self.name + ... e = cls(self.value).name + ... raise ValueError( + ... "aliases not allowed in UniqueEnum: %r --> %r" + ... % (a, e)) + ... + >>> class Color(UniqueEnum): + ... red = 1 + ... green = 2 + ... blue = 3 + ... grene = 2 + Traceback (most recent call last): + ... + ValueError: aliases not allowed in UniqueEnum: 'grene' --> 'green' + + +OrderedEnum +^^^^^^^^^^^ + +An ordered enumeration that is not based on ``IntEnum`` and so maintains +the normal ``Enum`` invariants (such as not being comparable to other +enumerations):: + + >>> class OrderedEnum(Enum): + ... def __ge__(self, other): + ... if self.__class__ is other.__class__: + ... return self._value_ >= other._value_ + ... return NotImplemented + ... def __gt__(self, other): + ... if self.__class__ is other.__class__: + ... return self._value_ > other._value_ + ... return NotImplemented + ... def __le__(self, other): + ... if self.__class__ is other.__class__: + ... return self._value_ <= other._value_ + ... return NotImplemented + ... def __lt__(self, other): + ... if self.__class__ is other.__class__: + ... return self._value_ < other._value_ + ... return NotImplemented + ... + >>> class Grade(OrderedEnum): + ... __ordered__ = 'A B C D F' + ... A = 5 + ... B = 4 + ... C = 3 + ... D = 2 + ... F = 1 + ... + >>> Grade.C < Grade.A + True + + +Planet +^^^^^^ + +If ``__new__`` or ``__init__`` is defined the value of the enum member +will be passed to those methods:: + + >>> class Planet(Enum): + ... MERCURY = (3.303e+23, 2.4397e6) + ... VENUS = (4.869e+24, 6.0518e6) + ... EARTH = (5.976e+24, 6.37814e6) + ... MARS = (6.421e+23, 3.3972e6) + ... JUPITER = (1.9e+27, 7.1492e7) + ... SATURN = (5.688e+26, 6.0268e7) + ... URANUS = (8.686e+25, 2.5559e7) + ... NEPTUNE = (1.024e+26, 2.4746e7) + ... def __init__(self, mass, radius): + ... self.mass = mass # in kilograms + ... self.radius = radius # in meters + ... @property + ... def surface_gravity(self): + ... # universal gravitational constant (m3 kg-1 s-2) + ... G = 6.67300E-11 + ... return G * self.mass / (self.radius * self.radius) + ... + >>> Planet.EARTH.value + (5.976e+24, 6378140.0) + >>> Planet.EARTH.surface_gravity + 9.802652743337129 + + +How are Enums different? +------------------------ + +Enums have a custom metaclass that affects many aspects of both derived Enum +classes and their instances (members). + + +Enum Classes +^^^^^^^^^^^^ + +The ``EnumMeta`` metaclass is responsible for providing the +``__contains__``, ``__dir__``, ``__iter__`` and other methods that +allow one to do things with an ``Enum`` class that fail on a typical +class, such as ``list(Color)`` or ``some_var in Color``. ``EnumMeta`` is +responsible for ensuring that various other methods on the final ``Enum`` +class are correct (such as ``__new__``, ``__getnewargs__``, +``__str__`` and ``__repr__``). + +.. note:: + + ``__dir__`` is not changed in the Python 2 line as it messes up some + of the decorators included in the stdlib. + + +Enum Members (aka instances) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The most interesting thing about Enum members is that they are singletons. +``EnumMeta`` creates them all while it is creating the ``Enum`` +class itself, and then puts a custom ``__new__`` in place to ensure +that no new ones are ever instantiated by returning only the existing +member instances. + + +Finer Points +^^^^^^^^^^^^ + +``Enum`` members are instances of an ``Enum`` class, and even though they +are accessible as `EnumClass.member1.member2`, they should not be +accessed directly from the member as that lookup may fail or, worse, +return something besides the ``Enum`` member you were looking for +(changed in version 1.1.1):: + + >>> class FieldTypes(Enum): + ... name = 1 + ... value = 2 + ... size = 3 + ... + >>> FieldTypes.value.size + + >>> FieldTypes.size.value + 3 + +The ``__members__`` attribute is only available on the class. + +In Python 3.x ``__members__`` is always an ``OrderedDict``, with the order being +the definition order. In Python 2.7 ``__members__`` is an ``OrderedDict`` if +``__order__`` was specified, and a plain ``dict`` otherwise. In all other Python +2.x versions ``__members__`` is a plain ``dict`` even if ``__order__`` was specified +as the ``OrderedDict`` type didn't exist yet. + +If you give your ``Enum`` subclass extra methods, like the `Planet`_ +class above, those methods will show up in a `dir` of the member, +but not of the class:: + + >>> dir(Planet) + ['EARTH', 'JUPITER', 'MARS', 'MERCURY', 'NEPTUNE', 'SATURN', 'URANUS', + 'VENUS', '__class__', '__doc__', '__members__', '__module__'] + >>> dir(Planet.EARTH) + ['__class__', '__doc__', '__module__', 'name', 'surface_gravity', 'value'] + +A ``__new__`` method will only be used for the creation of the +``Enum`` members -- after that it is replaced. This means if you wish to +change how ``Enum`` members are looked up you either have to write a +helper function or a ``classmethod``. diff --git a/testing/web-platform/tests/tools/third_party/enum/enum/test.py b/testing/web-platform/tests/tools/third_party/enum/enum/test.py new file mode 100644 index 0000000000..c8c4b96224 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/enum/enum/test.py @@ -0,0 +1,1841 @@ +from pickle import dumps, loads, PicklingError, HIGHEST_PROTOCOL +import sys +import unittest +pyver = float('%s.%s' % sys.version_info[:2]) +if pyver < 2.5: + sys.path.insert(0, '.') +import enum +from enum import Enum, IntEnum, unique, EnumMeta + +if pyver < 2.6: + from __builtin__ import enumerate as bltin_enumerate + def enumerate(thing, start=0): + result = [] + for i, item in bltin_enumerate(thing): + i = i + start + result.append((i, item)) + return result + +try: + any +except NameError: + def any(iterable): + for element in iterable: + if element: + return True + return False + +try: + unicode +except NameError: + unicode = str + +try: + from collections import OrderedDict +except ImportError: + OrderedDict = None + +# for pickle tests +try: + class Stooges(Enum): + LARRY = 1 + CURLY = 2 + MOE = 3 +except Exception: + Stooges = sys.exc_info()[1] + +try: + class IntStooges(int, Enum): + LARRY = 1 + CURLY = 2 + MOE = 3 +except Exception: + IntStooges = sys.exc_info()[1] + +try: + class FloatStooges(float, Enum): + LARRY = 1.39 + CURLY = 2.72 + MOE = 3.142596 +except Exception: + FloatStooges = sys.exc_info()[1] + +# for pickle test and subclass tests +try: + class StrEnum(str, Enum): + 'accepts only string values' + class Name(StrEnum): + BDFL = 'Guido van Rossum' + FLUFL = 'Barry Warsaw' +except Exception: + Name = sys.exc_info()[1] + +try: + Question = Enum('Question', 'who what when where why', module=__name__) +except Exception: + Question = sys.exc_info()[1] + +try: + Answer = Enum('Answer', 'him this then there because') +except Exception: + Answer = sys.exc_info()[1] + +try: + Theory = Enum('Theory', 'rule law supposition', qualname='spanish_inquisition') +except Exception: + Theory = sys.exc_info()[1] + +# for doctests +try: + class Fruit(Enum): + tomato = 1 + banana = 2 + cherry = 3 +except Exception: + pass + +def test_pickle_dump_load(assertion, source, target=None, + protocol=(0, HIGHEST_PROTOCOL)): + start, stop = protocol + failures = [] + for protocol in range(start, stop+1): + try: + if target is None: + assertion(loads(dumps(source, protocol=protocol)) is source) + else: + assertion(loads(dumps(source, protocol=protocol)), target) + except Exception: + exc, tb = sys.exc_info()[1:] + failures.append('%2d: %s' %(protocol, exc)) + if failures: + raise ValueError('Failed with protocols: %s' % ', '.join(failures)) + +def test_pickle_exception(assertion, exception, obj, + protocol=(0, HIGHEST_PROTOCOL)): + start, stop = protocol + failures = [] + for protocol in range(start, stop+1): + try: + assertion(exception, dumps, obj, protocol=protocol) + except Exception: + exc = sys.exc_info()[1] + failures.append('%d: %s %s' % (protocol, exc.__class__.__name__, exc)) + if failures: + raise ValueError('Failed with protocols: %s' % ', '.join(failures)) + + +class TestHelpers(unittest.TestCase): + # _is_descriptor, _is_sunder, _is_dunder + + def test_is_descriptor(self): + class foo: + pass + for attr in ('__get__','__set__','__delete__'): + obj = foo() + self.assertFalse(enum._is_descriptor(obj)) + setattr(obj, attr, 1) + self.assertTrue(enum._is_descriptor(obj)) + + def test_is_sunder(self): + for s in ('_a_', '_aa_'): + self.assertTrue(enum._is_sunder(s)) + + for s in ('a', 'a_', '_a', '__a', 'a__', '__a__', '_a__', '__a_', '_', + '__', '___', '____', '_____',): + self.assertFalse(enum._is_sunder(s)) + + def test_is_dunder(self): + for s in ('__a__', '__aa__'): + self.assertTrue(enum._is_dunder(s)) + for s in ('a', 'a_', '_a', '__a', 'a__', '_a_', '_a__', '__a_', '_', + '__', '___', '____', '_____',): + self.assertFalse(enum._is_dunder(s)) + + +class TestEnum(unittest.TestCase): + def setUp(self): + class Season(Enum): + SPRING = 1 + SUMMER = 2 + AUTUMN = 3 + WINTER = 4 + self.Season = Season + + class Konstants(float, Enum): + E = 2.7182818 + PI = 3.1415926 + TAU = 2 * PI + self.Konstants = Konstants + + class Grades(IntEnum): + A = 5 + B = 4 + C = 3 + D = 2 + F = 0 + self.Grades = Grades + + class Directional(str, Enum): + EAST = 'east' + WEST = 'west' + NORTH = 'north' + SOUTH = 'south' + self.Directional = Directional + + from datetime import date + class Holiday(date, Enum): + NEW_YEAR = 2013, 1, 1 + IDES_OF_MARCH = 2013, 3, 15 + self.Holiday = Holiday + + if pyver >= 3.0: # do not specify custom `dir` on previous versions + def test_dir_on_class(self): + Season = self.Season + self.assertEqual( + set(dir(Season)), + set(['__class__', '__doc__', '__members__', '__module__', + 'SPRING', 'SUMMER', 'AUTUMN', 'WINTER']), + ) + + def test_dir_on_item(self): + Season = self.Season + self.assertEqual( + set(dir(Season.WINTER)), + set(['__class__', '__doc__', '__module__', 'name', 'value']), + ) + + def test_dir_with_added_behavior(self): + class Test(Enum): + this = 'that' + these = 'those' + def wowser(self): + return ("Wowser! I'm %s!" % self.name) + self.assertEqual( + set(dir(Test)), + set(['__class__', '__doc__', '__members__', '__module__', 'this', 'these']), + ) + self.assertEqual( + set(dir(Test.this)), + set(['__class__', '__doc__', '__module__', 'name', 'value', 'wowser']), + ) + + def test_dir_on_sub_with_behavior_on_super(self): + # see issue22506 + class SuperEnum(Enum): + def invisible(self): + return "did you see me?" + class SubEnum(SuperEnum): + sample = 5 + self.assertEqual( + set(dir(SubEnum.sample)), + set(['__class__', '__doc__', '__module__', 'name', 'value', 'invisible']), + ) + + if pyver >= 2.7: # OrderedDict first available here + def test_members_is_ordereddict_if_ordered(self): + class Ordered(Enum): + __order__ = 'first second third' + first = 'bippity' + second = 'boppity' + third = 'boo' + self.assertTrue(type(Ordered.__members__) is OrderedDict) + + def test_members_is_ordereddict_if_not_ordered(self): + class Unordered(Enum): + this = 'that' + these = 'those' + self.assertTrue(type(Unordered.__members__) is OrderedDict) + + if pyver >= 3.0: # all objects are ordered in Python 2.x + def test_members_is_always_ordered(self): + class AlwaysOrdered(Enum): + first = 1 + second = 2 + third = 3 + self.assertTrue(type(AlwaysOrdered.__members__) is OrderedDict) + + def test_comparisons(self): + def bad_compare(): + Season.SPRING > 4 + Season = self.Season + self.assertNotEqual(Season.SPRING, 1) + self.assertRaises(TypeError, bad_compare) + + class Part(Enum): + SPRING = 1 + CLIP = 2 + BARREL = 3 + + self.assertNotEqual(Season.SPRING, Part.SPRING) + def bad_compare(): + Season.SPRING < Part.CLIP + self.assertRaises(TypeError, bad_compare) + + def test_enum_in_enum_out(self): + Season = self.Season + self.assertTrue(Season(Season.WINTER) is Season.WINTER) + + def test_enum_value(self): + Season = self.Season + self.assertEqual(Season.SPRING.value, 1) + + def test_intenum_value(self): + self.assertEqual(IntStooges.CURLY.value, 2) + + def test_enum(self): + Season = self.Season + lst = list(Season) + self.assertEqual(len(lst), len(Season)) + self.assertEqual(len(Season), 4, Season) + self.assertEqual( + [Season.SPRING, Season.SUMMER, Season.AUTUMN, Season.WINTER], lst) + + for i, season in enumerate('SPRING SUMMER AUTUMN WINTER'.split()): + i += 1 + e = Season(i) + self.assertEqual(e, getattr(Season, season)) + self.assertEqual(e.value, i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, season) + self.assertTrue(e in Season) + self.assertTrue(type(e) is Season) + self.assertTrue(isinstance(e, Season)) + self.assertEqual(str(e), 'Season.' + season) + self.assertEqual( + repr(e), + '' % (season, i), + ) + + def test_value_name(self): + Season = self.Season + self.assertEqual(Season.SPRING.name, 'SPRING') + self.assertEqual(Season.SPRING.value, 1) + def set_name(obj, new_value): + obj.name = new_value + def set_value(obj, new_value): + obj.value = new_value + self.assertRaises(AttributeError, set_name, Season.SPRING, 'invierno', ) + self.assertRaises(AttributeError, set_value, Season.SPRING, 2) + + def test_attribute_deletion(self): + class Season(Enum): + SPRING = 1 + SUMMER = 2 + AUTUMN = 3 + WINTER = 4 + + def spam(cls): + pass + + self.assertTrue(hasattr(Season, 'spam')) + del Season.spam + self.assertFalse(hasattr(Season, 'spam')) + + self.assertRaises(AttributeError, delattr, Season, 'SPRING') + self.assertRaises(AttributeError, delattr, Season, 'DRY') + self.assertRaises(AttributeError, delattr, Season.SPRING, 'name') + + def test_bool_of_class(self): + class Empty(Enum): + pass + self.assertTrue(bool(Empty)) + + def test_bool_of_member(self): + class Count(Enum): + zero = 0 + one = 1 + two = 2 + for member in Count: + self.assertTrue(bool(member)) + + def test_invalid_names(self): + def create_bad_class_1(): + class Wrong(Enum): + mro = 9 + def create_bad_class_2(): + class Wrong(Enum): + _reserved_ = 3 + self.assertRaises(ValueError, create_bad_class_1) + self.assertRaises(ValueError, create_bad_class_2) + + def test_contains(self): + Season = self.Season + self.assertTrue(Season.AUTUMN in Season) + self.assertTrue(3 not in Season) + + val = Season(3) + self.assertTrue(val in Season) + + class OtherEnum(Enum): + one = 1; two = 2 + self.assertTrue(OtherEnum.two not in Season) + + if pyver >= 2.6: # when `format` came into being + + def test_format_enum(self): + Season = self.Season + self.assertEqual('{0}'.format(Season.SPRING), + '{0}'.format(str(Season.SPRING))) + self.assertEqual( '{0:}'.format(Season.SPRING), + '{0:}'.format(str(Season.SPRING))) + self.assertEqual('{0:20}'.format(Season.SPRING), + '{0:20}'.format(str(Season.SPRING))) + self.assertEqual('{0:^20}'.format(Season.SPRING), + '{0:^20}'.format(str(Season.SPRING))) + self.assertEqual('{0:>20}'.format(Season.SPRING), + '{0:>20}'.format(str(Season.SPRING))) + self.assertEqual('{0:<20}'.format(Season.SPRING), + '{0:<20}'.format(str(Season.SPRING))) + + def test_format_enum_custom(self): + class TestFloat(float, Enum): + one = 1.0 + two = 2.0 + def __format__(self, spec): + return 'TestFloat success!' + self.assertEqual('{0}'.format(TestFloat.one), 'TestFloat success!') + + def assertFormatIsValue(self, spec, member): + self.assertEqual(spec.format(member), spec.format(member.value)) + + def test_format_enum_date(self): + Holiday = self.Holiday + self.assertFormatIsValue('{0}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{0:}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{0:20}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{0:^20}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{0:>20}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{0:<20}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{0:%Y %m}', Holiday.IDES_OF_MARCH) + self.assertFormatIsValue('{0:%Y %m %M:00}', Holiday.IDES_OF_MARCH) + + def test_format_enum_float(self): + Konstants = self.Konstants + self.assertFormatIsValue('{0}', Konstants.TAU) + self.assertFormatIsValue('{0:}', Konstants.TAU) + self.assertFormatIsValue('{0:20}', Konstants.TAU) + self.assertFormatIsValue('{0:^20}', Konstants.TAU) + self.assertFormatIsValue('{0:>20}', Konstants.TAU) + self.assertFormatIsValue('{0:<20}', Konstants.TAU) + self.assertFormatIsValue('{0:n}', Konstants.TAU) + self.assertFormatIsValue('{0:5.2}', Konstants.TAU) + self.assertFormatIsValue('{0:f}', Konstants.TAU) + + def test_format_enum_int(self): + Grades = self.Grades + self.assertFormatIsValue('{0}', Grades.C) + self.assertFormatIsValue('{0:}', Grades.C) + self.assertFormatIsValue('{0:20}', Grades.C) + self.assertFormatIsValue('{0:^20}', Grades.C) + self.assertFormatIsValue('{0:>20}', Grades.C) + self.assertFormatIsValue('{0:<20}', Grades.C) + self.assertFormatIsValue('{0:+}', Grades.C) + self.assertFormatIsValue('{0:08X}', Grades.C) + self.assertFormatIsValue('{0:b}', Grades.C) + + def test_format_enum_str(self): + Directional = self.Directional + self.assertFormatIsValue('{0}', Directional.WEST) + self.assertFormatIsValue('{0:}', Directional.WEST) + self.assertFormatIsValue('{0:20}', Directional.WEST) + self.assertFormatIsValue('{0:^20}', Directional.WEST) + self.assertFormatIsValue('{0:>20}', Directional.WEST) + self.assertFormatIsValue('{0:<20}', Directional.WEST) + + def test_hash(self): + Season = self.Season + dates = {} + dates[Season.WINTER] = '1225' + dates[Season.SPRING] = '0315' + dates[Season.SUMMER] = '0704' + dates[Season.AUTUMN] = '1031' + self.assertEqual(dates[Season.AUTUMN], '1031') + + def test_enum_duplicates(self): + class Season(Enum): + _order_ = "SPRING SUMMER AUTUMN WINTER" + SPRING = 1 + SUMMER = 2 + AUTUMN = FALL = 3 + WINTER = 4 + ANOTHER_SPRING = 1 + lst = list(Season) + self.assertEqual( + lst, + [Season.SPRING, Season.SUMMER, + Season.AUTUMN, Season.WINTER, + ]) + self.assertTrue(Season.FALL is Season.AUTUMN) + self.assertEqual(Season.FALL.value, 3) + self.assertEqual(Season.AUTUMN.value, 3) + self.assertTrue(Season(3) is Season.AUTUMN) + self.assertTrue(Season(1) is Season.SPRING) + self.assertEqual(Season.FALL.name, 'AUTUMN') + self.assertEqual( + set([k for k,v in Season.__members__.items() if v.name != k]), + set(['FALL', 'ANOTHER_SPRING']), + ) + + if pyver >= 3.0: + cls = vars() + result = {'Enum':Enum} + exec("""def test_duplicate_name(self): + with self.assertRaises(TypeError): + class Color(Enum): + red = 1 + green = 2 + blue = 3 + red = 4 + + with self.assertRaises(TypeError): + class Color(Enum): + red = 1 + green = 2 + blue = 3 + def red(self): + return 'red' + + with self.assertRaises(TypeError): + class Color(Enum): + @property + + def red(self): + return 'redder' + red = 1 + green = 2 + blue = 3""", + result) + cls['test_duplicate_name'] = result['test_duplicate_name'] + + def test_enum_with_value_name(self): + class Huh(Enum): + name = 1 + value = 2 + self.assertEqual( + list(Huh), + [Huh.name, Huh.value], + ) + self.assertTrue(type(Huh.name) is Huh) + self.assertEqual(Huh.name.name, 'name') + self.assertEqual(Huh.name.value, 1) + + def test_intenum_from_scratch(self): + class phy(int, Enum): + pi = 3 + tau = 2 * pi + self.assertTrue(phy.pi < phy.tau) + + def test_intenum_inherited(self): + class IntEnum(int, Enum): + pass + class phy(IntEnum): + pi = 3 + tau = 2 * pi + self.assertTrue(phy.pi < phy.tau) + + def test_floatenum_from_scratch(self): + class phy(float, Enum): + pi = 3.1415926 + tau = 2 * pi + self.assertTrue(phy.pi < phy.tau) + + def test_floatenum_inherited(self): + class FloatEnum(float, Enum): + pass + class phy(FloatEnum): + pi = 3.1415926 + tau = 2 * pi + self.assertTrue(phy.pi < phy.tau) + + def test_strenum_from_scratch(self): + class phy(str, Enum): + pi = 'Pi' + tau = 'Tau' + self.assertTrue(phy.pi < phy.tau) + + def test_strenum_inherited(self): + class StrEnum(str, Enum): + pass + class phy(StrEnum): + pi = 'Pi' + tau = 'Tau' + self.assertTrue(phy.pi < phy.tau) + + def test_intenum(self): + class WeekDay(IntEnum): + SUNDAY = 1 + MONDAY = 2 + TUESDAY = 3 + WEDNESDAY = 4 + THURSDAY = 5 + FRIDAY = 6 + SATURDAY = 7 + + self.assertEqual(['a', 'b', 'c'][WeekDay.MONDAY], 'c') + self.assertEqual([i for i in range(WeekDay.TUESDAY)], [0, 1, 2]) + + lst = list(WeekDay) + self.assertEqual(len(lst), len(WeekDay)) + self.assertEqual(len(WeekDay), 7) + target = 'SUNDAY MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY' + target = target.split() + for i, weekday in enumerate(target): + i += 1 + e = WeekDay(i) + self.assertEqual(e, i) + self.assertEqual(int(e), i) + self.assertEqual(e.name, weekday) + self.assertTrue(e in WeekDay) + self.assertEqual(lst.index(e)+1, i) + self.assertTrue(0 < e < 8) + self.assertTrue(type(e) is WeekDay) + self.assertTrue(isinstance(e, int)) + self.assertTrue(isinstance(e, Enum)) + + def test_intenum_duplicates(self): + class WeekDay(IntEnum): + __order__ = 'SUNDAY MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY' + SUNDAY = 1 + MONDAY = 2 + TUESDAY = TEUSDAY = 3 + WEDNESDAY = 4 + THURSDAY = 5 + FRIDAY = 6 + SATURDAY = 7 + self.assertTrue(WeekDay.TEUSDAY is WeekDay.TUESDAY) + self.assertEqual(WeekDay(3).name, 'TUESDAY') + self.assertEqual([k for k,v in WeekDay.__members__.items() + if v.name != k], ['TEUSDAY', ]) + + def test_pickle_enum(self): + if isinstance(Stooges, Exception): + raise Stooges + test_pickle_dump_load(self.assertTrue, Stooges.CURLY) + test_pickle_dump_load(self.assertTrue, Stooges) + + def test_pickle_int(self): + if isinstance(IntStooges, Exception): + raise IntStooges + test_pickle_dump_load(self.assertTrue, IntStooges.CURLY) + test_pickle_dump_load(self.assertTrue, IntStooges) + + def test_pickle_float(self): + if isinstance(FloatStooges, Exception): + raise FloatStooges + test_pickle_dump_load(self.assertTrue, FloatStooges.CURLY) + test_pickle_dump_load(self.assertTrue, FloatStooges) + + def test_pickle_enum_function(self): + if isinstance(Answer, Exception): + raise Answer + test_pickle_dump_load(self.assertTrue, Answer.him) + test_pickle_dump_load(self.assertTrue, Answer) + + def test_pickle_enum_function_with_module(self): + if isinstance(Question, Exception): + raise Question + test_pickle_dump_load(self.assertTrue, Question.who) + test_pickle_dump_load(self.assertTrue, Question) + + if pyver == 3.4: + def test_class_nested_enum_and_pickle_protocol_four(self): + # would normally just have this directly in the class namespace + class NestedEnum(Enum): + twigs = 'common' + shiny = 'rare' + + self.__class__.NestedEnum = NestedEnum + self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__ + test_pickle_exception( + self.assertRaises, PicklingError, self.NestedEnum.twigs, + protocol=(0, 3)) + test_pickle_dump_load(self.assertTrue, self.NestedEnum.twigs, + protocol=(4, HIGHEST_PROTOCOL)) + + elif pyver == 3.5: + def test_class_nested_enum_and_pickle_protocol_four(self): + # would normally just have this directly in the class namespace + class NestedEnum(Enum): + twigs = 'common' + shiny = 'rare' + + self.__class__.NestedEnum = NestedEnum + self.NestedEnum.__qualname__ = '%s.NestedEnum' % self.__class__.__name__ + test_pickle_dump_load(self.assertTrue, self.NestedEnum.twigs, + protocol=(0, HIGHEST_PROTOCOL)) + + def test_exploding_pickle(self): + BadPickle = Enum('BadPickle', 'dill sweet bread_n_butter') + enum._make_class_unpicklable(BadPickle) + globals()['BadPickle'] = BadPickle + test_pickle_exception(self.assertRaises, TypeError, BadPickle.dill) + test_pickle_exception(self.assertRaises, PicklingError, BadPickle) + + def test_string_enum(self): + class SkillLevel(str, Enum): + master = 'what is the sound of one hand clapping?' + journeyman = 'why did the chicken cross the road?' + apprentice = 'knock, knock!' + self.assertEqual(SkillLevel.apprentice, 'knock, knock!') + + def test_getattr_getitem(self): + class Period(Enum): + morning = 1 + noon = 2 + evening = 3 + night = 4 + self.assertTrue(Period(2) is Period.noon) + self.assertTrue(getattr(Period, 'night') is Period.night) + self.assertTrue(Period['morning'] is Period.morning) + + def test_getattr_dunder(self): + Season = self.Season + self.assertTrue(getattr(Season, '__hash__')) + + def test_iteration_order(self): + class Season(Enum): + _order_ = 'SUMMER WINTER AUTUMN SPRING' + SUMMER = 2 + WINTER = 4 + AUTUMN = 3 + SPRING = 1 + self.assertEqual( + list(Season), + [Season.SUMMER, Season.WINTER, Season.AUTUMN, Season.SPRING], + ) + + def test_iteration_order_reversed(self): + self.assertEqual( + list(reversed(self.Season)), + [self.Season.WINTER, self.Season.AUTUMN, self.Season.SUMMER, + self.Season.SPRING] + ) + + def test_iteration_order_with_unorderable_values(self): + class Complex(Enum): + a = complex(7, 9) + b = complex(3.14, 2) + c = complex(1, -1) + d = complex(-77, 32) + self.assertEqual( + list(Complex), + [Complex.a, Complex.b, Complex.c, Complex.d], + ) + + def test_programatic_function_string(self): + SummerMonth = Enum('SummerMonth', 'june july august') + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_string_with_start(self): + SummerMonth = Enum('SummerMonth', 'june july august', start=10) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 10): + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_string_list(self): + SummerMonth = Enum('SummerMonth', ['june', 'july', 'august']) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_string_list_with_start(self): + SummerMonth = Enum('SummerMonth', ['june', 'july', 'august'], start=20) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 20): + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_iterable(self): + SummerMonth = Enum( + 'SummerMonth', + (('june', 1), ('july', 2), ('august', 3)) + ) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_iterable_with_weird_names(self): + SummerMonth = Enum( + 'SummerMonth', + (('june', 1), ('july', 2), ('august', 3), ('fabulous september', 4)) + ) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 4, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august, SummerMonth['fabulous september']], + lst, + ) + for i, month in enumerate('june july august'.split() + ['fabulous september']): + i += 1 + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_from_dict(self): + SummerMonth = Enum( + 'SummerMonth', + dict((('june', 1), ('july', 2), ('august', 3))) + ) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + if pyver < 3.0: + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_type(self): + SummerMonth = Enum('SummerMonth', 'june july august', type=int) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_type_with_start(self): + SummerMonth = Enum('SummerMonth', 'june july august', type=int, start=30) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 30): + e = SummerMonth(i) + self.assertEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_type_from_subclass(self): + SummerMonth = IntEnum('SummerMonth', 'june july august') + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_type_from_subclass_with_start(self): + SummerMonth = IntEnum('SummerMonth', 'june july august', start=40) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate('june july august'.split(), 40): + e = SummerMonth(i) + self.assertEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_unicode(self): + SummerMonth = Enum('SummerMonth', unicode('june july august')) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate(unicode('june july august').split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_unicode_list(self): + SummerMonth = Enum('SummerMonth', [unicode('june'), unicode('july'), unicode('august')]) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate(unicode('june july august').split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_unicode_iterable(self): + SummerMonth = Enum( + 'SummerMonth', + ((unicode('june'), 1), (unicode('july'), 2), (unicode('august'), 3)) + ) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate(unicode('june july august').split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_from_unicode_dict(self): + SummerMonth = Enum( + 'SummerMonth', + dict(((unicode('june'), 1), (unicode('july'), 2), (unicode('august'), 3))) + ) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + if pyver < 3.0: + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate(unicode('june july august').split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(int(e.value), i) + self.assertNotEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_unicode_type(self): + SummerMonth = Enum('SummerMonth', unicode('june july august'), type=int) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate(unicode('june july august').split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programatic_function_unicode_type_from_subclass(self): + SummerMonth = IntEnum('SummerMonth', unicode('june july august')) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate(unicode('june july august').split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(e, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_programmatic_function_unicode_class(self): + if pyver < 3.0: + class_names = unicode('SummerMonth'), 'S\xfcmm\xe9rM\xf6nth'.decode('latin1') + else: + class_names = 'SummerMonth', 'S\xfcmm\xe9rM\xf6nth' + for i, class_name in enumerate(class_names): + if pyver < 3.0 and i == 1: + self.assertRaises(TypeError, Enum, class_name, unicode('june july august')) + else: + SummerMonth = Enum(class_name, unicode('june july august')) + lst = list(SummerMonth) + self.assertEqual(len(lst), len(SummerMonth)) + self.assertEqual(len(SummerMonth), 3, SummerMonth) + self.assertEqual( + [SummerMonth.june, SummerMonth.july, SummerMonth.august], + lst, + ) + for i, month in enumerate(unicode('june july august').split()): + i += 1 + e = SummerMonth(i) + self.assertEqual(e.value, i) + self.assertEqual(e.name, month) + self.assertTrue(e in SummerMonth) + self.assertTrue(type(e) is SummerMonth) + + def test_subclassing(self): + if isinstance(Name, Exception): + raise Name + self.assertEqual(Name.BDFL, 'Guido van Rossum') + self.assertTrue(Name.BDFL, Name('Guido van Rossum')) + self.assertTrue(Name.BDFL is getattr(Name, 'BDFL')) + test_pickle_dump_load(self.assertTrue, Name.BDFL) + + def test_extending(self): + def bad_extension(): + class Color(Enum): + red = 1 + green = 2 + blue = 3 + class MoreColor(Color): + cyan = 4 + magenta = 5 + yellow = 6 + self.assertRaises(TypeError, bad_extension) + + def test_exclude_methods(self): + class whatever(Enum): + this = 'that' + these = 'those' + def really(self): + return 'no, not %s' % self.value + self.assertFalse(type(whatever.really) is whatever) + self.assertEqual(whatever.this.really(), 'no, not that') + + def test_wrong_inheritance_order(self): + def wrong_inherit(): + class Wrong(Enum, str): + NotHere = 'error before this point' + self.assertRaises(TypeError, wrong_inherit) + + def test_intenum_transitivity(self): + class number(IntEnum): + one = 1 + two = 2 + three = 3 + class numero(IntEnum): + uno = 1 + dos = 2 + tres = 3 + self.assertEqual(number.one, numero.uno) + self.assertEqual(number.two, numero.dos) + self.assertEqual(number.three, numero.tres) + + def test_introspection(self): + class Number(IntEnum): + one = 100 + two = 200 + self.assertTrue(Number.one._member_type_ is int) + self.assertTrue(Number._member_type_ is int) + class String(str, Enum): + yarn = 'soft' + rope = 'rough' + wire = 'hard' + self.assertTrue(String.yarn._member_type_ is str) + self.assertTrue(String._member_type_ is str) + class Plain(Enum): + vanilla = 'white' + one = 1 + self.assertTrue(Plain.vanilla._member_type_ is object) + self.assertTrue(Plain._member_type_ is object) + + def test_wrong_enum_in_call(self): + class Monochrome(Enum): + black = 0 + white = 1 + class Gender(Enum): + male = 0 + female = 1 + self.assertRaises(ValueError, Monochrome, Gender.male) + + def test_wrong_enum_in_mixed_call(self): + class Monochrome(IntEnum): + black = 0 + white = 1 + class Gender(Enum): + male = 0 + female = 1 + self.assertRaises(ValueError, Monochrome, Gender.male) + + def test_mixed_enum_in_call_1(self): + class Monochrome(IntEnum): + black = 0 + white = 1 + class Gender(IntEnum): + male = 0 + female = 1 + self.assertTrue(Monochrome(Gender.female) is Monochrome.white) + + def test_mixed_enum_in_call_2(self): + class Monochrome(Enum): + black = 0 + white = 1 + class Gender(IntEnum): + male = 0 + female = 1 + self.assertTrue(Monochrome(Gender.male) is Monochrome.black) + + def test_flufl_enum(self): + class Fluflnum(Enum): + def __int__(self): + return int(self.value) + class MailManOptions(Fluflnum): + option1 = 1 + option2 = 2 + option3 = 3 + self.assertEqual(int(MailManOptions.option1), 1) + + def test_no_such_enum_member(self): + class Color(Enum): + red = 1 + green = 2 + blue = 3 + self.assertRaises(ValueError, Color, 4) + self.assertRaises(KeyError, Color.__getitem__, 'chartreuse') + + def test_new_repr(self): + class Color(Enum): + red = 1 + green = 2 + blue = 3 + def __repr__(self): + return "don't you just love shades of %s?" % self.name + self.assertEqual( + repr(Color.blue), + "don't you just love shades of blue?", + ) + + def test_inherited_repr(self): + class MyEnum(Enum): + def __repr__(self): + return "My name is %s." % self.name + class MyIntEnum(int, MyEnum): + this = 1 + that = 2 + theother = 3 + self.assertEqual(repr(MyIntEnum.that), "My name is that.") + + def test_multiple_mixin_mro(self): + class auto_enum(EnumMeta): + def __new__(metacls, cls, bases, classdict): + original_dict = classdict + classdict = enum._EnumDict() + for k, v in original_dict.items(): + classdict[k] = v + temp = type(classdict)() + names = set(classdict._member_names) + i = 0 + for k in classdict._member_names: + v = classdict[k] + if v == (): + v = i + else: + i = v + i += 1 + temp[k] = v + for k, v in classdict.items(): + if k not in names: + temp[k] = v + return super(auto_enum, metacls).__new__( + metacls, cls, bases, temp) + + AutoNumberedEnum = auto_enum('AutoNumberedEnum', (Enum,), {}) + + AutoIntEnum = auto_enum('AutoIntEnum', (IntEnum,), {}) + + class TestAutoNumber(AutoNumberedEnum): + a = () + b = 3 + c = () + + class TestAutoInt(AutoIntEnum): + a = () + b = 3 + c = () + + def test_subclasses_with_getnewargs(self): + class NamedInt(int): + __qualname__ = 'NamedInt' # needed for pickle protocol 4 + def __new__(cls, *args): + _args = args + if len(args) < 1: + raise TypeError("name and value must be specified") + name, args = args[0], args[1:] + self = int.__new__(cls, *args) + self._intname = name + self._args = _args + return self + def __getnewargs__(self): + return self._args + @property + def __name__(self): + return self._intname + def __repr__(self): + # repr() is updated to include the name and type info + return "%s(%r, %s)" % (type(self).__name__, + self.__name__, + int.__repr__(self)) + def __str__(self): + # str() is unchanged, even if it relies on the repr() fallback + base = int + base_str = base.__str__ + if base_str.__objclass__ is object: + return base.__repr__(self) + return base_str(self) + # for simplicity, we only define one operator that + # propagates expressions + def __add__(self, other): + temp = int(self) + int( other) + if isinstance(self, NamedInt) and isinstance(other, NamedInt): + return NamedInt( + '(%s + %s)' % (self.__name__, other.__name__), + temp ) + else: + return temp + + class NEI(NamedInt, Enum): + __qualname__ = 'NEI' # needed for pickle protocol 4 + x = ('the-x', 1) + y = ('the-y', 2) + + self.assertTrue(NEI.__new__ is Enum.__new__) + self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") + globals()['NamedInt'] = NamedInt + globals()['NEI'] = NEI + NI5 = NamedInt('test', 5) + self.assertEqual(NI5, 5) + test_pickle_dump_load(self.assertTrue, NI5, 5) + self.assertEqual(NEI.y.value, 2) + test_pickle_dump_load(self.assertTrue, NEI.y) + + if pyver >= 3.4: + def test_subclasses_with_getnewargs_ex(self): + class NamedInt(int): + __qualname__ = 'NamedInt' # needed for pickle protocol 4 + def __new__(cls, *args): + _args = args + if len(args) < 2: + raise TypeError("name and value must be specified") + name, args = args[0], args[1:] + self = int.__new__(cls, *args) + self._intname = name + self._args = _args + return self + def __getnewargs_ex__(self): + return self._args, {} + @property + def __name__(self): + return self._intname + def __repr__(self): + # repr() is updated to include the name and type info + return "{}({!r}, {})".format(type(self).__name__, + self.__name__, + int.__repr__(self)) + def __str__(self): + # str() is unchanged, even if it relies on the repr() fallback + base = int + base_str = base.__str__ + if base_str.__objclass__ is object: + return base.__repr__(self) + return base_str(self) + # for simplicity, we only define one operator that + # propagates expressions + def __add__(self, other): + temp = int(self) + int( other) + if isinstance(self, NamedInt) and isinstance(other, NamedInt): + return NamedInt( + '({0} + {1})'.format(self.__name__, other.__name__), + temp ) + else: + return temp + + class NEI(NamedInt, Enum): + __qualname__ = 'NEI' # needed for pickle protocol 4 + x = ('the-x', 1) + y = ('the-y', 2) + + + self.assertIs(NEI.__new__, Enum.__new__) + self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") + globals()['NamedInt'] = NamedInt + globals()['NEI'] = NEI + NI5 = NamedInt('test', 5) + self.assertEqual(NI5, 5) + test_pickle_dump_load(self.assertEqual, NI5, 5, protocol=(4, HIGHEST_PROTOCOL)) + self.assertEqual(NEI.y.value, 2) + test_pickle_dump_load(self.assertTrue, NEI.y, protocol=(4, HIGHEST_PROTOCOL)) + + def test_subclasses_with_reduce(self): + class NamedInt(int): + __qualname__ = 'NamedInt' # needed for pickle protocol 4 + def __new__(cls, *args): + _args = args + if len(args) < 1: + raise TypeError("name and value must be specified") + name, args = args[0], args[1:] + self = int.__new__(cls, *args) + self._intname = name + self._args = _args + return self + def __reduce__(self): + return self.__class__, self._args + @property + def __name__(self): + return self._intname + def __repr__(self): + # repr() is updated to include the name and type info + return "%s(%r, %s)" % (type(self).__name__, + self.__name__, + int.__repr__(self)) + def __str__(self): + # str() is unchanged, even if it relies on the repr() fallback + base = int + base_str = base.__str__ + if base_str.__objclass__ is object: + return base.__repr__(self) + return base_str(self) + # for simplicity, we only define one operator that + # propagates expressions + def __add__(self, other): + temp = int(self) + int( other) + if isinstance(self, NamedInt) and isinstance(other, NamedInt): + return NamedInt( + '(%s + %s)' % (self.__name__, other.__name__), + temp ) + else: + return temp + + class NEI(NamedInt, Enum): + __qualname__ = 'NEI' # needed for pickle protocol 4 + x = ('the-x', 1) + y = ('the-y', 2) + + + self.assertTrue(NEI.__new__ is Enum.__new__) + self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") + globals()['NamedInt'] = NamedInt + globals()['NEI'] = NEI + NI5 = NamedInt('test', 5) + self.assertEqual(NI5, 5) + test_pickle_dump_load(self.assertEqual, NI5, 5) + self.assertEqual(NEI.y.value, 2) + test_pickle_dump_load(self.assertTrue, NEI.y) + + def test_subclasses_with_reduce_ex(self): + class NamedInt(int): + __qualname__ = 'NamedInt' # needed for pickle protocol 4 + def __new__(cls, *args): + _args = args + if len(args) < 1: + raise TypeError("name and value must be specified") + name, args = args[0], args[1:] + self = int.__new__(cls, *args) + self._intname = name + self._args = _args + return self + def __reduce_ex__(self, proto): + return self.__class__, self._args + @property + def __name__(self): + return self._intname + def __repr__(self): + # repr() is updated to include the name and type info + return "%s(%r, %s)" % (type(self).__name__, + self.__name__, + int.__repr__(self)) + def __str__(self): + # str() is unchanged, even if it relies on the repr() fallback + base = int + base_str = base.__str__ + if base_str.__objclass__ is object: + return base.__repr__(self) + return base_str(self) + # for simplicity, we only define one operator that + # propagates expressions + def __add__(self, other): + temp = int(self) + int( other) + if isinstance(self, NamedInt) and isinstance(other, NamedInt): + return NamedInt( + '(%s + %s)' % (self.__name__, other.__name__), + temp ) + else: + return temp + + class NEI(NamedInt, Enum): + __qualname__ = 'NEI' # needed for pickle protocol 4 + x = ('the-x', 1) + y = ('the-y', 2) + + + self.assertTrue(NEI.__new__ is Enum.__new__) + self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") + globals()['NamedInt'] = NamedInt + globals()['NEI'] = NEI + NI5 = NamedInt('test', 5) + self.assertEqual(NI5, 5) + test_pickle_dump_load(self.assertEqual, NI5, 5) + self.assertEqual(NEI.y.value, 2) + test_pickle_dump_load(self.assertTrue, NEI.y) + + def test_subclasses_without_direct_pickle_support(self): + class NamedInt(int): + __qualname__ = 'NamedInt' + def __new__(cls, *args): + _args = args + name, args = args[0], args[1:] + if len(args) == 0: + raise TypeError("name and value must be specified") + self = int.__new__(cls, *args) + self._intname = name + self._args = _args + return self + @property + def __name__(self): + return self._intname + def __repr__(self): + # repr() is updated to include the name and type info + return "%s(%r, %s)" % (type(self).__name__, + self.__name__, + int.__repr__(self)) + def __str__(self): + # str() is unchanged, even if it relies on the repr() fallback + base = int + base_str = base.__str__ + if base_str.__objclass__ is object: + return base.__repr__(self) + return base_str(self) + # for simplicity, we only define one operator that + # propagates expressions + def __add__(self, other): + temp = int(self) + int( other) + if isinstance(self, NamedInt) and isinstance(other, NamedInt): + return NamedInt( + '(%s + %s)' % (self.__name__, other.__name__), + temp ) + else: + return temp + + class NEI(NamedInt, Enum): + __qualname__ = 'NEI' + x = ('the-x', 1) + y = ('the-y', 2) + + self.assertTrue(NEI.__new__ is Enum.__new__) + self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") + globals()['NamedInt'] = NamedInt + globals()['NEI'] = NEI + NI5 = NamedInt('test', 5) + self.assertEqual(NI5, 5) + self.assertEqual(NEI.y.value, 2) + test_pickle_exception(self.assertRaises, TypeError, NEI.x) + test_pickle_exception(self.assertRaises, PicklingError, NEI) + + def test_subclasses_without_direct_pickle_support_using_name(self): + class NamedInt(int): + __qualname__ = 'NamedInt' + def __new__(cls, *args): + _args = args + name, args = args[0], args[1:] + if len(args) == 0: + raise TypeError("name and value must be specified") + self = int.__new__(cls, *args) + self._intname = name + self._args = _args + return self + @property + def __name__(self): + return self._intname + def __repr__(self): + # repr() is updated to include the name and type info + return "%s(%r, %s)" % (type(self).__name__, + self.__name__, + int.__repr__(self)) + def __str__(self): + # str() is unchanged, even if it relies on the repr() fallback + base = int + base_str = base.__str__ + if base_str.__objclass__ is object: + return base.__repr__(self) + return base_str(self) + # for simplicity, we only define one operator that + # propagates expressions + def __add__(self, other): + temp = int(self) + int( other) + if isinstance(self, NamedInt) and isinstance(other, NamedInt): + return NamedInt( + '(%s + %s)' % (self.__name__, other.__name__), + temp ) + else: + return temp + + class NEI(NamedInt, Enum): + __qualname__ = 'NEI' + x = ('the-x', 1) + y = ('the-y', 2) + def __reduce_ex__(self, proto): + return getattr, (self.__class__, self._name_) + + self.assertTrue(NEI.__new__ is Enum.__new__) + self.assertEqual(repr(NEI.x + NEI.y), "NamedInt('(the-x + the-y)', 3)") + globals()['NamedInt'] = NamedInt + globals()['NEI'] = NEI + NI5 = NamedInt('test', 5) + self.assertEqual(NI5, 5) + self.assertEqual(NEI.y.value, 2) + test_pickle_dump_load(self.assertTrue, NEI.y) + test_pickle_dump_load(self.assertTrue, NEI) + + def test_tuple_subclass(self): + class SomeTuple(tuple, Enum): + __qualname__ = 'SomeTuple' + first = (1, 'for the money') + second = (2, 'for the show') + third = (3, 'for the music') + self.assertTrue(type(SomeTuple.first) is SomeTuple) + self.assertTrue(isinstance(SomeTuple.second, tuple)) + self.assertEqual(SomeTuple.third, (3, 'for the music')) + globals()['SomeTuple'] = SomeTuple + test_pickle_dump_load(self.assertTrue, SomeTuple.first) + + def test_duplicate_values_give_unique_enum_items(self): + class AutoNumber(Enum): + __order__ = 'enum_m enum_d enum_y' + enum_m = () + enum_d = () + enum_y = () + def __new__(cls): + value = len(cls.__members__) + 1 + obj = object.__new__(cls) + obj._value_ = value + return obj + def __int__(self): + return int(self._value_) + self.assertEqual(int(AutoNumber.enum_d), 2) + self.assertEqual(AutoNumber.enum_y.value, 3) + self.assertTrue(AutoNumber(1) is AutoNumber.enum_m) + self.assertEqual( + list(AutoNumber), + [AutoNumber.enum_m, AutoNumber.enum_d, AutoNumber.enum_y], + ) + + def test_inherited_new_from_enhanced_enum(self): + class AutoNumber2(Enum): + def __new__(cls): + value = len(cls.__members__) + 1 + obj = object.__new__(cls) + obj._value_ = value + return obj + def __int__(self): + return int(self._value_) + class Color(AutoNumber2): + _order_ = 'red green blue' + red = () + green = () + blue = () + self.assertEqual(len(Color), 3, "wrong number of elements: %d (should be %d)" % (len(Color), 3)) + self.assertEqual(list(Color), [Color.red, Color.green, Color.blue]) + if pyver >= 3.0: + self.assertEqual(list(map(int, Color)), [1, 2, 3]) + + def test_inherited_new_from_mixed_enum(self): + class AutoNumber3(IntEnum): + def __new__(cls): + value = len(cls.__members__) + 1 + obj = int.__new__(cls, value) + obj._value_ = value + return obj + class Color(AutoNumber3): + red = () + green = () + blue = () + self.assertEqual(len(Color), 3, "wrong number of elements: %d (should be %d)" % (len(Color), 3)) + Color.red + Color.green + Color.blue + + def test_equality(self): + class AlwaysEqual: + def __eq__(self, other): + return True + class OrdinaryEnum(Enum): + a = 1 + self.assertEqual(AlwaysEqual(), OrdinaryEnum.a) + self.assertEqual(OrdinaryEnum.a, AlwaysEqual()) + + def test_ordered_mixin(self): + class OrderedEnum(Enum): + def __ge__(self, other): + if self.__class__ is other.__class__: + return self._value_ >= other._value_ + return NotImplemented + def __gt__(self, other): + if self.__class__ is other.__class__: + return self._value_ > other._value_ + return NotImplemented + def __le__(self, other): + if self.__class__ is other.__class__: + return self._value_ <= other._value_ + return NotImplemented + def __lt__(self, other): + if self.__class__ is other.__class__: + return self._value_ < other._value_ + return NotImplemented + class Grade(OrderedEnum): + __order__ = 'A B C D F' + A = 5 + B = 4 + C = 3 + D = 2 + F = 1 + self.assertEqual(list(Grade), [Grade.A, Grade.B, Grade.C, Grade.D, Grade.F]) + self.assertTrue(Grade.A > Grade.B) + self.assertTrue(Grade.F <= Grade.C) + self.assertTrue(Grade.D < Grade.A) + self.assertTrue(Grade.B >= Grade.B) + + def test_extending2(self): + def bad_extension(): + class Shade(Enum): + def shade(self): + print(self.name) + class Color(Shade): + red = 1 + green = 2 + blue = 3 + class MoreColor(Color): + cyan = 4 + magenta = 5 + yellow = 6 + self.assertRaises(TypeError, bad_extension) + + def test_extending3(self): + class Shade(Enum): + def shade(self): + return self.name + class Color(Shade): + def hex(self): + return '%s hexlified!' % self.value + class MoreColor(Color): + cyan = 4 + magenta = 5 + yellow = 6 + self.assertEqual(MoreColor.magenta.hex(), '5 hexlified!') + + def test_no_duplicates(self): + def bad_duplicates(): + class UniqueEnum(Enum): + def __init__(self, *args): + cls = self.__class__ + if any(self.value == e.value for e in cls): + a = self.name + e = cls(self.value).name + raise ValueError( + "aliases not allowed in UniqueEnum: %r --> %r" + % (a, e) + ) + class Color(UniqueEnum): + red = 1 + green = 2 + blue = 3 + class Color(UniqueEnum): + red = 1 + green = 2 + blue = 3 + grene = 2 + self.assertRaises(ValueError, bad_duplicates) + + def test_init(self): + class Planet(Enum): + MERCURY = (3.303e+23, 2.4397e6) + VENUS = (4.869e+24, 6.0518e6) + EARTH = (5.976e+24, 6.37814e6) + MARS = (6.421e+23, 3.3972e6) + JUPITER = (1.9e+27, 7.1492e7) + SATURN = (5.688e+26, 6.0268e7) + URANUS = (8.686e+25, 2.5559e7) + NEPTUNE = (1.024e+26, 2.4746e7) + def __init__(self, mass, radius): + self.mass = mass # in kilograms + self.radius = radius # in meters + @property + def surface_gravity(self): + # universal gravitational constant (m3 kg-1 s-2) + G = 6.67300E-11 + return G * self.mass / (self.radius * self.radius) + self.assertEqual(round(Planet.EARTH.surface_gravity, 2), 9.80) + self.assertEqual(Planet.EARTH.value, (5.976e+24, 6.37814e6)) + + def test_nonhash_value(self): + class AutoNumberInAList(Enum): + def __new__(cls): + value = [len(cls.__members__) + 1] + obj = object.__new__(cls) + obj._value_ = value + return obj + class ColorInAList(AutoNumberInAList): + _order_ = 'red green blue' + red = () + green = () + blue = () + self.assertEqual(list(ColorInAList), [ColorInAList.red, ColorInAList.green, ColorInAList.blue]) + self.assertEqual(ColorInAList.red.value, [1]) + self.assertEqual(ColorInAList([1]), ColorInAList.red) + + def test_conflicting_types_resolved_in_new(self): + class LabelledIntEnum(int, Enum): + def __new__(cls, *args): + value, label = args + obj = int.__new__(cls, value) + obj.label = label + obj._value_ = value + return obj + + class LabelledList(LabelledIntEnum): + unprocessed = (1, "Unprocessed") + payment_complete = (2, "Payment Complete") + + self.assertEqual(list(LabelledList), [LabelledList.unprocessed, LabelledList.payment_complete]) + self.assertEqual(LabelledList.unprocessed, 1) + self.assertEqual(LabelledList(1), LabelledList.unprocessed) + + def test_empty_with_functional_api(self): + empty = enum.IntEnum('Foo', {}) + self.assertEqual(len(empty), 0) + + +class TestUnique(unittest.TestCase): + """2.4 doesn't allow class decorators, use function syntax.""" + + def test_unique_clean(self): + class Clean(Enum): + one = 1 + two = 'dos' + tres = 4.0 + unique(Clean) + class Cleaner(IntEnum): + single = 1 + double = 2 + triple = 3 + unique(Cleaner) + + def test_unique_dirty(self): + try: + class Dirty(Enum): + __order__ = 'one two tres' + one = 1 + two = 'dos' + tres = 1 + unique(Dirty) + except ValueError: + exc = sys.exc_info()[1] + message = exc.args[0] + self.assertTrue('tres -> one' in message) + + try: + class Dirtier(IntEnum): + _order_ = 'single double triple turkey' + single = 1 + double = 1 + triple = 3 + turkey = 3 + unique(Dirtier) + except ValueError: + exc = sys.exc_info()[1] + message = exc.args[0] + self.assertTrue('double -> single' in message) + self.assertTrue('turkey -> triple' in message) + + +class TestMe(unittest.TestCase): + + pass + +if __name__ == '__main__': + unittest.main() diff --git a/testing/web-platform/tests/tools/third_party/enum/setup.cfg b/testing/web-platform/tests/tools/third_party/enum/setup.cfg new file mode 100644 index 0000000000..8bfd5a12f8 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/enum/setup.cfg @@ -0,0 +1,4 @@ +[egg_info] +tag_build = +tag_date = 0 + diff --git a/testing/web-platform/tests/tools/third_party/enum/setup.py b/testing/web-platform/tests/tools/third_party/enum/setup.py new file mode 100644 index 0000000000..f54071e7f0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/enum/setup.py @@ -0,0 +1,105 @@ +import os +import sys +import setuptools +from distutils.core import setup + + +if sys.version_info[:2] < (2, 7): + required = ['ordereddict'] +else: + required = [] + +# Don't shadow builtin enum package if we are being installed on a +# recent Python. This causes conflicts since at least 3.6: +# https://bitbucket.org/stoneleaf/enum34/issues/19/enum34-isnt-compatible-with-python-36 +if sys.version_info[:2] < (3, 4): + packages = ['enum'] +else: + packages = [] + +long_desc = '''\ +enum --- support for enumerations +======================================== + +An enumeration is a set of symbolic names (members) bound to unique, constant +values. Within an enumeration, the members can be compared by identity, and +the enumeration itself can be iterated over. + + from enum import Enum + + class Fruit(Enum): + apple = 1 + banana = 2 + orange = 3 + + list(Fruit) + # [, , ] + + len(Fruit) + # 3 + + Fruit.banana + # + + Fruit['banana'] + # + + Fruit(2) + # + + Fruit.banana is Fruit['banana'] is Fruit(2) + # True + + Fruit.banana.name + # 'banana' + + Fruit.banana.value + # 2 + +Repository and Issue Tracker at https://bitbucket.org/stoneleaf/enum34. +''' + +py2_only = () +py3_only = () +make = [ + # 'rst2pdf enum/doc/enum.rst --output=enum/doc/enum.pdf', + ] + + +data = dict( + name='enum34', + version='1.1.10', + url='https://bitbucket.org/stoneleaf/enum34', + packages=packages, + package_data={ + 'enum' : [ + 'LICENSE', + 'README', + 'doc/enum.rst', + 'doc/enum.pdf', + 'test.py', + ] + }, + license='BSD License', + description='Python 3.4 Enum backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4', + long_description=long_desc, + provides=['enum'], + install_requires=required, + author='Ethan Furman', + author_email='ethan@stoneleaf.us', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Programming Language :: Python', + 'Topic :: Software Development', + 'Programming Language :: Python :: 2.4', + 'Programming Language :: Python :: 2.5', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3.3', + ], + ) + +if __name__ == '__main__': + setup(**data) diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/.coveragerc b/testing/web-platform/tests/tools/third_party/funcsigs/.coveragerc new file mode 100644 index 0000000000..d83bfc220b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/.coveragerc @@ -0,0 +1,6 @@ +[run] +source=funcsigs +omit=funcsigs/odict* + +[report] +include=funcsigs* diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/.gitignore b/testing/web-platform/tests/tools/third_party/funcsigs/.gitignore new file mode 100644 index 0000000000..c8d2af85d3 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/.gitignore @@ -0,0 +1,19 @@ +*~ +*.egg +*.egg-info +*.pyc +*.pyo +*.swp +.DS_Store +.coverage +.tox/ +MANIFEST +build/ +docs/.build/ +dist/ +env*/ +htmlcov/ +tmp/ +coverage.xml +junit.xml +.eggs/ diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/.travis.yml b/testing/web-platform/tests/tools/third_party/funcsigs/.travis.yml new file mode 100644 index 0000000000..c1e7abe0b4 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/.travis.yml @@ -0,0 +1,18 @@ +language: python +python: + - 2.6 + - 2.7 + - 3.3 + - 3.4 + - 3.5 + - nightly + - pypy +# - pypy3 +install: + - pip install -U pip setuptools wheel + - pip install -r requirements/development.txt . +script: + - coverage run setup.py test + - coverage report --show-missing +after_success: + - coveralls diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/CHANGELOG b/testing/web-platform/tests/tools/third_party/funcsigs/CHANGELOG new file mode 100644 index 0000000000..e1366d2668 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/CHANGELOG @@ -0,0 +1,24 @@ +Changelog +--------- + +0.5 +``` + +* Fix binding with self as a kwarg. (Robert Collins #14) + +0.4 (2013-12-20) +```````````````` +* Fix unbound methods getting their first parameter curried +* Publish Python wheel packages + +0.3 (2013-05-29) +```````````````` +* Fix annotation formatting of builtin types on Python 2.x + +0.2 (2012-01-07) +```````````````` +* PyPy compatability + +0.1 (2012-01-06) +```````````````` +* Initial release diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/LICENSE b/testing/web-platform/tests/tools/third_party/funcsigs/LICENSE new file mode 100644 index 0000000000..3e563d6fbd --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/LICENSE @@ -0,0 +1,13 @@ +Copyright 2013 Aaron Iles + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/MANIFEST.in b/testing/web-platform/tests/tools/third_party/funcsigs/MANIFEST.in new file mode 100644 index 0000000000..f0abb42f04 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/MANIFEST.in @@ -0,0 +1,7 @@ +recursive-include docs * +recursive-include tests *.py +include *.py +include CHANGELOG +include LICENSE +include MANIFEST.in +include README.rst diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/Makefile b/testing/web-platform/tests/tools/third_party/funcsigs/Makefile new file mode 100644 index 0000000000..e2329231b5 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/Makefile @@ -0,0 +1,39 @@ +SHELL := /bin/bash + +deps: + pip install --upgrade \ + -r requirements/development.txt \ + -r requirements/production.txt + +sdist: + python setup.py sdist + python setup.py bdist_wheel + +register: + python setup.py register + python setup.py sdist upload + python setup.py bdist_wheel upload + +site: + cd docs; make html + +test: + coverage run setup.py test + +unittest: + coverage run -m unittest discover + +lint: + flake8 --exit-zero funcsigs tests + +coverage: + coverage report --show-missing + +clean: + python setup.py clean --all + find . -type f -name "*.pyc" -exec rm '{}' + + find . -type d -name "__pycache__" -exec rmdir '{}' + + rm -rf *.egg-info .coverage + cd docs; make clean + +docs: site diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/README.rst b/testing/web-platform/tests/tools/third_party/funcsigs/README.rst new file mode 100644 index 0000000000..5fbca27e6e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/README.rst @@ -0,0 +1,353 @@ +.. funcsigs documentation master file, created by + sphinx-quickstart on Fri Apr 20 20:27:52 2012. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Introducing funcsigs +==================== + +The Funcsigs Package +-------------------- + +``funcsigs`` is a backport of the `PEP 362`_ function signature features from +Python 3.3's `inspect`_ module. The backport is compatible with Python 2.6, 2.7 +as well as 3.3 and up. 3.2 was supported by version 0.4, but with setuptools and +pip no longer supporting 3.2, we cannot make any statement about 3.2 +compatibility. + +Compatibility +````````````` + +The ``funcsigs`` backport has been tested against: + +* CPython 2.6 +* CPython 2.7 +* CPython 3.3 +* CPython 3.4 +* CPython 3.5 +* CPython nightlies +* PyPy and PyPy3(currently failing CI) + +Continuous integration testing is provided by `Travis CI`_. + +Under Python 2.x there is a compatibility issue when a function is assigned to +the ``__wrapped__`` property of a class after it has been constructed. +Similiarily there under PyPy directly passing the ``__call__`` method of a +builtin is also a compatibility issues. Otherwise the functionality is +believed to be uniform between both Python2 and Python3. + +Issues +`````` + +Source code for ``funcsigs`` is hosted on `GitHub`_. Any bug reports or feature +requests can be made using GitHub's `issues system`_. |build_status| |coverage| + +Example +------- + +To obtain a `Signature` object, pass the target function to the +``funcsigs.signature`` function. + +.. code-block:: python + + >>> from funcsigs import signature + >>> def foo(a, b=None, *args, **kwargs): + ... pass + ... + >>> sig = signature(foo) + >>> sig + + >>> sig.parameters + OrderedDict([('a', ), ('b', ), ('args', ), ('kwargs', )]) + >>> sig.return_annotation + + +Introspecting callables with the Signature object +------------------------------------------------- + +.. note:: + + This section of documentation is a direct reproduction of the Python + standard library documentation for the inspect module. + +The Signature object represents the call signature of a callable object and its +return annotation. To retrieve a Signature object, use the :func:`signature` +function. + +.. function:: signature(callable) + + Return a :class:`Signature` object for the given ``callable``:: + + >>> from funcsigs import signature + >>> def foo(a, *, b:int, **kwargs): + ... pass + + >>> sig = signature(foo) + + >>> str(sig) + '(a, *, b:int, **kwargs)' + + >>> str(sig.parameters['b']) + 'b:int' + + >>> sig.parameters['b'].annotation + + + Accepts a wide range of python callables, from plain functions and classes to + :func:`functools.partial` objects. + + .. note:: + + Some callables may not be introspectable in certain implementations of + Python. For example, in CPython, built-in functions defined in C provide + no metadata about their arguments. + + +.. class:: Signature + + A Signature object represents the call signature of a function and its return + annotation. For each parameter accepted by the function it stores a + :class:`Parameter` object in its :attr:`parameters` collection. + + Signature objects are *immutable*. Use :meth:`Signature.replace` to make a + modified copy. + + .. attribute:: Signature.empty + + A special class-level marker to specify absence of a return annotation. + + .. attribute:: Signature.parameters + + An ordered mapping of parameters' names to the corresponding + :class:`Parameter` objects. + + .. attribute:: Signature.return_annotation + + The "return" annotation for the callable. If the callable has no "return" + annotation, this attribute is set to :attr:`Signature.empty`. + + .. method:: Signature.bind(*args, **kwargs) + + Create a mapping from positional and keyword arguments to parameters. + Returns :class:`BoundArguments` if ``*args`` and ``**kwargs`` match the + signature, or raises a :exc:`TypeError`. + + .. method:: Signature.bind_partial(*args, **kwargs) + + Works the same way as :meth:`Signature.bind`, but allows the omission of + some required arguments (mimics :func:`functools.partial` behavior.) + Returns :class:`BoundArguments`, or raises a :exc:`TypeError` if the + passed arguments do not match the signature. + + .. method:: Signature.replace(*[, parameters][, return_annotation]) + + Create a new Signature instance based on the instance replace was invoked + on. It is possible to pass different ``parameters`` and/or + ``return_annotation`` to override the corresponding properties of the base + signature. To remove return_annotation from the copied Signature, pass in + :attr:`Signature.empty`. + + :: + + >>> def test(a, b): + ... pass + >>> sig = signature(test) + >>> new_sig = sig.replace(return_annotation="new return anno") + >>> str(new_sig) + "(a, b) -> 'new return anno'" + + +.. class:: Parameter + + Parameter objects are *immutable*. Instead of modifying a Parameter object, + you can use :meth:`Parameter.replace` to create a modified copy. + + .. attribute:: Parameter.empty + + A special class-level marker to specify absence of default values and + annotations. + + .. attribute:: Parameter.name + + The name of the parameter as a string. Must be a valid python identifier + name (with the exception of ``POSITIONAL_ONLY`` parameters, which can have + it set to ``None``). + + .. attribute:: Parameter.default + + The default value for the parameter. If the parameter has no default + value, this attribute is set to :attr:`Parameter.empty`. + + .. attribute:: Parameter.annotation + + The annotation for the parameter. If the parameter has no annotation, + this attribute is set to :attr:`Parameter.empty`. + + .. attribute:: Parameter.kind + + Describes how argument values are bound to the parameter. Possible values + (accessible via :class:`Parameter`, like ``Parameter.KEYWORD_ONLY``): + + +------------------------+----------------------------------------------+ + | Name | Meaning | + +========================+==============================================+ + | *POSITIONAL_ONLY* | Value must be supplied as a positional | + | | argument. | + | | | + | | Python has no explicit syntax for defining | + | | positional-only parameters, but many built-in| + | | and extension module functions (especially | + | | those that accept only one or two parameters)| + | | accept them. | + +------------------------+----------------------------------------------+ + | *POSITIONAL_OR_KEYWORD*| Value may be supplied as either a keyword or | + | | positional argument (this is the standard | + | | binding behaviour for functions implemented | + | | in Python.) | + +------------------------+----------------------------------------------+ + | *VAR_POSITIONAL* | A tuple of positional arguments that aren't | + | | bound to any other parameter. This | + | | corresponds to a ``*args`` parameter in a | + | | Python function definition. | + +------------------------+----------------------------------------------+ + | *KEYWORD_ONLY* | Value must be supplied as a keyword argument.| + | | Keyword only parameters are those which | + | | appear after a ``*`` or ``*args`` entry in a | + | | Python function definition. | + +------------------------+----------------------------------------------+ + | *VAR_KEYWORD* | A dict of keyword arguments that aren't bound| + | | to any other parameter. This corresponds to a| + | | ``**kwargs`` parameter in a Python function | + | | definition. | + +------------------------+----------------------------------------------+ + + Example: print all keyword-only arguments without default values:: + + >>> def foo(a, b, *, c, d=10): + ... pass + + >>> sig = signature(foo) + >>> for param in sig.parameters.values(): + ... if (param.kind == param.KEYWORD_ONLY and + ... param.default is param.empty): + ... print('Parameter:', param) + Parameter: c + + .. method:: Parameter.replace(*[, name][, kind][, default][, annotation]) + + Create a new Parameter instance based on the instance replaced was invoked + on. To override a :class:`Parameter` attribute, pass the corresponding + argument. To remove a default value or/and an annotation from a + Parameter, pass :attr:`Parameter.empty`. + + :: + + >>> from funcsigs import Parameter + >>> param = Parameter('foo', Parameter.KEYWORD_ONLY, default=42) + >>> str(param) + 'foo=42' + + >>> str(param.replace()) # Will create a shallow copy of 'param' + 'foo=42' + + >>> str(param.replace(default=Parameter.empty, annotation='spam')) + "foo:'spam'" + + +.. class:: BoundArguments + + Result of a :meth:`Signature.bind` or :meth:`Signature.bind_partial` call. + Holds the mapping of arguments to the function's parameters. + + .. attribute:: BoundArguments.arguments + + An ordered, mutable mapping (:class:`collections.OrderedDict`) of + parameters' names to arguments' values. Contains only explicitly bound + arguments. Changes in :attr:`arguments` will reflect in :attr:`args` and + :attr:`kwargs`. + + Should be used in conjunction with :attr:`Signature.parameters` for any + argument processing purposes. + + .. note:: + + Arguments for which :meth:`Signature.bind` or + :meth:`Signature.bind_partial` relied on a default value are skipped. + However, if needed, it is easy to include them. + + :: + + >>> def foo(a, b=10): + ... pass + + >>> sig = signature(foo) + >>> ba = sig.bind(5) + + >>> ba.args, ba.kwargs + ((5,), {}) + + >>> for param in sig.parameters.values(): + ... if param.name not in ba.arguments: + ... ba.arguments[param.name] = param.default + + >>> ba.args, ba.kwargs + ((5, 10), {}) + + + .. attribute:: BoundArguments.args + + A tuple of positional arguments values. Dynamically computed from the + :attr:`arguments` attribute. + + .. attribute:: BoundArguments.kwargs + + A dict of keyword arguments values. Dynamically computed from the + :attr:`arguments` attribute. + + The :attr:`args` and :attr:`kwargs` properties can be used to invoke + functions:: + + def test(a, *, b): + ... + + sig = signature(test) + ba = sig.bind(10, b=20) + test(*ba.args, **ba.kwargs) + + +.. seealso:: + + :pep:`362` - Function Signature Object. + The detailed specification, implementation details and examples. + +Copyright +--------- + +*funcsigs* is a derived work of CPython under the terms of the `PSF License +Agreement`_. The original CPython inspect module, its unit tests and +documentation are the copyright of the Python Software Foundation. The derived +work is distributed under the `Apache License Version 2.0`_. + +.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python +.. _Apache License Version 2.0: http://opensource.org/licenses/Apache-2.0 +.. _GitHub: https://github.com/testing-cabal/funcsigs +.. _PSF License Agreement: http://docs.python.org/3/license.html#terms-and-conditions-for-accessing-or-otherwise-using-python +.. _Travis CI: http://travis-ci.org/ +.. _Read The Docs: http://funcsigs.readthedocs.org/ +.. _PEP 362: http://www.python.org/dev/peps/pep-0362/ +.. _inspect: http://docs.python.org/3/library/inspect.html#introspecting-callables-with-the-signature-object +.. _issues system: https://github.com/testing-cabal/funcsigs/issues + +.. |build_status| image:: https://secure.travis-ci.org/aliles/funcsigs.png?branch=master + :target: http://travis-ci.org/#!/aliles/funcsigs + :alt: Current build status + +.. |coverage| image:: https://coveralls.io/repos/aliles/funcsigs/badge.png?branch=master + :target: https://coveralls.io/r/aliles/funcsigs?branch=master + :alt: Coverage status + +.. |pypi_version| image:: https://pypip.in/v/funcsigs/badge.png + :target: https://crate.io/packages/funcsigs/ + :alt: Latest PyPI version + + diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/docs/Makefile b/testing/web-platform/tests/tools/third_party/funcsigs/docs/Makefile new file mode 100644 index 0000000000..f7ab3d16b4 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR) + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/funcsigs.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/funcsigs.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/funcsigs" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/funcsigs" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/docs/_templates/page.html b/testing/web-platform/tests/tools/third_party/funcsigs/docs/_templates/page.html new file mode 100644 index 0000000000..5e1e00bcaf --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/docs/_templates/page.html @@ -0,0 +1,9 @@ +{% extends "!page.html" %} +{% block extrahead %} + + Fork me on GitHub + + {{ super() }} +{% endblock %} diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/docs/conf.py b/testing/web-platform/tests/tools/third_party/funcsigs/docs/conf.py new file mode 100644 index 0000000000..c6e4194cc0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/docs/conf.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# funcsigs documentation build configuration file, created by +# sphinx-quickstart on Fri Apr 20 20:27:52 2012. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('..')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'funcsigs' +copyright = '2013, Aaron Iles' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +from funcsigs import __version__ +version = '.'.join(__version__.split('.')[:2]) +# The full version, including alpha/beta/rc tags. +release = __version__ + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'agogo' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'funcsigsdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'funcsigs.tex', 'funcsigs Documentation', + 'Aaron Iles', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'funcsigs', 'funcsigs Documentation', + ['Aaron Iles'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'funcsigs', 'funcsigs Documentation', + 'Aaron Iles', 'funcsigs', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'python3': ('http://docs.python.org/py3k', None), + 'python': ('http://docs.python.org/', None) +} diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/funcsigs/__init__.py b/testing/web-platform/tests/tools/third_party/funcsigs/funcsigs/__init__.py new file mode 100644 index 0000000000..5f5378b42a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/funcsigs/__init__.py @@ -0,0 +1,829 @@ +# Copyright 2001-2013 Python Software Foundation; All Rights Reserved +"""Function signature objects for callables + +Back port of Python 3.3's function signature tools from the inspect module, +modified to be compatible with Python 2.6, 2.7 and 3.3+. +""" +from __future__ import absolute_import, division, print_function +import itertools +import functools +import re +import types + +try: + from collections import OrderedDict +except ImportError: + from ordereddict import OrderedDict + +from funcsigs.version import __version__ + +__all__ = ['BoundArguments', 'Parameter', 'Signature', 'signature'] + + +_WrapperDescriptor = type(type.__call__) +_MethodWrapper = type(all.__call__) + +_NonUserDefinedCallables = (_WrapperDescriptor, + _MethodWrapper, + types.BuiltinFunctionType) + + +def formatannotation(annotation, base_module=None): + if isinstance(annotation, type): + if annotation.__module__ in ('builtins', '__builtin__', base_module): + return annotation.__name__ + return annotation.__module__+'.'+annotation.__name__ + return repr(annotation) + + +def _get_user_defined_method(cls, method_name, *nested): + try: + if cls is type: + return + meth = getattr(cls, method_name) + for name in nested: + meth = getattr(meth, name, meth) + except AttributeError: + return + else: + if not isinstance(meth, _NonUserDefinedCallables): + # Once '__signature__' will be added to 'C'-level + # callables, this check won't be necessary + return meth + + +def signature(obj): + '''Get a signature object for the passed callable.''' + + if not callable(obj): + raise TypeError('{0!r} is not a callable object'.format(obj)) + + if isinstance(obj, types.MethodType): + sig = signature(obj.__func__) + if obj.__self__ is None: + # Unbound method - preserve as-is. + return sig + else: + # Bound method. Eat self - if we can. + params = tuple(sig.parameters.values()) + + if not params or params[0].kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + raise ValueError('invalid method signature') + + kind = params[0].kind + if kind in (_POSITIONAL_OR_KEYWORD, _POSITIONAL_ONLY): + # Drop first parameter: + # '(p1, p2[, ...])' -> '(p2[, ...])' + params = params[1:] + else: + if kind is not _VAR_POSITIONAL: + # Unless we add a new parameter type we never + # get here + raise ValueError('invalid argument type') + # It's a var-positional parameter. + # Do nothing. '(*args[, ...])' -> '(*args[, ...])' + + return sig.replace(parameters=params) + + try: + sig = obj.__signature__ + except AttributeError: + pass + else: + if sig is not None: + return sig + + try: + # Was this function wrapped by a decorator? + wrapped = obj.__wrapped__ + except AttributeError: + pass + else: + return signature(wrapped) + + if isinstance(obj, types.FunctionType): + return Signature.from_function(obj) + + if isinstance(obj, functools.partial): + sig = signature(obj.func) + + new_params = OrderedDict(sig.parameters.items()) + + partial_args = obj.args or () + partial_keywords = obj.keywords or {} + try: + ba = sig.bind_partial(*partial_args, **partial_keywords) + except TypeError as ex: + msg = 'partial object {0!r} has incorrect arguments'.format(obj) + raise ValueError(msg) + + for arg_name, arg_value in ba.arguments.items(): + param = new_params[arg_name] + if arg_name in partial_keywords: + # We set a new default value, because the following code + # is correct: + # + # >>> def foo(a): print(a) + # >>> print(partial(partial(foo, a=10), a=20)()) + # 20 + # >>> print(partial(partial(foo, a=10), a=20)(a=30)) + # 30 + # + # So, with 'partial' objects, passing a keyword argument is + # like setting a new default value for the corresponding + # parameter + # + # We also mark this parameter with '_partial_kwarg' + # flag. Later, in '_bind', the 'default' value of this + # parameter will be added to 'kwargs', to simulate + # the 'functools.partial' real call. + new_params[arg_name] = param.replace(default=arg_value, + _partial_kwarg=True) + + elif (param.kind not in (_VAR_KEYWORD, _VAR_POSITIONAL) and + not param._partial_kwarg): + new_params.pop(arg_name) + + return sig.replace(parameters=new_params.values()) + + sig = None + if isinstance(obj, type): + # obj is a class or a metaclass + + # First, let's see if it has an overloaded __call__ defined + # in its metaclass + call = _get_user_defined_method(type(obj), '__call__') + if call is not None: + sig = signature(call) + else: + # Now we check if the 'obj' class has a '__new__' method + new = _get_user_defined_method(obj, '__new__') + if new is not None: + sig = signature(new) + else: + # Finally, we should have at least __init__ implemented + init = _get_user_defined_method(obj, '__init__') + if init is not None: + sig = signature(init) + elif not isinstance(obj, _NonUserDefinedCallables): + # An object with __call__ + # We also check that the 'obj' is not an instance of + # _WrapperDescriptor or _MethodWrapper to avoid + # infinite recursion (and even potential segfault) + call = _get_user_defined_method(type(obj), '__call__', 'im_func') + if call is not None: + sig = signature(call) + + if sig is not None: + # For classes and objects we skip the first parameter of their + # __call__, __new__, or __init__ methods + return sig.replace(parameters=tuple(sig.parameters.values())[1:]) + + if isinstance(obj, types.BuiltinFunctionType): + # Raise a nicer error message for builtins + msg = 'no signature found for builtin function {0!r}'.format(obj) + raise ValueError(msg) + + raise ValueError('callable {0!r} is not supported by signature'.format(obj)) + + +class _void(object): + '''A private marker - used in Parameter & Signature''' + + +class _empty(object): + pass + + +class _ParameterKind(int): + def __new__(self, *args, **kwargs): + obj = int.__new__(self, *args) + obj._name = kwargs['name'] + return obj + + def __str__(self): + return self._name + + def __repr__(self): + return '<_ParameterKind: {0!r}>'.format(self._name) + + +_POSITIONAL_ONLY = _ParameterKind(0, name='POSITIONAL_ONLY') +_POSITIONAL_OR_KEYWORD = _ParameterKind(1, name='POSITIONAL_OR_KEYWORD') +_VAR_POSITIONAL = _ParameterKind(2, name='VAR_POSITIONAL') +_KEYWORD_ONLY = _ParameterKind(3, name='KEYWORD_ONLY') +_VAR_KEYWORD = _ParameterKind(4, name='VAR_KEYWORD') + + +class Parameter(object): + '''Represents a parameter in a function signature. + + Has the following public attributes: + + * name : str + The name of the parameter as a string. + * default : object + The default value for the parameter if specified. If the + parameter has no default value, this attribute is not set. + * annotation + The annotation for the parameter if specified. If the + parameter has no annotation, this attribute is not set. + * kind : str + Describes how argument values are bound to the parameter. + Possible values: `Parameter.POSITIONAL_ONLY`, + `Parameter.POSITIONAL_OR_KEYWORD`, `Parameter.VAR_POSITIONAL`, + `Parameter.KEYWORD_ONLY`, `Parameter.VAR_KEYWORD`. + ''' + + __slots__ = ('_name', '_kind', '_default', '_annotation', '_partial_kwarg') + + POSITIONAL_ONLY = _POSITIONAL_ONLY + POSITIONAL_OR_KEYWORD = _POSITIONAL_OR_KEYWORD + VAR_POSITIONAL = _VAR_POSITIONAL + KEYWORD_ONLY = _KEYWORD_ONLY + VAR_KEYWORD = _VAR_KEYWORD + + empty = _empty + + def __init__(self, name, kind, default=_empty, annotation=_empty, + _partial_kwarg=False): + + if kind not in (_POSITIONAL_ONLY, _POSITIONAL_OR_KEYWORD, + _VAR_POSITIONAL, _KEYWORD_ONLY, _VAR_KEYWORD): + raise ValueError("invalid value for 'Parameter.kind' attribute") + self._kind = kind + + if default is not _empty: + if kind in (_VAR_POSITIONAL, _VAR_KEYWORD): + msg = '{0} parameters cannot have default values'.format(kind) + raise ValueError(msg) + self._default = default + self._annotation = annotation + + if name is None: + if kind != _POSITIONAL_ONLY: + raise ValueError("None is not a valid name for a " + "non-positional-only parameter") + self._name = name + else: + name = str(name) + if kind != _POSITIONAL_ONLY and not re.match(r'[a-z_]\w*$', name, re.I): + msg = '{0!r} is not a valid parameter name'.format(name) + raise ValueError(msg) + self._name = name + + self._partial_kwarg = _partial_kwarg + + @property + def name(self): + return self._name + + @property + def default(self): + return self._default + + @property + def annotation(self): + return self._annotation + + @property + def kind(self): + return self._kind + + def replace(self, name=_void, kind=_void, annotation=_void, + default=_void, _partial_kwarg=_void): + '''Creates a customized copy of the Parameter.''' + + if name is _void: + name = self._name + + if kind is _void: + kind = self._kind + + if annotation is _void: + annotation = self._annotation + + if default is _void: + default = self._default + + if _partial_kwarg is _void: + _partial_kwarg = self._partial_kwarg + + return type(self)(name, kind, default=default, annotation=annotation, + _partial_kwarg=_partial_kwarg) + + def __str__(self): + kind = self.kind + + formatted = self._name + if kind == _POSITIONAL_ONLY: + if formatted is None: + formatted = '' + formatted = '<{0}>'.format(formatted) + + # Add annotation and default value + if self._annotation is not _empty: + formatted = '{0}:{1}'.format(formatted, + formatannotation(self._annotation)) + + if self._default is not _empty: + formatted = '{0}={1}'.format(formatted, repr(self._default)) + + if kind == _VAR_POSITIONAL: + formatted = '*' + formatted + elif kind == _VAR_KEYWORD: + formatted = '**' + formatted + + return formatted + + def __repr__(self): + return '<{0} at {1:#x} {2!r}>'.format(self.__class__.__name__, + id(self), self.name) + + def __hash__(self): + msg = "unhashable type: '{0}'".format(self.__class__.__name__) + raise TypeError(msg) + + def __eq__(self, other): + return (issubclass(other.__class__, Parameter) and + self._name == other._name and + self._kind == other._kind and + self._default == other._default and + self._annotation == other._annotation) + + def __ne__(self, other): + return not self.__eq__(other) + + +class BoundArguments(object): + '''Result of `Signature.bind` call. Holds the mapping of arguments + to the function's parameters. + + Has the following public attributes: + + * arguments : OrderedDict + An ordered mutable mapping of parameters' names to arguments' values. + Does not contain arguments' default values. + * signature : Signature + The Signature object that created this instance. + * args : tuple + Tuple of positional arguments values. + * kwargs : dict + Dict of keyword arguments values. + ''' + + def __init__(self, signature, arguments): + self.arguments = arguments + self._signature = signature + + @property + def signature(self): + return self._signature + + @property + def args(self): + args = [] + for param_name, param in self._signature.parameters.items(): + if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or + param._partial_kwarg): + # Keyword arguments mapped by 'functools.partial' + # (Parameter._partial_kwarg is True) are mapped + # in 'BoundArguments.kwargs', along with VAR_KEYWORD & + # KEYWORD_ONLY + break + + try: + arg = self.arguments[param_name] + except KeyError: + # We're done here. Other arguments + # will be mapped in 'BoundArguments.kwargs' + break + else: + if param.kind == _VAR_POSITIONAL: + # *args + args.extend(arg) + else: + # plain argument + args.append(arg) + + return tuple(args) + + @property + def kwargs(self): + kwargs = {} + kwargs_started = False + for param_name, param in self._signature.parameters.items(): + if not kwargs_started: + if (param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY) or + param._partial_kwarg): + kwargs_started = True + else: + if param_name not in self.arguments: + kwargs_started = True + continue + + if not kwargs_started: + continue + + try: + arg = self.arguments[param_name] + except KeyError: + pass + else: + if param.kind == _VAR_KEYWORD: + # **kwargs + kwargs.update(arg) + else: + # plain keyword argument + kwargs[param_name] = arg + + return kwargs + + def __hash__(self): + msg = "unhashable type: '{0}'".format(self.__class__.__name__) + raise TypeError(msg) + + def __eq__(self, other): + return (issubclass(other.__class__, BoundArguments) and + self.signature == other.signature and + self.arguments == other.arguments) + + def __ne__(self, other): + return not self.__eq__(other) + + +class Signature(object): + '''A Signature object represents the overall signature of a function. + It stores a Parameter object for each parameter accepted by the + function, as well as information specific to the function itself. + + A Signature object has the following public attributes and methods: + + * parameters : OrderedDict + An ordered mapping of parameters' names to the corresponding + Parameter objects (keyword-only arguments are in the same order + as listed in `code.co_varnames`). + * return_annotation : object + The annotation for the return type of the function if specified. + If the function has no annotation for its return type, this + attribute is not set. + * bind(*args, **kwargs) -> BoundArguments + Creates a mapping from positional and keyword arguments to + parameters. + * bind_partial(*args, **kwargs) -> BoundArguments + Creates a partial mapping from positional and keyword arguments + to parameters (simulating 'functools.partial' behavior.) + ''' + + __slots__ = ('_return_annotation', '_parameters') + + _parameter_cls = Parameter + _bound_arguments_cls = BoundArguments + + empty = _empty + + def __init__(self, parameters=None, return_annotation=_empty, + __validate_parameters__=True): + '''Constructs Signature from the given list of Parameter + objects and 'return_annotation'. All arguments are optional. + ''' + + if parameters is None: + params = OrderedDict() + else: + if __validate_parameters__: + params = OrderedDict() + top_kind = _POSITIONAL_ONLY + + for idx, param in enumerate(parameters): + kind = param.kind + if kind < top_kind: + msg = 'wrong parameter order: {0} before {1}' + msg = msg.format(top_kind, param.kind) + raise ValueError(msg) + else: + top_kind = kind + + name = param.name + if name is None: + name = str(idx) + param = param.replace(name=name) + + if name in params: + msg = 'duplicate parameter name: {0!r}'.format(name) + raise ValueError(msg) + params[name] = param + else: + params = OrderedDict(((param.name, param) + for param in parameters)) + + self._parameters = params + self._return_annotation = return_annotation + + @classmethod + def from_function(cls, func): + '''Constructs Signature for the given python function''' + + if not isinstance(func, types.FunctionType): + raise TypeError('{0!r} is not a Python function'.format(func)) + + Parameter = cls._parameter_cls + + # Parameter information. + func_code = func.__code__ + pos_count = func_code.co_argcount + arg_names = func_code.co_varnames + positional = tuple(arg_names[:pos_count]) + keyword_only_count = getattr(func_code, 'co_kwonlyargcount', 0) + keyword_only = arg_names[pos_count:(pos_count + keyword_only_count)] + annotations = getattr(func, '__annotations__', {}) + defaults = func.__defaults__ + kwdefaults = getattr(func, '__kwdefaults__', None) + + if defaults: + pos_default_count = len(defaults) + else: + pos_default_count = 0 + + parameters = [] + + # Non-keyword-only parameters w/o defaults. + non_default_count = pos_count - pos_default_count + for name in positional[:non_default_count]: + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_POSITIONAL_OR_KEYWORD)) + + # ... w/ defaults. + for offset, name in enumerate(positional[non_default_count:]): + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_POSITIONAL_OR_KEYWORD, + default=defaults[offset])) + + # *args + if func_code.co_flags & 0x04: + name = arg_names[pos_count + keyword_only_count] + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_VAR_POSITIONAL)) + + # Keyword-only parameters. + for name in keyword_only: + default = _empty + if kwdefaults is not None: + default = kwdefaults.get(name, _empty) + + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_KEYWORD_ONLY, + default=default)) + # **kwargs + if func_code.co_flags & 0x08: + index = pos_count + keyword_only_count + if func_code.co_flags & 0x04: + index += 1 + + name = arg_names[index] + annotation = annotations.get(name, _empty) + parameters.append(Parameter(name, annotation=annotation, + kind=_VAR_KEYWORD)) + + return cls(parameters, + return_annotation=annotations.get('return', _empty), + __validate_parameters__=False) + + @property + def parameters(self): + try: + return types.MappingProxyType(self._parameters) + except AttributeError: + return OrderedDict(self._parameters.items()) + + @property + def return_annotation(self): + return self._return_annotation + + def replace(self, parameters=_void, return_annotation=_void): + '''Creates a customized copy of the Signature. + Pass 'parameters' and/or 'return_annotation' arguments + to override them in the new copy. + ''' + + if parameters is _void: + parameters = self.parameters.values() + + if return_annotation is _void: + return_annotation = self._return_annotation + + return type(self)(parameters, + return_annotation=return_annotation) + + def __hash__(self): + msg = "unhashable type: '{0}'".format(self.__class__.__name__) + raise TypeError(msg) + + def __eq__(self, other): + if (not issubclass(type(other), Signature) or + self.return_annotation != other.return_annotation or + len(self.parameters) != len(other.parameters)): + return False + + other_positions = dict((param, idx) + for idx, param in enumerate(other.parameters.keys())) + + for idx, (param_name, param) in enumerate(self.parameters.items()): + if param.kind == _KEYWORD_ONLY: + try: + other_param = other.parameters[param_name] + except KeyError: + return False + else: + if param != other_param: + return False + else: + try: + other_idx = other_positions[param_name] + except KeyError: + return False + else: + if (idx != other_idx or + param != other.parameters[param_name]): + return False + + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def _bind(self, args, kwargs, partial=False): + '''Private method. Don't use directly.''' + + arguments = OrderedDict() + + parameters = iter(self.parameters.values()) + parameters_ex = () + arg_vals = iter(args) + + if partial: + # Support for binding arguments to 'functools.partial' objects. + # See 'functools.partial' case in 'signature()' implementation + # for details. + for param_name, param in self.parameters.items(): + if (param._partial_kwarg and param_name not in kwargs): + # Simulating 'functools.partial' behavior + kwargs[param_name] = param.default + + while True: + # Let's iterate through the positional arguments and corresponding + # parameters + try: + arg_val = next(arg_vals) + except StopIteration: + # No more positional arguments + try: + param = next(parameters) + except StopIteration: + # No more parameters. That's it. Just need to check that + # we have no `kwargs` after this while loop + break + else: + if param.kind == _VAR_POSITIONAL: + # That's OK, just empty *args. Let's start parsing + # kwargs + break + elif param.name in kwargs: + if param.kind == _POSITIONAL_ONLY: + msg = '{arg!r} parameter is positional only, ' \ + 'but was passed as a keyword' + msg = msg.format(arg=param.name) + raise TypeError(msg) + parameters_ex = (param,) + break + elif (param.kind == _VAR_KEYWORD or + param.default is not _empty): + # That's fine too - we have a default value for this + # parameter. So, lets start parsing `kwargs`, starting + # with the current parameter + parameters_ex = (param,) + break + else: + if partial: + parameters_ex = (param,) + break + else: + msg = '{arg!r} parameter lacking default value' + msg = msg.format(arg=param.name) + raise TypeError(msg) + else: + # We have a positional argument to process + try: + param = next(parameters) + except StopIteration: + raise TypeError('too many positional arguments') + else: + if param.kind in (_VAR_KEYWORD, _KEYWORD_ONLY): + # Looks like we have no parameter for this positional + # argument + raise TypeError('too many positional arguments') + + if param.kind == _VAR_POSITIONAL: + # We have an '*args'-like argument, let's fill it with + # all positional arguments we have left and move on to + # the next phase + values = [arg_val] + values.extend(arg_vals) + arguments[param.name] = tuple(values) + break + + if param.name in kwargs: + raise TypeError('multiple values for argument ' + '{arg!r}'.format(arg=param.name)) + + arguments[param.name] = arg_val + + # Now, we iterate through the remaining parameters to process + # keyword arguments + kwargs_param = None + for param in itertools.chain(parameters_ex, parameters): + if param.kind == _POSITIONAL_ONLY: + # This should never happen in case of a properly built + # Signature object (but let's have this check here + # to ensure correct behaviour just in case) + raise TypeError('{arg!r} parameter is positional only, ' + 'but was passed as a keyword'. \ + format(arg=param.name)) + + if param.kind == _VAR_KEYWORD: + # Memorize that we have a '**kwargs'-like parameter + kwargs_param = param + continue + + param_name = param.name + try: + arg_val = kwargs.pop(param_name) + except KeyError: + # We have no value for this parameter. It's fine though, + # if it has a default value, or it is an '*args'-like + # parameter, left alone by the processing of positional + # arguments. + if (not partial and param.kind != _VAR_POSITIONAL and + param.default is _empty): + raise TypeError('{arg!r} parameter lacking default value'. \ + format(arg=param_name)) + + else: + arguments[param_name] = arg_val + + if kwargs: + if kwargs_param is not None: + # Process our '**kwargs'-like parameter + arguments[kwargs_param.name] = kwargs + else: + raise TypeError('too many keyword arguments %r' % kwargs) + + return self._bound_arguments_cls(self, arguments) + + def bind(*args, **kwargs): + '''Get a BoundArguments object, that maps the passed `args` + and `kwargs` to the function's signature. Raises `TypeError` + if the passed arguments can not be bound. + ''' + return args[0]._bind(args[1:], kwargs) + + def bind_partial(self, *args, **kwargs): + '''Get a BoundArguments object, that partially maps the + passed `args` and `kwargs` to the function's signature. + Raises `TypeError` if the passed arguments can not be bound. + ''' + return self._bind(args, kwargs, partial=True) + + def __str__(self): + result = [] + render_kw_only_separator = True + for idx, param in enumerate(self.parameters.values()): + formatted = str(param) + + kind = param.kind + if kind == _VAR_POSITIONAL: + # OK, we have an '*args'-like parameter, so we won't need + # a '*' to separate keyword-only arguments + render_kw_only_separator = False + elif kind == _KEYWORD_ONLY and render_kw_only_separator: + # We have a keyword-only parameter to render and we haven't + # rendered an '*args'-like parameter before, so add a '*' + # separator to the parameters list ("foo(arg1, *, arg2)" case) + result.append('*') + # This condition should be only triggered once, so + # reset the flag + render_kw_only_separator = False + + result.append(formatted) + + rendered = '({0})'.format(', '.join(result)) + + if self.return_annotation is not _empty: + anno = formatannotation(self.return_annotation) + rendered += ' -> {0}'.format(anno) + + return rendered diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/funcsigs/version.py b/testing/web-platform/tests/tools/third_party/funcsigs/funcsigs/version.py new file mode 100644 index 0000000000..7863915fa5 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/funcsigs/version.py @@ -0,0 +1 @@ +__version__ = "1.0.2" diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/requirements/development.txt b/testing/web-platform/tests/tools/third_party/funcsigs/requirements/development.txt new file mode 100644 index 0000000000..40dedd92bf --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/requirements/development.txt @@ -0,0 +1,5 @@ +coverage +coveralls +flake8 +sphinx +unittest2 diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/setup.cfg b/testing/web-platform/tests/tools/third_party/funcsigs/setup.cfg new file mode 100644 index 0000000000..5e4090017a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/setup.cfg @@ -0,0 +1,2 @@ +[wheel] +universal = 1 diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/setup.py b/testing/web-platform/tests/tools/third_party/funcsigs/setup.py new file mode 100644 index 0000000000..f3696888f9 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/setup.py @@ -0,0 +1,52 @@ +#!/usr/bin/env python +from setuptools import setup +import re +import sys + +def load_version(filename='funcsigs/version.py'): + "Parse a __version__ number from a source file" + with open(filename) as source: + text = source.read() + match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", text) + if not match: + msg = "Unable to find version number in {}".format(filename) + raise RuntimeError(msg) + version = match.group(1) + return version + + +setup( + name="funcsigs", + version=load_version(), + packages=['funcsigs'], + zip_safe=False, + author="Testing Cabal", + author_email="testing-in-python@lists.idyll.org", + url="http://funcsigs.readthedocs.org", + description="Python function signatures from PEP362 for Python 2.6, 2.7 and 3.2+", + long_description=open('README.rst').read(), + license="ASL", + extras_require = { + ':python_version<"2.7"': ['ordereddict'], + }, + setup_requires = ["setuptools>=17.1"], + classifiers = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Topic :: Software Development :: Libraries :: Python Modules' + ], + tests_require = ['unittest2'], + test_suite = 'unittest2.collector', +) diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/tests/__init__.py b/testing/web-platform/tests/tools/third_party/funcsigs/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/tests/test_formatannotation.py b/testing/web-platform/tests/tools/third_party/funcsigs/tests/test_formatannotation.py new file mode 100644 index 0000000000..4b98e6037d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/tests/test_formatannotation.py @@ -0,0 +1,17 @@ +import funcsigs + +import unittest2 as unittest + +class TestFormatAnnotation(unittest.TestCase): + def test_string (self): + self.assertEqual(funcsigs.formatannotation("annotation"), + "'annotation'") + + def test_builtin_type (self): + self.assertEqual(funcsigs.formatannotation(int), + "int") + + def test_user_type (self): + class dummy (object): pass + self.assertEqual(funcsigs.formatannotation(dummy), + "tests.test_formatannotation.dummy") diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/tests/test_funcsigs.py b/testing/web-platform/tests/tools/third_party/funcsigs/tests/test_funcsigs.py new file mode 100644 index 0000000000..a7b9cca767 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/tests/test_funcsigs.py @@ -0,0 +1,91 @@ +import unittest2 as unittest + +import doctest +import sys + +import funcsigs as inspect + + +class TestFunctionSignatures(unittest.TestCase): + + @staticmethod + def signature(func): + sig = inspect.signature(func) + return (tuple((param.name, + (Ellipsis if param.default is param.empty else param.default), + (Ellipsis if param.annotation is param.empty + else param.annotation), + str(param.kind).lower()) + for param in sig.parameters.values()), + (Ellipsis if sig.return_annotation is sig.empty + else sig.return_annotation)) + + def test_zero_arguments(self): + def test(): + pass + self.assertEqual(self.signature(test), + ((), Ellipsis)) + + def test_single_positional_argument(self): + def test(a): + pass + self.assertEqual(self.signature(test), + (((('a', Ellipsis, Ellipsis, "positional_or_keyword")),), Ellipsis)) + + def test_single_keyword_argument(self): + def test(a=None): + pass + self.assertEqual(self.signature(test), + (((('a', None, Ellipsis, "positional_or_keyword")),), Ellipsis)) + + def test_var_args(self): + def test(*args): + pass + self.assertEqual(self.signature(test), + (((('args', Ellipsis, Ellipsis, "var_positional")),), Ellipsis)) + + def test_keywords_args(self): + def test(**kwargs): + pass + self.assertEqual(self.signature(test), + (((('kwargs', Ellipsis, Ellipsis, "var_keyword")),), Ellipsis)) + + def test_multiple_arguments(self): + def test(a, b=None, *args, **kwargs): + pass + self.assertEqual(self.signature(test), (( + ('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('b', None, Ellipsis, "positional_or_keyword"), + ('args', Ellipsis, Ellipsis, "var_positional"), + ('kwargs', Ellipsis, Ellipsis, "var_keyword"), + ), Ellipsis)) + + def test_has_version(self): + self.assertTrue(inspect.__version__) + + def test_readme(self): + # XXX: This fails but doesn't fail the build. + # (and the syntax isn't valid on all pythons so that seems a little + # hard to get right. + doctest.testfile('../README.rst') + + def test_unbound_method(self): + self_kind = "positional_or_keyword" + class Test(object): + def method(self): + pass + def method_with_args(self, a): + pass + def method_with_varargs(*args): + pass + self.assertEqual( + self.signature(Test.method), + (((('self', Ellipsis, Ellipsis, self_kind)),), Ellipsis)) + self.assertEqual( + self.signature(Test.method_with_args), + ((('self', Ellipsis, Ellipsis, self_kind), + ('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ), Ellipsis)) + self.assertEqual( + self.signature(Test.method_with_varargs), + ((('args', Ellipsis, Ellipsis, "var_positional"),), Ellipsis)) diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/tests/test_inspect.py b/testing/web-platform/tests/tools/third_party/funcsigs/tests/test_inspect.py new file mode 100644 index 0000000000..98d6592fcc --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/tests/test_inspect.py @@ -0,0 +1,1002 @@ +# Copyright 2001-2013 Python Software Foundation; All Rights Reserved +from __future__ import absolute_import, division, print_function +import collections +import functools +import sys + +import unittest2 as unittest + +import funcsigs as inspect + + +class TestSignatureObject(unittest.TestCase): + @staticmethod + def signature(func): + sig = inspect.signature(func) + return (tuple((param.name, + (Ellipsis if param.default is param.empty else param.default), + (Ellipsis if param.annotation is param.empty + else param.annotation), + str(param.kind).lower()) + for param in sig.parameters.values()), + (Ellipsis if sig.return_annotation is sig.empty + else sig.return_annotation)) + + if sys.version_info[0] > 2: + exec(""" +def test_signature_object(self): + S = inspect.Signature + P = inspect.Parameter + + self.assertEqual(str(S()), '()') + + def test(po, pk, *args, ko, **kwargs): + pass + sig = inspect.signature(test) + po = sig.parameters['po'].replace(kind=P.POSITIONAL_ONLY) + pk = sig.parameters['pk'] + args = sig.parameters['args'] + ko = sig.parameters['ko'] + kwargs = sig.parameters['kwargs'] + + S((po, pk, args, ko, kwargs)) + + with self.assertRaisesRegex(ValueError, 'wrong parameter order'): + S((pk, po, args, ko, kwargs)) + + with self.assertRaisesRegex(ValueError, 'wrong parameter order'): + S((po, args, pk, ko, kwargs)) + + with self.assertRaisesRegex(ValueError, 'wrong parameter order'): + S((args, po, pk, ko, kwargs)) + + with self.assertRaisesRegex(ValueError, 'wrong parameter order'): + S((po, pk, args, kwargs, ko)) + + kwargs2 = kwargs.replace(name='args') + with self.assertRaisesRegex(ValueError, 'duplicate parameter name'): + S((po, pk, args, kwargs2, ko)) +""") + + def test_signature_immutability(self): + def test(a): + pass + sig = inspect.signature(test) + + with self.assertRaises(AttributeError): + sig.foo = 'bar' + + # Python2 does not have MappingProxyType class + if sys.version_info[:2] < (3, 3): + return + + with self.assertRaises(TypeError): + sig.parameters['a'] = None + + def test_signature_on_noarg(self): + def test(): + pass + self.assertEqual(self.signature(test), ((), Ellipsis)) + + if sys.version_info[0] > 2: + exec(""" +def test_signature_on_wargs(self): + def test(a, b:'foo') -> 123: + pass + self.assertEqual(self.signature(test), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('b', Ellipsis, 'foo', "positional_or_keyword")), + 123)) +""") + + if sys.version_info[0] > 2: + exec(""" +def test_signature_on_wkwonly(self): + def test(*, a:float, b:str) -> int: + pass + self.assertEqual(self.signature(test), + ((('a', Ellipsis, float, "keyword_only"), + ('b', Ellipsis, str, "keyword_only")), + int)) +""") + + if sys.version_info[0] > 2: + exec(""" +def test_signature_on_complex_args(self): + def test(a, b:'foo'=10, *args:'bar', spam:'baz', ham=123, **kwargs:int): + pass + self.assertEqual(self.signature(test), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('b', 10, 'foo', "positional_or_keyword"), + ('args', Ellipsis, 'bar', "var_positional"), + ('spam', Ellipsis, 'baz', "keyword_only"), + ('ham', 123, Ellipsis, "keyword_only"), + ('kwargs', Ellipsis, int, "var_keyword")), + Ellipsis)) +""") + + def test_signature_on_builtin_function(self): + with self.assertRaisesRegex(ValueError, 'not supported by signature'): + inspect.signature(type) + with self.assertRaisesRegex(ValueError, 'not supported by signature'): + # support for 'wrapper_descriptor' + inspect.signature(type.__call__) + if hasattr(sys, 'pypy_version_info'): + raise ValueError('not supported by signature') + with self.assertRaisesRegex(ValueError, 'not supported by signature'): + # support for 'method-wrapper' + inspect.signature(min.__call__) + if hasattr(sys, 'pypy_version_info'): + raise ValueError('not supported by signature') + with self.assertRaisesRegex(ValueError, + 'no signature found for builtin function'): + # support for 'method-wrapper' + inspect.signature(min) + + def test_signature_on_non_function(self): + with self.assertRaisesRegex(TypeError, 'is not a callable object'): + inspect.signature(42) + + with self.assertRaisesRegex(TypeError, 'is not a Python function'): + inspect.Signature.from_function(42) + + if sys.version_info[0] > 2: + exec(""" +def test_signature_on_method(self): + class Test: + def foo(self, arg1, arg2=1) -> int: + pass + + meth = Test().foo + + self.assertEqual(self.signature(meth), + ((('arg1', Ellipsis, Ellipsis, "positional_or_keyword"), + ('arg2', 1, Ellipsis, "positional_or_keyword")), + int)) +""") + + if sys.version_info[0] > 2: + exec(""" +def test_signature_on_classmethod(self): + class Test: + @classmethod + def foo(cls, arg1, *, arg2=1): + pass + + meth = Test().foo + self.assertEqual(self.signature(meth), + ((('arg1', Ellipsis, Ellipsis, "positional_or_keyword"), + ('arg2', 1, Ellipsis, "keyword_only")), + Ellipsis)) + + meth = Test.foo + self.assertEqual(self.signature(meth), + ((('arg1', Ellipsis, Ellipsis, "positional_or_keyword"), + ('arg2', 1, Ellipsis, "keyword_only")), + Ellipsis)) +""") + + if sys.version_info[0] > 2: + exec(""" +def test_signature_on_staticmethod(self): + class Test: + @staticmethod + def foo(cls, *, arg): + pass + + meth = Test().foo + self.assertEqual(self.signature(meth), + ((('cls', Ellipsis, Ellipsis, "positional_or_keyword"), + ('arg', Ellipsis, Ellipsis, "keyword_only")), + Ellipsis)) + + meth = Test.foo + self.assertEqual(self.signature(meth), + ((('cls', Ellipsis, Ellipsis, "positional_or_keyword"), + ('arg', Ellipsis, Ellipsis, "keyword_only")), + Ellipsis)) +""") + + if sys.version_info[0] > 2: + exec(""" +def test_signature_on_partial(self): + from functools import partial + + def test(): + pass + + self.assertEqual(self.signature(partial(test)), ((), Ellipsis)) + + with self.assertRaisesRegex(ValueError, "has incorrect arguments"): + inspect.signature(partial(test, 1)) + + with self.assertRaisesRegex(ValueError, "has incorrect arguments"): + inspect.signature(partial(test, a=1)) + + def test(a, b, *, c, d): + pass + + self.assertEqual(self.signature(partial(test)), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('b', Ellipsis, Ellipsis, "positional_or_keyword"), + ('c', Ellipsis, Ellipsis, "keyword_only"), + ('d', Ellipsis, Ellipsis, "keyword_only")), + Ellipsis)) + + self.assertEqual(self.signature(partial(test, 1)), + ((('b', Ellipsis, Ellipsis, "positional_or_keyword"), + ('c', Ellipsis, Ellipsis, "keyword_only"), + ('d', Ellipsis, Ellipsis, "keyword_only")), + Ellipsis)) + + self.assertEqual(self.signature(partial(test, 1, c=2)), + ((('b', Ellipsis, Ellipsis, "positional_or_keyword"), + ('c', 2, Ellipsis, "keyword_only"), + ('d', Ellipsis, Ellipsis, "keyword_only")), + Ellipsis)) + + self.assertEqual(self.signature(partial(test, b=1, c=2)), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('b', 1, Ellipsis, "positional_or_keyword"), + ('c', 2, Ellipsis, "keyword_only"), + ('d', Ellipsis, Ellipsis, "keyword_only")), + Ellipsis)) + + self.assertEqual(self.signature(partial(test, 0, b=1, c=2)), + ((('b', 1, Ellipsis, "positional_or_keyword"), + ('c', 2, Ellipsis, "keyword_only"), + ('d', Ellipsis, Ellipsis, "keyword_only"),), + Ellipsis)) + + def test(a, *args, b, **kwargs): + pass + + self.assertEqual(self.signature(partial(test, 1)), + ((('args', Ellipsis, Ellipsis, "var_positional"), + ('b', Ellipsis, Ellipsis, "keyword_only"), + ('kwargs', Ellipsis, Ellipsis, "var_keyword")), + Ellipsis)) + + self.assertEqual(self.signature(partial(test, 1, 2, 3)), + ((('args', Ellipsis, Ellipsis, "var_positional"), + ('b', Ellipsis, Ellipsis, "keyword_only"), + ('kwargs', Ellipsis, Ellipsis, "var_keyword")), + Ellipsis)) + + + self.assertEqual(self.signature(partial(test, 1, 2, 3, test=True)), + ((('args', Ellipsis, Ellipsis, "var_positional"), + ('b', Ellipsis, Ellipsis, "keyword_only"), + ('kwargs', Ellipsis, Ellipsis, "var_keyword")), + Ellipsis)) + + self.assertEqual(self.signature(partial(test, 1, 2, 3, test=1, b=0)), + ((('args', Ellipsis, Ellipsis, "var_positional"), + ('b', 0, Ellipsis, "keyword_only"), + ('kwargs', Ellipsis, Ellipsis, "var_keyword")), + Ellipsis)) + + self.assertEqual(self.signature(partial(test, b=0)), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('args', Ellipsis, Ellipsis, "var_positional"), + ('b', 0, Ellipsis, "keyword_only"), + ('kwargs', Ellipsis, Ellipsis, "var_keyword")), + Ellipsis)) + + self.assertEqual(self.signature(partial(test, b=0, test=1)), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('args', Ellipsis, Ellipsis, "var_positional"), + ('b', 0, Ellipsis, "keyword_only"), + ('kwargs', Ellipsis, Ellipsis, "var_keyword")), + Ellipsis)) + + def test(a, b, c:int) -> 42: + pass + + sig = test.__signature__ = inspect.signature(test) + + self.assertEqual(self.signature(partial(partial(test, 1))), + ((('b', Ellipsis, Ellipsis, "positional_or_keyword"), + ('c', Ellipsis, int, "positional_or_keyword")), + 42)) + + self.assertEqual(self.signature(partial(partial(test, 1), 2)), + ((('c', Ellipsis, int, "positional_or_keyword"),), + 42)) + + psig = inspect.signature(partial(partial(test, 1), 2)) + + def foo(a): + return a + _foo = partial(partial(foo, a=10), a=20) + self.assertEqual(self.signature(_foo), + ((('a', 20, Ellipsis, "positional_or_keyword"),), + Ellipsis)) + # check that we don't have any side-effects in signature(), + # and the partial object is still functioning + self.assertEqual(_foo(), 20) + + def foo(a, b, c): + return a, b, c + _foo = partial(partial(foo, 1, b=20), b=30) + self.assertEqual(self.signature(_foo), + ((('b', 30, Ellipsis, "positional_or_keyword"), + ('c', Ellipsis, Ellipsis, "positional_or_keyword")), + Ellipsis)) + self.assertEqual(_foo(c=10), (1, 30, 10)) + _foo = partial(_foo, 2) # now 'b' has two values - + # positional and keyword + with self.assertRaisesRegex(ValueError, "has incorrect arguments"): + inspect.signature(_foo) + + def foo(a, b, c, *, d): + return a, b, c, d + _foo = partial(partial(foo, d=20, c=20), b=10, d=30) + self.assertEqual(self.signature(_foo), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('b', 10, Ellipsis, "positional_or_keyword"), + ('c', 20, Ellipsis, "positional_or_keyword"), + ('d', 30, Ellipsis, "keyword_only")), + Ellipsis)) + ba = inspect.signature(_foo).bind(a=200, b=11) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (200, 11, 20, 30)) + + def foo(a=1, b=2, c=3): + return a, b, c + _foo = partial(foo, a=10, c=13) + ba = inspect.signature(_foo).bind(11) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 2, 13)) + ba = inspect.signature(_foo).bind(11, 12) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13)) + ba = inspect.signature(_foo).bind(11, b=12) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13)) + ba = inspect.signature(_foo).bind(b=12) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (10, 12, 13)) + _foo = partial(_foo, b=10) + ba = inspect.signature(_foo).bind(12, 14) + self.assertEqual(_foo(*ba.args, **ba.kwargs), (12, 14, 13)) +""") + + if sys.version_info[0] > 2: + exec(""" +def test_signature_on_decorated(self): + import functools + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs) -> int: + return func(*args, **kwargs) + return wrapper + + class Foo: + @decorator + def bar(self, a, b): + pass + + self.assertEqual(self.signature(Foo.bar), + ((('self', Ellipsis, Ellipsis, "positional_or_keyword"), + ('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('b', Ellipsis, Ellipsis, "positional_or_keyword")), + Ellipsis)) + + self.assertEqual(self.signature(Foo().bar), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('b', Ellipsis, Ellipsis, "positional_or_keyword")), + Ellipsis)) + + # Test that we handle method wrappers correctly + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs) -> int: + return func(42, *args, **kwargs) + sig = inspect.signature(func) + new_params = tuple(sig.parameters.values())[1:] + wrapper.__signature__ = sig.replace(parameters=new_params) + return wrapper + + class Foo: + @decorator + def __call__(self, a, b): + pass + + self.assertEqual(self.signature(Foo.__call__), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"), + ('b', Ellipsis, Ellipsis, "positional_or_keyword")), + Ellipsis)) + + self.assertEqual(self.signature(Foo().__call__), + ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),), + Ellipsis)) +""") + + if sys.version_info[0] > 2: + exec(""" +def test_signature_on_class(self): + class C: + def __init__(self, a): + pass + + self.assertEqual(self.signature(C), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),), + Ellipsis)) + + class CM(type): + def __call__(cls, a): + pass + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(C), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),), + Ellipsis)) + + class CM(type): + def __new__(mcls, name, bases, dct, *, foo=1): + return super().__new__(mcls, name, bases, dct) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(C), + ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),), + Ellipsis)) + + self.assertEqual(self.signature(CM), + ((('name', Ellipsis, Ellipsis, "positional_or_keyword"), + ('bases', Ellipsis, Ellipsis, "positional_or_keyword"), + ('dct', Ellipsis, Ellipsis, "positional_or_keyword"), + ('foo', 1, Ellipsis, "keyword_only")), + Ellipsis)) + + class CMM(type): + def __new__(mcls, name, bases, dct, *, foo=1): + return super().__new__(mcls, name, bases, dct) + def __call__(cls, nm, bs, dt): + return type(nm, bs, dt) + class CM(type, metaclass=CMM): + def __new__(mcls, name, bases, dct, *, bar=2): + return super().__new__(mcls, name, bases, dct) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(CMM), + ((('name', Ellipsis, Ellipsis, "positional_or_keyword"), + ('bases', Ellipsis, Ellipsis, "positional_or_keyword"), + ('dct', Ellipsis, Ellipsis, "positional_or_keyword"), + ('foo', 1, Ellipsis, "keyword_only")), + Ellipsis)) + + self.assertEqual(self.signature(CM), + ((('nm', Ellipsis, Ellipsis, "positional_or_keyword"), + ('bs', Ellipsis, Ellipsis, "positional_or_keyword"), + ('dt', Ellipsis, Ellipsis, "positional_or_keyword")), + Ellipsis)) + + self.assertEqual(self.signature(C), + ((('b', Ellipsis, Ellipsis, "positional_or_keyword"),), + Ellipsis)) + + class CM(type): + def __init__(cls, name, bases, dct, *, bar=2): + return super().__init__(name, bases, dct) + class C(metaclass=CM): + def __init__(self, b): + pass + + self.assertEqual(self.signature(CM), + ((('name', Ellipsis, Ellipsis, "positional_or_keyword"), + ('bases', Ellipsis, Ellipsis, "positional_or_keyword"), + ('dct', Ellipsis, Ellipsis, "positional_or_keyword"), + ('bar', 2, Ellipsis, "keyword_only")), + Ellipsis)) +""") + + def test_signature_on_callable_objects(self): + class Foo(object): + def __call__(self, a): + pass + + self.assertEqual(self.signature(Foo()), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),), + Ellipsis)) + + class Spam(object): + pass + with self.assertRaisesRegex(TypeError, "is not a callable object"): + inspect.signature(Spam()) + + class Bar(Spam, Foo): + pass + + self.assertEqual(self.signature(Bar()), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),), + Ellipsis)) + + class ToFail(object): + __call__ = type + with self.assertRaisesRegex(ValueError, "not supported by signature"): + inspect.signature(ToFail()) + + if sys.version_info[0] < 3: + return + + class Wrapped(object): + pass + Wrapped.__wrapped__ = lambda a: None + self.assertEqual(self.signature(Wrapped), + ((('a', Ellipsis, Ellipsis, "positional_or_keyword"),), + Ellipsis)) + + def test_signature_on_lambdas(self): + self.assertEqual(self.signature((lambda a=10: a)), + ((('a', 10, Ellipsis, "positional_or_keyword"),), + Ellipsis)) + + if sys.version_info[0] > 2: + exec(""" +def test_signature_equality(self): + def foo(a, *, b:int) -> float: pass + self.assertNotEqual(inspect.signature(foo), 42) + + def bar(a, *, b:int) -> float: pass + self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + + def bar(a, *, b:int) -> int: pass + self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + + def bar(a, *, b:int): pass + self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + + def bar(a, *, b:int=42) -> float: pass + self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + + def bar(a, *, c) -> float: pass + self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + + def bar(a, b:int) -> float: pass + self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + def spam(b:int, a) -> float: pass + self.assertNotEqual(inspect.signature(spam), inspect.signature(bar)) + + def foo(*, a, b, c): pass + def bar(*, c, b, a): pass + self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + + def foo(*, a=1, b, c): pass + def bar(*, c, b, a=1): pass + self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + + def foo(pos, *, a=1, b, c): pass + def bar(pos, *, c, b, a=1): pass + self.assertEqual(inspect.signature(foo), inspect.signature(bar)) + + def foo(pos, *, a, b, c): pass + def bar(pos, *, c, b, a=1): pass + self.assertNotEqual(inspect.signature(foo), inspect.signature(bar)) + + def foo(pos, *args, a=42, b, c, **kwargs:int): pass + def bar(pos, *args, c, b, a=42, **kwargs:int): pass + self.assertEqual(inspect.signature(foo), inspect.signature(bar)) +""") + + def test_signature_unhashable(self): + def foo(a): pass + sig = inspect.signature(foo) + with self.assertRaisesRegex(TypeError, 'unhashable type'): + hash(sig) + + + if sys.version_info[0] > 2: + exec(""" +def test_signature_str(self): + def foo(a:int=1, *, b, c=None, **kwargs) -> 42: + pass + self.assertEqual(str(inspect.signature(foo)), + '(a:int=1, *, b, c=None, **kwargs) -> 42') + + def foo(a:int=1, *args, b, c=None, **kwargs) -> 42: + pass + self.assertEqual(str(inspect.signature(foo)), + '(a:int=1, *args, b, c=None, **kwargs) -> 42') + + def foo(): + pass + self.assertEqual(str(inspect.signature(foo)), '()') +""") + + if sys.version_info[0] > 2: + exec(""" +def test_signature_str_positional_only(self): + P = inspect.Parameter + + def test(a_po, *, b, **kwargs): + return a_po, kwargs + + sig = inspect.signature(test) + new_params = list(sig.parameters.values()) + new_params[0] = new_params[0].replace(kind=P.POSITIONAL_ONLY) + test.__signature__ = sig.replace(parameters=new_params) + + self.assertEqual(str(inspect.signature(test)), + '(, *, b, **kwargs)') + + sig = inspect.signature(test) + new_params = list(sig.parameters.values()) + new_params[0] = new_params[0].replace(name=None) + test.__signature__ = sig.replace(parameters=new_params) + self.assertEqual(str(inspect.signature(test)), + '(<0>, *, b, **kwargs)') +""") + + if sys.version_info[0] > 2: + exec(""" +def test_signature_replace_anno(self): + def test() -> 42: + pass + + sig = inspect.signature(test) + sig = sig.replace(return_annotation=None) + self.assertIs(sig.return_annotation, None) + sig = sig.replace(return_annotation=sig.empty) + self.assertIs(sig.return_annotation, sig.empty) + sig = sig.replace(return_annotation=42) + self.assertEqual(sig.return_annotation, 42) + self.assertEqual(sig, inspect.signature(test)) +""") + + +class TestParameterObject(unittest.TestCase): + + def test_signature_parameter_kinds(self): + P = inspect.Parameter + self.assertTrue(P.POSITIONAL_ONLY < P.POSITIONAL_OR_KEYWORD < \ + P.VAR_POSITIONAL < P.KEYWORD_ONLY < P.VAR_KEYWORD) + + self.assertEqual(str(P.POSITIONAL_ONLY), 'POSITIONAL_ONLY') + self.assertTrue('POSITIONAL_ONLY' in repr(P.POSITIONAL_ONLY)) + + def test_signature_parameter_object(self): + p = inspect.Parameter('foo', default=10, + kind=inspect.Parameter.POSITIONAL_ONLY) + self.assertEqual(p.name, 'foo') + self.assertEqual(p.default, 10) + self.assertIs(p.annotation, p.empty) + self.assertEqual(p.kind, inspect.Parameter.POSITIONAL_ONLY) + + with self.assertRaisesRegex(ValueError, 'invalid value'): + inspect.Parameter('foo', default=10, kind='123') + + with self.assertRaisesRegex(ValueError, 'not a valid parameter name'): + inspect.Parameter('1', kind=inspect.Parameter.VAR_KEYWORD) + + with self.assertRaisesRegex(ValueError, + 'non-positional-only parameter'): + inspect.Parameter(None, kind=inspect.Parameter.VAR_KEYWORD) + + with self.assertRaisesRegex(ValueError, 'cannot have default values'): + inspect.Parameter('a', default=42, + kind=inspect.Parameter.VAR_KEYWORD) + + with self.assertRaisesRegex(ValueError, 'cannot have default values'): + inspect.Parameter('a', default=42, + kind=inspect.Parameter.VAR_POSITIONAL) + + p = inspect.Parameter('a', default=42, + kind=inspect.Parameter.POSITIONAL_OR_KEYWORD) + with self.assertRaisesRegex(ValueError, 'cannot have default values'): + p.replace(kind=inspect.Parameter.VAR_POSITIONAL) + + self.assertTrue(repr(p).startswith('') + + p = p.replace(name='1') + self.assertEqual(str(p), '<1>') + + def test_signature_parameter_immutability(self): + p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY) + + with self.assertRaises(AttributeError): + p.foo = 'bar' + + with self.assertRaises(AttributeError): + p.kind = 123 + + +class TestSignatureBind(unittest.TestCase): + @staticmethod + def call(func, *args, **kwargs): + sig = inspect.signature(func) + ba = sig.bind(*args, **kwargs) + return func(*ba.args, **ba.kwargs) + + def test_signature_bind_empty(self): + def test(): + return 42 + + self.assertEqual(self.call(test), 42) + with self.assertRaisesRegex(TypeError, 'too many positional arguments'): + self.call(test, 1) + with self.assertRaisesRegex(TypeError, 'too many positional arguments'): + self.call(test, 1, spam=10) + with self.assertRaisesRegex(TypeError, 'too many keyword arguments'): + self.call(test, spam=1) + + def test_signature_bind_var(self): + def test(*args, **kwargs): + return args, kwargs + + self.assertEqual(self.call(test), ((), {})) + self.assertEqual(self.call(test, 1), ((1,), {})) + self.assertEqual(self.call(test, 1, 2), ((1, 2), {})) + self.assertEqual(self.call(test, foo='bar'), ((), {'foo': 'bar'})) + self.assertEqual(self.call(test, 1, foo='bar'), ((1,), {'foo': 'bar'})) + self.assertEqual(self.call(test, args=10), ((), {'args': 10})) + self.assertEqual(self.call(test, 1, 2, foo='bar'), + ((1, 2), {'foo': 'bar'})) + + def test_signature_bind_just_args(self): + def test(a, b, c): + return a, b, c + + self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3)) + + with self.assertRaisesRegex(TypeError, 'too many positional arguments'): + self.call(test, 1, 2, 3, 4) + + with self.assertRaisesRegex(TypeError, "'b' parameter lacking default"): + self.call(test, 1) + + with self.assertRaisesRegex(TypeError, "'a' parameter lacking default"): + self.call(test) + + def test(a, b, c=10): + return a, b, c + self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3)) + self.assertEqual(self.call(test, 1, 2), (1, 2, 10)) + + def test(a=1, b=2, c=3): + return a, b, c + self.assertEqual(self.call(test, a=10, c=13), (10, 2, 13)) + self.assertEqual(self.call(test, a=10), (10, 2, 3)) + self.assertEqual(self.call(test, b=10), (1, 10, 3)) + + def test_signature_bind_varargs_order(self): + def test(*args): + return args + + self.assertEqual(self.call(test), ()) + self.assertEqual(self.call(test, 1, 2, 3), (1, 2, 3)) + + def test_signature_bind_args_and_varargs(self): + def test(a, b, c=3, *args): + return a, b, c, args + + self.assertEqual(self.call(test, 1, 2, 3, 4, 5), (1, 2, 3, (4, 5))) + self.assertEqual(self.call(test, 1, 2), (1, 2, 3, ())) + self.assertEqual(self.call(test, b=1, a=2), (2, 1, 3, ())) + self.assertEqual(self.call(test, 1, b=2), (1, 2, 3, ())) + + with self.assertRaisesRegex(TypeError, + "multiple values for argument 'c'"): + self.call(test, 1, 2, 3, c=4) + + def test_signature_bind_just_kwargs(self): + def test(**kwargs): + return kwargs + + self.assertEqual(self.call(test), {}) + self.assertEqual(self.call(test, foo='bar', spam='ham'), + {'foo': 'bar', 'spam': 'ham'}) + + def test_signature_bind_args_and_kwargs(self): + def test(a, b, c=3, **kwargs): + return a, b, c, kwargs + + self.assertEqual(self.call(test, 1, 2), (1, 2, 3, {})) + self.assertEqual(self.call(test, 1, 2, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, b=2, a=1, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, a=1, b=2, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, 1, b=2, foo='bar', spam='ham'), + (1, 2, 3, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, 1, b=2, c=4, foo='bar', spam='ham'), + (1, 2, 4, {'foo': 'bar', 'spam': 'ham'})) + self.assertEqual(self.call(test, 1, 2, 4, foo='bar'), + (1, 2, 4, {'foo': 'bar'})) + self.assertEqual(self.call(test, c=5, a=4, b=3), + (4, 3, 5, {})) + + if sys.version_info[0] > 2: + exec(""" +def test_signature_bind_kwonly(self): + def test(*, foo): + return foo + with self.assertRaisesRegex(TypeError, + 'too many positional arguments'): + self.call(test, 1) + self.assertEqual(self.call(test, foo=1), 1) + + def test(a, *, foo=1, bar): + return foo + with self.assertRaisesRegex(TypeError, + "'bar' parameter lacking default value"): + self.call(test, 1) + + def test(foo, *, bar): + return foo, bar + self.assertEqual(self.call(test, 1, bar=2), (1, 2)) + self.assertEqual(self.call(test, bar=2, foo=1), (1, 2)) + + with self.assertRaisesRegex(TypeError, + 'too many keyword arguments'): + self.call(test, bar=2, foo=1, spam=10) + + with self.assertRaisesRegex(TypeError, + 'too many positional arguments'): + self.call(test, 1, 2) + + with self.assertRaisesRegex(TypeError, + 'too many positional arguments'): + self.call(test, 1, 2, bar=2) + + with self.assertRaisesRegex(TypeError, + 'too many keyword arguments'): + self.call(test, 1, bar=2, spam='ham') + + with self.assertRaisesRegex(TypeError, + "'bar' parameter lacking default value"): + self.call(test, 1) + + def test(foo, *, bar, **bin): + return foo, bar, bin + self.assertEqual(self.call(test, 1, bar=2), (1, 2, {})) + self.assertEqual(self.call(test, foo=1, bar=2), (1, 2, {})) + self.assertEqual(self.call(test, 1, bar=2, spam='ham'), + (1, 2, {'spam': 'ham'})) + self.assertEqual(self.call(test, spam='ham', foo=1, bar=2), + (1, 2, {'spam': 'ham'})) + with self.assertRaisesRegex(TypeError, + "'foo' parameter lacking default value"): + self.call(test, spam='ham', bar=2) + self.assertEqual(self.call(test, 1, bar=2, bin=1, spam=10), + (1, 2, {'bin': 1, 'spam': 10})) +""") +# + if sys.version_info[0] > 2: + exec(""" +def test_signature_bind_arguments(self): + def test(a, *args, b, z=100, **kwargs): + pass + sig = inspect.signature(test) + ba = sig.bind(10, 20, b=30, c=40, args=50, kwargs=60) + # we won't have 'z' argument in the bound arguments object, as we didn't + # pass it to the 'bind' + self.assertEqual(tuple(ba.arguments.items()), + (('a', 10), ('args', (20,)), ('b', 30), + ('kwargs', {'c': 40, 'args': 50, 'kwargs': 60}))) + self.assertEqual(ba.kwargs, + {'b': 30, 'c': 40, 'args': 50, 'kwargs': 60}) + self.assertEqual(ba.args, (10, 20)) +""") +# + if sys.version_info[0] > 2: + exec(""" +def test_signature_bind_positional_only(self): + P = inspect.Parameter + + def test(a_po, b_po, c_po=3, foo=42, *, bar=50, **kwargs): + return a_po, b_po, c_po, foo, bar, kwargs + + sig = inspect.signature(test) + new_params = collections.OrderedDict(tuple(sig.parameters.items())) + for name in ('a_po', 'b_po', 'c_po'): + new_params[name] = new_params[name].replace(kind=P.POSITIONAL_ONLY) + new_sig = sig.replace(parameters=new_params.values()) + test.__signature__ = new_sig + + self.assertEqual(self.call(test, 1, 2, 4, 5, bar=6), + (1, 2, 4, 5, 6, {})) + + with self.assertRaisesRegex(TypeError, "parameter is positional only"): + self.call(test, 1, 2, c_po=4) + + with self.assertRaisesRegex(TypeError, "parameter is positional only"): + self.call(test, a_po=1, b_po=2) +""") + + def test_bind_self(self): + class F: + def f(a, self): + return a, self + an_f = F() + partial_f = functools.partial(F.f, an_f) + ba = inspect.signature(partial_f).bind(self=10) + self.assertEqual((an_f, 10), partial_f(*ba.args, **ba.kwargs)) + + +class TestBoundArguments(unittest.TestCase): + + def test_signature_bound_arguments_unhashable(self): + def foo(a): pass + ba = inspect.signature(foo).bind(1) + + with self.assertRaisesRegex(TypeError, 'unhashable type'): + hash(ba) + + def test_signature_bound_arguments_equality(self): + def foo(a): pass + ba = inspect.signature(foo).bind(1) + self.assertEqual(ba, ba) + + ba2 = inspect.signature(foo).bind(1) + self.assertEqual(ba, ba2) + + ba3 = inspect.signature(foo).bind(2) + self.assertNotEqual(ba, ba3) + ba3.arguments['a'] = 1 + self.assertEqual(ba, ba3) + + def bar(b): pass + ba4 = inspect.signature(bar).bind(1) + self.assertNotEqual(ba, ba4) diff --git a/testing/web-platform/tests/tools/third_party/funcsigs/tox.ini b/testing/web-platform/tests/tools/third_party/funcsigs/tox.ini new file mode 100644 index 0000000000..1873c744a0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/funcsigs/tox.ini @@ -0,0 +1,8 @@ +[tox] +envlist = py26, py27, py33, py34, py35, py36, pypy, pypy3 + +[testenv] +deps = -rrequirements/development.txt +commands = + coverage run setup.py test + coverage report --show-missing diff --git a/testing/web-platform/tests/tools/third_party/h2/.coveragerc b/testing/web-platform/tests/tools/third_party/h2/.coveragerc new file mode 100644 index 0000000000..153e38d3e0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/.coveragerc @@ -0,0 +1,18 @@ +[run] +branch = True +source = h2 + +[report] +fail_under = 100 +show_missing = True +exclude_lines = + pragma: no cover + .*:.* # Python \d.* + assert False, "Should not be reachable" + .*:.* # Platform-specific: + +[paths] +source = + h2/ + .tox/*/lib/python*/site-packages/h2 + .tox/pypy*/site-packages/h2 diff --git a/testing/web-platform/tests/tools/third_party/h2/CONTRIBUTORS.rst b/testing/web-platform/tests/tools/third_party/h2/CONTRIBUTORS.rst new file mode 100644 index 0000000000..5c4849fef0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/CONTRIBUTORS.rst @@ -0,0 +1,115 @@ +Hyper-h2 is written and maintained by Cory Benfield and various contributors: + +Development Lead +```````````````` + +- Cory Benfield + +Contributors +```````````` + +In chronological order: + +- Robert Collins (@rbtcollins) + + - Provided invaluable and substantial early input into API design and layout. + - Added code preventing ``Proxy-Authorization`` from getting added to HPACK + compression contexts. + +- Maximilian Hils (@maximilianhils) + + - Added asyncio example. + +- Alex Chan (@alexwlchan) + + - Fixed docstring, added URLs to README. + +- Glyph Lefkowitz (@glyph) + + - Improved example Twisted server. + +- Thomas Kriechbaumer (@Kriechi) + + - Fixed incorrect arguments being passed to ``StreamIDTooLowError``. + - Added new arguments to ``close_connection``. + +- WeiZheng Xu (@boyxuper) + + - Reported a bug relating to hyper-h2's updating of the connection window in + response to SETTINGS_INITIAL_WINDOW_SIZE. + +- Evgeny Tataurov (@etataurov) + + - Added the ``additional_data`` field to the ``ConnectionTerminated`` event. + +- Brett Cannon (@brettcannon) + + - Changed Travis status icon to SVG. + - Documentation improvements. + +- Felix Yan (@felixonmars) + + - Widened allowed version numbers of enum34. + - Updated test requirements. + +- Keith Dart (@kdart) + + - Fixed curio example server flow control problems. + +- Gil Gonçalves (@LuRsT) + + - Added code forbidding non-RFC 7540 pseudo-headers. + +- Louis Taylor (@kragniz) + + - Cleaned up the README + +- Berker Peksag (@berkerpeksag) + + - Improved the docstring for ``StreamIDTooLowError``. + +- Adrian Lewis (@aidylewis) + + - Fixed the broken Twisted HEAD request example. + - Added verification logic for ensuring that responses to HEAD requests have + no body. + +- Lorenzo (@Mec-iS) + + - Changed documentation to stop using dictionaries for header blocks. + +- Kracekumar Ramaraj (@kracekumar) + + - Cleaned up Twisted example. + +- @mlvnd + + - Cleaned up curio example. + +- Tom Offermann (@toffer) + + - Added Tornado example. + +- Tarashish Mishra (@sunu) + + - Added code to reject header fields with leading/trailing whitespace. + - Added code to remove leading/trailing whitespace from sent header fields. + +- Nate Prewitt (@nateprewitt) + + - Added code to validate that trailers do not contain pseudo-header fields. + +- Chun-Han, Hsiao (@chhsiao90) + + - Fixed a bug with invalid ``HTTP2-Settings`` header output in plaintext + upgrade. + +- Bhavishya (@bhavishyagopesh) + + - Added support for equality testing to ``h2.settings.Settings`` objects. + +- Fred Thomsen (@fredthomsen) + + - Added logging. + - Enhance equality testing of ``h2.settings.Settings`` objects with + ``hypothesis``. diff --git a/testing/web-platform/tests/tools/third_party/h2/HISTORY.rst b/testing/web-platform/tests/tools/third_party/h2/HISTORY.rst new file mode 100644 index 0000000000..5244cd8a94 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/HISTORY.rst @@ -0,0 +1,760 @@ +Release History +=============== + +3.2.0 (2020-02-08) +------------------ + +Bugfixes +~~~~~~~~ + +- Receiving DATA frames on closed (or reset) streams now properly emit a + WINDOW_UPDATE to keep the connection flow window topped up. + +API Changes (Backward-Incompatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``h2.config.logger`` now uses a `trace(...)` function, in addition + to `debug(...)`. If you defined a custom logger object, you need to handle + these new function calls. + + +3.1.1 (2019-08-02) +------------------ + +Bugfixes +~~~~~~~~ + +- Ignore WINDOW_UPDATE and RST_STREAM frames received after stream + closure. + + +3.1.0 (2019-01-22) +------------------ + +API Changes (Backward-Incompatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``h2.connection.H2Connection.data_to_send`` first and only argument ``amt`` + was renamed to ``amount``. +- Support for Python 3.3 has been removed. + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- ``h2.connection.H2Connection.send_data`` now supports ``data`` parameter + being a ``memoryview`` object. +- Refactor ping-related events: a ``h2.events.PingReceived`` event is fired + when a PING frame is received and a ``h2.events.PingAckReceived`` event is + fired when a PING frame with an ACK flag is received. + ``h2.events.PingAcknowledged`` is deprecated in favour of the identical + ``h2.events.PingAckReceived``. +- Added ``ENABLE_CONNECT_PROTOCOL`` to ``h2.settings.SettingCodes``. +- Support ``CONNECT`` requests with a ``:protocol`` pseudo header + thereby supporting RFC 8441. +- A limit to the number of closed streams kept in memory by the + connection is applied. It can be configured by + ``h2.connection.H2Connection.MAX_CLOSED_STREAMS``. + +Bugfixes +~~~~~~~~ + +- Debug logging when stream_id is None is now fixed and no longer errors. + +3.0.1 (2017-04-03) +------------------ + +Bugfixes +~~~~~~~~ + +- CONTINUATION frames sent on closed streams previously caused stream errors + of type STREAM_CLOSED. RFC 7540 § 6.10 requires that these be connection + errors of type PROTOCOL_ERROR, and so this release changes to match that + behaviour. +- Remote peers incrementing their inbound connection window beyond the maximum + allowed value now cause stream-level errors, rather than connection-level + errors, allowing connections to stay up longer. +- h2 now rejects receiving and sending request header blocks that are missing + any of the mandatory pseudo-header fields (:path, :scheme, and :method). +- h2 now rejects receiving and sending request header blocks that have an empty + :path pseudo-header value. +- h2 now rejects receiving and sending request header blocks that contain + response-only pseudo-headers, and vice versa. +- h2 now correct respects user-initiated changes to the HEADER_TABLE_SIZE + local setting, and ensures that if users shrink or increase the header + table size it is policed appropriately. + + +2.6.2 (2017-04-03) +------------------ + +Bugfixes +~~~~~~~~ + +- CONTINUATION frames sent on closed streams previously caused stream errors + of type STREAM_CLOSED. RFC 7540 § 6.10 requires that these be connection + errors of type PROTOCOL_ERROR, and so this release changes to match that + behaviour. +- Remote peers incrementing their inbound connection window beyond the maximum + allowed value now cause stream-level errors, rather than connection-level + errors, allowing connections to stay up longer. +- h2 now rejects receiving and sending request header blocks that are missing + any of the mandatory pseudo-header fields (:path, :scheme, and :method). +- h2 now rejects receiving and sending request header blocks that have an empty + :path pseudo-header value. +- h2 now rejects receiving and sending request header blocks that contain + response-only pseudo-headers, and vice versa. +- h2 now correct respects user-initiated changes to the HEADER_TABLE_SIZE + local setting, and ensures that if users shrink or increase the header + table size it is policed appropriately. + + +2.5.4 (2017-04-03) +------------------ + +Bugfixes +~~~~~~~~ + +- CONTINUATION frames sent on closed streams previously caused stream errors + of type STREAM_CLOSED. RFC 7540 § 6.10 requires that these be connection + errors of type PROTOCOL_ERROR, and so this release changes to match that + behaviour. +- Remote peers incrementing their inbound connection window beyond the maximum + allowed value now cause stream-level errors, rather than connection-level + errors, allowing connections to stay up longer. +- h2 now correct respects user-initiated changes to the HEADER_TABLE_SIZE + local setting, and ensures that if users shrink or increase the header + table size it is policed appropriately. + + +3.0.0 (2017-03-24) +------------------ + +API Changes (Backward-Incompatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- By default, hyper-h2 now joins together received cookie header fields, per + RFC 7540 Section 8.1.2.5. +- Added a ``normalize_inbound_headers`` flag to the ``H2Configuration`` object + that defaults to ``True``. Setting this to ``False`` changes the behaviour + from the previous point back to the v2 behaviour. +- Removed deprecated fields from ``h2.errors`` module. +- Removed deprecated fields from ``h2.settings`` module. +- Removed deprecated ``client_side`` and ``header_encoding`` arguments from + ``H2Connection``. +- Removed deprecated ``client_side`` and ``header_encoding`` properties from + ``H2Connection``. +- ``dict`` objects are no longer allowed for user-supplied headers. +- The default header encoding is now ``None``, not ``utf-8``: this means that + all events that carry headers now return those headers as byte strings by + default. The header encoding can be set back to ``utf-8`` to restore the old + behaviour. + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Added new ``UnknownFrameReceived`` event that fires when unknown extension + frames have been received. This only fires when using hyperframe 5.0 or + later: earlier versions of hyperframe cause us to silently ignore extension + frames. + +Bugfixes +~~~~~~~~ + +None + + +2.6.1 (2017-03-16) +------------------ + +Bugfixes +~~~~~~~~ + +- Allowed hyperframe v5 support while continuing to ignore unexpected frames. + + +2.5.3 (2017-03-16) +------------------ + +Bugfixes +~~~~~~~~ + +- Allowed hyperframe v5 support while continuing to ignore unexpected frames. + + +2.4.4 (2017-03-16) +------------------ + +Bugfixes +~~~~~~~~ + +- Allowed hyperframe v5 support while continuing to ignore unexpected frames. + + +2.6.0 (2017-02-28) +------------------ + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Added a new ``h2.events.Event`` class that acts as a base class for all + events. +- Rather than reject outbound Connection-specific headers, h2 will now + normalize the header block by removing them. +- Implement equality for the ``h2.settings.Settings`` class. +- Added ``h2.settings.SettingCodes``, an enum that is used to store all the + HTTP/2 setting codes. This allows us to use a better printed representation of + the setting code in most places that it is used. +- The ``setting`` field in ``ChangedSetting`` for the ``RemoteSettingsChanged`` + and ``SettingsAcknowledged`` events has been updated to be instances of + ``SettingCodes`` whenever they correspond to a known setting code. When they + are an unknown setting code, they are instead ``int``. As ``SettingCodes`` is + a subclass of ``int``, this is non-breaking. +- Deprecated the other fields in ``h2.settings``. These will be removed in + 3.0.0. +- Added an optional ``pad_length`` parameter to ``H2Connection.send_data`` + to allow the user to include padding on a data frame. +- Added a new parameter to the ``h2.config.H2Configuration`` initializer which + takes a logger. This allows us to log by providing a logger that conforms + to the requirements of this module so that it can be used in different + environments. + +Bugfixes +~~~~~~~~ + +- Correctly reject pushed request header blocks whenever they have malformed + request header blocks. +- Correctly normalize pushed request header blocks whenever they have + normalizable header fields. +- Remote peers are now allowed to send zero or any positive number as a value + for ``SETTINGS_MAX_HEADER_LIST_SIZE``, where previously sending zero would + raise a ``InvalidSettingsValueError``. +- Resolved issue where the ``HTTP2-Settings`` header value for plaintext + upgrade that was emitted by ``initiate_upgrade_connection`` included the + *entire* ``SETTINGS`` frame, instead of just the payload. +- Resolved issue where the ``HTTP2-Settings`` header value sent by a client for + plaintext upgrade would be ignored by ``initiate_upgrade_connection``, rather + than have those settings applied appropriately. +- Resolved an issue whereby certain frames received from a peer in the CLOSED + state would trigger connection errors when RFC 7540 says they should have + triggered stream errors instead. Added more detailed stream closure tracking + to ensure we don't throw away connections unnecessarily. + + +2.5.2 (2017-01-27) +------------------ + +- Resolved issue where the ``HTTP2-Settings`` header value for plaintext + upgrade that was emitted by ``initiate_upgrade_connection`` included the + *entire* ``SETTINGS`` frame, instead of just the payload. +- Resolved issue where the ``HTTP2-Settings`` header value sent by a client for + plaintext upgrade would be ignored by ``initiate_upgrade_connection``, rather + than have those settings applied appropriately. + + +2.4.3 (2017-01-27) +------------------ + +- Resolved issue where the ``HTTP2-Settings`` header value for plaintext + upgrade that was emitted by ``initiate_upgrade_connection`` included the + *entire* ``SETTINGS`` frame, instead of just the payload. +- Resolved issue where the ``HTTP2-Settings`` header value sent by a client for + plaintext upgrade would be ignored by ``initiate_upgrade_connection``, rather + than have those settings applied appropriately. + + +2.3.4 (2017-01-27) +------------------ + +- Resolved issue where the ``HTTP2-Settings`` header value for plaintext + upgrade that was emitted by ``initiate_upgrade_connection`` included the + *entire* ``SETTINGS`` frame, instead of just the payload. +- Resolved issue where the ``HTTP2-Settings`` header value sent by a client for + plaintext upgrade would be ignored by ``initiate_upgrade_connection``, rather + than have those settings applied appropriately. + + +2.5.1 (2016-12-17) +------------------ + +Bugfixes +~~~~~~~~ + +- Remote peers are now allowed to send zero or any positive number as a value + for ``SETTINGS_MAX_HEADER_LIST_SIZE``, where previously sending zero would + raise a ``InvalidSettingsValueError``. + + +2.5.0 (2016-10-25) +------------------ + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Added a new ``H2Configuration`` object that allows rich configuration of + a ``H2Connection``. This object supersedes the prior keyword arguments to the + ``H2Connection`` object, which are now deprecated and will be removed in 3.0. +- Added support for automated window management via the + ``acknowledge_received_data`` method. See the documentation for more details. +- Added a ``DenialOfServiceError`` that is raised whenever a behaviour that + looks like a DoS attempt is encountered: for example, an overly large + decompressed header list. This is a subclass of ``ProtocolError``. +- Added support for setting and managing ``SETTINGS_MAX_HEADER_LIST_SIZE``. + This setting is now defaulted to 64kB. +- Added ``h2.errors.ErrorCodes``, an enum that is used to store all the HTTP/2 + error codes. This allows us to use a better printed representation of the + error code in most places that it is used. +- The ``error_code`` fields on ``ConnectionTerminated`` and ``StreamReset`` + events have been updated to be instances of ``ErrorCodes`` whenever they + correspond to a known error code. When they are an unknown error code, they + are instead ``int``. As ``ErrorCodes`` is a subclass of ``int``, this is + non-breaking. +- Deprecated the other fields in ``h2.errors``. These will be removed in 3.0.0. + +Bugfixes +~~~~~~~~ + +- Correctly reject request header blocks with neither :authority nor Host + headers, or header blocks which contain mismatched :authority and Host + headers, per RFC 7540 Section 8.1.2.3. +- Correctly expect that responses to HEAD requests will have no body regardless + of the value of the Content-Length header, and reject those that do. +- Correctly refuse to send header blocks that contain neither :authority nor + Host headers, or header blocks which contain mismatched :authority and Host + headers, per RFC 7540 Section 8.1.2.3. +- Hyper-h2 will now reject header field names and values that contain leading + or trailing whitespace. +- Correctly strip leading/trailing whitespace from header field names and + values. +- Correctly refuse to send header blocks with a TE header whose value is not + ``trailers``, per RFC 7540 Section 8.1.2.2. +- Correctly refuse to send header blocks with connection-specific headers, + per RFC 7540 Section 8.1.2.2. +- Correctly refuse to send header blocks that contain duplicate pseudo-header + fields, or with pseudo-header fields that appear after ordinary header fields, + per RFC 7540 Section 8.1.2.1. + + This may cause passing a dictionary as the header block to ``send_headers`` + to throw a ``ProtocolError``, because dictionaries are unordered and so they + may trip this check. Passing dictionaries here is deprecated, and callers + should change to using a sequence of 2-tuples as their header blocks. +- Correctly reject trailers that contain HTTP/2 pseudo-header fields, per RFC + 7540 Section 8.1.2.1. +- Correctly refuse to send trailers that contain HTTP/2 pseudo-header fields, + per RFC 7540 Section 8.1.2.1. +- Correctly reject responses that do not contain the ``:status`` header field, + per RFC 7540 Section 8.1.2.4. +- Correctly refuse to send responses that do not contain the ``:status`` header + field, per RFC 7540 Section 8.1.2.4. +- Correctly update the maximum frame size when the user updates the value of + that setting. Prior to this release, if the user updated the maximum frame + size hyper-h2 would ignore the update, preventing the remote peer from using + the higher frame sizes. + +2.4.2 (2016-10-25) +------------------ + +Bugfixes +~~~~~~~~ + +- Correctly update the maximum frame size when the user updates the value of + that setting. Prior to this release, if the user updated the maximum frame + size hyper-h2 would ignore the update, preventing the remote peer from using + the higher frame sizes. + +2.3.3 (2016-10-25) +------------------ + +Bugfixes +~~~~~~~~ + +- Correctly update the maximum frame size when the user updates the value of + that setting. Prior to this release, if the user updated the maximum frame + size hyper-h2 would ignore the update, preventing the remote peer from using + the higher frame sizes. + +2.2.7 (2016-10-25) +------------------ + +*Final 2.2.X release* + +Bugfixes +~~~~~~~~ + +- Correctly update the maximum frame size when the user updates the value of + that setting. Prior to this release, if the user updated the maximum frame + size hyper-h2 would ignore the update, preventing the remote peer from using + the higher frame sizes. + +2.4.1 (2016-08-23) +------------------ + +Bugfixes +~~~~~~~~ + +- Correctly expect that responses to HEAD requests will have no body regardless + of the value of the Content-Length header, and reject those that do. + +2.3.2 (2016-08-23) +------------------ + +Bugfixes +~~~~~~~~ + +- Correctly expect that responses to HEAD requests will have no body regardless + of the value of the Content-Length header, and reject those that do. + +2.4.0 (2016-07-01) +------------------ + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Adds ``additional_data`` to ``H2Connection.close_connection``, allowing the + user to send additional debug data on the GOAWAY frame. +- Adds ``last_stream_id`` to ``H2Connection.close_connection``, allowing the + user to manually control what the reported last stream ID is. +- Add new method: ``prioritize``. +- Add support for emitting stream priority information when sending headers + frames using three new keyword arguments: ``priority_weight``, + ``priority_depends_on``, and ``priority_exclusive``. +- Add support for "related events": events that fire simultaneously on a single + frame. + + +2.3.1 (2016-05-12) +------------------ + +Bugfixes +~~~~~~~~ + +- Resolved ``AttributeError`` encountered when receiving more than one sequence + of CONTINUATION frames on a given connection. + + +2.2.5 (2016-05-12) +------------------ + +Bugfixes +~~~~~~~~ + +- Resolved ``AttributeError`` encountered when receiving more than one sequence + of CONTINUATION frames on a given connection. + + +2.3.0 (2016-04-26) +------------------ + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Added a new flag to the ``H2Connection`` constructor: ``header_encoding``, + that controls what encoding is used (if any) to decode the headers from bytes + to unicode. This defaults to UTF-8 for backward compatibility. To disable the + decode and use bytes exclusively, set the field to False, None, or the empty + string. This affects all headers, including those pushed by servers. +- Bumped the minimum version of HPACK allowed from 2.0 to 2.2. +- Added support for advertising RFC 7838 Alternative services. +- Allowed users to provide ``hpack.HeaderTuple`` and + ``hpack.NeverIndexedHeaderTuple`` objects to all methods that send headers. +- Changed all events that carry headers to emit ``hpack.HeaderTuple`` and + ``hpack.NeverIndexedHeaderTuple`` instead of plain tuples. This allows users + to maintain header indexing state. +- Added support for plaintext upgrade with the ``initiate_upgrade_connection`` + method. + +Bugfixes +~~~~~~~~ + +- Automatically ensure that all ``Authorization`` and ``Proxy-Authorization`` + headers, as well as short ``Cookie`` headers, are prevented from being added + to encoding contexts. + +2.2.4 (2016-04-25) +------------------ + +Bugfixes +~~~~~~~~ + +- Correctly forbid pseudo-headers that were not defined in RFC 7540. +- Ignore AltSvc frames, rather than exploding when receiving them. + +2.1.5 (2016-04-25) +------------------ + +*Final 2.1.X release* + +Bugfixes +~~~~~~~~ + +- Correctly forbid pseudo-headers that were not defined in RFC 7540. +- Ignore AltSvc frames, rather than exploding when receiving them. + +2.2.3 (2016-04-13) +------------------ + +Bugfixes +~~~~~~~~ + +- Allowed the 4.X series of hyperframe releases as dependencies. + +2.1.4 (2016-04-13) +------------------ + +Bugfixes +~~~~~~~~ + +- Allowed the 4.X series of hyperframe releases as dependencies. + + +2.2.2 (2016-04-05) +------------------ + +Bugfixes +~~~~~~~~ + +- Fixed issue where informational responses were erroneously not allowed to be + sent in the ``HALF_CLOSED_REMOTE`` state. +- Fixed issue where informational responses were erroneously not allowed to be + received in the ``HALF_CLOSED_LOCAL`` state. +- Fixed issue where we allowed information responses to be sent or received + after final responses. + +2.2.1 (2016-03-23) +------------------ + +Bugfixes +~~~~~~~~ + +- Fixed issue where users using locales that did not default to UTF-8 were + unable to install source distributions of the package. + +2.2.0 (2016-03-23) +------------------ + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Added support for sending informational responses (responses with 1XX status) + codes as part of the standard flow. HTTP/2 allows zero or more informational + responses with no upper limit: hyper-h2 does too. +- Added support for receiving informational responses (responses with 1XX + status) codes as part of the standard flow. HTTP/2 allows zero or more + informational responses with no upper limit: hyper-h2 does too. +- Added a new event: ``ReceivedInformationalResponse``. This response is fired + when informational responses (those with 1XX status codes). +- Added an ``additional_data`` field to the ``ConnectionTerminated`` event that + carries any additional data sent on the GOAWAY frame. May be ``None`` if no + such data was sent. +- Added the ``initial_values`` optional argument to the ``Settings`` object. + +Bugfixes +~~~~~~~~ + +- Correctly reject all of the connection-specific headers mentioned in RFC 7540 + § 8.1.2.2, not just the ``Connection:`` header. +- Defaulted the value of ``SETTINGS_MAX_CONCURRENT_STREAMS`` to 100, unless + explicitly overridden. This is a safe defensive initial value for this + setting. + +2.1.3 (2016-03-16) +------------------ + +Deprecations +~~~~~~~~~~~~ + +- Passing dictionaries to ``send_headers`` as the header block is deprecated, + and will be removed in 3.0. + +2.1.2 (2016-02-17) +------------------ + +Bugfixes +~~~~~~~~ + +- Reject attempts to push streams on streams that were themselves pushed: + streams can only be pushed on streams that were initiated by the client. +- Correctly allow CONTINUATION frames to extend the header block started by a + PUSH_PROMISE frame. +- Changed our handling of frames received on streams that were reset by the + user. + + Previously these would, at best, cause ProtocolErrors to be raised and the + connection to be torn down (rather defeating the point of resetting streams + at all) and, at worst, would cause subtle inconsistencies in state between + hyper-h2 and the remote peer that could lead to header block decoding errors + or flow control blockages. + + Now when the user resets a stream all further frames received on that stream + are ignored except where they affect some form of connection-level state, + where they have their effect and are then ignored. +- Fixed a bug whereby receiving a PUSH_PROMISE frame on a stream that was + closed would cause a RST_STREAM frame to be emitted on the closed-stream, + but not the newly-pushed one. Now this causes a ``ProtocolError``. + +2.1.1 (2016-02-05) +------------------ + +Bugfixes +~~~~~~~~ + +- Added debug representations for all events. +- Fixed problems with setup.py that caused trouble on older setuptools/pip + installs. + +2.1.0 (2016-02-02) +------------------ + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Added new field to ``DataReceived``: ``flow_controlled_length``. This is the + length of the frame including padded data, allowing users to correctly track + changes to the flow control window. +- Defined new ``UnsupportedFrameError``, thrown when frames that are known to + hyperframe but not supported by hyper-h2 are received. For + backward-compatibility reasons, this is a ``ProtocolError`` *and* a + ``KeyError``. + +Bugfixes +~~~~~~~~ + +- Hyper-h2 now correctly accounts for padding when maintaining flow control + windows. +- Resolved a bug where hyper-h2 would mistakenly apply + SETTINGS_INITIAL_WINDOW_SIZE to the connection flow control window in + addition to the stream-level flow control windows. +- Invalid Content-Length headers now throw ``ProtocolError`` exceptions and + correctly tear the connection down, instead of leaving the connection in an + indeterminate state. +- Invalid header blocks now throw ``ProtocolError``, rather than a grab bag of + possible other exceptions. + +2.0.0 (2016-01-25) +------------------ + +API Changes (Breaking) +~~~~~~~~~~~~~~~~~~~~~~ + +- Attempts to open streams with invalid stream IDs, either by the remote peer + or by the user, are now rejected as a ``ProtocolError``. Previously these + were allowed, and would cause remote peers to error. +- Receiving frames that have invalid padding now causes the connection to be + terminated with a ``ProtocolError`` being raised. Previously these passed + undetected. +- Settings values set by both the user and the remote peer are now validated + when they're set. If they're invalid, a new ``InvalidSettingsValueError`` is + raised and, if set by the remote peer, a connection error is signaled. + Previously, it was possible to set invalid values. These would either be + caught when building frames, or would be allowed to stand. +- Settings changes no longer require user action to be acknowledged: hyper-h2 + acknowledges them automatically. This moves the location where some + exceptions may be thrown, and also causes the ``acknowledge_settings`` method + to be removed from the public API. +- Removed a number of methods on the ``H2Connection`` object from the public, + semantically versioned API, by renaming them to have leading underscores. + Specifically, removed: + + - ``get_stream_by_id`` + - ``get_or_create_stream`` + - ``begin_new_stream`` + - ``receive_frame`` + - ``acknowledge_settings`` + +- Added full support for receiving CONTINUATION frames, including policing + logic about when and how they are received. Previously, receiving + CONTINUATION frames was not supported and would throw exceptions. +- All public API functions on ``H2Connection`` except for ``receive_data`` no + longer return lists of events, because these lists were always empty. Events + are now only raised by ``receive_data``. +- Calls to ``increment_flow_control_window`` with out of range values now raise + ``ValueError`` exceptions. Previously they would be allowed, or would cause + errors when serializing frames. + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Added ``PriorityUpdated`` event for signaling priority changes. +- Added ``get_next_available_stream_id`` function. +- Receiving DATA frames on streams not in the OPEN or HALF_CLOSED_LOCAL states + now causes a stream reset, rather than a connection reset. The error is now + also classified as a ``StreamClosedError``, rather than a more generic + ``ProtocolError``. +- Receiving HEADERS or PUSH_PROMISE frames in the HALF_CLOSED_REMOTE state now + causes a stream reset, rather than a connection reset. +- Receiving frames that violate the max frame size now causes connection errors + with error code FRAME_SIZE_ERROR, not a generic PROTOCOL_ERROR. This + condition now also raises a ``FrameTooLargeError``, a new subclass of + ``ProtocolError``. +- Made ``NoSuchStreamError`` a subclass of ``ProtocolError``. +- The ``StreamReset`` event is now also fired whenever a protocol error from + the remote peer forces a stream to close early. This is only fired once. +- The ``StreamReset`` event now carries a flag, ``remote_reset``, that is set + to ``True`` in all cases where ``StreamReset`` would previously have fired + (e.g. when the remote peer sent a RST_STREAM), and is set to ``False`` when + it fires because the remote peer made a protocol error. +- Hyper-h2 now rejects attempts by peers to increment a flow control window by + zero bytes. +- Hyper-h2 now rejects peers sending header blocks that are ill-formed for a + number of reasons as set out in RFC 7540 Section 8.1.2. +- Attempting to send non-PRIORITY frames on closed streams now raises + ``StreamClosedError``. +- Remote peers attempting to increase the flow control window beyond + ``2**31 - 1``, either by window increment or by settings frame, are now + rejected as ``ProtocolError``. +- Local attempts to increase the flow control window beyond ``2**31 - 1`` by + window increment are now rejected as ``ProtocolError``. +- The bytes that represent individual settings are now available in + ``h2.settings``, instead of needing users to import them from hyperframe. + +Bugfixes +~~~~~~~~ + +- RFC 7540 requires that a separate minimum stream ID be used for inbound and + outbound streams. Hyper-h2 now obeys this requirement. +- Hyper-h2 now does a better job of reporting the last stream ID it has + partially handled when terminating connections. +- Fixed an error in the arguments of ``StreamIDTooLowError``. +- Prevent ``ValueError`` leaking from Hyperframe. +- Prevent ``struct.error`` and ``InvalidFrameError`` leaking from Hyperframe. + +1.1.1 (2015-11-17) +------------------ + +Bugfixes +~~~~~~~~ + +- Forcibly lowercase all header names to improve compatibility with + implementations that demand lower-case header names. + +1.1.0 (2015-10-28) +------------------ + +API Changes (Backward-Compatible) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Added a new ``ConnectionTerminated`` event, which fires when GOAWAY frames + are received. +- Added a subclass of ``NoSuchStreamError``, called ``StreamClosedError``, that + fires when actions are taken on a stream that is closed and has had its state + flushed from the system. +- Added ``StreamIDTooLowError``, raised when the user or the remote peer + attempts to create a stream with an ID lower than one previously used in the + dialog. Inherits from ``ValueError`` for backward-compatibility reasons. + +Bugfixes +~~~~~~~~ + +- Do not throw ``ProtocolError`` when attempting to send multiple GOAWAY + frames on one connection. +- We no longer forcefully change the decoder table size when settings changes + are ACKed, instead waiting for remote acknowledgement of the change. +- Improve the performance of checking whether a stream is open. +- We now attempt to lazily garbage collect closed streams, to avoid having the + state hang around indefinitely, leaking memory. +- Avoid further per-stream allocations, leading to substantial performance + improvements when many short-lived streams are used. + +1.0.0 (2015-10-15) +------------------ + +- First production release! diff --git a/testing/web-platform/tests/tools/third_party/h2/LICENSE b/testing/web-platform/tests/tools/third_party/h2/LICENSE new file mode 100644 index 0000000000..7bb76c58ec --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2016 Cory Benfield 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/testing/web-platform/tests/tools/third_party/h2/MANIFEST.in b/testing/web-platform/tests/tools/third_party/h2/MANIFEST.in new file mode 100644 index 0000000000..04400de6b7 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/MANIFEST.in @@ -0,0 +1,8 @@ +include README.rst LICENSE CONTRIBUTORS.rst HISTORY.rst tox.ini test_requirements.txt .coveragerc Makefile +recursive-include test *.py *.sh +graft docs +prune docs/build +graft visualizer +recursive-include examples *.py *.crt *.key *.pem *.csr +recursive-include utils *.sh +recursive-include _travis *.sh diff --git a/testing/web-platform/tests/tools/third_party/h2/Makefile b/testing/web-platform/tests/tools/third_party/h2/Makefile new file mode 100644 index 0000000000..689077472f --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/Makefile @@ -0,0 +1,9 @@ +.PHONY: publish test + +publish: + rm -rf dist/ + python setup.py sdist bdist_wheel + twine upload -s dist/* + +test: + py.test -n 4 --cov h2 test/ diff --git a/testing/web-platform/tests/tools/third_party/h2/README.rst b/testing/web-platform/tests/tools/third_party/h2/README.rst new file mode 100644 index 0000000000..7140d37acc --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/README.rst @@ -0,0 +1,65 @@ +=============================== +hyper-h2: HTTP/2 Protocol Stack +=============================== + +.. image:: https://raw.github.com/Lukasa/hyper/development/docs/source/images/hyper.png + +.. image:: https://travis-ci.org/python-hyper/hyper-h2.svg?branch=master + :target: https://travis-ci.org/python-hyper/hyper-h2 + +This repository contains a pure-Python implementation of a HTTP/2 protocol +stack. It's written from the ground up to be embeddable in whatever program you +choose to use, ensuring that you can speak HTTP/2 regardless of your +programming paradigm. + +You use it like this: + +.. code-block:: python + + import h2.connection + + conn = h2.connection.H2Connection() + conn.send_headers(stream_id=stream_id, headers=headers) + conn.send_data(stream_id, data) + socket.sendall(conn.data_to_send()) + events = conn.receive_data(socket_data) + +This repository does not provide a parsing layer, a network layer, or any rules +about concurrency. Instead, it's a purely in-memory solution, defined in terms +of data actions and HTTP/2 frames. This is one building block of a full Python +HTTP implementation. + +To install it, just run: + +.. code-block:: console + + $ pip install h2 + +Documentation +============= + +Documentation is available at http://python-hyper.org/h2/. + +Contributing +============ + +``hyper-h2`` welcomes contributions from anyone! Unlike many other projects we +are happy to accept cosmetic contributions and small contributions, in addition +to large feature requests and changes. + +Before you contribute (either by opening an issue or filing a pull request), +please `read the contribution guidelines`_. + +.. _read the contribution guidelines: http://python-hyper.org/en/latest/contributing.html + +License +======= + +``hyper-h2`` is made available under the MIT License. For more details, see the +``LICENSE`` file in the repository. + +Authors +======= + +``hyper-h2`` is maintained by Cory Benfield, with contributions from others. For +more details about the contributors, please see ``CONTRIBUTORS.rst``. diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/Makefile b/testing/web-platform/tests/tools/third_party/h2/docs/Makefile new file mode 100644 index 0000000000..32b233be83 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/hyper-h2.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/hyper-h2.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/hyper-h2" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/hyper-h2" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/make.bat b/testing/web-platform/tests/tools/third_party/h2/docs/make.bat new file mode 100644 index 0000000000..537686d817 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/make.bat @@ -0,0 +1,242 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source +set I18NSPHINXOPTS=%SPHINXOPTS% source +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\hyper-h2.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\hyper-h2.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/_static/.keep b/testing/web-platform/tests/tools/third_party/h2/docs/source/_static/.keep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/_static/h2.connection.H2ConnectionStateMachine.dot.png b/testing/web-platform/tests/tools/third_party/h2/docs/source/_static/h2.connection.H2ConnectionStateMachine.dot.png new file mode 100644 index 0000000000..f2c814ec77 Binary files /dev/null and b/testing/web-platform/tests/tools/third_party/h2/docs/source/_static/h2.connection.H2ConnectionStateMachine.dot.png differ diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/_static/h2.stream.H2StreamStateMachine.dot.png b/testing/web-platform/tests/tools/third_party/h2/docs/source/_static/h2.stream.H2StreamStateMachine.dot.png new file mode 100644 index 0000000000..85bcb68321 Binary files /dev/null and b/testing/web-platform/tests/tools/third_party/h2/docs/source/_static/h2.stream.H2StreamStateMachine.dot.png differ diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/advanced-usage.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/advanced-usage.rst new file mode 100644 index 0000000000..40496f0eae --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/advanced-usage.rst @@ -0,0 +1,325 @@ +Advanced Usage +============== + +Priority +-------- + +.. versionadded:: 2.0.0 + +`RFC 7540`_ has a fairly substantial and complex section describing how to +build a HTTP/2 priority tree, and the effect that should have on sending data +from a server. + +Hyper-h2 does not enforce any priority logic by default for servers. This is +because scheduling data sends is outside the scope of this library, as it +likely requires fairly substantial understanding of the scheduler being used. + +However, for servers that *do* want to follow the priority recommendations +given by clients, the Hyper project provides `an implementation`_ of the +`RFC 7540`_ priority tree that will be useful to plug into a server. That, +combined with the :class:`PriorityUpdated ` event from +this library, can be used to build a server that conforms to RFC 7540's +recommendations for priority handling. + +Related Events +-------------- + +.. versionadded:: 2.4.0 + +In the 2.4.0 release hyper-h2 added support for signaling "related events". +These are a HTTP/2-only construct that exist because certain HTTP/2 events can +occur simultaneously: that is, one HTTP/2 frame can cause multiple state +transitions to occur at the same time. One example of this is a HEADERS frame +that contains priority information and carries the END_STREAM flag: this would +cause three events to fire (one of the various request/response received +events, a :class:`PriorityUpdated ` event, and a +:class:`StreamEnded ` event). + +Ordinarily hyper-h2's logic will emit those events to you one at a time. This +means that you may attempt to process, for example, a +:class:`DataReceived ` event, not knowing that the next +event out will be a :class:`StreamEnded ` event. +hyper-h2 *does* know this, however, and so will forbid you from taking certain +actions that are a violation of the HTTP/2 protocol. + +To avoid this asymmetry of information, events that can occur simultaneously +now carry properties for their "related events". These allow users to find the +events that can have occurred simultaneously with each other before the event +is emitted by hyper-h2. The following objects have "related events": + +- :class:`RequestReceived `: + + - :data:`stream_ended `: any + :class:`StreamEnded ` event that occurred at the + same time as receiving this request. + + - :data:`priority_updated + `: any + :class:`PriorityUpdated ` event that occurred + at the same time as receiving this request. + +- :class:`ResponseReceived `: + + - :data:`stream_ended `: any + :class:`StreamEnded ` event that occurred at the + same time as receiving this response. + + - :data:`priority_updated + `: any + :class:`PriorityUpdated ` event that occurred + at the same time as receiving this response. + +- :class:`TrailersReceived `: + + - :data:`stream_ended `: any + :class:`StreamEnded ` event that occurred at the + same time as receiving this set of trailers. This will **always** be + present for trailers, as they must terminate streams. + + - :data:`priority_updated + `: any + :class:`PriorityUpdated ` event that occurred + at the same time as receiving this response. + +- :class:`InformationalResponseReceived + `: + + - :data:`priority_updated + `: any + :class:`PriorityUpdated ` event that occurred + at the same time as receiving this informational response. + +- :class:`DataReceived `: + + - :data:`stream_ended `: any + :class:`StreamEnded ` event that occurred at the + same time as receiving this data. + + +.. warning:: hyper-h2 does not know if you are looking for related events or + expecting to find events in the event stream. Therefore, it will + always emit "related events" in the event stream. If you are using + the "related events" event pattern, you will want to be careful to + avoid double-processing related events. + +.. _h2-connection-advanced: + +Connections: Advanced +--------------------- + +Thread Safety +~~~~~~~~~~~~~ + +``H2Connection`` objects are *not* thread-safe. They cannot safely be accessed +from multiple threads at once. This is a deliberate design decision: it is not +trivially possible to design the ``H2Connection`` object in a way that would +be either lock-free or have the locks at a fine granularity. + +Your implementations should bear this in mind, and handle it appropriately. It +should be simple enough to use locking alongside the ``H2Connection``: simply +lock around the connection object itself. Because the ``H2Connection`` object +does no I/O it should be entirely safe to do that. Alternatively, have a single +thread take ownership of the ``H2Connection`` and use a message-passing +interface to serialize access to the ``H2Connection``. + +If you are using a non-threaded concurrency approach (e.g. Twisted), this +should not affect you. + +Internal Buffers +~~~~~~~~~~~~~~~~ + +In order to avoid doing I/O, the ``H2Connection`` employs an internal buffer. +This buffer is *unbounded* in size: it can potentially grow infinitely. This +means that, if you are not making sure to regularly empty it, you are at risk +of exceeding the memory limit of a single process and finding your program +crashes. + +It is highly recommended that you send data at regular intervals, ideally as +soon as possible. + +.. _advanced-sending-data: + +Sending Data +~~~~~~~~~~~~ + +When sending data on the network, it's important to remember that you may not +be able to send an unbounded amount of data at once. Particularly when using +TCP, it is often the case that there are limits on how much data may be in +flight at any one time. These limits can be very low, and your operating system +will only buffer so much data in memory before it starts to complain. + +For this reason, it is possible to consume only a subset of the data available +when you call :meth:`data_to_send `. +However, once you have pulled the data out of the ``H2Connection`` internal +buffer, it is *not* possible to put it back on again. For that reason, it is +adviseable that you confirm how much space is available in the OS buffer before +sending. + +Alternatively, use tools made available by your framework. For example, the +Python standard library :mod:`socket ` module provides a +:meth:`sendall ` method that will automatically +block until all the data has been sent. This will enable you to always use the +unbounded form of +:meth:`data_to_send `, and will help +you avoid subtle bugs. + +When To Send +~~~~~~~~~~~~ + +In addition to knowing how much data to send (see :ref:`advanced-sending-data`) +it is important to know when to send data. For hyper-h2, this amounts to +knowing when to call :meth:`data_to_send +`. + +Hyper-h2 may write data into its send buffer at two times. The first is +whenever :meth:`receive_data ` is +called. This data is sent in response to some control frames that require no +user input: for example, responding to PING frames. The second time is in +response to user action: whenever a user calls a method like +:meth:`send_headers `, data may be +written into the buffer. + +In a standard design for a hyper-h2 consumer, then, that means there are two +places where you'll potentially want to send data. The first is in your +"receive data" loop. This is where you take the data you receive, pass it into +:meth:`receive_data `, and then +dispatch events. For this loop, it is usually best to save sending data until +the loop is complete: that allows you to empty the buffer only once. + +The other place you'll want to send the data is when initiating requests or +taking any other active, unprompted action on the connection. In this instance, +you'll want to make all the relevant ``send_*`` calls, and *then* call +:meth:`data_to_send `. + +Headers +------- + +HTTP/2 defines several "special header fields" which are used to encode data +that was previously sent in either the request or status line of HTTP/1.1. +These header fields are distinguished from ordinary header fields because their +field name begins with a ``:`` character. The special header fields defined in +`RFC 7540`_ are: + +- ``:status`` +- ``:path`` +- ``:method`` +- ``:scheme`` +- ``:authority`` + +`RFC 7540`_ **mandates** that all of these header fields appear *first* in the +header block, before the ordinary header fields. This could cause difficulty if +the :meth:`send_headers ` method +accepted a plain ``dict`` for the ``headers`` argument, because ``dict`` +objects are unordered. For this reason, we require that you provide a list of +two-tuples. + +.. _RFC 7540: https://tools.ietf.org/html/rfc7540 +.. _an implementation: http://python-hyper.org/projects/priority/en/latest/ + +Flow Control +------------ + +HTTP/2 defines a complex flow control system that uses a sliding window of +data on both a per-stream and per-connection basis. Essentially, each +implementation allows its peer to send a specific amount of data at any time +(the "flow control window") before it must stop. Each stream has a separate +window, and the connection as a whole has a window. Each window can be opened +by an implementation by sending a ``WINDOW_UPDATE`` frame, either on a specific +stream (causing the window for that stream to be opened), or on stream ``0``, +which causes the window for the entire connection to be opened. + +In HTTP/2, only data in ``DATA`` frames is flow controlled. All other frames +are exempt from flow control. Each ``DATA`` frame consumes both stream and +connection flow control window bytes. This means that the maximum amount of +data that can be sent on any one stream before a ``WINDOW_UPDATE`` frame is +received is the *lower* of the stream and connection windows. The maximum +amount of data that can be sent on *all* streams before a ``WINDOW_UPDATE`` +frame is received is the size of the connection flow control window. + +Working With Flow Control +~~~~~~~~~~~~~~~~~~~~~~~~~ + +The amount of flow control window a ``DATA`` frame consumes is the sum of both +its contained application data *and* the amount of padding used. hyper-h2 shows +this to the user in a :class:`DataReceived ` event by +using the :data:`flow_controlled_length +` field. When working with flow +control in hyper-h2, users *must* use this field: simply using +``len(datareceived.data)`` can eventually lead to deadlock. + +When data has been received and given to the user in a :class:`DataReceived +`, it is the responsibility of the user to re-open the +flow control window when the user is ready for more data. hyper-h2 does not do +this automatically to avoid flooding the user with data: if we did, the remote +peer could send unbounded amounts of data that the user would need to buffer +before processing. + +To re-open the flow control window, then, the user must call +:meth:`increment_flow_control_window +` with the +:data:`flow_controlled_length ` +of the received data. hyper-h2 requires that you manage both the connection +and the stream flow control windows separately, so you may need to increment +both the stream the data was received on and stream ``0``. + +When sending data, a HTTP/2 implementation must not send more than flow control +window available for that stream. As noted above, the maximum amount of data +that can be sent on the stream is the minimum of the stream and the connection +flow control windows. You can find out how much data you can send on a given +stream by using the :meth:`local_flow_control_window +` method, which will do +all of these calculations for you. If you attempt to send more than this amount +of data on a stream, hyper-h2 will throw a :class:`ProtocolError +` and refuse to send the data. + +In hyper-h2, receiving a ``WINDOW_UPDATE`` frame causes a :class:`WindowUpdated +` event to fire. This will notify you that there is +potentially more room in a flow control window. Note that, just because an +increment of a given size was received *does not* mean that that much more data +can be sent: remember that both the connection and stream flow control windows +constrain how much data can be sent. + +As a result, when a :class:`WindowUpdated ` event +fires with a non-zero stream ID, and the user has more data to send on that +stream, the user should call :meth:`local_flow_control_window +` to check if there +really is more room to send data on that stream. + +When a :class:`WindowUpdated ` event fires with a +stream ID of ``0``, that may have unblocked *all* streams that are currently +blocked. The user should use :meth:`local_flow_control_window +` to check all blocked +streams to see if more data is available. + +Auto Flow Control +~~~~~~~~~~~~~~~~~ + +.. versionadded:: 2.5.0 + +In most cases, there is no advantage for users in managing their own flow +control strategies. While particular high performance or specific-use-case +applications may gain value from directly controlling the emission of +``WINDOW_UPDATE`` frames, the average application can use a +lowest-common-denominator strategy to emit those frames. As of version 2.5.0, +hyper-h2 now provides this automatic strategy for users, if they want to use +it. + +This automatic strategy is built around a single method: +:meth:`acknowledge_received_data +`. This method +flags to the connection object that your application has dealt with a certain +number of flow controlled bytes, and that the window should be incremented in +some way. Whenever your application has "processed" some received bytes, this +method should be called to signal that they have been processed. + +The key difference between this method and :meth:`increment_flow_control_window +` is that the method +:meth:`acknowledge_received_data +` does not guarantee that +it will emit a ``WINDOW_UPDATE`` frame, and if it does it will not necessarily +emit them for *only* the stream or *only* the frame. Instead, the +``WINDOW_UPDATE`` frames will be *coalesced*: they will be emitted only when +a certain number of bytes have been freed up. + +For most applications, this method should be preferred to the manual flow +control mechanism. diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/api.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/api.rst new file mode 100644 index 0000000000..a46f8cce7d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/api.rst @@ -0,0 +1,169 @@ +Hyper-h2 API +============ + +This document details the API of Hyper-h2. + +Semantic Versioning +------------------- + +Hyper-h2 follows semantic versioning for its public API. Please note that the +guarantees of semantic versioning apply only to the API that is *documented +here*. Simply because a method or data field is not prefaced by an underscore +does not make it part of Hyper-h2's public API. Anything not documented here is +subject to change at any time. + +Connection +---------- + +.. autoclass:: h2.connection.H2Connection + :members: + :exclude-members: inbound_flow_control_window + + +Configuration +------------- + +.. autoclass:: h2.config.H2Configuration + :members: + + +.. _h2-events-api: + +Events +------ + +.. autoclass:: h2.events.RequestReceived + :members: + +.. autoclass:: h2.events.ResponseReceived + :members: + +.. autoclass:: h2.events.TrailersReceived + :members: + +.. autoclass:: h2.events.InformationalResponseReceived + :members: + +.. autoclass:: h2.events.DataReceived + :members: + +.. autoclass:: h2.events.WindowUpdated + :members: + +.. autoclass:: h2.events.RemoteSettingsChanged + :members: + +.. autoclass:: h2.events.PingReceived + :members: + +.. autoclass:: h2.events.PingAckReceived + :members: + +.. autoclass:: h2.events.StreamEnded + :members: + +.. autoclass:: h2.events.StreamReset + :members: + +.. autoclass:: h2.events.PushedStreamReceived + :members: + +.. autoclass:: h2.events.SettingsAcknowledged + :members: + +.. autoclass:: h2.events.PriorityUpdated + :members: + +.. autoclass:: h2.events.ConnectionTerminated + :members: + +.. autoclass:: h2.events.AlternativeServiceAvailable + :members: + +.. autoclass:: h2.events.UnknownFrameReceived + :members: + + +Exceptions +---------- + +.. autoclass:: h2.exceptions.H2Error + :members: + +.. autoclass:: h2.exceptions.NoSuchStreamError + :show-inheritance: + :members: + +.. autoclass:: h2.exceptions.StreamClosedError + :show-inheritance: + :members: + +.. autoclass:: h2.exceptions.RFC1122Error + :show-inheritance: + :members: + + +Protocol Errors +~~~~~~~~~~~~~~~ + +.. autoclass:: h2.exceptions.ProtocolError + :show-inheritance: + :members: + +.. autoclass:: h2.exceptions.FrameTooLargeError + :show-inheritance: + :members: + +.. autoclass:: h2.exceptions.FrameDataMissingError + :show-inheritance: + :members: + +.. autoclass:: h2.exceptions.TooManyStreamsError + :show-inheritance: + :members: + +.. autoclass:: h2.exceptions.FlowControlError + :show-inheritance: + :members: + +.. autoclass:: h2.exceptions.StreamIDTooLowError + :show-inheritance: + :members: + +.. autoclass:: h2.exceptions.InvalidSettingsValueError + :members: + +.. autoclass:: h2.exceptions.NoAvailableStreamIDError + :show-inheritance: + :members: + +.. autoclass:: h2.exceptions.InvalidBodyLengthError + :show-inheritance: + :members: + +.. autoclass:: h2.exceptions.UnsupportedFrameError + :members: + +.. autoclass:: h2.exceptions.DenialOfServiceError + :show-inheritance: + :members: + + +HTTP/2 Error Codes +------------------ + +.. automodule:: h2.errors + :members: + + +Settings +-------- + +.. autoclass:: h2.settings.SettingCodes + :members: + +.. autoclass:: h2.settings.Settings + :inherited-members: + +.. autoclass:: h2.settings.ChangedSetting + :members: diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/asyncio-example.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/asyncio-example.rst new file mode 100644 index 0000000000..d3afbfd051 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/asyncio-example.rst @@ -0,0 +1,17 @@ +Asyncio Example Server +====================== + +This example is a basic HTTP/2 server written using `asyncio`_, using some +functionality that was introduced in Python 3.5. This server represents +basically just the same JSON-headers-returning server that was built in the +:doc:`basic-usage` document. + +This example demonstrates some basic asyncio techniques. + +.. literalinclude:: ../../examples/asyncio/asyncio-server.py + :language: python + :linenos: + :encoding: utf-8 + + +.. _asyncio: https://docs.python.org/3/library/asyncio.html diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/basic-usage.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/basic-usage.rst new file mode 100644 index 0000000000..b9aab6c6cf --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/basic-usage.rst @@ -0,0 +1,746 @@ +Getting Started: Writing Your Own HTTP/2 Server +=============================================== + +This document explains how to get started writing fully-fledged HTTP/2 +implementations using Hyper-h2 as the underlying protocol stack. It covers the +basic concepts you need to understand, and talks you through writing a very +simple HTTP/2 server. + +This document assumes you're moderately familiar with writing Python, and have +*some* understanding of how computer networks work. If you don't, you'll find +it a lot easier if you get some understanding of those concepts first and then +return to this documentation. + + +.. _h2-connection-basic: + +Connections +----------- + +Hyper-h2's core object is the +:class:`H2Connection ` object. This object is an +abstract representation of the state of a single HTTP/2 connection, and holds +all the important protocol state. When using Hyper-h2, this object will be the +first thing you create and the object that does most of the heavy lifting. + +The interface to this object is relatively simple. For sending data, you +call the object with methods indicating what actions you want to perform: for +example, you may want to send headers (you'd use the +:meth:`send_headers ` method), or +send data (you'd use the +:meth:`send_data ` method). After you've +decided what actions you want to perform, you get some bytes out of the object +that represent the HTTP/2-encoded representation of your actions, and send them +out over the network however you see fit. + +When you receive data from the network, you pass that data in to the +``H2Connection`` object, which returns a list of *events*. +These events, covered in more detail later in :ref:`h2-events-basic`, define +the set of actions the remote peer has performed on the connection, as +represented by the HTTP/2-encoded data you just passed to the object. + +Thus, you end up with a simple loop (which you may recognise as a more-specific +form of an `event loop`_): + + 1. First, you perform some actions. + 2. You send the data created by performing those actions to the network. + 3. You read data from the network. + 4. You decode those into events. + 5. The events cause you to trigger some actions: go back to step 1. + +Of course, HTTP/2 is more complex than that, but in the very simplest case you +can write a fairly effective HTTP/2 tool using just that kind of loop. Later in +this document, we'll do just that. + +Some important subtleties of ``H2Connection`` objects are covered in +:doc:`advanced-usage`: see :ref:`h2-connection-advanced` for more information. +However, one subtlety should be covered, and that is this: Hyper-h2's +``H2Connection`` object doesn't do I/O. Let's talk briefly about why. + +I/O +~~~ + +Any useful HTTP/2 tool eventually needs to do I/O. This is because it's not +very useful to be able to speak to other computers using a protocol like HTTP/2 +unless you actually *speak* to them sometimes. + +However, doing I/O is not a trivial thing: there are lots of different ways to +do it, and once you choose a way to do it your code usually won't work well +with the approaches you *didn't* choose. + +While there are lots of different ways to do I/O, when it comes down to it +all HTTP/2 implementations transform bytes received into events, and events +into bytes to send. So there's no reason to have lots of different versions of +this core protocol code: one for Twisted, one for gevent, one for threading, +and one for synchronous code. + +This is why we said at the top that Hyper-h2 is a *HTTP/2 Protocol Stack*, not +a *fully-fledged implementation*. Hyper-h2 knows how to transform bytes into +events and back, but that's it. The I/O and smarts might be different, but +the core HTTP/2 logic is the same: that's what Hyper-h2 provides. + +Not doing I/O makes Hyper-h2 general, and also relatively simple. It has an +easy-to-understand performance envelope, it's easy to test (and as a result +easy to get correct behaviour out of), and it behaves in a reproducible way. +These are all great traits to have in a library that is doing something quite +complex. + +This document will talk you through how to build a relatively simple HTTP/2 +implementation using Hyper-h2, to give you an understanding of where it fits in +your software. + + +.. _h2-events-basic: + +Events +------ + +When writing a HTTP/2 implementation it's important to know what the remote +peer is doing: if you didn't care, writing networked programs would be a lot +easier! + +Hyper-h2 encodes the actions of the remote peer in the form of *events*. When +you receive data from the remote peer and pass it into your ``H2Connection`` +object (see :ref:`h2-connection-basic`), the ``H2Connection`` returns a list +of objects, each one representing a single event that has occurred. Each +event refers to a single action the remote peer has taken. + +Some events are fairly high-level, referring to things that are more general +than HTTP/2: for example, the +:class:`RequestReceived ` event is a general HTTP +concept, not just a HTTP/2 one. Other events are extremely HTTP/2-specific: +for example, :class:`PushedStreamReceived ` +refers to Server Push, a very HTTP/2-specific concept. + +The reason these events exist is that Hyper-h2 is intended to be very general. +This means that, in many cases, Hyper-h2 does not know exactly what to do in +response to an event. Your code will need to handle these events, and make +decisions about what to do. That's the major role of any HTTP/2 implementation +built on top of Hyper-h2. + +A full list of events is available in :ref:`h2-events-api`. For the purposes +of this example, we will handle only a small set of events. + + +Writing Your Server +------------------- + +Armed with the knowledge you just obtained, we're going to write a very simple +HTTP/2 web server. The goal of this server is to write a server that can handle +a HTTP GET, and that returns the headers sent by the client, encoded in JSON. +Basically, something a lot like `httpbin.org/get`_. Nothing fancy, but this is +a good way to get a handle on how you should interact with Hyper-h2. + +For the sake of simplicity, we're going to write this using the Python standard +library, in Python 3. In reality, you'll probably want to use an asynchronous +framework of some kind: see the `examples directory`_ in the repository for +some examples of how you'd do that. + +Before we start, create a new file called ``h2server.py``: we'll use that as +our workspace. Additionally, you should install Hyper-h2: follow the +instructions in :doc:`installation`. + +Step 1: Sockets +~~~~~~~~~~~~~~~ + +To begin with, we need to make sure we can listen for incoming data and send it +back. To do that, we need to use the `standard library's socket module`_. For +now we're going to skip doing TLS: if you want to reach your server from your +web browser, though, you'll need to add TLS and some other function. Consider +looking at our examples in our `examples directory`_ instead. + +Let's begin. First, open up ``h2server.py``. We need to import the socket +module and start listening for connections. + +This is not a socket tutorial, so we're not going to dive too deeply into how +this works. If you want more detail about sockets, there are lots of good +tutorials on the web that you should investigate. + +When you want to listen for incoming connections, the you need to *bind* an +address first. So let's do that. Try setting up your file to look like this: + +.. code-block:: python + + import socket + + sock = socket.socket() + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(('0.0.0.0', 8080)) + sock.listen(5) + + while True: + print(sock.accept()) + +In a shell window, execute this program (``python h2server.py``). Then, open +another shell and run ``curl http://localhost:8080/``. In the first shell, you +should see something like this: + +.. code-block:: console + + $ python h2server.py + (, ('127.0.0.1', 58800)) + +Run that ``curl`` command a few more times. You should see a few more similar +lines appear. Note that the ``curl`` command itself will exit with an error. +That's fine: it happens because we didn't send any data. + +Now go ahead and stop the server running by hitting Ctrl+C in the first shell. +You should see a ``KeyboardInterrupt`` error take the process down. + +What's the program above doing? Well, first it creates a +:func:`socket ` object. This socket is then *bound* to +a specific address: ``('0.0.0.0', 8080)``. This is a special address: it means +that this socket should be listening for any traffic to TCP port 8080. Don't +worry about the call to ``setsockopt``: it just makes sure you can run this +program repeatedly. + +We then loop forever calling the :meth:`accept ` +method on the socket. The accept method blocks until someone attempts to +connect to our TCP port: when they do, it returns a tuple: the first element is +a new socket object, the second element is a tuple of the address the new +connection is from. You can see this in the output from our ``h2server.py`` +script. + +At this point, we have a script that can accept inbound connections. This is a +good start! Let's start getting HTTP/2 involved. + + +Step 2: Add a H2Connection +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Now that we can listen for socket information, we want to prepare our HTTP/2 +connection object and start handing it data. For now, let's just see what +happens as we feed it data. + +To make HTTP/2 connections, we need a tool that knows how to speak HTTP/2. +Most versions of curl in the wild don't, so let's install a Python tool. In +your Python environment, run ``pip install hyper``. This will install a Python +command-line HTTP/2 tool called ``hyper``. To confirm that it works, try +running this command and verifying that the output looks similar to the one +shown below: + +.. code-block:: console + + $ hyper GET https://nghttp2.org/httpbin/get + {'args': {}, + 'headers': {'Host': 'nghttp2.org'}, + 'origin': '10.0.0.2', + 'url': 'https://nghttp2.org/httpbin/get'} + +Assuming it works, you're now ready to start sending HTTP/2 data. + +Back in our ``h2server.py`` script, we're going to want to start handling data. +Let's add a function that takes a socket returned from ``accept``, and reads +data from it. Let's call that function ``handle``. That function should create +a :class:`H2Connection ` object and then loop on +the socket, reading data and passing it to the connection. + +To read data from a socket we need to call ``recv``. The ``recv`` function +takes a number as its argument, which is the *maximum* amount of data to be +returned from a single call (note that ``recv`` will return as soon as any data +is available, even if that amount is vastly less than the number you passed to +it). For the purposes of writing this kind of software the specific value is +not enormously useful, but should not be overly large. For that reason, when +you're unsure, a number like 4096 or 65535 is a good bet. We'll use 65535 for +this example. + +The function should look something like this: + +.. code-block:: python + + import h2.connection + import h2.config + + def handle(sock): + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) + + while True: + data = sock.recv(65535) + print(conn.receive_data(data)) + +Let's update our main loop so that it passes data on to our new data handling +function. Your ``h2server.py`` should end up looking a like this: + +.. code-block:: python + + import socket + + import h2.connection + import h2.config + + def handle(sock): + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) + + while True: + data = sock.recv(65535) + if not data: + break + + print(conn.receive_data(data)) + + + sock = socket.socket() + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(('0.0.0.0', 8080)) + sock.listen(5) + + while True: + handle(sock.accept()[0]) + +Running that in one shell, in your other shell you can run +``hyper --h2 GET http://localhost:8080/``. That shell should hang, and you +should then see the following output from your ``h2server.py`` shell: + +.. code-block:: console + + $ python h2server.py + [] + +You'll then need to kill ``hyper`` and ``h2server.py`` with Ctrl+C. Feel free +to do this a few times, to see how things behave. + +So, what did we see here? When the connection was opened, we used the +:meth:`recv ` method to read some data from the +socket, in a loop. We then passed that data to the connection object, which +returned us a single event object: +:class:`RemoteSettingsChanged `. + +But what we didn't see was anything else. So it seems like all ``hyper`` did +was change its settings, but nothing else. If you look at the other ``hyper`` +window, you'll notice that it hangs for a while and then eventually fails with +a socket timeout. It was waiting for something: what? + +Well, it turns out that at the start of a connection, both sides need to send +a bit of data, called "the HTTP/2 preamble". We don't need to get into too much +detail here, but basically both sides need to send a single block of HTTP/2 +data that tells the other side what their settings are. ``hyper`` did that, +but we didn't. + +Let's do that next. + + +Step 3: Sending the Preamble +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Hyper-h2 makes doing connection setup really easy. All you need to do is call +the +:meth:`initiate_connection ` +method, and then send the corresponding data. Let's update our ``handle`` +function to do just that: + +.. code-block:: python + + def handle(sock): + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) + conn.initiate_connection() + sock.sendall(conn.data_to_send()) + + while True: + data = sock.recv(65535) + print(conn.receive_data(data)) + + +The big change here is the call to ``initiate_connection``, but there's another +new method in there: +:meth:`data_to_send `. + +When you make function calls on your ``H2Connection`` object, these will often +want to cause HTTP/2 data to be written out to the network. But Hyper-h2 +doesn't do any I/O, so it can't do that itself. Instead, it writes it to an +internal buffer. You can retrieve data from this buffer using the +``data_to_send`` method. There are some subtleties about that method, but we +don't need to worry about them right now: all we need to do is make sure we're +sending whatever data is outstanding. + +Your ``h2server.py`` script should now look like this: + +.. code-block:: python + + import socket + + import h2.connection + import h2.config + + def handle(sock): + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) + conn.initiate_connection() + sock.sendall(conn.data_to_send()) + + while True: + data = sock.recv(65535) + if not data: + break + + print(conn.receive_data(data)) + + + sock = socket.socket() + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(('0.0.0.0', 8080)) + sock.listen(5) + + while True: + handle(sock.accept()[0]) + + +With this change made, rerun your ``h2server.py`` script and hit it with the +same ``hyper`` command: ``hyper --h2 GET http://localhost:8080/``. The +``hyper`` command still hangs, but this time we get a bit more output from our +``h2server.py`` script: + +.. code-block:: console + + $ python h2server.py + [] + [] + [, ] + +So, what's happening? + +The first thing to note is that we're going around our loop more than once now. +First, we receive some data that triggers a +:class:`RemoteSettingsChanged ` event. +Then, we get some more data that triggers a +:class:`SettingsAcknowledged ` event. +Finally, even more data that triggers *two* events: +:class:`RequestReceived ` and +:class:`StreamEnded `. + +So, what's happening is that ``hyper`` is telling us about its settings, +acknowledging ours, and then sending us a request. Then it ends a *stream*, +which is a HTTP/2 communications channel that holds a request and response +pair. + +A stream isn't done until it's either *reset* or both sides *close* it: +in this sense it's bi-directional. So what the ``StreamEnded`` event tells us +is that ``hyper`` is closing its half of the stream: it won't send us any more +data on that stream. That means the request is done. + +So why is ``hyper`` hanging? Well, we haven't sent a response yet: let's do +that. + + +Step 4: Handling Events +~~~~~~~~~~~~~~~~~~~~~~~ + +What we want to do is send a response when we receive a request. Happily, we +get an event when we receive a request, so we can use that to be our signal. + +Let's define a new function that sends a response. For now, this response can +just be a little bit of data that prints "it works!". + +The function should take the ``H2Connection`` object, and the event that +signaled the request. Let's define it. + +.. code-block:: python + + def send_response(conn, event): + stream_id = event.stream_id + conn.send_headers( + stream_id=stream_id, + headers=[ + (':status', '200'), + ('server', 'basic-h2-server/1.0') + ], + ) + conn.send_data( + stream_id=stream_id, + data=b'it works!', + end_stream=True + ) + +So while this is only a short function, there's quite a lot going on here we +need to unpack. Firstly, what's a stream ID? Earlier we discussed streams +briefly, to say that they're a bi-directional communications channel that holds +a request and response pair. Part of what makes HTTP/2 great is that there can +be lots of streams going on at once, sending and receiving different requests +and responses. To identify each stream, we use a *stream ID*. These are unique +across the lifetime of a connection, and they go in ascending order. + +Most ``H2Connection`` functions take a stream ID: they require you to actively +tell the connection which one to use. In this case, as a simple server, we will +never need to choose a stream ID ourselves: the client will always choose one +for us. That means we'll always be able to get the one we need off the events +that fire. + +Next, we send some *headers*. In HTTP/2, a response is made up of some set of +headers, and optionally some data. The headers have to come first: if you're a +client then you'll be sending *request* headers, but in our case these headers +are our *response* headers. + +Mostly these aren't very exciting, but you'll notice once special header in +there: ``:status``. This is a HTTP/2-specific header, and it's used to hold the +HTTP status code that used to go at the top of a HTTP response. Here, we're +saying the response is ``200 OK``, which is successful. + +To send headers in Hyper-h2, you use the +:meth:`send_headers ` function. + +Next, we want to send the body data. To do that, we use the +:meth:`send_data ` function. This also +takes a stream ID. Note that the data is binary: Hyper-h2 does not work with +unicode strings, so you *must* pass bytestrings to the ``H2Connection``. The +one exception is headers: Hyper-h2 will automatically encode those into UTF-8. + +The last thing to note is that on our call to ``send_data``, we set +``end_stream`` to ``True``. This tells Hyper-h2 (and the remote peer) that +we're done with sending data: the response is over. Because we know that +``hyper`` will have ended its side of the stream, when we end ours the stream +will be totally done with. + +We're nearly ready to go with this: we just need to plumb this function in. +Let's amend our ``handle`` function again: + +.. code-block:: python + + import h2.events + import h2.config + + def handle(sock): + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) + conn.initiate_connection() + sock.sendall(conn.data_to_send()) + + while True: + data = sock.recv(65535) + if not data: + break + + events = conn.receive_data(data) + for event in events: + if isinstance(event, h2.events.RequestReceived): + send_response(conn, event) + + data_to_send = conn.data_to_send() + if data_to_send: + sock.sendall(data_to_send) + +The changes here are all at the end. Now, when we receive some events, we +look through them for the ``RequestReceived`` event. If we find it, we make +sure we send a response. + +Then, at the bottom of the loop we check whether we have any data to send, and +if we do, we send it. Then, we repeat again. + +With these changes, your ``h2server.py`` file should look like this: + +.. code-block:: python + + import socket + + import h2.connection + import h2.events + import h2.config + + def send_response(conn, event): + stream_id = event.stream_id + conn.send_headers( + stream_id=stream_id, + headers=[ + (':status', '200'), + ('server', 'basic-h2-server/1.0') + ], + ) + conn.send_data( + stream_id=stream_id, + data=b'it works!', + end_stream=True + ) + + def handle(sock): + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) + conn.initiate_connection() + sock.sendall(conn.data_to_send()) + + while True: + data = sock.recv(65535) + if not data: + break + + events = conn.receive_data(data) + for event in events: + if isinstance(event, h2.events.RequestReceived): + send_response(conn, event) + + data_to_send = conn.data_to_send() + if data_to_send: + sock.sendall(data_to_send) + + + sock = socket.socket() + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(('0.0.0.0', 8080)) + sock.listen(5) + + while True: + handle(sock.accept()[0]) + +Alright. Let's run this, and then run our ``hyper`` command again. + +This time, nothing is printed from our server, and the ``hyper`` side prints +``it works!``. Success! Try running it a few more times, and we can see that +not only does it work the first time, it works the other times too! + +We can speak HTTP/2! Let's add the final step: returning the JSON-encoded +request headers. + +Step 5: Returning Headers +~~~~~~~~~~~~~~~~~~~~~~~~~ + +If we want to return the request headers in JSON, the first thing we have to do +is find them. Handily, if you check the documentation for +:class:`RequestReceived ` you'll find that this +event carries, in addition to the stream ID, the request headers. + +This means we can make a really simple change to our ``send_response`` +function to take those headers and encode them as a JSON object. Let's do that: + +.. code-block:: python + + import json + + def send_response(conn, event): + stream_id = event.stream_id + response_data = json.dumps(dict(event.headers)).encode('utf-8') + + conn.send_headers( + stream_id=stream_id, + headers=[ + (':status', '200'), + ('server', 'basic-h2-server/1.0'), + ('content-length', str(len(response_data))), + ('content-type', 'application/json'), + ], + ) + conn.send_data( + stream_id=stream_id, + data=response_data, + end_stream=True + ) + +This is a really simple change, but it's all we need to do: a few extra headers +and the JSON dump, but that's it. + +Section 6: Bringing It All Together +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This should be all we need! + +Let's take all the work we just did and throw that into our ``h2server.py`` +file, which should now look like this: + +.. code-block:: python + + import json + import socket + + import h2.connection + import h2.events + import h2.config + + def send_response(conn, event): + stream_id = event.stream_id + response_data = json.dumps(dict(event.headers)).encode('utf-8') + + conn.send_headers( + stream_id=stream_id, + headers=[ + (':status', '200'), + ('server', 'basic-h2-server/1.0'), + ('content-length', str(len(response_data))), + ('content-type', 'application/json'), + ], + ) + conn.send_data( + stream_id=stream_id, + data=response_data, + end_stream=True + ) + + def handle(sock): + config = h2.config.H2Configuration(client_side=False) + conn = h2.connection.H2Connection(config=config) + conn.initiate_connection() + sock.sendall(conn.data_to_send()) + + while True: + data = sock.recv(65535) + if not data: + break + + events = conn.receive_data(data) + for event in events: + if isinstance(event, h2.events.RequestReceived): + send_response(conn, event) + + data_to_send = conn.data_to_send() + if data_to_send: + sock.sendall(data_to_send) + + + sock = socket.socket() + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind(('0.0.0.0', 8080)) + sock.listen(5) + + while True: + handle(sock.accept()[0]) + +Now, execute ``h2server.py`` and then point ``hyper`` at it again. You should +see something like the following output from ``hyper``: + +.. code-block:: console + + $ hyper --h2 GET http://localhost:8080/ + {":scheme": "http", ":authority": "localhost", ":method": "GET", ":path": "/"} + +Here you can see the HTTP/2 request 'special headers' that ``hyper`` sends. +These are similar to the ``:status`` header we have to send on our response: +they encode important parts of the HTTP request in a clearly-defined way. If +you were writing a client stack using Hyper-h2, you'd need to make sure you +were sending those headers. + +Congratulations! +~~~~~~~~~~~~~~~~ + +Congratulations! You've written your first HTTP/2 server! If you want to extend +it, there are a few directions you could investigate: + +- We didn't handle a few events that we saw were being raised: you could add + some methods to handle those appropriately. +- Right now our server is single threaded, so it can only handle one client at + a time. Consider rewriting this server to use threads, or writing this + server again using your favourite asynchronous programming framework. + + If you plan to use threads, you should know that a ``H2Connection`` object is + deliberately not thread-safe. As a possible design pattern, consider creating + threads and passing the sockets returned by ``accept`` to those threads, and + then letting those threads create their own ``H2Connection`` objects. +- Take a look at some of our long-form code examples in :doc:`examples`. +- Alternatively, try playing around with our examples in our repository's + `examples directory`_. These examples are a bit more fully-featured, and can + be reached from your web browser. Try adjusting what they do, or adding new + features to them! +- You may want to make this server reachable from your web browser. To do that, + you'll need to add proper TLS support to your server. This can be tricky, and + in many cases requires `PyOpenSSL`_ in addition to the other libraries you + have installed. Check the `Eventlet example`_ to see what PyOpenSSL code is + required to TLS-ify your server. + + + +.. _event loop: https://en.wikipedia.org/wiki/Event_loop +.. _httpbin.org/get: https://httpbin.org/get +.. _examples directory: https://github.com/python-hyper/hyper-h2/tree/master/examples +.. _standard library's socket module: https://docs.python.org/3.5/library/socket.html +.. _Application Layer Protocol Negotiation: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation +.. _get your certificate here: https://raw.githubusercontent.com/python-hyper/hyper-h2/master/examples/twisted/server.crt +.. _get your private key here: https://raw.githubusercontent.com/python-hyper/hyper-h2/master/examples/twisted/server.key +.. _PyOpenSSL: http://pyopenssl.readthedocs.org/ +.. _Eventlet example: https://github.com/python-hyper/hyper-h2/blob/master/examples/eventlet/eventlet-server.py diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/conf.py b/testing/web-platform/tests/tools/third_party/h2/docs/source/conf.py new file mode 100644 index 0000000000..a15a214634 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/conf.py @@ -0,0 +1,270 @@ +# -*- coding: utf-8 -*- +# +# hyper-h2 documentation build configuration file, created by +# sphinx-quickstart on Thu Sep 17 10:06:02 2015. +# +# This file is execfile()d with the current directory set to its +# containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('../..')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'hyper-h2' +copyright = u'2015, Cory Benfield' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '3.2.0' +# The full version, including alpha/beta/rc tags. +release = '3.2.0' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output ---------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Add any extra paths that contain custom files (such as robots.txt or +# .htaccess) here, relative to this directory. These files are copied +# directly to the root of the documentation. +#html_extra_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'hyper-h2doc' + + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + ('index', 'hyper-h2.tex', u'hyper-h2 Documentation', + u'Cory Benfield', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'hyper-h2', u'hyper-h2 Documentation', + [u'Cory Benfield'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'hyper-h2', u'hyper-h2 Documentation', + u'Cory Benfield', 'hyper-h2', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'python': ('https://docs.python.org/3.5/', None), + 'hpack': ('https://python-hyper.org/hpack/en/stable/', None), + 'pyopenssl': ('https://pyopenssl.readthedocs.org/en/latest/', None), +} diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/contributors.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/contributors.rst new file mode 100644 index 0000000000..d84c791f6d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/contributors.rst @@ -0,0 +1,4 @@ +Contributors +============ + +.. include:: ../../CONTRIBUTORS.rst diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/curio-example.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/curio-example.rst new file mode 100644 index 0000000000..7cdb61608a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/curio-example.rst @@ -0,0 +1,17 @@ +Curio Example Server +==================== + +This example is a basic HTTP/2 server written using `curio`_, David Beazley's +example of how to build a concurrent networking framework using Python 3.5's +new ``async``/``await`` syntax. + +This example is notable for demonstrating the correct use of HTTP/2 flow +control with Hyper-h2. It is also a good example of the brand new syntax. + +.. literalinclude:: ../../examples/curio/curio-server.py + :language: python + :linenos: + :encoding: utf-8 + + +.. _curio: https://curio.readthedocs.org/en/latest/ diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/eventlet-example.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/eventlet-example.rst new file mode 100644 index 0000000000..a23b5e248f --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/eventlet-example.rst @@ -0,0 +1,19 @@ +Eventlet Example Server +======================= + +This example is a basic HTTP/2 server written using the `eventlet`_ concurrent +networking framework. This example is notable for demonstrating how to +configure `PyOpenSSL`_, which `eventlet`_ uses for its TLS layer. + +In terms of HTTP/2 functionality, this example is very simple: it returns the +request headers as a JSON document to the caller. It does not obey HTTP/2 flow +control, which is a flaw, but it is otherwise functional. + +.. literalinclude:: ../../examples/eventlet/eventlet-server.py + :language: python + :linenos: + :encoding: utf-8 + + +.. _eventlet: http://eventlet.net/ +.. _PyOpenSSL: https://pyopenssl.readthedocs.org/en/stable/ diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/examples.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/examples.rst new file mode 100644 index 0000000000..ed7c5037bb --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/examples.rst @@ -0,0 +1,28 @@ +Code Examples +============= + +This section of the documentation contains long-form code examples. These are +intended as references for developers that would like to get an understanding +of how Hyper-h2 fits in with various Python I/O frameworks. + +Example Servers +--------------- + +.. toctree:: + :maxdepth: 2 + + asyncio-example + twisted-example + eventlet-example + curio-example + tornado-example + wsgi-example + +Example Clients +--------------- + +.. toctree:: + :maxdepth: 2 + + twisted-head-example + twisted-post-example diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/index.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/index.rst new file mode 100644 index 0000000000..be85dec7c3 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/index.rst @@ -0,0 +1,41 @@ +.. hyper-h2 documentation master file, created by + sphinx-quickstart on Thu Sep 17 10:06:02 2015. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Hyper-h2: A pure-Python HTTP/2 protocol stack +============================================= + +Hyper-h2 is a HTTP/2 protocol stack, written entirely in Python. The goal of +Hyper-h2 is to be a common HTTP/2 stack for the Python ecosystem, +usable in all programs regardless of concurrency model or environment. + +To achieve this, Hyper-h2 is entirely self-contained: it does no I/O of any +kind, leaving that up to a wrapper library to control. This ensures that it can +seamlessly work in all kinds of environments, from single-threaded code to +Twisted. + +Its goal is to be 100% compatible with RFC 7540, implementing a complete HTTP/2 +protocol stack build on a set of finite state machines. Its secondary goals are +to be fast, clear, and efficient. + +For usage examples, see :doc:`basic-usage` or consult the examples in the +repository. + +Contents +-------- + +.. toctree:: + :maxdepth: 2 + + installation + basic-usage + negotiating-http2 + examples + advanced-usage + low-level + api + testimonials + release-process + release-notes + contributors \ No newline at end of file diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/installation.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/installation.rst new file mode 100644 index 0000000000..683085f97b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/installation.rst @@ -0,0 +1,18 @@ +Installation +============ + +Hyper-h2 is a pure-python project. This means installing it is extremely +simple. To get the latest release from PyPI, simply run: + +.. code-block:: console + + $ pip install h2 + +Alternatively, feel free to download one of the release tarballs from +`our GitHub page`_, extract it to your favourite directory, and then run + +.. code-block:: console + + $ python setup.py install + +.. _our GitHub page: https://github.com/python-hyper/hyper-h2 diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/low-level.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/low-level.rst new file mode 100644 index 0000000000..824ba8e6ea --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/low-level.rst @@ -0,0 +1,159 @@ +Low-Level Details +================= + +.. warning:: This section of the documentation covers low-level implementation + details of hyper-h2. This is most likely to be of use to hyper-h2 + developers and to other HTTP/2 implementers, though it could well + be of general interest. Feel free to peruse it, but if you're + looking for information about how to *use* hyper-h2 you should + consider looking elsewhere. + +State Machines +-------------- + +hyper-h2 is fundamentally built on top of a pair of interacting Finite State +Machines. One of these FSMs manages per-connection state, and another manages +per-stream state. Almost without exception (see :ref:`priority` for more +details) every single frame is unconditionally translated into events for +both state machines and those state machines are turned. + +The advantages of a system such as this is that the finite state machines can +very densely encode the kinds of things that are allowed at any particular +moment in a HTTP/2 connection. However, most importantly, almost all protocols +are defined *in terms* of finite state machines: that is, protocol descriptions +can be reduced to a number of states and inputs. That makes FSMs a very natural +tool for implementing protocol stacks. + +Indeed, most protocol implementations that do not explicitly encode a finite +state machine almost always *implicitly* encode a finite state machine, by +using classes with a bunch of variables that amount to state-tracking +variables, or by using the call-stack as an implicit state tracking mechanism. +While these methods are not immediately problematic, they tend to lack +*explicitness*, and can lead to subtle bugs of the form "protocol action X is +incorrectly allowed in state Y". + +For these reasons, we have implemented two *explicit* finite state machines. +These machines aim to encode most of the protocol-specific state, in particular +regarding what frame is allowed at what time. This target goal is sometimes not +achieved: in particular, as of this writing the *stream* FSM contains a number +of other state variables that really ought to be rolled into the state machine +itself in the form of new states, or in the form of a transformation of the +FSM to use state *vectors* instead of state *scalars*. + +The following sections contain some implementers notes on these FSMs. + +Connection State Machine +~~~~~~~~~~~~~~~~~~~~~~~~ + +The "outer" state machine, the first one that is encountered when sending or +receiving data, is the connection state machine. This state machine tracks +whole-connection state. + +This state machine is primarily intended to forbid certain actions on the basis +of whether the implementation is acting as a client or a server. For example, +clients are not permitted to send ``PUSH_PROMISE`` frames: this state machine +forbids that by refusing to define a valid transition from the ``CLIENT_OPEN`` +state for the ``SEND_PUSH_PROMISE`` event. + +Otherwise, this particular state machine triggers no side-effects. It has a +very coarse, high-level, functionality. + +A visual representation of this FSM is shown below: + +.. image:: _static/h2.connection.H2ConnectionStateMachine.dot.png + :alt: A visual representation of the connection FSM. + :target: _static/h2.connection.H2ConnectionStateMachine.dot.png + + +.. _stream-state-machine: + +Stream State Machine +~~~~~~~~~~~~~~~~~~~~ + +Once the connection state machine has been spun, any frame that belongs to a +stream is passed to the stream state machine for its given stream. Each stream +has its own instance of the state machine, but all of them share the transition +table: this is because the table itself is sufficiently large that having it be +per-instance would be a ridiculous memory overhead. + +Unlike the connection state machine, the stream state machine is quite complex. +This is because it frequently needs to encode some side-effects. The most +common side-effect is emitting a ``RST_STREAM`` frame when an error is +encountered: the need to do this means that far more transitions need to be +encoded than for the connection state machine. + +Many of the side-effect functions in this state machine also raise +:class:`ProtocolError ` exceptions. This is almost +always done on the basis of an extra state variable, which is an annoying code +smell: it should always be possible for the state machine itself to police +these using explicit state management. A future refactor will hopefully address +this problem by making these additional state variables part of the state +definitions in the FSM, which will lead to an expansion of the number of states +but a greater degree of simplicity in understanding and tracking what is going +on in the state machine. + +The other action taken by the side-effect functions defined here is returning +:ref:`events `. Most of these events are returned directly to +the user, and reflect the specific state transition that has taken place, but +some of the events are purely *internal*: they are used to signal to other +parts of the hyper-h2 codebase what action has been taken. + +The major use of the internal events functionality at this time is for +validating header blocks: there are different rules for request headers than +there are for response headers, and different rules again for trailers. The +internal events are used to determine *exactly what* kind of data the user is +attempting to send, and using that information to do the correct kind of +validation. This approach ensures that the final source of truth about what's +happening at the protocol level lives inside the FSM, which is an extremely +important design principle we want to continue to enshrine in hyper-h2. + +A visual representation of this FSM is shown below: + +.. image:: _static/h2.stream.H2StreamStateMachine.dot.png + :alt: A visual representation of the stream FSM. + :target: _static/h2.stream.H2StreamStateMachine.dot.png + + +.. _priority: + +Priority +~~~~~~~~ + +In the :ref:`stream-state-machine` section we said that any frame that belongs +to a stream is passed to the stream state machine. This turns out to be not +quite true. + +Specifically, while ``PRIORITY`` frames are technically sent on a given stream +(that is, `RFC 7540 Section 6.3`_ defines them as "always identifying a stream" +and forbids the use of stream ID ``0`` for them), in practice they are almost +completely exempt from the usual stream FSM behaviour. Specifically, the RFC +has this to say: + + The ``PRIORITY`` frame can be sent on a stream in any state, though it + cannot be sent between consecutive frames that comprise a single + header block (Section 4.3). + +Given that the consecutive header block requirement is handled outside of the +FSMs, this section of the RFC essentially means that there is *never* a +situation where it is invalid to receive a ``PRIORITY`` frame. This means that +including it in the stream FSM would require that we allow ``SEND_PRIORITY`` +and ``RECV_PRIORITY`` in all states. + +This is not a totally onerous task: however, another key note is that hyper-h2 +uses the *absence* of a stream state machine to flag a closed stream. This is +primarily for memory conservation reasons: if we needed to keep around an FSM +for every stream we've ever seen, that would cause long-lived HTTP/2 +connections to consume increasingly large amounts of memory. On top of this, +it would require us to create a stream FSM each time we received a ``PRIORITY`` +frame for a given stream, giving a malicious peer an easy route to force a +hyper-h2 user to allocate nearly unbounded amounts of memory. + +For this reason, hyper-h2 circumvents the stream FSM entirely for ``PRIORITY`` +frames. Instead, these frames are treated as being connection-level frames that +*just happen* to identify a specific stream. They do not bring streams into +being, or in any sense interact with hyper-h2's view of streams. Their stream +details are treated as strictly metadata that hyper-h2 is not interested in +beyond being able to parse it out. + + +.. _RFC 7540 Section 6.3: https://tools.ietf.org/html/rfc7540#section-6.3 diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/negotiating-http2.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/negotiating-http2.rst new file mode 100644 index 0000000000..20d58a71f1 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/negotiating-http2.rst @@ -0,0 +1,103 @@ +Negotiating HTTP/2 +================== + +`RFC 7540`_ specifies three methods of negotiating HTTP/2 connections. This document outlines how to use Hyper-h2 with each one. + +.. _starting-alpn: + +HTTPS URLs (ALPN) +------------------------- + +Starting HTTP/2 for HTTPS URLs is outlined in `RFC 7540 Section 3.3`_. In this case, the client and server use a TLS extension to negotiate HTTP/2: `ALPN`_. How to use ALPN is currently not covered in this document: please consult the documentation for either the :mod:`ssl module ` in the standard library, or the :mod:`PyOpenSSL ` third-party modules, for more on this topic. + +This method is the simplest to use once the TLS connection is established. To use it with Hyper-h2, after you've established the connection and confirmed that HTTP/2 has been negotiated with `ALPN`_, create a :class:`H2Connection ` object and call :meth:`H2Connection.initiate_connection `. This will ensure that the appropriate preamble data is placed in the data buffer. You should then immediately send the data returned by :meth:`H2Connection.data_to_send ` on your TLS connection. + +At this point, you're free to use all the HTTP/2 functionality provided by Hyper-h2. + +.. note:: + Although Hyper-h2 is not concerned with negotiating protocol versions, it is important to note that support for `ALPN`_ is not available in the standard library of Python versions < 2.7.9. + As a consequence, clients may encounter various errors due to protocol versions mismatch. + +Server Setup Example +~~~~~~~~~~~~~~~~~~~~ + +This example uses the APIs as defined in Python 3.5. If you are using an older version of Python you may not have access to the APIs used here. As noted above, please consult the documentation for the :mod:`ssl module ` to confirm. + +.. literalinclude:: ../../examples/fragments/server_https_setup_fragment.py + :language: python + :linenos: + :encoding: utf-8 + + +Client Setup Example +~~~~~~~~~~~~~~~~~~~~ + +The client example is very similar to the server example above. The :class:`SSLContext ` object requires some minor changes, as does the :class:`H2Connection `, but the bulk of the code is the same. + +.. literalinclude:: ../../examples/fragments/client_https_setup_fragment.py + :language: python + :linenos: + :encoding: utf-8 + + +.. _starting-upgrade: + +HTTP URLs (Upgrade) +------------------- + +Starting HTTP/2 for HTTP URLs is outlined in `RFC 7540 Section 3.2`_. In this case, the client and server use the HTTP Upgrade mechanism originally described in `RFC 7230 Section 6.7`_. The client sends its initial HTTP/1.1 request with two extra headers. The first is ``Upgrade: h2c``, which requests upgrade to cleartext HTTP/2. The second is a ``HTTP2-Settings`` header, which contains a specially formatted string that encodes a HTTP/2 Settings frame. + +To do this with Hyper-h2 you have two slightly different flows: one for clients, one for servers. + +Clients +~~~~~~~ + +For a client, when sending the first request you should manually add your ``Upgrade`` header. You should then create a :class:`H2Connection ` object and call :meth:`H2Connection.initiate_upgrade_connection ` with no arguments. This method will return a bytestring to use as the value of your ``HTTP2-Settings`` header. + +If the server returns a ``101`` status code, it has accepted the upgrade, and you should immediately send the data returned by :meth:`H2Connection.data_to_send `. Now you should consume the entire ``101`` header block. All data after the ``101`` header block is HTTP/2 data that should be fed directly to :meth:`H2Connection.receive_data ` and handled as normal with Hyper-h2. + +If the server does not return a ``101`` status code then it is not upgrading. Continue with HTTP/1.1 as normal: you may throw away your :class:`H2Connection ` object, as it is of no further use. + +The server will respond to your original request in HTTP/2. Please pay attention to the events received from Hyper-h2, as they will define the server's response. + +Client Example +^^^^^^^^^^^^^^ + +The code below demonstrates how to handle a plaintext upgrade from the perspective of the client. For the purposes of keeping the example code as simple and generic as possible it uses the synchronous socket API that comes with the Python standard library: if you want to use asynchronous I/O, you will need to translate this code to the appropriate idiom. + +.. literalinclude:: ../../examples/fragments/client_upgrade_fragment.py + :language: python + :linenos: + :encoding: utf-8 + + +Servers +~~~~~~~ + +If the first request you receive on a connection from the client contains an ``Upgrade`` header with the ``h2c`` token in it, and you're willing to upgrade, you should create a :class:`H2Connection ` object and call :meth:`H2Connection.initiate_upgrade_connection ` with the value of the ``HTTP2-Settings`` header (as a bytestring) as the only argument. + +Then, you should send back a ``101`` response that contains ``h2c`` in the ``Upgrade`` header. That response will inform the client that you're switching to HTTP/2. Then, you should immediately send the data that is returned to you by :meth:`H2Connection.data_to_send ` on the connection: this is a necessary part of the HTTP/2 upgrade process. + +At this point, you may now respond to the original HTTP/1.1 request in HTTP/2 by calling the appropriate methods on the :class:`H2Connection ` object. No further HTTP/1.1 may be sent on this connection: from this point onward, all data sent by you and the client will be HTTP/2 data. + +Server Example +^^^^^^^^^^^^^^ + +The code below demonstrates how to handle a plaintext upgrade from the perspective of the server. For the purposes of keeping the example code as simple and generic as possible it uses the synchronous socket API that comes with the Python standard library: if you want to use asynchronous I/O, you will need to translate this code to the appropriate idiom. + +.. literalinclude:: ../../examples/fragments/server_upgrade_fragment.py + :language: python + :linenos: + :encoding: utf-8 + + +Prior Knowledge +--------------- + +It's possible that you as a client know that a particular server supports HTTP/2, and that you do not need to perform any of the negotiations described above. In that case, you may follow the steps in :ref:`starting-alpn`, ignoring all references to ALPN: there's no need to perform the upgrade dance described in :ref:`starting-upgrade`. + +.. _RFC 7540: https://tools.ietf.org/html/rfc7540 +.. _RFC 7540 Section 3.2: https://tools.ietf.org/html/rfc7540#section-3.2 +.. _RFC 7540 Section 3.3: https://tools.ietf.org/html/rfc7540#section-3.3 +.. _ALPN: https://en.wikipedia.org/wiki/Application-Layer_Protocol_Negotiation +.. _RFC 7230 Section 6.7: https://tools.ietf.org/html/rfc7230#section-6.7 diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/release-notes.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/release-notes.rst new file mode 100644 index 0000000000..fa425f1fef --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/release-notes.rst @@ -0,0 +1,101 @@ +Release Notes +============= + +This document contains release notes for Hyper-h2. In addition to the +:ref:`detailed-release-notes` found at the bottom of this document, this +document also includes a high-level prose overview of each major release after +1.0.0. + +High Level Notes +---------------- + +3.0.0: 24 March 2017 +~~~~~~~~~~~~~~~~~~~~ + +The Hyper-h2 team and the Hyper project are delighted to announce the release +of Hyper-h2 version 3.0.0! Unlike the really notable 2.0.0 release, this +release is proportionally quite small: however, it has the effect of removing a +lot of cruft and complexity that has built up in the codebase over the lifetime +of the v2 release series. + +This release was motivated primarily by discovering that applications that +attempted to use both HTTP/1.1 and HTTP/2 using hyper-h2 would encounter +problems with cookies, because hyper-h2 did not join together cookie headers as +required by RFC 7540. Normally adding such behaviour would be a non-breaking +change, but we previously had no flags to prevent normalization of received +HTTP headers. + +Because it makes no sense for the cookie to be split *by default*, we needed to +add a controlling flag and set it to true. The breaking nature of this change +is very subtle, and it's possible most users would never notice, but +nevertheless it *is* a breaking change and we need to treat it as such. + +Happily, we can take this opportunity to finalise a bunch of deprecations we'd +made over the past year. The v2 release series was long-lived and successful, +having had a series of releases across the past year-and-a-bit, and the Hyper +team are very proud of it. However, it's time to open a new chapter, and remove +the deprecated code. + +The past year has been enormously productive for the Hyper team. A total of 30 +v2 releases were made, an enormous amount of work. A good number of people have +made their first contribution in this time, more than I can thank reasonably +without taking up an unreasonable amount of space in this document, so instead +I invite you to check out `our awesome contributor list`_. + +We're looking forward to the next chapter in hyper-h2: it's been a fun ride so +far, and we hope even more of you come along and join in the fun over the next +year! + +.. _our awesome contributor list: https://github.com/python-hyper/hyper-h2/graphs/contributors + + +2.0.0: 25 January 2016 +~~~~~~~~~~~~~~~~~~~~~~ + +The Hyper-h2 team and the Hyper project are delighted to announce the release +of Hyper-h2 version 2.0.0! This is an enormous release that contains a gigantic +collection of new features and fixes, with the goal of making it easier than +ever to use Hyper-h2 to build a compliant HTTP/2 server or client. + +An enormous chunk of this work has been focused on tighter enforcement of +restrictions in RFC 7540, ensuring that we correctly police the actions of +remote peers, and error appropriately when those peers violate the +specification. Several of these constitute breaking changes, because data that +was previously received and handled without obvious error now raises +``ProtocolError`` exceptions and causes the connection to be terminated. + +Additionally, the public API was cleaned up and had several helper methods that +had been inavertently exposed removed from the public API. The team wants to +stress that while Hyper-h2 follows semantic versioning, the guarantees of +semver apply only to the public API as documented in :doc:`api`. Reducing the +surface area of these APIs makes it easier for us to continue to ensure that +the guarantees of semver are respected on our public API. + +We also attempted to clear up some of the warts that had appeared in the API, +and add features that are helpful for implementing HTTP/2 endpoints. For +example, the :class:`H2Connection ` object now +exposes a method for generating the next stream ID that your client or server +can use to initiate a connection (:meth:`get_next_available_stream_id +`). We also removed +some needless return values that were guaranteed to return empty lists, which +were an attempt to make a forward-looking guarantee that was entirely unneeded. + +Altogether, this has been an extremely productive period for Hyper-h2, and a +lot of great work has been done by the community. To that end, we'd also like +to extend a great thankyou to those contributors who made their first contribution +to the project between release 1.0.0 and 2.0.0. Many thanks to: +`Thomas Kriechbaumer`_, `Alex Chan`_, `Maximilian Hils`_, and `Glyph`_. For a +full historical list of contributors, see :doc:`contributors`. + +We're looking forward to the next few months of Python HTTP/2 work, and hoping +that you'll find lots of excellent HTTP/2 applications to build with Hyper-h2! + + +.. _Thomas Kriechbaumer: https://github.com/Kriechi +.. _Alex Chan: https://github.com/alexwlchan +.. _Maximilian Hils: https://github.com/mhils +.. _Glyph: https://github.com/glyph + + +.. _detailed-release-notes: +.. include:: ../../HISTORY.rst diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/release-process.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/release-process.rst new file mode 100644 index 0000000000..e7b46064d5 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/release-process.rst @@ -0,0 +1,56 @@ +Release Process +=============== + +Because of Hyper-h2's place at the bottom of the dependency tree, it is +extremely important that the project maintains a diligent release schedule. +This document outlines our process for managing releases. + +Versioning +---------- + +Hyper-h2 follows `semantic versioning`_ of its public API when it comes to +numbering releases. The public API of Hyper-h2 is strictly limited to the +entities listed in the :doc:`api` documentation: anything not mentioned in that +document is not considered part of the public API and is not covered by the +versioning guarantees given by semantic versioning. + +Maintenance +----------- + +Hyper-h2 has the notion of a "release series", given by a major and minor +version number: for example, there is the 2.1 release series. When each minor +release is made and a release series is born, a branch is made off the release +tag: for example, for the 2.1 release series, the 2.1.X branch. + +All changes merged into the master branch will be evaluated for whether they +can be considered 'bugfixes' only (that is, they do not affect the public API). +If they can, they will also be cherry-picked back to all active maintenance +branches that require the bugfix. If the bugfix is not necessary, because the +branch in question is unaffected by that bug, the bugfix will not be +backported. + +Supported Release Series' +------------------------- + +The developers of Hyper-h2 commit to supporting the following release series: + +- The most recent, as identified by the first two numbers in the highest + version currently released. +- The immediately prior release series. + +The only exception to this policy is that no release series earlier than the +2.1 series will be supported. In this context, "supported" means that they will +continue to receive bugfix releases. + +For releases other than the ones identified above, no support is guaranteed. +The developers may *choose* to support such a release series, but they do not +promise to. + +The exception here is for security vulnerabilities. If a security vulnerability +is identified in an out-of-support release series, the developers will do their +best to patch it and issue an emergency release. For more information, see +`our security documentation`_. + + +.. _semantic versioning: http://semver.org/ +.. _our security documentation: http://python-hyper.org/en/latest/security.html diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/testimonials.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/testimonials.rst new file mode 100644 index 0000000000..ec32fb9572 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/testimonials.rst @@ -0,0 +1,9 @@ +Testimonials +============ + +Glyph Lefkowitz +~~~~~~~~~~~~~~~ + +Frankly, Hyper-h2 is almost SURREAL in how well-factored and decoupled the implementation is from I/O. If libraries in the Python ecosystem looked like this generally, Twisted would be a much better platform than it is. (Frankly, most of Twisted's _own_ protocol implementations should aspire to such cleanliness.) + +(`Source `_) diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/tornado-example.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/tornado-example.rst new file mode 100644 index 0000000000..c7a80713a1 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/tornado-example.rst @@ -0,0 +1,16 @@ +Tornado Example Server +====================== + +This example is a basic HTTP/2 server written using the `Tornado`_ asynchronous +networking library. + +The server returns the request headers as a JSON document to the caller, just +like the example from the :doc:`basic-usage` document. + +.. literalinclude:: ../../examples/tornado/tornado-server.py + :language: python + :linenos: + :encoding: utf-8 + + +.. _Tornado: http://www.tornadoweb.org/ diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-example.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-example.rst new file mode 100644 index 0000000000..10d111628b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-example.rst @@ -0,0 +1,18 @@ +Twisted Example Server +====================== + +This example is a basic HTTP/2 server written for the `Twisted`_ asynchronous +networking framework. This is a relatively fleshed out example, and in +particular it makes sure to obey HTTP/2 flow control rules. + +This server differs from some of the other example servers by serving files, +rather than simply sending JSON responses. This makes the example lengthier, +but also brings it closer to a real-world use-case. + +.. literalinclude:: ../../examples/twisted/twisted-server.py + :language: python + :linenos: + :encoding: utf-8 + + +.. _Twisted: https://twistedmatrix.com/ \ No newline at end of file diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-head-example.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-head-example.rst new file mode 100644 index 0000000000..df93b144e7 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-head-example.rst @@ -0,0 +1,17 @@ +Twisted Example Client: Head Requests +===================================== + +This example is a basic HTTP/2 client written for the `Twisted`_ asynchronous +networking framework. + +This client is fairly simple: it makes a hard-coded HEAD request to +nghttp2.org/httpbin/ and prints out the response data. Its purpose is to demonstrate +how to write a very basic HTTP/2 client implementation. + +.. literalinclude:: ../../examples/twisted/head_request.py + :language: python + :linenos: + :encoding: utf-8 + + +.. _Twisted: https://twistedmatrix.com/ diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-post-example.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-post-example.rst new file mode 100644 index 0000000000..7e3aba41a3 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/twisted-post-example.rst @@ -0,0 +1,18 @@ +Twisted Example Client: Post Requests +===================================== + +This example is a basic HTTP/2 client written for the `Twisted`_ asynchronous +networking framework. + +This client is fairly simple: it makes a hard-coded POST request to +nghttp2.org/httpbin/post and prints out the response data, sending a file that is provided +on the command line or the script itself. Its purpose is to demonstrate how to +write a HTTP/2 client implementation that handles flow control. + +.. literalinclude:: ../../examples/twisted/post_request.py + :language: python + :linenos: + :encoding: utf-8 + + +.. _Twisted: https://twistedmatrix.com/ diff --git a/testing/web-platform/tests/tools/third_party/h2/docs/source/wsgi-example.rst b/testing/web-platform/tests/tools/third_party/h2/docs/source/wsgi-example.rst new file mode 100644 index 0000000000..82513899b2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/docs/source/wsgi-example.rst @@ -0,0 +1,23 @@ +Example HTTP/2-only WSGI Server +=============================== + +This example is a more complex HTTP/2 server that acts as a WSGI server, +passing data to an arbitrary WSGI application. This example is written using +`asyncio`_. The server supports most of PEP-3333, and so could in principle be +used as a production WSGI server: however, that's *not recommended* as certain +shortcuts have been taken to ensure ease of implementation and understanding. + +The main advantages of this example are: + +1. It properly demonstrates HTTP/2 flow control management. +2. It demonstrates how to plug hyper-h2 into a larger, more complex + application. + + +.. literalinclude:: ../../examples/asyncio/wsgi-server.py + :language: python + :linenos: + :encoding: utf-8 + + +.. _asyncio: https://docs.python.org/3/library/asyncio.html diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/asyncio-server.py b/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/asyncio-server.py new file mode 100644 index 0000000000..278774644b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/asyncio-server.py @@ -0,0 +1,210 @@ +# -*- coding: utf-8 -*- +""" +asyncio-server.py +~~~~~~~~~~~~~~~~~ + +A fully-functional HTTP/2 server using asyncio. Requires Python 3.5+. + +This example demonstrates handling requests with bodies, as well as handling +those without. In particular, it demonstrates the fact that DataReceived may +be called multiple times, and that applications must handle that possibility. +""" +import asyncio +import io +import json +import ssl +import collections +from typing import List, Tuple + +from h2.config import H2Configuration +from h2.connection import H2Connection +from h2.events import ( + ConnectionTerminated, DataReceived, RemoteSettingsChanged, + RequestReceived, StreamEnded, StreamReset, WindowUpdated +) +from h2.errors import ErrorCodes +from h2.exceptions import ProtocolError, StreamClosedError +from h2.settings import SettingCodes + + +RequestData = collections.namedtuple('RequestData', ['headers', 'data']) + + +class H2Protocol(asyncio.Protocol): + def __init__(self): + config = H2Configuration(client_side=False, header_encoding='utf-8') + self.conn = H2Connection(config=config) + self.transport = None + self.stream_data = {} + self.flow_control_futures = {} + + def connection_made(self, transport: asyncio.Transport): + self.transport = transport + self.conn.initiate_connection() + self.transport.write(self.conn.data_to_send()) + + def connection_lost(self, exc): + for future in self.flow_control_futures.values(): + future.cancel() + self.flow_control_futures = {} + + def data_received(self, data: bytes): + try: + events = self.conn.receive_data(data) + except ProtocolError as e: + self.transport.write(self.conn.data_to_send()) + self.transport.close() + else: + self.transport.write(self.conn.data_to_send()) + for event in events: + if isinstance(event, RequestReceived): + self.request_received(event.headers, event.stream_id) + elif isinstance(event, DataReceived): + self.receive_data(event.data, event.stream_id) + elif isinstance(event, StreamEnded): + self.stream_complete(event.stream_id) + elif isinstance(event, ConnectionTerminated): + self.transport.close() + elif isinstance(event, StreamReset): + self.stream_reset(event.stream_id) + elif isinstance(event, WindowUpdated): + self.window_updated(event.stream_id, event.delta) + elif isinstance(event, RemoteSettingsChanged): + if SettingCodes.INITIAL_WINDOW_SIZE in event.changed_settings: + self.window_updated(None, 0) + + self.transport.write(self.conn.data_to_send()) + + def request_received(self, headers: List[Tuple[str, str]], stream_id: int): + headers = collections.OrderedDict(headers) + method = headers[':method'] + + # Store off the request data. + request_data = RequestData(headers, io.BytesIO()) + self.stream_data[stream_id] = request_data + + def stream_complete(self, stream_id: int): + """ + When a stream is complete, we can send our response. + """ + try: + request_data = self.stream_data[stream_id] + except KeyError: + # Just return, we probably 405'd this already + return + + headers = request_data.headers + body = request_data.data.getvalue().decode('utf-8') + + data = json.dumps( + {"headers": headers, "body": body}, indent=4 + ).encode("utf8") + + response_headers = ( + (':status', '200'), + ('content-type', 'application/json'), + ('content-length', str(len(data))), + ('server', 'asyncio-h2'), + ) + self.conn.send_headers(stream_id, response_headers) + asyncio.ensure_future(self.send_data(data, stream_id)) + + def receive_data(self, data: bytes, stream_id: int): + """ + We've received some data on a stream. If that stream is one we're + expecting data on, save it off. Otherwise, reset the stream. + """ + try: + stream_data = self.stream_data[stream_id] + except KeyError: + self.conn.reset_stream( + stream_id, error_code=ErrorCodes.PROTOCOL_ERROR + ) + else: + stream_data.data.write(data) + + def stream_reset(self, stream_id): + """ + A stream reset was sent. Stop sending data. + """ + if stream_id in self.flow_control_futures: + future = self.flow_control_futures.pop(stream_id) + future.cancel() + + async def send_data(self, data, stream_id): + """ + Send data according to the flow control rules. + """ + while data: + while self.conn.local_flow_control_window(stream_id) < 1: + try: + await self.wait_for_flow_control(stream_id) + except asyncio.CancelledError: + return + + chunk_size = min( + self.conn.local_flow_control_window(stream_id), + len(data), + self.conn.max_outbound_frame_size, + ) + + try: + self.conn.send_data( + stream_id, + data[:chunk_size], + end_stream=(chunk_size == len(data)) + ) + except (StreamClosedError, ProtocolError): + # The stream got closed and we didn't get told. We're done + # here. + break + + self.transport.write(self.conn.data_to_send()) + data = data[chunk_size:] + + async def wait_for_flow_control(self, stream_id): + """ + Waits for a Future that fires when the flow control window is opened. + """ + f = asyncio.Future() + self.flow_control_futures[stream_id] = f + await f + + def window_updated(self, stream_id, delta): + """ + A window update frame was received. Unblock some number of flow control + Futures. + """ + if stream_id and stream_id in self.flow_control_futures: + f = self.flow_control_futures.pop(stream_id) + f.set_result(delta) + elif not stream_id: + for f in self.flow_control_futures.values(): + f.set_result(delta) + + self.flow_control_futures = {} + + +ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) +ssl_context.options |= ( + ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_COMPRESSION +) +ssl_context.load_cert_chain(certfile="cert.crt", keyfile="cert.key") +ssl_context.set_alpn_protocols(["h2"]) + +loop = asyncio.get_event_loop() +# Each client connection will create a new protocol instance +coro = loop.create_server(H2Protocol, '127.0.0.1', 8443, ssl=ssl_context) +server = loop.run_until_complete(coro) + +# Serve requests until Ctrl+C is pressed +print('Serving on {}'.format(server.sockets[0].getsockname())) +try: + loop.run_forever() +except KeyboardInterrupt: + pass + +# Close the server +server.close() +loop.run_until_complete(server.wait_closed()) +loop.close() diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/cert.crt b/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/cert.crt new file mode 100644 index 0000000000..d6cf7d504d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/cert.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhTCCAm2gAwIBAgIJAOrxh0dOYJLdMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTA5MTkxNDE2 +NDRaFw0xNTEwMTkxNDE2NDRaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l +LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV +BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqt +A1iu8EN00FU0eBcBGlLVmNEgV7Jkbukra+kwS8j/U2y50QPGJc/FiIVDfuBqk5dL +ACTNc6A/FQcXvWmOc5ixmC3QKKasMpuofqKz0V9C6irZdYXZ9rcsW0gHQIr989yd +R+N1VbIlEVW/T9FJL3B2UD9GVIkUELzm47CSOWZvAxQUlsx8CUNuUCWqyZJoqTFN +j0LeJDOWGCsug1Pkj0Q1x+jMVL6l6Zf6vMkLNOMsOsWsxUk+0L3tl/OzcTgUOCsw +UzY59RIi6Rudrp0oaU8NuHr91yiSqPbKFlX10M9KwEEdnIpcxhND3dacrDycj3ux +eWlqKync2vOFUkhwiaMCAwEAAaNQME4wHQYDVR0OBBYEFA0PN+PGoofZ+QIys2Jy +1Zz94vBOMB8GA1UdIwQYMBaAFA0PN+PGoofZ+QIys2Jy1Zz94vBOMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEplethBoPpcP3EbR5Rz6snDDIcbtAJu +Ngd0YZppGT+P0DYnPJva4vRG3bb84ZMSuppz5j67qD6DdWte8UXhK8BzWiHzwmQE +QmbKyzzTMKQgTNFntpx5cgsSvTtrHpNYoMHzHOmyAOboNeM0DWiRXsYLkWTitLTN +qbOpstwPubExbT9lPjLclntShT/lCupt+zsbnrR9YiqlYFY/fDzfAybZhrD5GMBY +XdMPItwAc/sWvH31yztarjkLmld76AGCcO5r8cSR/cX98SicyfjOBbSco8GkjYNY +582gTPkKGYpStuN7GNT5tZmxvMq935HRa2XZvlAIe8ufp8EHVoYiF3c= +-----END CERTIFICATE----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/cert.key b/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/cert.key new file mode 100644 index 0000000000..bda69e836c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/cert.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyq0DWK7wQ3TQVTR4FwEaUtWY0SBXsmRu6Str6TBLyP9TbLnR +A8Ylz8WIhUN+4GqTl0sAJM1zoD8VBxe9aY5zmLGYLdAopqwym6h+orPRX0LqKtl1 +hdn2tyxbSAdAiv3z3J1H43VVsiURVb9P0UkvcHZQP0ZUiRQQvObjsJI5Zm8DFBSW +zHwJQ25QJarJkmipMU2PQt4kM5YYKy6DU+SPRDXH6MxUvqXpl/q8yQs04yw6xazF +ST7Qve2X87NxOBQ4KzBTNjn1EiLpG52unShpTw24ev3XKJKo9soWVfXQz0rAQR2c +ilzGE0Pd1pysPJyPe7F5aWorKdza84VSSHCJowIDAQABAoIBACp+nh4BB/VMz8Wd +q7Q/EfLeQB1Q57JKpoqTBRwueSVai3ZXe4CMEi9/HkG6xiZtkiZ9njkZLq4hq9oB +2z//kzMnwV2RsIRJxI6ohGy+wR51HD4BvEdlTPpY/Yabpqe92VyfSYxidKZWaU0O +QMED1EODOw4ZQ+4928iPrJu//PMB4e7TFao0b9Fk/XLWtu5/tQZz9jsrlTi1zthh +7n+oaGNhfTeIJJL4jrhTrKW1CLHXATtr9SJlfZ3wbMxQVeyj2wUlP1V0M6kBuhNj +tbGbMpixD5iCNJ49Cm2PHg+wBOfS3ADGIpi3PcGw5mb8nB3N9eGBRPhLShAlq5Hi +Lv4tyykCgYEA8u3b3xJ04pxWYN25ou/Sc8xzgDCK4XvDNdHVTuZDjLVA+VTVPzql +lw7VvJArsx47MSPvsaX/+4hQXYtfnR7yJpx6QagvQ+z4ludnIZYrQwdUmb9pFL1s +8UNj+3j9QFRPenIiIQ8qxxNIQ9w2HsVQ8scvc9CjYop/YYAPaQyHaL8CgYEA1ZSz +CR4NcpfgRSILdhb1dLcyw5Qus1VOSAx3DYkhDkMiB8XZwgMdJjwehJo9yaqRCLE8 +Sw5znMnkfoZpu7+skrjK0FqmMpXMH9gIszHvFG8wSw/6+2HIWS19/wOu8dh95LuC +0zurMk8rFqxgWMWF20afhgYrUz42cvUTo10FVB0CgYEAt7mW6W3PArfUSCxIwmb4 +VmXREKkl0ATHDYQl/Cb//YHzot467TgQll883QB4XF5HzBFurX9rSzO7/BN1e6I0 +52i+ubtWC9xD4fUetXMaQvZfUGxIL8xXgVxDWKQXfLiG54c8Mp6C7s6xf8kjEUCP +yR1F0SSA/Pzb+8RbY0p7eocCgYA+1rs+SXtHZev0KyoYGnUpW+Uxqd17ofOgOxqj +/t6c5Z+TjeCdtnDTGQkZlo/rT6XQWuUUaDIXxUbW+xEMzj4mBPyXBLS1WWFvVQ5q +OpzO9E/PJeqAH6rkof/aEelc+oc/zvOU1o9uA+D3kMvgEm1psIOq2RHSMhGvDPA0 +NmAk+QKBgQCwd1681GagdIYSZUCBecnLtevXmIsJyDW2yR1NNcIe/ukcVQREMDvy +5DDkhnGDgnV1D5gYcXb34g9vYvbfTnBMl/JXmMAAG1kIS+3pvHyN6f1poVe3yJV1 +yHVuvymnJxKnyaV0L3ntepVvV0vVNIkA3oauoUTLto6txBI+b/ImDA== +-----END RSA PRIVATE KEY----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/wsgi-server.py b/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/wsgi-server.py new file mode 100644 index 0000000000..9fdb2fa0fc --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/asyncio/wsgi-server.py @@ -0,0 +1,760 @@ +# -*- coding: utf-8 -*- +""" +asyncio-server.py +~~~~~~~~~~~~~~~~~ + +A fully-functional WSGI server, written using hyper-h2. Requires asyncio. + +To test it, try installing httpbin from pip (``pip install httpbin``) and then +running the server (``python asyncio-server.py httpbin:app``). + +This server does not support HTTP/1.1: it is a HTTP/2-only WSGI server. The +purpose of this code is to demonstrate how to integrate hyper-h2 into a more +complex application, and to demonstrate several principles of concurrent +programming. + +The architecture looks like this: + ++---------------------------------+ +| 1x HTTP/2 Server Thread | +| (running asyncio) | ++---------------------------------+ ++---------------------------------+ +| N WSGI Application Threads | +| (no asyncio) | ++---------------------------------+ + +Essentially, we spin up an asyncio-based event loop in the main thread. This +launches one HTTP/2 Protocol instance for each inbound connection, all of which +will read and write data from within the main thread in an asynchronous manner. + +When each HTTP request comes in, the server will build the WSGI environment +dictionary and create a ``Stream`` object. This object will hold the relevant +state for the request/response pair and will act as the WSGI side of the logic. +That object will then be passed to a background thread pool, and when a worker +is available the WSGI logic will begin to be executed. This model ensures that +the asyncio web server itself is never blocked by the WSGI application. + +The WSGI application and the HTTP/2 server communicate via an asyncio queue, +together with locks and threading events. The locks themselves are implicit in +asyncio's "call_soon_threadsafe", which allows for a background thread to +register an action with the main asyncio thread. When the asyncio thread +eventually takes the action in question it sets as threading event, signaling +to the background thread that it is free to continue its work. + +To make the WSGI application work with flow control, there is a very important +invariant that must be observed. Any WSGI action that would cause data to be +emitted to the network MUST be accompanied by a threading Event that is not +set until that data has been written to the transport. This ensures that the +WSGI application *blocks* until the data is actually sent. The reason we +require this invariant is that the HTTP/2 server may choose to re-order some +data chunks for flow control reasons: that is, the application for stream X may +have actually written its data first, but the server may elect to send the data +for stream Y first. This means that it's vital that there not be *two* writes +for stream X active at any one point or they may get reordered, which would be +particularly terrible. + +Thus, the server must cooperate to ensure that each threading event only fires +when the *complete* data for that event has been written to the asyncio +transport. Any earlier will cause untold craziness. +""" +import asyncio +import importlib +import queue +import ssl +import sys +import threading + +from h2.config import H2Configuration +from h2.connection import H2Connection +from h2.events import ( + DataReceived, RequestReceived, WindowUpdated, StreamEnded, StreamReset +) + + +# Used to signal that a request has completed. +# +# This is a convenient way to do "in-band" signaling of stream completion +# without doing anything so heavyweight as using a class. Essentially, we can +# test identity against this empty object. In fact, this is so convenient that +# we use this object for all streams, for data in both directions: in and out. +END_DATA_SENTINEL = object() + +# The WSGI callable. Stored here so that the protocol instances can get hold +# of the data. +APPLICATION = None + + +class H2Protocol(asyncio.Protocol): + def __init__(self): + config = H2Configuration(client_side=False, header_encoding='utf-8') + + # Our server-side state machine. + self.conn = H2Connection(config=config) + + # The backing transport. + self.transport = None + + # A dictionary of ``Stream`` objects, keyed by their stream ID. This + # makes it easy to route data to the correct WSGI application instance. + self.streams = {} + + # A queue of data emitted by WSGI applications that has not yet been + # sent. Each stream may only have one chunk of data in either this + # queue or the flow_controlled_data dictionary at any one time. + self._stream_data = asyncio.Queue() + + # Data that has been pulled off the queue that is for a stream blocked + # behind flow control limitations. This is used to avoid spinning on + # _stream_data queue when a stream cannot have its data sent. Data that + # cannot be sent on the connection when it is popped off the queue gets + # placed here until the stream flow control window opens up again. + self._flow_controlled_data = {} + + # A reference to the loop in which this protocol runs. This is needed + # to synchronise up with background threads. + self._loop = asyncio.get_event_loop() + + # Any streams that have been remotely reset. We keep track of these to + # ensure that we don't emit data from a WSGI application whose stream + # has been cancelled. + self._reset_streams = set() + + # Keep track of the loop sending task so we can kill it when the + # connection goes away. + self._send_loop_task = None + + def connection_made(self, transport): + """ + The connection has been made. Here we need to save off our transport, + do basic HTTP/2 connection setup, and then start our data writing + coroutine. + """ + self.transport = transport + self.conn.initiate_connection() + self.transport.write(self.conn.data_to_send()) + self._send_loop_task = self._loop.create_task(self.sending_loop()) + + def connection_lost(self, exc): + """ + With the end of the connection, we just want to cancel our data sending + coroutine. + """ + self._send_loop_task.cancel() + + def data_received(self, data): + """ + Process inbound data. + """ + events = self.conn.receive_data(data) + + for event in events: + if isinstance(event, RequestReceived): + self.request_received(event) + elif isinstance(event, DataReceived): + self.data_frame_received(event) + elif isinstance(event, WindowUpdated): + self.window_opened(event) + elif isinstance(event, StreamEnded): + self.end_stream(event) + elif isinstance(event, StreamReset): + self.reset_stream(event) + + outbound_data = self.conn.data_to_send() + if outbound_data: + self.transport.write(outbound_data) + + def window_opened(self, event): + """ + The flow control window got opened. + + This is important because it's possible that we were unable to send + some WSGI data because the flow control window was too small. If that + happens, the sending_loop coroutine starts buffering data. + + As the window gets opened, we need to unbuffer the data. We do that by + placing the data chunks back on the back of the send queue and letting + the sending loop take another shot at sending them. + + This system only works because we require that each stream only have + *one* data chunk in the sending queue at any time. The threading events + force this invariant to remain true. + """ + if event.stream_id: + # This is specific to a single stream. + if event.stream_id in self._flow_controlled_data: + self._stream_data.put_nowait( + self._flow_controlled_data.pop(event.stream_id) + ) + else: + # This event is specific to the connection. Free up *all* the + # streams. This is a bit tricky, but we *must not* yield the flow + # of control here or it all goes wrong. + for data in self._flow_controlled_data.values(): + self._stream_data.put_nowait(data) + + self._flow_controlled_data = {} + + @asyncio.coroutine + def sending_loop(self): + """ + A call that loops forever, attempting to send data. This sending loop + contains most of the flow-control smarts of this class: it pulls data + off of the asyncio queue and then attempts to send it. + + The difficulties here are all around flow control. Specifically, a + chunk of data may be too large to send. In this case, what will happen + is that this coroutine will attempt to send what it can and will then + store the unsent data locally. When a flow control event comes in that + data will be freed up and placed back onto the asyncio queue, causing + it to pop back up into the sending logic of this coroutine. + + This method explicitly *does not* handle HTTP/2 priority. That adds an + extra layer of complexity to what is already a fairly complex method, + and we'll look at how to do it another time. + + This coroutine explicitly *does not end*. + """ + while True: + stream_id, data, event = yield from self._stream_data.get() + + # If this stream got reset, just drop the data on the floor. Note + # that we need to reset the event here to make sure that + # application doesn't lock up. + if stream_id in self._reset_streams: + event.set() + + # Check if the body is done. If it is, this is really easy! Again, + # we *must* set the event here or the application will lock up. + if data is END_DATA_SENTINEL: + self.conn.end_stream(stream_id) + self.transport.write(self.conn.data_to_send()) + event.set() + continue + + # We need to send data, but not to exceed the flow control window. + # For that reason, grab only the data that fits: we'll buffer the + # rest. + window_size = self.conn.local_flow_control_window(stream_id) + chunk_size = min(window_size, len(data)) + data_to_send = data[:chunk_size] + data_to_buffer = data[chunk_size:] + + if data_to_send: + # There's a maximum frame size we have to respect. Because we + # aren't paying any attention to priority here, we can quite + # safely just split this string up into chunks of max frame + # size and blast them out. + # + # In a *real* application you'd want to consider priority here. + max_size = self.conn.max_outbound_frame_size + chunks = ( + data_to_send[x:x+max_size] + for x in range(0, len(data_to_send), max_size) + ) + for chunk in chunks: + self.conn.send_data(stream_id, chunk) + self.transport.write(self.conn.data_to_send()) + + # If there's data left to buffer, we should do that. Put it in a + # dictionary and *don't set the event*: the app must not generate + # any more data until we got rid of all of this data. + if data_to_buffer: + self._flow_controlled_data[stream_id] = ( + stream_id, data_to_buffer, event + ) + else: + # We sent everything. We can let the WSGI app progress. + event.set() + + def request_received(self, event): + """ + A HTTP/2 request has been received. We need to invoke the WSGI + application in a background thread to handle it. + """ + # First, we are going to want an object to hold all the relevant state + # for this request/response. For that, we have a stream object. We + # need to store the stream object somewhere reachable for when data + # arrives later. + s = Stream(event.stream_id, self) + self.streams[event.stream_id] = s + + # Next, we need to build the WSGI environ dictionary. + environ = _build_environ_dict(event.headers, s) + + # Finally, we want to throw these arguments out to a threadpool and + # let it run. + self._loop.run_in_executor( + None, + s.run_in_threadpool, + APPLICATION, + environ, + ) + + def data_frame_received(self, event): + """ + Data has been received by WSGI server and needs to be dispatched to a + running application. + + Note that the flow control window is not modified here. That's + deliberate: see Stream.__next__ for a longer discussion of why. + """ + # Grab the stream in question from our dictionary and pass it on. + stream = self.streams[event.stream_id] + stream.receive_data(event.data, event.flow_controlled_length) + + def end_stream(self, event): + """ + The stream data is complete. + """ + stream = self.streams[event.stream_id] + stream.request_complete() + + def reset_stream(self, event): + """ + A stream got forcefully reset. + + This is a tricky thing to deal with because WSGI doesn't really have a + good notion for it. Essentially, you have to let the application run + until completion, but not actually let it send any data. + + We do that by discarding any data we currently have for it, and then + marking the stream as reset to allow us to spot when that stream is + trying to send data and drop that data on the floor. + + We then *also* signal the WSGI application that no more data is + incoming, to ensure that it does not attempt to do further reads of the + data. + """ + if event.stream_id in self._flow_controlled_data: + del self._flow_controlled_data + + self._reset_streams.add(event.stream_id) + self.end_stream(event) + + def data_for_stream(self, stream_id, data): + """ + Thread-safe method called from outside the main asyncio thread in order + to send data on behalf of a WSGI application. + + Places data being written by a stream on an asyncio queue. Returns a + threading event that will fire when that data is sent. + """ + event = threading.Event() + self._loop.call_soon_threadsafe( + self._stream_data.put_nowait, + (stream_id, data, event) + ) + return event + + def send_response(self, stream_id, headers): + """ + Thread-safe method called from outside the main asyncio thread in order + to send the HTTP response headers on behalf of a WSGI application. + + Returns a threading event that will fire when the headers have been + emitted to the network. + """ + event = threading.Event() + + def _inner_send(stream_id, headers, event): + self.conn.send_headers(stream_id, headers, end_stream=False) + self.transport.write(self.conn.data_to_send()) + event.set() + + self._loop.call_soon_threadsafe( + _inner_send, + stream_id, + headers, + event + ) + return event + + def open_flow_control_window(self, stream_id, increment): + """ + Opens a flow control window for the given stream by the given amount. + Called from a WSGI thread. Does not return an event because there's no + need to block on this action, it may take place at any time. + """ + def _inner_open(stream_id, increment): + self.conn.increment_flow_control_window(increment, stream_id) + self.conn.increment_flow_control_window(increment, None) + self.transport.write(self.conn.data_to_send()) + + self._loop.call_soon_threadsafe( + _inner_open, + stream_id, + increment, + ) + + +class Stream: + """ + This class holds all of the state for a single stream. It also provides + several of the callables used by the WSGI application. Finally, it provides + the logic for actually interfacing with the WSGI application. + + For these reasons, the object has *strict* requirements on thread-safety. + While the object can be initialized in the main WSGI thread, the + ``run_in_threadpool`` method *must* be called from outside that thread. At + that point, the main WSGI thread may only call specific methods. + """ + def __init__(self, stream_id, protocol): + self.stream_id = stream_id + self._protocol = protocol + + # Queue for data that has been received from the network. This is a + # thread-safe queue, to allow both the WSGI application to block on + # receiving more data and to allow the asyncio server to keep sending + # more data. + # + # This queue is unbounded in size, but in practice it cannot contain + # too much data because the flow control window doesn't get adjusted + # unless data is removed from it. + self._received_data = queue.Queue() + + # This buffer is used to hold partial chunks of data from + # _received_data that were not returned out of ``read`` and friends. + self._temp_buffer = b'' + + # Temporary variables that allow us to keep hold of the headers and + # response status until such time as the application needs us to send + # them. + self._response_status = b'' + self._response_headers = [] + self._headers_emitted = False + + # Whether the application has received all the data from the network + # or not. This allows us to short-circuit some reads. + self._complete = False + + def receive_data(self, data, flow_controlled_size): + """ + Called by the H2Protocol when more data has been received from the + network. + + Places the data directly on the queue in a thread-safe manner without + blocking. Does not introspect or process the data. + """ + self._received_data.put_nowait((data, flow_controlled_size)) + + def request_complete(self): + """ + Called by the H2Protocol when all the request data has been received. + + This works by placing the ``END_DATA_SENTINEL`` on the queue. The + reading code knows, when it sees the ``END_DATA_SENTINEL``, to expect + no more data from the network. This ensures that the state of the + application only changes when it has finished processing the data from + the network, even though the server may have long-since finished + receiving all the data for this request. + """ + self._received_data.put_nowait((END_DATA_SENTINEL, None)) + + def run_in_threadpool(self, wsgi_application, environ): + """ + This method should be invoked in a threadpool. At the point this method + is invoked, the only safe methods to call from the original thread are + ``receive_data`` and ``request_complete``: any other method is unsafe. + + This method handles the WSGI logic. It invokes the application callable + in this thread, passing control over to the WSGI application. It then + ensures that the data makes it back to the HTTP/2 connection via + the thread-safe APIs provided below. + """ + result = wsgi_application(environ, self.start_response) + + try: + for data in result: + self.write(data) + finally: + # This signals that we're done with data. The server will know that + # this allows it to clean up its state: we're done here. + self.write(END_DATA_SENTINEL) + + # The next few methods are called by the WSGI application. Firstly, the + # three methods provided by the input stream. + def read(self, size=None): + """ + Called by the WSGI application to read data. + + This method is the one of two that explicitly pumps the input data + queue, which means it deals with the ``_complete`` flag and the + ``END_DATA_SENTINEL``. + """ + # If we've already seen the END_DATA_SENTINEL, return immediately. + if self._complete: + return b'' + + # If we've been asked to read everything, just iterate over ourselves. + if size is None: + return b''.join(self) + + # Otherwise, as long as we don't have enough data, spin looking for + # another data chunk. + data = b'' + while len(data) < size: + try: + chunk = next(self) + except StopIteration: + break + + # Concatenating strings this way is slow, but that's ok, this is + # just a demo. + data += chunk + + # We have *at least* enough data to return, but we may have too much. + # If we do, throw it on a buffer: we'll use it later. + to_return = data[:size] + self._temp_buffer = data[size:] + return to_return + + def readline(self, hint=None): + """ + Called by the WSGI application to read a single line of data. + + This method rigorously observes the ``hint`` parameter: it will only + ever read that much data. It then splits the data on a newline + character and throws everything it doesn't need into a buffer. + """ + data = self.read(hint) + first_newline = data.find(b'\n') + if first_newline == -1: + # No newline, return all the data + return data + + # We want to slice the data so that the head *includes* the first + # newline. Then, any data left in this line we don't care about should + # be prepended to the internal buffer. + head, tail = data[:first_newline + 1], data[first_newline + 1:] + self._temp_buffer = tail + self._temp_buffer + + return head + + def readlines(self, hint=None): + """ + Called by the WSGI application to read several lines of data. + + This method is really pretty stupid. It rigorously observes the + ``hint`` parameter, and quite happily returns the input split into + lines. + """ + # This method is *crazy inefficient*, but it's also a pretty stupid + # method to call. + data = self.read(hint) + lines = data.split(b'\n') + + # Split removes the newline character, but we want it, so put it back. + lines = [line + b'\n' for line in lines] + + # Except if the last character was a newline character we now have an + # extra line that is just a newline: pull that out. + if lines[-1] == b'\n': + lines = lines[:-1] + return lines + + def start_response(self, status, response_headers, exc_info=None): + """ + This is the PEP-3333 mandated start_response callable. + + All it does is store the headers for later sending, and return our + ```write`` callable. + """ + if self._headers_emitted and exc_info is not None: + raise exc_info[1].with_traceback(exc_info[2]) + + assert not self._response_status or exc_info is not None + self._response_status = status + self._response_headers = response_headers + + return self.write + + def write(self, data): + """ + Provides some data to write. + + This function *blocks* until such time as the data is allowed by + HTTP/2 flow control. This allows a client to slow or pause the response + as needed. + + This function is not supposed to be used, according to PEP-3333, but + once we have it it becomes quite convenient to use it, so this app + actually runs all writes through this function. + """ + if not self._headers_emitted: + self._emit_headers() + event = self._protocol.data_for_stream(self.stream_id, data) + event.wait() + return + + def _emit_headers(self): + """ + Sends the response headers. + + This is only called from the write callable and should only ever be + called once. It does some minor processing (converts the status line + into a status code because reason phrases are evil) and then passes + the headers on to the server. This call explicitly blocks until the + server notifies us that the headers have reached the network. + """ + assert self._response_status and self._response_headers + assert not self._headers_emitted + self._headers_emitted = True + + # We only need the status code + status = self._response_status.split(" ", 1)[0] + headers = [(":status", status)] + headers.extend(self._response_headers) + event = self._protocol.send_response(self.stream_id, headers) + event.wait() + return + + # These two methods implement the iterator protocol. This allows a WSGI + # application to iterate over this Stream object to get the data. + def __iter__(self): + return self + + def __next__(self): + # If the complete request has been read, abort immediately. + if self._complete: + raise StopIteration() + + # If we have data stored in a temporary buffer for any reason, return + # that and clear the buffer. + # + # This can actually only happen when the application uses one of the + # read* callables, but that's fine. + if self._temp_buffer: + buffered_data = self._temp_buffer + self._temp_buffer = b'' + return buffered_data + + # Otherwise, pull data off the queue (blocking as needed). If this is + # the end of the request, we're done here: mark ourselves as complete + # and call it time. Otherwise, open the flow control window an + # appropriate amount and hand the chunk off. + chunk, chunk_size = self._received_data.get() + if chunk is END_DATA_SENTINEL: + self._complete = True + raise StopIteration() + + # Let's talk a little bit about why we're opening the flow control + # window *here*, and not in the server thread. + # + # The purpose of HTTP/2 flow control is to allow for servers and + # clients to avoid needing to buffer data indefinitely because their + # peer is producing data faster than they can consume it. As a result, + # it's important that the flow control window be opened as late in the + # processing as possible. In this case, we open the flow control window + # exactly when the server hands the data to the application. This means + # that the flow control window essentially signals to the remote peer + # how much data hasn't even been *seen* by the application yet. + # + # If you wanted to be really clever you could consider not opening the + # flow control window until the application asks for the *next* chunk + # of data. That means that any buffers at the application level are now + # included in the flow control window processing. In my opinion, the + # advantage of that process does not outweigh the extra logical + # complexity involved in doing it, so we don't bother here. + # + # Another note: you'll notice that we don't include the _temp_buffer in + # our flow control considerations. This means you could in principle + # lead us to buffer slightly more than one connection flow control + # window's worth of data. That risk is considered acceptable for the + # much simpler logic available here. + # + # Finally, this is a pretty dumb flow control window management scheme: + # it causes us to emit a *lot* of window updates. A smarter server + # would want to use the content-length header to determine whether + # flow control window updates need to be emitted at all, and then to be + # more efficient about emitting them to avoid firing them off really + # frequently. For an example like this, there's very little gained by + # worrying about that. + self._protocol.open_flow_control_window(self.stream_id, chunk_size) + + return chunk + + +def _build_environ_dict(headers, stream): + """ + Build the WSGI environ dictionary for a given request. To do that, we'll + temporarily create a dictionary for the headers. While this isn't actually + a valid way to represent headers, we know that the special headers we need + can only have one appearance in the block. + + This code is arguably somewhat incautious: the conversion to dictionary + should only happen in a way that allows us to correctly join headers that + appear multiple times. That's acceptable in a demo app: in a productised + version you'd want to fix it. + """ + header_dict = dict(headers) + path = header_dict.pop(u':path') + try: + path, query = path.split(u'?', 1) + except ValueError: + query = u"" + server_name = header_dict.pop(u':authority') + try: + server_name, port = server_name.split(u':', 1) + except ValueError as e: + port = "8443" + + environ = { + u'REQUEST_METHOD': header_dict.pop(u':method'), + u'SCRIPT_NAME': u'', + u'PATH_INFO': path, + u'QUERY_STRING': query, + u'SERVER_NAME': server_name, + u'SERVER_PORT': port, + u'SERVER_PROTOCOL': u'HTTP/2', + u'HTTPS': u"on", + u'SSL_PROTOCOL': u'TLSv1.2', + u'wsgi.version': (1, 0), + u'wsgi.url_scheme': header_dict.pop(u':scheme'), + u'wsgi.input': stream, + u'wsgi.errors': sys.stderr, + u'wsgi.multithread': True, + u'wsgi.multiprocess': False, + u'wsgi.run_once': False, + } + if u'content-type' in header_dict: + environ[u'CONTENT_TYPE'] = header_dict[u'content-type'] + if u'content-length' in header_dict: + environ[u'CONTENT_LENGTH'] = header_dict[u'content-length'] + for name, value in header_dict.items(): + environ[u'HTTP_' + name.upper()] = value + return environ + + +# Set up the WSGI app. +application_string = sys.argv[1] +path, func = application_string.split(':', 1) +module = importlib.import_module(path) +APPLICATION = getattr(module, func) + +# Set up TLS +ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) +ssl_context.options |= ( + ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_COMPRESSION +) +ssl_context.set_ciphers("ECDHE+AESGCM") +ssl_context.load_cert_chain(certfile="cert.crt", keyfile="cert.key") +ssl_context.set_alpn_protocols(["h2"]) + +# Do the asnycio bits +loop = asyncio.get_event_loop() +# Each client connection will create a new protocol instance +coro = loop.create_server(H2Protocol, '127.0.0.1', 8443, ssl=ssl_context) +server = loop.run_until_complete(coro) + +# Serve requests until Ctrl+C is pressed +print('Serving on {}'.format(server.sockets[0].getsockname())) +try: + loop.run_forever() +except KeyboardInterrupt: + pass + +# Close the server +server.close() +loop.run_until_complete(server.wait_closed()) +loop.close() diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/curio/curio-server.py b/testing/web-platform/tests/tools/third_party/h2/examples/curio/curio-server.py new file mode 100644 index 0000000000..f93d4db9d0 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/curio/curio-server.py @@ -0,0 +1,206 @@ +#!/usr/bin/env python3.5 +# -*- coding: utf-8 -*- +""" +curio-server.py +~~~~~~~~~~~~~~~ + +A fully-functional HTTP/2 server written for curio. + +Requires Python 3.5+. +""" +import mimetypes +import os +import sys + +from curio import Event, spawn, socket, ssl, run + +import h2.config +import h2.connection +import h2.events + + +# The maximum amount of a file we'll send in a single DATA frame. +READ_CHUNK_SIZE = 8192 + + +async def create_listening_ssl_socket(address, certfile, keyfile): + """ + Create and return a listening TLS socket on a given address. + """ + ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_context.options |= ( + ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_COMPRESSION + ) + ssl_context.set_ciphers("ECDHE+AESGCM") + ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile) + ssl_context.set_alpn_protocols(["h2"]) + + sock = socket.socket() + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock = await ssl_context.wrap_socket(sock) + sock.bind(address) + sock.listen() + + return sock + + +async def h2_server(address, root, certfile, keyfile): + """ + Create an HTTP/2 server at the given address. + """ + sock = await create_listening_ssl_socket(address, certfile, keyfile) + print("Now listening on %s:%d" % address) + + async with sock: + while True: + client, _ = await sock.accept() + server = H2Server(client, root) + await spawn(server.run()) + + +class H2Server: + """ + A basic HTTP/2 file server. This is essentially very similar to + SimpleHTTPServer from the standard library, but uses HTTP/2 instead of + HTTP/1.1. + """ + def __init__(self, sock, root): + config = h2.config.H2Configuration( + client_side=False, header_encoding='utf-8' + ) + self.sock = sock + self.conn = h2.connection.H2Connection(config=config) + self.root = root + self.flow_control_events = {} + + async def run(self): + """ + Loop over the connection, managing it appropriately. + """ + self.conn.initiate_connection() + await self.sock.sendall(self.conn.data_to_send()) + + while True: + # 65535 is basically arbitrary here: this amounts to "give me + # whatever data you have". + data = await self.sock.recv(65535) + if not data: + break + + events = self.conn.receive_data(data) + for event in events: + if isinstance(event, h2.events.RequestReceived): + await spawn( + self.request_received(event.headers, event.stream_id) + ) + elif isinstance(event, h2.events.DataReceived): + self.conn.reset_stream(event.stream_id) + elif isinstance(event, h2.events.WindowUpdated): + await self.window_updated(event) + + await self.sock.sendall(self.conn.data_to_send()) + + async def request_received(self, headers, stream_id): + """ + Handle a request by attempting to serve a suitable file. + """ + headers = dict(headers) + assert headers[':method'] == 'GET' + + path = headers[':path'].lstrip('/') + full_path = os.path.join(self.root, path) + + if not os.path.exists(full_path): + response_headers = ( + (':status', '404'), + ('content-length', '0'), + ('server', 'curio-h2'), + ) + self.conn.send_headers( + stream_id, response_headers, end_stream=True + ) + await self.sock.sendall(self.conn.data_to_send()) + else: + await self.send_file(full_path, stream_id) + + async def send_file(self, file_path, stream_id): + """ + Send a file, obeying the rules of HTTP/2 flow control. + """ + filesize = os.stat(file_path).st_size + content_type, content_encoding = mimetypes.guess_type(file_path) + response_headers = [ + (':status', '200'), + ('content-length', str(filesize)), + ('server', 'curio-h2'), + ] + if content_type: + response_headers.append(('content-type', content_type)) + if content_encoding: + response_headers.append(('content-encoding', content_encoding)) + + self.conn.send_headers(stream_id, response_headers) + await self.sock.sendall(self.conn.data_to_send()) + + with open(file_path, 'rb', buffering=0) as f: + await self._send_file_data(f, stream_id) + + async def _send_file_data(self, fileobj, stream_id): + """ + Send the data portion of a file. Handles flow control rules. + """ + while True: + while self.conn.local_flow_control_window(stream_id) < 1: + await self.wait_for_flow_control(stream_id) + + chunk_size = min( + self.conn.local_flow_control_window(stream_id), + READ_CHUNK_SIZE, + ) + + data = fileobj.read(chunk_size) + keep_reading = (len(data) == chunk_size) + + self.conn.send_data(stream_id, data, not keep_reading) + await self.sock.sendall(self.conn.data_to_send()) + + if not keep_reading: + break + + async def wait_for_flow_control(self, stream_id): + """ + Blocks until the flow control window for a given stream is opened. + """ + evt = Event() + self.flow_control_events[stream_id] = evt + await evt.wait() + + async def window_updated(self, event): + """ + Unblock streams waiting on flow control, if needed. + """ + stream_id = event.stream_id + + if stream_id and stream_id in self.flow_control_events: + evt = self.flow_control_events.pop(stream_id) + await evt.set() + elif not stream_id: + # Need to keep a real list here to use only the events present at + # this time. + blocked_streams = list(self.flow_control_events.keys()) + for stream_id in blocked_streams: + event = self.flow_control_events.pop(stream_id) + await event.set() + return + + +if __name__ == '__main__': + host = sys.argv[2] if len(sys.argv) > 2 else "localhost" + print("Try GETting:") + print(" On OSX after 'brew install curl --with-c-ares --with-libidn --with-nghttp2 --with-openssl':") + print("/usr/local/opt/curl/bin/curl --tlsv1.2 --http2 -k https://localhost:5000/bundle.js") + print("Or open a browser to: https://localhost:5000/") + print(" (Accept all the warnings)") + run(h2_server((host, 5000), sys.argv[1], + "{}.crt.pem".format(host), + "{}.key".format(host)), with_monitor=True) diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/curio/localhost.crt.pem b/testing/web-platform/tests/tools/third_party/h2/examples/curio/localhost.crt.pem new file mode 100644 index 0000000000..d6cf7d504d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/curio/localhost.crt.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDhTCCAm2gAwIBAgIJAOrxh0dOYJLdMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV +BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX +aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0xNTA5MTkxNDE2 +NDRaFw0xNTEwMTkxNDE2NDRaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21l +LVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV +BAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMqt +A1iu8EN00FU0eBcBGlLVmNEgV7Jkbukra+kwS8j/U2y50QPGJc/FiIVDfuBqk5dL +ACTNc6A/FQcXvWmOc5ixmC3QKKasMpuofqKz0V9C6irZdYXZ9rcsW0gHQIr989yd +R+N1VbIlEVW/T9FJL3B2UD9GVIkUELzm47CSOWZvAxQUlsx8CUNuUCWqyZJoqTFN +j0LeJDOWGCsug1Pkj0Q1x+jMVL6l6Zf6vMkLNOMsOsWsxUk+0L3tl/OzcTgUOCsw +UzY59RIi6Rudrp0oaU8NuHr91yiSqPbKFlX10M9KwEEdnIpcxhND3dacrDycj3ux +eWlqKync2vOFUkhwiaMCAwEAAaNQME4wHQYDVR0OBBYEFA0PN+PGoofZ+QIys2Jy +1Zz94vBOMB8GA1UdIwQYMBaAFA0PN+PGoofZ+QIys2Jy1Zz94vBOMAwGA1UdEwQF +MAMBAf8wDQYJKoZIhvcNAQELBQADggEBAEplethBoPpcP3EbR5Rz6snDDIcbtAJu +Ngd0YZppGT+P0DYnPJva4vRG3bb84ZMSuppz5j67qD6DdWte8UXhK8BzWiHzwmQE +QmbKyzzTMKQgTNFntpx5cgsSvTtrHpNYoMHzHOmyAOboNeM0DWiRXsYLkWTitLTN +qbOpstwPubExbT9lPjLclntShT/lCupt+zsbnrR9YiqlYFY/fDzfAybZhrD5GMBY +XdMPItwAc/sWvH31yztarjkLmld76AGCcO5r8cSR/cX98SicyfjOBbSco8GkjYNY +582gTPkKGYpStuN7GNT5tZmxvMq935HRa2XZvlAIe8ufp8EHVoYiF3c= +-----END CERTIFICATE----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/curio/localhost.key b/testing/web-platform/tests/tools/third_party/h2/examples/curio/localhost.key new file mode 100644 index 0000000000..bda69e836c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/curio/localhost.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAyq0DWK7wQ3TQVTR4FwEaUtWY0SBXsmRu6Str6TBLyP9TbLnR +A8Ylz8WIhUN+4GqTl0sAJM1zoD8VBxe9aY5zmLGYLdAopqwym6h+orPRX0LqKtl1 +hdn2tyxbSAdAiv3z3J1H43VVsiURVb9P0UkvcHZQP0ZUiRQQvObjsJI5Zm8DFBSW +zHwJQ25QJarJkmipMU2PQt4kM5YYKy6DU+SPRDXH6MxUvqXpl/q8yQs04yw6xazF +ST7Qve2X87NxOBQ4KzBTNjn1EiLpG52unShpTw24ev3XKJKo9soWVfXQz0rAQR2c +ilzGE0Pd1pysPJyPe7F5aWorKdza84VSSHCJowIDAQABAoIBACp+nh4BB/VMz8Wd +q7Q/EfLeQB1Q57JKpoqTBRwueSVai3ZXe4CMEi9/HkG6xiZtkiZ9njkZLq4hq9oB +2z//kzMnwV2RsIRJxI6ohGy+wR51HD4BvEdlTPpY/Yabpqe92VyfSYxidKZWaU0O +QMED1EODOw4ZQ+4928iPrJu//PMB4e7TFao0b9Fk/XLWtu5/tQZz9jsrlTi1zthh +7n+oaGNhfTeIJJL4jrhTrKW1CLHXATtr9SJlfZ3wbMxQVeyj2wUlP1V0M6kBuhNj +tbGbMpixD5iCNJ49Cm2PHg+wBOfS3ADGIpi3PcGw5mb8nB3N9eGBRPhLShAlq5Hi +Lv4tyykCgYEA8u3b3xJ04pxWYN25ou/Sc8xzgDCK4XvDNdHVTuZDjLVA+VTVPzql +lw7VvJArsx47MSPvsaX/+4hQXYtfnR7yJpx6QagvQ+z4ludnIZYrQwdUmb9pFL1s +8UNj+3j9QFRPenIiIQ8qxxNIQ9w2HsVQ8scvc9CjYop/YYAPaQyHaL8CgYEA1ZSz +CR4NcpfgRSILdhb1dLcyw5Qus1VOSAx3DYkhDkMiB8XZwgMdJjwehJo9yaqRCLE8 +Sw5znMnkfoZpu7+skrjK0FqmMpXMH9gIszHvFG8wSw/6+2HIWS19/wOu8dh95LuC +0zurMk8rFqxgWMWF20afhgYrUz42cvUTo10FVB0CgYEAt7mW6W3PArfUSCxIwmb4 +VmXREKkl0ATHDYQl/Cb//YHzot467TgQll883QB4XF5HzBFurX9rSzO7/BN1e6I0 +52i+ubtWC9xD4fUetXMaQvZfUGxIL8xXgVxDWKQXfLiG54c8Mp6C7s6xf8kjEUCP +yR1F0SSA/Pzb+8RbY0p7eocCgYA+1rs+SXtHZev0KyoYGnUpW+Uxqd17ofOgOxqj +/t6c5Z+TjeCdtnDTGQkZlo/rT6XQWuUUaDIXxUbW+xEMzj4mBPyXBLS1WWFvVQ5q +OpzO9E/PJeqAH6rkof/aEelc+oc/zvOU1o9uA+D3kMvgEm1psIOq2RHSMhGvDPA0 +NmAk+QKBgQCwd1681GagdIYSZUCBecnLtevXmIsJyDW2yR1NNcIe/ukcVQREMDvy +5DDkhnGDgnV1D5gYcXb34g9vYvbfTnBMl/JXmMAAG1kIS+3pvHyN6f1poVe3yJV1 +yHVuvymnJxKnyaV0L3ntepVvV0vVNIkA3oauoUTLto6txBI+b/ImDA== +-----END RSA PRIVATE KEY----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/eventlet/eventlet-server.py b/testing/web-platform/tests/tools/third_party/h2/examples/eventlet/eventlet-server.py new file mode 100644 index 0000000000..a46cfb3d15 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/eventlet/eventlet-server.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +""" +eventlet-server.py +~~~~~~~~~~~~~~~~~~ + +A fully-functional HTTP/2 server written for Eventlet. +""" +import collections +import json + +import eventlet + +from eventlet.green.OpenSSL import SSL, crypto +from h2.config import H2Configuration +from h2.connection import H2Connection +from h2.events import RequestReceived, DataReceived + + +class ConnectionManager(object): + """ + An object that manages a single HTTP/2 connection. + """ + def __init__(self, sock): + config = H2Configuration(client_side=False) + self.sock = sock + self.conn = H2Connection(config=config) + + def run_forever(self): + self.conn.initiate_connection() + self.sock.sendall(self.conn.data_to_send()) + + while True: + data = self.sock.recv(65535) + if not data: + break + + events = self.conn.receive_data(data) + + for event in events: + if isinstance(event, RequestReceived): + self.request_received(event.headers, event.stream_id) + elif isinstance(event, DataReceived): + self.conn.reset_stream(event.stream_id) + + self.sock.sendall(self.conn.data_to_send()) + + def request_received(self, headers, stream_id): + headers = collections.OrderedDict(headers) + data = json.dumps({'headers': headers}, indent=4).encode('utf-8') + + response_headers = ( + (':status', '200'), + ('content-type', 'application/json'), + ('content-length', str(len(data))), + ('server', 'eventlet-h2'), + ) + self.conn.send_headers(stream_id, response_headers) + self.conn.send_data(stream_id, data, end_stream=True) + + +def alpn_callback(conn, protos): + if b'h2' in protos: + return b'h2' + + raise RuntimeError("No acceptable protocol offered!") + + +def npn_advertise_cb(conn): + return [b'h2'] + + +# Let's set up SSL. This is a lot of work in PyOpenSSL. +options = ( + SSL.OP_NO_COMPRESSION | + SSL.OP_NO_SSLv2 | + SSL.OP_NO_SSLv3 | + SSL.OP_NO_TLSv1 | + SSL.OP_NO_TLSv1_1 +) +context = SSL.Context(SSL.SSLv23_METHOD) +context.set_options(options) +context.set_verify(SSL.VERIFY_NONE, lambda *args: True) +context.use_privatekey_file('server.key') +context.use_certificate_file('server.crt') +context.set_npn_advertise_callback(npn_advertise_cb) +context.set_alpn_select_callback(alpn_callback) +context.set_cipher_list( + "ECDHE+AESGCM" +) +context.set_tmp_ecdh(crypto.get_elliptic_curve(u'prime256v1')) + +server = eventlet.listen(('0.0.0.0', 443)) +server = SSL.Connection(context, server) +pool = eventlet.GreenPool() + +while True: + try: + new_sock, _ = server.accept() + manager = ConnectionManager(new_sock) + pool.spawn_n(manager.run_forever) + except (SystemExit, KeyboardInterrupt): + break diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/eventlet/server.crt b/testing/web-platform/tests/tools/third_party/h2/examples/eventlet/server.crt new file mode 100644 index 0000000000..bc8a4c08d2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/eventlet/server.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUjCCAjoCCQCQmNzzpQTCijANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQGEwJH +QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xETAPBgNVBAoTCGh5 +cGVyLWgyMREwDwYDVQQLEwhoeXBleS1oMjEUMBIGA1UEAxMLZXhhbXBsZS5jb20w +HhcNMTUwOTE2MjAyOTA0WhcNMTYwOTE1MjAyOTA0WjBrMQswCQYDVQQGEwJHQjEP +MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xETAPBgNVBAoTCGh5cGVy +LWgyMREwDwYDVQQLEwhoeXBleS1oMjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC74ZeB4Jdb5cnC9KXXLJuzjwTg +45q5EeShDYQe0TbKgreiUP6clU3BR0fFAVedN1q/LOuQ1HhvrDk1l4TfGF2bpCIq +K+U9CnzcQknvdpyyVeOLtSsCjOPk4xydHwkQxwJvHVdtJx4CzDDqGbHNHCF/9gpQ +lsa3JZW+tIZLK0XMEPFQ4XFXgegxTStO7kBBPaVIgG9Ooqc2MG4rjMNUpxa28WF1 +SyqWTICf2N8T/C+fPzbQLKCWrFrKUP7WQlOaqPNQL9bCDhSTPRTwQOc2/MzVZ9gT +Xr0Z+JMTXwkSMKO52adE1pmKt00jJ1ecZBiJFyjx0X6hH+/59dLbG/7No+PzAgMB +AAEwDQYJKoZIhvcNAQEFBQADggEBAG3UhOCa0EemL2iY+C+PR6CwEHQ+n7vkBzNz +gKOG+Q39spyzqU1qJAzBxLTE81bIQbDg0R8kcLWHVH2y4zViRxZ0jHUFKMgjONW+ +Aj4evic/2Y/LxpLxCajECq/jeMHYrmQONszf9pbc0+exrQpgnwd8asfsM3d/FJS2 +5DIWryCKs/61m9vYL8icWx/9cnfPkBoNv1ER+V1L1TH3ARvABh406SBaeqLTm/kG +MNuKytKWJsQbNlxzWHVgkKzVsBKvYj0uIEJpClIhbe6XNYRDy8T8mKXVWhJuxH4p +/agmCG3nxO8aCrUK/EVmbWmVIfCH3t7jlwMX1nJ8MsRE7Ydnk8I= +-----END CERTIFICATE----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/eventlet/server.key b/testing/web-platform/tests/tools/third_party/h2/examples/eventlet/server.key new file mode 100644 index 0000000000..11f9ea094b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/eventlet/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAu+GXgeCXW+XJwvSl1yybs48E4OOauRHkoQ2EHtE2yoK3olD+ +nJVNwUdHxQFXnTdavyzrkNR4b6w5NZeE3xhdm6QiKivlPQp83EJJ73acslXji7Ur +Aozj5OMcnR8JEMcCbx1XbSceAsww6hmxzRwhf/YKUJbGtyWVvrSGSytFzBDxUOFx +V4HoMU0rTu5AQT2lSIBvTqKnNjBuK4zDVKcWtvFhdUsqlkyAn9jfE/wvnz820Cyg +lqxaylD+1kJTmqjzUC/Wwg4Ukz0U8EDnNvzM1WfYE169GfiTE18JEjCjudmnRNaZ +irdNIydXnGQYiRco8dF+oR/v+fXS2xv+zaPj8wIDAQABAoIBAQCsdq278+0c13d4 +tViSh4k5r1w8D9IUdp9XU2/nVgckqA9nOVAvbkJc3FC+P7gsQgbUHKj0XoVbhU1S +q461t8kduPH/oiGhAcKR8WurHEdE0OC6ewhLJAeCMRQwCrAorXXHh7icIt9ClCuG +iSWUcXEy5Cidx3oL3r1xvIbV85fzdDtE9RC1I/kMjAy63S47YGiqh5vYmJkCa8rG +Dsd1sEMDPr63XJpqJj3uHRcPvySgXTa+ssTmUH8WJlPTjvDB5hnPz+lkk2JKVPNu +8adzftZ6hSun+tsc4ZJp8XhGu/m/7MjxWh8MeupLHlXcOEsnj4uHQQsOM3zHojr3 +aDCZiC1pAoGBAOAhwe1ujoS2VJ5RXJ9KMs7eBER/02MDgWZjo54Jv/jFxPWGslKk +QQceuTe+PruRm41nzvk3q4iZXt8pG0bvpgigN2epcVx/O2ouRsUWWBT0JrVlEzha +TIvWjtZ5tSQExXgHL3VlM9+ka40l+NldLSPn25+prizaqhalWuvTpP23AoGBANaY +VhEI6yhp0BBUSATEv9lRgkwx3EbcnXNXPQjDMOthsyfq7FxbdOBEK1rwSDyuE6Ij +zQGcTOfdiur5Ttg0OQilTJIXJAlpoeecOQ9yGma08c5FMXVJJvcZUuWRZWg1ocQj +/hx0WVE9NwOoKwTBERv8HX7vJOFRZyvgkJwFxoulAoGAe4m/1XoZrga9z2GzNs10 +AdgX7BW00x+MhH4pIiPnn1yK+nYa9jg4647Asnv3IfXZEnEEgRNxReKbi0+iDFBt +aNW+lDGuHTi37AfD1EBDnpEQgO1MUcRb6rwBkTAWatsCaO00+HUmyX9cFLm4Vz7n +caILyQ6CxZBlLgRIgDHxADMCgYEAtubsJGTHmZBmSCStpXLUWbOBLNQqfTM398DZ +QoirP1PsUQ+IGUfSG/u+QCogR6fPEBkXeFHxsoY/Cvsm2lvYaKgK1VFn46Xm2vNq +JuIH4pZCqp6LAv4weddZslT0a5eaowRSZ4o7PmTAaRuCXvD3VjTSJwhJFMo+90TV +vEWn7gkCgYEAkk+unX9kYmKoUdLh22/tzQekBa8WqMxXDwzBCECTAs2GlpL/f73i +zD15TnaNfLP6Q5RNb0N9tb0Gz1wSkwI1+jGAQLnh2K9X9cIVIqJn8Mf/KQa/wUDV +Tb1j7FoGUEgX7vbsyWuTd8P76kNYyGqCss1XmbttcSolqpbIdlSUcO0= +-----END RSA PRIVATE KEY----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/fragments/client_https_setup_fragment.py b/testing/web-platform/tests/tools/third_party/h2/examples/fragments/client_https_setup_fragment.py new file mode 100644 index 0000000000..269194d6c7 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/fragments/client_https_setup_fragment.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- +""" +Client HTTPS Setup +~~~~~~~~~~~~~~~~~~ + +This example code fragment demonstrates how to set up a HTTP/2 client that +negotiates HTTP/2 using NPN and ALPN. For the sake of maximum explanatory value +this code uses the synchronous, low-level sockets API: however, if you're not +using sockets directly (e.g. because you're using asyncio), you should focus on +the set up required for the SSLContext object. For other concurrency libraries +you may need to use other setup (e.g. for Twisted you'll need to use +IProtocolNegotiationFactory). + +This code requires Python 3.5 or later. +""" +import h2.connection +import socket +import ssl + + +def establish_tcp_connection(): + """ + This function establishes a client-side TCP connection. How it works isn't + very important to this example. For the purpose of this example we connect + to localhost. + """ + return socket.create_connection(('localhost', 443)) + + +def get_http2_ssl_context(): + """ + This function creates an SSLContext object that is suitably configured for + HTTP/2. If you're working with Python TLS directly, you'll want to do the + exact same setup as this function does. + """ + # Get the basic context from the standard library. + ctx = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH) + + # RFC 7540 Section 9.2: Implementations of HTTP/2 MUST use TLS version 1.2 + # or higher. Disable TLS 1.1 and lower. + ctx.options |= ( + ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 + ) + + # RFC 7540 Section 9.2.1: A deployment of HTTP/2 over TLS 1.2 MUST disable + # compression. + ctx.options |= ssl.OP_NO_COMPRESSION + + # RFC 7540 Section 9.2.2: "deployments of HTTP/2 that use TLS 1.2 MUST + # support TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256". In practice, the + # blocklist defined in this section allows only the AES GCM and ChaCha20 + # cipher suites with ephemeral key negotiation. + ctx.set_ciphers("ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20") + + # We want to negotiate using NPN and ALPN. ALPN is mandatory, but NPN may + # be absent, so allow that. This setup allows for negotiation of HTTP/1.1. + ctx.set_alpn_protocols(["h2", "http/1.1"]) + + try: + ctx.set_npn_protocols(["h2", "http/1.1"]) + except NotImplementedError: + pass + + return ctx + + +def negotiate_tls(tcp_conn, context): + """ + Given an established TCP connection and a HTTP/2-appropriate TLS context, + this function: + + 1. wraps TLS around the TCP connection. + 2. confirms that HTTP/2 was negotiated and, if it was not, throws an error. + """ + # Note that SNI is mandatory for HTTP/2, so you *must* pass the + # server_hostname argument. + tls_conn = context.wrap_socket(tcp_conn, server_hostname='localhost') + + # Always prefer the result from ALPN to that from NPN. + # You can only check what protocol was negotiated once the handshake is + # complete. + negotiated_protocol = tls_conn.selected_alpn_protocol() + if negotiated_protocol is None: + negotiated_protocol = tls_conn.selected_npn_protocol() + + if negotiated_protocol != "h2": + raise RuntimeError("Didn't negotiate HTTP/2!") + + return tls_conn + + +def main(): + # Step 1: Set up your TLS context. + context = get_http2_ssl_context() + + # Step 2: Create a TCP connection. + connection = establish_tcp_connection() + + # Step 3: Wrap the connection in TLS and validate that we negotiated HTTP/2 + tls_connection = negotiate_tls(connection, context) + + # Step 4: Create a client-side H2 connection. + http2_connection = h2.connection.H2Connection() + + # Step 5: Initiate the connection + http2_connection.initiate_connection() + tls_connection.sendall(http2_connection.data_to_send()) + + # The TCP, TLS, and HTTP/2 handshakes are now complete. You can enter your + # main loop now. diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/fragments/client_upgrade_fragment.py b/testing/web-platform/tests/tools/third_party/h2/examples/fragments/client_upgrade_fragment.py new file mode 100644 index 0000000000..f45c002df7 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/fragments/client_upgrade_fragment.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- +""" +Client Plaintext Upgrade +~~~~~~~~~~~~~~~~~~~~~~~~ + +This example code fragment demonstrates how to set up a HTTP/2 client that uses +the plaintext HTTP Upgrade mechanism to negotiate HTTP/2 connectivity. For +maximum explanatory value it uses the synchronous socket API that comes with +the Python standard library. In product code you will want to use an actual +HTTP/1.1 client if possible. + +This code requires Python 3.5 or later. +""" +import h2.connection +import socket + + +def establish_tcp_connection(): + """ + This function establishes a client-side TCP connection. How it works isn't + very important to this example. For the purpose of this example we connect + to localhost. + """ + return socket.create_connection(('localhost', 80)) + + +def send_initial_request(connection, settings): + """ + For the sake of this upgrade demonstration, we're going to issue a GET + request against the root of the site. In principle the best request to + issue for an upgrade is actually ``OPTIONS *``, but this is remarkably + poorly supported and can break in weird ways. + """ + # Craft our initial request per RFC 7540 Section 3.2. This requires two + # special header fields: the Upgrade headre, and the HTTP2-Settings header. + # The value of the HTTP2-Settings header field comes from h2. + request = ( + b"GET / HTTP/1.1\r\n" + + b"Host: localhost\r\n" + + b"Upgrade: h2c\r\n" + + b"HTTP2-Settings: " + settings + b"\r\n" + + b"\r\n" + ) + connection.sendall(request) + + +def get_upgrade_response(connection): + """ + This function reads from the socket until the HTTP/1.1 end-of-headers + sequence (CRLFCRLF) is received. It then checks what the status code of the + response is. + + This is not a substitute for proper HTTP/1.1 parsing, but it's good enough + for example purposes. + """ + data = b'' + while b'\r\n\r\n' not in data: + data += connection.recv(8192) + + headers, rest = data.split(b'\r\n\r\n', 1) + + # An upgrade response begins HTTP/1.1 101 Switching Protocols. Look for the + # code. In production code you should also check that the upgrade is to + # h2c, but here we know we only offered one upgrade so there's only one + # possible upgrade in use. + split_headers = headers.split() + if split_headers[1] != b'101': + raise RuntimeError("Not upgrading!") + + # We don't care about the HTTP/1.1 data anymore, but we do care about + # any other data we read from the socket: this is going to be HTTP/2 data + # that must be passed to the H2Connection. + return rest + + +def main(): + """ + The client upgrade flow. + """ + # Step 1: Establish the TCP connecton. + connection = establish_tcp_connection() + + # Step 2: Create H2 Connection object, put it in upgrade mode, and get the + # value of the HTTP2-Settings header we want to use. + h2_connection = h2.connection.H2Connection() + settings_header_value = h2_connection.initiate_upgrade_connection() + + # Step 3: Send the initial HTTP/1.1 request with the upgrade fields. + send_initial_request(connection, settings_header_value) + + # Step 4: Read the HTTP/1.1 response, look for 101 response. + extra_data = get_upgrade_response(connection) + + # Step 5: Immediately send the pending HTTP/2 data. + connection.sendall(h2_connection.data_to_send()) + + # Step 6: Feed the body data to the connection. + events = connection.receive_data(extra_data) + + # Now you can enter your main loop, beginning by processing the first set + # of events above. These events may include ResponseReceived, which will + # contain the response to the request we made in Step 3. + main_loop(events) diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/fragments/server_https_setup_fragment.py b/testing/web-platform/tests/tools/third_party/h2/examples/fragments/server_https_setup_fragment.py new file mode 100644 index 0000000000..9fc361f2c6 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/fragments/server_https_setup_fragment.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +""" +Server HTTPS Setup +~~~~~~~~~~~~~~~~~~ + +This example code fragment demonstrates how to set up a HTTP/2 server that +negotiates HTTP/2 using NPN and ALPN. For the sake of maximum explanatory value +this code uses the synchronous, low-level sockets API: however, if you're not +using sockets directly (e.g. because you're using asyncio), you should focus on +the set up required for the SSLContext object. For other concurrency libraries +you may need to use other setup (e.g. for Twisted you'll need to use +IProtocolNegotiationFactory). + +This code requires Python 3.5 or later. +""" +import h2.config +import h2.connection +import socket +import ssl + + +def establish_tcp_connection(): + """ + This function establishes a server-side TCP connection. How it works isn't + very important to this example. + """ + bind_socket = socket.socket() + bind_socket.bind(('', 443)) + bind_socket.listen(5) + return bind_socket.accept()[0] + + +def get_http2_ssl_context(): + """ + This function creates an SSLContext object that is suitably configured for + HTTP/2. If you're working with Python TLS directly, you'll want to do the + exact same setup as this function does. + """ + # Get the basic context from the standard library. + ctx = ssl.create_default_context(purpose=ssl.Purpose.CLIENT_AUTH) + + # RFC 7540 Section 9.2: Implementations of HTTP/2 MUST use TLS version 1.2 + # or higher. Disable TLS 1.1 and lower. + ctx.options |= ( + ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 + ) + + # RFC 7540 Section 9.2.1: A deployment of HTTP/2 over TLS 1.2 MUST disable + # compression. + ctx.options |= ssl.OP_NO_COMPRESSION + + # RFC 7540 Section 9.2.2: "deployments of HTTP/2 that use TLS 1.2 MUST + # support TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256". In practice, the + # blocklist defined in this section allows only the AES GCM and ChaCha20 + # cipher suites with ephemeral key negotiation. + ctx.set_ciphers("ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20") + + # We want to negotiate using NPN and ALPN. ALPN is mandatory, but NPN may + # be absent, so allow that. This setup allows for negotiation of HTTP/1.1. + ctx.set_alpn_protocols(["h2", "http/1.1"]) + + try: + ctx.set_npn_protocols(["h2", "http/1.1"]) + except NotImplementedError: + pass + + return ctx + + +def negotiate_tls(tcp_conn, context): + """ + Given an established TCP connection and a HTTP/2-appropriate TLS context, + this function: + + 1. wraps TLS around the TCP connection. + 2. confirms that HTTP/2 was negotiated and, if it was not, throws an error. + """ + tls_conn = context.wrap_socket(tcp_conn, server_side=True) + + # Always prefer the result from ALPN to that from NPN. + # You can only check what protocol was negotiated once the handshake is + # complete. + negotiated_protocol = tls_conn.selected_alpn_protocol() + if negotiated_protocol is None: + negotiated_protocol = tls_conn.selected_npn_protocol() + + if negotiated_protocol != "h2": + raise RuntimeError("Didn't negotiate HTTP/2!") + + return tls_conn + + +def main(): + # Step 1: Set up your TLS context. + context = get_http2_ssl_context() + + # Step 2: Receive a TCP connection. + connection = establish_tcp_connection() + + # Step 3: Wrap the connection in TLS and validate that we negotiated HTTP/2 + tls_connection = negotiate_tls(connection, context) + + # Step 4: Create a server-side H2 connection. + config = h2.config.H2Configuration(client_side=False) + http2_connection = h2.connection.H2Connection(config=config) + + # Step 5: Initiate the connection + http2_connection.initiate_connection() + tls_connection.sendall(http2_connection.data_to_send()) + + # The TCP, TLS, and HTTP/2 handshakes are now complete. You can enter your + # main loop now. diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/fragments/server_upgrade_fragment.py b/testing/web-platform/tests/tools/third_party/h2/examples/fragments/server_upgrade_fragment.py new file mode 100644 index 0000000000..7e8b1f0eea --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/fragments/server_upgrade_fragment.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- +""" +Server Plaintext Upgrade +~~~~~~~~~~~~~~~~~~~~~~~~ + +This example code fragment demonstrates how to set up a HTTP/2 server that uses +the plaintext HTTP Upgrade mechanism to negotiate HTTP/2 connectivity. For +maximum explanatory value it uses the synchronous socket API that comes with +the Python standard library. In product code you will want to use an actual +HTTP/1.1 server library if possible. + +This code requires Python 3.5 or later. +""" +import h2.config +import h2.connection +import re +import socket + + +def establish_tcp_connection(): + """ + This function establishes a server-side TCP connection. How it works isn't + very important to this example. + """ + bind_socket = socket.socket() + bind_socket.bind(('', 443)) + bind_socket.listen(5) + return bind_socket.accept()[0] + + +def receive_initial_request(connection): + """ + We're going to receive a request. For the sake of this example, we're going + to assume that the first request has no body. If it doesn't have the + Upgrade: h2c header field and the HTTP2-Settings header field, we'll throw + errors. + + In production code, you should use a proper HTTP/1.1 parser and actually + serve HTTP/1.1 requests! + + Returns the value of the HTTP2-Settings header field. + """ + data = b'' + while not data.endswith(b'\r\n\r\n'): + data += connection.recv(8192) + + match = re.search(b'Upgrade: h2c\r\n', data) + if match is None: + raise RuntimeError("HTTP/2 upgrade not requested!") + + # We need to look for the HTTP2-Settings header field. Again, in production + # code you shouldn't use regular expressions for this, but it's good enough + # for the example. + match = re.search(b'HTTP2-Settings: (\\S+)\r\n', data) + if match is None: + raise RuntimeError("HTTP2-Settings header field not present!") + + return match.group(1) + + +def send_upgrade_response(connection): + """ + This function writes the 101 Switching Protocols response. + """ + response = ( + b"HTTP/1.1 101 Switching Protocols\r\n" + b"Upgrade: h2c\r\n" + b"\r\n" + ) + connection.sendall(response) + + +def main(): + """ + The server upgrade flow. + """ + # Step 1: Establish the TCP connecton. + connection = establish_tcp_connection() + + # Step 2: Read the response. We expect this to request an upgrade. + settings_header_value = receive_initial_request(connection) + + # Step 3: Create a H2Connection object in server mode, and pass it the + # value of the HTTP2-Settings header field. + config = h2.config.H2Configuration(client_side=False) + h2_connection = h2.connection.H2Connection(config=config) + h2_connection.initiate_upgrade_connection( + settings_header=settings_header_value + ) + + # Step 4: Send the 101 Switching Protocols response. + send_upgrade_response(connection) + + # Step 5: Send pending HTTP/2 data. + connection.sendall(h2_connection.data_to_send()) + + # At this point, you can enter your main loop. The first step has to be to + # send the response to the initial HTTP/1.1 request you received on stream + # 1. + main_loop() diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/tornado/server.crt b/testing/web-platform/tests/tools/third_party/h2/examples/tornado/server.crt new file mode 100644 index 0000000000..bc8a4c08d2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/tornado/server.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUjCCAjoCCQCQmNzzpQTCijANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQGEwJH +QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xETAPBgNVBAoTCGh5 +cGVyLWgyMREwDwYDVQQLEwhoeXBleS1oMjEUMBIGA1UEAxMLZXhhbXBsZS5jb20w +HhcNMTUwOTE2MjAyOTA0WhcNMTYwOTE1MjAyOTA0WjBrMQswCQYDVQQGEwJHQjEP +MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xETAPBgNVBAoTCGh5cGVy +LWgyMREwDwYDVQQLEwhoeXBleS1oMjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC74ZeB4Jdb5cnC9KXXLJuzjwTg +45q5EeShDYQe0TbKgreiUP6clU3BR0fFAVedN1q/LOuQ1HhvrDk1l4TfGF2bpCIq +K+U9CnzcQknvdpyyVeOLtSsCjOPk4xydHwkQxwJvHVdtJx4CzDDqGbHNHCF/9gpQ +lsa3JZW+tIZLK0XMEPFQ4XFXgegxTStO7kBBPaVIgG9Ooqc2MG4rjMNUpxa28WF1 +SyqWTICf2N8T/C+fPzbQLKCWrFrKUP7WQlOaqPNQL9bCDhSTPRTwQOc2/MzVZ9gT +Xr0Z+JMTXwkSMKO52adE1pmKt00jJ1ecZBiJFyjx0X6hH+/59dLbG/7No+PzAgMB +AAEwDQYJKoZIhvcNAQEFBQADggEBAG3UhOCa0EemL2iY+C+PR6CwEHQ+n7vkBzNz +gKOG+Q39spyzqU1qJAzBxLTE81bIQbDg0R8kcLWHVH2y4zViRxZ0jHUFKMgjONW+ +Aj4evic/2Y/LxpLxCajECq/jeMHYrmQONszf9pbc0+exrQpgnwd8asfsM3d/FJS2 +5DIWryCKs/61m9vYL8icWx/9cnfPkBoNv1ER+V1L1TH3ARvABh406SBaeqLTm/kG +MNuKytKWJsQbNlxzWHVgkKzVsBKvYj0uIEJpClIhbe6XNYRDy8T8mKXVWhJuxH4p +/agmCG3nxO8aCrUK/EVmbWmVIfCH3t7jlwMX1nJ8MsRE7Ydnk8I= +-----END CERTIFICATE----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/tornado/server.key b/testing/web-platform/tests/tools/third_party/h2/examples/tornado/server.key new file mode 100644 index 0000000000..11f9ea094b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/tornado/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAu+GXgeCXW+XJwvSl1yybs48E4OOauRHkoQ2EHtE2yoK3olD+ +nJVNwUdHxQFXnTdavyzrkNR4b6w5NZeE3xhdm6QiKivlPQp83EJJ73acslXji7Ur +Aozj5OMcnR8JEMcCbx1XbSceAsww6hmxzRwhf/YKUJbGtyWVvrSGSytFzBDxUOFx +V4HoMU0rTu5AQT2lSIBvTqKnNjBuK4zDVKcWtvFhdUsqlkyAn9jfE/wvnz820Cyg +lqxaylD+1kJTmqjzUC/Wwg4Ukz0U8EDnNvzM1WfYE169GfiTE18JEjCjudmnRNaZ +irdNIydXnGQYiRco8dF+oR/v+fXS2xv+zaPj8wIDAQABAoIBAQCsdq278+0c13d4 +tViSh4k5r1w8D9IUdp9XU2/nVgckqA9nOVAvbkJc3FC+P7gsQgbUHKj0XoVbhU1S +q461t8kduPH/oiGhAcKR8WurHEdE0OC6ewhLJAeCMRQwCrAorXXHh7icIt9ClCuG +iSWUcXEy5Cidx3oL3r1xvIbV85fzdDtE9RC1I/kMjAy63S47YGiqh5vYmJkCa8rG +Dsd1sEMDPr63XJpqJj3uHRcPvySgXTa+ssTmUH8WJlPTjvDB5hnPz+lkk2JKVPNu +8adzftZ6hSun+tsc4ZJp8XhGu/m/7MjxWh8MeupLHlXcOEsnj4uHQQsOM3zHojr3 +aDCZiC1pAoGBAOAhwe1ujoS2VJ5RXJ9KMs7eBER/02MDgWZjo54Jv/jFxPWGslKk +QQceuTe+PruRm41nzvk3q4iZXt8pG0bvpgigN2epcVx/O2ouRsUWWBT0JrVlEzha +TIvWjtZ5tSQExXgHL3VlM9+ka40l+NldLSPn25+prizaqhalWuvTpP23AoGBANaY +VhEI6yhp0BBUSATEv9lRgkwx3EbcnXNXPQjDMOthsyfq7FxbdOBEK1rwSDyuE6Ij +zQGcTOfdiur5Ttg0OQilTJIXJAlpoeecOQ9yGma08c5FMXVJJvcZUuWRZWg1ocQj +/hx0WVE9NwOoKwTBERv8HX7vJOFRZyvgkJwFxoulAoGAe4m/1XoZrga9z2GzNs10 +AdgX7BW00x+MhH4pIiPnn1yK+nYa9jg4647Asnv3IfXZEnEEgRNxReKbi0+iDFBt +aNW+lDGuHTi37AfD1EBDnpEQgO1MUcRb6rwBkTAWatsCaO00+HUmyX9cFLm4Vz7n +caILyQ6CxZBlLgRIgDHxADMCgYEAtubsJGTHmZBmSCStpXLUWbOBLNQqfTM398DZ +QoirP1PsUQ+IGUfSG/u+QCogR6fPEBkXeFHxsoY/Cvsm2lvYaKgK1VFn46Xm2vNq +JuIH4pZCqp6LAv4weddZslT0a5eaowRSZ4o7PmTAaRuCXvD3VjTSJwhJFMo+90TV +vEWn7gkCgYEAkk+unX9kYmKoUdLh22/tzQekBa8WqMxXDwzBCECTAs2GlpL/f73i +zD15TnaNfLP6Q5RNb0N9tb0Gz1wSkwI1+jGAQLnh2K9X9cIVIqJn8Mf/KQa/wUDV +Tb1j7FoGUEgX7vbsyWuTd8P76kNYyGqCss1XmbttcSolqpbIdlSUcO0= +-----END RSA PRIVATE KEY----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/tornado/tornado-server.py b/testing/web-platform/tests/tools/third_party/h2/examples/tornado/tornado-server.py new file mode 100755 index 0000000000..e7d08ab191 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/tornado/tornado-server.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" +tornado-server.py +~~~~~~~~~~~~~~~~~ + +A fully-functional HTTP/2 server written for Tornado. +""" +import collections +import json +import ssl + +import tornado.gen +import tornado.ioloop +import tornado.iostream +import tornado.tcpserver + +from h2.config import H2Configuration +from h2.connection import H2Connection +from h2.events import RequestReceived, DataReceived + + +def create_ssl_context(certfile, keyfile): + ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_context.options |= ( + ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1 | ssl.OP_NO_COMPRESSION + ) + ssl_context.set_ciphers("ECDHE+AESGCM") + ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile) + ssl_context.set_alpn_protocols(["h2"]) + return ssl_context + + +class H2Server(tornado.tcpserver.TCPServer): + + @tornado.gen.coroutine + def handle_stream(self, stream, address): + handler = EchoHeadersHandler(stream) + yield handler.handle() + + +class EchoHeadersHandler(object): + + def __init__(self, stream): + self.stream = stream + + config = H2Configuration(client_side=False) + self.conn = H2Connection(config=config) + + @tornado.gen.coroutine + def handle(self): + self.conn.initiate_connection() + yield self.stream.write(self.conn.data_to_send()) + + while True: + try: + data = yield self.stream.read_bytes(65535, partial=True) + if not data: + break + + events = self.conn.receive_data(data) + for event in events: + if isinstance(event, RequestReceived): + self.request_received(event.headers, event.stream_id) + elif isinstance(event, DataReceived): + self.conn.reset_stream(event.stream_id) + + yield self.stream.write(self.conn.data_to_send()) + + except tornado.iostream.StreamClosedError: + break + + def request_received(self, headers, stream_id): + headers = collections.OrderedDict(headers) + data = json.dumps({'headers': headers}, indent=4).encode('utf-8') + + response_headers = ( + (':status', '200'), + ('content-type', 'application/json'), + ('content-length', str(len(data))), + ('server', 'tornado-h2'), + ) + self.conn.send_headers(stream_id, response_headers) + self.conn.send_data(stream_id, data, end_stream=True) + + +if __name__ == '__main__': + ssl_context = create_ssl_context('server.crt', 'server.key') + server = H2Server(ssl_options=ssl_context) + server.listen(8888) + io_loop = tornado.ioloop.IOLoop.current() + io_loop.start() diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/twisted/head_request.py b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/head_request.py new file mode 100644 index 0000000000..4a7538024a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/head_request.py @@ -0,0 +1,111 @@ +# -*- coding: utf-8 -*- +""" +head_request.py +~~~~~~~~~~~~~~~ + +A short example that demonstrates a client that makes HEAD requests to certain +websites. + +This example is intended as a reproduction of nghttp2 issue 396, for the +purposes of compatibility testing. +""" +from __future__ import print_function + +from twisted.internet import reactor +from twisted.internet.endpoints import connectProtocol, SSL4ClientEndpoint +from twisted.internet.protocol import Protocol +from twisted.internet.ssl import optionsForClientTLS +from hyperframe.frame import SettingsFrame +from h2.connection import H2Connection +from h2.events import ( + ResponseReceived, DataReceived, StreamEnded, + StreamReset, SettingsAcknowledged, +) + + +AUTHORITY = u'nghttp2.org' +PATH = '/httpbin/' +SIZE = 4096 + + +class H2Protocol(Protocol): + def __init__(self): + self.conn = H2Connection() + self.known_proto = None + self.request_made = False + + def connectionMade(self): + self.conn.initiate_connection() + + # This reproduces the error in #396, by changing the header table size. + self.conn.update_settings({SettingsFrame.HEADER_TABLE_SIZE: SIZE}) + + self.transport.write(self.conn.data_to_send()) + + def dataReceived(self, data): + if not self.known_proto: + self.known_proto = self.transport.negotiatedProtocol + assert self.known_proto == b'h2' + + events = self.conn.receive_data(data) + + for event in events: + if isinstance(event, ResponseReceived): + self.handleResponse(event.headers, event.stream_id) + elif isinstance(event, DataReceived): + self.handleData(event.data, event.stream_id) + elif isinstance(event, StreamEnded): + self.endStream(event.stream_id) + elif isinstance(event, SettingsAcknowledged): + self.settingsAcked(event) + elif isinstance(event, StreamReset): + reactor.stop() + raise RuntimeError("Stream reset: %d" % event.error_code) + else: + print(event) + + data = self.conn.data_to_send() + if data: + self.transport.write(data) + + def settingsAcked(self, event): + # Having received the remote settings change, lets send our request. + if not self.request_made: + self.sendRequest() + + def handleResponse(self, response_headers, stream_id): + for name, value in response_headers: + print("%s: %s" % (name.decode('utf-8'), value.decode('utf-8'))) + + print("") + + def handleData(self, data, stream_id): + print(data, end='') + + def endStream(self, stream_id): + self.conn.close_connection() + self.transport.write(self.conn.data_to_send()) + self.transport.loseConnection() + reactor.stop() + + def sendRequest(self): + request_headers = [ + (':method', 'HEAD'), + (':authority', AUTHORITY), + (':scheme', 'https'), + (':path', PATH), + ('user-agent', 'hyper-h2/1.0.0'), + ] + self.conn.send_headers(1, request_headers, end_stream=True) + self.request_made = True + +options = optionsForClientTLS( + hostname=AUTHORITY, + acceptableProtocols=[b'h2'], +) + +connectProtocol( + SSL4ClientEndpoint(reactor, AUTHORITY, 443, options), + H2Protocol() +) +reactor.run() diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/twisted/post_request.py b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/post_request.py new file mode 100644 index 0000000000..c817bac465 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/post_request.py @@ -0,0 +1,249 @@ +# -*- coding: utf-8 -*- +""" +post_request.py +~~~~~~~~~~~~~~~ + +A short example that demonstrates a client that makes POST requests to certain +websites. + +This example is intended to demonstrate how to handle uploading request bodies. +In this instance, a file will be uploaded. In order to handle arbitrary files, +this example also demonstrates how to obey HTTP/2 flow control rules. + +Takes one command-line argument: a path to a file in the filesystem to upload. +If none is present, uploads this file. +""" +from __future__ import print_function + +import mimetypes +import os +import sys + +from twisted.internet import reactor, defer +from twisted.internet.endpoints import connectProtocol, SSL4ClientEndpoint +from twisted.internet.protocol import Protocol +from twisted.internet.ssl import optionsForClientTLS +from h2.connection import H2Connection +from h2.events import ( + ResponseReceived, DataReceived, StreamEnded, StreamReset, WindowUpdated, + SettingsAcknowledged, +) + + +AUTHORITY = u'nghttp2.org' +PATH = '/httpbin/post' + + +class H2Protocol(Protocol): + def __init__(self, file_path): + self.conn = H2Connection() + self.known_proto = None + self.request_made = False + self.request_complete = False + self.file_path = file_path + self.flow_control_deferred = None + self.fileobj = None + self.file_size = None + + def connectionMade(self): + """ + Called by Twisted when the TCP connection is established. We can start + sending some data now: we should open with the connection preamble. + """ + self.conn.initiate_connection() + self.transport.write(self.conn.data_to_send()) + + def dataReceived(self, data): + """ + Called by Twisted when data is received on the connection. + + We need to check a few things here. Firstly, we want to validate that + we actually negotiated HTTP/2: if we didn't, we shouldn't proceed! + + Then, we want to pass the data to the protocol stack and check what + events occurred. + """ + if not self.known_proto: + self.known_proto = self.transport.negotiatedProtocol + assert self.known_proto == b'h2' + + events = self.conn.receive_data(data) + + for event in events: + if isinstance(event, ResponseReceived): + self.handleResponse(event.headers) + elif isinstance(event, DataReceived): + self.handleData(event.data) + elif isinstance(event, StreamEnded): + self.endStream() + elif isinstance(event, SettingsAcknowledged): + self.settingsAcked(event) + elif isinstance(event, StreamReset): + reactor.stop() + raise RuntimeError("Stream reset: %d" % event.error_code) + elif isinstance(event, WindowUpdated): + self.windowUpdated(event) + + data = self.conn.data_to_send() + if data: + self.transport.write(data) + + def settingsAcked(self, event): + """ + Called when the remote party ACKs our settings. We send a SETTINGS + frame as part of the preamble, so if we want to be very polite we can + wait until the ACK for that frame comes before we start sending our + request. + """ + if not self.request_made: + self.sendRequest() + + def handleResponse(self, response_headers): + """ + Handle the response by printing the response headers. + """ + for name, value in response_headers: + print("%s: %s" % (name.decode('utf-8'), value.decode('utf-8'))) + + print("") + + def handleData(self, data): + """ + We handle data that's received by just printing it. + """ + print(data, end='') + + def endStream(self): + """ + We call this when the stream is cleanly ended by the remote peer. That + means that the response is complete. + + Because this code only makes a single HTTP/2 request, once we receive + the complete response we can safely tear the connection down and stop + the reactor. We do that as cleanly as possible. + """ + self.request_complete = True + self.conn.close_connection() + self.transport.write(self.conn.data_to_send()) + self.transport.loseConnection() + + def windowUpdated(self, event): + """ + We call this when the flow control window for the connection or the + stream has been widened. If there's a flow control deferred present + (that is, if we're blocked behind the flow control), we fire it. + Otherwise, we do nothing. + """ + if self.flow_control_deferred is None: + return + + # Make sure we remove the flow control deferred to avoid firing it + # more than once. + flow_control_deferred = self.flow_control_deferred + self.flow_control_deferred = None + flow_control_deferred.callback(None) + + def connectionLost(self, reason=None): + """ + Called by Twisted when the connection is gone. Regardless of whether + it was clean or not, we want to stop the reactor. + """ + if self.fileobj is not None: + self.fileobj.close() + + if reactor.running: + reactor.stop() + + def sendRequest(self): + """ + Send the POST request. + + A POST request is made up of one headers frame, and then 0+ data + frames. This method begins by sending the headers, and then starts a + series of calls to send data. + """ + # First, we need to work out how large the file is. + self.file_size = os.stat(self.file_path).st_size + + # Next, we want to guess a content-type and content-encoding. + content_type, content_encoding = mimetypes.guess_type(self.file_path) + + # Now we can build a header block. + request_headers = [ + (':method', 'POST'), + (':authority', AUTHORITY), + (':scheme', 'https'), + (':path', PATH), + ('user-agent', 'hyper-h2/1.0.0'), + ('content-length', str(self.file_size)), + ] + + if content_type is not None: + request_headers.append(('content-type', content_type)) + + if content_encoding is not None: + request_headers.append(('content-encoding', content_encoding)) + + self.conn.send_headers(1, request_headers) + self.request_made = True + + # We can now open the file. + self.fileobj = open(self.file_path, 'rb') + + # We now need to send all the relevant data. We do this by checking + # what the acceptable amount of data is to send, and sending it. If we + # find ourselves blocked behind flow control, we then place a deferred + # and wait until that deferred fires. + self.sendFileData() + + def sendFileData(self): + """ + Send some file data on the connection. + """ + # Firstly, check what the flow control window is for stream 1. + window_size = self.conn.local_flow_control_window(stream_id=1) + + # Next, check what the maximum frame size is. + max_frame_size = self.conn.max_outbound_frame_size + + # We will send no more than the window size or the remaining file size + # of data in this call, whichever is smaller. + bytes_to_send = min(window_size, self.file_size) + + # We now need to send a number of data frames. + while bytes_to_send > 0: + chunk_size = min(bytes_to_send, max_frame_size) + data_chunk = self.fileobj.read(chunk_size) + self.conn.send_data(stream_id=1, data=data_chunk) + + bytes_to_send -= chunk_size + self.file_size -= chunk_size + + # We've prepared a whole chunk of data to send. If the file is fully + # sent, we also want to end the stream: we're done here. + if self.file_size == 0: + self.conn.end_stream(stream_id=1) + else: + # We've still got data left to send but the window is closed. Save + # a Deferred that will call us when the window gets opened. + self.flow_control_deferred = defer.Deferred() + self.flow_control_deferred.addCallback(self.sendFileData) + + self.transport.write(self.conn.data_to_send()) + + +try: + filename = sys.argv[1] +except IndexError: + filename = __file__ + +options = optionsForClientTLS( + hostname=AUTHORITY, + acceptableProtocols=[b'h2'], +) + +connectProtocol( + SSL4ClientEndpoint(reactor, AUTHORITY, 443, options), + H2Protocol(filename) +) +reactor.run() diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.crt b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.crt new file mode 100644 index 0000000000..bc8a4c08d2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDUjCCAjoCCQCQmNzzpQTCijANBgkqhkiG9w0BAQUFADBrMQswCQYDVQQGEwJH +QjEPMA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xETAPBgNVBAoTCGh5 +cGVyLWgyMREwDwYDVQQLEwhoeXBleS1oMjEUMBIGA1UEAxMLZXhhbXBsZS5jb20w +HhcNMTUwOTE2MjAyOTA0WhcNMTYwOTE1MjAyOTA0WjBrMQswCQYDVQQGEwJHQjEP +MA0GA1UECBMGTG9uZG9uMQ8wDQYDVQQHEwZMb25kb24xETAPBgNVBAoTCGh5cGVy +LWgyMREwDwYDVQQLEwhoeXBleS1oMjEUMBIGA1UEAxMLZXhhbXBsZS5jb20wggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC74ZeB4Jdb5cnC9KXXLJuzjwTg +45q5EeShDYQe0TbKgreiUP6clU3BR0fFAVedN1q/LOuQ1HhvrDk1l4TfGF2bpCIq +K+U9CnzcQknvdpyyVeOLtSsCjOPk4xydHwkQxwJvHVdtJx4CzDDqGbHNHCF/9gpQ +lsa3JZW+tIZLK0XMEPFQ4XFXgegxTStO7kBBPaVIgG9Ooqc2MG4rjMNUpxa28WF1 +SyqWTICf2N8T/C+fPzbQLKCWrFrKUP7WQlOaqPNQL9bCDhSTPRTwQOc2/MzVZ9gT +Xr0Z+JMTXwkSMKO52adE1pmKt00jJ1ecZBiJFyjx0X6hH+/59dLbG/7No+PzAgMB +AAEwDQYJKoZIhvcNAQEFBQADggEBAG3UhOCa0EemL2iY+C+PR6CwEHQ+n7vkBzNz +gKOG+Q39spyzqU1qJAzBxLTE81bIQbDg0R8kcLWHVH2y4zViRxZ0jHUFKMgjONW+ +Aj4evic/2Y/LxpLxCajECq/jeMHYrmQONszf9pbc0+exrQpgnwd8asfsM3d/FJS2 +5DIWryCKs/61m9vYL8icWx/9cnfPkBoNv1ER+V1L1TH3ARvABh406SBaeqLTm/kG +MNuKytKWJsQbNlxzWHVgkKzVsBKvYj0uIEJpClIhbe6XNYRDy8T8mKXVWhJuxH4p +/agmCG3nxO8aCrUK/EVmbWmVIfCH3t7jlwMX1nJ8MsRE7Ydnk8I= +-----END CERTIFICATE----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.csr b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.csr new file mode 100644 index 0000000000..cadb53a512 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICsDCCAZgCAQAwazELMAkGA1UEBhMCR0IxDzANBgNVBAgTBkxvbmRvbjEPMA0G +A1UEBxMGTG9uZG9uMREwDwYDVQQKEwhoeXBlci1oMjERMA8GA1UECxMIaHlwZXkt +aDIxFDASBgNVBAMTC2V4YW1wbGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A +MIIBCgKCAQEAu+GXgeCXW+XJwvSl1yybs48E4OOauRHkoQ2EHtE2yoK3olD+nJVN +wUdHxQFXnTdavyzrkNR4b6w5NZeE3xhdm6QiKivlPQp83EJJ73acslXji7UrAozj +5OMcnR8JEMcCbx1XbSceAsww6hmxzRwhf/YKUJbGtyWVvrSGSytFzBDxUOFxV4Ho +MU0rTu5AQT2lSIBvTqKnNjBuK4zDVKcWtvFhdUsqlkyAn9jfE/wvnz820Cyglqxa +ylD+1kJTmqjzUC/Wwg4Ukz0U8EDnNvzM1WfYE169GfiTE18JEjCjudmnRNaZirdN +IydXnGQYiRco8dF+oR/v+fXS2xv+zaPj8wIDAQABoAAwDQYJKoZIhvcNAQEFBQAD +ggEBACZpSoZWxHU5uagpM2Vinh2E7CXiMAlBc6NXhQMD/3fycr9sX4d/+y9Gy3bL +OfEOHBPlQVGrt05aiTh7m5s3HQfsH8l3RfKpfzCfoqd2ESVwgB092bJwY9fBnkw/ +UzIHvSnlaKc78h+POUoATOb4faQ8P04wzJHzckbCDI8zRzBZTMVGuiWUopq7K5Ce +VSesbqHHnW9ob/apigKNE0k7et/28NOXNEP90tTsz98yN3TP+Nv9puwvT9JZOOoG +0PZIQKJIaZ1NZoNQHLN9gXz012XWa99cBE0qNiBUugXlNhXjkIIM8FIhDQOREB18 +0KDxEma+A0quyjnDMwPSoZsMca4= +-----END CERTIFICATE REQUEST----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.key b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.key new file mode 100644 index 0000000000..11f9ea094b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAu+GXgeCXW+XJwvSl1yybs48E4OOauRHkoQ2EHtE2yoK3olD+ +nJVNwUdHxQFXnTdavyzrkNR4b6w5NZeE3xhdm6QiKivlPQp83EJJ73acslXji7Ur +Aozj5OMcnR8JEMcCbx1XbSceAsww6hmxzRwhf/YKUJbGtyWVvrSGSytFzBDxUOFx +V4HoMU0rTu5AQT2lSIBvTqKnNjBuK4zDVKcWtvFhdUsqlkyAn9jfE/wvnz820Cyg +lqxaylD+1kJTmqjzUC/Wwg4Ukz0U8EDnNvzM1WfYE169GfiTE18JEjCjudmnRNaZ +irdNIydXnGQYiRco8dF+oR/v+fXS2xv+zaPj8wIDAQABAoIBAQCsdq278+0c13d4 +tViSh4k5r1w8D9IUdp9XU2/nVgckqA9nOVAvbkJc3FC+P7gsQgbUHKj0XoVbhU1S +q461t8kduPH/oiGhAcKR8WurHEdE0OC6ewhLJAeCMRQwCrAorXXHh7icIt9ClCuG +iSWUcXEy5Cidx3oL3r1xvIbV85fzdDtE9RC1I/kMjAy63S47YGiqh5vYmJkCa8rG +Dsd1sEMDPr63XJpqJj3uHRcPvySgXTa+ssTmUH8WJlPTjvDB5hnPz+lkk2JKVPNu +8adzftZ6hSun+tsc4ZJp8XhGu/m/7MjxWh8MeupLHlXcOEsnj4uHQQsOM3zHojr3 +aDCZiC1pAoGBAOAhwe1ujoS2VJ5RXJ9KMs7eBER/02MDgWZjo54Jv/jFxPWGslKk +QQceuTe+PruRm41nzvk3q4iZXt8pG0bvpgigN2epcVx/O2ouRsUWWBT0JrVlEzha +TIvWjtZ5tSQExXgHL3VlM9+ka40l+NldLSPn25+prizaqhalWuvTpP23AoGBANaY +VhEI6yhp0BBUSATEv9lRgkwx3EbcnXNXPQjDMOthsyfq7FxbdOBEK1rwSDyuE6Ij +zQGcTOfdiur5Ttg0OQilTJIXJAlpoeecOQ9yGma08c5FMXVJJvcZUuWRZWg1ocQj +/hx0WVE9NwOoKwTBERv8HX7vJOFRZyvgkJwFxoulAoGAe4m/1XoZrga9z2GzNs10 +AdgX7BW00x+MhH4pIiPnn1yK+nYa9jg4647Asnv3IfXZEnEEgRNxReKbi0+iDFBt +aNW+lDGuHTi37AfD1EBDnpEQgO1MUcRb6rwBkTAWatsCaO00+HUmyX9cFLm4Vz7n +caILyQ6CxZBlLgRIgDHxADMCgYEAtubsJGTHmZBmSCStpXLUWbOBLNQqfTM398DZ +QoirP1PsUQ+IGUfSG/u+QCogR6fPEBkXeFHxsoY/Cvsm2lvYaKgK1VFn46Xm2vNq +JuIH4pZCqp6LAv4weddZslT0a5eaowRSZ4o7PmTAaRuCXvD3VjTSJwhJFMo+90TV +vEWn7gkCgYEAkk+unX9kYmKoUdLh22/tzQekBa8WqMxXDwzBCECTAs2GlpL/f73i +zD15TnaNfLP6Q5RNb0N9tb0Gz1wSkwI1+jGAQLnh2K9X9cIVIqJn8Mf/KQa/wUDV +Tb1j7FoGUEgX7vbsyWuTd8P76kNYyGqCss1XmbttcSolqpbIdlSUcO0= +-----END RSA PRIVATE KEY----- diff --git a/testing/web-platform/tests/tools/third_party/h2/examples/twisted/twisted-server.py b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/twisted-server.py new file mode 100644 index 0000000000..75a271d9b7 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/examples/twisted/twisted-server.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- +""" +twisted-server.py +~~~~~~~~~~~~~~~~~ + +A fully-functional HTTP/2 server written for Twisted. +""" +import functools +import mimetypes +import os +import os.path +import sys + +from OpenSSL import crypto +from twisted.internet.defer import Deferred, inlineCallbacks +from twisted.internet.protocol import Protocol, Factory +from twisted.internet import endpoints, reactor, ssl +from h2.config import H2Configuration +from h2.connection import H2Connection +from h2.events import ( + RequestReceived, DataReceived, WindowUpdated +) +from h2.exceptions import ProtocolError + + +def close_file(file, d): + file.close() + + +READ_CHUNK_SIZE = 8192 + + +class H2Protocol(Protocol): + def __init__(self, root): + config = H2Configuration(client_side=False) + self.conn = H2Connection(config=config) + self.known_proto = None + self.root = root + + self._flow_control_deferreds = {} + + def connectionMade(self): + self.conn.initiate_connection() + self.transport.write(self.conn.data_to_send()) + + def dataReceived(self, data): + if not self.known_proto: + self.known_proto = True + + try: + events = self.conn.receive_data(data) + except ProtocolError: + if self.conn.data_to_send: + self.transport.write(self.conn.data_to_send()) + self.transport.loseConnection() + else: + for event in events: + if isinstance(event, RequestReceived): + self.requestReceived(event.headers, event.stream_id) + elif isinstance(event, DataReceived): + self.dataFrameReceived(event.stream_id) + elif isinstance(event, WindowUpdated): + self.windowUpdated(event) + + if self.conn.data_to_send: + self.transport.write(self.conn.data_to_send()) + + def requestReceived(self, headers, stream_id): + headers = dict(headers) # Invalid conversion, fix later. + assert headers[b':method'] == b'GET' + + path = headers[b':path'].lstrip(b'/') + full_path = os.path.join(self.root, path) + + if not os.path.exists(full_path): + response_headers = ( + (':status', '404'), + ('content-length', '0'), + ('server', 'twisted-h2'), + ) + self.conn.send_headers( + stream_id, response_headers, end_stream=True + ) + self.transport.write(self.conn.data_to_send()) + else: + self.sendFile(full_path, stream_id) + + return + + def dataFrameReceived(self, stream_id): + self.conn.reset_stream(stream_id) + self.transport.write(self.conn.data_to_send()) + + def sendFile(self, file_path, stream_id): + filesize = os.stat(file_path).st_size + content_type, content_encoding = mimetypes.guess_type( + file_path.decode('utf-8') + ) + response_headers = [ + (':status', '200'), + ('content-length', str(filesize)), + ('server', 'twisted-h2'), + ] + if content_type: + response_headers.append(('content-type', content_type)) + if content_encoding: + response_headers.append(('content-encoding', content_encoding)) + + self.conn.send_headers(stream_id, response_headers) + self.transport.write(self.conn.data_to_send()) + + f = open(file_path, 'rb') + d = self._send_file(f, stream_id) + d.addErrback(functools.partial(close_file, f)) + + def windowUpdated(self, event): + """ + Handle a WindowUpdated event by firing any waiting data sending + callbacks. + """ + stream_id = event.stream_id + + if stream_id and stream_id in self._flow_control_deferreds: + d = self._flow_control_deferreds.pop(stream_id) + d.callback(event.delta) + elif not stream_id: + for d in self._flow_control_deferreds.values(): + d.callback(event.delta) + + self._flow_control_deferreds = {} + + return + + @inlineCallbacks + def _send_file(self, file, stream_id): + """ + This callback sends more data for a given file on the stream. + """ + keep_reading = True + while keep_reading: + while not self.conn.remote_flow_control_window(stream_id): + yield self.wait_for_flow_control(stream_id) + + chunk_size = min( + self.conn.remote_flow_control_window(stream_id), READ_CHUNK_SIZE + ) + data = file.read(chunk_size) + keep_reading = len(data) == chunk_size + self.conn.send_data(stream_id, data, not keep_reading) + self.transport.write(self.conn.data_to_send()) + + if not keep_reading: + break + + file.close() + + def wait_for_flow_control(self, stream_id): + """ + Returns a Deferred that fires when the flow control window is opened. + """ + d = Deferred() + self._flow_control_deferreds[stream_id] = d + return d + + +class H2Factory(Factory): + def __init__(self, root): + self.root = root + + def buildProtocol(self, addr): + print(H2Protocol) + return H2Protocol(self.root) + + +root = sys.argv[1].encode('utf-8') + +with open('server.crt', 'r') as f: + cert_data = f.read() +with open('server.key', 'r') as f: + key_data = f.read() + +cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_data) +key = crypto.load_privatekey(crypto.FILETYPE_PEM, key_data) +options = ssl.CertificateOptions( + privateKey=key, + certificate=cert, + acceptableProtocols=[b'h2'], +) + +endpoint = endpoints.SSL4ServerEndpoint(reactor, 8080, options, backlog=128) +endpoint.listen(H2Factory(root)) +reactor.run() diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/__init__.py b/testing/web-platform/tests/tools/third_party/h2/h2/__init__.py new file mode 100644 index 0000000000..6290710e63 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/__init__.py @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" +h2 +~~ + +A HTTP/2 implementation. +""" +__version__ = '3.2.0' diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/config.py b/testing/web-platform/tests/tools/third_party/h2/h2/config.py new file mode 100644 index 0000000000..1c437ee24f --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/config.py @@ -0,0 +1,170 @@ +# -*- coding: utf-8 -*- +""" +h2/config +~~~~~~~~~ + +Objects for controlling the configuration of the HTTP/2 stack. +""" + + +class _BooleanConfigOption(object): + """ + Descriptor for handling a boolean config option. This will block + attempts to set boolean config options to non-bools. + """ + def __init__(self, name): + self.name = name + self.attr_name = '_%s' % self.name + + def __get__(self, instance, owner): + return getattr(instance, self.attr_name) + + def __set__(self, instance, value): + if not isinstance(value, bool): + raise ValueError("%s must be a bool" % self.name) + setattr(instance, self.attr_name, value) + + +class DummyLogger(object): + """ + An Logger object that does not actual logging, hence a DummyLogger. + + For the class the log operation is merely a no-op. The intent is to avoid + conditionals being sprinkled throughout the hyper-h2 code for calls to + logging functions when no logger is passed into the corresponding object. + """ + def __init__(self, *vargs): + pass + + def debug(self, *vargs, **kwargs): + """ + No-op logging. Only level needed for now. + """ + pass + + def trace(self, *vargs, **kwargs): + """ + No-op logging. Only level needed for now. + """ + pass + + +class H2Configuration(object): + """ + An object that controls the way a single HTTP/2 connection behaves. + + This object allows the users to customize behaviour. In particular, it + allows users to enable or disable optional features, or to otherwise handle + various unusual behaviours. + + This object has very little behaviour of its own: it mostly just ensures + that configuration is self-consistent. + + :param client_side: Whether this object is to be used on the client side of + a connection, or on the server side. Affects the logic used by the + state machine, the default settings values, the allowable stream IDs, + and several other properties. Defaults to ``True``. + :type client_side: ``bool`` + + :param header_encoding: Controls whether the headers emitted by this object + in events are transparently decoded to ``unicode`` strings, and what + encoding is used to do that decoding. This defaults to ``None``, + meaning that headers will be returned as bytes. To automatically + decode headers (that is, to return them as unicode strings), this can + be set to the string name of any encoding, e.g. ``'utf-8'``. + + .. versionchanged:: 3.0.0 + Changed default value from ``'utf-8'`` to ``None`` + + :type header_encoding: ``str``, ``False``, or ``None`` + + :param validate_outbound_headers: Controls whether the headers emitted + by this object are validated against the rules in RFC 7540. + Disabling this setting will cause outbound header validation to + be skipped, and allow the object to emit headers that may be illegal + according to RFC 7540. Defaults to ``True``. + :type validate_outbound_headers: ``bool`` + + :param normalize_outbound_headers: Controls whether the headers emitted + by this object are normalized before sending. Disabling this setting + will cause outbound header normalization to be skipped, and allow + the object to emit headers that may be illegal according to + RFC 7540. Defaults to ``True``. + :type normalize_outbound_headers: ``bool`` + + :param validate_inbound_headers: Controls whether the headers received + by this object are validated against the rules in RFC 7540. + Disabling this setting will cause inbound header validation to + be skipped, and allow the object to receive headers that may be illegal + according to RFC 7540. Defaults to ``True``. + :type validate_inbound_headers: ``bool`` + + :param normalize_inbound_headers: Controls whether the headers received by + this object are normalized according to the rules of RFC 7540. + Disabling this setting may lead to hyper-h2 emitting header blocks that + some RFCs forbid, e.g. with multiple cookie fields. + + .. versionadded:: 3.0.0 + + :type normalize_inbound_headers: ``bool`` + + :param logger: A logger that conforms to the requirements for this module, + those being no I/O and no context switches, which is needed in order + to run in asynchronous operation. + + .. versionadded:: 2.6.0 + + :type logger: ``logging.Logger`` + """ + client_side = _BooleanConfigOption('client_side') + validate_outbound_headers = _BooleanConfigOption( + 'validate_outbound_headers' + ) + normalize_outbound_headers = _BooleanConfigOption( + 'normalize_outbound_headers' + ) + validate_inbound_headers = _BooleanConfigOption( + 'validate_inbound_headers' + ) + normalize_inbound_headers = _BooleanConfigOption( + 'normalize_inbound_headers' + ) + + def __init__(self, + client_side=True, + header_encoding=None, + validate_outbound_headers=True, + normalize_outbound_headers=True, + validate_inbound_headers=True, + normalize_inbound_headers=True, + logger=None): + self.client_side = client_side + self.header_encoding = header_encoding + self.validate_outbound_headers = validate_outbound_headers + self.normalize_outbound_headers = normalize_outbound_headers + self.validate_inbound_headers = validate_inbound_headers + self.normalize_inbound_headers = normalize_inbound_headers + self.logger = logger or DummyLogger(__name__) + + @property + def header_encoding(self): + """ + Controls whether the headers emitted by this object in events are + transparently decoded to ``unicode`` strings, and what encoding is used + to do that decoding. This defaults to ``None``, meaning that headers + will be returned as bytes. To automatically decode headers (that is, to + return them as unicode strings), this can be set to the string name of + any encoding, e.g. ``'utf-8'``. + """ + return self._header_encoding + + @header_encoding.setter + def header_encoding(self, value): + """ + Enforces constraints on the value of header encoding. + """ + if not isinstance(value, (bool, str, type(None))): + raise ValueError("header_encoding must be bool, string, or None") + if value is True: + raise ValueError("header_encoding cannot be True") + self._header_encoding = value diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/connection.py b/testing/web-platform/tests/tools/third_party/h2/h2/connection.py new file mode 100644 index 0000000000..35e6fd6da8 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/connection.py @@ -0,0 +1,2048 @@ +# -*- coding: utf-8 -*- +""" +h2/connection +~~~~~~~~~~~~~ + +An implementation of a HTTP/2 connection. +""" +import base64 + +from enum import Enum, IntEnum + +from hyperframe.exceptions import InvalidPaddingError +from hyperframe.frame import ( + GoAwayFrame, WindowUpdateFrame, HeadersFrame, DataFrame, PingFrame, + PushPromiseFrame, SettingsFrame, RstStreamFrame, PriorityFrame, + ContinuationFrame, AltSvcFrame, ExtensionFrame +) +from hpack.hpack import Encoder, Decoder +from hpack.exceptions import HPACKError, OversizedHeaderListError + +from .config import H2Configuration +from .errors import ErrorCodes, _error_code_from_int +from .events import ( + WindowUpdated, RemoteSettingsChanged, PingReceived, PingAckReceived, + SettingsAcknowledged, ConnectionTerminated, PriorityUpdated, + AlternativeServiceAvailable, UnknownFrameReceived +) +from .exceptions import ( + ProtocolError, NoSuchStreamError, FlowControlError, FrameTooLargeError, + TooManyStreamsError, StreamClosedError, StreamIDTooLowError, + NoAvailableStreamIDError, RFC1122Error, DenialOfServiceError +) +from .frame_buffer import FrameBuffer +from .settings import Settings, SettingCodes +from .stream import H2Stream, StreamClosedBy +from .utilities import SizeLimitDict, guard_increment_window +from .windows import WindowManager + + +class ConnectionState(Enum): + IDLE = 0 + CLIENT_OPEN = 1 + SERVER_OPEN = 2 + CLOSED = 3 + + +class ConnectionInputs(Enum): + SEND_HEADERS = 0 + SEND_PUSH_PROMISE = 1 + SEND_DATA = 2 + SEND_GOAWAY = 3 + SEND_WINDOW_UPDATE = 4 + SEND_PING = 5 + SEND_SETTINGS = 6 + SEND_RST_STREAM = 7 + SEND_PRIORITY = 8 + RECV_HEADERS = 9 + RECV_PUSH_PROMISE = 10 + RECV_DATA = 11 + RECV_GOAWAY = 12 + RECV_WINDOW_UPDATE = 13 + RECV_PING = 14 + RECV_SETTINGS = 15 + RECV_RST_STREAM = 16 + RECV_PRIORITY = 17 + SEND_ALTERNATIVE_SERVICE = 18 # Added in 2.3.0 + RECV_ALTERNATIVE_SERVICE = 19 # Added in 2.3.0 + + +class AllowedStreamIDs(IntEnum): + EVEN = 0 + ODD = 1 + + +class H2ConnectionStateMachine(object): + """ + A single HTTP/2 connection state machine. + + This state machine, while defined in its own class, is logically part of + the H2Connection class also defined in this file. The state machine itself + maintains very little state directly, instead focusing entirely on managing + state transitions. + """ + # For the purposes of this state machine we treat HEADERS and their + # associated CONTINUATION frames as a single jumbo frame. The protocol + # allows/requires this by preventing other frames from being interleved in + # between HEADERS/CONTINUATION frames. + # + # The _transitions dictionary contains a mapping of tuples of + # (state, input) to tuples of (side_effect_function, end_state). This map + # contains all allowed transitions: anything not in this map is invalid + # and immediately causes a transition to ``closed``. + + _transitions = { + # State: idle + (ConnectionState.IDLE, ConnectionInputs.SEND_HEADERS): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.IDLE, ConnectionInputs.RECV_HEADERS): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.IDLE, ConnectionInputs.SEND_SETTINGS): + (None, ConnectionState.IDLE), + (ConnectionState.IDLE, ConnectionInputs.RECV_SETTINGS): + (None, ConnectionState.IDLE), + (ConnectionState.IDLE, ConnectionInputs.SEND_WINDOW_UPDATE): + (None, ConnectionState.IDLE), + (ConnectionState.IDLE, ConnectionInputs.RECV_WINDOW_UPDATE): + (None, ConnectionState.IDLE), + (ConnectionState.IDLE, ConnectionInputs.SEND_PING): + (None, ConnectionState.IDLE), + (ConnectionState.IDLE, ConnectionInputs.RECV_PING): + (None, ConnectionState.IDLE), + (ConnectionState.IDLE, ConnectionInputs.SEND_GOAWAY): + (None, ConnectionState.CLOSED), + (ConnectionState.IDLE, ConnectionInputs.RECV_GOAWAY): + (None, ConnectionState.CLOSED), + (ConnectionState.IDLE, ConnectionInputs.SEND_PRIORITY): + (None, ConnectionState.IDLE), + (ConnectionState.IDLE, ConnectionInputs.RECV_PRIORITY): + (None, ConnectionState.IDLE), + (ConnectionState.IDLE, ConnectionInputs.SEND_ALTERNATIVE_SERVICE): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.IDLE, ConnectionInputs.RECV_ALTERNATIVE_SERVICE): + (None, ConnectionState.CLIENT_OPEN), + + # State: open, client side. + (ConnectionState.CLIENT_OPEN, ConnectionInputs.SEND_HEADERS): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.SEND_DATA): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.SEND_GOAWAY): + (None, ConnectionState.CLOSED), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.SEND_WINDOW_UPDATE): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.SEND_PING): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.SEND_SETTINGS): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.SEND_PRIORITY): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.RECV_HEADERS): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.RECV_PUSH_PROMISE): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.RECV_DATA): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.RECV_GOAWAY): + (None, ConnectionState.CLOSED), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.RECV_WINDOW_UPDATE): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.RECV_PING): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.RECV_SETTINGS): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.SEND_RST_STREAM): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.RECV_RST_STREAM): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, ConnectionInputs.RECV_PRIORITY): + (None, ConnectionState.CLIENT_OPEN), + (ConnectionState.CLIENT_OPEN, + ConnectionInputs.RECV_ALTERNATIVE_SERVICE): + (None, ConnectionState.CLIENT_OPEN), + + # State: open, server side. + (ConnectionState.SERVER_OPEN, ConnectionInputs.SEND_HEADERS): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.SEND_PUSH_PROMISE): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.SEND_DATA): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.SEND_GOAWAY): + (None, ConnectionState.CLOSED), + (ConnectionState.SERVER_OPEN, ConnectionInputs.SEND_WINDOW_UPDATE): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.SEND_PING): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.SEND_SETTINGS): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.SEND_PRIORITY): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.RECV_HEADERS): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.RECV_DATA): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.RECV_GOAWAY): + (None, ConnectionState.CLOSED), + (ConnectionState.SERVER_OPEN, ConnectionInputs.RECV_WINDOW_UPDATE): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.RECV_PING): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.RECV_SETTINGS): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.RECV_PRIORITY): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.SEND_RST_STREAM): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, ConnectionInputs.RECV_RST_STREAM): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, + ConnectionInputs.SEND_ALTERNATIVE_SERVICE): + (None, ConnectionState.SERVER_OPEN), + (ConnectionState.SERVER_OPEN, + ConnectionInputs.RECV_ALTERNATIVE_SERVICE): + (None, ConnectionState.SERVER_OPEN), + + # State: closed + (ConnectionState.CLOSED, ConnectionInputs.SEND_GOAWAY): + (None, ConnectionState.CLOSED), + (ConnectionState.CLOSED, ConnectionInputs.RECV_GOAWAY): + (None, ConnectionState.CLOSED), + } + + def __init__(self): + self.state = ConnectionState.IDLE + + def process_input(self, input_): + """ + Process a specific input in the state machine. + """ + if not isinstance(input_, ConnectionInputs): + raise ValueError("Input must be an instance of ConnectionInputs") + + try: + func, target_state = self._transitions[(self.state, input_)] + except KeyError: + old_state = self.state + self.state = ConnectionState.CLOSED + raise ProtocolError( + "Invalid input %s in state %s" % (input_, old_state) + ) + else: + self.state = target_state + if func is not None: # pragma: no cover + return func() + + return [] + + +class H2Connection(object): + """ + A low-level HTTP/2 connection object. This handles building and receiving + frames and maintains both connection and per-stream state for all streams + on this connection. + + This wraps a HTTP/2 Connection state machine implementation, ensuring that + frames can only be sent/received when the connection is in a valid state. + It also builds stream state machines on demand to ensure that the + constraints of those state machines are met as well. Attempts to create + frames that cannot be sent will raise a ``ProtocolError``. + + .. versionchanged:: 2.3.0 + Added the ``header_encoding`` keyword argument. + + .. versionchanged:: 2.5.0 + Added the ``config`` keyword argument. Deprecated the ``client_side`` + and ``header_encoding`` parameters. + + .. versionchanged:: 3.0.0 + Removed deprecated parameters and properties. + + :param config: The configuration for the HTTP/2 connection. + + .. versionadded:: 2.5.0 + + :type config: :class:`H2Configuration ` + """ + # The initial maximum outbound frame size. This can be changed by receiving + # a settings frame. + DEFAULT_MAX_OUTBOUND_FRAME_SIZE = 65535 + + # The initial maximum inbound frame size. This is somewhat arbitrarily + # chosen. + DEFAULT_MAX_INBOUND_FRAME_SIZE = 2**24 + + # The highest acceptable stream ID. + HIGHEST_ALLOWED_STREAM_ID = 2**31 - 1 + + # The largest acceptable window increment. + MAX_WINDOW_INCREMENT = 2**31 - 1 + + # The initial default value of SETTINGS_MAX_HEADER_LIST_SIZE. + DEFAULT_MAX_HEADER_LIST_SIZE = 2**16 + + # Keep in memory limited amount of results for streams closes + MAX_CLOSED_STREAMS = 2**16 + + def __init__(self, config=None): + self.state_machine = H2ConnectionStateMachine() + self.streams = {} + self.highest_inbound_stream_id = 0 + self.highest_outbound_stream_id = 0 + self.encoder = Encoder() + self.decoder = Decoder() + + # This won't always actually do anything: for versions of HPACK older + # than 2.3.0 it does nothing. However, we have to try! + self.decoder.max_header_list_size = self.DEFAULT_MAX_HEADER_LIST_SIZE + + #: The configuration for this HTTP/2 connection object. + #: + #: .. versionadded:: 2.5.0 + self.config = config + if self.config is None: + self.config = H2Configuration( + client_side=True, + ) + + # Objects that store settings, including defaults. + # + # We set the MAX_CONCURRENT_STREAMS value to 100 because its default is + # unbounded, and that's a dangerous default because it allows + # essentially unbounded resources to be allocated regardless of how + # they will be used. 100 should be suitable for the average + # application. This default obviously does not apply to the remote + # peer's settings: the remote peer controls them! + # + # We also set MAX_HEADER_LIST_SIZE to a reasonable value. This is to + # advertise our defence against CVE-2016-6581. However, not all + # versions of HPACK will let us do it. That's ok: we should at least + # suggest that we're not vulnerable. + self.local_settings = Settings( + client=self.config.client_side, + initial_values={ + SettingCodes.MAX_CONCURRENT_STREAMS: 100, + SettingCodes.MAX_HEADER_LIST_SIZE: + self.DEFAULT_MAX_HEADER_LIST_SIZE, + } + ) + self.remote_settings = Settings(client=not self.config.client_side) + + # The current value of the connection flow control windows on the + # connection. + self.outbound_flow_control_window = ( + self.remote_settings.initial_window_size + ) + + #: The maximum size of a frame that can be emitted by this peer, in + #: bytes. + self.max_outbound_frame_size = self.remote_settings.max_frame_size + + #: The maximum size of a frame that can be received by this peer, in + #: bytes. + self.max_inbound_frame_size = self.local_settings.max_frame_size + + # Buffer for incoming data. + self.incoming_buffer = FrameBuffer(server=not self.config.client_side) + + # A private variable to store a sequence of received header frames + # until completion. + self._header_frames = [] + + # Data that needs to be sent. + self._data_to_send = bytearray() + + # Keeps track of how streams are closed. + # Used to ensure that we don't blow up in the face of frames that were + # in flight when a RST_STREAM was sent. + # Also used to determine whether we should consider a frame received + # while a stream is closed as either a stream error or a connection + # error. + self._closed_streams = SizeLimitDict( + size_limit=self.MAX_CLOSED_STREAMS + ) + + # The flow control window manager for the connection. + self._inbound_flow_control_window_manager = WindowManager( + max_window_size=self.local_settings.initial_window_size + ) + + # When in doubt use dict-dispatch. + self._frame_dispatch_table = { + HeadersFrame: self._receive_headers_frame, + PushPromiseFrame: self._receive_push_promise_frame, + SettingsFrame: self._receive_settings_frame, + DataFrame: self._receive_data_frame, + WindowUpdateFrame: self._receive_window_update_frame, + PingFrame: self._receive_ping_frame, + RstStreamFrame: self._receive_rst_stream_frame, + PriorityFrame: self._receive_priority_frame, + GoAwayFrame: self._receive_goaway_frame, + ContinuationFrame: self._receive_naked_continuation, + AltSvcFrame: self._receive_alt_svc_frame, + ExtensionFrame: self._receive_unknown_frame + } + + def _prepare_for_sending(self, frames): + if not frames: + return + self._data_to_send += b''.join(f.serialize() for f in frames) + assert all(f.body_len <= self.max_outbound_frame_size for f in frames) + + def _open_streams(self, remainder): + """ + A common method of counting number of open streams. Returns the number + of streams that are open *and* that have (stream ID % 2) == remainder. + While it iterates, also deletes any closed streams. + """ + count = 0 + to_delete = [] + + for stream_id, stream in self.streams.items(): + if stream.open and (stream_id % 2 == remainder): + count += 1 + elif stream.closed: + to_delete.append(stream_id) + + for stream_id in to_delete: + stream = self.streams.pop(stream_id) + self._closed_streams[stream_id] = stream.closed_by + + return count + + @property + def open_outbound_streams(self): + """ + The current number of open outbound streams. + """ + outbound_numbers = int(self.config.client_side) + return self._open_streams(outbound_numbers) + + @property + def open_inbound_streams(self): + """ + The current number of open inbound streams. + """ + inbound_numbers = int(not self.config.client_side) + return self._open_streams(inbound_numbers) + + @property + def inbound_flow_control_window(self): + """ + The size of the inbound flow control window for the connection. This is + rarely publicly useful: instead, use :meth:`remote_flow_control_window + `. This + shortcut is largely present to provide a shortcut to this data. + """ + return self._inbound_flow_control_window_manager.current_window_size + + def _begin_new_stream(self, stream_id, allowed_ids): + """ + Initiate a new stream. + + .. versionchanged:: 2.0.0 + Removed this function from the public API. + + :param stream_id: The ID of the stream to open. + :param allowed_ids: What kind of stream ID is allowed. + """ + self.config.logger.debug( + "Attempting to initiate stream ID %d", stream_id + ) + outbound = self._stream_id_is_outbound(stream_id) + highest_stream_id = ( + self.highest_outbound_stream_id if outbound else + self.highest_inbound_stream_id + ) + + if stream_id <= highest_stream_id: + raise StreamIDTooLowError(stream_id, highest_stream_id) + + if (stream_id % 2) != int(allowed_ids): + raise ProtocolError( + "Invalid stream ID for peer." + ) + + s = H2Stream( + stream_id, + config=self.config, + inbound_window_size=self.local_settings.initial_window_size, + outbound_window_size=self.remote_settings.initial_window_size + ) + self.config.logger.debug("Stream ID %d created", stream_id) + s.max_inbound_frame_size = self.max_inbound_frame_size + s.max_outbound_frame_size = self.max_outbound_frame_size + + self.streams[stream_id] = s + self.config.logger.debug("Current streams: %s", self.streams.keys()) + + if outbound: + self.highest_outbound_stream_id = stream_id + else: + self.highest_inbound_stream_id = stream_id + + return s + + def initiate_connection(self): + """ + Provides any data that needs to be sent at the start of the connection. + Must be called for both clients and servers. + """ + self.config.logger.debug("Initializing connection") + self.state_machine.process_input(ConnectionInputs.SEND_SETTINGS) + if self.config.client_side: + preamble = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' + else: + preamble = b'' + + f = SettingsFrame(0) + for setting, value in self.local_settings.items(): + f.settings[setting] = value + self.config.logger.debug( + "Send Settings frame: %s", self.local_settings + ) + + self._data_to_send += preamble + f.serialize() + + def initiate_upgrade_connection(self, settings_header=None): + """ + Call to initialise the connection object for use with an upgraded + HTTP/2 connection (i.e. a connection negotiated using the + ``Upgrade: h2c`` HTTP header). + + This method differs from :meth:`initiate_connection + ` in several ways. + Firstly, it handles the additional SETTINGS frame that is sent in the + ``HTTP2-Settings`` header field. When called on a client connection, + this method will return a bytestring that the caller can put in the + ``HTTP2-Settings`` field they send on their initial request. When + called on a server connection, the user **must** provide the value they + received from the client in the ``HTTP2-Settings`` header field to the + ``settings_header`` argument, which will be used appropriately. + + Additionally, this method sets up stream 1 in a half-closed state + appropriate for this side of the connection, to reflect the fact that + the request is already complete. + + Finally, this method also prepares the appropriate preamble to be sent + after the upgrade. + + .. versionadded:: 2.3.0 + + :param settings_header: (optional, server-only): The value of the + ``HTTP2-Settings`` header field received from the client. + :type settings_header: ``bytes`` + + :returns: For clients, a bytestring to put in the ``HTTP2-Settings``. + For servers, returns nothing. + :rtype: ``bytes`` or ``None`` + """ + self.config.logger.debug( + "Upgrade connection. Current settings: %s", self.local_settings + ) + + frame_data = None + # Begin by getting the preamble in place. + self.initiate_connection() + + if self.config.client_side: + f = SettingsFrame(0) + for setting, value in self.local_settings.items(): + f.settings[setting] = value + + frame_data = f.serialize_body() + frame_data = base64.urlsafe_b64encode(frame_data) + elif settings_header: + # We have a settings header from the client. This needs to be + # applied, but we want to throw away the ACK. We do this by + # inserting the data into a Settings frame and then passing it to + # the state machine, but ignoring the return value. + settings_header = base64.urlsafe_b64decode(settings_header) + f = SettingsFrame(0) + f.parse_body(settings_header) + self._receive_settings_frame(f) + + # Set up appropriate state. Stream 1 in a half-closed state: + # half-closed(local) for clients, half-closed(remote) for servers. + # Additionally, we need to set up the Connection state machine. + connection_input = ( + ConnectionInputs.SEND_HEADERS if self.config.client_side + else ConnectionInputs.RECV_HEADERS + ) + self.config.logger.debug("Process input %s", connection_input) + self.state_machine.process_input(connection_input) + + # Set up stream 1. + self._begin_new_stream(stream_id=1, allowed_ids=AllowedStreamIDs.ODD) + self.streams[1].upgrade(self.config.client_side) + return frame_data + + def _get_or_create_stream(self, stream_id, allowed_ids): + """ + Gets a stream by its stream ID. Will create one if one does not already + exist. Use allowed_ids to circumvent the usual stream ID rules for + clients and servers. + + .. versionchanged:: 2.0.0 + Removed this function from the public API. + """ + try: + return self.streams[stream_id] + except KeyError: + return self._begin_new_stream(stream_id, allowed_ids) + + def _get_stream_by_id(self, stream_id): + """ + Gets a stream by its stream ID. Raises NoSuchStreamError if the stream + ID does not correspond to a known stream and is higher than the current + maximum: raises if it is lower than the current maximum. + + .. versionchanged:: 2.0.0 + Removed this function from the public API. + """ + try: + return self.streams[stream_id] + except KeyError: + outbound = self._stream_id_is_outbound(stream_id) + highest_stream_id = ( + self.highest_outbound_stream_id if outbound else + self.highest_inbound_stream_id + ) + + if stream_id > highest_stream_id: + raise NoSuchStreamError(stream_id) + else: + raise StreamClosedError(stream_id) + + def get_next_available_stream_id(self): + """ + Returns an integer suitable for use as the stream ID for the next + stream created by this endpoint. For server endpoints, this stream ID + will be even. For client endpoints, this stream ID will be odd. If no + stream IDs are available, raises :class:`NoAvailableStreamIDError + `. + + .. warning:: The return value from this function does not change until + the stream ID has actually been used by sending or pushing + headers on that stream. For that reason, it should be + called as close as possible to the actual use of the + stream ID. + + .. versionadded:: 2.0.0 + + :raises: :class:`NoAvailableStreamIDError + ` + :returns: The next free stream ID this peer can use to initiate a + stream. + :rtype: ``int`` + """ + # No streams have been opened yet, so return the lowest allowed stream + # ID. + if not self.highest_outbound_stream_id: + next_stream_id = 1 if self.config.client_side else 2 + else: + next_stream_id = self.highest_outbound_stream_id + 2 + self.config.logger.debug( + "Next available stream ID %d", next_stream_id + ) + if next_stream_id > self.HIGHEST_ALLOWED_STREAM_ID: + raise NoAvailableStreamIDError("Exhausted allowed stream IDs") + + return next_stream_id + + def send_headers(self, stream_id, headers, end_stream=False, + priority_weight=None, priority_depends_on=None, + priority_exclusive=None): + """ + Send headers on a given stream. + + This function can be used to send request or response headers: the kind + that are sent depends on whether this connection has been opened as a + client or server connection, and whether the stream was opened by the + remote peer or not. + + If this is a client connection, calling ``send_headers`` will send the + headers as a request. It will also implicitly open the stream being + used. If this is a client connection and ``send_headers`` has *already* + been called, this will send trailers instead. + + If this is a server connection, calling ``send_headers`` will send the + headers as a response. It is a protocol error for a server to open a + stream by sending headers. If this is a server connection and + ``send_headers`` has *already* been called, this will send trailers + instead. + + When acting as a server, you may call ``send_headers`` any number of + times allowed by the following rules, in this order: + + - zero or more times with ``(':status', '1XX')`` (where ``1XX`` is a + placeholder for any 100-level status code). + - once with any other status header. + - zero or one time for trailers. + + That is, you are allowed to send as many informational responses as you + like, followed by one complete response and zero or one HTTP trailer + blocks. + + Clients may send one or two header blocks: one request block, and + optionally one trailer block. + + If it is important to send HPACK "never indexed" header fields (as + defined in `RFC 7451 Section 7.1.3 + `_), the user may + instead provide headers using the HPACK library's :class:`HeaderTuple + ` and :class:`NeverIndexedHeaderTuple + ` objects. + + This method also allows users to prioritize the stream immediately, + by sending priority information on the HEADERS frame directly. To do + this, any one of ``priority_weight``, ``priority_depends_on``, or + ``priority_exclusive`` must be set to a value that is not ``None``. For + more information on the priority fields, see :meth:`prioritize + `. + + .. warning:: In HTTP/2, it is mandatory that all the HTTP/2 special + headers (that is, ones whose header keys begin with ``:``) appear + at the start of the header block, before any normal headers. + + .. versionchanged:: 2.3.0 + Added support for using :class:`HeaderTuple + ` objects to store headers. + + .. versionchanged:: 2.4.0 + Added the ability to provide priority keyword arguments: + ``priority_weight``, ``priority_depends_on``, and + ``priority_exclusive``. + + :param stream_id: The stream ID to send the headers on. If this stream + does not currently exist, it will be created. + :type stream_id: ``int`` + + :param headers: The request/response headers to send. + :type headers: An iterable of two tuples of bytestrings or + :class:`HeaderTuple ` objects. + + :param end_stream: Whether this headers frame should end the stream + immediately (that is, whether no more data will be sent after this + frame). Defaults to ``False``. + :type end_stream: ``bool`` + + :param priority_weight: Sets the priority weight of the stream. See + :meth:`prioritize ` for more + about how this field works. Defaults to ``None``, which means that + no priority information will be sent. + :type priority_weight: ``int`` or ``None`` + + :param priority_depends_on: Sets which stream this one depends on for + priority purposes. See :meth:`prioritize + ` for more about how this + field works. Defaults to ``None``, which means that no priority + information will be sent. + :type priority_depends_on: ``int`` or ``None`` + + :param priority_exclusive: Sets whether this stream exclusively depends + on the stream given in ``priority_depends_on`` for priority + purposes. See :meth:`prioritize + ` for more about how this + field workds. Defaults to ``None``, which means that no priority + information will be sent. + :type priority_depends_on: ``bool`` or ``None`` + + :returns: Nothing + """ + self.config.logger.debug( + "Send headers on stream ID %d", stream_id + ) + + # Check we can open the stream. + if stream_id not in self.streams: + max_open_streams = self.remote_settings.max_concurrent_streams + if (self.open_outbound_streams + 1) > max_open_streams: + raise TooManyStreamsError( + "Max outbound streams is %d, %d open" % + (max_open_streams, self.open_outbound_streams) + ) + + self.state_machine.process_input(ConnectionInputs.SEND_HEADERS) + stream = self._get_or_create_stream( + stream_id, AllowedStreamIDs(self.config.client_side) + ) + frames = stream.send_headers( + headers, self.encoder, end_stream + ) + + # We may need to send priority information. + priority_present = ( + (priority_weight is not None) or + (priority_depends_on is not None) or + (priority_exclusive is not None) + ) + + if priority_present: + if not self.config.client_side: + raise RFC1122Error("Servers SHOULD NOT prioritize streams.") + + headers_frame = frames[0] + headers_frame.flags.add('PRIORITY') + frames[0] = _add_frame_priority( + headers_frame, + priority_weight, + priority_depends_on, + priority_exclusive + ) + + self._prepare_for_sending(frames) + + def send_data(self, stream_id, data, end_stream=False, pad_length=None): + """ + Send data on a given stream. + + This method does no breaking up of data: if the data is larger than the + value returned by :meth:`local_flow_control_window + ` for this stream + then a :class:`FlowControlError ` will + be raised. If the data is larger than :data:`max_outbound_frame_size + ` then a + :class:`FrameTooLargeError ` will be + raised. + + Hyper-h2 does this to avoid buffering the data internally. If the user + has more data to send than hyper-h2 will allow, consider breaking it up + and buffering it externally. + + :param stream_id: The ID of the stream on which to send the data. + :type stream_id: ``int`` + :param data: The data to send on the stream. + :type data: ``bytes`` + :param end_stream: (optional) Whether this is the last data to be sent + on the stream. Defaults to ``False``. + :type end_stream: ``bool`` + :param pad_length: (optional) Length of the padding to apply to the + data frame. Defaults to ``None`` for no use of padding. Note that + a value of ``0`` results in padding of length ``0`` + (with the "padding" flag set on the frame). + + .. versionadded:: 2.6.0 + + :type pad_length: ``int`` + :returns: Nothing + """ + self.config.logger.debug( + "Send data on stream ID %d with len %d", stream_id, len(data) + ) + frame_size = len(data) + if pad_length is not None: + if not isinstance(pad_length, int): + raise TypeError("pad_length must be an int") + if pad_length < 0 or pad_length > 255: + raise ValueError("pad_length must be within range: [0, 255]") + # Account for padding bytes plus the 1-byte padding length field. + frame_size += pad_length + 1 + self.config.logger.debug( + "Frame size on stream ID %d is %d", stream_id, frame_size + ) + + if frame_size > self.local_flow_control_window(stream_id): + raise FlowControlError( + "Cannot send %d bytes, flow control window is %d." % + (frame_size, self.local_flow_control_window(stream_id)) + ) + elif frame_size > self.max_outbound_frame_size: + raise FrameTooLargeError( + "Cannot send frame size %d, max frame size is %d" % + (frame_size, self.max_outbound_frame_size) + ) + + self.state_machine.process_input(ConnectionInputs.SEND_DATA) + frames = self.streams[stream_id].send_data( + data, end_stream, pad_length=pad_length + ) + + self._prepare_for_sending(frames) + + self.outbound_flow_control_window -= frame_size + self.config.logger.debug( + "Outbound flow control window size is %d", + self.outbound_flow_control_window + ) + assert self.outbound_flow_control_window >= 0 + + def end_stream(self, stream_id): + """ + Cleanly end a given stream. + + This method ends a stream by sending an empty DATA frame on that stream + with the ``END_STREAM`` flag set. + + :param stream_id: The ID of the stream to end. + :type stream_id: ``int`` + :returns: Nothing + """ + self.config.logger.debug("End stream ID %d", stream_id) + self.state_machine.process_input(ConnectionInputs.SEND_DATA) + frames = self.streams[stream_id].end_stream() + self._prepare_for_sending(frames) + + def increment_flow_control_window(self, increment, stream_id=None): + """ + Increment a flow control window, optionally for a single stream. Allows + the remote peer to send more data. + + .. versionchanged:: 2.0.0 + Rejects attempts to increment the flow control window by out of + range values with a ``ValueError``. + + :param increment: The amount to increment the flow control window by. + :type increment: ``int`` + :param stream_id: (optional) The ID of the stream that should have its + flow control window opened. If not present or ``None``, the + connection flow control window will be opened instead. + :type stream_id: ``int`` or ``None`` + :returns: Nothing + :raises: ``ValueError`` + """ + if not (1 <= increment <= self.MAX_WINDOW_INCREMENT): + raise ValueError( + "Flow control increment must be between 1 and %d" % + self.MAX_WINDOW_INCREMENT + ) + + self.state_machine.process_input(ConnectionInputs.SEND_WINDOW_UPDATE) + + if stream_id is not None: + stream = self.streams[stream_id] + frames = stream.increase_flow_control_window( + increment + ) + + self.config.logger.debug( + "Increase stream ID %d flow control window by %d", + stream_id, increment + ) + else: + self._inbound_flow_control_window_manager.window_opened(increment) + f = WindowUpdateFrame(0) + f.window_increment = increment + frames = [f] + + self.config.logger.debug( + "Increase connection flow control window by %d", increment + ) + + self._prepare_for_sending(frames) + + def push_stream(self, stream_id, promised_stream_id, request_headers): + """ + Push a response to the client by sending a PUSH_PROMISE frame. + + If it is important to send HPACK "never indexed" header fields (as + defined in `RFC 7451 Section 7.1.3 + `_), the user may + instead provide headers using the HPACK library's :class:`HeaderTuple + ` and :class:`NeverIndexedHeaderTuple + ` objects. + + :param stream_id: The ID of the stream that this push is a response to. + :type stream_id: ``int`` + :param promised_stream_id: The ID of the stream that the pushed + response will be sent on. + :type promised_stream_id: ``int`` + :param request_headers: The headers of the request that the pushed + response will be responding to. + :type request_headers: An iterable of two tuples of bytestrings or + :class:`HeaderTuple ` objects. + :returns: Nothing + """ + self.config.logger.debug( + "Send Push Promise frame on stream ID %d", stream_id + ) + + if not self.remote_settings.enable_push: + raise ProtocolError("Remote peer has disabled stream push") + + self.state_machine.process_input(ConnectionInputs.SEND_PUSH_PROMISE) + stream = self._get_stream_by_id(stream_id) + + # We need to prevent users pushing streams in response to streams that + # they themselves have already pushed: see #163 and RFC 7540 § 6.6. The + # easiest way to do that is to assert that the stream_id is not even: + # this shortcut works because only servers can push and the state + # machine will enforce this. + if (stream_id % 2) == 0: + raise ProtocolError("Cannot recursively push streams.") + + new_stream = self._begin_new_stream( + promised_stream_id, AllowedStreamIDs.EVEN + ) + self.streams[promised_stream_id] = new_stream + + frames = stream.push_stream_in_band( + promised_stream_id, request_headers, self.encoder + ) + new_frames = new_stream.locally_pushed() + self._prepare_for_sending(frames + new_frames) + + def ping(self, opaque_data): + """ + Send a PING frame. + + :param opaque_data: A bytestring of length 8 that will be sent in the + PING frame. + :returns: Nothing + """ + self.config.logger.debug("Send Ping frame") + + if not isinstance(opaque_data, bytes) or len(opaque_data) != 8: + raise ValueError("Invalid value for ping data: %r" % opaque_data) + + self.state_machine.process_input(ConnectionInputs.SEND_PING) + f = PingFrame(0) + f.opaque_data = opaque_data + self._prepare_for_sending([f]) + + def reset_stream(self, stream_id, error_code=0): + """ + Reset a stream. + + This method forcibly closes a stream by sending a RST_STREAM frame for + a given stream. This is not a graceful closure. To gracefully end a + stream, try the :meth:`end_stream + ` method. + + :param stream_id: The ID of the stream to reset. + :type stream_id: ``int`` + :param error_code: (optional) The error code to use to reset the + stream. Defaults to :data:`ErrorCodes.NO_ERROR + `. + :type error_code: ``int`` + :returns: Nothing + """ + self.config.logger.debug("Reset stream ID %d", stream_id) + self.state_machine.process_input(ConnectionInputs.SEND_RST_STREAM) + stream = self._get_stream_by_id(stream_id) + frames = stream.reset_stream(error_code) + self._prepare_for_sending(frames) + + def close_connection(self, error_code=0, additional_data=None, + last_stream_id=None): + + """ + Close a connection, emitting a GOAWAY frame. + + .. versionchanged:: 2.4.0 + Added ``additional_data`` and ``last_stream_id`` arguments. + + :param error_code: (optional) The error code to send in the GOAWAY + frame. + :param additional_data: (optional) Additional debug data indicating + a reason for closing the connection. Must be a bytestring. + :param last_stream_id: (optional) The last stream which was processed + by the sender. Defaults to ``highest_inbound_stream_id``. + :returns: Nothing + """ + self.config.logger.debug("Close connection") + self.state_machine.process_input(ConnectionInputs.SEND_GOAWAY) + + # Additional_data must be bytes + if additional_data is not None: + assert isinstance(additional_data, bytes) + + if last_stream_id is None: + last_stream_id = self.highest_inbound_stream_id + + f = GoAwayFrame( + stream_id=0, + last_stream_id=last_stream_id, + error_code=error_code, + additional_data=(additional_data or b'') + ) + self._prepare_for_sending([f]) + + def update_settings(self, new_settings): + """ + Update the local settings. This will prepare and emit the appropriate + SETTINGS frame. + + :param new_settings: A dictionary of {setting: new value} + """ + self.config.logger.debug( + "Update connection settings to %s", new_settings + ) + self.state_machine.process_input(ConnectionInputs.SEND_SETTINGS) + self.local_settings.update(new_settings) + s = SettingsFrame(0) + s.settings = new_settings + self._prepare_for_sending([s]) + + def advertise_alternative_service(self, + field_value, + origin=None, + stream_id=None): + """ + Notify a client about an available Alternative Service. + + An Alternative Service is defined in `RFC 7838 + `_. An Alternative Service + notification informs a client that a given origin is also available + elsewhere. + + Alternative Services can be advertised in two ways. Firstly, they can + be advertised explicitly: that is, a server can say "origin X is also + available at Y". To advertise like this, set the ``origin`` argument + and not the ``stream_id`` argument. Alternatively, they can be + advertised implicitly: that is, a server can say "the origin you're + contacting on stream X is also available at Y". To advertise like this, + set the ``stream_id`` argument and not the ``origin`` argument. + + The explicit method of advertising can be done as long as the + connection is active. The implicit method can only be done after the + client has sent the request headers and before the server has sent the + response headers: outside of those points, Hyper-h2 will forbid sending + the Alternative Service advertisement by raising a ProtocolError. + + The ``field_value`` parameter is specified in RFC 7838. Hyper-h2 does + not validate or introspect this argument: the user is required to + ensure that it's well-formed. ``field_value`` corresponds to RFC 7838's + "Alternative Service Field Value". + + .. note:: It is strongly preferred to use the explicit method of + advertising Alternative Services. The implicit method of + advertising Alternative Services has a number of subtleties + and can lead to inconsistencies between the server and + client. Hyper-h2 allows both mechanisms, but caution is + strongly advised. + + .. versionadded:: 2.3.0 + + :param field_value: The RFC 7838 Alternative Service Field Value. This + argument is not introspected by Hyper-h2: the user is responsible + for ensuring that it is well-formed. + :type field_value: ``bytes`` + + :param origin: The origin/authority to which the Alternative Service + being advertised applies. Must not be provided at the same time as + ``stream_id``. + :type origin: ``bytes`` or ``None`` + + :param stream_id: The ID of the stream which was sent to the authority + for which this Alternative Service advertisement applies. Must not + be provided at the same time as ``origin``. + :type stream_id: ``int`` or ``None`` + + :returns: Nothing. + """ + if not isinstance(field_value, bytes): + raise ValueError("Field must be bytestring.") + + if origin is not None and stream_id is not None: + raise ValueError("Must not provide both origin and stream_id") + + self.state_machine.process_input( + ConnectionInputs.SEND_ALTERNATIVE_SERVICE + ) + + if origin is not None: + # This ALTSVC is sent on stream zero. + f = AltSvcFrame(stream_id=0) + f.origin = origin + f.field = field_value + frames = [f] + else: + stream = self._get_stream_by_id(stream_id) + frames = stream.advertise_alternative_service(field_value) + + self._prepare_for_sending(frames) + + def prioritize(self, stream_id, weight=None, depends_on=None, + exclusive=None): + """ + Notify a server about the priority of a stream. + + Stream priorities are a form of guidance to a remote server: they + inform the server about how important a given response is, so that the + server may allocate its resources (e.g. bandwidth, CPU time, etc.) + accordingly. This exists to allow clients to ensure that the most + important data arrives earlier, while less important data does not + starve out the more important data. + + Stream priorities are explained in depth in `RFC 7540 Section 5.3 + `_. + + This method updates the priority information of a single stream. It may + be called well before a stream is actively in use, or well after a + stream is closed. + + .. warning:: RFC 7540 allows for servers to change the priority of + streams. However, hyper-h2 **does not** allow server + stacks to do this. This is because most clients do not + adequately know how to respond when provided conflicting + priority information, and relatively little utility is + provided by making that functionality available. + + .. note:: hyper-h2 **does not** maintain any information about the + RFC 7540 priority tree. That means that hyper-h2 does not + prevent incautious users from creating invalid priority + trees, particularly by creating priority loops. While some + basic error checking is provided by hyper-h2, users are + strongly recommended to understand their prioritisation + strategies before using the priority tools here. + + .. note:: Priority information is strictly advisory. Servers are + allowed to disregard it entirely. Avoid relying on the idea + that your priority signaling will definitely be obeyed. + + .. versionadded:: 2.4.0 + + :param stream_id: The ID of the stream to prioritize. + :type stream_id: ``int`` + + :param weight: The weight to give the stream. Defaults to ``16``, the + default weight of any stream. May be any value between ``1`` and + ``256`` inclusive. The relative weight of a stream indicates what + proportion of available resources will be allocated to that + stream. + :type weight: ``int`` + + :param depends_on: The ID of the stream on which this stream depends. + This stream will only be progressed if it is impossible to + progress the parent stream (the one on which this one depends). + Passing the value ``0`` means that this stream does not depend on + any other. Defaults to ``0``. + :type depends_on: ``int`` + + :param exclusive: Whether this stream is an exclusive dependency of its + "parent" stream (i.e. the stream given by ``depends_on``). If a + stream is an exclusive dependency of another, that means that all + previously-set children of the parent are moved to become children + of the new exclusively-dependent stream. Defaults to ``False``. + :type exclusive: ``bool`` + """ + if not self.config.client_side: + raise RFC1122Error("Servers SHOULD NOT prioritize streams.") + + self.state_machine.process_input( + ConnectionInputs.SEND_PRIORITY + ) + + frame = PriorityFrame(stream_id) + frame = _add_frame_priority(frame, weight, depends_on, exclusive) + + self._prepare_for_sending([frame]) + + def local_flow_control_window(self, stream_id): + """ + Returns the maximum amount of data that can be sent on stream + ``stream_id``. + + This value will never be larger than the total data that can be sent on + the connection: even if the given stream allows more data, the + connection window provides a logical maximum to the amount of data that + can be sent. + + The maximum data that can be sent in a single data frame on a stream + is either this value, or the maximum frame size, whichever is + *smaller*. + + :param stream_id: The ID of the stream whose flow control window is + being queried. + :type stream_id: ``int`` + :returns: The amount of data in bytes that can be sent on the stream + before the flow control window is exhausted. + :rtype: ``int`` + """ + stream = self._get_stream_by_id(stream_id) + return min( + self.outbound_flow_control_window, + stream.outbound_flow_control_window + ) + + def remote_flow_control_window(self, stream_id): + """ + Returns the maximum amount of data the remote peer can send on stream + ``stream_id``. + + This value will never be larger than the total data that can be sent on + the connection: even if the given stream allows more data, the + connection window provides a logical maximum to the amount of data that + can be sent. + + The maximum data that can be sent in a single data frame on a stream + is either this value, or the maximum frame size, whichever is + *smaller*. + + :param stream_id: The ID of the stream whose flow control window is + being queried. + :type stream_id: ``int`` + :returns: The amount of data in bytes that can be received on the + stream before the flow control window is exhausted. + :rtype: ``int`` + """ + stream = self._get_stream_by_id(stream_id) + return min( + self.inbound_flow_control_window, + stream.inbound_flow_control_window + ) + + def acknowledge_received_data(self, acknowledged_size, stream_id): + """ + Inform the :class:`H2Connection ` that a + certain number of flow-controlled bytes have been processed, and that + the space should be handed back to the remote peer at an opportune + time. + + .. versionadded:: 2.5.0 + + :param acknowledged_size: The total *flow-controlled size* of the data + that has been processed. Note that this must include the amount of + padding that was sent with that data. + :type acknowledged_size: ``int`` + :param stream_id: The ID of the stream on which this data was received. + :type stream_id: ``int`` + :returns: Nothing + :rtype: ``None`` + """ + self.config.logger.debug( + "Ack received data on stream ID %d with size %d", + stream_id, acknowledged_size + ) + if stream_id <= 0: + raise ValueError( + "Stream ID %d is not valid for acknowledge_received_data" % + stream_id + ) + if acknowledged_size < 0: + raise ValueError("Cannot acknowledge negative data") + + frames = [] + + conn_manager = self._inbound_flow_control_window_manager + conn_increment = conn_manager.process_bytes(acknowledged_size) + if conn_increment: + f = WindowUpdateFrame(0) + f.window_increment = conn_increment + frames.append(f) + + try: + stream = self._get_stream_by_id(stream_id) + except StreamClosedError: + # The stream is already gone. We're not worried about incrementing + # the window in this case. + pass + else: + # No point incrementing the windows of closed streams. + if stream.open: + frames.extend( + stream.acknowledge_received_data(acknowledged_size) + ) + + self._prepare_for_sending(frames) + + def data_to_send(self, amount=None): + """ + Returns some data for sending out of the internal data buffer. + + This method is analogous to ``read`` on a file-like object, but it + doesn't block. Instead, it returns as much data as the user asks for, + or less if that much data is not available. It does not perform any + I/O, and so uses a different name. + + :param amount: (optional) The maximum amount of data to return. If not + set, or set to ``None``, will return as much data as possible. + :type amount: ``int`` + :returns: A bytestring containing the data to send on the wire. + :rtype: ``bytes`` + """ + if amount is None: + data = bytes(self._data_to_send) + self._data_to_send = bytearray() + return data + else: + data = bytes(self._data_to_send[:amount]) + self._data_to_send = self._data_to_send[amount:] + return data + + def clear_outbound_data_buffer(self): + """ + Clears the outbound data buffer, such that if this call was immediately + followed by a call to + :meth:`data_to_send `, that + call would return no data. + + This method should not normally be used, but is made available to avoid + exposing implementation details. + """ + self._data_to_send = bytearray() + + def _acknowledge_settings(self): + """ + Acknowledge settings that have been received. + + .. versionchanged:: 2.0.0 + Removed from public API, removed useless ``event`` parameter, made + automatic. + + :returns: Nothing + """ + self.state_machine.process_input(ConnectionInputs.SEND_SETTINGS) + + changes = self.remote_settings.acknowledge() + + if SettingCodes.INITIAL_WINDOW_SIZE in changes: + setting = changes[SettingCodes.INITIAL_WINDOW_SIZE] + self._flow_control_change_from_settings( + setting.original_value, + setting.new_value, + ) + + # HEADER_TABLE_SIZE changes by the remote part affect our encoder: cf. + # RFC 7540 Section 6.5.2. + if SettingCodes.HEADER_TABLE_SIZE in changes: + setting = changes[SettingCodes.HEADER_TABLE_SIZE] + self.encoder.header_table_size = setting.new_value + + if SettingCodes.MAX_FRAME_SIZE in changes: + setting = changes[SettingCodes.MAX_FRAME_SIZE] + self.max_outbound_frame_size = setting.new_value + for stream in self.streams.values(): + stream.max_outbound_frame_size = setting.new_value + + f = SettingsFrame(0) + f.flags.add('ACK') + return [f] + + def _flow_control_change_from_settings(self, old_value, new_value): + """ + Update flow control windows in response to a change in the value of + SETTINGS_INITIAL_WINDOW_SIZE. + + When this setting is changed, it automatically updates all flow control + windows by the delta in the settings values. Note that it does not + increment the *connection* flow control window, per section 6.9.2 of + RFC 7540. + """ + delta = new_value - old_value + + for stream in self.streams.values(): + stream.outbound_flow_control_window = guard_increment_window( + stream.outbound_flow_control_window, + delta + ) + + def _inbound_flow_control_change_from_settings(self, old_value, new_value): + """ + Update remote flow control windows in response to a change in the value + of SETTINGS_INITIAL_WINDOW_SIZE. + + When this setting is changed, it automatically updates all remote flow + control windows by the delta in the settings values. + """ + delta = new_value - old_value + + for stream in self.streams.values(): + stream._inbound_flow_control_change_from_settings(delta) + + def receive_data(self, data): + """ + Pass some received HTTP/2 data to the connection for handling. + + :param data: The data received from the remote peer on the network. + :type data: ``bytes`` + :returns: A list of events that the remote peer triggered by sending + this data. + """ + self.config.logger.trace( + "Process received data on connection. Received data: %r", data + ) + + events = [] + self.incoming_buffer.add_data(data) + self.incoming_buffer.max_frame_size = self.max_inbound_frame_size + + try: + for frame in self.incoming_buffer: + events.extend(self._receive_frame(frame)) + except InvalidPaddingError: + self._terminate_connection(ErrorCodes.PROTOCOL_ERROR) + raise ProtocolError("Received frame with invalid padding.") + except ProtocolError as e: + # For whatever reason, receiving the frame caused a protocol error. + # We should prepare to emit a GoAway frame before throwing the + # exception up further. No need for an event: the exception will + # do fine. + self._terminate_connection(e.error_code) + raise + + return events + + def _receive_frame(self, frame): + """ + Handle a frame received on the connection. + + .. versionchanged:: 2.0.0 + Removed from the public API. + """ + try: + # I don't love using __class__ here, maybe reconsider it. + frames, events = self._frame_dispatch_table[frame.__class__](frame) + except StreamClosedError as e: + # If the stream was closed by RST_STREAM, we just send a RST_STREAM + # to the remote peer. Otherwise, this is a connection error, and so + # we will re-raise to trigger one. + if self._stream_is_closed_by_reset(e.stream_id): + f = RstStreamFrame(e.stream_id) + f.error_code = e.error_code + self._prepare_for_sending([f]) + events = e._events + else: + raise + except StreamIDTooLowError as e: + # The stream ID seems invalid. This may happen when the closed + # stream has been cleaned up, or when the remote peer has opened a + # new stream with a higher stream ID than this one, forcing it + # closed implicitly. + # + # Check how the stream was closed: depending on the mechanism, it + # is either a stream error or a connection error. + if self._stream_is_closed_by_reset(e.stream_id): + # Closed by RST_STREAM is a stream error. + f = RstStreamFrame(e.stream_id) + f.error_code = ErrorCodes.STREAM_CLOSED + self._prepare_for_sending([f]) + events = [] + elif self._stream_is_closed_by_end(e.stream_id): + # Closed by END_STREAM is a connection error. + raise StreamClosedError(e.stream_id) + else: + # Closed implicitly, also a connection error, but of type + # PROTOCOL_ERROR. + raise + else: + self._prepare_for_sending(frames) + + return events + + def _terminate_connection(self, error_code): + """ + Terminate the connection early. Used in error handling blocks to send + GOAWAY frames. + """ + f = GoAwayFrame(0) + f.last_stream_id = self.highest_inbound_stream_id + f.error_code = error_code + self.state_machine.process_input(ConnectionInputs.SEND_GOAWAY) + self._prepare_for_sending([f]) + + def _receive_headers_frame(self, frame): + """ + Receive a headers frame on the connection. + """ + # If necessary, check we can open the stream. Also validate that the + # stream ID is valid. + if frame.stream_id not in self.streams: + max_open_streams = self.local_settings.max_concurrent_streams + if (self.open_inbound_streams + 1) > max_open_streams: + raise TooManyStreamsError( + "Max outbound streams is %d, %d open" % + (max_open_streams, self.open_outbound_streams) + ) + + # Let's decode the headers. We handle headers as bytes internally up + # until we hang them off the event, at which point we may optionally + # convert them to unicode. + headers = _decode_headers(self.decoder, frame.data) + + events = self.state_machine.process_input( + ConnectionInputs.RECV_HEADERS + ) + stream = self._get_or_create_stream( + frame.stream_id, AllowedStreamIDs(not self.config.client_side) + ) + frames, stream_events = stream.receive_headers( + headers, + 'END_STREAM' in frame.flags, + self.config.header_encoding + ) + + if 'PRIORITY' in frame.flags: + p_frames, p_events = self._receive_priority_frame(frame) + stream_events[0].priority_updated = p_events[0] + stream_events.extend(p_events) + assert not p_frames + + return frames, events + stream_events + + def _receive_push_promise_frame(self, frame): + """ + Receive a push-promise frame on the connection. + """ + if not self.local_settings.enable_push: + raise ProtocolError("Received pushed stream") + + pushed_headers = _decode_headers(self.decoder, frame.data) + + events = self.state_machine.process_input( + ConnectionInputs.RECV_PUSH_PROMISE + ) + + try: + stream = self._get_stream_by_id(frame.stream_id) + except NoSuchStreamError: + # We need to check if the parent stream was reset by us. If it was + # then we presume that the PUSH_PROMISE was in flight when we reset + # the parent stream. Rather than accept the new stream, just reset + # it. + # + # If this was closed naturally, however, we should call this a + # PROTOCOL_ERROR: pushing a stream on a naturally closed stream is + # a real problem because it creates a brand new stream that the + # remote peer now believes exists. + if (self._stream_closed_by(frame.stream_id) == + StreamClosedBy.SEND_RST_STREAM): + f = RstStreamFrame(frame.promised_stream_id) + f.error_code = ErrorCodes.REFUSED_STREAM + return [f], events + + raise ProtocolError("Attempted to push on closed stream.") + + # We need to prevent peers pushing streams in response to streams that + # they themselves have already pushed: see #163 and RFC 7540 § 6.6. The + # easiest way to do that is to assert that the stream_id is not even: + # this shortcut works because only servers can push and the state + # machine will enforce this. + if (frame.stream_id % 2) == 0: + raise ProtocolError("Cannot recursively push streams.") + + try: + frames, stream_events = stream.receive_push_promise_in_band( + frame.promised_stream_id, + pushed_headers, + self.config.header_encoding, + ) + except StreamClosedError: + # The parent stream was reset by us, so we presume that + # PUSH_PROMISE was in flight when we reset the parent stream. + # So we just reset the new stream. + f = RstStreamFrame(frame.promised_stream_id) + f.error_code = ErrorCodes.REFUSED_STREAM + return [f], events + + new_stream = self._begin_new_stream( + frame.promised_stream_id, AllowedStreamIDs.EVEN + ) + self.streams[frame.promised_stream_id] = new_stream + new_stream.remotely_pushed(pushed_headers) + + return frames, events + stream_events + + def _handle_data_on_closed_stream(self, events, exc, frame): + # This stream is already closed - and yet we received a DATA frame. + # The received DATA frame counts towards the connection flow window. + # We need to manually to acknowledge the DATA frame to update the flow + # window of the connection. Otherwise the whole connection stalls due + # the inbound flow window being 0. + frames = [] + conn_manager = self._inbound_flow_control_window_manager + conn_increment = conn_manager.process_bytes( + frame.flow_controlled_length + ) + if conn_increment: + f = WindowUpdateFrame(0) + f.window_increment = conn_increment + frames.append(f) + self.config.logger.debug( + "Received DATA frame on closed stream %d - " + "auto-emitted a WINDOW_UPDATE by %d", + frame.stream_id, conn_increment + ) + f = RstStreamFrame(exc.stream_id) + f.error_code = exc.error_code + frames.append(f) + self.config.logger.debug( + "Stream %d already CLOSED or cleaned up - " + "auto-emitted a RST_FRAME" % frame.stream_id + ) + return frames, events + exc._events + + def _receive_data_frame(self, frame): + """ + Receive a data frame on the connection. + """ + flow_controlled_length = frame.flow_controlled_length + + events = self.state_machine.process_input( + ConnectionInputs.RECV_DATA + ) + self._inbound_flow_control_window_manager.window_consumed( + flow_controlled_length + ) + + try: + stream = self._get_stream_by_id(frame.stream_id) + frames, stream_events = stream.receive_data( + frame.data, + 'END_STREAM' in frame.flags, + flow_controlled_length + ) + except StreamClosedError as e: + # This stream is either marked as CLOSED or already gone from our + # internal state. + return self._handle_data_on_closed_stream(events, e, frame) + + return frames, events + stream_events + + def _receive_settings_frame(self, frame): + """ + Receive a SETTINGS frame on the connection. + """ + events = self.state_machine.process_input( + ConnectionInputs.RECV_SETTINGS + ) + + # This is an ack of the local settings. + if 'ACK' in frame.flags: + changed_settings = self._local_settings_acked() + ack_event = SettingsAcknowledged() + ack_event.changed_settings = changed_settings + events.append(ack_event) + return [], events + + # Add the new settings. + self.remote_settings.update(frame.settings) + events.append( + RemoteSettingsChanged.from_settings( + self.remote_settings, frame.settings + ) + ) + frames = self._acknowledge_settings() + + return frames, events + + def _receive_window_update_frame(self, frame): + """ + Receive a WINDOW_UPDATE frame on the connection. + """ + # Validate the frame. + if not (1 <= frame.window_increment <= self.MAX_WINDOW_INCREMENT): + raise ProtocolError( + "Flow control increment must be between 1 and %d, received %d" + % (self.MAX_WINDOW_INCREMENT, frame.window_increment) + ) + + events = self.state_machine.process_input( + ConnectionInputs.RECV_WINDOW_UPDATE + ) + + if frame.stream_id: + stream = self._get_stream_by_id(frame.stream_id) + frames, stream_events = stream.receive_window_update( + frame.window_increment + ) + else: + # Increment our local flow control window. + self.outbound_flow_control_window = guard_increment_window( + self.outbound_flow_control_window, + frame.window_increment + ) + + # FIXME: Should we split this into one event per active stream? + window_updated_event = WindowUpdated() + window_updated_event.stream_id = 0 + window_updated_event.delta = frame.window_increment + stream_events = [window_updated_event] + frames = [] + + return frames, events + stream_events + + def _receive_ping_frame(self, frame): + """ + Receive a PING frame on the connection. + """ + events = self.state_machine.process_input( + ConnectionInputs.RECV_PING + ) + flags = [] + + if 'ACK' in frame.flags: + evt = PingAckReceived() + else: + evt = PingReceived() + + # automatically ACK the PING with the same 'opaque data' + f = PingFrame(0) + f.flags = {'ACK'} + f.opaque_data = frame.opaque_data + flags.append(f) + + evt.ping_data = frame.opaque_data + events.append(evt) + + return flags, events + + def _receive_rst_stream_frame(self, frame): + """ + Receive a RST_STREAM frame on the connection. + """ + events = self.state_machine.process_input( + ConnectionInputs.RECV_RST_STREAM + ) + try: + stream = self._get_stream_by_id(frame.stream_id) + except NoSuchStreamError: + # The stream is missing. That's ok, we just do nothing here. + stream_frames = [] + stream_events = [] + else: + stream_frames, stream_events = stream.stream_reset(frame) + + return stream_frames, events + stream_events + + def _receive_priority_frame(self, frame): + """ + Receive a PRIORITY frame on the connection. + """ + events = self.state_machine.process_input( + ConnectionInputs.RECV_PRIORITY + ) + + event = PriorityUpdated() + event.stream_id = frame.stream_id + event.depends_on = frame.depends_on + event.exclusive = frame.exclusive + + # Weight is an integer between 1 and 256, but the byte only allows + # 0 to 255: add one. + event.weight = frame.stream_weight + 1 + + # A stream may not depend on itself. + if event.depends_on == frame.stream_id: + raise ProtocolError( + "Stream %d may not depend on itself" % frame.stream_id + ) + events.append(event) + + return [], events + + def _receive_goaway_frame(self, frame): + """ + Receive a GOAWAY frame on the connection. + """ + events = self.state_machine.process_input( + ConnectionInputs.RECV_GOAWAY + ) + + # Clear the outbound data buffer: we cannot send further data now. + self.clear_outbound_data_buffer() + + # Fire an appropriate ConnectionTerminated event. + new_event = ConnectionTerminated() + new_event.error_code = _error_code_from_int(frame.error_code) + new_event.last_stream_id = frame.last_stream_id + new_event.additional_data = (frame.additional_data + if frame.additional_data else None) + events.append(new_event) + + return [], events + + def _receive_naked_continuation(self, frame): + """ + A naked CONTINUATION frame has been received. This is always an error, + but the type of error it is depends on the state of the stream and must + transition the state of the stream, so we need to pass it to the + appropriate stream. + """ + stream = self._get_stream_by_id(frame.stream_id) + stream.receive_continuation() + assert False, "Should not be reachable" + + def _receive_alt_svc_frame(self, frame): + """ + An ALTSVC frame has been received. This frame, specified in RFC 7838, + is used to advertise alternative places where the same service can be + reached. + + This frame can optionally be received either on a stream or on stream + 0, and its semantics are different in each case. + """ + events = self.state_machine.process_input( + ConnectionInputs.RECV_ALTERNATIVE_SERVICE + ) + frames = [] + + if frame.stream_id: + # Given that it makes no sense to receive ALTSVC on a stream + # before that stream has been opened with a HEADERS frame, the + # ALTSVC frame cannot create a stream. If the stream is not + # present, we simply ignore the frame. + try: + stream = self._get_stream_by_id(frame.stream_id) + except (NoSuchStreamError, StreamClosedError): + pass + else: + stream_frames, stream_events = stream.receive_alt_svc(frame) + frames.extend(stream_frames) + events.extend(stream_events) + else: + # This frame is sent on stream 0. The origin field on the frame + # must be present, though if it isn't it's not a ProtocolError + # (annoyingly), we just need to ignore it. + if not frame.origin: + return frames, events + + # If we're a server, we want to ignore this (RFC 7838 says so). + if not self.config.client_side: + return frames, events + + event = AlternativeServiceAvailable() + event.origin = frame.origin + event.field_value = frame.field + events.append(event) + + return frames, events + + def _receive_unknown_frame(self, frame): + """ + We have received a frame that we do not understand. This is almost + certainly an extension frame, though it's impossible to be entirely + sure. + + RFC 7540 § 5.5 says that we MUST ignore unknown frame types: so we + do. We do notify the user that we received one, however. + """ + # All we do here is log. + self.config.logger.debug( + "Received unknown extension frame (ID %d)", frame.stream_id + ) + event = UnknownFrameReceived() + event.frame = frame + return [], [event] + + def _local_settings_acked(self): + """ + Handle the local settings being ACKed, update internal state. + """ + changes = self.local_settings.acknowledge() + + if SettingCodes.INITIAL_WINDOW_SIZE in changes: + setting = changes[SettingCodes.INITIAL_WINDOW_SIZE] + self._inbound_flow_control_change_from_settings( + setting.original_value, + setting.new_value, + ) + + if SettingCodes.MAX_HEADER_LIST_SIZE in changes: + setting = changes[SettingCodes.MAX_HEADER_LIST_SIZE] + self.decoder.max_header_list_size = setting.new_value + + if SettingCodes.MAX_FRAME_SIZE in changes: + setting = changes[SettingCodes.MAX_FRAME_SIZE] + self.max_inbound_frame_size = setting.new_value + + if SettingCodes.HEADER_TABLE_SIZE in changes: + setting = changes[SettingCodes.HEADER_TABLE_SIZE] + # This is safe across all hpack versions: some versions just won't + # respect it. + self.decoder.max_allowed_table_size = setting.new_value + + return changes + + def _stream_id_is_outbound(self, stream_id): + """ + Returns ``True`` if the stream ID corresponds to an outbound stream + (one initiated by this peer), returns ``False`` otherwise. + """ + return (stream_id % 2 == int(self.config.client_side)) + + def _stream_closed_by(self, stream_id): + """ + Returns how the stream was closed. + + The return value will be either a member of + ``h2.stream.StreamClosedBy`` or ``None``. If ``None``, the stream was + closed implicitly by the peer opening a stream with a higher stream ID + before opening this one. + """ + if stream_id in self.streams: + return self.streams[stream_id].closed_by + if stream_id in self._closed_streams: + return self._closed_streams[stream_id] + return None + + def _stream_is_closed_by_reset(self, stream_id): + """ + Returns ``True`` if the stream was closed by sending or receiving a + RST_STREAM frame. Returns ``False`` otherwise. + """ + return self._stream_closed_by(stream_id) in ( + StreamClosedBy.RECV_RST_STREAM, StreamClosedBy.SEND_RST_STREAM + ) + + def _stream_is_closed_by_end(self, stream_id): + """ + Returns ``True`` if the stream was closed by sending or receiving an + END_STREAM flag in a HEADERS or DATA frame. Returns ``False`` + otherwise. + """ + return self._stream_closed_by(stream_id) in ( + StreamClosedBy.RECV_END_STREAM, StreamClosedBy.SEND_END_STREAM + ) + + +def _add_frame_priority(frame, weight=None, depends_on=None, exclusive=None): + """ + Adds priority data to a given frame. Does not change any flags set on that + frame: if the caller is adding priority information to a HEADERS frame they + must set that themselves. + + This method also deliberately sets defaults for anything missing. + + This method validates the input values. + """ + # A stream may not depend on itself. + if depends_on == frame.stream_id: + raise ProtocolError( + "Stream %d may not depend on itself" % frame.stream_id + ) + + # Weight must be between 1 and 256. + if weight is not None: + if weight > 256 or weight < 1: + raise ProtocolError( + "Weight must be between 1 and 256, not %d" % weight + ) + else: + # Weight is an integer between 1 and 256, but the byte only allows + # 0 to 255: subtract one. + weight -= 1 + + # Set defaults for anything not provided. + weight = weight if weight is not None else 15 + depends_on = depends_on if depends_on is not None else 0 + exclusive = exclusive if exclusive is not None else False + + frame.stream_weight = weight + frame.depends_on = depends_on + frame.exclusive = exclusive + + return frame + + +def _decode_headers(decoder, encoded_header_block): + """ + Decode a HPACK-encoded header block, translating HPACK exceptions into + sensible hyper-h2 errors. + + This only ever returns bytestring headers: hyper-h2 may emit them as + unicode later, but internally it processes them as bytestrings only. + """ + try: + return decoder.decode(encoded_header_block, raw=True) + except OversizedHeaderListError as e: + # This is a symptom of a HPACK bomb attack: the user has + # disregarded our requirements on how large a header block we'll + # accept. + raise DenialOfServiceError("Oversized header block: %s" % e) + except (HPACKError, IndexError, TypeError, UnicodeDecodeError) as e: + # We should only need HPACKError here, but versions of HPACK older + # than 2.1.0 throw all three others as well. For maximum + # compatibility, catch all of them. + raise ProtocolError("Error decoding header block: %s" % e) diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/errors.py b/testing/web-platform/tests/tools/third_party/h2/h2/errors.py new file mode 100644 index 0000000000..baef2001cd --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/errors.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +""" +h2/errors +~~~~~~~~~~~~~~~~~~~ + +Global error code registry containing the established HTTP/2 error codes. + +The current registry is available at: +https://tools.ietf.org/html/rfc7540#section-11.4 +""" +import enum + + +class ErrorCodes(enum.IntEnum): + """ + All known HTTP/2 error codes. + + .. versionadded:: 2.5.0 + """ + #: Graceful shutdown. + NO_ERROR = 0x0 + + #: Protocol error detected. + PROTOCOL_ERROR = 0x1 + + #: Implementation fault. + INTERNAL_ERROR = 0x2 + + #: Flow-control limits exceeded. + FLOW_CONTROL_ERROR = 0x3 + + #: Settings not acknowledged. + SETTINGS_TIMEOUT = 0x4 + + #: Frame received for closed stream. + STREAM_CLOSED = 0x5 + + #: Frame size incorrect. + FRAME_SIZE_ERROR = 0x6 + + #: Stream not processed. + REFUSED_STREAM = 0x7 + + #: Stream cancelled. + CANCEL = 0x8 + + #: Compression state not updated. + COMPRESSION_ERROR = 0x9 + + #: TCP connection error for CONNECT method. + CONNECT_ERROR = 0xa + + #: Processing capacity exceeded. + ENHANCE_YOUR_CALM = 0xb + + #: Negotiated TLS parameters not acceptable. + INADEQUATE_SECURITY = 0xc + + #: Use HTTP/1.1 for the request. + HTTP_1_1_REQUIRED = 0xd + + +def _error_code_from_int(code): + """ + Given an integer error code, returns either one of :class:`ErrorCodes + ` or, if not present in the known set of codes, + returns the integer directly. + """ + try: + return ErrorCodes(code) + except ValueError: + return code + + +__all__ = ['ErrorCodes'] diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/events.py b/testing/web-platform/tests/tools/third_party/h2/h2/events.py new file mode 100644 index 0000000000..a06c9903d5 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/events.py @@ -0,0 +1,648 @@ +# -*- coding: utf-8 -*- +""" +h2/events +~~~~~~~~~ + +Defines Event types for HTTP/2. + +Events are returned by the H2 state machine to allow implementations to keep +track of events triggered by receiving data. Each time data is provided to the +H2 state machine it processes the data and returns a list of Event objects. +""" +import binascii + +from .settings import ChangedSetting, _setting_code_from_int + + +class Event(object): + """ + Base class for h2 events. + """ + pass + + +class RequestReceived(Event): + """ + The RequestReceived event is fired whenever request headers are received. + This event carries the HTTP headers for the given request and the stream ID + of the new stream. + + .. versionchanged:: 2.3.0 + Changed the type of ``headers`` to :class:`HeaderTuple + `. This has no effect on current users. + + .. versionchanged:: 2.4.0 + Added ``stream_ended`` and ``priority_updated`` properties. + """ + def __init__(self): + #: The Stream ID for the stream this request was made on. + self.stream_id = None + + #: The request headers. + self.headers = None + + #: If this request also ended the stream, the associated + #: :class:`StreamEnded ` event will be available + #: here. + #: + #: .. versionadded:: 2.4.0 + self.stream_ended = None + + #: If this request also had associated priority information, the + #: associated :class:`PriorityUpdated ` + #: event will be available here. + #: + #: .. versionadded:: 2.4.0 + self.priority_updated = None + + def __repr__(self): + return "" % ( + self.stream_id, self.headers + ) + + +class ResponseReceived(Event): + """ + The ResponseReceived event is fired whenever response headers are received. + This event carries the HTTP headers for the given response and the stream + ID of the new stream. + + .. versionchanged:: 2.3.0 + Changed the type of ``headers`` to :class:`HeaderTuple + `. This has no effect on current users. + + .. versionchanged:: 2.4.0 + Added ``stream_ended`` and ``priority_updated`` properties. + """ + def __init__(self): + #: The Stream ID for the stream this response was made on. + self.stream_id = None + + #: The response headers. + self.headers = None + + #: If this response also ended the stream, the associated + #: :class:`StreamEnded ` event will be available + #: here. + #: + #: .. versionadded:: 2.4.0 + self.stream_ended = None + + #: If this response also had associated priority information, the + #: associated :class:`PriorityUpdated ` + #: event will be available here. + #: + #: .. versionadded:: 2.4.0 + self.priority_updated = None + + def __repr__(self): + return "" % ( + self.stream_id, self.headers + ) + + +class TrailersReceived(Event): + """ + The TrailersReceived event is fired whenever trailers are received on a + stream. Trailers are a set of headers sent after the body of the + request/response, and are used to provide information that wasn't known + ahead of time (e.g. content-length). This event carries the HTTP header + fields that form the trailers and the stream ID of the stream on which they + were received. + + .. versionchanged:: 2.3.0 + Changed the type of ``headers`` to :class:`HeaderTuple + `. This has no effect on current users. + + .. versionchanged:: 2.4.0 + Added ``stream_ended`` and ``priority_updated`` properties. + """ + def __init__(self): + #: The Stream ID for the stream on which these trailers were received. + self.stream_id = None + + #: The trailers themselves. + self.headers = None + + #: Trailers always end streams. This property has the associated + #: :class:`StreamEnded ` in it. + #: + #: .. versionadded:: 2.4.0 + self.stream_ended = None + + #: If the trailers also set associated priority information, the + #: associated :class:`PriorityUpdated ` + #: event will be available here. + #: + #: .. versionadded:: 2.4.0 + self.priority_updated = None + + def __repr__(self): + return "" % ( + self.stream_id, self.headers + ) + + +class _HeadersSent(Event): + """ + The _HeadersSent event is fired whenever headers are sent. + + This is an internal event, used to determine validation steps on + outgoing header blocks. + """ + pass + + +class _ResponseSent(_HeadersSent): + """ + The _ResponseSent event is fired whenever response headers are sent + on a stream. + + This is an internal event, used to determine validation steps on + outgoing header blocks. + """ + pass + + +class _RequestSent(_HeadersSent): + """ + The _RequestSent event is fired whenever request headers are sent + on a stream. + + This is an internal event, used to determine validation steps on + outgoing header blocks. + """ + pass + + +class _TrailersSent(_HeadersSent): + """ + The _TrailersSent event is fired whenever trailers are sent on a + stream. Trailers are a set of headers sent after the body of the + request/response, and are used to provide information that wasn't known + ahead of time (e.g. content-length). + + This is an internal event, used to determine validation steps on + outgoing header blocks. + """ + pass + + +class _PushedRequestSent(_HeadersSent): + """ + The _PushedRequestSent event is fired whenever pushed request headers are + sent. + + This is an internal event, used to determine validation steps on outgoing + header blocks. + """ + pass + + +class InformationalResponseReceived(Event): + """ + The InformationalResponseReceived event is fired when an informational + response (that is, one whose status code is a 1XX code) is received from + the remote peer. + + The remote peer may send any number of these, from zero upwards. These + responses are most commonly sent in response to requests that have the + ``expect: 100-continue`` header field present. Most users can safely + ignore this event unless you are intending to use the + ``expect: 100-continue`` flow, or are for any reason expecting a different + 1XX status code. + + .. versionadded:: 2.2.0 + + .. versionchanged:: 2.3.0 + Changed the type of ``headers`` to :class:`HeaderTuple + `. This has no effect on current users. + + .. versionchanged:: 2.4.0 + Added ``priority_updated`` property. + """ + def __init__(self): + #: The Stream ID for the stream this informational response was made + #: on. + self.stream_id = None + + #: The headers for this informational response. + self.headers = None + + #: If this response also had associated priority information, the + #: associated :class:`PriorityUpdated ` + #: event will be available here. + #: + #: .. versionadded:: 2.4.0 + self.priority_updated = None + + def __repr__(self): + return "" % ( + self.stream_id, self.headers + ) + + +class DataReceived(Event): + """ + The DataReceived event is fired whenever data is received on a stream from + the remote peer. The event carries the data itself, and the stream ID on + which the data was received. + + .. versionchanged:: 2.4.0 + Added ``stream_ended`` property. + """ + def __init__(self): + #: The Stream ID for the stream this data was received on. + self.stream_id = None + + #: The data itself. + self.data = None + + #: The amount of data received that counts against the flow control + #: window. Note that padding counts against the flow control window, so + #: when adjusting flow control you should always use this field rather + #: than ``len(data)``. + self.flow_controlled_length = None + + #: If this data chunk also completed the stream, the associated + #: :class:`StreamEnded ` event will be available + #: here. + #: + #: .. versionadded:: 2.4.0 + self.stream_ended = None + + def __repr__(self): + return ( + "" % ( + self.stream_id, + self.flow_controlled_length, + _bytes_representation(self.data[:20]), + ) + ) + + +class WindowUpdated(Event): + """ + The WindowUpdated event is fired whenever a flow control window changes + size. HTTP/2 defines flow control windows for connections and streams: this + event fires for both connections and streams. The event carries the ID of + the stream to which it applies (set to zero if the window update applies to + the connection), and the delta in the window size. + """ + def __init__(self): + #: The Stream ID of the stream whose flow control window was changed. + #: May be ``0`` if the connection window was changed. + self.stream_id = None + + #: The window delta. + self.delta = None + + def __repr__(self): + return "" % ( + self.stream_id, self.delta + ) + + +class RemoteSettingsChanged(Event): + """ + The RemoteSettingsChanged event is fired whenever the remote peer changes + its settings. It contains a complete inventory of changed settings, + including their previous values. + + In HTTP/2, settings changes need to be acknowledged. hyper-h2 automatically + acknowledges settings changes for efficiency. However, it is possible that + the caller may not be happy with the changed setting. + + When this event is received, the caller should confirm that the new + settings are acceptable. If they are not acceptable, the user should close + the connection with the error code :data:`PROTOCOL_ERROR + `. + + .. versionchanged:: 2.0.0 + Prior to this version the user needed to acknowledge settings changes. + This is no longer the case: hyper-h2 now automatically acknowledges + them. + """ + def __init__(self): + #: A dictionary of setting byte to + #: :class:`ChangedSetting `, representing + #: the changed settings. + self.changed_settings = {} + + @classmethod + def from_settings(cls, old_settings, new_settings): + """ + Build a RemoteSettingsChanged event from a set of changed settings. + + :param old_settings: A complete collection of old settings, in the form + of a dictionary of ``{setting: value}``. + :param new_settings: All the changed settings and their new values, in + the form of a dictionary of ``{setting: value}``. + """ + e = cls() + for setting, new_value in new_settings.items(): + setting = _setting_code_from_int(setting) + original_value = old_settings.get(setting) + change = ChangedSetting(setting, original_value, new_value) + e.changed_settings[setting] = change + + return e + + def __repr__(self): + return "" % ( + ", ".join(repr(cs) for cs in self.changed_settings.values()), + ) + + +class PingReceived(Event): + """ + The PingReceived event is fired whenever a PING is received. It contains + the 'opaque data' of the PING frame. A ping acknowledgment with the same + 'opaque data' is automatically emitted after receiving a ping. + + .. versionadded:: 3.1.0 + """ + def __init__(self): + #: The data included on the ping. + self.ping_data = None + + def __repr__(self): + return "" % ( + _bytes_representation(self.ping_data), + ) + + +class PingAcknowledged(Event): + """ + Same as PingAckReceived. + + .. deprecated:: 3.1.0 + """ + def __init__(self): + #: The data included on the ping. + self.ping_data = None + + def __repr__(self): + return "" % ( + _bytes_representation(self.ping_data), + ) + + +class PingAckReceived(PingAcknowledged): + """ + The PingAckReceived event is fired whenever a PING acknowledgment is + received. It contains the 'opaque data' of the PING+ACK frame, allowing the + user to correlate PINGs and calculate RTT. + + .. versionadded:: 3.1.0 + """ + pass + + +class StreamEnded(Event): + """ + The StreamEnded event is fired whenever a stream is ended by a remote + party. The stream may not be fully closed if it has not been closed + locally, but no further data or headers should be expected on that stream. + """ + def __init__(self): + #: The Stream ID of the stream that was closed. + self.stream_id = None + + def __repr__(self): + return "" % self.stream_id + + +class StreamReset(Event): + """ + The StreamReset event is fired in two situations. The first is when the + remote party forcefully resets the stream. The second is when the remote + party has made a protocol error which only affects a single stream. In this + case, Hyper-h2 will terminate the stream early and return this event. + + .. versionchanged:: 2.0.0 + This event is now fired when Hyper-h2 automatically resets a stream. + """ + def __init__(self): + #: The Stream ID of the stream that was reset. + self.stream_id = None + + #: The error code given. Either one of :class:`ErrorCodes + #: ` or ``int`` + self.error_code = None + + #: Whether the remote peer sent a RST_STREAM or we did. + self.remote_reset = True + + def __repr__(self): + return "" % ( + self.stream_id, self.error_code, self.remote_reset + ) + + +class PushedStreamReceived(Event): + """ + The PushedStreamReceived event is fired whenever a pushed stream has been + received from a remote peer. The event carries on it the new stream ID, the + ID of the parent stream, and the request headers pushed by the remote peer. + """ + def __init__(self): + #: The Stream ID of the stream created by the push. + self.pushed_stream_id = None + + #: The Stream ID of the stream that the push is related to. + self.parent_stream_id = None + + #: The request headers, sent by the remote party in the push. + self.headers = None + + def __repr__(self): + return ( + "" % ( + self.pushed_stream_id, + self.parent_stream_id, + self.headers, + ) + ) + + +class SettingsAcknowledged(Event): + """ + The SettingsAcknowledged event is fired whenever a settings ACK is received + from the remote peer. The event carries on it the settings that were + acknowedged, in the same format as + :class:`h2.events.RemoteSettingsChanged`. + """ + def __init__(self): + #: A dictionary of setting byte to + #: :class:`ChangedSetting `, representing + #: the changed settings. + self.changed_settings = {} + + def __repr__(self): + return "" % ( + ", ".join(repr(cs) for cs in self.changed_settings.values()), + ) + + +class PriorityUpdated(Event): + """ + The PriorityUpdated event is fired whenever a stream sends updated priority + information. This can occur when the stream is opened, or at any time + during the stream lifetime. + + This event is purely advisory, and does not need to be acted on. + + .. versionadded:: 2.0.0 + """ + def __init__(self): + #: The ID of the stream whose priority information is being updated. + self.stream_id = None + + #: The new stream weight. May be the same as the original stream + #: weight. An integer between 1 and 256. + self.weight = None + + #: The stream ID this stream now depends on. May be ``0``. + self.depends_on = None + + #: Whether the stream *exclusively* depends on the parent stream. If it + #: does, this stream should inherit the current children of its new + #: parent. + self.exclusive = None + + def __repr__(self): + return ( + "" % ( + self.stream_id, + self.weight, + self.depends_on, + self.exclusive + ) + ) + + +class ConnectionTerminated(Event): + """ + The ConnectionTerminated event is fired when a connection is torn down by + the remote peer using a GOAWAY frame. Once received, no further action may + be taken on the connection: a new connection must be established. + """ + def __init__(self): + #: The error code cited when tearing down the connection. Should be + #: one of :class:`ErrorCodes `, but may not be if + #: unknown HTTP/2 extensions are being used. + self.error_code = None + + #: The stream ID of the last stream the remote peer saw. This can + #: provide an indication of what data, if any, never reached the remote + #: peer and so can safely be resent. + self.last_stream_id = None + + #: Additional debug data that can be appended to GOAWAY frame. + self.additional_data = None + + def __repr__(self): + return ( + "" % ( + self.error_code, + self.last_stream_id, + _bytes_representation( + self.additional_data[:20] + if self.additional_data else None) + ) + ) + + +class AlternativeServiceAvailable(Event): + """ + The AlternativeServiceAvailable event is fired when the remote peer + advertises an `RFC 7838 `_ Alternative + Service using an ALTSVC frame. + + This event always carries the origin to which the ALTSVC information + applies. That origin is either supplied by the server directly, or inferred + by hyper-h2 from the ``:authority`` pseudo-header field that was sent by + the user when initiating a given stream. + + This event also carries what RFC 7838 calls the "Alternative Service Field + Value", which is formatted like a HTTP header field and contains the + relevant alternative service information. Hyper-h2 does not parse or in any + way modify that information: the user is required to do that. + + This event can only be fired on the client end of a connection. + + .. versionadded:: 2.3.0 + """ + def __init__(self): + #: The origin to which the alternative service field value applies. + #: This field is either supplied by the server directly, or inferred by + #: hyper-h2 from the ``:authority`` pseudo-header field that was sent + #: by the user when initiating the stream on which the frame was + #: received. + self.origin = None + + #: The ALTSVC field value. This contains information about the HTTP + #: alternative service being advertised by the server. Hyper-h2 does + #: not parse this field: it is left exactly as sent by the server. The + #: structure of the data in this field is given by `RFC 7838 Section 3 + #: `_. + self.field_value = None + + def __repr__(self): + return ( + "" % ( + self.origin.decode('utf-8', 'ignore'), + self.field_value.decode('utf-8', 'ignore'), + ) + ) + + +class UnknownFrameReceived(Event): + """ + The UnknownFrameReceived event is fired when the remote peer sends a frame + that hyper-h2 does not understand. This occurs primarily when the remote + peer is employing HTTP/2 extensions that hyper-h2 doesn't know anything + about. + + RFC 7540 requires that HTTP/2 implementations ignore these frames. hyper-h2 + does so. However, this event is fired to allow implementations to perform + special processing on those frames if needed (e.g. if the implementation + is capable of handling the frame itself). + + .. versionadded:: 2.7.0 + """ + def __init__(self): + #: The hyperframe Frame object that encapsulates the received frame. + self.frame = None + + def __repr__(self): + return "" + + +def _bytes_representation(data): + """ + Converts a bytestring into something that is safe to print on all Python + platforms. + + This function is relatively expensive, so it should not be called on the + mainline of the code. It's safe to use in things like object repr methods + though. + """ + if data is None: + return None + + hex = binascii.hexlify(data) + + # This is moderately clever: on all Python versions hexlify returns a byte + # string. On Python 3 we want an actual string, so we just check whether + # that's what we have. + if not isinstance(hex, str): # pragma: no cover + hex = hex.decode('ascii') + + return hex diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/exceptions.py b/testing/web-platform/tests/tools/third_party/h2/h2/exceptions.py new file mode 100644 index 0000000000..388f9e9a38 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/exceptions.py @@ -0,0 +1,186 @@ +# -*- coding: utf-8 -*- +""" +h2/exceptions +~~~~~~~~~~~~~ + +Exceptions for the HTTP/2 module. +""" +import h2.errors + + +class H2Error(Exception): + """ + The base class for all exceptions for the HTTP/2 module. + """ + + +class ProtocolError(H2Error): + """ + An action was attempted in violation of the HTTP/2 protocol. + """ + #: The error code corresponds to this kind of Protocol Error. + error_code = h2.errors.ErrorCodes.PROTOCOL_ERROR + + +class FrameTooLargeError(ProtocolError): + """ + The frame that we tried to send or that we received was too large. + """ + #: This error code that corresponds to this kind of Protocol Error. + error_code = h2.errors.ErrorCodes.FRAME_SIZE_ERROR + + +class FrameDataMissingError(ProtocolError): + """ + The frame that we received is missing some data. + + .. versionadded:: 2.0.0 + """ + #: The error code that corresponds to this kind of Protocol Error + error_code = h2.errors.ErrorCodes.FRAME_SIZE_ERROR + + +class TooManyStreamsError(ProtocolError): + """ + An attempt was made to open a stream that would lead to too many concurrent + streams. + """ + pass + + +class FlowControlError(ProtocolError): + """ + An attempted action violates flow control constraints. + """ + #: The error code that corresponds to this kind of + #: :class:`ProtocolError ` + error_code = h2.errors.ErrorCodes.FLOW_CONTROL_ERROR + + +class StreamIDTooLowError(ProtocolError): + """ + An attempt was made to open a stream that had an ID that is lower than the + highest ID we have seen on this connection. + """ + def __init__(self, stream_id, max_stream_id): + #: The ID of the stream that we attempted to open. + self.stream_id = stream_id + + #: The current highest-seen stream ID. + self.max_stream_id = max_stream_id + + def __str__(self): + return "StreamIDTooLowError: %d is lower than %d" % ( + self.stream_id, self.max_stream_id + ) + + +class NoAvailableStreamIDError(ProtocolError): + """ + There are no available stream IDs left to the connection. All stream IDs + have been exhausted. + + .. versionadded:: 2.0.0 + """ + pass + + +class NoSuchStreamError(ProtocolError): + """ + A stream-specific action referenced a stream that does not exist. + + .. versionchanged:: 2.0.0 + Became a subclass of :class:`ProtocolError + ` + """ + def __init__(self, stream_id): + #: The stream ID that corresponds to the non-existent stream. + self.stream_id = stream_id + + +class StreamClosedError(NoSuchStreamError): + """ + A more specific form of + :class:`NoSuchStreamError `. Indicates + that the stream has since been closed, and that all state relating to that + stream has been removed. + """ + def __init__(self, stream_id): + #: The stream ID that corresponds to the nonexistent stream. + self.stream_id = stream_id + + #: The relevant HTTP/2 error code. + self.error_code = h2.errors.ErrorCodes.STREAM_CLOSED + + # Any events that internal code may need to fire. Not relevant to + # external users that may receive a StreamClosedError. + self._events = [] + + +class InvalidSettingsValueError(ProtocolError, ValueError): + """ + An attempt was made to set an invalid Settings value. + + .. versionadded:: 2.0.0 + """ + def __init__(self, msg, error_code): + super(InvalidSettingsValueError, self).__init__(msg) + self.error_code = error_code + + +class InvalidBodyLengthError(ProtocolError): + """ + The remote peer sent more or less data that the Content-Length header + indicated. + + .. versionadded:: 2.0.0 + """ + def __init__(self, expected, actual): + self.expected_length = expected + self.actual_length = actual + + def __str__(self): + return "InvalidBodyLengthError: Expected %d bytes, received %d" % ( + self.expected_length, self.actual_length + ) + + +class UnsupportedFrameError(ProtocolError, KeyError): + """ + The remote peer sent a frame that is unsupported in this context. + + .. versionadded:: 2.1.0 + """ + # TODO: Remove the KeyError in 3.0.0 + pass + + +class RFC1122Error(H2Error): + """ + Emitted when users attempt to do something that is literally allowed by the + relevant RFC, but is sufficiently ill-defined that it's unwise to allow + users to actually do it. + + While there is some disagreement about whether or not we should be liberal + in what accept, it is a truth universally acknowledged that we should be + conservative in what emit. + + .. versionadded:: 2.4.0 + """ + # shazow says I'm going to regret naming the exception this way. If that + # turns out to be true, TELL HIM NOTHING. + pass + + +class DenialOfServiceError(ProtocolError): + """ + Emitted when the remote peer exhibits a behaviour that is likely to be an + attempt to perform a Denial of Service attack on the implementation. This + is a form of ProtocolError that carries a different error code, and allows + more easy detection of this kind of behaviour. + + .. versionadded:: 2.5.0 + """ + #: The error code that corresponds to this kind of + #: :class:`ProtocolError ` + error_code = h2.errors.ErrorCodes.ENHANCE_YOUR_CALM diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/frame_buffer.py b/testing/web-platform/tests/tools/third_party/h2/h2/frame_buffer.py new file mode 100644 index 0000000000..e79f6ec2de --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/frame_buffer.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +""" +h2/frame_buffer +~~~~~~~~~~~~~~~ + +A data structure that provides a way to iterate over a byte buffer in terms of +frames. +""" +from hyperframe.exceptions import InvalidFrameError +from hyperframe.frame import ( + Frame, HeadersFrame, ContinuationFrame, PushPromiseFrame +) + +from .exceptions import ( + ProtocolError, FrameTooLargeError, FrameDataMissingError +) + +# To avoid a DOS attack based on sending loads of continuation frames, we limit +# the maximum number we're perpared to receive. In this case, we'll set the +# limit to 64, which means the largest encoded header block we can receive by +# default is 262144 bytes long, and the largest possible *at all* is 1073741760 +# bytes long. +# +# This value seems reasonable for now, but in future we may want to evaluate +# making it configurable. +CONTINUATION_BACKLOG = 64 + + +class FrameBuffer(object): + """ + This is a data structure that expects to act as a buffer for HTTP/2 data + that allows iteraton in terms of H2 frames. + """ + def __init__(self, server=False): + self.data = b'' + self.max_frame_size = 0 + self._preamble = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' if server else b'' + self._preamble_len = len(self._preamble) + self._headers_buffer = [] + + def add_data(self, data): + """ + Add more data to the frame buffer. + + :param data: A bytestring containing the byte buffer. + """ + if self._preamble_len: + data_len = len(data) + of_which_preamble = min(self._preamble_len, data_len) + + if self._preamble[:of_which_preamble] != data[:of_which_preamble]: + raise ProtocolError("Invalid HTTP/2 preamble.") + + data = data[of_which_preamble:] + self._preamble_len -= of_which_preamble + self._preamble = self._preamble[of_which_preamble:] + + self.data += data + + def _parse_frame_header(self, data): + """ + Parses the frame header from the data. Either returns a tuple of + (frame, length), or throws an exception. The returned frame may be None + if the frame is of unknown type. + """ + try: + frame, length = Frame.parse_frame_header(data[:9]) + except ValueError as e: + # The frame header is invalid. This is a ProtocolError + raise ProtocolError("Invalid frame header received: %s" % str(e)) + + return frame, length + + def _validate_frame_length(self, length): + """ + Confirm that the frame is an appropriate length. + """ + if length > self.max_frame_size: + raise FrameTooLargeError( + "Received overlong frame: length %d, max %d" % + (length, self.max_frame_size) + ) + + def _update_header_buffer(self, f): + """ + Updates the internal header buffer. Returns a frame that should replace + the current one. May throw exceptions if this frame is invalid. + """ + # Check if we're in the middle of a headers block. If we are, this + # frame *must* be a CONTINUATION frame with the same stream ID as the + # leading HEADERS or PUSH_PROMISE frame. Anything else is a + # ProtocolError. If the frame *is* valid, append it to the header + # buffer. + if self._headers_buffer: + stream_id = self._headers_buffer[0].stream_id + valid_frame = ( + f is not None and + isinstance(f, ContinuationFrame) and + f.stream_id == stream_id + ) + if not valid_frame: + raise ProtocolError("Invalid frame during header block.") + + # Append the frame to the buffer. + self._headers_buffer.append(f) + if len(self._headers_buffer) > CONTINUATION_BACKLOG: + raise ProtocolError("Too many continuation frames received.") + + # If this is the end of the header block, then we want to build a + # mutant HEADERS frame that's massive. Use the original one we got, + # then set END_HEADERS and set its data appopriately. If it's not + # the end of the block, lose the current frame: we can't yield it. + if 'END_HEADERS' in f.flags: + f = self._headers_buffer[0] + f.flags.add('END_HEADERS') + f.data = b''.join(x.data for x in self._headers_buffer) + self._headers_buffer = [] + else: + f = None + elif (isinstance(f, (HeadersFrame, PushPromiseFrame)) and + 'END_HEADERS' not in f.flags): + # This is the start of a headers block! Save the frame off and then + # act like we didn't receive one. + self._headers_buffer.append(f) + f = None + + return f + + # The methods below support the iterator protocol. + def __iter__(self): + return self + + def next(self): # Python 2 + # First, check that we have enough data to successfully parse the + # next frame header. If not, bail. Otherwise, parse it. + if len(self.data) < 9: + raise StopIteration() + + try: + f, length = self._parse_frame_header(self.data) + except InvalidFrameError: # pragma: no cover + raise ProtocolError("Received frame with invalid frame header.") + + # Next, check that we have enough length to parse the frame body. If + # not, bail, leaving the frame header data in the buffer for next time. + if len(self.data) < length + 9: + raise StopIteration() + + # Confirm the frame has an appropriate length. + self._validate_frame_length(length) + + # Don't try to parse the body if we didn't get a frame we know about: + # there's nothing we can do with it anyway. + if f is not None: + try: + f.parse_body(memoryview(self.data[9:9+length])) + except InvalidFrameError: + raise FrameDataMissingError("Frame data missing or invalid") + + # At this point, as we know we'll use or discard the entire frame, we + # can update the data. + self.data = self.data[9+length:] + + # Pass the frame through the header buffer. + f = self._update_header_buffer(f) + + # If we got a frame we didn't understand or shouldn't yield, rather + # than return None it'd be better if we just tried to get the next + # frame in the sequence instead. Recurse back into ourselves to do + # that. This is safe because the amount of work we have to do here is + # strictly bounded by the length of the buffer. + return f if f is not None else self.next() + + def __next__(self): # Python 3 + return self.next() diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/settings.py b/testing/web-platform/tests/tools/third_party/h2/h2/settings.py new file mode 100644 index 0000000000..bf87c94f4e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/settings.py @@ -0,0 +1,339 @@ +# -*- coding: utf-8 -*- +""" +h2/settings +~~~~~~~~~~~ + +This module contains a HTTP/2 settings object. This object provides a simple +API for manipulating HTTP/2 settings, keeping track of both the current active +state of the settings and the unacknowledged future values of the settings. +""" +import collections +import enum + +from hyperframe.frame import SettingsFrame + +from h2.errors import ErrorCodes +from h2.exceptions import InvalidSettingsValueError + +try: + from collections.abc import MutableMapping +except ImportError: # pragma: no cover + # Python 2.7 compatibility + from collections import MutableMapping + + +class SettingCodes(enum.IntEnum): + """ + All known HTTP/2 setting codes. + + .. versionadded:: 2.6.0 + """ + + #: Allows the sender to inform the remote endpoint of the maximum size of + #: the header compression table used to decode header blocks, in octets. + HEADER_TABLE_SIZE = SettingsFrame.HEADER_TABLE_SIZE + + #: This setting can be used to disable server push. To disable server push + #: on a client, set this to 0. + ENABLE_PUSH = SettingsFrame.ENABLE_PUSH + + #: Indicates the maximum number of concurrent streams that the sender will + #: allow. + MAX_CONCURRENT_STREAMS = SettingsFrame.MAX_CONCURRENT_STREAMS + + #: Indicates the sender's initial window size (in octets) for stream-level + #: flow control. + INITIAL_WINDOW_SIZE = SettingsFrame.INITIAL_WINDOW_SIZE + + #: Indicates the size of the largest frame payload that the sender is + #: willing to receive, in octets. + MAX_FRAME_SIZE = SettingsFrame.MAX_FRAME_SIZE + + #: This advisory setting informs a peer of the maximum size of header list + #: that the sender is prepared to accept, in octets. The value is based on + #: the uncompressed size of header fields, including the length of the name + #: and value in octets plus an overhead of 32 octets for each header field. + MAX_HEADER_LIST_SIZE = SettingsFrame.MAX_HEADER_LIST_SIZE + + #: This setting can be used to enable the connect protocol. To enable on a + #: client set this to 1. + ENABLE_CONNECT_PROTOCOL = SettingsFrame.ENABLE_CONNECT_PROTOCOL + + +def _setting_code_from_int(code): + """ + Given an integer setting code, returns either one of :class:`SettingCodes + ` or, if not present in the known set of codes, + returns the integer directly. + """ + try: + return SettingCodes(code) + except ValueError: + return code + + +class ChangedSetting: + + def __init__(self, setting, original_value, new_value): + #: The setting code given. Either one of :class:`SettingCodes + #: ` or ``int`` + #: + #: .. versionchanged:: 2.6.0 + self.setting = setting + + #: The original value before being changed. + self.original_value = original_value + + #: The new value after being changed. + self.new_value = new_value + + def __repr__(self): + return ( + "ChangedSetting(setting=%s, original_value=%s, " + "new_value=%s)" + ) % ( + self.setting, + self.original_value, + self.new_value + ) + + +class Settings(MutableMapping): + """ + An object that encapsulates HTTP/2 settings state. + + HTTP/2 Settings are a complex beast. Each party, remote and local, has its + own settings and a view of the other party's settings. When a settings + frame is emitted by a peer it cannot assume that the new settings values + are in place until the remote peer acknowledges the setting. In principle, + multiple settings changes can be "in flight" at the same time, all with + different values. + + This object encapsulates this mess. It provides a dict-like interface to + settings, which return the *current* values of the settings in question. + Additionally, it keeps track of the stack of proposed values: each time an + acknowledgement is sent/received, it updates the current values with the + stack of proposed values. On top of all that, it validates the values to + make sure they're allowed, and raises :class:`InvalidSettingsValueError + ` if they are not. + + Finally, this object understands what the default values of the HTTP/2 + settings are, and sets those defaults appropriately. + + .. versionchanged:: 2.2.0 + Added the ``initial_values`` parameter. + + .. versionchanged:: 2.5.0 + Added the ``max_header_list_size`` property. + + :param client: (optional) Whether these settings should be defaulted for a + client implementation or a server implementation. Defaults to ``True``. + :type client: ``bool`` + :param initial_values: (optional) Any initial values the user would like + set, rather than RFC 7540's defaults. + :type initial_vales: ``MutableMapping`` + """ + def __init__(self, client=True, initial_values=None): + # Backing object for the settings. This is a dictionary of + # (setting: [list of values]), where the first value in the list is the + # current value of the setting. Strictly this doesn't use lists but + # instead uses collections.deque to avoid repeated memory allocations. + # + # This contains the default values for HTTP/2. + self._settings = { + SettingCodes.HEADER_TABLE_SIZE: collections.deque([4096]), + SettingCodes.ENABLE_PUSH: collections.deque([int(client)]), + SettingCodes.INITIAL_WINDOW_SIZE: collections.deque([65535]), + SettingCodes.MAX_FRAME_SIZE: collections.deque([16384]), + SettingCodes.ENABLE_CONNECT_PROTOCOL: collections.deque([0]), + } + if initial_values is not None: + for key, value in initial_values.items(): + invalid = _validate_setting(key, value) + if invalid: + raise InvalidSettingsValueError( + "Setting %d has invalid value %d" % (key, value), + error_code=invalid + ) + self._settings[key] = collections.deque([value]) + + def acknowledge(self): + """ + The settings have been acknowledged, either by the user (remote + settings) or by the remote peer (local settings). + + :returns: A dict of {setting: ChangedSetting} that were applied. + """ + changed_settings = {} + + # If there is more than one setting in the list, we have a setting + # value outstanding. Update them. + for k, v in self._settings.items(): + if len(v) > 1: + old_setting = v.popleft() + new_setting = v[0] + changed_settings[k] = ChangedSetting( + k, old_setting, new_setting + ) + + return changed_settings + + # Provide easy-access to well known settings. + @property + def header_table_size(self): + """ + The current value of the :data:`HEADER_TABLE_SIZE + ` setting. + """ + return self[SettingCodes.HEADER_TABLE_SIZE] + + @header_table_size.setter + def header_table_size(self, value): + self[SettingCodes.HEADER_TABLE_SIZE] = value + + @property + def enable_push(self): + """ + The current value of the :data:`ENABLE_PUSH + ` setting. + """ + return self[SettingCodes.ENABLE_PUSH] + + @enable_push.setter + def enable_push(self, value): + self[SettingCodes.ENABLE_PUSH] = value + + @property + def initial_window_size(self): + """ + The current value of the :data:`INITIAL_WINDOW_SIZE + ` setting. + """ + return self[SettingCodes.INITIAL_WINDOW_SIZE] + + @initial_window_size.setter + def initial_window_size(self, value): + self[SettingCodes.INITIAL_WINDOW_SIZE] = value + + @property + def max_frame_size(self): + """ + The current value of the :data:`MAX_FRAME_SIZE + ` setting. + """ + return self[SettingCodes.MAX_FRAME_SIZE] + + @max_frame_size.setter + def max_frame_size(self, value): + self[SettingCodes.MAX_FRAME_SIZE] = value + + @property + def max_concurrent_streams(self): + """ + The current value of the :data:`MAX_CONCURRENT_STREAMS + ` setting. + """ + return self.get(SettingCodes.MAX_CONCURRENT_STREAMS, 2**32+1) + + @max_concurrent_streams.setter + def max_concurrent_streams(self, value): + self[SettingCodes.MAX_CONCURRENT_STREAMS] = value + + @property + def max_header_list_size(self): + """ + The current value of the :data:`MAX_HEADER_LIST_SIZE + ` setting. If not set, + returns ``None``, which means unlimited. + + .. versionadded:: 2.5.0 + """ + return self.get(SettingCodes.MAX_HEADER_LIST_SIZE, None) + + @max_header_list_size.setter + def max_header_list_size(self, value): + self[SettingCodes.MAX_HEADER_LIST_SIZE] = value + + @property + def enable_connect_protocol(self): + """ + The current value of the :data:`ENABLE_CONNECT_PROTOCOL + ` setting. + """ + return self[SettingCodes.ENABLE_CONNECT_PROTOCOL] + + @enable_connect_protocol.setter + def enable_connect_protocol(self, value): + self[SettingCodes.ENABLE_CONNECT_PROTOCOL] = value + + # Implement the MutableMapping API. + def __getitem__(self, key): + val = self._settings[key][0] + + # Things that were created when a setting was received should stay + # KeyError'd. + if val is None: + raise KeyError + + return val + + def __setitem__(self, key, value): + invalid = _validate_setting(key, value) + if invalid: + raise InvalidSettingsValueError( + "Setting %d has invalid value %d" % (key, value), + error_code=invalid + ) + + try: + items = self._settings[key] + except KeyError: + items = collections.deque([None]) + self._settings[key] = items + + items.append(value) + + def __delitem__(self, key): + del self._settings[key] + + def __iter__(self): + return self._settings.__iter__() + + def __len__(self): + return len(self._settings) + + def __eq__(self, other): + if isinstance(other, Settings): + return self._settings == other._settings + else: + return NotImplemented + + def __ne__(self, other): + if isinstance(other, Settings): + return not self == other + else: + return NotImplemented + + +def _validate_setting(setting, value): # noqa: C901 + """ + Confirms that a specific setting has a well-formed value. If the setting is + invalid, returns an error code. Otherwise, returns 0 (NO_ERROR). + """ + if setting == SettingCodes.ENABLE_PUSH: + if value not in (0, 1): + return ErrorCodes.PROTOCOL_ERROR + elif setting == SettingCodes.INITIAL_WINDOW_SIZE: + if not 0 <= value <= 2147483647: # 2^31 - 1 + return ErrorCodes.FLOW_CONTROL_ERROR + elif setting == SettingCodes.MAX_FRAME_SIZE: + if not 16384 <= value <= 16777215: # 2^14 and 2^24 - 1 + return ErrorCodes.PROTOCOL_ERROR + elif setting == SettingCodes.MAX_HEADER_LIST_SIZE: + if value < 0: + return ErrorCodes.PROTOCOL_ERROR + elif setting == SettingCodes.ENABLE_CONNECT_PROTOCOL: + if value not in (0, 1): + return ErrorCodes.PROTOCOL_ERROR + + return 0 diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/stream.py b/testing/web-platform/tests/tools/third_party/h2/h2/stream.py new file mode 100644 index 0000000000..1cb91786d4 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/stream.py @@ -0,0 +1,1369 @@ +# -*- coding: utf-8 -*- +""" +h2/stream +~~~~~~~~~ + +An implementation of a HTTP/2 stream. +""" +from enum import Enum, IntEnum +from hpack import HeaderTuple +from hyperframe.frame import ( + HeadersFrame, ContinuationFrame, DataFrame, WindowUpdateFrame, + RstStreamFrame, PushPromiseFrame, AltSvcFrame +) + +from .errors import ErrorCodes, _error_code_from_int +from .events import ( + RequestReceived, ResponseReceived, DataReceived, WindowUpdated, + StreamEnded, PushedStreamReceived, StreamReset, TrailersReceived, + InformationalResponseReceived, AlternativeServiceAvailable, + _ResponseSent, _RequestSent, _TrailersSent, _PushedRequestSent +) +from .exceptions import ( + ProtocolError, StreamClosedError, InvalidBodyLengthError, FlowControlError +) +from .utilities import ( + guard_increment_window, is_informational_response, authority_from_headers, + validate_headers, validate_outbound_headers, normalize_outbound_headers, + HeaderValidationFlags, extract_method_header, normalize_inbound_headers +) +from .windows import WindowManager + + +class StreamState(IntEnum): + IDLE = 0 + RESERVED_REMOTE = 1 + RESERVED_LOCAL = 2 + OPEN = 3 + HALF_CLOSED_REMOTE = 4 + HALF_CLOSED_LOCAL = 5 + CLOSED = 6 + + +class StreamInputs(Enum): + SEND_HEADERS = 0 + SEND_PUSH_PROMISE = 1 + SEND_RST_STREAM = 2 + SEND_DATA = 3 + SEND_WINDOW_UPDATE = 4 + SEND_END_STREAM = 5 + RECV_HEADERS = 6 + RECV_PUSH_PROMISE = 7 + RECV_RST_STREAM = 8 + RECV_DATA = 9 + RECV_WINDOW_UPDATE = 10 + RECV_END_STREAM = 11 + RECV_CONTINUATION = 12 # Added in 2.0.0 + SEND_INFORMATIONAL_HEADERS = 13 # Added in 2.2.0 + RECV_INFORMATIONAL_HEADERS = 14 # Added in 2.2.0 + SEND_ALTERNATIVE_SERVICE = 15 # Added in 2.3.0 + RECV_ALTERNATIVE_SERVICE = 16 # Added in 2.3.0 + UPGRADE_CLIENT = 17 # Added 2.3.0 + UPGRADE_SERVER = 18 # Added 2.3.0 + + +class StreamClosedBy(Enum): + SEND_END_STREAM = 0 + RECV_END_STREAM = 1 + SEND_RST_STREAM = 2 + RECV_RST_STREAM = 3 + + +# This array is initialized once, and is indexed by the stream states above. +# It indicates whether a stream in the given state is open. The reason we do +# this is that we potentially check whether a stream in a given state is open +# quite frequently: given that we check so often, we should do so in the +# fastest and most performant way possible. +STREAM_OPEN = [False for _ in range(0, len(StreamState))] +STREAM_OPEN[StreamState.OPEN] = True +STREAM_OPEN[StreamState.HALF_CLOSED_LOCAL] = True +STREAM_OPEN[StreamState.HALF_CLOSED_REMOTE] = True + + +class H2StreamStateMachine(object): + """ + A single HTTP/2 stream state machine. + + This stream object implements basically the state machine described in + RFC 7540 section 5.1. + + :param stream_id: The stream ID of this stream. This is stored primarily + for logging purposes. + """ + def __init__(self, stream_id): + self.state = StreamState.IDLE + self.stream_id = stream_id + + #: Whether this peer is the client side of this stream. + self.client = None + + # Whether trailers have been sent/received on this stream or not. + self.headers_sent = None + self.trailers_sent = None + self.headers_received = None + self.trailers_received = None + + # How the stream was closed. One of StreamClosedBy. + self.stream_closed_by = None + + def process_input(self, input_): + """ + Process a specific input in the state machine. + """ + if not isinstance(input_, StreamInputs): + raise ValueError("Input must be an instance of StreamInputs") + + try: + func, target_state = _transitions[(self.state, input_)] + except KeyError: + old_state = self.state + self.state = StreamState.CLOSED + raise ProtocolError( + "Invalid input %s in state %s" % (input_, old_state) + ) + else: + previous_state = self.state + self.state = target_state + if func is not None: + try: + return func(self, previous_state) + except ProtocolError: + self.state = StreamState.CLOSED + raise + except AssertionError as e: # pragma: no cover + self.state = StreamState.CLOSED + raise ProtocolError(e) + + return [] + + def request_sent(self, previous_state): + """ + Fires when a request is sent. + """ + self.client = True + self.headers_sent = True + event = _RequestSent() + + return [event] + + def response_sent(self, previous_state): + """ + Fires when something that should be a response is sent. This 'response' + may actually be trailers. + """ + if not self.headers_sent: + if self.client is True or self.client is None: + raise ProtocolError("Client cannot send responses.") + self.headers_sent = True + event = _ResponseSent() + else: + assert not self.trailers_sent + self.trailers_sent = True + event = _TrailersSent() + + return [event] + + def request_received(self, previous_state): + """ + Fires when a request is received. + """ + assert not self.headers_received + assert not self.trailers_received + + self.client = False + self.headers_received = True + event = RequestReceived() + + event.stream_id = self.stream_id + return [event] + + def response_received(self, previous_state): + """ + Fires when a response is received. Also disambiguates between responses + and trailers. + """ + if not self.headers_received: + assert self.client is True + self.headers_received = True + event = ResponseReceived() + else: + assert not self.trailers_received + self.trailers_received = True + event = TrailersReceived() + + event.stream_id = self.stream_id + return [event] + + def data_received(self, previous_state): + """ + Fires when data is received. + """ + event = DataReceived() + event.stream_id = self.stream_id + return [event] + + def window_updated(self, previous_state): + """ + Fires when a window update frame is received. + """ + event = WindowUpdated() + event.stream_id = self.stream_id + return [event] + + def stream_half_closed(self, previous_state): + """ + Fires when an END_STREAM flag is received in the OPEN state, + transitioning this stream to a HALF_CLOSED_REMOTE state. + """ + event = StreamEnded() + event.stream_id = self.stream_id + return [event] + + def stream_ended(self, previous_state): + """ + Fires when a stream is cleanly ended. + """ + self.stream_closed_by = StreamClosedBy.RECV_END_STREAM + event = StreamEnded() + event.stream_id = self.stream_id + return [event] + + def stream_reset(self, previous_state): + """ + Fired when a stream is forcefully reset. + """ + self.stream_closed_by = StreamClosedBy.RECV_RST_STREAM + event = StreamReset() + event.stream_id = self.stream_id + return [event] + + def send_new_pushed_stream(self, previous_state): + """ + Fires on the newly pushed stream, when pushed by the local peer. + + No event here, but definitionally this peer must be a server. + """ + assert self.client is None + self.client = False + self.headers_received = True + return [] + + def recv_new_pushed_stream(self, previous_state): + """ + Fires on the newly pushed stream, when pushed by the remote peer. + + No event here, but definitionally this peer must be a client. + """ + assert self.client is None + self.client = True + self.headers_sent = True + return [] + + def send_push_promise(self, previous_state): + """ + Fires on the already-existing stream when a PUSH_PROMISE frame is sent. + We may only send PUSH_PROMISE frames if we're a server. + """ + if self.client is True: + raise ProtocolError("Cannot push streams from client peers.") + + event = _PushedRequestSent() + return [event] + + def recv_push_promise(self, previous_state): + """ + Fires on the already-existing stream when a PUSH_PROMISE frame is + received. We may only receive PUSH_PROMISE frames if we're a client. + + Fires a PushedStreamReceived event. + """ + if not self.client: + if self.client is None: # pragma: no cover + msg = "Idle streams cannot receive pushes" + else: # pragma: no cover + msg = "Cannot receive pushed streams as a server" + raise ProtocolError(msg) + + event = PushedStreamReceived() + event.parent_stream_id = self.stream_id + return [event] + + def send_end_stream(self, previous_state): + """ + Called when an attempt is made to send END_STREAM in the + HALF_CLOSED_REMOTE state. + """ + self.stream_closed_by = StreamClosedBy.SEND_END_STREAM + + def send_reset_stream(self, previous_state): + """ + Called when an attempt is made to send RST_STREAM in a non-closed + stream state. + """ + self.stream_closed_by = StreamClosedBy.SEND_RST_STREAM + + def reset_stream_on_error(self, previous_state): + """ + Called when we need to forcefully emit another RST_STREAM frame on + behalf of the state machine. + + If this is the first time we've done this, we should also hang an event + off the StreamClosedError so that the user can be informed. We know + it's the first time we've done this if the stream is currently in a + state other than CLOSED. + """ + self.stream_closed_by = StreamClosedBy.SEND_RST_STREAM + + error = StreamClosedError(self.stream_id) + + event = StreamReset() + event.stream_id = self.stream_id + event.error_code = ErrorCodes.STREAM_CLOSED + event.remote_reset = False + error._events = [event] + raise error + + def recv_on_closed_stream(self, previous_state): + """ + Called when an unexpected frame is received on an already-closed + stream. + + An endpoint that receives an unexpected frame should treat it as + a stream error or connection error with type STREAM_CLOSED, depending + on the specific frame. The error handling is done at a higher level: + this just raises the appropriate error. + """ + raise StreamClosedError(self.stream_id) + + def send_on_closed_stream(self, previous_state): + """ + Called when an attempt is made to send data on an already-closed + stream. + + This essentially overrides the standard logic by throwing a + more-specific error: StreamClosedError. This is a ProtocolError, so it + matches the standard API of the state machine, but provides more detail + to the user. + """ + raise StreamClosedError(self.stream_id) + + def recv_push_on_closed_stream(self, previous_state): + """ + Called when a PUSH_PROMISE frame is received on a full stop + stream. + + If the stream was closed by us sending a RST_STREAM frame, then we + presume that the PUSH_PROMISE was in flight when we reset the parent + stream. Rathen than accept the new stream, we just reset it. + Otherwise, we should call this a PROTOCOL_ERROR: pushing a stream on a + naturally closed stream is a real problem because it creates a brand + new stream that the remote peer now believes exists. + """ + assert self.stream_closed_by is not None + + if self.stream_closed_by == StreamClosedBy.SEND_RST_STREAM: + raise StreamClosedError(self.stream_id) + else: + raise ProtocolError("Attempted to push on closed stream.") + + def send_push_on_closed_stream(self, previous_state): + """ + Called when an attempt is made to push on an already-closed stream. + + This essentially overrides the standard logic by providing a more + useful error message. It's necessary because simply indicating that the + stream is closed is not enough: there is now a new stream that is not + allowed to be there. The only recourse is to tear the whole connection + down. + """ + raise ProtocolError("Attempted to push on closed stream.") + + def send_informational_response(self, previous_state): + """ + Called when an informational header block is sent (that is, a block + where the :status header has a 1XX value). + + Only enforces that these are sent *before* final headers are sent. + """ + if self.headers_sent: + raise ProtocolError("Information response after final response") + + event = _ResponseSent() + return [event] + + def recv_informational_response(self, previous_state): + """ + Called when an informational header block is received (that is, a block + where the :status header has a 1XX value). + """ + if self.headers_received: + raise ProtocolError("Informational response after final response") + + event = InformationalResponseReceived() + event.stream_id = self.stream_id + return [event] + + def recv_alt_svc(self, previous_state): + """ + Called when receiving an ALTSVC frame. + + RFC 7838 allows us to receive ALTSVC frames at any stream state, which + is really absurdly overzealous. For that reason, we want to limit the + states in which we can actually receive it. It's really only sensible + to receive it after we've sent our own headers and before the server + has sent its header block: the server can't guarantee that we have any + state around after it completes its header block, and the server + doesn't know what origin we're talking about before we've sent ours. + + For that reason, this function applies a few extra checks on both state + and some of the little state variables we keep around. If those suggest + an unreasonable situation for the ALTSVC frame to have been sent in, + we quietly ignore it (as RFC 7838 suggests). + + This function is also *not* always called by the state machine. In some + states (IDLE, RESERVED_LOCAL, CLOSED) we don't bother to call it, + because we know the frame cannot be valid in that state (IDLE because + the server cannot know what origin the stream applies to, CLOSED + because the server cannot assume we still have state around, + RESERVED_LOCAL because by definition if we're in the RESERVED_LOCAL + state then *we* are the server). + """ + # Servers can't receive ALTSVC frames, but RFC 7838 tells us to ignore + # them. + if self.client is False: + return [] + + # If we've received the response headers from the server they can't + # guarantee we still have any state around. Other implementations + # (like nghttp2) ignore ALTSVC in this state, so we will too. + if self.headers_received: + return [] + + # Otherwise, this is a sensible enough frame to have received. Return + # the event and let it get populated. + return [AlternativeServiceAvailable()] + + def send_alt_svc(self, previous_state): + """ + Called when sending an ALTSVC frame on this stream. + + For consistency with the restrictions we apply on receiving ALTSVC + frames in ``recv_alt_svc``, we want to restrict when users can send + ALTSVC frames to the situations when we ourselves would accept them. + + That means: when we are a server, when we have received the request + headers, and when we have not yet sent our own response headers. + """ + # We should not send ALTSVC after we've sent response headers, as the + # client may have disposed of its state. + if self.headers_sent: + raise ProtocolError( + "Cannot send ALTSVC after sending response headers." + ) + + return + + +# STATE MACHINE +# +# The stream state machine is defined here to avoid the need to allocate it +# repeatedly for each stream. It cannot be defined in the stream class because +# it needs to be able to reference the callbacks defined on the class, but +# because Python's scoping rules are weird the class object is not actually in +# scope during the body of the class object. +# +# For the sake of clarity, we reproduce the RFC 7540 state machine here: +# +# +--------+ +# send PP | | recv PP +# ,--------| idle |--------. +# / | | \ +# v +--------+ v +# +----------+ | +----------+ +# | | | send H / | | +# ,------| reserved | | recv H | reserved |------. +# | | (local) | | | (remote) | | +# | +----------+ v +----------+ | +# | | +--------+ | | +# | | recv ES | | send ES | | +# | send H | ,-------| open |-------. | recv H | +# | | / | | \ | | +# | v v +--------+ v v | +# | +----------+ | +----------+ | +# | | half | | | half | | +# | | closed | | send R / | closed | | +# | | (remote) | | recv R | (local) | | +# | +----------+ | +----------+ | +# | | | | | +# | | send ES / | recv ES / | | +# | | send R / v send R / | | +# | | recv R +--------+ recv R | | +# | send R / `----------->| |<-----------' send R / | +# | recv R | closed | recv R | +# `----------------------->| |<----------------------' +# +--------+ +# +# send: endpoint sends this frame +# recv: endpoint receives this frame +# +# H: HEADERS frame (with implied CONTINUATIONs) +# PP: PUSH_PROMISE frame (with implied CONTINUATIONs) +# ES: END_STREAM flag +# R: RST_STREAM frame +# +# For the purposes of this state machine we treat HEADERS and their +# associated CONTINUATION frames as a single jumbo frame. The protocol +# allows/requires this by preventing other frames from being interleved in +# between HEADERS/CONTINUATION frames. However, if a CONTINUATION frame is +# received without a prior HEADERS frame, it *will* be passed to this state +# machine. The state machine should always reject that frame, either as an +# invalid transition or because the stream is closed. +# +# There is a confusing relationship around PUSH_PROMISE frames. The state +# machine above considers them to be frames belonging to the new stream, +# which is *somewhat* true. However, they are sent with the stream ID of +# their related stream, and are only sendable in some cases. +# For this reason, our state machine implementation below allows for +# PUSH_PROMISE frames both in the IDLE state (as in the diagram), but also +# in the OPEN, HALF_CLOSED_LOCAL, and HALF_CLOSED_REMOTE states. +# Essentially, for hyper-h2, PUSH_PROMISE frames are effectively sent on +# two streams. +# +# The _transitions dictionary contains a mapping of tuples of +# (state, input) to tuples of (side_effect_function, end_state). This +# map contains all allowed transitions: anything not in this map is +# invalid and immediately causes a transition to ``closed``. +_transitions = { + # State: idle + (StreamState.IDLE, StreamInputs.SEND_HEADERS): + (H2StreamStateMachine.request_sent, StreamState.OPEN), + (StreamState.IDLE, StreamInputs.RECV_HEADERS): + (H2StreamStateMachine.request_received, StreamState.OPEN), + (StreamState.IDLE, StreamInputs.RECV_DATA): + (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), + (StreamState.IDLE, StreamInputs.SEND_PUSH_PROMISE): + (H2StreamStateMachine.send_new_pushed_stream, + StreamState.RESERVED_LOCAL), + (StreamState.IDLE, StreamInputs.RECV_PUSH_PROMISE): + (H2StreamStateMachine.recv_new_pushed_stream, + StreamState.RESERVED_REMOTE), + (StreamState.IDLE, StreamInputs.RECV_ALTERNATIVE_SERVICE): + (None, StreamState.IDLE), + (StreamState.IDLE, StreamInputs.UPGRADE_CLIENT): + (H2StreamStateMachine.request_sent, StreamState.HALF_CLOSED_LOCAL), + (StreamState.IDLE, StreamInputs.UPGRADE_SERVER): + (H2StreamStateMachine.request_received, + StreamState.HALF_CLOSED_REMOTE), + + # State: reserved local + (StreamState.RESERVED_LOCAL, StreamInputs.SEND_HEADERS): + (H2StreamStateMachine.response_sent, StreamState.HALF_CLOSED_REMOTE), + (StreamState.RESERVED_LOCAL, StreamInputs.RECV_DATA): + (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), + (StreamState.RESERVED_LOCAL, StreamInputs.SEND_WINDOW_UPDATE): + (None, StreamState.RESERVED_LOCAL), + (StreamState.RESERVED_LOCAL, StreamInputs.RECV_WINDOW_UPDATE): + (H2StreamStateMachine.window_updated, StreamState.RESERVED_LOCAL), + (StreamState.RESERVED_LOCAL, StreamInputs.SEND_RST_STREAM): + (H2StreamStateMachine.send_reset_stream, StreamState.CLOSED), + (StreamState.RESERVED_LOCAL, StreamInputs.RECV_RST_STREAM): + (H2StreamStateMachine.stream_reset, StreamState.CLOSED), + (StreamState.RESERVED_LOCAL, StreamInputs.SEND_ALTERNATIVE_SERVICE): + (H2StreamStateMachine.send_alt_svc, StreamState.RESERVED_LOCAL), + (StreamState.RESERVED_LOCAL, StreamInputs.RECV_ALTERNATIVE_SERVICE): + (None, StreamState.RESERVED_LOCAL), + + # State: reserved remote + (StreamState.RESERVED_REMOTE, StreamInputs.RECV_HEADERS): + (H2StreamStateMachine.response_received, + StreamState.HALF_CLOSED_LOCAL), + (StreamState.RESERVED_REMOTE, StreamInputs.RECV_DATA): + (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), + (StreamState.RESERVED_REMOTE, StreamInputs.SEND_WINDOW_UPDATE): + (None, StreamState.RESERVED_REMOTE), + (StreamState.RESERVED_REMOTE, StreamInputs.RECV_WINDOW_UPDATE): + (H2StreamStateMachine.window_updated, StreamState.RESERVED_REMOTE), + (StreamState.RESERVED_REMOTE, StreamInputs.SEND_RST_STREAM): + (H2StreamStateMachine.send_reset_stream, StreamState.CLOSED), + (StreamState.RESERVED_REMOTE, StreamInputs.RECV_RST_STREAM): + (H2StreamStateMachine.stream_reset, StreamState.CLOSED), + (StreamState.RESERVED_REMOTE, StreamInputs.RECV_ALTERNATIVE_SERVICE): + (H2StreamStateMachine.recv_alt_svc, StreamState.RESERVED_REMOTE), + + # State: open + (StreamState.OPEN, StreamInputs.SEND_HEADERS): + (H2StreamStateMachine.response_sent, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.RECV_HEADERS): + (H2StreamStateMachine.response_received, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.SEND_DATA): + (None, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.RECV_DATA): + (H2StreamStateMachine.data_received, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.SEND_END_STREAM): + (None, StreamState.HALF_CLOSED_LOCAL), + (StreamState.OPEN, StreamInputs.RECV_END_STREAM): + (H2StreamStateMachine.stream_half_closed, + StreamState.HALF_CLOSED_REMOTE), + (StreamState.OPEN, StreamInputs.SEND_WINDOW_UPDATE): + (None, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.RECV_WINDOW_UPDATE): + (H2StreamStateMachine.window_updated, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.SEND_RST_STREAM): + (H2StreamStateMachine.send_reset_stream, StreamState.CLOSED), + (StreamState.OPEN, StreamInputs.RECV_RST_STREAM): + (H2StreamStateMachine.stream_reset, StreamState.CLOSED), + (StreamState.OPEN, StreamInputs.SEND_PUSH_PROMISE): + (H2StreamStateMachine.send_push_promise, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.RECV_PUSH_PROMISE): + (H2StreamStateMachine.recv_push_promise, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.SEND_INFORMATIONAL_HEADERS): + (H2StreamStateMachine.send_informational_response, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.RECV_INFORMATIONAL_HEADERS): + (H2StreamStateMachine.recv_informational_response, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.SEND_ALTERNATIVE_SERVICE): + (H2StreamStateMachine.send_alt_svc, StreamState.OPEN), + (StreamState.OPEN, StreamInputs.RECV_ALTERNATIVE_SERVICE): + (H2StreamStateMachine.recv_alt_svc, StreamState.OPEN), + + # State: half-closed remote + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_HEADERS): + (H2StreamStateMachine.response_sent, StreamState.HALF_CLOSED_REMOTE), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_HEADERS): + (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_DATA): + (None, StreamState.HALF_CLOSED_REMOTE), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_DATA): + (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_END_STREAM): + (H2StreamStateMachine.send_end_stream, StreamState.CLOSED), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_WINDOW_UPDATE): + (None, StreamState.HALF_CLOSED_REMOTE), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_WINDOW_UPDATE): + (H2StreamStateMachine.window_updated, StreamState.HALF_CLOSED_REMOTE), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_RST_STREAM): + (H2StreamStateMachine.send_reset_stream, StreamState.CLOSED), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_RST_STREAM): + (H2StreamStateMachine.stream_reset, StreamState.CLOSED), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_PUSH_PROMISE): + (H2StreamStateMachine.send_push_promise, + StreamState.HALF_CLOSED_REMOTE), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_PUSH_PROMISE): + (H2StreamStateMachine.reset_stream_on_error, StreamState.CLOSED), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_INFORMATIONAL_HEADERS): + (H2StreamStateMachine.send_informational_response, + StreamState.HALF_CLOSED_REMOTE), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.SEND_ALTERNATIVE_SERVICE): + (H2StreamStateMachine.send_alt_svc, StreamState.HALF_CLOSED_REMOTE), + (StreamState.HALF_CLOSED_REMOTE, StreamInputs.RECV_ALTERNATIVE_SERVICE): + (H2StreamStateMachine.recv_alt_svc, StreamState.HALF_CLOSED_REMOTE), + + # State: half-closed local + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_HEADERS): + (H2StreamStateMachine.response_received, + StreamState.HALF_CLOSED_LOCAL), + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_DATA): + (H2StreamStateMachine.data_received, StreamState.HALF_CLOSED_LOCAL), + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_END_STREAM): + (H2StreamStateMachine.stream_ended, StreamState.CLOSED), + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.SEND_WINDOW_UPDATE): + (None, StreamState.HALF_CLOSED_LOCAL), + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_WINDOW_UPDATE): + (H2StreamStateMachine.window_updated, StreamState.HALF_CLOSED_LOCAL), + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.SEND_RST_STREAM): + (H2StreamStateMachine.send_reset_stream, StreamState.CLOSED), + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_RST_STREAM): + (H2StreamStateMachine.stream_reset, StreamState.CLOSED), + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_PUSH_PROMISE): + (H2StreamStateMachine.recv_push_promise, + StreamState.HALF_CLOSED_LOCAL), + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_INFORMATIONAL_HEADERS): + (H2StreamStateMachine.recv_informational_response, + StreamState.HALF_CLOSED_LOCAL), + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.SEND_ALTERNATIVE_SERVICE): + (H2StreamStateMachine.send_alt_svc, StreamState.HALF_CLOSED_LOCAL), + (StreamState.HALF_CLOSED_LOCAL, StreamInputs.RECV_ALTERNATIVE_SERVICE): + (H2StreamStateMachine.recv_alt_svc, StreamState.HALF_CLOSED_LOCAL), + + # State: closed + (StreamState.CLOSED, StreamInputs.RECV_END_STREAM): + (None, StreamState.CLOSED), + (StreamState.CLOSED, StreamInputs.RECV_ALTERNATIVE_SERVICE): + (None, StreamState.CLOSED), + + # RFC 7540 Section 5.1 defines how the end point should react when + # receiving a frame on a closed stream with the following statements: + # + # > An endpoint that receives any frame other than PRIORITY after receiving + # > a RST_STREAM MUST treat that as a stream error of type STREAM_CLOSED. + # > An endpoint that receives any frames after receiving a frame with the + # > END_STREAM flag set MUST treat that as a connection error of type + # > STREAM_CLOSED. + (StreamState.CLOSED, StreamInputs.RECV_HEADERS): + (H2StreamStateMachine.recv_on_closed_stream, StreamState.CLOSED), + (StreamState.CLOSED, StreamInputs.RECV_DATA): + (H2StreamStateMachine.recv_on_closed_stream, StreamState.CLOSED), + + # > WINDOW_UPDATE or RST_STREAM frames can be received in this state + # > for a short period after a DATA or HEADERS frame containing a + # > END_STREAM flag is sent, as instructed in RFC 7540 Section 5.1. But we + # > don't have access to a clock so we just always allow it. + (StreamState.CLOSED, StreamInputs.RECV_WINDOW_UPDATE): + (None, StreamState.CLOSED), + (StreamState.CLOSED, StreamInputs.RECV_RST_STREAM): + (None, StreamState.CLOSED), + + # > A receiver MUST treat the receipt of a PUSH_PROMISE on a stream that is + # > neither "open" nor "half-closed (local)" as a connection error of type + # > PROTOCOL_ERROR. + (StreamState.CLOSED, StreamInputs.RECV_PUSH_PROMISE): + (H2StreamStateMachine.recv_push_on_closed_stream, StreamState.CLOSED), + + # Also, users should be forbidden from sending on closed streams. + (StreamState.CLOSED, StreamInputs.SEND_HEADERS): + (H2StreamStateMachine.send_on_closed_stream, StreamState.CLOSED), + (StreamState.CLOSED, StreamInputs.SEND_PUSH_PROMISE): + (H2StreamStateMachine.send_push_on_closed_stream, StreamState.CLOSED), + (StreamState.CLOSED, StreamInputs.SEND_RST_STREAM): + (H2StreamStateMachine.send_on_closed_stream, StreamState.CLOSED), + (StreamState.CLOSED, StreamInputs.SEND_DATA): + (H2StreamStateMachine.send_on_closed_stream, StreamState.CLOSED), + (StreamState.CLOSED, StreamInputs.SEND_WINDOW_UPDATE): + (H2StreamStateMachine.send_on_closed_stream, StreamState.CLOSED), + (StreamState.CLOSED, StreamInputs.SEND_END_STREAM): + (H2StreamStateMachine.send_on_closed_stream, StreamState.CLOSED), +} + + +class H2Stream(object): + """ + A low-level HTTP/2 stream object. This handles building and receiving + frames and maintains per-stream state. + + This wraps a HTTP/2 Stream state machine implementation, ensuring that + frames can only be sent/received when the stream is in a valid state. + Attempts to create frames that cannot be sent will raise a + ``ProtocolError``. + """ + def __init__(self, + stream_id, + config, + inbound_window_size, + outbound_window_size): + self.state_machine = H2StreamStateMachine(stream_id) + self.stream_id = stream_id + self.max_outbound_frame_size = None + self.request_method = None + + # The current value of the outbound stream flow control window + self.outbound_flow_control_window = outbound_window_size + + # The flow control manager. + self._inbound_window_manager = WindowManager(inbound_window_size) + + # The expected content length, if any. + self._expected_content_length = None + + # The actual received content length. Always tracked. + self._actual_content_length = 0 + + # The authority we believe this stream belongs to. + self._authority = None + + # The configuration for this stream. + self.config = config + + def __repr__(self): + return "<%s id:%d state:%r>" % ( + type(self).__name__, + self.stream_id, + self.state_machine.state + ) + + @property + def inbound_flow_control_window(self): + """ + The size of the inbound flow control window for the stream. This is + rarely publicly useful: instead, use :meth:`remote_flow_control_window + `. This shortcut is + largely present to provide a shortcut to this data. + """ + return self._inbound_window_manager.current_window_size + + @property + def open(self): + """ + Whether the stream is 'open' in any sense: that is, whether it counts + against the number of concurrent streams. + """ + # RFC 7540 Section 5.1.2 defines 'open' for this purpose to mean either + # the OPEN state or either of the HALF_CLOSED states. Perplexingly, + # this excludes the reserved states. + # For more detail on why we're doing this in this slightly weird way, + # see the comment on ``STREAM_OPEN`` at the top of the file. + return STREAM_OPEN[self.state_machine.state] + + @property + def closed(self): + """ + Whether the stream is closed. + """ + return self.state_machine.state == StreamState.CLOSED + + @property + def closed_by(self): + """ + Returns how the stream was closed, as one of StreamClosedBy. + """ + return self.state_machine.stream_closed_by + + def upgrade(self, client_side): + """ + Called by the connection to indicate that this stream is the initial + request/response of an upgraded connection. Places the stream into an + appropriate state. + """ + self.config.logger.debug("Upgrading %r", self) + + assert self.stream_id == 1 + input_ = ( + StreamInputs.UPGRADE_CLIENT if client_side + else StreamInputs.UPGRADE_SERVER + ) + + # This may return events, we deliberately don't want them. + self.state_machine.process_input(input_) + return + + def send_headers(self, headers, encoder, end_stream=False): + """ + Returns a list of HEADERS/CONTINUATION frames to emit as either headers + or trailers. + """ + self.config.logger.debug("Send headers %s on %r", headers, self) + + # Because encoding headers makes an irreversible change to the header + # compression context, we make the state transition before we encode + # them. + + # First, check if we're a client. If we are, no problem: if we aren't, + # we need to scan the header block to see if this is an informational + # response. + input_ = StreamInputs.SEND_HEADERS + if ((not self.state_machine.client) and + is_informational_response(headers)): + if end_stream: + raise ProtocolError( + "Cannot set END_STREAM on informational responses." + ) + + input_ = StreamInputs.SEND_INFORMATIONAL_HEADERS + + events = self.state_machine.process_input(input_) + + hf = HeadersFrame(self.stream_id) + hdr_validation_flags = self._build_hdr_validation_flags(events) + frames = self._build_headers_frames( + headers, encoder, hf, hdr_validation_flags + ) + + if end_stream: + # Not a bug: the END_STREAM flag is valid on the initial HEADERS + # frame, not the CONTINUATION frames that follow. + self.state_machine.process_input(StreamInputs.SEND_END_STREAM) + frames[0].flags.add('END_STREAM') + + if self.state_machine.trailers_sent and not end_stream: + raise ProtocolError("Trailers must have END_STREAM set.") + + if self.state_machine.client and self._authority is None: + self._authority = authority_from_headers(headers) + + # store request method for _initialize_content_length + self.request_method = extract_method_header(headers) + + return frames + + def push_stream_in_band(self, related_stream_id, headers, encoder): + """ + Returns a list of PUSH_PROMISE/CONTINUATION frames to emit as a pushed + stream header. Called on the stream that has the PUSH_PROMISE frame + sent on it. + """ + self.config.logger.debug("Push stream %r", self) + + # Because encoding headers makes an irreversible change to the header + # compression context, we make the state transition *first*. + + events = self.state_machine.process_input( + StreamInputs.SEND_PUSH_PROMISE + ) + + ppf = PushPromiseFrame(self.stream_id) + ppf.promised_stream_id = related_stream_id + hdr_validation_flags = self._build_hdr_validation_flags(events) + frames = self._build_headers_frames( + headers, encoder, ppf, hdr_validation_flags + ) + + return frames + + def locally_pushed(self): + """ + Mark this stream as one that was pushed by this peer. Must be called + immediately after initialization. Sends no frames, simply updates the + state machine. + """ + # This does not trigger any events. + events = self.state_machine.process_input( + StreamInputs.SEND_PUSH_PROMISE + ) + assert not events + return [] + + def send_data(self, data, end_stream=False, pad_length=None): + """ + Prepare some data frames. Optionally end the stream. + + .. warning:: Does not perform flow control checks. + """ + self.config.logger.debug( + "Send data on %r with end stream set to %s", self, end_stream + ) + + self.state_machine.process_input(StreamInputs.SEND_DATA) + + df = DataFrame(self.stream_id) + df.data = data + if end_stream: + self.state_machine.process_input(StreamInputs.SEND_END_STREAM) + df.flags.add('END_STREAM') + if pad_length is not None: + df.flags.add('PADDED') + df.pad_length = pad_length + + # Subtract flow_controlled_length to account for possible padding + self.outbound_flow_control_window -= df.flow_controlled_length + assert self.outbound_flow_control_window >= 0 + + return [df] + + def end_stream(self): + """ + End a stream without sending data. + """ + self.config.logger.debug("End stream %r", self) + + self.state_machine.process_input(StreamInputs.SEND_END_STREAM) + df = DataFrame(self.stream_id) + df.flags.add('END_STREAM') + return [df] + + def advertise_alternative_service(self, field_value): + """ + Advertise an RFC 7838 alternative service. The semantics of this are + better documented in the ``H2Connection`` class. + """ + self.config.logger.debug( + "Advertise alternative service of %r for %r", field_value, self + ) + self.state_machine.process_input(StreamInputs.SEND_ALTERNATIVE_SERVICE) + asf = AltSvcFrame(self.stream_id) + asf.field = field_value + return [asf] + + def increase_flow_control_window(self, increment): + """ + Increase the size of the flow control window for the remote side. + """ + self.config.logger.debug( + "Increase flow control window for %r by %d", + self, increment + ) + self.state_machine.process_input(StreamInputs.SEND_WINDOW_UPDATE) + self._inbound_window_manager.window_opened(increment) + + wuf = WindowUpdateFrame(self.stream_id) + wuf.window_increment = increment + return [wuf] + + def receive_push_promise_in_band(self, + promised_stream_id, + headers, + header_encoding): + """ + Receives a push promise frame sent on this stream, pushing a remote + stream. This is called on the stream that has the PUSH_PROMISE sent + on it. + """ + self.config.logger.debug( + "Receive Push Promise on %r for remote stream %d", + self, promised_stream_id + ) + events = self.state_machine.process_input( + StreamInputs.RECV_PUSH_PROMISE + ) + events[0].pushed_stream_id = promised_stream_id + + hdr_validation_flags = self._build_hdr_validation_flags(events) + events[0].headers = self._process_received_headers( + headers, hdr_validation_flags, header_encoding + ) + return [], events + + def remotely_pushed(self, pushed_headers): + """ + Mark this stream as one that was pushed by the remote peer. Must be + called immediately after initialization. Sends no frames, simply + updates the state machine. + """ + self.config.logger.debug("%r pushed by remote peer", self) + events = self.state_machine.process_input( + StreamInputs.RECV_PUSH_PROMISE + ) + self._authority = authority_from_headers(pushed_headers) + return [], events + + def receive_headers(self, headers, end_stream, header_encoding): + """ + Receive a set of headers (or trailers). + """ + if is_informational_response(headers): + if end_stream: + raise ProtocolError( + "Cannot set END_STREAM on informational responses" + ) + input_ = StreamInputs.RECV_INFORMATIONAL_HEADERS + else: + input_ = StreamInputs.RECV_HEADERS + + events = self.state_machine.process_input(input_) + + if end_stream: + es_events = self.state_machine.process_input( + StreamInputs.RECV_END_STREAM + ) + events[0].stream_ended = es_events[0] + events += es_events + + self._initialize_content_length(headers) + + if isinstance(events[0], TrailersReceived): + if not end_stream: + raise ProtocolError("Trailers must have END_STREAM set") + + hdr_validation_flags = self._build_hdr_validation_flags(events) + events[0].headers = self._process_received_headers( + headers, hdr_validation_flags, header_encoding + ) + return [], events + + def receive_data(self, data, end_stream, flow_control_len): + """ + Receive some data. + """ + self.config.logger.debug( + "Receive data on %r with end stream %s and flow control length " + "set to %d", self, end_stream, flow_control_len + ) + events = self.state_machine.process_input(StreamInputs.RECV_DATA) + self._inbound_window_manager.window_consumed(flow_control_len) + self._track_content_length(len(data), end_stream) + + if end_stream: + es_events = self.state_machine.process_input( + StreamInputs.RECV_END_STREAM + ) + events[0].stream_ended = es_events[0] + events.extend(es_events) + + events[0].data = data + events[0].flow_controlled_length = flow_control_len + return [], events + + def receive_window_update(self, increment): + """ + Handle a WINDOW_UPDATE increment. + """ + self.config.logger.debug( + "Receive Window Update on %r for increment of %d", + self, increment + ) + events = self.state_machine.process_input( + StreamInputs.RECV_WINDOW_UPDATE + ) + frames = [] + + # If we encounter a problem with incrementing the flow control window, + # this should be treated as a *stream* error, not a *connection* error. + # That means we need to catch the error and forcibly close the stream. + if events: + events[0].delta = increment + try: + self.outbound_flow_control_window = guard_increment_window( + self.outbound_flow_control_window, + increment + ) + except FlowControlError: + # Ok, this is bad. We're going to need to perform a local + # reset. + event = StreamReset() + event.stream_id = self.stream_id + event.error_code = ErrorCodes.FLOW_CONTROL_ERROR + event.remote_reset = False + + events = [event] + frames = self.reset_stream(event.error_code) + + return frames, events + + def receive_continuation(self): + """ + A naked CONTINUATION frame has been received. This is always an error, + but the type of error it is depends on the state of the stream and must + transition the state of the stream, so we need to handle it. + """ + self.config.logger.debug("Receive Continuation frame on %r", self) + self.state_machine.process_input( + StreamInputs.RECV_CONTINUATION + ) + assert False, "Should not be reachable" + + def receive_alt_svc(self, frame): + """ + An Alternative Service frame was received on the stream. This frame + inherits the origin associated with this stream. + """ + self.config.logger.debug( + "Receive Alternative Service frame on stream %r", self + ) + + # If the origin is present, RFC 7838 says we have to ignore it. + if frame.origin: + return [], [] + + events = self.state_machine.process_input( + StreamInputs.RECV_ALTERNATIVE_SERVICE + ) + + # There are lots of situations where we want to ignore the ALTSVC + # frame. If we need to pay attention, we'll have an event and should + # fill it out. + if events: + assert isinstance(events[0], AlternativeServiceAvailable) + events[0].origin = self._authority + events[0].field_value = frame.field + + return [], events + + def reset_stream(self, error_code=0): + """ + Close the stream locally. Reset the stream with an error code. + """ + self.config.logger.debug( + "Local reset %r with error code: %d", self, error_code + ) + self.state_machine.process_input(StreamInputs.SEND_RST_STREAM) + + rsf = RstStreamFrame(self.stream_id) + rsf.error_code = error_code + return [rsf] + + def stream_reset(self, frame): + """ + Handle a stream being reset remotely. + """ + self.config.logger.debug( + "Remote reset %r with error code: %d", self, frame.error_code + ) + events = self.state_machine.process_input(StreamInputs.RECV_RST_STREAM) + + if events: + # We don't fire an event if this stream is already closed. + events[0].error_code = _error_code_from_int(frame.error_code) + + return [], events + + def acknowledge_received_data(self, acknowledged_size): + """ + The user has informed us that they've processed some amount of data + that was received on this stream. Pass that to the window manager and + potentially return some WindowUpdate frames. + """ + self.config.logger.debug( + "Acknowledge received data with size %d on %r", + acknowledged_size, self + ) + increment = self._inbound_window_manager.process_bytes( + acknowledged_size + ) + if increment: + f = WindowUpdateFrame(self.stream_id) + f.window_increment = increment + return [f] + + return [] + + def _build_hdr_validation_flags(self, events): + """ + Constructs a set of header validation flags for use when normalizing + and validating header blocks. + """ + is_trailer = isinstance( + events[0], (_TrailersSent, TrailersReceived) + ) + is_response_header = isinstance( + events[0], + ( + _ResponseSent, + ResponseReceived, + InformationalResponseReceived + ) + ) + is_push_promise = isinstance( + events[0], (PushedStreamReceived, _PushedRequestSent) + ) + + return HeaderValidationFlags( + is_client=self.state_machine.client, + is_trailer=is_trailer, + is_response_header=is_response_header, + is_push_promise=is_push_promise, + ) + + def _build_headers_frames(self, + headers, + encoder, + first_frame, + hdr_validation_flags): + """ + Helper method to build headers or push promise frames. + """ + # We need to lowercase the header names, and to ensure that secure + # header fields are kept out of compression contexts. + if self.config.normalize_outbound_headers: + headers = normalize_outbound_headers( + headers, hdr_validation_flags + ) + if self.config.validate_outbound_headers: + headers = validate_outbound_headers( + headers, hdr_validation_flags + ) + + encoded_headers = encoder.encode(headers) + + # Slice into blocks of max_outbound_frame_size. Be careful with this: + # it only works right because we never send padded frames or priority + # information on the frames. Revisit this if we do. + header_blocks = [ + encoded_headers[i:i+self.max_outbound_frame_size] + for i in range( + 0, len(encoded_headers), self.max_outbound_frame_size + ) + ] + + frames = [] + first_frame.data = header_blocks[0] + frames.append(first_frame) + + for block in header_blocks[1:]: + cf = ContinuationFrame(self.stream_id) + cf.data = block + frames.append(cf) + + frames[-1].flags.add('END_HEADERS') + return frames + + def _process_received_headers(self, + headers, + header_validation_flags, + header_encoding): + """ + When headers have been received from the remote peer, run a processing + pipeline on them to transform them into the appropriate form for + attaching to an event. + """ + if self.config.normalize_inbound_headers: + headers = normalize_inbound_headers( + headers, header_validation_flags + ) + + if self.config.validate_inbound_headers: + headers = validate_headers(headers, header_validation_flags) + + if header_encoding: + headers = _decode_headers(headers, header_encoding) + + # The above steps are all generators, so we need to concretize the + # headers now. + return list(headers) + + def _initialize_content_length(self, headers): + """ + Checks the headers for a content-length header and initializes the + _expected_content_length field from it. It's not an error for no + Content-Length header to be present. + """ + if self.request_method == b'HEAD': + self._expected_content_length = 0 + return + + for n, v in headers: + if n == b'content-length': + try: + self._expected_content_length = int(v, 10) + except ValueError: + raise ProtocolError( + "Invalid content-length header: %s" % v + ) + + return + + def _track_content_length(self, length, end_stream): + """ + Update the expected content length in response to data being received. + Validates that the appropriate amount of data is sent. Always updates + the received data, but only validates the length against the + content-length header if one was sent. + + :param length: The length of the body chunk received. + :param end_stream: If this is the last body chunk received. + """ + self._actual_content_length += length + actual = self._actual_content_length + expected = self._expected_content_length + + if expected is not None: + if expected < actual: + raise InvalidBodyLengthError(expected, actual) + + if end_stream and expected != actual: + raise InvalidBodyLengthError(expected, actual) + + def _inbound_flow_control_change_from_settings(self, delta): + """ + We changed SETTINGS_INITIAL_WINDOW_SIZE, which means we need to + update the target window size for flow control. For our flow control + strategy, this means we need to do two things: we need to adjust the + current window size, but we also need to set the target maximum window + size to the new value. + """ + new_max_size = self._inbound_window_manager.max_window_size + delta + self._inbound_window_manager.window_opened(delta) + self._inbound_window_manager.max_window_size = new_max_size + + +def _decode_headers(headers, encoding): + """ + Given an iterable of header two-tuples and an encoding, decodes those + headers using that encoding while preserving the type of the header tuple. + This ensures that the use of ``HeaderTuple`` is preserved. + """ + for header in headers: + # This function expects to work on decoded headers, which are always + # HeaderTuple objects. + assert isinstance(header, HeaderTuple) + + name, value = header + name = name.decode(encoding) + value = value.decode(encoding) + yield header.__class__(name, value) diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/utilities.py b/testing/web-platform/tests/tools/third_party/h2/h2/utilities.py new file mode 100644 index 0000000000..06c916eea7 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/utilities.py @@ -0,0 +1,660 @@ +# -*- coding: utf-8 -*- +""" +h2/utilities +~~~~~~~~~~~~ + +Utility functions that do not belong in a separate module. +""" +import collections +import re +from string import whitespace +import sys + +from hpack import HeaderTuple, NeverIndexedHeaderTuple + +from .exceptions import ProtocolError, FlowControlError + +UPPER_RE = re.compile(b"[A-Z]") + +# A set of headers that are hop-by-hop or connection-specific and thus +# forbidden in HTTP/2. This list comes from RFC 7540 § 8.1.2.2. +CONNECTION_HEADERS = frozenset([ + b'connection', u'connection', + b'proxy-connection', u'proxy-connection', + b'keep-alive', u'keep-alive', + b'transfer-encoding', u'transfer-encoding', + b'upgrade', u'upgrade', +]) + + +_ALLOWED_PSEUDO_HEADER_FIELDS = frozenset([ + b':method', u':method', + b':scheme', u':scheme', + b':authority', u':authority', + b':path', u':path', + b':status', u':status', + b':protocol', u':protocol', +]) + + +_SECURE_HEADERS = frozenset([ + # May have basic credentials which are vulnerable to dictionary attacks. + b'authorization', u'authorization', + b'proxy-authorization', u'proxy-authorization', +]) + + +_REQUEST_ONLY_HEADERS = frozenset([ + b':scheme', u':scheme', + b':path', u':path', + b':authority', u':authority', + b':method', u':method', + b':protocol', u':protocol', +]) + + +_RESPONSE_ONLY_HEADERS = frozenset([b':status', u':status']) + + +# A Set of pseudo headers that are only valid if the method is +# CONNECT, see RFC 8441 § 5 +_CONNECT_REQUEST_ONLY_HEADERS = frozenset([b':protocol', u':protocol']) + + +if sys.version_info[0] == 2: # Python 2.X + _WHITESPACE = frozenset(whitespace) +else: # Python 3.3+ + _WHITESPACE = frozenset(map(ord, whitespace)) + + +def _secure_headers(headers, hdr_validation_flags): + """ + Certain headers are at risk of being attacked during the header compression + phase, and so need to be kept out of header compression contexts. This + function automatically transforms certain specific headers into HPACK + never-indexed fields to ensure they don't get added to header compression + contexts. + + This function currently implements two rules: + + - 'authorization' and 'proxy-authorization' fields are automatically made + never-indexed. + - Any 'cookie' header field shorter than 20 bytes long is made + never-indexed. + + These fields are the most at-risk. These rules are inspired by Firefox + and nghttp2. + """ + for header in headers: + if header[0] in _SECURE_HEADERS: + yield NeverIndexedHeaderTuple(*header) + elif header[0] in (b'cookie', u'cookie') and len(header[1]) < 20: + yield NeverIndexedHeaderTuple(*header) + else: + yield header + + +def extract_method_header(headers): + """ + Extracts the request method from the headers list. + """ + for k, v in headers: + if k in (b':method', u':method'): + if not isinstance(v, bytes): + return v.encode('utf-8') + else: + return v + + +def is_informational_response(headers): + """ + Searches a header block for a :status header to confirm that a given + collection of headers are an informational response. Assumes the header + block is well formed: that is, that the HTTP/2 special headers are first + in the block, and so that it can stop looking when it finds the first + header field whose name does not begin with a colon. + + :param headers: The HTTP/2 header block. + :returns: A boolean indicating if this is an informational response. + """ + for n, v in headers: + if isinstance(n, bytes): + sigil = b':' + status = b':status' + informational_start = b'1' + else: + sigil = u':' + status = u':status' + informational_start = u'1' + + # If we find a non-special header, we're done here: stop looping. + if not n.startswith(sigil): + return False + + # This isn't the status header, bail. + if n != status: + continue + + # If the first digit is a 1, we've got informational headers. + return v.startswith(informational_start) + + +def guard_increment_window(current, increment): + """ + Increments a flow control window, guarding against that window becoming too + large. + + :param current: The current value of the flow control window. + :param increment: The increment to apply to that window. + :returns: The new value of the window. + :raises: ``FlowControlError`` + """ + # The largest value the flow control window may take. + LARGEST_FLOW_CONTROL_WINDOW = 2**31 - 1 + + new_size = current + increment + + if new_size > LARGEST_FLOW_CONTROL_WINDOW: + raise FlowControlError( + "May not increment flow control window past %d" % + LARGEST_FLOW_CONTROL_WINDOW + ) + + return new_size + + +def authority_from_headers(headers): + """ + Given a header set, searches for the authority header and returns the + value. + + Note that this doesn't terminate early, so should only be called if the + headers are for a client request. Otherwise, will loop over the entire + header set, which is potentially unwise. + + :param headers: The HTTP header set. + :returns: The value of the authority header, or ``None``. + :rtype: ``bytes`` or ``None``. + """ + for n, v in headers: + # This gets run against headers that come both from HPACK and from the + # user, so we may have unicode floating around in here. We only want + # bytes. + if n in (b':authority', u':authority'): + return v.encode('utf-8') if not isinstance(v, bytes) else v + + return None + + +# Flags used by the validate_headers pipeline to determine which checks +# should be applied to a given set of headers. +HeaderValidationFlags = collections.namedtuple( + 'HeaderValidationFlags', + ['is_client', 'is_trailer', 'is_response_header', 'is_push_promise'] +) + + +def validate_headers(headers, hdr_validation_flags): + """ + Validates a header sequence against a set of constraints from RFC 7540. + + :param headers: The HTTP header set. + :param hdr_validation_flags: An instance of HeaderValidationFlags. + """ + # This validation logic is built on a sequence of generators that are + # iterated over to provide the final header list. This reduces some of the + # overhead of doing this checking. However, it's worth noting that this + # checking remains somewhat expensive, and attempts should be made wherever + # possible to reduce the time spent doing them. + # + # For example, we avoid tuple upacking in loops because it represents a + # fixed cost that we don't want to spend, instead indexing into the header + # tuples. + headers = _reject_uppercase_header_fields( + headers, hdr_validation_flags + ) + headers = _reject_surrounding_whitespace( + headers, hdr_validation_flags + ) + headers = _reject_te( + headers, hdr_validation_flags + ) + headers = _reject_connection_header( + headers, hdr_validation_flags + ) + headers = _reject_pseudo_header_fields( + headers, hdr_validation_flags + ) + headers = _check_host_authority_header( + headers, hdr_validation_flags + ) + headers = _check_path_header(headers, hdr_validation_flags) + + return headers + + +def _reject_uppercase_header_fields(headers, hdr_validation_flags): + """ + Raises a ProtocolError if any uppercase character is found in a header + block. + """ + for header in headers: + if UPPER_RE.search(header[0]): + raise ProtocolError( + "Received uppercase header name %s." % header[0]) + yield header + + +def _reject_surrounding_whitespace(headers, hdr_validation_flags): + """ + Raises a ProtocolError if any header name or value is surrounded by + whitespace characters. + """ + # For compatibility with RFC 7230 header fields, we need to allow the field + # value to be an empty string. This is ludicrous, but technically allowed. + # The field name may not be empty, though, so we can safely assume that it + # must have at least one character in it and throw exceptions if it + # doesn't. + for header in headers: + if header[0][0] in _WHITESPACE or header[0][-1] in _WHITESPACE: + raise ProtocolError( + "Received header name surrounded by whitespace %r" % header[0]) + if header[1] and ((header[1][0] in _WHITESPACE) or + (header[1][-1] in _WHITESPACE)): + raise ProtocolError( + "Received header value surrounded by whitespace %r" % header[1] + ) + yield header + + +def _reject_te(headers, hdr_validation_flags): + """ + Raises a ProtocolError if the TE header is present in a header block and + its value is anything other than "trailers". + """ + for header in headers: + if header[0] in (b'te', u'te'): + if header[1].lower() not in (b'trailers', u'trailers'): + raise ProtocolError( + "Invalid value for Transfer-Encoding header: %s" % + header[1] + ) + + yield header + + +def _reject_connection_header(headers, hdr_validation_flags): + """ + Raises a ProtocolError if the Connection header is present in a header + block. + """ + for header in headers: + if header[0] in CONNECTION_HEADERS: + raise ProtocolError( + "Connection-specific header field present: %s." % header[0] + ) + + yield header + + +def _custom_startswith(test_string, bytes_prefix, unicode_prefix): + """ + Given a string that might be a bytestring or a Unicode string, + return True if it starts with the appropriate prefix. + """ + if isinstance(test_string, bytes): + return test_string.startswith(bytes_prefix) + else: + return test_string.startswith(unicode_prefix) + + +def _assert_header_in_set(string_header, bytes_header, header_set): + """ + Given a set of header names, checks whether the string or byte version of + the header name is present. Raises a Protocol error with the appropriate + error if it's missing. + """ + if not (string_header in header_set or bytes_header in header_set): + raise ProtocolError( + "Header block missing mandatory %s header" % string_header + ) + + +def _reject_pseudo_header_fields(headers, hdr_validation_flags): + """ + Raises a ProtocolError if duplicate pseudo-header fields are found in a + header block or if a pseudo-header field appears in a block after an + ordinary header field. + + Raises a ProtocolError if pseudo-header fields are found in trailers. + """ + seen_pseudo_header_fields = set() + seen_regular_header = False + method = None + + for header in headers: + if _custom_startswith(header[0], b':', u':'): + if header[0] in seen_pseudo_header_fields: + raise ProtocolError( + "Received duplicate pseudo-header field %s" % header[0] + ) + + seen_pseudo_header_fields.add(header[0]) + + if seen_regular_header: + raise ProtocolError( + "Received pseudo-header field out of sequence: %s" % + header[0] + ) + + if header[0] not in _ALLOWED_PSEUDO_HEADER_FIELDS: + raise ProtocolError( + "Received custom pseudo-header field %s" % header[0] + ) + + if header[0] in (b':method', u':method'): + if not isinstance(header[1], bytes): + method = header[1].encode('utf-8') + else: + method = header[1] + + else: + seen_regular_header = True + + yield header + + # Check the pseudo-headers we got to confirm they're acceptable. + _check_pseudo_header_field_acceptability( + seen_pseudo_header_fields, method, hdr_validation_flags + ) + + +def _check_pseudo_header_field_acceptability(pseudo_headers, + method, + hdr_validation_flags): + """ + Given the set of pseudo-headers present in a header block and the + validation flags, confirms that RFC 7540 allows them. + """ + # Pseudo-header fields MUST NOT appear in trailers - RFC 7540 § 8.1.2.1 + if hdr_validation_flags.is_trailer and pseudo_headers: + raise ProtocolError( + "Received pseudo-header in trailer %s" % pseudo_headers + ) + + # If ':status' pseudo-header is not there in a response header, reject it. + # Similarly, if ':path', ':method', or ':scheme' are not there in a request + # header, reject it. Additionally, if a response contains any request-only + # headers or vice-versa, reject it. + # Relevant RFC section: RFC 7540 § 8.1.2.4 + # https://tools.ietf.org/html/rfc7540#section-8.1.2.4 + if hdr_validation_flags.is_response_header: + _assert_header_in_set(u':status', b':status', pseudo_headers) + invalid_response_headers = pseudo_headers & _REQUEST_ONLY_HEADERS + if invalid_response_headers: + raise ProtocolError( + "Encountered request-only headers %s" % + invalid_response_headers + ) + elif (not hdr_validation_flags.is_response_header and + not hdr_validation_flags.is_trailer): + # This is a request, so we need to have seen :path, :method, and + # :scheme. + _assert_header_in_set(u':path', b':path', pseudo_headers) + _assert_header_in_set(u':method', b':method', pseudo_headers) + _assert_header_in_set(u':scheme', b':scheme', pseudo_headers) + invalid_request_headers = pseudo_headers & _RESPONSE_ONLY_HEADERS + if invalid_request_headers: + raise ProtocolError( + "Encountered response-only headers %s" % + invalid_request_headers + ) + if method != b'CONNECT': + invalid_headers = pseudo_headers & _CONNECT_REQUEST_ONLY_HEADERS + if invalid_headers: + raise ProtocolError( + "Encountered connect-request-only headers %s" % + invalid_headers + ) + + +def _validate_host_authority_header(headers): + """ + Given the :authority and Host headers from a request block that isn't + a trailer, check that: + 1. At least one of these headers is set. + 2. If both headers are set, they match. + + :param headers: The HTTP header set. + :raises: ``ProtocolError`` + """ + # We use None as a sentinel value. Iterate over the list of headers, + # and record the value of these headers (if present). We don't need + # to worry about receiving duplicate :authority headers, as this is + # enforced by the _reject_pseudo_header_fields() pipeline. + # + # TODO: We should also guard against receiving duplicate Host headers, + # and against sending duplicate headers. + authority_header_val = None + host_header_val = None + + for header in headers: + if header[0] in (b':authority', u':authority'): + authority_header_val = header[1] + elif header[0] in (b'host', u'host'): + host_header_val = header[1] + + yield header + + # If we have not-None values for these variables, then we know we saw + # the corresponding header. + authority_present = (authority_header_val is not None) + host_present = (host_header_val is not None) + + # It is an error for a request header block to contain neither + # an :authority header nor a Host header. + if not authority_present and not host_present: + raise ProtocolError( + "Request header block does not have an :authority or Host header." + ) + + # If we receive both headers, they should definitely match. + if authority_present and host_present: + if authority_header_val != host_header_val: + raise ProtocolError( + "Request header block has mismatched :authority and " + "Host headers: %r / %r" + % (authority_header_val, host_header_val) + ) + + +def _check_host_authority_header(headers, hdr_validation_flags): + """ + Raises a ProtocolError if a header block arrives that does not contain an + :authority or a Host header, or if a header block contains both fields, + but their values do not match. + """ + # We only expect to see :authority and Host headers on request header + # blocks that aren't trailers, so skip this validation if this is a + # response header or we're looking at trailer blocks. + skip_validation = ( + hdr_validation_flags.is_response_header or + hdr_validation_flags.is_trailer + ) + if skip_validation: + return headers + + return _validate_host_authority_header(headers) + + +def _check_path_header(headers, hdr_validation_flags): + """ + Raise a ProtocolError if a header block arrives or is sent that contains an + empty :path header. + """ + def inner(): + for header in headers: + if header[0] in (b':path', u':path'): + if not header[1]: + raise ProtocolError("An empty :path header is forbidden") + + yield header + + # We only expect to see :authority and Host headers on request header + # blocks that aren't trailers, so skip this validation if this is a + # response header or we're looking at trailer blocks. + skip_validation = ( + hdr_validation_flags.is_response_header or + hdr_validation_flags.is_trailer + ) + if skip_validation: + return headers + else: + return inner() + + +def _lowercase_header_names(headers, hdr_validation_flags): + """ + Given an iterable of header two-tuples, rebuilds that iterable with the + header names lowercased. This generator produces tuples that preserve the + original type of the header tuple for tuple and any ``HeaderTuple``. + """ + for header in headers: + if isinstance(header, HeaderTuple): + yield header.__class__(header[0].lower(), header[1]) + else: + yield (header[0].lower(), header[1]) + + +def _strip_surrounding_whitespace(headers, hdr_validation_flags): + """ + Given an iterable of header two-tuples, strip both leading and trailing + whitespace from both header names and header values. This generator + produces tuples that preserve the original type of the header tuple for + tuple and any ``HeaderTuple``. + """ + for header in headers: + if isinstance(header, HeaderTuple): + yield header.__class__(header[0].strip(), header[1].strip()) + else: + yield (header[0].strip(), header[1].strip()) + + +def _strip_connection_headers(headers, hdr_validation_flags): + """ + Strip any connection headers as per RFC7540 § 8.1.2.2. + """ + for header in headers: + if header[0] not in CONNECTION_HEADERS: + yield header + + +def _check_sent_host_authority_header(headers, hdr_validation_flags): + """ + Raises an InvalidHeaderBlockError if we try to send a header block + that does not contain an :authority or a Host header, or if + the header block contains both fields, but their values do not match. + """ + # We only expect to see :authority and Host headers on request header + # blocks that aren't trailers, so skip this validation if this is a + # response header or we're looking at trailer blocks. + skip_validation = ( + hdr_validation_flags.is_response_header or + hdr_validation_flags.is_trailer + ) + if skip_validation: + return headers + + return _validate_host_authority_header(headers) + + +def _combine_cookie_fields(headers, hdr_validation_flags): + """ + RFC 7540 § 8.1.2.5 allows HTTP/2 clients to split the Cookie header field, + which must normally appear only once, into multiple fields for better + compression. However, they MUST be joined back up again when received. + This normalization step applies that transform. The side-effect is that + all cookie fields now appear *last* in the header block. + """ + # There is a problem here about header indexing. Specifically, it's + # possible that all these cookies are sent with different header indexing + # values. At this point it shouldn't matter too much, so we apply our own + # logic and make them never-indexed. + cookies = [] + for header in headers: + if header[0] == b'cookie': + cookies.append(header[1]) + else: + yield header + if cookies: + cookie_val = b'; '.join(cookies) + yield NeverIndexedHeaderTuple(b'cookie', cookie_val) + + +def normalize_outbound_headers(headers, hdr_validation_flags): + """ + Normalizes a header sequence that we are about to send. + + :param headers: The HTTP header set. + :param hdr_validation_flags: An instance of HeaderValidationFlags. + """ + headers = _lowercase_header_names(headers, hdr_validation_flags) + headers = _strip_surrounding_whitespace(headers, hdr_validation_flags) + headers = _strip_connection_headers(headers, hdr_validation_flags) + headers = _secure_headers(headers, hdr_validation_flags) + + return headers + + +def normalize_inbound_headers(headers, hdr_validation_flags): + """ + Normalizes a header sequence that we have received. + + :param headers: The HTTP header set. + :param hdr_validation_flags: An instance of HeaderValidationFlags + """ + headers = _combine_cookie_fields(headers, hdr_validation_flags) + return headers + + +def validate_outbound_headers(headers, hdr_validation_flags): + """ + Validates and normalizes a header sequence that we are about to send. + + :param headers: The HTTP header set. + :param hdr_validation_flags: An instance of HeaderValidationFlags. + """ + headers = _reject_te( + headers, hdr_validation_flags + ) + headers = _reject_connection_header( + headers, hdr_validation_flags + ) + headers = _reject_pseudo_header_fields( + headers, hdr_validation_flags + ) + headers = _check_sent_host_authority_header( + headers, hdr_validation_flags + ) + headers = _check_path_header(headers, hdr_validation_flags) + + return headers + + +class SizeLimitDict(collections.OrderedDict): + + def __init__(self, *args, **kwargs): + self._size_limit = kwargs.pop("size_limit", None) + super(SizeLimitDict, self).__init__(*args, **kwargs) + + self._check_size_limit() + + def __setitem__(self, key, value): + super(SizeLimitDict, self).__setitem__(key, value) + + self._check_size_limit() + + def _check_size_limit(self): + if self._size_limit is not None: + while len(self) > self._size_limit: + self.popitem(last=False) diff --git a/testing/web-platform/tests/tools/third_party/h2/h2/windows.py b/testing/web-platform/tests/tools/third_party/h2/h2/windows.py new file mode 100644 index 0000000000..6656975f48 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/h2/windows.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +""" +h2/windows +~~~~~~~~~~ + +Defines tools for managing HTTP/2 flow control windows. + +The objects defined in this module are used to automatically manage HTTP/2 +flow control windows. Specifically, they keep track of what the size of the +window is, how much data has been consumed from that window, and how much data +the user has already used. It then implements a basic algorithm that attempts +to manage the flow control window without user input, trying to ensure that it +does not emit too many WINDOW_UPDATE frames. +""" +from __future__ import division + +from .exceptions import FlowControlError + + +# The largest acceptable value for a HTTP/2 flow control window. +LARGEST_FLOW_CONTROL_WINDOW = 2**31 - 1 + + +class WindowManager(object): + """ + A basic HTTP/2 window manager. + + :param max_window_size: The maximum size of the flow control window. + :type max_window_size: ``int`` + """ + def __init__(self, max_window_size): + assert max_window_size <= LARGEST_FLOW_CONTROL_WINDOW + self.max_window_size = max_window_size + self.current_window_size = max_window_size + self._bytes_processed = 0 + + def window_consumed(self, size): + """ + We have received a certain number of bytes from the remote peer. This + necessarily shrinks the flow control window! + + :param size: The number of flow controlled bytes we received from the + remote peer. + :type size: ``int`` + :returns: Nothing. + :rtype: ``None`` + """ + self.current_window_size -= size + if self.current_window_size < 0: + raise FlowControlError("Flow control window shrunk below 0") + + def window_opened(self, size): + """ + The flow control window has been incremented, either because of manual + flow control management or because of the user changing the flow + control settings. This can have the effect of increasing what we + consider to be the "maximum" flow control window size. + + This does not increase our view of how many bytes have been processed, + only of how much space is in the window. + + :param size: The increment to the flow control window we received. + :type size: ``int`` + :returns: Nothing + :rtype: ``None`` + """ + self.current_window_size += size + + if self.current_window_size > LARGEST_FLOW_CONTROL_WINDOW: + raise FlowControlError( + "Flow control window mustn't exceed %d" % + LARGEST_FLOW_CONTROL_WINDOW + ) + + if self.current_window_size > self.max_window_size: + self.max_window_size = self.current_window_size + + def process_bytes(self, size): + """ + The application has informed us that it has processed a certain number + of bytes. This may cause us to want to emit a window update frame. If + we do want to emit a window update frame, this method will return the + number of bytes that we should increment the window by. + + :param size: The number of flow controlled bytes that the application + has processed. + :type size: ``int`` + :returns: The number of bytes to increment the flow control window by, + or ``None``. + :rtype: ``int`` or ``None`` + """ + self._bytes_processed += size + return self._maybe_update_window() + + def _maybe_update_window(self): + """ + Run the algorithm. + + Our current algorithm can be described like this. + + 1. If no bytes have been processed, we immediately return 0. There is + no meaningful way for us to hand space in the window back to the + remote peer, so let's not even try. + 2. If there is no space in the flow control window, and we have + processed at least 1024 bytes (or 1/4 of the window, if the window + is smaller), we will emit a window update frame. This is to avoid + the risk of blocking a stream altogether. + 3. If there is space in the flow control window, and we have processed + at least 1/2 of the window worth of bytes, we will emit a window + update frame. This is to minimise the number of window update frames + we have to emit. + + In a healthy system with large flow control windows, this will + irregularly emit WINDOW_UPDATE frames. This prevents us starving the + connection by emitting eleventy bajillion WINDOW_UPDATE frames, + especially in situations where the remote peer is sending a lot of very + small DATA frames. + """ + # TODO: Can the window be smaller than 1024 bytes? If not, we can + # streamline this algorithm. + if not self._bytes_processed: + return None + + max_increment = (self.max_window_size - self.current_window_size) + increment = 0 + + # Note that, even though we may increment less than _bytes_processed, + # we still want to set it to zero whenever we emit an increment. This + # is because we'll always increment up to the maximum we can. + if (self.current_window_size == 0) and ( + self._bytes_processed > min(1024, self.max_window_size // 4)): + increment = min(self._bytes_processed, max_increment) + self._bytes_processed = 0 + elif self._bytes_processed >= (self.max_window_size // 2): + increment = min(self._bytes_processed, max_increment) + self._bytes_processed = 0 + + self.current_window_size += increment + return increment diff --git a/testing/web-platform/tests/tools/third_party/h2/setup.cfg b/testing/web-platform/tests/tools/third_party/h2/setup.cfg new file mode 100644 index 0000000000..3670507ff1 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/setup.cfg @@ -0,0 +1,10 @@ +[tool:pytest] +testpaths = test + +[wheel] +universal = 1 + +[egg_info] +tag_build = +tag_date = 0 + diff --git a/testing/web-platform/tests/tools/third_party/h2/setup.py b/testing/web-platform/tests/tools/third_party/h2/setup.py new file mode 100644 index 0000000000..1ce95d5796 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/setup.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import codecs +import os +import re +import sys + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +# Get the version +version_regex = r'__version__ = ["\']([^"\']*)["\']' +with open('h2/__init__.py', 'r') as f: + text = f.read() + match = re.search(version_regex, text) + + if match: + version = match.group(1) + else: + raise RuntimeError("No version number found!") + +# Stealing this from Kenneth Reitz +if sys.argv[-1] == 'publish': + os.system('python setup.py sdist upload') + sys.exit() + +packages = [ + 'h2', +] + +readme = codecs.open('README.rst', encoding='utf-8').read() +history = codecs.open('HISTORY.rst', encoding='utf-8').read() + +setup( + name='h2', + version=version, + description='HTTP/2 State-Machine based protocol implementation', + long_description=u'\n\n'.join([readme, history]), + author='Cory Benfield', + author_email='cory@lukasa.co.uk', + url='https://github.com/python-hyper/hyper-h2', + project_urls={ + 'Documentation': 'https://python-hyper.org/projects/h2', + 'Source': 'https://github.com/python-hyper/hyper-h2', + }, + packages=packages, + package_data={'': ['LICENSE', 'README.rst', 'CONTRIBUTORS.rst', 'HISTORY.rst', 'NOTICES']}, + package_dir={'h2': 'h2'}, + include_package_data=True, + license='MIT License', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + ], + install_requires=[ + 'hyperframe>=5.2.0, <6', + 'hpack>=3.0,<4', + ], + extras_require={ + ':python_version == "2.7"': ['enum34>=1.1.6, <2'], + } +) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/conftest.py b/testing/web-platform/tests/tools/third_party/h2/test/conftest.py new file mode 100644 index 0000000000..c646ad361c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/conftest.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +from hypothesis import settings, HealthCheck + +import pytest +import helpers + +# Set up a CI profile that allows slow example generation. +settings.register_profile( + "travis", + settings(suppress_health_check=[HealthCheck.too_slow]) +) + + +@pytest.fixture +def frame_factory(): + return helpers.FrameFactory() diff --git a/testing/web-platform/tests/tools/third_party/h2/test/coroutine_tests.py b/testing/web-platform/tests/tools/third_party/h2/test/coroutine_tests.py new file mode 100644 index 0000000000..0f48c02d99 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/coroutine_tests.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- +""" +coroutine_tests +~~~~~~~~~~~~~~~ + +This file gives access to a coroutine-based test class. This allows each test +case to be defined as a pair of interacting coroutines, sending data to each +other by yielding the flow of control. + +The advantage of this method is that we avoid the difficulty of using threads +in Python, as well as the pain of using sockets and events to communicate and +organise the communication. This makes the tests entirely deterministic and +makes them behave identically on all platforms, as well as ensuring they both +succeed and fail quickly. +""" +import itertools +import functools + +import pytest + + +class CoroutineTestCase(object): + """ + A base class for tests that use interacting coroutines. + + The run_until_complete method takes a number of coroutines as arguments. + Each one is, in order, passed the output of the previous coroutine until + one is exhausted. If a coroutine does not initially yield data (that is, + its first action is to receive data), the calling code should prime it by + using the 'server' decorator on this class. + """ + def run_until_complete(self, *coroutines): + """ + Executes a set of coroutines that communicate between each other. Each + one is, in order, passed the output of the previous coroutine until + one is exhausted. If a coroutine does not initially yield data (that + is, its first action is to receive data), the calling code should prime + it by using the 'server' decorator on this class. + + Once a coroutine is exhausted, the method performs a final check to + ensure that all other coroutines are exhausted. This ensures that all + assertions in those coroutines got executed. + """ + looping_coroutines = itertools.cycle(coroutines) + data = None + + for coro in looping_coroutines: + try: + data = coro.send(data) + except StopIteration: + break + + for coro in coroutines: + try: + next(coro) + except StopIteration: + continue + else: + pytest.fail("Coroutine %s not exhausted" % coro) + + def server(self, func): + """ + A decorator that marks a test coroutine as a 'server' coroutine: that + is, one whose first action is to consume data, rather than one that + initially emits data. The effect of this decorator is simply to prime + the coroutine. + """ + @functools.wraps(func) + def wrapper(*args, **kwargs): + c = func(*args, **kwargs) + next(c) + return c + + return wrapper diff --git a/testing/web-platform/tests/tools/third_party/h2/test/helpers.py b/testing/web-platform/tests/tools/third_party/h2/test/helpers.py new file mode 100644 index 0000000000..2a4e909321 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/helpers.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +""" +helpers +~~~~~~~ + +This module contains helpers for the h2 tests. +""" +from hyperframe.frame import ( + HeadersFrame, DataFrame, SettingsFrame, WindowUpdateFrame, PingFrame, + GoAwayFrame, RstStreamFrame, PushPromiseFrame, PriorityFrame, + ContinuationFrame, AltSvcFrame +) +from hpack.hpack import Encoder + + +SAMPLE_SETTINGS = { + SettingsFrame.HEADER_TABLE_SIZE: 4096, + SettingsFrame.ENABLE_PUSH: 1, + SettingsFrame.MAX_CONCURRENT_STREAMS: 2, +} + + +class FrameFactory(object): + """ + A class containing lots of helper methods and state to build frames. This + allows test cases to easily build correct HTTP/2 frames to feed to + hyper-h2. + """ + def __init__(self): + self.encoder = Encoder() + + def refresh_encoder(self): + self.encoder = Encoder() + + def preamble(self): + return b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' + + def build_headers_frame(self, + headers, + flags=[], + stream_id=1, + **priority_kwargs): + """ + Builds a single valid headers frame out of the contained headers. + """ + f = HeadersFrame(stream_id) + f.data = self.encoder.encode(headers) + f.flags.add('END_HEADERS') + for flag in flags: + f.flags.add(flag) + + for k, v in priority_kwargs.items(): + setattr(f, k, v) + + return f + + def build_continuation_frame(self, header_block, flags=[], stream_id=1): + """ + Builds a single continuation frame out of the binary header block. + """ + f = ContinuationFrame(stream_id) + f.data = header_block + f.flags = set(flags) + + return f + + def build_data_frame(self, data, flags=None, stream_id=1, padding_len=0): + """ + Builds a single data frame out of a chunk of data. + """ + flags = set(flags) if flags is not None else set() + f = DataFrame(stream_id) + f.data = data + f.flags = flags + + if padding_len: + flags.add('PADDED') + f.pad_length = padding_len + + return f + + def build_settings_frame(self, settings, ack=False): + """ + Builds a single settings frame. + """ + f = SettingsFrame(0) + if ack: + f.flags.add('ACK') + + f.settings = settings + return f + + def build_window_update_frame(self, stream_id, increment): + """ + Builds a single WindowUpdate frame. + """ + f = WindowUpdateFrame(stream_id) + f.window_increment = increment + return f + + def build_ping_frame(self, ping_data, flags=None): + """ + Builds a single Ping frame. + """ + f = PingFrame(0) + f.opaque_data = ping_data + if flags: + f.flags = set(flags) + + return f + + def build_goaway_frame(self, + last_stream_id, + error_code=0, + additional_data=b''): + """ + Builds a single GOAWAY frame. + """ + f = GoAwayFrame(0) + f.error_code = error_code + f.last_stream_id = last_stream_id + f.additional_data = additional_data + return f + + def build_rst_stream_frame(self, stream_id, error_code=0): + """ + Builds a single RST_STREAM frame. + """ + f = RstStreamFrame(stream_id) + f.error_code = error_code + return f + + def build_push_promise_frame(self, + stream_id, + promised_stream_id, + headers, + flags=[]): + """ + Builds a single PUSH_PROMISE frame. + """ + f = PushPromiseFrame(stream_id) + f.promised_stream_id = promised_stream_id + f.data = self.encoder.encode(headers) + f.flags = set(flags) + f.flags.add('END_HEADERS') + return f + + def build_priority_frame(self, + stream_id, + weight, + depends_on=0, + exclusive=False): + """ + Builds a single priority frame. + """ + f = PriorityFrame(stream_id) + f.depends_on = depends_on + f.stream_weight = weight + f.exclusive = exclusive + return f + + def build_alt_svc_frame(self, stream_id, origin, field): + """ + Builds a single ALTSVC frame. + """ + f = AltSvcFrame(stream_id) + f.origin = origin + f.field = field + return f + + def change_table_size(self, new_size): + """ + Causes the encoder to send a dynamic size update in the next header + block it sends. + """ + self.encoder.header_table_size = new_size diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_basic_logic.py b/testing/web-platform/tests/tools/third_party/h2/test/test_basic_logic.py new file mode 100644 index 0000000000..7df99a6a57 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_basic_logic.py @@ -0,0 +1,1877 @@ +# -*- coding: utf-8 -*- +""" +test_basic_logic +~~~~~~~~~~~~~~~~ + +Test the basic logic of the h2 state machines. +""" +import random +import sys + +import hyperframe +import pytest + +import h2.config +import h2.connection +import h2.errors +import h2.events +import h2.exceptions +import h2.frame_buffer +import h2.settings +import h2.stream + +import helpers + +from hypothesis import given +from hypothesis.strategies import integers + + +IS_PYTHON3 = sys.version_info >= (3, 0) + + +class TestBasicClient(object): + """ + Basic client-side tests. + """ + example_request_headers = [ + (u':authority', u'example.com'), + (u':path', u'/'), + (u':scheme', u'https'), + (u':method', u'GET'), + ] + bytes_example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + ] + example_response_headers = [ + (u':status', u'200'), + (u'server', u'fake-serv/0.1.0') + ] + bytes_example_response_headers = [ + (b':status', b'200'), + (b'server', b'fake-serv/0.1.0') + ] + + def test_begin_connection(self, frame_factory): + """ + Client connections emit the HTTP/2 preamble. + """ + c = h2.connection.H2Connection() + expected_settings = frame_factory.build_settings_frame( + c.local_settings + ) + expected_data = ( + b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' + expected_settings.serialize() + ) + + events = c.initiate_connection() + assert not events + assert c.data_to_send() == expected_data + + def test_sending_headers(self): + """ + Single headers frames are correctly encoded. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + # Clear the data, then send headers. + c.clear_outbound_data_buffer() + events = c.send_headers(1, self.example_request_headers) + assert not events + assert c.data_to_send() == ( + b'\x00\x00\r\x01\x04\x00\x00\x00\x01' + b'A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x87\x82' + ) + + def test_sending_data(self): + """ + Single data frames are encoded correctly. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + + # Clear the data, then send some data. + c.clear_outbound_data_buffer() + events = c.send_data(1, b'some data') + assert not events + data_to_send = c.data_to_send() + assert ( + data_to_send == b'\x00\x00\t\x00\x00\x00\x00\x00\x01some data' + ) + + buffer = h2.frame_buffer.FrameBuffer(server=False) + buffer.max_frame_size = 65535 + buffer.add_data(data_to_send) + data_frame = list(buffer)[0] + sanity_check_data_frame( + data_frame=data_frame, + expected_flow_controlled_length=len(b'some data'), + expect_padded_flag=False, + expected_data_frame_pad_length=0 + ) + + def test_sending_data_in_memoryview(self): + """ + Support memoryview for sending data. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + + # Clear the data, then send some data. + c.clear_outbound_data_buffer() + events = c.send_data(1, memoryview(b'some data')) + assert not events + data_to_send = c.data_to_send() + assert ( + data_to_send == b'\x00\x00\t\x00\x00\x00\x00\x00\x01some data' + ) + + def test_sending_data_with_padding(self): + """ + Single data frames with padding are encoded correctly. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + + # Clear the data, then send some data. + c.clear_outbound_data_buffer() + events = c.send_data(1, b'some data', pad_length=5) + assert not events + data_to_send = c.data_to_send() + assert data_to_send == ( + b'\x00\x00\x0f\x00\x08\x00\x00\x00\x01' + b'\x05some data\x00\x00\x00\x00\x00' + ) + + buffer = h2.frame_buffer.FrameBuffer(server=False) + buffer.max_frame_size = 65535 + buffer.add_data(data_to_send) + data_frame = list(buffer)[0] + sanity_check_data_frame( + data_frame=data_frame, + expected_flow_controlled_length=len(b'some data') + 1 + 5, + expect_padded_flag=True, + expected_data_frame_pad_length=5 + ) + + def test_sending_data_with_zero_length_padding(self): + """ + Single data frames with zero-length padding are encoded + correctly. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + + # Clear the data, then send some data. + c.clear_outbound_data_buffer() + events = c.send_data(1, b'some data', pad_length=0) + assert not events + data_to_send = c.data_to_send() + assert data_to_send == ( + b'\x00\x00\x0a\x00\x08\x00\x00\x00\x01' + b'\x00some data' + ) + + buffer = h2.frame_buffer.FrameBuffer(server=False) + buffer.max_frame_size = 65535 + buffer.add_data(data_to_send) + data_frame = list(buffer)[0] + sanity_check_data_frame( + data_frame=data_frame, + expected_flow_controlled_length=len(b'some data') + 1, + expect_padded_flag=True, + expected_data_frame_pad_length=0 + ) + + @pytest.mark.parametrize("expected_error,pad_length", [ + (None, 0), + (None, 255), + (None, None), + (ValueError, -1), + (ValueError, 256), + (TypeError, 'invalid'), + (TypeError, ''), + (TypeError, '10'), + (TypeError, {}), + (TypeError, ['1', '2', '3']), + (TypeError, []), + (TypeError, 1.5), + (TypeError, 1.0), + (TypeError, -1.0), + ]) + def test_sending_data_with_invalid_padding_length(self, + expected_error, + pad_length): + """ + ``send_data`` with a ``pad_length`` parameter that is an integer + outside the range of [0, 255] throws a ``ValueError``, and a + ``pad_length`` parameter which is not an ``integer`` type + throws a ``TypeError``. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + + c.clear_outbound_data_buffer() + if expected_error is not None: + with pytest.raises(expected_error): + c.send_data(1, b'some data', pad_length=pad_length) + else: + c.send_data(1, b'some data', pad_length=pad_length) + + def test_closing_stream_sending_data(self, frame_factory): + """ + We can close a stream with a data frame. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + + f = frame_factory.build_data_frame( + data=b'some data', + flags=['END_STREAM'], + ) + + # Clear the data, then send some data. + c.clear_outbound_data_buffer() + events = c.send_data(1, b'some data', end_stream=True) + assert not events + assert c.data_to_send() == f.serialize() + + def test_receiving_a_response(self, frame_factory): + """ + When receiving a response, the ResponseReceived event fires. + """ + config = h2.config.H2Configuration(header_encoding='utf-8') + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + + # Clear the data + f = frame_factory.build_headers_frame( + self.example_response_headers + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.ResponseReceived) + assert event.stream_id == 1 + assert event.headers == self.example_response_headers + + def test_receiving_a_response_bytes(self, frame_factory): + """ + When receiving a response, the ResponseReceived event fires with bytes + headers if the encoding is set appropriately. + """ + config = h2.config.H2Configuration(header_encoding=False) + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + + # Clear the data + f = frame_factory.build_headers_frame( + self.example_response_headers + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.ResponseReceived) + assert event.stream_id == 1 + assert event.headers == self.bytes_example_response_headers + + def test_receiving_a_response_change_encoding(self, frame_factory): + """ + When receiving a response, the ResponseReceived event fires with bytes + headers if the encoding is set appropriately, but if this changes then + the change reflects it. + """ + config = h2.config.H2Configuration(header_encoding=False) + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + + f = frame_factory.build_headers_frame( + self.example_response_headers + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.ResponseReceived) + assert event.stream_id == 1 + assert event.headers == self.bytes_example_response_headers + + c.send_headers(3, self.example_request_headers, end_stream=True) + c.config.header_encoding = 'utf-8' + f = frame_factory.build_headers_frame( + self.example_response_headers, + stream_id=3, + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.ResponseReceived) + assert event.stream_id == 3 + assert event.headers == self.example_response_headers + + def test_end_stream_without_data(self, frame_factory): + """ + Ending a stream without data emits a zero-length DATA frame with + END_STREAM set. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=False) + + # Clear the data + c.clear_outbound_data_buffer() + f = frame_factory.build_data_frame(b'', flags=['END_STREAM']) + events = c.end_stream(1) + + assert not events + assert c.data_to_send() == f.serialize() + + def test_cannot_send_headers_on_lower_stream_id(self): + """ + Once stream ID x has been used, cannot use stream ID y where y < x. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(3, self.example_request_headers, end_stream=False) + + with pytest.raises(h2.exceptions.StreamIDTooLowError) as e: + c.send_headers(1, self.example_request_headers, end_stream=True) + + assert e.value.stream_id == 1 + assert e.value.max_stream_id == 3 + + def test_receiving_pushed_stream(self, frame_factory): + """ + Pushed streams fire a PushedStreamReceived event, followed by + ResponseReceived when the response headers are received. + """ + config = h2.config.H2Configuration(header_encoding='utf-8') + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=False) + + f1 = frame_factory.build_headers_frame( + self.example_response_headers + ) + f2 = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers, + flags=['END_HEADERS'], + ) + f3 = frame_factory.build_headers_frame( + self.example_response_headers, + stream_id=2, + ) + data = b''.join(x.serialize() for x in [f1, f2, f3]) + + events = c.receive_data(data) + + assert len(events) == 3 + stream_push_event = events[1] + response_event = events[2] + assert isinstance(stream_push_event, h2.events.PushedStreamReceived) + assert isinstance(response_event, h2.events.ResponseReceived) + + assert stream_push_event.pushed_stream_id == 2 + assert stream_push_event.parent_stream_id == 1 + assert ( + stream_push_event.headers == self.example_request_headers + ) + assert response_event.stream_id == 2 + assert response_event.headers == self.example_response_headers + + def test_receiving_pushed_stream_bytes(self, frame_factory): + """ + Pushed headers are not decoded if the header encoding is set to False. + """ + config = h2.config.H2Configuration(header_encoding=False) + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=False) + + f1 = frame_factory.build_headers_frame( + self.example_response_headers + ) + f2 = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers, + flags=['END_HEADERS'], + ) + f3 = frame_factory.build_headers_frame( + self.example_response_headers, + stream_id=2, + ) + data = b''.join(x.serialize() for x in [f1, f2, f3]) + + events = c.receive_data(data) + + assert len(events) == 3 + stream_push_event = events[1] + response_event = events[2] + assert isinstance(stream_push_event, h2.events.PushedStreamReceived) + assert isinstance(response_event, h2.events.ResponseReceived) + + assert stream_push_event.pushed_stream_id == 2 + assert stream_push_event.parent_stream_id == 1 + assert ( + stream_push_event.headers == self.bytes_example_request_headers + ) + assert response_event.stream_id == 2 + assert response_event.headers == self.bytes_example_response_headers + + def test_cannot_receive_pushed_stream_when_enable_push_is_0(self, + frame_factory): + """ + If we have set SETTINGS_ENABLE_PUSH to 0, receiving PUSH_PROMISE frames + triggers the connection to be closed. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.local_settings.enable_push = 0 + c.send_headers(1, self.example_request_headers, end_stream=False) + + f1 = frame_factory.build_settings_frame({}, ack=True) + f2 = frame_factory.build_headers_frame( + self.example_response_headers + ) + f3 = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers, + flags=['END_HEADERS'], + ) + c.receive_data(f1.serialize()) + c.receive_data(f2.serialize()) + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f3.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + 0, h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_receiving_response_no_body(self, frame_factory): + """ + Receiving a response without a body fires two events, ResponseReceived + and StreamEnded. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + + f = frame_factory.build_headers_frame( + self.example_response_headers, + flags=['END_STREAM'] + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 2 + response_event = events[0] + end_stream = events[1] + + assert isinstance(response_event, h2.events.ResponseReceived) + assert isinstance(end_stream, h2.events.StreamEnded) + + def test_oversize_headers(self): + """ + Sending headers that are oversized generates a stream of CONTINUATION + frames. + """ + all_bytes = [chr(x) for x in range(0, 256)] + if IS_PYTHON3: + all_bytes = [x.encode('latin1') for x in all_bytes] + + large_binary_string = b''.join( + random.choice(all_bytes) for _ in range(0, 256) + ) + test_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':method', 'GET'), + (':scheme', 'https'), + ('key', large_binary_string) + ] + c = h2.connection.H2Connection() + + # Greatly shrink the max frame size to force us over. + c.max_outbound_frame_size = 48 + c.initiate_connection() + c.send_headers(1, test_headers, end_stream=True) + + # Use the frame buffer here, because we don't care about decoding + # the headers. Don't send all the data in because that will force the + # frame buffer to stop caching the CONTINUATION frames, so instead + # send all but one byte. + buffer = h2.frame_buffer.FrameBuffer(server=True) + buffer.max_frame_size = 65535 + data = c.data_to_send() + buffer.add_data(data[:-1]) + + # Drain the buffer, confirming that it only provides a single frame + # (the settings frame) + assert len(list(buffer)) == 1 + + # Get the cached frames. + frames = buffer._headers_buffer + + # Split the frames up. + headers_frame = frames[0] + continuation_frames = frames[1:] + + assert isinstance(headers_frame, hyperframe.frame.HeadersFrame) + assert all( + map( + lambda f: isinstance(f, hyperframe.frame.ContinuationFrame), + continuation_frames) + ) + assert all( + map(lambda f: len(f.data) <= c.max_outbound_frame_size, frames) + ) + + assert frames[0].flags == {'END_STREAM'} + + buffer.add_data(data[-1:]) + headers = list(buffer)[0] + assert isinstance(headers, hyperframe.frame.HeadersFrame) + + def test_handle_stream_reset(self, frame_factory): + """ + Streams being remotely reset fires a StreamReset event. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + c.clear_outbound_data_buffer() + + f = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.StreamReset) + assert event.stream_id == 1 + assert event.error_code is h2.errors.ErrorCodes.STREAM_CLOSED + assert isinstance(event.error_code, h2.errors.ErrorCodes) + assert event.remote_reset + + def test_handle_stream_reset_with_unknown_erorr_code(self, frame_factory): + """ + Streams being remotely reset with unknown error codes behave exactly as + they do with known error codes, but the error code on the event is an + int, instead of being an ErrorCodes. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + c.clear_outbound_data_buffer() + + f = frame_factory.build_rst_stream_frame(stream_id=1, error_code=0xFA) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.StreamReset) + assert event.stream_id == 1 + assert event.error_code == 250 + assert not isinstance(event.error_code, h2.errors.ErrorCodes) + assert event.remote_reset + + def test_can_consume_partial_data_from_connection(self): + """ + We can do partial reads from the connection. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + assert len(c.data_to_send(2)) == 2 + assert len(c.data_to_send(3)) == 3 + assert 0 < len(c.data_to_send(500)) < 500 + assert len(c.data_to_send(10)) == 0 + assert len(c.data_to_send()) == 0 + + def test_we_can_update_settings(self, frame_factory): + """ + Updating the settings emits a SETTINGS frame. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + new_settings = { + h2.settings.SettingCodes.HEADER_TABLE_SIZE: 52, + h2.settings.SettingCodes.ENABLE_PUSH: 0, + } + events = c.update_settings(new_settings) + assert not events + + f = frame_factory.build_settings_frame(new_settings) + assert c.data_to_send() == f.serialize() + + def test_settings_get_acked_correctly(self, frame_factory): + """ + When settings changes are ACKed, they contain the changed settings. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + new_settings = { + h2.settings.SettingCodes.HEADER_TABLE_SIZE: 52, + h2.settings.SettingCodes.ENABLE_PUSH: 0, + } + c.update_settings(new_settings) + + f = frame_factory.build_settings_frame({}, ack=True) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.SettingsAcknowledged) + assert len(event.changed_settings) == len(new_settings) + for setting, value in new_settings.items(): + assert event.changed_settings[setting].new_value == value + + def test_cannot_create_new_outbound_stream_over_limit(self, frame_factory): + """ + When the number of outbound streams exceeds the remote peer's + MAX_CONCURRENT_STREAMS setting, attempting to open new streams fails. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + f = frame_factory.build_settings_frame( + {h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 1} + ) + c.receive_data(f.serialize())[0] + + c.send_headers(1, self.example_request_headers) + + with pytest.raises(h2.exceptions.TooManyStreamsError): + c.send_headers(3, self.example_request_headers) + + def test_can_receive_trailers(self, frame_factory): + """ + When two HEADERS blocks are received in the same stream from a + server, the second set are trailers. + """ + config = h2.config.H2Configuration(header_encoding='utf-8') + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + f = frame_factory.build_headers_frame(self.example_response_headers) + c.receive_data(f.serialize()) + + # Send in trailers. + trailers = [('content-length', '0')] + f = frame_factory.build_headers_frame( + trailers, + flags=['END_STREAM'], + ) + events = c.receive_data(f.serialize()) + assert len(events) == 2 + + event = events[0] + assert isinstance(event, h2.events.TrailersReceived) + assert event.headers == trailers + assert event.stream_id == 1 + + def test_reject_trailers_not_ending_stream(self, frame_factory): + """ + When trailers are received without the END_STREAM flag being present, + this is a ProtocolError. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + f = frame_factory.build_headers_frame(self.example_response_headers) + c.receive_data(f.serialize()) + + # Send in trailers. + c.clear_outbound_data_buffer() + trailers = [('content-length', '0')] + f = frame_factory.build_headers_frame( + trailers, + flags=[], + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_can_send_trailers(self, frame_factory): + """ + When a second set of headers are sent, they are properly trailers. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + c.send_headers(1, self.example_request_headers) + + # Now send trailers. + trailers = [('content-length', '0')] + c.send_headers(1, trailers, end_stream=True) + + frame_factory.refresh_encoder() + f1 = frame_factory.build_headers_frame( + self.example_request_headers, + ) + f2 = frame_factory.build_headers_frame( + trailers, + flags=['END_STREAM'], + ) + assert c.data_to_send() == f1.serialize() + f2.serialize() + + def test_trailers_must_have_end_stream(self, frame_factory): + """ + A set of trailers must carry the END_STREAM flag. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + # Send headers. + c.send_headers(1, self.example_request_headers) + + # Now send trailers. + trailers = [('content-length', '0')] + + with pytest.raises(h2.exceptions.ProtocolError): + c.send_headers(1, trailers) + + def test_headers_are_lowercase(self, frame_factory): + """ + When headers are sent, they are forced to lower-case. + """ + weird_headers = self.example_request_headers + [ + ('ChAnGiNg-CaSe', 'AlsoHere'), + ('alllowercase', 'alllowercase'), + ('ALLCAPS', 'ALLCAPS'), + ] + expected_headers = self.example_request_headers + [ + ('changing-case', 'AlsoHere'), + ('alllowercase', 'alllowercase'), + ('allcaps', 'ALLCAPS'), + ] + + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + c.send_headers(1, weird_headers) + expected_frame = frame_factory.build_headers_frame( + headers=expected_headers + ) + + assert c.data_to_send() == expected_frame.serialize() + + @given(frame_size=integers(min_value=2**14, max_value=(2**24 - 1))) + def test_changing_max_frame_size(self, frame_factory, frame_size): + """ + When the user changes the max frame size and the change is ACKed, the + remote peer is now bound by the new frame size. + """ + # We need to refresh the encoder because hypothesis has a problem with + # integrating with py.test, meaning that we use the same frame factory + # for all tests. + # See https://github.com/HypothesisWorks/hypothesis-python/issues/377 + frame_factory.refresh_encoder() + + c = h2.connection.H2Connection() + c.initiate_connection() + + # Set up the stream. + c.send_headers(1, self.example_request_headers, end_stream=True) + headers_frame = frame_factory.build_headers_frame( + headers=self.example_response_headers, + ) + c.receive_data(headers_frame.serialize()) + + # Change the max frame size. + c.update_settings( + {h2.settings.SettingCodes.MAX_FRAME_SIZE: frame_size} + ) + settings_ack = frame_factory.build_settings_frame({}, ack=True) + c.receive_data(settings_ack.serialize()) + + # Greatly increase the flow control windows: we're not here to test + # flow control today. + c.increment_flow_control_window(increment=(2 * frame_size) + 1) + c.increment_flow_control_window( + increment=(2 * frame_size) + 1, stream_id=1 + ) + + # Send one DATA frame that is exactly the max frame size: confirm it's + # fine. + data = frame_factory.build_data_frame( + data=(b'\x00' * frame_size), + ) + events = c.receive_data(data.serialize()) + assert len(events) == 1 + assert isinstance(events[0], h2.events.DataReceived) + assert events[0].flow_controlled_length == frame_size + + # Send one that is one byte too large: confirm a protocol error is + # raised. + data.data += b'\x00' + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(data.serialize()) + + def test_cookies_are_joined_on_push(self, frame_factory): + """ + RFC 7540 Section 8.1.2.5 requires that we join multiple Cookie headers + in a header block together when they're received on a push. + """ + # This is a moderately varied set of cookie headers: some combined, + # some split. + cookie_headers = [ + ('cookie', + 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC'), + ('cookie', 'path=1'), + ('cookie', 'test1=val1; test2=val2') + ] + expected = ( + 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC; ' + 'path=1; test1=val1; test2=val2' + ) + + config = h2.config.H2Configuration(header_encoding='utf-8') + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers + cookie_headers + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + e = events[0] + + cookie_fields = [(n, v) for n, v in e.headers if n == 'cookie'] + assert len(cookie_fields) == 1 + + _, v = cookie_fields[0] + assert v == expected + + def test_cookies_arent_joined_without_normalization(self, frame_factory): + """ + If inbound header normalization is disabled, cookie headers aren't + joined. + """ + # This is a moderately varied set of cookie headers: some combined, + # some split. + cookie_headers = [ + ('cookie', + 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC'), + ('cookie', 'path=1'), + ('cookie', 'test1=val1; test2=val2') + ] + + config = h2.config.H2Configuration( + client_side=True, + normalize_inbound_headers=False, + header_encoding='utf-8' + ) + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers + cookie_headers + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + e = events[0] + + received_cookies = [(n, v) for n, v in e.headers if n == 'cookie'] + assert len(received_cookies) == 3 + assert cookie_headers == received_cookies + + +class TestBasicServer(object): + """ + Basic server-side tests. + """ + example_request_headers = [ + (u':authority', u'example.com'), + (u':path', u'/'), + (u':scheme', u'https'), + (u':method', u'GET'), + ] + bytes_example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + ] + example_response_headers = [ + (':status', '200'), + ('server', 'hyper-h2/0.1.0') + ] + server_config = h2.config.H2Configuration( + client_side=False, header_encoding='utf-8' + ) + + def test_ignores_preamble(self): + """ + The preamble does not cause any events or frames to be written. + """ + c = h2.connection.H2Connection(config=self.server_config) + preamble = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' + + events = c.receive_data(preamble) + assert not events + assert not c.data_to_send() + + @pytest.mark.parametrize("chunk_size", range(1, 24)) + def test_drip_feed_preamble(self, chunk_size): + """ + The preamble can be sent in in less than a single buffer. + """ + c = h2.connection.H2Connection(config=self.server_config) + preamble = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' + events = [] + + for i in range(0, len(preamble), chunk_size): + events += c.receive_data(preamble[i:i+chunk_size]) + + assert not events + assert not c.data_to_send() + + def test_initiate_connection_sends_server_preamble(self, frame_factory): + """ + For server-side connections, initiate_connection sends a server + preamble. + """ + c = h2.connection.H2Connection(config=self.server_config) + expected_settings = frame_factory.build_settings_frame( + c.local_settings + ) + expected_data = expected_settings.serialize() + + events = c.initiate_connection() + assert not events + assert c.data_to_send() == expected_data + + def test_headers_event(self, frame_factory): + """ + When a headers frame is received a RequestReceived event fires. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame(self.example_request_headers) + data = f.serialize() + events = c.receive_data(data) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.RequestReceived) + assert event.stream_id == 1 + assert event.headers == self.example_request_headers + + def test_headers_event_bytes(self, frame_factory): + """ + When a headers frame is received a RequestReceived event fires with + bytes headers if the encoding is set appropriately. + """ + config = h2.config.H2Configuration( + client_side=False, header_encoding=False + ) + c = h2.connection.H2Connection(config=config) + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame(self.example_request_headers) + data = f.serialize() + events = c.receive_data(data) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.RequestReceived) + assert event.stream_id == 1 + assert event.headers == self.bytes_example_request_headers + + def test_data_event(self, frame_factory): + """ + Test that data received on a stream fires a DataReceived event. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + f1 = frame_factory.build_headers_frame( + self.example_request_headers, stream_id=3 + ) + f2 = frame_factory.build_data_frame( + b'some request data', + stream_id=3, + ) + data = b''.join(map(lambda f: f.serialize(), [f1, f2])) + events = c.receive_data(data) + + assert len(events) == 2 + event = events[1] + + assert isinstance(event, h2.events.DataReceived) + assert event.stream_id == 3 + assert event.data == b'some request data' + assert event.flow_controlled_length == 17 + + def test_data_event_with_padding(self, frame_factory): + """ + Test that data received on a stream fires a DataReceived event that + accounts for padding. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + f1 = frame_factory.build_headers_frame( + self.example_request_headers, stream_id=3 + ) + f2 = frame_factory.build_data_frame( + b'some request data', + stream_id=3, + padding_len=20 + ) + data = b''.join(map(lambda f: f.serialize(), [f1, f2])) + events = c.receive_data(data) + + assert len(events) == 2 + event = events[1] + + assert isinstance(event, h2.events.DataReceived) + assert event.stream_id == 3 + assert event.data == b'some request data' + assert event.flow_controlled_length == 17 + 20 + 1 + + def test_receiving_ping_frame(self, frame_factory): + """ + Ping frames should be immediately ACKed. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + ping_data = b'\x01' * 8 + sent_frame = frame_factory.build_ping_frame(ping_data) + expected_frame = frame_factory.build_ping_frame( + ping_data, flags=["ACK"] + ) + expected_data = expected_frame.serialize() + + c.clear_outbound_data_buffer() + events = c.receive_data(sent_frame.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.PingReceived) + assert event.ping_data == ping_data + + assert c.data_to_send() == expected_data + + def test_receiving_settings_frame_event(self, frame_factory): + """ + Settings frames should cause a RemoteSettingsChanged event to fire. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_settings_frame( + settings=helpers.SAMPLE_SETTINGS + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.RemoteSettingsChanged) + assert len(event.changed_settings) == len(helpers.SAMPLE_SETTINGS) + + def test_acknowledging_settings(self, frame_factory): + """ + Acknowledging settings causes appropriate Settings frame to be emitted. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + received_frame = frame_factory.build_settings_frame( + settings=helpers.SAMPLE_SETTINGS + ) + expected_frame = frame_factory.build_settings_frame( + settings={}, ack=True + ) + expected_data = expected_frame.serialize() + + c.clear_outbound_data_buffer() + events = c.receive_data(received_frame.serialize()) + + assert len(events) == 1 + assert c.data_to_send() == expected_data + + def test_close_connection(self, frame_factory): + """ + Closing the connection with no error code emits a GOAWAY frame with + error code 0. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_goaway_frame(last_stream_id=0) + expected_data = f.serialize() + + c.clear_outbound_data_buffer() + events = c.close_connection() + + assert not events + assert c.data_to_send() == expected_data + + @pytest.mark.parametrize("error_code", h2.errors.ErrorCodes) + def test_close_connection_with_error_code(self, frame_factory, error_code): + """ + Closing the connection with an error code emits a GOAWAY frame with + that error code. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_goaway_frame( + error_code=error_code, last_stream_id=0 + ) + expected_data = f.serialize() + + c.clear_outbound_data_buffer() + events = c.close_connection(error_code) + + assert not events + assert c.data_to_send() == expected_data + + @pytest.mark.parametrize("last_stream_id,output", [ + (None, 23), + (0, 0), + (42, 42) + ]) + def test_close_connection_with_last_stream_id(self, frame_factory, + last_stream_id, output): + """ + Closing the connection with last_stream_id set emits a GOAWAY frame + with that value. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + headers_frame = frame_factory.build_headers_frame( + [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ], + stream_id=23) + c.receive_data(headers_frame.serialize()) + + f = frame_factory.build_goaway_frame( + last_stream_id=output + ) + expected_data = f.serialize() + + c.clear_outbound_data_buffer() + events = c.close_connection(last_stream_id=last_stream_id) + + assert not events + assert c.data_to_send() == expected_data + + @pytest.mark.parametrize("additional_data,output", [ + (None, b''), + (b'', b''), + (b'foobar', b'foobar') + ]) + def test_close_connection_with_additional_data(self, frame_factory, + additional_data, output): + """ + Closing the connection with additional debug data emits a GOAWAY frame + with that data attached. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_goaway_frame( + last_stream_id=0, additional_data=output + ) + expected_data = f.serialize() + + c.clear_outbound_data_buffer() + events = c.close_connection(additional_data=additional_data) + + assert not events + assert c.data_to_send() == expected_data + + def test_reset_stream(self, frame_factory): + """ + Resetting a stream with no error code emits a RST_STREAM frame with + error code 0. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame(self.example_request_headers) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + expected_frame = frame_factory.build_rst_stream_frame(stream_id=1) + expected_data = expected_frame.serialize() + + events = c.reset_stream(stream_id=1) + + assert not events + assert c.data_to_send() == expected_data + + @pytest.mark.parametrize("error_code", h2.errors.ErrorCodes) + def test_reset_stream_with_error_code(self, frame_factory, error_code): + """ + Resetting a stream with an error code emits a RST_STREAM frame with + that error code. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame( + self.example_request_headers, + stream_id=3 + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + expected_frame = frame_factory.build_rst_stream_frame( + stream_id=3, error_code=error_code + ) + expected_data = expected_frame.serialize() + + events = c.reset_stream(stream_id=3, error_code=error_code) + + assert not events + assert c.data_to_send() == expected_data + + def test_cannot_reset_nonexistent_stream(self, frame_factory): + """ + Resetting nonexistent streams raises NoSuchStreamError. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame( + self.example_request_headers, + stream_id=3 + ) + c.receive_data(f.serialize()) + + with pytest.raises(h2.exceptions.NoSuchStreamError) as e: + c.reset_stream(stream_id=1) + + assert e.value.stream_id == 1 + + with pytest.raises(h2.exceptions.NoSuchStreamError) as e: + c.reset_stream(stream_id=5) + + assert e.value.stream_id == 5 + + def test_basic_sending_ping_frame_logic(self, frame_factory): + """ + Sending ping frames serializes a ping frame on stream 0 with + approriate opaque data. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + ping_data = b'\x01\x02\x03\x04\x05\x06\x07\x08' + + expected_frame = frame_factory.build_ping_frame(ping_data) + expected_data = expected_frame.serialize() + + events = c.ping(ping_data) + + assert not events + assert c.data_to_send() == expected_data + + @pytest.mark.parametrize( + 'opaque_data', + [ + b'', + b'\x01\x02\x03\x04\x05\x06\x07', + u'abcdefgh', + b'too many bytes', + ] + ) + def test_ping_frame_opaque_data_must_be_length_8_bytestring(self, + frame_factory, + opaque_data): + """ + Sending a ping frame only works with 8-byte bytestrings. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + with pytest.raises(ValueError): + c.ping(opaque_data) + + def test_receiving_ping_acknowledgement(self, frame_factory): + """ + Receiving a PING acknowledgement fires a PingAckReceived event. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + ping_data = b'\x01\x02\x03\x04\x05\x06\x07\x08' + + f = frame_factory.build_ping_frame( + ping_data, flags=['ACK'] + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.PingAckReceived) + assert isinstance(event, h2.events.PingAcknowledged) # deprecated + assert event.ping_data == ping_data + + def test_stream_ended_remotely(self, frame_factory): + """ + When the remote stream ends with a non-empty data frame a DataReceived + event and a StreamEnded event are fired. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + f1 = frame_factory.build_headers_frame( + self.example_request_headers, stream_id=3 + ) + f2 = frame_factory.build_data_frame( + b'some request data', + flags=['END_STREAM'], + stream_id=3, + ) + data = b''.join(map(lambda f: f.serialize(), [f1, f2])) + events = c.receive_data(data) + + assert len(events) == 3 + data_event = events[1] + stream_ended_event = events[2] + + assert isinstance(data_event, h2.events.DataReceived) + assert isinstance(stream_ended_event, h2.events.StreamEnded) + stream_ended_event.stream_id == 3 + + def test_can_push_stream(self, frame_factory): + """ + Pushing a stream causes a PUSH_PROMISE frame to be emitted. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame( + self.example_request_headers + ) + c.receive_data(f.serialize()) + + frame_factory.refresh_encoder() + expected_frame = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers, + flags=['END_HEADERS'], + ) + + c.clear_outbound_data_buffer() + c.push_stream( + stream_id=1, + promised_stream_id=2, + request_headers=self.example_request_headers + ) + + assert c.data_to_send() == expected_frame.serialize() + + def test_cannot_push_streams_when_disabled(self, frame_factory): + """ + When the remote peer has disabled stream pushing, we should fail. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_settings_frame( + {h2.settings.SettingCodes.ENABLE_PUSH: 0} + ) + c.receive_data(f.serialize()) + + f = frame_factory.build_headers_frame( + self.example_request_headers + ) + c.receive_data(f.serialize()) + + with pytest.raises(h2.exceptions.ProtocolError): + c.push_stream( + stream_id=1, + promised_stream_id=2, + request_headers=self.example_request_headers + ) + + def test_settings_remote_change_header_table_size(self, frame_factory): + """ + Acknowledging a remote HEADER_TABLE_SIZE settings change causes us to + change the header table size of our encoder. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + assert c.encoder.header_table_size == 4096 + + received_frame = frame_factory.build_settings_frame( + {h2.settings.SettingCodes.HEADER_TABLE_SIZE: 80} + ) + c.receive_data(received_frame.serialize())[0] + + assert c.encoder.header_table_size == 80 + + def test_settings_local_change_header_table_size(self, frame_factory): + """ + The remote peer acknowledging a local HEADER_TABLE_SIZE settings change + does not cause us to change the header table size of our decoder. + + For an explanation of why this test is this way around, see issue #37. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + assert c.decoder.header_table_size == 4096 + + expected_frame = frame_factory.build_settings_frame({}, ack=True) + c.update_settings( + {h2.settings.SettingCodes.HEADER_TABLE_SIZE: 80} + ) + c.receive_data(expected_frame.serialize()) + c.clear_outbound_data_buffer() + + assert c.decoder.header_table_size == 4096 + + def test_restricting_outbound_frame_size_by_settings(self, frame_factory): + """ + The remote peer can shrink the maximum outbound frame size using + settings. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame(self.example_request_headers) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.FrameTooLargeError): + c.send_data(1, b'\x01' * 17000) + + received_frame = frame_factory.build_settings_frame( + {h2.settings.SettingCodes.MAX_FRAME_SIZE: 17001} + ) + c.receive_data(received_frame.serialize()) + + c.send_data(1, b'\x01' * 17000) + assert c.data_to_send() + + def test_restricting_inbound_frame_size_by_settings(self, frame_factory): + """ + We throw ProtocolErrors and tear down connections if oversize frames + are received. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + h = frame_factory.build_headers_frame(self.example_request_headers) + c.receive_data(h.serialize()) + c.clear_outbound_data_buffer() + + data_frame = frame_factory.build_data_frame(b'\x01' * 17000) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(data_frame.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, error_code=h2.errors.ErrorCodes.FRAME_SIZE_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_cannot_receive_new_streams_over_limit(self, frame_factory): + """ + When the number of inbound streams exceeds our MAX_CONCURRENT_STREAMS + setting, their attempt to open new streams fails. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.update_settings( + {h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 1} + ) + f = frame_factory.build_settings_frame({}, ack=True) + c.receive_data(f.serialize()) + + f = frame_factory.build_headers_frame( + stream_id=1, + headers=self.example_request_headers, + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + stream_id=3, + headers=self.example_request_headers, + ) + with pytest.raises(h2.exceptions.TooManyStreamsError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_can_receive_trailers(self, frame_factory): + """ + When two HEADERS blocks are received in the same stream from a + client, the second set are trailers. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame(self.example_request_headers) + c.receive_data(f.serialize()) + + # Send in trailers. + trailers = [('content-length', '0')] + f = frame_factory.build_headers_frame( + trailers, + flags=['END_STREAM'], + ) + events = c.receive_data(f.serialize()) + assert len(events) == 2 + + event = events[0] + assert isinstance(event, h2.events.TrailersReceived) + assert event.headers == trailers + assert event.stream_id == 1 + + def test_reject_trailers_not_ending_stream(self, frame_factory): + """ + When trailers are received without the END_STREAM flag being present, + this is a ProtocolError. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame(self.example_request_headers) + c.receive_data(f.serialize()) + + # Send in trailers. + c.clear_outbound_data_buffer() + trailers = [('content-length', '0')] + f = frame_factory.build_headers_frame( + trailers, + flags=[], + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_can_send_trailers(self, frame_factory): + """ + When a second set of headers are sent, they are properly trailers. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame(self.example_request_headers) + c.receive_data(f.serialize()) + + # Send headers. + c.clear_outbound_data_buffer() + c.send_headers(1, self.example_response_headers) + + # Now send trailers. + trailers = [('content-length', '0')] + c.send_headers(1, trailers, end_stream=True) + + frame_factory.refresh_encoder() + f1 = frame_factory.build_headers_frame( + self.example_response_headers, + ) + f2 = frame_factory.build_headers_frame( + trailers, + flags=['END_STREAM'], + ) + assert c.data_to_send() == f1.serialize() + f2.serialize() + + def test_trailers_must_have_end_stream(self, frame_factory): + """ + A set of trailers must carry the END_STREAM flag. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame(self.example_request_headers) + c.receive_data(f.serialize()) + + # Send headers. + c.send_headers(1, self.example_response_headers) + + # Now send trailers. + trailers = [('content-length', '0')] + + with pytest.raises(h2.exceptions.ProtocolError): + c.send_headers(1, trailers) + + @pytest.mark.parametrize("frame_id", range(12, 256)) + def test_unknown_frames_are_ignored(self, frame_factory, frame_id): + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_data_frame(data=b'abcdefghtdst') + f.type = frame_id + + events = c.receive_data(f.serialize()) + assert not c.data_to_send() + assert len(events) == 1 + assert isinstance(events[0], h2.events.UnknownFrameReceived) + assert isinstance(events[0].frame, hyperframe.frame.ExtensionFrame) + + def test_can_send_goaway_repeatedly(self, frame_factory): + """ + We can send a GOAWAY frame as many times as we like. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + c.close_connection() + c.close_connection() + c.close_connection() + + f = frame_factory.build_goaway_frame(last_stream_id=0) + + assert c.data_to_send() == (f.serialize() * 3) + + def test_receiving_goaway_frame(self, frame_factory): + """ + Receiving a GOAWAY frame causes a ConnectionTerminated event to be + fired and transitions the connection to the CLOSED state, and clears + the outbound data buffer. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_goaway_frame( + last_stream_id=5, error_code=h2.errors.ErrorCodes.SETTINGS_TIMEOUT + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.ConnectionTerminated) + assert event.error_code == h2.errors.ErrorCodes.SETTINGS_TIMEOUT + assert isinstance(event.error_code, h2.errors.ErrorCodes) + assert event.last_stream_id == 5 + assert event.additional_data is None + assert c.state_machine.state == h2.connection.ConnectionState.CLOSED + + assert not c.data_to_send() + + def test_receiving_multiple_goaway_frames(self, frame_factory): + """ + Multiple GOAWAY frames can be received at once, and are allowed. Each + one fires a ConnectionTerminated event. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_goaway_frame(last_stream_id=0) + events = c.receive_data(f.serialize() * 3) + + assert len(events) == 3 + assert all( + isinstance(event, h2.events.ConnectionTerminated) + for event in events + ) + + def test_receiving_goaway_frame_with_additional_data(self, frame_factory): + """ + GOAWAY frame can contain additional data, + it should be available via ConnectionTerminated event. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + additional_data = b'debug data' + f = frame_factory.build_goaway_frame(last_stream_id=0, + additional_data=additional_data) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.ConnectionTerminated) + assert event.additional_data == additional_data + + def test_receiving_goaway_frame_with_unknown_error(self, frame_factory): + """ + Receiving a GOAWAY frame with an unknown error code behaves exactly the + same as receiving one we know about, but the code is reported as an + integer instead of as an ErrorCodes. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_goaway_frame( + last_stream_id=5, error_code=0xFA + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.ConnectionTerminated) + assert event.error_code == 250 + assert not isinstance(event.error_code, h2.errors.ErrorCodes) + assert event.last_stream_id == 5 + assert event.additional_data is None + assert c.state_machine.state == h2.connection.ConnectionState.CLOSED + + assert not c.data_to_send() + + def test_cookies_are_joined(self, frame_factory): + """ + RFC 7540 Section 8.1.2.5 requires that we join multiple Cookie headers + in a header block together. + """ + # This is a moderately varied set of cookie headers: some combined, + # some split. + cookie_headers = [ + ('cookie', + 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC'), + ('cookie', 'path=1'), + ('cookie', 'test1=val1; test2=val2') + ] + expected = ( + 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC; ' + 'path=1; test1=val1; test2=val2' + ) + + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + self.example_request_headers + cookie_headers + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + e = events[0] + + cookie_fields = [(n, v) for n, v in e.headers if n == 'cookie'] + assert len(cookie_fields) == 1 + + _, v = cookie_fields[0] + assert v == expected + + def test_cookies_arent_joined_without_normalization(self, frame_factory): + """ + If inbound header normalization is disabled, cookie headers aren't + joined. + """ + # This is a moderately varied set of cookie headers: some combined, + # some split. + cookie_headers = [ + ('cookie', + 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC'), + ('cookie', 'path=1'), + ('cookie', 'test1=val1; test2=val2') + ] + + config = h2.config.H2Configuration( + client_side=False, + normalize_inbound_headers=False, + header_encoding='utf-8' + ) + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + self.example_request_headers + cookie_headers + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + e = events[0] + + received_cookies = [(n, v) for n, v in e.headers if n == 'cookie'] + assert len(received_cookies) == 3 + assert cookie_headers == received_cookies + + def test_stream_repr(self): + """ + Ensure stream string representation is appropriate. + """ + s = h2.stream.H2Stream(4, None, 12, 14) + assert repr(s) == ">" + + +def sanity_check_data_frame(data_frame, + expected_flow_controlled_length, + expect_padded_flag, + expected_data_frame_pad_length): + """ + ``data_frame`` is a frame of type ``hyperframe.frame.DataFrame``, + and the ``flags`` and ``flow_controlled_length`` of ``data_frame`` + match expectations. + """ + + assert isinstance(data_frame, hyperframe.frame.DataFrame) + + assert data_frame.flow_controlled_length == expected_flow_controlled_length + + if expect_padded_flag: + assert 'PADDED' in data_frame.flags + else: + assert 'PADDED' not in data_frame.flags + + assert data_frame.pad_length == expected_data_frame_pad_length diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_closed_streams.py b/testing/web-platform/tests/tools/third_party/h2/test/test_closed_streams.py new file mode 100644 index 0000000000..631a66787a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_closed_streams.py @@ -0,0 +1,555 @@ +# -*- coding: utf-8 -*- +""" +test_closed_streams +~~~~~~~~~~~~~~~~~~~ + +Tests that we handle closed streams correctly. +""" +import pytest + +import h2.config +import h2.connection +import h2.errors +import h2.events +import h2.exceptions + + +class TestClosedStreams(object): + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + example_response_headers = [ + (':status', '200'), + ('server', 'fake-serv/0.1.0') + ] + server_config = h2.config.H2Configuration(client_side=False) + + def test_can_receive_multiple_rst_stream_frames(self, frame_factory): + """ + Multiple RST_STREAM frames can be received, either at once or well + after one another. Only the first fires an event. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + + f = frame_factory.build_rst_stream_frame(stream_id=1) + events = c.receive_data(f.serialize() * 3) + + # Force an iteration over all the streams to remove them. + c.open_outbound_streams + + # Receive more data. + events += c.receive_data(f.serialize() * 3) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.StreamReset) + + def test_receiving_low_stream_id_causes_goaway(self, frame_factory): + """ + The remote peer creating a stream with a lower ID than one we've seen + causes a GOAWAY frame. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.initiate_connection() + + f = frame_factory.build_headers_frame( + self.example_request_headers, + stream_id=3, + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + self.example_request_headers, + stream_id=1, + ) + + with pytest.raises(h2.exceptions.StreamIDTooLowError) as e: + c.receive_data(f.serialize()) + + assert e.value.stream_id == 1 + assert e.value.max_stream_id == 3 + + f = frame_factory.build_goaway_frame( + last_stream_id=3, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + ) + assert c.data_to_send() == f.serialize() + + def test_closed_stream_not_present_in_streams_dict(self, frame_factory): + """ + When streams have been closed, they get removed from the streams + dictionary the next time we count the open streams. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.initiate_connection() + + f = frame_factory.build_headers_frame(self.example_request_headers) + c.receive_data(f.serialize()) + c.push_stream(1, 2, self.example_request_headers) + c.reset_stream(1) + c.clear_outbound_data_buffer() + + f = frame_factory.build_rst_stream_frame(stream_id=2) + c.receive_data(f.serialize()) + + # Force a count of the streams. + assert not c.open_outbound_streams + + # The streams dictionary should be empty. + assert not c.streams + + def test_receive_rst_stream_on_closed_stream(self, frame_factory): + """ + RST_STREAM frame should be ignored if stream is in a closed state. + See RFC 7540 Section 5.1 (closed state) + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + # Client sends request + c.send_headers(1, self.example_request_headers) + + # Some time passes and client sends DATA frame and closes stream, + # so it is in a half-closed state + c.send_data(1, b'some data', end_stream=True) + + # Server received HEADERS frame but DATA frame is still on the way. + # Stream is in open state on the server-side. In this state server is + # allowed to end stream and reset it - this trick helps immediately + # close stream on the server-side. + headers_frame = frame_factory.build_headers_frame( + [(':status', '200')], + flags=['END_STREAM'], + stream_id=1, + ) + events = c.receive_data(headers_frame.serialize()) + assert len(events) == 2 + response_received, stream_ended = events + assert isinstance(response_received, h2.events.ResponseReceived) + assert isinstance(stream_ended, h2.events.StreamEnded) + + rst_stream_frame = frame_factory.build_rst_stream_frame(stream_id=1) + events = c.receive_data(rst_stream_frame.serialize()) + assert not events + + def test_receive_window_update_on_closed_stream(self, frame_factory): + """ + WINDOW_UPDATE frame should be ignored if stream is in a closed state. + See RFC 7540 Section 5.1 (closed state) + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + # Client sends request + c.send_headers(1, self.example_request_headers) + + # Some time passes and client sends DATA frame and closes stream, + # so it is in a half-closed state + c.send_data(1, b'some data', end_stream=True) + + # Server received HEADERS frame but DATA frame is still on the way. + # Stream is in open state on the server-side. In this state server is + # allowed to end stream and after that acknowledge received data by + # sending WINDOW_UPDATE frames. + headers_frame = frame_factory.build_headers_frame( + [(':status', '200')], + flags=['END_STREAM'], + stream_id=1, + ) + events = c.receive_data(headers_frame.serialize()) + assert len(events) == 2 + response_received, stream_ended = events + assert isinstance(response_received, h2.events.ResponseReceived) + assert isinstance(stream_ended, h2.events.StreamEnded) + + window_update_frame = frame_factory.build_window_update_frame( + stream_id=1, + increment=1, + ) + events = c.receive_data(window_update_frame.serialize()) + assert not events + + +class TestStreamsClosedByEndStream(object): + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + example_response_headers = [ + (':status', '200'), + ('server', 'fake-serv/0.1.0') + ] + server_config = h2.config.H2Configuration(client_side=False) + + @pytest.mark.parametrize( + "frame", + [ + lambda self, ff: ff.build_headers_frame( + self.example_request_headers, flags=['END_STREAM']), + lambda self, ff: ff.build_headers_frame( + self.example_request_headers), + ] + ) + @pytest.mark.parametrize("clear_streams", [True, False]) + def test_frames_after_recv_end_will_error(self, + frame_factory, + frame, + clear_streams): + """ + A stream that is closed by receiving END_STREAM raises + ProtocolError when it receives an unexpected frame. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.initiate_connection() + + f = frame_factory.build_headers_frame( + self.example_request_headers, flags=['END_STREAM'] + ) + c.receive_data(f.serialize()) + c.send_headers( + stream_id=1, + headers=self.example_response_headers, + end_stream=True + ) + + if clear_streams: + # Call open_inbound_streams to force the connection to clean + # closed streams. + c.open_inbound_streams + + c.clear_outbound_data_buffer() + + f = frame(self, frame_factory) + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + f = frame_factory.build_goaway_frame( + last_stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize( + "frame", + [ + lambda self, ff: ff.build_headers_frame( + self.example_response_headers, flags=['END_STREAM']), + lambda self, ff: ff.build_headers_frame( + self.example_response_headers), + ] + ) + @pytest.mark.parametrize("clear_streams", [True, False]) + def test_frames_after_send_end_will_error(self, + frame_factory, + frame, + clear_streams): + """ + A stream that is closed by sending END_STREAM raises + ProtocolError when it receives an unexpected frame. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers, + end_stream=True) + + f = frame_factory.build_headers_frame( + self.example_response_headers, flags=['END_STREAM'] + ) + c.receive_data(f.serialize()) + + if clear_streams: + # Call open_outbound_streams to force the connection to clean + # closed streams. + c.open_outbound_streams + + c.clear_outbound_data_buffer() + + f = frame(self, frame_factory) + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + f = frame_factory.build_goaway_frame( + last_stream_id=0, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize( + "frame", + [ + lambda self, ff: ff.build_window_update_frame(1, 1), + lambda self, ff: ff.build_rst_stream_frame(1) + ] + ) + def test_frames_after_send_end_will_be_ignored(self, + frame_factory, + frame): + """ + A stream that is closed by sending END_STREAM will raise + ProtocolError when received unexpected frame. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.initiate_connection() + + f = frame_factory.build_headers_frame( + self.example_request_headers, flags=['END_STREAM'] + ) + c.receive_data(f.serialize()) + c.send_headers( + stream_id=1, + headers=self.example_response_headers, + end_stream=True + ) + + c.clear_outbound_data_buffer() + + f = frame(self, frame_factory) + events = c.receive_data(f.serialize()) + + assert not events + + +class TestStreamsClosedByRstStream(object): + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + example_response_headers = [ + (':status', '200'), + ('server', 'fake-serv/0.1.0') + ] + server_config = h2.config.H2Configuration(client_side=False) + + @pytest.mark.parametrize( + "frame", + [ + lambda self, ff: ff.build_headers_frame( + self.example_request_headers), + lambda self, ff: ff.build_headers_frame( + self.example_request_headers, flags=['END_STREAM']), + ] + ) + def test_resets_further_frames_after_recv_reset(self, + frame_factory, + frame): + """ + A stream that is closed by receive RST_STREAM can receive further + frames: it simply sends RST_STREAM for it, and additionally + WINDOW_UPDATE for DATA frames. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.initiate_connection() + + header_frame = frame_factory.build_headers_frame( + self.example_request_headers, flags=['END_STREAM'] + ) + c.receive_data(header_frame.serialize()) + + c.send_headers( + stream_id=1, + headers=self.example_response_headers, + end_stream=False + ) + + rst_frame = frame_factory.build_rst_stream_frame( + 1, h2.errors.ErrorCodes.STREAM_CLOSED + ) + c.receive_data(rst_frame.serialize()) + c.clear_outbound_data_buffer() + + f = frame(self, frame_factory) + events = c.receive_data(f.serialize()) + + rst_frame = frame_factory.build_rst_stream_frame( + 1, h2.errors.ErrorCodes.STREAM_CLOSED + ) + assert not events + assert c.data_to_send() == rst_frame.serialize() + + events = c.receive_data(f.serialize() * 3) + assert not events + assert c.data_to_send() == rst_frame.serialize() * 3 + + # Iterate over the streams to make sure it's gone, then confirm the + # behaviour is unchanged. + c.open_outbound_streams + + events = c.receive_data(f.serialize() * 3) + assert not events + assert c.data_to_send() == rst_frame.serialize() * 3 + + def test_resets_further_data_frames_after_recv_reset(self, + frame_factory): + """ + A stream that is closed by receive RST_STREAM can receive further + DATA frames: it simply sends WINDOW_UPDATE for the connection flow + window, and RST_STREAM for the stream. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.initiate_connection() + + header_frame = frame_factory.build_headers_frame( + self.example_request_headers, flags=['END_STREAM'] + ) + c.receive_data(header_frame.serialize()) + + c.send_headers( + stream_id=1, + headers=self.example_response_headers, + end_stream=False + ) + + rst_frame = frame_factory.build_rst_stream_frame( + 1, h2.errors.ErrorCodes.STREAM_CLOSED + ) + c.receive_data(rst_frame.serialize()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_data_frame( + data=b'some data' + ) + + events = c.receive_data(f.serialize()) + assert not events + + expected = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ).serialize() + assert c.data_to_send() == expected + + events = c.receive_data(f.serialize() * 3) + assert not events + assert c.data_to_send() == expected * 3 + + # Iterate over the streams to make sure it's gone, then confirm the + # behaviour is unchanged. + c.open_outbound_streams + + events = c.receive_data(f.serialize() * 3) + assert not events + assert c.data_to_send() == expected * 3 + + @pytest.mark.parametrize( + "frame", + [ + lambda self, ff: ff.build_headers_frame( + self.example_request_headers), + lambda self, ff: ff.build_headers_frame( + self.example_request_headers, flags=['END_STREAM']), + ] + ) + def test_resets_further_frames_after_send_reset(self, + frame_factory, + frame): + """ + A stream that is closed by sent RST_STREAM can receive further frames: + it simply sends RST_STREAM for it. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.initiate_connection() + + header_frame = frame_factory.build_headers_frame( + self.example_request_headers, flags=['END_STREAM'] + ) + c.receive_data(header_frame.serialize()) + + c.send_headers( + stream_id=1, + headers=self.example_response_headers, + end_stream=False + ) + + c.reset_stream(1, h2.errors.ErrorCodes.INTERNAL_ERROR) + + rst_frame = frame_factory.build_rst_stream_frame( + 1, h2.errors.ErrorCodes.STREAM_CLOSED + ) + c.clear_outbound_data_buffer() + + f = frame(self, frame_factory) + events = c.receive_data(f.serialize()) + + rst_frame = frame_factory.build_rst_stream_frame( + 1, h2.errors.ErrorCodes.STREAM_CLOSED + ) + assert not events + assert c.data_to_send() == rst_frame.serialize() + + events = c.receive_data(f.serialize() * 3) + assert not events + assert c.data_to_send() == rst_frame.serialize() * 3 + + # Iterate over the streams to make sure it's gone, then confirm the + # behaviour is unchanged. + c.open_outbound_streams + + events = c.receive_data(f.serialize() * 3) + assert not events + assert c.data_to_send() == rst_frame.serialize() * 3 + + def test_resets_further_data_frames_after_send_reset(self, + frame_factory): + """ + A stream that is closed by sent RST_STREAM can receive further + data frames: it simply sends WINDOW_UPDATE and RST_STREAM for it. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.initiate_connection() + + header_frame = frame_factory.build_headers_frame( + self.example_request_headers, flags=['END_STREAM'] + ) + c.receive_data(header_frame.serialize()) + + c.send_headers( + stream_id=1, + headers=self.example_response_headers, + end_stream=False + ) + + c.reset_stream(1, h2.errors.ErrorCodes.INTERNAL_ERROR) + + c.clear_outbound_data_buffer() + + f = frame_factory.build_data_frame( + data=b'some data' + ) + events = c.receive_data(f.serialize()) + assert not events + expected = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ).serialize() + assert c.data_to_send() == expected + + events = c.receive_data(f.serialize() * 3) + assert not events + assert c.data_to_send() == expected * 3 + + # Iterate over the streams to make sure it's gone, then confirm the + # behaviour is unchanged. + c.open_outbound_streams + + events = c.receive_data(f.serialize() * 3) + assert not events + assert c.data_to_send() == expected * 3 diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_complex_logic.py b/testing/web-platform/tests/tools/third_party/h2/test/test_complex_logic.py new file mode 100644 index 0000000000..ff90bb8bf2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_complex_logic.py @@ -0,0 +1,586 @@ +# -*- coding: utf-8 -*- +""" +test_complex_logic +~~~~~~~~~~~~~~~~ + +More complex tests that try to do more. + +Certain tests don't really eliminate incorrect behaviour unless they do quite +a bit. These tests should live here, to keep the pain in once place rather than +hide it in the other parts of the test suite. +""" +import pytest + +import h2 +import h2.config +import h2.connection + + +class TestComplexClient(object): + """ + Complex tests for client-side stacks. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + example_response_headers = [ + (':status', '200'), + ('server', 'fake-serv/0.1.0') + ] + + def test_correctly_count_server_streams(self, frame_factory): + """ + We correctly count the number of server streams, both inbound and + outbound. + """ + # This test makes no sense unless you do both inbound and outbound, + # because it's important to confirm that we count them correctly. + c = h2.connection.H2Connection() + c.initiate_connection() + expected_inbound_streams = expected_outbound_streams = 0 + + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + for stream_id in range(1, 15, 2): + # Open an outbound stream + c.send_headers(stream_id, self.example_request_headers) + expected_outbound_streams += 1 + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + # Receive a pushed stream (to create an inbound one). This doesn't + # open until we also receive headers. + f = frame_factory.build_push_promise_frame( + stream_id=stream_id, + promised_stream_id=stream_id+1, + headers=self.example_request_headers, + ) + c.receive_data(f.serialize()) + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + f = frame_factory.build_headers_frame( + stream_id=stream_id+1, + headers=self.example_response_headers, + ) + c.receive_data(f.serialize()) + expected_inbound_streams += 1 + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + for stream_id in range(13, 0, -2): + # Close an outbound stream. + c.end_stream(stream_id) + + # Stream doesn't close until both sides close it. + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + f = frame_factory.build_headers_frame( + stream_id=stream_id, + headers=self.example_response_headers, + flags=['END_STREAM'], + ) + c.receive_data(f.serialize()) + expected_outbound_streams -= 1 + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + # Pushed streams can only be closed remotely. + f = frame_factory.build_data_frame( + stream_id=stream_id+1, + data=b'the content', + flags=['END_STREAM'], + ) + c.receive_data(f.serialize()) + expected_inbound_streams -= 1 + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + assert c.open_inbound_streams == 0 + assert c.open_outbound_streams == 0 + + +class TestComplexServer(object): + """ + Complex tests for server-side stacks. + """ + example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + ] + example_response_headers = [ + (b':status', b'200'), + (b'server', b'fake-serv/0.1.0') + ] + server_config = h2.config.H2Configuration(client_side=False) + + def test_correctly_count_server_streams(self, frame_factory): + """ + We correctly count the number of server streams, both inbound and + outbound. + """ + # This test makes no sense unless you do both inbound and outbound, + # because it's important to confirm that we count them correctly. + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + expected_inbound_streams = expected_outbound_streams = 0 + + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + for stream_id in range(1, 15, 2): + # Receive an inbound stream. + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=stream_id, + ) + c.receive_data(f.serialize()) + expected_inbound_streams += 1 + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + # Push a stream (to create a outbound one). This doesn't open + # until we send our response headers. + c.push_stream(stream_id, stream_id+1, self.example_request_headers) + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + c.send_headers(stream_id+1, self.example_response_headers) + expected_outbound_streams += 1 + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + for stream_id in range(13, 0, -2): + # Close an inbound stream. + f = frame_factory.build_data_frame( + data=b'', + flags=['END_STREAM'], + stream_id=stream_id, + ) + c.receive_data(f.serialize()) + + # Stream doesn't close until both sides close it. + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + c.send_data(stream_id, b'', end_stream=True) + expected_inbound_streams -= 1 + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + # Pushed streams, however, we can close ourselves. + c.send_data( + stream_id=stream_id+1, + data=b'', + end_stream=True, + ) + expected_outbound_streams -= 1 + assert c.open_inbound_streams == expected_inbound_streams + assert c.open_outbound_streams == expected_outbound_streams + + assert c.open_inbound_streams == 0 + assert c.open_outbound_streams == 0 + + +class TestContinuationFrames(object): + """ + Tests for the relatively complex CONTINUATION frame logic. + """ + example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + ] + server_config = h2.config.H2Configuration(client_side=False) + + def _build_continuation_sequence(self, headers, block_size, frame_factory): + f = frame_factory.build_headers_frame(headers) + header_data = f.data + chunks = [ + header_data[x:x+block_size] + for x in range(0, len(header_data), block_size) + ] + f.data = chunks.pop(0) + frames = [ + frame_factory.build_continuation_frame(c) for c in chunks + ] + f.flags = {'END_STREAM'} + frames[-1].flags.add('END_HEADERS') + frames.insert(0, f) + return frames + + def test_continuation_frame_basic(self, frame_factory): + """ + Test that we correctly decode a header block split across continuation + frames. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + frames = self._build_continuation_sequence( + headers=self.example_request_headers, + block_size=5, + frame_factory=frame_factory, + ) + data = b''.join(f.serialize() for f in frames) + events = c.receive_data(data) + + assert len(events) == 2 + first_event, second_event = events + + assert isinstance(first_event, h2.events.RequestReceived) + assert first_event.headers == self.example_request_headers + assert first_event.stream_id == 1 + + assert isinstance(second_event, h2.events.StreamEnded) + assert second_event.stream_id == 1 + + @pytest.mark.parametrize('stream_id', [3, 1]) + def test_continuation_cannot_interleave_headers(self, + frame_factory, + stream_id): + """ + We cannot interleave a new headers block with a CONTINUATION sequence. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + frames = self._build_continuation_sequence( + headers=self.example_request_headers, + block_size=5, + frame_factory=frame_factory, + ) + assert len(frames) > 2 # This is mostly defensive. + + bogus_frame = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=stream_id, + flags=['END_STREAM'], + ) + frames.insert(len(frames) - 2, bogus_frame) + data = b''.join(f.serialize() for f in frames) + + with pytest.raises(h2.exceptions.ProtocolError) as e: + c.receive_data(data) + + assert "invalid frame" in str(e.value).lower() + + def test_continuation_cannot_interleave_data(self, frame_factory): + """ + We cannot interleave a data frame with a CONTINUATION sequence. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + frames = self._build_continuation_sequence( + headers=self.example_request_headers, + block_size=5, + frame_factory=frame_factory, + ) + assert len(frames) > 2 # This is mostly defensive. + + bogus_frame = frame_factory.build_data_frame( + data=b'hello', + stream_id=1, + ) + frames.insert(len(frames) - 2, bogus_frame) + data = b''.join(f.serialize() for f in frames) + + with pytest.raises(h2.exceptions.ProtocolError) as e: + c.receive_data(data) + + assert "invalid frame" in str(e.value).lower() + + def test_continuation_cannot_interleave_unknown_frame(self, frame_factory): + """ + We cannot interleave an unknown frame with a CONTINUATION sequence. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + frames = self._build_continuation_sequence( + headers=self.example_request_headers, + block_size=5, + frame_factory=frame_factory, + ) + assert len(frames) > 2 # This is mostly defensive. + + bogus_frame = frame_factory.build_data_frame( + data=b'hello', + stream_id=1, + ) + bogus_frame.type = 88 + frames.insert(len(frames) - 2, bogus_frame) + data = b''.join(f.serialize() for f in frames) + + with pytest.raises(h2.exceptions.ProtocolError) as e: + c.receive_data(data) + + assert "invalid frame" in str(e.value).lower() + + def test_continuation_frame_multiple_blocks(self, frame_factory): + """ + Test that we correctly decode several header blocks split across + continuation frames. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + for stream_id in range(1, 7, 2): + frames = self._build_continuation_sequence( + headers=self.example_request_headers, + block_size=2, + frame_factory=frame_factory, + ) + for frame in frames: + frame.stream_id = stream_id + + data = b''.join(f.serialize() for f in frames) + events = c.receive_data(data) + + assert len(events) == 2 + first_event, second_event = events + + assert isinstance(first_event, h2.events.RequestReceived) + assert first_event.headers == self.example_request_headers + assert first_event.stream_id == stream_id + + assert isinstance(second_event, h2.events.StreamEnded) + assert second_event.stream_id == stream_id + + +class TestContinuationFramesPushPromise(object): + """ + Tests for the relatively complex CONTINUATION frame logic working with + PUSH_PROMISE frames. + """ + example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + ] + example_response_headers = [ + (b':status', b'200'), + (b'server', b'fake-serv/0.1.0') + ] + + def _build_continuation_sequence(self, headers, block_size, frame_factory): + f = frame_factory.build_push_promise_frame( + stream_id=1, promised_stream_id=2, headers=headers + ) + header_data = f.data + chunks = [ + header_data[x:x+block_size] + for x in range(0, len(header_data), block_size) + ] + f.data = chunks.pop(0) + frames = [ + frame_factory.build_continuation_frame(c) for c in chunks + ] + f.flags = {'END_STREAM'} + frames[-1].flags.add('END_HEADERS') + frames.insert(0, f) + return frames + + def test_continuation_frame_basic_push_promise(self, frame_factory): + """ + Test that we correctly decode a header block split across continuation + frames when that header block is initiated with a PUSH_PROMISE. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + frames = self._build_continuation_sequence( + headers=self.example_request_headers, + block_size=5, + frame_factory=frame_factory, + ) + data = b''.join(f.serialize() for f in frames) + events = c.receive_data(data) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.PushedStreamReceived) + assert event.headers == self.example_request_headers + assert event.parent_stream_id == 1 + assert event.pushed_stream_id == 2 + + @pytest.mark.parametrize('stream_id', [3, 1, 2]) + def test_continuation_cannot_interleave_headers_pp(self, + frame_factory, + stream_id): + """ + We cannot interleave a new headers block with a CONTINUATION sequence + when the headers block is based on a PUSH_PROMISE frame. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + frames = self._build_continuation_sequence( + headers=self.example_request_headers, + block_size=5, + frame_factory=frame_factory, + ) + assert len(frames) > 2 # This is mostly defensive. + + bogus_frame = frame_factory.build_headers_frame( + headers=self.example_response_headers, + stream_id=stream_id, + flags=['END_STREAM'], + ) + frames.insert(len(frames) - 2, bogus_frame) + data = b''.join(f.serialize() for f in frames) + + with pytest.raises(h2.exceptions.ProtocolError) as e: + c.receive_data(data) + + assert "invalid frame" in str(e.value).lower() + + def test_continuation_cannot_interleave_data(self, frame_factory): + """ + We cannot interleave a data frame with a CONTINUATION sequence when + that sequence began with a PUSH_PROMISE frame. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + frames = self._build_continuation_sequence( + headers=self.example_request_headers, + block_size=5, + frame_factory=frame_factory, + ) + assert len(frames) > 2 # This is mostly defensive. + + bogus_frame = frame_factory.build_data_frame( + data=b'hello', + stream_id=1, + ) + frames.insert(len(frames) - 2, bogus_frame) + data = b''.join(f.serialize() for f in frames) + + with pytest.raises(h2.exceptions.ProtocolError) as e: + c.receive_data(data) + + assert "invalid frame" in str(e.value).lower() + + def test_continuation_cannot_interleave_unknown_frame(self, frame_factory): + """ + We cannot interleave an unknown frame with a CONTINUATION sequence when + that sequence began with a PUSH_PROMISE frame. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + frames = self._build_continuation_sequence( + headers=self.example_request_headers, + block_size=5, + frame_factory=frame_factory, + ) + assert len(frames) > 2 # This is mostly defensive. + + bogus_frame = frame_factory.build_data_frame( + data=b'hello', + stream_id=1, + ) + bogus_frame.type = 88 + frames.insert(len(frames) - 2, bogus_frame) + data = b''.join(f.serialize() for f in frames) + + with pytest.raises(h2.exceptions.ProtocolError) as e: + c.receive_data(data) + + assert "invalid frame" in str(e.value).lower() + + @pytest.mark.parametrize('evict', [True, False]) + def test_stream_remotely_closed_disallows_push_promise(self, + evict, + frame_factory): + """ + Streams closed normally by the remote peer disallow PUSH_PROMISE + frames, and cause a GOAWAY. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, + headers=self.example_request_headers, + end_stream=True + ) + + f = frame_factory.build_headers_frame( + stream_id=1, + headers=self.example_response_headers, + flags=['END_STREAM'] + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + if evict: + # This is annoyingly stateful, but enumerating the list of open + # streams will force us to flush state. + assert not c.open_outbound_streams + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers, + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + f = frame_factory.build_goaway_frame( + last_stream_id=0, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + ) + assert c.data_to_send() == f.serialize() + + def test_continuation_frame_multiple_push_promise(self, frame_factory): + """ + Test that we correctly decode header blocks split across continuation + frames when those header block is initiated with a PUSH_PROMISE, for + more than one pushed stream. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + for promised_stream_id in range(2, 8, 2): + frames = self._build_continuation_sequence( + headers=self.example_request_headers, + block_size=2, + frame_factory=frame_factory, + ) + frames[0].promised_stream_id = promised_stream_id + data = b''.join(f.serialize() for f in frames) + events = c.receive_data(data) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.PushedStreamReceived) + assert event.headers == self.example_request_headers + assert event.parent_stream_id == 1 + assert event.pushed_stream_id == promised_stream_id diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_config.py b/testing/web-platform/tests/tools/third_party/h2/test/test_config.py new file mode 100644 index 0000000000..8eb7fdc862 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_config.py @@ -0,0 +1,130 @@ +# -*- coding: utf-8 -*- +""" +test_config +~~~~~~~~~~~ + +Test the configuration object. +""" +import logging +import pytest + +import h2.config + + +class TestH2Config(object): + """ + Tests of the H2 config object. + """ + def test_defaults(self): + """ + The default values of the HTTP/2 config object are sensible. + """ + config = h2.config.H2Configuration() + assert config.client_side + assert config.header_encoding is None + assert isinstance(config.logger, h2.config.DummyLogger) + + boolean_config_options = [ + 'client_side', + 'validate_outbound_headers', + 'normalize_outbound_headers', + 'validate_inbound_headers', + 'normalize_inbound_headers', + ] + + @pytest.mark.parametrize('option_name', boolean_config_options) + @pytest.mark.parametrize('value', [None, 'False', 1]) + def test_boolean_config_options_reject_non_bools_init( + self, option_name, value + ): + """ + The boolean config options raise an error if you try to set a value + that isn't a boolean via the initializer. + """ + with pytest.raises(ValueError): + h2.config.H2Configuration(**{option_name: value}) + + @pytest.mark.parametrize('option_name', boolean_config_options) + @pytest.mark.parametrize('value', [None, 'False', 1]) + def test_boolean_config_options_reject_non_bools_attr( + self, option_name, value + ): + """ + The boolean config options raise an error if you try to set a value + that isn't a boolean via attribute setter. + """ + config = h2.config.H2Configuration() + with pytest.raises(ValueError): + setattr(config, option_name, value) + + @pytest.mark.parametrize('option_name', boolean_config_options) + @pytest.mark.parametrize('value', [True, False]) + def test_boolean_config_option_is_reflected_init(self, option_name, value): + """ + The value of the boolean config options, when set, is reflected + in the value via the initializer. + """ + config = h2.config.H2Configuration(**{option_name: value}) + assert getattr(config, option_name) == value + + @pytest.mark.parametrize('option_name', boolean_config_options) + @pytest.mark.parametrize('value', [True, False]) + def test_boolean_config_option_is_reflected_attr(self, option_name, value): + """ + The value of the boolean config options, when set, is reflected + in the value via attribute setter. + """ + config = h2.config.H2Configuration() + setattr(config, option_name, value) + assert getattr(config, option_name) == value + + @pytest.mark.parametrize('header_encoding', [True, 1, object()]) + def test_header_encoding_must_be_false_str_none_init( + self, header_encoding + ): + """ + The value of the ``header_encoding`` setting must be False, a string, + or None via the initializer. + """ + with pytest.raises(ValueError): + h2.config.H2Configuration(header_encoding=header_encoding) + + @pytest.mark.parametrize('header_encoding', [True, 1, object()]) + def test_header_encoding_must_be_false_str_none_attr( + self, header_encoding + ): + """ + The value of the ``header_encoding`` setting must be False, a string, + or None via attribute setter. + """ + config = h2.config.H2Configuration() + with pytest.raises(ValueError): + config.header_encoding = header_encoding + + @pytest.mark.parametrize('header_encoding', [False, 'ascii', None]) + def test_header_encoding_is_reflected_init(self, header_encoding): + """ + The value of ``header_encoding``, when set, is reflected in the value + via the initializer. + """ + config = h2.config.H2Configuration(header_encoding=header_encoding) + assert config.header_encoding == header_encoding + + @pytest.mark.parametrize('header_encoding', [False, 'ascii', None]) + def test_header_encoding_is_reflected_attr(self, header_encoding): + """ + The value of ``header_encoding``, when set, is reflected in the value + via the attribute setter. + """ + config = h2.config.H2Configuration() + config.header_encoding = header_encoding + assert config.header_encoding == header_encoding + + def test_logger_instance_is_reflected(self): + """ + The value of ``logger``, when set, is reflected in the value. + """ + logger = logging.Logger('hyper-h2.test') + config = h2.config.H2Configuration() + config.logger = logger + assert config.logger is logger diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_events.py b/testing/web-platform/tests/tools/third_party/h2/test/test_events.py new file mode 100644 index 0000000000..a6e8d83790 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_events.py @@ -0,0 +1,367 @@ +# -*- coding: utf-8 -*- +""" +test_events.py +~~~~~~~~~~~~~~ + +Specific tests for any function that is logically self-contained as part of +events.py. +""" +import inspect +import sys + +from hypothesis import given +from hypothesis.strategies import ( + integers, lists, tuples +) +import pytest + +import h2.errors +import h2.events +import h2.settings + + +# We define a fairly complex Hypothesis strategy here. We want to build a list +# of two tuples of (Setting, value). For Setting we want to make sure we can +# handle settings that the rest of hyper knows nothing about, so we want to +# use integers from 0 to (2**16-1). For values, they're from 0 to (2**32-1). +# Define that strategy here for clarity. +SETTINGS_STRATEGY = lists( + tuples( + integers(min_value=0, max_value=2**16-1), + integers(min_value=0, max_value=2**32-1), + ) +) + + +class TestRemoteSettingsChanged(object): + """ + Validate the function of the RemoteSettingsChanged event. + """ + @given(SETTINGS_STRATEGY) + def test_building_settings_from_scratch(self, settings_list): + """ + Missing old settings are defaulted to None. + """ + settings_dict = dict(settings_list) + e = h2.events.RemoteSettingsChanged.from_settings( + old_settings={}, + new_settings=settings_dict, + ) + + for setting, new_value in settings_dict.items(): + assert e.changed_settings[setting].setting == setting + assert e.changed_settings[setting].original_value is None + assert e.changed_settings[setting].new_value == new_value + + @given(SETTINGS_STRATEGY, SETTINGS_STRATEGY) + def test_only_reports_changed_settings(self, + old_settings_list, + new_settings_list): + """ + Settings that were not changed are not reported. + """ + old_settings_dict = dict(old_settings_list) + new_settings_dict = dict(new_settings_list) + e = h2.events.RemoteSettingsChanged.from_settings( + old_settings=old_settings_dict, + new_settings=new_settings_dict, + ) + + assert len(e.changed_settings) == len(new_settings_dict) + assert ( + sorted(list(e.changed_settings.keys())) == + sorted(list(new_settings_dict.keys())) + ) + + @given(SETTINGS_STRATEGY, SETTINGS_STRATEGY) + def test_correctly_reports_changed_settings(self, + old_settings_list, + new_settings_list): + """ + Settings that are changed are correctly reported. + """ + old_settings_dict = dict(old_settings_list) + new_settings_dict = dict(new_settings_list) + e = h2.events.RemoteSettingsChanged.from_settings( + old_settings=old_settings_dict, + new_settings=new_settings_dict, + ) + + for setting, new_value in new_settings_dict.items(): + original_value = old_settings_dict.get(setting) + assert e.changed_settings[setting].setting == setting + assert e.changed_settings[setting].original_value == original_value + assert e.changed_settings[setting].new_value == new_value + + +class TestEventReprs(object): + """ + Events have useful representations. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + example_informational_headers = [ + (':status', '100'), + ('server', 'fake-serv/0.1.0') + ] + example_response_headers = [ + (':status', '200'), + ('server', 'fake-serv/0.1.0') + ] + + def test_requestreceived_repr(self): + """ + RequestReceived has a useful debug representation. + """ + e = h2.events.RequestReceived() + e.stream_id = 5 + e.headers = self.example_request_headers + + assert repr(e) == ( + "" + ) + + def test_responsereceived_repr(self): + """ + ResponseReceived has a useful debug representation. + """ + e = h2.events.ResponseReceived() + e.stream_id = 500 + e.headers = self.example_response_headers + + assert repr(e) == ( + "" + ) + + def test_trailersreceived_repr(self): + """ + TrailersReceived has a useful debug representation. + """ + e = h2.events.TrailersReceived() + e.stream_id = 62 + e.headers = self.example_response_headers + + assert repr(e) == ( + "" + ) + + def test_informationalresponsereceived_repr(self): + """ + InformationalResponseReceived has a useful debug representation. + """ + e = h2.events.InformationalResponseReceived() + e.stream_id = 62 + e.headers = self.example_informational_headers + + assert repr(e) == ( + "" + ) + + def test_datareceived_repr(self): + """ + DataReceived has a useful debug representation. + """ + e = h2.events.DataReceived() + e.stream_id = 888 + e.data = b"abcdefghijklmnopqrstuvwxyz" + e.flow_controlled_length = 88 + + assert repr(e) == ( + "" + ) + + def test_windowupdated_repr(self): + """ + WindowUpdated has a useful debug representation. + """ + e = h2.events.WindowUpdated() + e.stream_id = 0 + e.delta = 2**16 + + assert repr(e) == "" + + def test_remotesettingschanged_repr(self): + """ + RemoteSettingsChanged has a useful debug representation. + """ + e = h2.events.RemoteSettingsChanged() + e.changed_settings = { + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: + h2.settings.ChangedSetting( + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, 2**16, 2**15 + ), + } + + assert repr(e) == ( + "" + ) + + def test_pingreceived_repr(self): + """ + PingReceived has a useful debug representation. + """ + e = h2.events.PingReceived() + e.ping_data = b'abcdefgh' + + assert repr(e) == "" + + def test_pingackreceived_repr(self): + """ + PingAckReceived has a useful debug representation. + """ + e = h2.events.PingAckReceived() + e.ping_data = b'abcdefgh' + + assert repr(e) == "" + + def test_streamended_repr(self): + """ + StreamEnded has a useful debug representation. + """ + e = h2.events.StreamEnded() + e.stream_id = 99 + + assert repr(e) == "" + + def test_streamreset_repr(self): + """ + StreamEnded has a useful debug representation. + """ + e = h2.events.StreamReset() + e.stream_id = 919 + e.error_code = h2.errors.ErrorCodes.ENHANCE_YOUR_CALM + e.remote_reset = False + + assert repr(e) == ( + "" + ) + + def test_pushedstreamreceived_repr(self): + """ + PushedStreamReceived has a useful debug representation. + """ + e = h2.events.PushedStreamReceived() + e.pushed_stream_id = 50 + e.parent_stream_id = 11 + e.headers = self.example_request_headers + + assert repr(e) == ( + "" + ) + + def test_settingsacknowledged_repr(self): + """ + SettingsAcknowledged has a useful debug representation. + """ + e = h2.events.SettingsAcknowledged() + e.changed_settings = { + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: + h2.settings.ChangedSetting( + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, 2**16, 2**15 + ), + } + + assert repr(e) == ( + "" + ) + + def test_priorityupdated_repr(self): + """ + PriorityUpdated has a useful debug representation. + """ + e = h2.events.PriorityUpdated() + e.stream_id = 87 + e.weight = 32 + e.depends_on = 8 + e.exclusive = True + + assert repr(e) == ( + "" + ) + + @pytest.mark.parametrize("additional_data,data_repr", [ + (None, "None"), + (b'some data', "736f6d652064617461") + ]) + def test_connectionterminated_repr(self, additional_data, data_repr): + """ + ConnectionTerminated has a useful debug representation. + """ + e = h2.events.ConnectionTerminated() + e.error_code = h2.errors.ErrorCodes.INADEQUATE_SECURITY + e.last_stream_id = 33 + e.additional_data = additional_data + + assert repr(e) == ( + "" % data_repr + ) + + def test_alternativeserviceavailable_repr(self): + """ + AlternativeServiceAvailable has a useful debug representation. + """ + e = h2.events.AlternativeServiceAvailable() + e.origin = b"example.com" + e.field_value = b'h2=":8000"; ma=60' + + assert repr(e) == ( + '' + ) + + def test_unknownframereceived_repr(self): + """ + UnknownFrameReceived has a useful debug representation. + """ + e = h2.events.UnknownFrameReceived() + assert repr(e) == '' + + +def all_events(): + """ + Generates all the classes (i.e., events) defined in h2.events. + """ + for _, obj in inspect.getmembers(sys.modules['h2.events']): + + # We are only interested in objects that are defined in h2.events; + # objects that are imported from other modules are not of interest. + if hasattr(obj, '__module__') and (obj.__module__ != 'h2.events'): + continue + + if inspect.isclass(obj): + yield obj + + +@pytest.mark.parametrize('event', all_events()) +def test_all_events_subclass_from_event(event): + """ + Every event defined in h2.events subclasses from h2.events.Event. + """ + assert (event is h2.events.Event) or issubclass(event, h2.events.Event) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_exceptions.py b/testing/web-platform/tests/tools/third_party/h2/test/test_exceptions.py new file mode 100644 index 0000000000..1890459978 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_exceptions.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- +""" +test_exceptions +~~~~~~~~~~~~~~~ + +Tests that verify logic local to exceptions. +""" +import h2.exceptions + + +class TestExceptions(object): + def test_stream_id_too_low_prints_properly(self): + x = h2.exceptions.StreamIDTooLowError(5, 10) + + assert "StreamIDTooLowError: 5 is lower than 10" == str(x) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_flow_control_window.py b/testing/web-platform/tests/tools/third_party/h2/test/test_flow_control_window.py new file mode 100644 index 0000000000..24b345aac3 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_flow_control_window.py @@ -0,0 +1,952 @@ +# -*- coding: utf-8 -*- +""" +test_flow_control +~~~~~~~~~~~~~~~~~ + +Tests of the flow control management in h2 +""" +import pytest + +from hypothesis import given +from hypothesis.strategies import integers + +import h2.config +import h2.connection +import h2.errors +import h2.events +import h2.exceptions +import h2.settings + + +class TestFlowControl(object): + """ + Tests of the flow control management in the connection objects. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + server_config = h2.config.H2Configuration(client_side=False) + + DEFAULT_FLOW_WINDOW = 65535 + + def test_flow_control_initializes_properly(self): + """ + The flow control window for a stream should initially be the default + flow control value. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + + assert c.local_flow_control_window(1) == self.DEFAULT_FLOW_WINDOW + assert c.remote_flow_control_window(1) == self.DEFAULT_FLOW_WINDOW + + def test_flow_control_decreases_with_sent_data(self): + """ + When data is sent on a stream, the flow control window should drop. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + c.send_data(1, b'some data') + + remaining_length = self.DEFAULT_FLOW_WINDOW - len(b'some data') + assert (c.local_flow_control_window(1) == remaining_length) + + @pytest.mark.parametrize("pad_length", [5, 0]) + def test_flow_control_decreases_with_sent_data_with_padding(self, + pad_length): + """ + When padded data is sent on a stream, the flow control window drops + by the length of the padding plus 1 for the 1-byte padding length + field. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + + c.send_data(1, b'some data', pad_length=pad_length) + remaining_length = ( + self.DEFAULT_FLOW_WINDOW - len(b'some data') - pad_length - 1 + ) + assert c.local_flow_control_window(1) == remaining_length + + def test_flow_control_decreases_with_received_data(self, frame_factory): + """ + When data is received on a stream, the remote flow control window + should drop. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f1 = frame_factory.build_headers_frame(self.example_request_headers) + f2 = frame_factory.build_data_frame(b'some data') + + c.receive_data(f1.serialize() + f2.serialize()) + + remaining_length = self.DEFAULT_FLOW_WINDOW - len(b'some data') + assert (c.remote_flow_control_window(1) == remaining_length) + + def test_flow_control_decreases_with_padded_data(self, frame_factory): + """ + When padded data is received on a stream, the remote flow control + window drops by an amount that includes the padding. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f1 = frame_factory.build_headers_frame(self.example_request_headers) + f2 = frame_factory.build_data_frame(b'some data', padding_len=10) + + c.receive_data(f1.serialize() + f2.serialize()) + + remaining_length = ( + self.DEFAULT_FLOW_WINDOW - len(b'some data') - 10 - 1 + ) + assert (c.remote_flow_control_window(1) == remaining_length) + + def test_flow_control_is_limited_by_connection(self): + """ + The flow control window is limited by the flow control of the + connection. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + c.send_data(1, b'some data') + c.send_headers(3, self.example_request_headers) + + remaining_length = self.DEFAULT_FLOW_WINDOW - len(b'some data') + assert (c.local_flow_control_window(3) == remaining_length) + + def test_remote_flow_control_is_limited_by_connection(self, frame_factory): + """ + The remote flow control window is limited by the flow control of the + connection. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + f1 = frame_factory.build_headers_frame(self.example_request_headers) + f2 = frame_factory.build_data_frame(b'some data') + f3 = frame_factory.build_headers_frame( + self.example_request_headers, + stream_id=3, + ) + c.receive_data(f1.serialize() + f2.serialize() + f3.serialize()) + + remaining_length = self.DEFAULT_FLOW_WINDOW - len(b'some data') + assert (c.remote_flow_control_window(3) == remaining_length) + + def test_cannot_send_more_data_than_window(self): + """ + Sending more data than the remaining flow control window raises a + FlowControlError. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + c.outbound_flow_control_window = 5 + + with pytest.raises(h2.exceptions.FlowControlError): + c.send_data(1, b'some data') + + def test_increasing_connection_window_allows_sending(self, frame_factory): + """ + Confirm that sending a WindowUpdate frame on the connection frees + up space for further frames. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + c.outbound_flow_control_window = 5 + + with pytest.raises(h2.exceptions.FlowControlError): + c.send_data(1, b'some data') + + f = frame_factory.build_window_update_frame( + stream_id=0, + increment=5, + ) + c.receive_data(f.serialize()) + + c.clear_outbound_data_buffer() + c.send_data(1, b'some data') + assert c.data_to_send() + + def test_increasing_stream_window_allows_sending(self, frame_factory): + """ + Confirm that sending a WindowUpdate frame on the connection frees + up space for further frames. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + c._get_stream_by_id(1).outbound_flow_control_window = 5 + + with pytest.raises(h2.exceptions.FlowControlError): + c.send_data(1, b'some data') + + f = frame_factory.build_window_update_frame( + stream_id=1, + increment=5, + ) + c.receive_data(f.serialize()) + + c.clear_outbound_data_buffer() + c.send_data(1, b'some data') + assert c.data_to_send() + + def test_flow_control_shrinks_in_response_to_settings(self, frame_factory): + """ + Acknowledging SETTINGS_INITIAL_WINDOW_SIZE shrinks the flow control + window. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + + assert c.local_flow_control_window(1) == 65535 + + f = frame_factory.build_settings_frame( + settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 1280} + ) + c.receive_data(f.serialize()) + + assert c.local_flow_control_window(1) == 1280 + + def test_flow_control_grows_in_response_to_settings(self, frame_factory): + """ + Acknowledging SETTINGS_INITIAL_WINDOW_SIZE grows the flow control + window. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + + # Greatly increase the connection flow control window. + f = frame_factory.build_window_update_frame( + stream_id=0, increment=128000 + ) + c.receive_data(f.serialize()) + + # The stream flow control window is the bottleneck here. + assert c.local_flow_control_window(1) == 65535 + + f = frame_factory.build_settings_frame( + settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000} + ) + c.receive_data(f.serialize()) + + # The stream window is still the bottleneck, but larger now. + assert c.local_flow_control_window(1) == 128000 + + def test_flow_control_settings_blocked_by_conn_window(self, frame_factory): + """ + Changing SETTINGS_INITIAL_WINDOW_SIZE does not affect the effective + flow control window if the connection window isn't changed. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + + assert c.local_flow_control_window(1) == 65535 + + f = frame_factory.build_settings_frame( + settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000} + ) + c.receive_data(f.serialize()) + + assert c.local_flow_control_window(1) == 65535 + + def test_new_streams_have_flow_control_per_settings(self, frame_factory): + """ + After a SETTINGS_INITIAL_WINDOW_SIZE change is received, new streams + have appropriate new flow control windows. + """ + c = h2.connection.H2Connection() + + f = frame_factory.build_settings_frame( + settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000} + ) + c.receive_data(f.serialize()) + + # Greatly increase the connection flow control window. + f = frame_factory.build_window_update_frame( + stream_id=0, increment=128000 + ) + c.receive_data(f.serialize()) + + c.send_headers(1, self.example_request_headers) + assert c.local_flow_control_window(1) == 128000 + + def test_window_update_no_stream(self, frame_factory): + """ + WindowUpdate frames received without streams fire an appropriate + WindowUpdated event. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_window_update_frame( + stream_id=0, + increment=5 + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.WindowUpdated) + assert event.stream_id == 0 + assert event.delta == 5 + + def test_window_update_with_stream(self, frame_factory): + """ + WindowUpdate frames received with streams fire an appropriate + WindowUpdated event. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + f1 = frame_factory.build_headers_frame(self.example_request_headers) + f2 = frame_factory.build_window_update_frame( + stream_id=1, + increment=66 + ) + data = b''.join(map(lambda f: f.serialize(), [f1, f2])) + events = c.receive_data(data) + + assert len(events) == 2 + event = events[1] + + assert isinstance(event, h2.events.WindowUpdated) + assert event.stream_id == 1 + assert event.delta == 66 + + def test_we_can_increment_stream_flow_control(self, frame_factory): + """ + It is possible for the user to increase the flow control window for + streams. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + c.clear_outbound_data_buffer() + + expected_frame = frame_factory.build_window_update_frame( + stream_id=1, + increment=5 + ) + + events = c.increment_flow_control_window(increment=5, stream_id=1) + assert not events + assert c.data_to_send() == expected_frame.serialize() + + def test_we_can_increment_connection_flow_control(self, frame_factory): + """ + It is possible for the user to increase the flow control window for + the entire connection. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + c.clear_outbound_data_buffer() + + expected_frame = frame_factory.build_window_update_frame( + stream_id=0, + increment=5 + ) + + events = c.increment_flow_control_window(increment=5) + assert not events + assert c.data_to_send() == expected_frame.serialize() + + def test_we_enforce_our_flow_control_window(self, frame_factory): + """ + The user can set a low flow control window, which leads to connection + teardown if violated. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + # Change the flow control window to 80 bytes. + c.update_settings( + {h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 80} + ) + f = frame_factory.build_settings_frame({}, ack=True) + c.receive_data(f.serialize()) + + # Receive a new stream. + f = frame_factory.build_headers_frame(self.example_request_headers) + c.receive_data(f.serialize()) + + # Attempt to violate the flow control window. + c.clear_outbound_data_buffer() + f = frame_factory.build_data_frame(b'\x01' * 100) + + with pytest.raises(h2.exceptions.FlowControlError): + c.receive_data(f.serialize()) + + # Verify we tear down appropriately. + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, + error_code=h2.errors.ErrorCodes.FLOW_CONTROL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_shrink_remote_flow_control_settings(self, frame_factory): + """ + The remote peer acknowledging our SETTINGS_INITIAL_WINDOW_SIZE shrinks + the flow control window. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + + assert c.remote_flow_control_window(1) == 65535 + + c.update_settings({h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 1280}) + + f = frame_factory.build_settings_frame({}, ack=True) + c.receive_data(f.serialize()) + + assert c.remote_flow_control_window(1) == 1280 + + def test_grow_remote_flow_control_settings(self, frame_factory): + """ + The remote peer acknowledging our SETTINGS_INITIAL_WINDOW_SIZE grows + the flow control window. + """ + c = h2.connection.H2Connection() + c.send_headers(1, self.example_request_headers) + + # Increase the connection flow control window greatly. + c.increment_flow_control_window(increment=128000) + + assert c.remote_flow_control_window(1) == 65535 + + c.update_settings( + {h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000} + ) + f = frame_factory.build_settings_frame({}, ack=True) + c.receive_data(f.serialize()) + + assert c.remote_flow_control_window(1) == 128000 + + def test_new_streams_have_remote_flow_control(self, frame_factory): + """ + After a SETTINGS_INITIAL_WINDOW_SIZE change is acknowledged by the + remote peer, new streams have appropriate new flow control windows. + """ + c = h2.connection.H2Connection() + + c.update_settings( + {h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000} + ) + f = frame_factory.build_settings_frame({}, ack=True) + c.receive_data(f.serialize()) + + # Increase the connection flow control window greatly. + c.increment_flow_control_window(increment=128000) + + c.send_headers(1, self.example_request_headers) + assert c.remote_flow_control_window(1) == 128000 + + @pytest.mark.parametrize( + 'increment', [0, -15, 2**31] + ) + def test_reject_bad_attempts_to_increment_flow_control(self, increment): + """ + Attempting to increment a flow control increment outside the valid + range causes a ValueError to be raised. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + c.clear_outbound_data_buffer() + + # Fails both on and off streams. + with pytest.raises(ValueError): + c.increment_flow_control_window(increment=increment, stream_id=1) + + with pytest.raises(ValueError): + c.increment_flow_control_window(increment=increment) + + @pytest.mark.parametrize('stream_id', [0, 1]) + def test_reject_bad_remote_increments(self, frame_factory, stream_id): + """ + Remote peers attempting to increment flow control outside the valid + range cause connection errors of type PROTOCOL_ERROR. + """ + # The only number that can be encoded in a WINDOW_UPDATE frame but + # isn't valid is 0. + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + c.clear_outbound_data_buffer() + + f = frame_factory.build_window_update_frame( + stream_id=stream_id, increment=0 + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_reject_increasing_connection_window_too_far(self, frame_factory): + """ + Attempts by the remote peer to increase the connection flow control + window beyond 2**31 - 1 are rejected. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + increment = 2**31 - c.outbound_flow_control_window + + f = frame_factory.build_window_update_frame( + stream_id=0, increment=increment + ) + + with pytest.raises(h2.exceptions.FlowControlError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, + error_code=h2.errors.ErrorCodes.FLOW_CONTROL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_reject_increasing_stream_window_too_far(self, frame_factory): + """ + Attempts by the remote peer to increase the stream flow control window + beyond 2**31 - 1 are rejected. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + c.clear_outbound_data_buffer() + + increment = 2**31 - c.outbound_flow_control_window + + f = frame_factory.build_window_update_frame( + stream_id=1, increment=increment + ) + + events = c.receive_data(f.serialize()) + assert len(events) == 1 + + event = events[0] + assert isinstance(event, h2.events.StreamReset) + assert event.stream_id == 1 + assert event.error_code == h2.errors.ErrorCodes.FLOW_CONTROL_ERROR + assert not event.remote_reset + + expected_frame = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.FLOW_CONTROL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_reject_overlarge_conn_window_settings(self, frame_factory): + """ + SETTINGS frames cannot change the size of the connection flow control + window. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + # Go one byte smaller than the limit. + increment = 2**31 - 1 - c.outbound_flow_control_window + + f = frame_factory.build_window_update_frame( + stream_id=0, increment=increment + ) + c.receive_data(f.serialize()) + + # Receive an increment to the initial window size. + f = frame_factory.build_settings_frame( + settings={ + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: + self.DEFAULT_FLOW_WINDOW + 1 + } + ) + c.clear_outbound_data_buffer() + + # No error is encountered. + events = c.receive_data(f.serialize()) + assert len(events) == 1 + assert isinstance(events[0], h2.events.RemoteSettingsChanged) + + expected_frame = frame_factory.build_settings_frame( + settings={}, + ack=True + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_reject_overlarge_stream_window_settings(self, frame_factory): + """ + Remote attempts to create overlarge stream windows via SETTINGS frames + are rejected. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + + # Go one byte smaller than the limit. + increment = 2**31 - 1 - c.outbound_flow_control_window + + f = frame_factory.build_window_update_frame( + stream_id=1, increment=increment + ) + c.receive_data(f.serialize()) + + # Receive an increment to the initial window size. + f = frame_factory.build_settings_frame( + settings={ + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: + self.DEFAULT_FLOW_WINDOW + 1 + } + ) + c.clear_outbound_data_buffer() + with pytest.raises(h2.exceptions.FlowControlError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, + error_code=h2.errors.ErrorCodes.FLOW_CONTROL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_reject_local_overlarge_increase_connection_window(self): + """ + Local attempts to increase the connection window too far are rejected. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + increment = 2**31 - c.inbound_flow_control_window + + with pytest.raises(h2.exceptions.FlowControlError): + c.increment_flow_control_window(increment=increment) + + def test_reject_local_overlarge_increase_stream_window(self): + """ + Local attempts to increase the connection window too far are rejected. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + + increment = 2**31 - c.inbound_flow_control_window + + with pytest.raises(h2.exceptions.FlowControlError): + c.increment_flow_control_window(increment=increment, stream_id=1) + + def test_send_update_on_closed_streams(self, frame_factory): + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + c.reset_stream(1) + + c.clear_outbound_data_buffer() + c.open_outbound_streams + c.open_inbound_streams + + f = frame_factory.build_data_frame(b'some data'*1500) + events = c.receive_data(f.serialize()*3) + assert not events + + expected = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ).serialize() * 2 + frame_factory.build_window_update_frame( + stream_id=0, + increment=40500, + ).serialize() + frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ).serialize() + assert c.data_to_send() == expected + + f = frame_factory.build_data_frame(b'') + events = c.receive_data(f.serialize()) + assert not events + + expected = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ).serialize() + assert c.data_to_send() == expected + + +class TestAutomaticFlowControl(object): + """ + Tests for the automatic flow control logic. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + server_config = h2.config.H2Configuration(client_side=False) + + DEFAULT_FLOW_WINDOW = 65535 + + def _setup_connection_and_send_headers(self, frame_factory): + """ + Setup a server-side H2Connection and send a headers frame, and then + clear the outbound data buffer. Also increase the maximum frame size. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + c.update_settings( + {h2.settings.SettingCodes.MAX_FRAME_SIZE: self.DEFAULT_FLOW_WINDOW} + ) + settings_frame = frame_factory.build_settings_frame( + settings={}, ack=True + ) + c.receive_data(settings_frame.serialize()) + c.clear_outbound_data_buffer() + + headers_frame = frame_factory.build_headers_frame( + headers=self.example_request_headers + ) + c.receive_data(headers_frame.serialize()) + c.clear_outbound_data_buffer() + return c + + @given(stream_id=integers(max_value=0)) + def test_must_acknowledge_for_stream(self, frame_factory, stream_id): + """ + Flow control acknowledgements must be done on a stream ID that is + greater than zero. + """ + # We need to refresh the encoder because hypothesis has a problem with + # integrating with py.test, meaning that we use the same frame factory + # for all tests. + # See https://github.com/HypothesisWorks/hypothesis-python/issues/377 + frame_factory.refresh_encoder() + + # Create a connection in a state that might actually accept + # data acknolwedgement. + c = self._setup_connection_and_send_headers(frame_factory) + data_frame = frame_factory.build_data_frame( + b'some data', flags=['END_STREAM'] + ) + c.receive_data(data_frame.serialize()) + + with pytest.raises(ValueError): + c.acknowledge_received_data( + acknowledged_size=5, stream_id=stream_id + ) + + @given(size=integers(max_value=-1)) + def test_cannot_acknowledge_less_than_zero(self, frame_factory, size): + """ + The user must acknowledge at least 0 bytes. + """ + # We need to refresh the encoder because hypothesis has a problem with + # integrating with py.test, meaning that we use the same frame factory + # for all tests. + # See https://github.com/HypothesisWorks/hypothesis-python/issues/377 + frame_factory.refresh_encoder() + + # Create a connection in a state that might actually accept + # data acknolwedgement. + c = self._setup_connection_and_send_headers(frame_factory) + data_frame = frame_factory.build_data_frame( + b'some data', flags=['END_STREAM'] + ) + c.receive_data(data_frame.serialize()) + + with pytest.raises(ValueError): + c.acknowledge_received_data(acknowledged_size=size, stream_id=1) + + def test_acknowledging_small_chunks_does_nothing(self, frame_factory): + """ + When a small amount of data is received and acknowledged, no window + update is emitted. + """ + c = self._setup_connection_and_send_headers(frame_factory) + + data_frame = frame_factory.build_data_frame( + b'some data', flags=['END_STREAM'] + ) + data_event = c.receive_data(data_frame.serialize())[0] + + c.acknowledge_received_data( + data_event.flow_controlled_length, stream_id=1 + ) + + assert not c.data_to_send() + + def test_acknowledging_no_data_does_nothing(self, frame_factory): + """ + If a user accidentally acknowledges no data, nothing happens. + """ + c = self._setup_connection_and_send_headers(frame_factory) + + # Send an empty data frame, just to give the user impetus to ack the + # data. + data_frame = frame_factory.build_data_frame(b'') + c.receive_data(data_frame.serialize()) + + c.acknowledge_received_data(0, stream_id=1) + assert not c.data_to_send() + + @pytest.mark.parametrize('force_cleanup', (True, False)) + def test_acknowledging_data_on_closed_stream(self, + frame_factory, + force_cleanup): + """ + When acknowledging data on a stream that has just been closed, no + acknowledgement is given for that stream, only for the connection. + """ + c = self._setup_connection_and_send_headers(frame_factory) + + data_to_send = b'\x00' * self.DEFAULT_FLOW_WINDOW + data_frame = frame_factory.build_data_frame(data_to_send) + c.receive_data(data_frame.serialize()) + + rst_frame = frame_factory.build_rst_stream_frame( + stream_id=1 + ) + c.receive_data(rst_frame.serialize()) + c.clear_outbound_data_buffer() + + if force_cleanup: + # Check how many streams are open to force the old one to be + # cleaned up. + assert c.open_outbound_streams == 0 + + c.acknowledge_received_data(2048, stream_id=1) + + expected = frame_factory.build_window_update_frame( + stream_id=0, increment=2048 + ) + assert c.data_to_send() == expected.serialize() + + def test_acknowledging_streams_we_never_saw(self, frame_factory): + """ + If the user acknowledges a stream ID we've never seen, that raises a + NoSuchStreamError. + """ + c = self._setup_connection_and_send_headers(frame_factory) + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.NoSuchStreamError): + c.acknowledge_received_data(2048, stream_id=101) + + @given(integers(min_value=1025, max_value=DEFAULT_FLOW_WINDOW)) + def test_acknowledging_1024_bytes_when_empty_increments(self, + frame_factory, + increment): + """ + If the flow control window is empty and we acknowledge 1024 bytes or + more, we will emit a WINDOW_UPDATE frame just to move the connection + forward. + """ + # We need to refresh the encoder because hypothesis has a problem with + # integrating with py.test, meaning that we use the same frame factory + # for all tests. + # See https://github.com/HypothesisWorks/hypothesis-python/issues/377 + frame_factory.refresh_encoder() + + c = self._setup_connection_and_send_headers(frame_factory) + + data_to_send = b'\x00' * self.DEFAULT_FLOW_WINDOW + data_frame = frame_factory.build_data_frame(data_to_send) + c.receive_data(data_frame.serialize()) + + c.acknowledge_received_data(increment, stream_id=1) + + first_expected = frame_factory.build_window_update_frame( + stream_id=0, increment=increment + ) + second_expected = frame_factory.build_window_update_frame( + stream_id=1, increment=increment + ) + expected_data = b''.join( + [first_expected.serialize(), second_expected.serialize()] + ) + assert c.data_to_send() == expected_data + + # This test needs to use a lower cap, because otherwise the algo will + # increment the stream window anyway. + @given(integers(min_value=1025, max_value=(DEFAULT_FLOW_WINDOW // 4) - 1)) + def test_connection_only_empty(self, frame_factory, increment): + """ + If the connection flow control window is empty, but the stream flow + control windows aren't, and 1024 bytes or more are acknowledged by the + user, we increment the connection window only. + """ + # We need to refresh the encoder because hypothesis has a problem with + # integrating with py.test, meaning that we use the same frame factory + # for all tests. + # See https://github.com/HypothesisWorks/hypothesis-python/issues/377 + frame_factory.refresh_encoder() + + # Here we'll use 4 streams. Set them up. + c = self._setup_connection_and_send_headers(frame_factory) + + for stream_id in [3, 5, 7]: + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, stream_id=stream_id + ) + c.receive_data(f.serialize()) + + # Now we send 1/4 of the connection window per stream. Annoyingly, + # that's an odd number, so we need to round the last frame up. + data_to_send = b'\x00' * (self.DEFAULT_FLOW_WINDOW // 4) + for stream_id in [1, 3, 5]: + f = frame_factory.build_data_frame( + data_to_send, stream_id=stream_id + ) + c.receive_data(f.serialize()) + + data_to_send = b'\x00' * c.remote_flow_control_window(7) + data_frame = frame_factory.build_data_frame(data_to_send, stream_id=7) + c.receive_data(data_frame.serialize()) + + # Ok, now the actual test. + c.acknowledge_received_data(increment, stream_id=1) + + expected_data = frame_factory.build_window_update_frame( + stream_id=0, increment=increment + ).serialize() + assert c.data_to_send() == expected_data + + @given(integers(min_value=1025, max_value=DEFAULT_FLOW_WINDOW)) + def test_mixing_update_forms(self, frame_factory, increment): + """ + If the user mixes ackowledging data with manually incrementing windows, + we still keep track of what's going on. + """ + # We need to refresh the encoder because hypothesis has a problem with + # integrating with py.test, meaning that we use the same frame factory + # for all tests. + # See https://github.com/HypothesisWorks/hypothesis-python/issues/377 + frame_factory.refresh_encoder() + + # Empty the flow control window. + c = self._setup_connection_and_send_headers(frame_factory) + data_to_send = b'\x00' * self.DEFAULT_FLOW_WINDOW + data_frame = frame_factory.build_data_frame(data_to_send) + c.receive_data(data_frame.serialize()) + + # Manually increment the connection flow control window back to fully + # open, but leave the stream window closed. + c.increment_flow_control_window( + stream_id=None, increment=self.DEFAULT_FLOW_WINDOW + ) + c.clear_outbound_data_buffer() + + # Now, acknowledge the receipt of that data. This should cause the + # stream window to be widened, but not the connection window, because + # it is already open. + c.acknowledge_received_data(increment, stream_id=1) + + # We expect to see one window update frame only, for the stream. + expected_data = frame_factory.build_window_update_frame( + stream_id=1, increment=increment + ).serialize() + assert c.data_to_send() == expected_data diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_h2_upgrade.py b/testing/web-platform/tests/tools/third_party/h2/test/test_h2_upgrade.py new file mode 100644 index 0000000000..d63d44f3f7 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_h2_upgrade.py @@ -0,0 +1,302 @@ +# -*- coding: utf-8 -*- +""" +test_h2_upgrade.py +~~~~~~~~~~~~~~~~~~ + +This module contains tests that exercise the HTTP Upgrade functionality of +hyper-h2, ensuring that clients and servers can upgrade their plaintext +HTTP/1.1 connections to HTTP/2. +""" +import base64 + +import pytest + +import h2.config +import h2.connection +import h2.errors +import h2.events +import h2.exceptions + + +class TestClientUpgrade(object): + """ + Tests of the client-side of the HTTP/2 upgrade dance. + """ + example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + ] + example_response_headers = [ + (b':status', b'200'), + (b'server', b'fake-serv/0.1.0') + ] + + def test_returns_http2_settings(self, frame_factory): + """ + Calling initiate_upgrade_connection returns a base64url encoded + Settings frame with the settings used by the connection. + """ + conn = h2.connection.H2Connection() + data = conn.initiate_upgrade_connection() + + # The base64 encoding must not be padded. + assert not data.endswith(b'=') + + # However, SETTINGS frames should never need to be padded. + decoded_frame = base64.urlsafe_b64decode(data) + expected_frame = frame_factory.build_settings_frame( + settings=conn.local_settings + ) + assert decoded_frame == expected_frame.serialize_body() + + def test_emits_preamble(self, frame_factory): + """ + Calling initiate_upgrade_connection emits the connection preamble. + """ + conn = h2.connection.H2Connection() + conn.initiate_upgrade_connection() + + data = conn.data_to_send() + assert data.startswith(frame_factory.preamble()) + + data = data[len(frame_factory.preamble()):] + expected_frame = frame_factory.build_settings_frame( + settings=conn.local_settings + ) + assert data == expected_frame.serialize() + + def test_can_receive_response(self, frame_factory): + """ + After upgrading, we can safely receive a response. + """ + c = h2.connection.H2Connection() + c.initiate_upgrade_connection() + c.clear_outbound_data_buffer() + + f1 = frame_factory.build_headers_frame( + stream_id=1, + headers=self.example_response_headers, + ) + f2 = frame_factory.build_data_frame( + stream_id=1, + data=b'some data', + flags=['END_STREAM'] + ) + events = c.receive_data(f1.serialize() + f2.serialize()) + assert len(events) == 3 + + assert isinstance(events[0], h2.events.ResponseReceived) + assert isinstance(events[1], h2.events.DataReceived) + assert isinstance(events[2], h2.events.StreamEnded) + + assert events[0].headers == self.example_response_headers + assert events[1].data == b'some data' + assert all(e.stream_id == 1 for e in events) + + assert not c.data_to_send() + + def test_can_receive_pushed_stream(self, frame_factory): + """ + After upgrading, we can safely receive a pushed stream. + """ + c = h2.connection.H2Connection() + c.initiate_upgrade_connection() + c.clear_outbound_data_buffer() + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers, + ) + events = c.receive_data(f.serialize()) + assert len(events) == 1 + + assert isinstance(events[0], h2.events.PushedStreamReceived) + assert events[0].headers == self.example_request_headers + assert events[0].parent_stream_id == 1 + assert events[0].pushed_stream_id == 2 + + def test_cannot_send_headers_stream_1(self, frame_factory): + """ + After upgrading, we cannot send headers on stream 1. + """ + c = h2.connection.H2Connection() + c.initiate_upgrade_connection() + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.send_headers(stream_id=1, headers=self.example_request_headers) + + def test_cannot_send_data_stream_1(self, frame_factory): + """ + After upgrading, we cannot send data on stream 1. + """ + c = h2.connection.H2Connection() + c.initiate_upgrade_connection() + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.send_data(stream_id=1, data=b'some data') + + +class TestServerUpgrade(object): + """ + Tests of the server-side of the HTTP/2 upgrade dance. + """ + example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + ] + example_response_headers = [ + (b':status', b'200'), + (b'server', b'fake-serv/0.1.0') + ] + server_config = h2.config.H2Configuration(client_side=False) + + def test_returns_nothing(self, frame_factory): + """ + Calling initiate_upgrade_connection returns nothing. + """ + conn = h2.connection.H2Connection(config=self.server_config) + curl_header = b"AAMAAABkAAQAAP__" + data = conn.initiate_upgrade_connection(curl_header) + assert data is None + + def test_emits_preamble(self, frame_factory): + """ + Calling initiate_upgrade_connection emits the connection preamble. + """ + conn = h2.connection.H2Connection(config=self.server_config) + conn.initiate_upgrade_connection() + + data = conn.data_to_send() + expected_frame = frame_factory.build_settings_frame( + settings=conn.local_settings + ) + assert data == expected_frame.serialize() + + def test_can_send_response(self, frame_factory): + """ + After upgrading, we can safely send a response. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_upgrade_connection() + c.clear_outbound_data_buffer() + + c.send_headers(stream_id=1, headers=self.example_response_headers) + c.send_data(stream_id=1, data=b'some data', end_stream=True) + + f1 = frame_factory.build_headers_frame( + stream_id=1, + headers=self.example_response_headers, + ) + f2 = frame_factory.build_data_frame( + stream_id=1, + data=b'some data', + flags=['END_STREAM'] + ) + + expected_data = f1.serialize() + f2.serialize() + assert c.data_to_send() == expected_data + + def test_can_push_stream(self, frame_factory): + """ + After upgrading, we can safely push a stream. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_upgrade_connection() + c.clear_outbound_data_buffer() + + c.push_stream( + stream_id=1, + promised_stream_id=2, + request_headers=self.example_request_headers + ) + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers, + ) + assert c.data_to_send() == f.serialize() + + def test_cannot_receive_headers_stream_1(self, frame_factory): + """ + After upgrading, we cannot receive headers on stream 1. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_upgrade_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + stream_id=1, + headers=self.example_request_headers, + ) + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_cannot_receive_data_stream_1(self, frame_factory): + """ + After upgrading, we cannot receive data on stream 1. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_upgrade_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_data_frame( + stream_id=1, + data=b'some data', + ) + c.receive_data(f.serialize()) + + expected = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ).serialize() + assert c.data_to_send() == expected + + def test_client_settings_are_applied(self, frame_factory): + """ + The settings provided by the client are applied and immediately + ACK'ed. + """ + server = h2.connection.H2Connection(config=self.server_config) + client = h2.connection.H2Connection() + + # As a precaution, let's confirm that the server and client, at the + # start of the connection, do not agree on their initial settings + # state. + assert ( + client.local_settings != server.remote_settings + ) + + # Get the client header data and pass it to the server. + header_data = client.initiate_upgrade_connection() + server.initiate_upgrade_connection(header_data) + + # This gets complex, but here we go. + # RFC 7540 § 3.2.1 says that "explicit acknowledgement" of the settings + # in the header is "not necessary". That's annoyingly vague, but we + # interpret that to mean "should not be sent". So to test that this + # worked we need to test that the server has only sent the preamble, + # and has not sent a SETTINGS ack, and also that the server has the + # correct settings. + expected_frame = frame_factory.build_settings_frame( + server.local_settings + ) + assert server.data_to_send() == expected_frame.serialize() + + assert ( + client.local_settings == server.remote_settings + ) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_head_request.py b/testing/web-platform/tests/tools/third_party/h2/test/test_head_request.py new file mode 100644 index 0000000000..ef73007254 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_head_request.py @@ -0,0 +1,55 @@ +# -*- coding; utf-8 -*- +""" +test_head_request +~~~~~~~~~~~~~~~~~ +""" +import h2.connection +import pytest + + +class TestHeadRequest(object): + example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'HEAD'), + ] + + example_response_headers = [ + (b':status', b'200'), + (b'server', b'fake-serv/0.1.0'), + (b'content_length', b'1'), + ] + + def test_non_zero_content_and_no_body(self, frame_factory): + + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + + f = frame_factory.build_headers_frame( + self.example_response_headers, + flags=['END_STREAM'] + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 2 + event = events[0] + + assert isinstance(event, h2.events.ResponseReceived) + assert event.stream_id == 1 + assert event.headers == self.example_response_headers + + def test_reject_non_zero_content_and_body(self, frame_factory): + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers) + + headers = frame_factory.build_headers_frame( + self.example_response_headers + ) + data = frame_factory.build_data_frame(data=b'\x01') + + c.receive_data(headers.serialize()) + with pytest.raises(h2.exceptions.InvalidBodyLengthError): + c.receive_data(data.serialize()) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_header_indexing.py b/testing/web-platform/tests/tools/third_party/h2/test/test_header_indexing.py new file mode 100644 index 0000000000..23fd06f15b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_header_indexing.py @@ -0,0 +1,637 @@ +# -*- coding: utf-8 -*- +""" +test_header_indexing.py +~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains tests that use HPACK header tuples that provide additional +metadata to the hpack module about how to encode the headers. +""" +import pytest + +from hpack import HeaderTuple, NeverIndexedHeaderTuple + +import h2.config +import h2.connection + + +def assert_header_blocks_actually_equal(block_a, block_b): + """ + Asserts that two header bocks are really, truly equal, down to the types + of their tuples. Doesn't return anything. + """ + assert len(block_a) == len(block_b) + + for a, b in zip(block_a, block_b): + assert a == b + assert a.__class__ is b.__class__ + + +class TestHeaderIndexing(object): + """ + Test that Hyper-h2 can correctly handle never indexed header fields using + the appropriate hpack data structures. + """ + example_request_headers = [ + HeaderTuple(u':authority', u'example.com'), + HeaderTuple(u':path', u'/'), + HeaderTuple(u':scheme', u'https'), + HeaderTuple(u':method', u'GET'), + ] + bytes_example_request_headers = [ + HeaderTuple(b':authority', b'example.com'), + HeaderTuple(b':path', b'/'), + HeaderTuple(b':scheme', b'https'), + HeaderTuple(b':method', b'GET'), + ] + + extended_request_headers = [ + HeaderTuple(u':authority', u'example.com'), + HeaderTuple(u':path', u'/'), + HeaderTuple(u':scheme', u'https'), + HeaderTuple(u':method', u'GET'), + NeverIndexedHeaderTuple(u'authorization', u'realpassword'), + ] + bytes_extended_request_headers = [ + HeaderTuple(b':authority', b'example.com'), + HeaderTuple(b':path', b'/'), + HeaderTuple(b':scheme', b'https'), + HeaderTuple(b':method', b'GET'), + NeverIndexedHeaderTuple(b'authorization', b'realpassword'), + ] + + example_response_headers = [ + HeaderTuple(u':status', u'200'), + HeaderTuple(u'server', u'fake-serv/0.1.0') + ] + bytes_example_response_headers = [ + HeaderTuple(b':status', b'200'), + HeaderTuple(b'server', b'fake-serv/0.1.0') + ] + + extended_response_headers = [ + HeaderTuple(u':status', u'200'), + HeaderTuple(u'server', u'fake-serv/0.1.0'), + NeverIndexedHeaderTuple(u'secure', u'you-bet'), + ] + bytes_extended_response_headers = [ + HeaderTuple(b':status', b'200'), + HeaderTuple(b'server', b'fake-serv/0.1.0'), + NeverIndexedHeaderTuple(b'secure', b'you-bet'), + ] + + server_config = h2.config.H2Configuration(client_side=False) + + @pytest.mark.parametrize( + 'headers', ( + example_request_headers, + bytes_example_request_headers, + extended_request_headers, + bytes_extended_request_headers, + ) + ) + def test_sending_header_tuples(self, headers, frame_factory): + """ + Providing HeaderTuple and HeaderTuple subclasses preserves the metadata + about indexing. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + # Clear the data, then send headers. + c.clear_outbound_data_buffer() + c.send_headers(1, headers) + + f = frame_factory.build_headers_frame(headers=headers) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize( + 'headers', ( + example_request_headers, + bytes_example_request_headers, + extended_request_headers, + bytes_extended_request_headers, + ) + ) + def test_header_tuples_in_pushes(self, headers, frame_factory): + """ + Providing HeaderTuple and HeaderTuple subclasses to push promises + preserves metadata about indexing. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + # We can use normal headers for the request. + f = frame_factory.build_headers_frame( + self.example_request_headers + ) + c.receive_data(f.serialize()) + + frame_factory.refresh_encoder() + expected_frame = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=headers, + flags=['END_HEADERS'], + ) + + c.clear_outbound_data_buffer() + c.push_stream( + stream_id=1, + promised_stream_id=2, + request_headers=headers + ) + + assert c.data_to_send() == expected_frame.serialize() + + @pytest.mark.parametrize( + 'headers,encoding', ( + (example_request_headers, 'utf-8'), + (bytes_example_request_headers, None), + (extended_request_headers, 'utf-8'), + (bytes_extended_request_headers, None), + ) + ) + def test_header_tuples_are_decoded_request(self, + headers, + encoding, + frame_factory): + """ + The indexing status of the header is preserved when emitting + RequestReceived events. + """ + config = h2.config.H2Configuration( + client_side=False, header_encoding=encoding + ) + c = h2.connection.H2Connection(config=config) + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame(headers) + data = f.serialize() + events = c.receive_data(data) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.RequestReceived) + assert_header_blocks_actually_equal(headers, event.headers) + + @pytest.mark.parametrize( + 'headers,encoding', ( + (example_response_headers, 'utf-8'), + (bytes_example_response_headers, None), + (extended_response_headers, 'utf-8'), + (bytes_extended_response_headers, None), + ) + ) + def test_header_tuples_are_decoded_response(self, + headers, + encoding, + frame_factory): + """ + The indexing status of the header is preserved when emitting + ResponseReceived events. + """ + config = h2.config.H2Configuration( + header_encoding=encoding + ) + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + f = frame_factory.build_headers_frame(headers) + data = f.serialize() + events = c.receive_data(data) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.ResponseReceived) + assert_header_blocks_actually_equal(headers, event.headers) + + @pytest.mark.parametrize( + 'headers,encoding', ( + (example_response_headers, 'utf-8'), + (bytes_example_response_headers, None), + (extended_response_headers, 'utf-8'), + (bytes_extended_response_headers, None), + ) + ) + def test_header_tuples_are_decoded_info_response(self, + headers, + encoding, + frame_factory): + """ + The indexing status of the header is preserved when emitting + InformationalResponseReceived events. + """ + # Manipulate the headers to send 100 Continue. We need to copy the list + # to avoid breaking the example headers. + headers = headers[:] + if encoding: + headers[0] = HeaderTuple(u':status', u'100') + else: + headers[0] = HeaderTuple(b':status', b'100') + + config = h2.config.H2Configuration( + header_encoding=encoding + ) + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + f = frame_factory.build_headers_frame(headers) + data = f.serialize() + events = c.receive_data(data) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.InformationalResponseReceived) + assert_header_blocks_actually_equal(headers, event.headers) + + @pytest.mark.parametrize( + 'headers,encoding', ( + (example_response_headers, 'utf-8'), + (bytes_example_response_headers, None), + (extended_response_headers, 'utf-8'), + (bytes_extended_response_headers, None), + ) + ) + def test_header_tuples_are_decoded_trailers(self, + headers, + encoding, + frame_factory): + """ + The indexing status of the header is preserved when emitting + TrailersReceived events. + """ + # Manipulate the headers to remove the status, which shouldn't be in + # the trailers. We need to copy the list to avoid breaking the example + # headers. + headers = headers[1:] + + config = h2.config.H2Configuration( + header_encoding=encoding + ) + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + f = frame_factory.build_headers_frame(self.example_response_headers) + data = f.serialize() + c.receive_data(data) + + f = frame_factory.build_headers_frame(headers, flags=['END_STREAM']) + data = f.serialize() + events = c.receive_data(data) + + assert len(events) == 2 + event = events[0] + + assert isinstance(event, h2.events.TrailersReceived) + assert_header_blocks_actually_equal(headers, event.headers) + + @pytest.mark.parametrize( + 'headers,encoding', ( + (example_request_headers, 'utf-8'), + (bytes_example_request_headers, None), + (extended_request_headers, 'utf-8'), + (bytes_extended_request_headers, None), + ) + ) + def test_header_tuples_are_decoded_push_promise(self, + headers, + encoding, + frame_factory): + """ + The indexing status of the header is preserved when emitting + PushedStreamReceived events. + """ + config = h2.config.H2Configuration( + header_encoding=encoding + ) + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=headers, + flags=['END_HEADERS'], + ) + data = f.serialize() + events = c.receive_data(data) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.PushedStreamReceived) + assert_header_blocks_actually_equal(headers, event.headers) + + +class TestSecureHeaders(object): + """ + Certain headers should always be transformed to their never-indexed form. + """ + example_request_headers = [ + (u':authority', u'example.com'), + (u':path', u'/'), + (u':scheme', u'https'), + (u':method', u'GET'), + ] + bytes_example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + ] + possible_auth_headers = [ + (u'authorization', u'test'), + (u'Authorization', u'test'), + (u'authorization', u'really long test'), + HeaderTuple(u'authorization', u'test'), + HeaderTuple(u'Authorization', u'test'), + HeaderTuple(u'authorization', u'really long test'), + NeverIndexedHeaderTuple(u'authorization', u'test'), + NeverIndexedHeaderTuple(u'Authorization', u'test'), + NeverIndexedHeaderTuple(u'authorization', u'really long test'), + (b'authorization', b'test'), + (b'Authorization', b'test'), + (b'authorization', b'really long test'), + HeaderTuple(b'authorization', b'test'), + HeaderTuple(b'Authorization', b'test'), + HeaderTuple(b'authorization', b'really long test'), + NeverIndexedHeaderTuple(b'authorization', b'test'), + NeverIndexedHeaderTuple(b'Authorization', b'test'), + NeverIndexedHeaderTuple(b'authorization', b'really long test'), + (u'proxy-authorization', u'test'), + (u'Proxy-Authorization', u'test'), + (u'proxy-authorization', u'really long test'), + HeaderTuple(u'proxy-authorization', u'test'), + HeaderTuple(u'Proxy-Authorization', u'test'), + HeaderTuple(u'proxy-authorization', u'really long test'), + NeverIndexedHeaderTuple(u'proxy-authorization', u'test'), + NeverIndexedHeaderTuple(u'Proxy-Authorization', u'test'), + NeverIndexedHeaderTuple(u'proxy-authorization', u'really long test'), + (b'proxy-authorization', b'test'), + (b'Proxy-Authorization', b'test'), + (b'proxy-authorization', b'really long test'), + HeaderTuple(b'proxy-authorization', b'test'), + HeaderTuple(b'Proxy-Authorization', b'test'), + HeaderTuple(b'proxy-authorization', b'really long test'), + NeverIndexedHeaderTuple(b'proxy-authorization', b'test'), + NeverIndexedHeaderTuple(b'Proxy-Authorization', b'test'), + NeverIndexedHeaderTuple(b'proxy-authorization', b'really long test'), + ] + secured_cookie_headers = [ + (u'cookie', u'short'), + (u'Cookie', u'short'), + (u'cookie', u'nineteen byte cooki'), + HeaderTuple(u'cookie', u'short'), + HeaderTuple(u'Cookie', u'short'), + HeaderTuple(u'cookie', u'nineteen byte cooki'), + NeverIndexedHeaderTuple(u'cookie', u'short'), + NeverIndexedHeaderTuple(u'Cookie', u'short'), + NeverIndexedHeaderTuple(u'cookie', u'nineteen byte cooki'), + NeverIndexedHeaderTuple(u'cookie', u'longer manually secured cookie'), + (b'cookie', b'short'), + (b'Cookie', b'short'), + (b'cookie', b'nineteen byte cooki'), + HeaderTuple(b'cookie', b'short'), + HeaderTuple(b'Cookie', b'short'), + HeaderTuple(b'cookie', b'nineteen byte cooki'), + NeverIndexedHeaderTuple(b'cookie', b'short'), + NeverIndexedHeaderTuple(b'Cookie', b'short'), + NeverIndexedHeaderTuple(b'cookie', b'nineteen byte cooki'), + NeverIndexedHeaderTuple(b'cookie', b'longer manually secured cookie'), + ] + unsecured_cookie_headers = [ + (u'cookie', u'twenty byte cookie!!'), + (u'Cookie', u'twenty byte cookie!!'), + (u'cookie', u'substantially longer than 20 byte cookie'), + HeaderTuple(u'cookie', u'twenty byte cookie!!'), + HeaderTuple(u'cookie', u'twenty byte cookie!!'), + HeaderTuple(u'Cookie', u'twenty byte cookie!!'), + (b'cookie', b'twenty byte cookie!!'), + (b'Cookie', b'twenty byte cookie!!'), + (b'cookie', b'substantially longer than 20 byte cookie'), + HeaderTuple(b'cookie', b'twenty byte cookie!!'), + HeaderTuple(b'cookie', b'twenty byte cookie!!'), + HeaderTuple(b'Cookie', b'twenty byte cookie!!'), + ] + + server_config = h2.config.H2Configuration(client_side=False) + + @pytest.mark.parametrize( + 'headers', (example_request_headers, bytes_example_request_headers) + ) + @pytest.mark.parametrize('auth_header', possible_auth_headers) + def test_authorization_headers_never_indexed(self, + headers, + auth_header, + frame_factory): + """ + Authorization and Proxy-Authorization headers are always forced to be + never-indexed, regardless of their form. + """ + # Regardless of what we send, we expect it to be never indexed. + send_headers = headers + [auth_header] + expected_headers = headers + [ + NeverIndexedHeaderTuple(auth_header[0].lower(), auth_header[1]) + ] + + c = h2.connection.H2Connection() + c.initiate_connection() + + # Clear the data, then send headers. + c.clear_outbound_data_buffer() + c.send_headers(1, send_headers) + + f = frame_factory.build_headers_frame(headers=expected_headers) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize( + 'headers', (example_request_headers, bytes_example_request_headers) + ) + @pytest.mark.parametrize('auth_header', possible_auth_headers) + def test_authorization_headers_never_indexed_push(self, + headers, + auth_header, + frame_factory): + """ + Authorization and Proxy-Authorization headers are always forced to be + never-indexed, regardless of their form, when pushed by a server. + """ + # Regardless of what we send, we expect it to be never indexed. + send_headers = headers + [auth_header] + expected_headers = headers + [ + NeverIndexedHeaderTuple(auth_header[0].lower(), auth_header[1]) + ] + + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + # We can use normal headers for the request. + f = frame_factory.build_headers_frame( + self.example_request_headers + ) + c.receive_data(f.serialize()) + + frame_factory.refresh_encoder() + expected_frame = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=expected_headers, + flags=['END_HEADERS'], + ) + + c.clear_outbound_data_buffer() + c.push_stream( + stream_id=1, + promised_stream_id=2, + request_headers=send_headers + ) + + assert c.data_to_send() == expected_frame.serialize() + + @pytest.mark.parametrize( + 'headers', (example_request_headers, bytes_example_request_headers) + ) + @pytest.mark.parametrize('cookie_header', secured_cookie_headers) + def test_short_cookie_headers_never_indexed(self, + headers, + cookie_header, + frame_factory): + """ + Short cookie headers, and cookies provided as NeverIndexedHeaderTuple, + are never indexed. + """ + # Regardless of what we send, we expect it to be never indexed. + send_headers = headers + [cookie_header] + expected_headers = headers + [ + NeverIndexedHeaderTuple(cookie_header[0].lower(), cookie_header[1]) + ] + + c = h2.connection.H2Connection() + c.initiate_connection() + + # Clear the data, then send headers. + c.clear_outbound_data_buffer() + c.send_headers(1, send_headers) + + f = frame_factory.build_headers_frame(headers=expected_headers) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize( + 'headers', (example_request_headers, bytes_example_request_headers) + ) + @pytest.mark.parametrize('cookie_header', secured_cookie_headers) + def test_short_cookie_headers_never_indexed_push(self, + headers, + cookie_header, + frame_factory): + """ + Short cookie headers, and cookies provided as NeverIndexedHeaderTuple, + are never indexed when pushed by servers. + """ + # Regardless of what we send, we expect it to be never indexed. + send_headers = headers + [cookie_header] + expected_headers = headers + [ + NeverIndexedHeaderTuple(cookie_header[0].lower(), cookie_header[1]) + ] + + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + # We can use normal headers for the request. + f = frame_factory.build_headers_frame( + self.example_request_headers + ) + c.receive_data(f.serialize()) + + frame_factory.refresh_encoder() + expected_frame = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=expected_headers, + flags=['END_HEADERS'], + ) + + c.clear_outbound_data_buffer() + c.push_stream( + stream_id=1, + promised_stream_id=2, + request_headers=send_headers + ) + + assert c.data_to_send() == expected_frame.serialize() + + @pytest.mark.parametrize( + 'headers', (example_request_headers, bytes_example_request_headers) + ) + @pytest.mark.parametrize('cookie_header', unsecured_cookie_headers) + def test_long_cookie_headers_can_be_indexed(self, + headers, + cookie_header, + frame_factory): + """ + Longer cookie headers can be indexed. + """ + # Regardless of what we send, we expect it to be indexed. + send_headers = headers + [cookie_header] + expected_headers = headers + [ + HeaderTuple(cookie_header[0].lower(), cookie_header[1]) + ] + + c = h2.connection.H2Connection() + c.initiate_connection() + + # Clear the data, then send headers. + c.clear_outbound_data_buffer() + c.send_headers(1, send_headers) + + f = frame_factory.build_headers_frame(headers=expected_headers) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize( + 'headers', (example_request_headers, bytes_example_request_headers) + ) + @pytest.mark.parametrize('cookie_header', unsecured_cookie_headers) + def test_long_cookie_headers_can_be_indexed_push(self, + headers, + cookie_header, + frame_factory): + """ + Longer cookie headers can be indexed. + """ + # Regardless of what we send, we expect it to be never indexed. + send_headers = headers + [cookie_header] + expected_headers = headers + [ + HeaderTuple(cookie_header[0].lower(), cookie_header[1]) + ] + + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + # We can use normal headers for the request. + f = frame_factory.build_headers_frame( + self.example_request_headers + ) + c.receive_data(f.serialize()) + + frame_factory.refresh_encoder() + expected_frame = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=expected_headers, + flags=['END_HEADERS'], + ) + + c.clear_outbound_data_buffer() + c.push_stream( + stream_id=1, + promised_stream_id=2, + request_headers=send_headers + ) + + assert c.data_to_send() == expected_frame.serialize() diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_informational_responses.py b/testing/web-platform/tests/tools/third_party/h2/test/test_informational_responses.py new file mode 100644 index 0000000000..e18c44bcb4 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_informational_responses.py @@ -0,0 +1,444 @@ +# -*- coding: utf-8 -*- +""" +test_informational_responses +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Tests that validate that hyper-h2 correctly handles informational (1XX) +responses in its state machine. +""" +import pytest + +import h2.config +import h2.connection +import h2.events +import h2.exceptions + + +class TestReceivingInformationalResponses(object): + """ + Tests for receiving informational responses. + """ + example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + (b'expect', b'100-continue'), + ] + example_informational_headers = [ + (b':status', b'100'), + (b'server', b'fake-serv/0.1.0') + ] + example_response_headers = [ + (b':status', b'200'), + (b'server', b'fake-serv/0.1.0') + ] + example_trailers = [ + (b'trailer', b'you-bet'), + ] + + @pytest.mark.parametrize('end_stream', (True, False)) + def test_single_informational_response(self, frame_factory, end_stream): + """ + When receiving a informational response, the appropriate event is + signaled. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, + headers=self.example_request_headers, + end_stream=end_stream + ) + + f = frame_factory.build_headers_frame( + headers=self.example_informational_headers, + stream_id=1, + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.InformationalResponseReceived) + assert event.headers == self.example_informational_headers + assert event.stream_id == 1 + + @pytest.mark.parametrize('end_stream', (True, False)) + def test_receiving_multiple_header_blocks(self, frame_factory, end_stream): + """ + At least three header blocks can be received: informational, headers, + trailers. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, + headers=self.example_request_headers, + end_stream=end_stream + ) + + f1 = frame_factory.build_headers_frame( + headers=self.example_informational_headers, + stream_id=1, + ) + f2 = frame_factory.build_headers_frame( + headers=self.example_response_headers, + stream_id=1, + ) + f3 = frame_factory.build_headers_frame( + headers=self.example_trailers, + stream_id=1, + flags=['END_STREAM'], + ) + events = c.receive_data( + f1.serialize() + f2.serialize() + f3.serialize() + ) + + assert len(events) == 4 + + assert isinstance(events[0], h2.events.InformationalResponseReceived) + assert events[0].headers == self.example_informational_headers + assert events[0].stream_id == 1 + + assert isinstance(events[1], h2.events.ResponseReceived) + assert events[1].headers == self.example_response_headers + assert events[1].stream_id == 1 + + assert isinstance(events[2], h2.events.TrailersReceived) + assert events[2].headers == self.example_trailers + assert events[2].stream_id == 1 + + @pytest.mark.parametrize('end_stream', (True, False)) + def test_receiving_multiple_informational_responses(self, + frame_factory, + end_stream): + """ + More than one informational response is allowed. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, + headers=self.example_request_headers, + end_stream=end_stream + ) + + f1 = frame_factory.build_headers_frame( + headers=self.example_informational_headers, + stream_id=1, + ) + f2 = frame_factory.build_headers_frame( + headers=[(':status', '101')], + stream_id=1, + ) + events = c.receive_data(f1.serialize() + f2.serialize()) + + assert len(events) == 2 + + assert isinstance(events[0], h2.events.InformationalResponseReceived) + assert events[0].headers == self.example_informational_headers + assert events[0].stream_id == 1 + + assert isinstance(events[1], h2.events.InformationalResponseReceived) + assert events[1].headers == [(b':status', b'101')] + assert events[1].stream_id == 1 + + @pytest.mark.parametrize('end_stream', (True, False)) + def test_receive_provisional_response_with_end_stream(self, + frame_factory, + end_stream): + """ + Receiving provisional responses with END_STREAM set causes + ProtocolErrors. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, + headers=self.example_request_headers, + end_stream=end_stream + ) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + headers=self.example_informational_headers, + stream_id=1, + flags=['END_STREAM'] + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + expected = frame_factory.build_goaway_frame( + last_stream_id=0, + error_code=1, + ) + assert c.data_to_send() == expected.serialize() + + @pytest.mark.parametrize('end_stream', (True, False)) + def test_receiving_out_of_order_headers(self, frame_factory, end_stream): + """ + When receiving a informational response after the actual response + headers we consider it a ProtocolError and raise it. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, + headers=self.example_request_headers, + end_stream=end_stream + ) + + f1 = frame_factory.build_headers_frame( + headers=self.example_response_headers, + stream_id=1, + ) + f2 = frame_factory.build_headers_frame( + headers=self.example_informational_headers, + stream_id=1, + ) + c.receive_data(f1.serialize()) + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f2.serialize()) + + expected = frame_factory.build_goaway_frame( + last_stream_id=0, + error_code=1, + ) + assert c.data_to_send() == expected.serialize() + + +class TestSendingInformationalResponses(object): + """ + Tests for sending informational responses. + """ + example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + (b'expect', b'100-continue'), + ] + unicode_informational_headers = [ + (u':status', u'100'), + (u'server', u'fake-serv/0.1.0') + ] + bytes_informational_headers = [ + (b':status', b'100'), + (b'server', b'fake-serv/0.1.0') + ] + example_response_headers = [ + (b':status', b'200'), + (b'server', b'fake-serv/0.1.0') + ] + example_trailers = [ + (b'trailer', b'you-bet'), + ] + server_config = h2.config.H2Configuration(client_side=False) + + @pytest.mark.parametrize( + 'hdrs', (unicode_informational_headers, bytes_informational_headers), + ) + @pytest.mark.parametrize('end_stream', (True, False)) + def test_single_informational_response(self, + frame_factory, + hdrs, + end_stream): + """ + When sending a informational response, the appropriate frames are + emitted. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + flags = ['END_STREAM'] if end_stream else [] + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=1, + flags=flags, + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + frame_factory.refresh_encoder() + + c.send_headers( + stream_id=1, + headers=hdrs + ) + + f = frame_factory.build_headers_frame( + headers=hdrs, + stream_id=1, + ) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize( + 'hdrs', (unicode_informational_headers, bytes_informational_headers), + ) + @pytest.mark.parametrize('end_stream', (True, False)) + def test_sending_multiple_header_blocks(self, + frame_factory, + hdrs, + end_stream): + """ + At least three header blocks can be sent: informational, headers, + trailers. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + flags = ['END_STREAM'] if end_stream else [] + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=1, + flags=flags, + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + frame_factory.refresh_encoder() + + # Send the three header blocks. + c.send_headers( + stream_id=1, + headers=hdrs + ) + c.send_headers( + stream_id=1, + headers=self.example_response_headers + ) + c.send_headers( + stream_id=1, + headers=self.example_trailers, + end_stream=True + ) + + # Check that we sent them properly. + f1 = frame_factory.build_headers_frame( + headers=hdrs, + stream_id=1, + ) + f2 = frame_factory.build_headers_frame( + headers=self.example_response_headers, + stream_id=1, + ) + f3 = frame_factory.build_headers_frame( + headers=self.example_trailers, + stream_id=1, + flags=['END_STREAM'] + ) + assert ( + c.data_to_send() == + f1.serialize() + f2.serialize() + f3.serialize() + ) + + @pytest.mark.parametrize( + 'hdrs', (unicode_informational_headers, bytes_informational_headers), + ) + @pytest.mark.parametrize('end_stream', (True, False)) + def test_sending_multiple_informational_responses(self, + frame_factory, + hdrs, + end_stream): + """ + More than one informational response is allowed. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + flags = ['END_STREAM'] if end_stream else [] + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=1, + flags=flags, + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + frame_factory.refresh_encoder() + + # Send two informational responses. + c.send_headers( + stream_id=1, + headers=hdrs, + ) + c.send_headers( + stream_id=1, + headers=[(':status', '101')] + ) + + # Check we sent them both. + f1 = frame_factory.build_headers_frame( + headers=hdrs, + stream_id=1, + ) + f2 = frame_factory.build_headers_frame( + headers=[(':status', '101')], + stream_id=1, + ) + assert c.data_to_send() == f1.serialize() + f2.serialize() + + @pytest.mark.parametrize( + 'hdrs', (unicode_informational_headers, bytes_informational_headers), + ) + @pytest.mark.parametrize('end_stream', (True, False)) + def test_send_provisional_response_with_end_stream(self, + frame_factory, + hdrs, + end_stream): + """ + Sending provisional responses with END_STREAM set causes + ProtocolErrors. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + flags = ['END_STREAM'] if end_stream else [] + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=1, + flags=flags, + ) + c.receive_data(f.serialize()) + + with pytest.raises(h2.exceptions.ProtocolError): + c.send_headers( + stream_id=1, + headers=hdrs, + end_stream=True, + ) + + @pytest.mark.parametrize( + 'hdrs', (unicode_informational_headers, bytes_informational_headers), + ) + @pytest.mark.parametrize('end_stream', (True, False)) + def test_reject_sending_out_of_order_headers(self, + frame_factory, + hdrs, + end_stream): + """ + When sending an informational response after the actual response + headers we consider it a ProtocolError and raise it. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + flags = ['END_STREAM'] if end_stream else [] + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=1, + flags=flags, + ) + c.receive_data(f.serialize()) + + c.send_headers( + stream_id=1, + headers=self.example_response_headers + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.send_headers( + stream_id=1, + headers=hdrs + ) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_interacting_stacks.py b/testing/web-platform/tests/tools/third_party/h2/test/test_interacting_stacks.py new file mode 100644 index 0000000000..90776829c8 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_interacting_stacks.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +""" +test_interacting_stacks +~~~~~~~~~~~~~~~~~~~~~~~ + +These tests run two entities, a client and a server, in parallel threads. These +two entities talk to each other, running what amounts to a number of carefully +controlled simulations of real flows. + +This is to ensure that the stack as a whole behaves intelligently in both +client and server cases. + +These tests are long, complex, and somewhat brittle, so they aren't in general +recommended for writing the majority of test cases. Their purposes is primarily +to validate that the top-level API of the library behaves as described. + +We should also consider writing helper functions to reduce the complexity of +these tests, so that they can be written more easily, as they are remarkably +useful. +""" +import coroutine_tests + +import h2.config +import h2.connection +import h2.events +import h2.settings + + +class TestCommunication(coroutine_tests.CoroutineTestCase): + """ + Test that two communicating state machines can work together. + """ + server_config = h2.config.H2Configuration(client_side=False) + + def test_basic_request_response(self): + """ + A request issued by hyper-h2 can be responded to by hyper-h2. + """ + request_headers = [ + (b':method', b'GET'), + (b':path', b'/'), + (b':authority', b'example.com'), + (b':scheme', b'https'), + (b'user-agent', b'test-client/0.1.0'), + ] + response_headers = [ + (b':status', b'204'), + (b'server', b'test-server/0.1.0'), + (b'content-length', b'0'), + ] + + def client(): + c = h2.connection.H2Connection() + + # Do the handshake. First send the preamble. + c.initiate_connection() + data = yield c.data_to_send() + + # Next, handle the remote preamble. + events = c.receive_data(data) + assert len(events) == 2 + assert isinstance(events[0], h2.events.SettingsAcknowledged) + assert isinstance(events[1], h2.events.RemoteSettingsChanged) + changed = events[1].changed_settings + assert ( + changed[ + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS + ].new_value == 100 + ) + + # Send a request. + events = c.send_headers(1, request_headers, end_stream=True) + assert not events + data = yield c.data_to_send() + + # Validate the response. + events = c.receive_data(data) + assert len(events) == 2 + assert isinstance(events[0], h2.events.ResponseReceived) + assert events[0].stream_id == 1 + assert events[0].headers == response_headers + assert isinstance(events[1], h2.events.StreamEnded) + assert events[1].stream_id == 1 + + @self.server + def server(): + c = h2.connection.H2Connection(config=self.server_config) + + # First, read for the preamble. + data = yield + events = c.receive_data(data) + assert len(events) == 1 + assert isinstance(events[0], h2.events.RemoteSettingsChanged) + changed = events[0].changed_settings + assert ( + changed[ + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS + ].new_value == 100 + ) + + # Send our preamble back. + c.initiate_connection() + data = yield c.data_to_send() + + # Listen for the request. + events = c.receive_data(data) + assert len(events) == 3 + assert isinstance(events[0], h2.events.SettingsAcknowledged) + assert isinstance(events[1], h2.events.RequestReceived) + assert events[1].stream_id == 1 + assert events[1].headers == request_headers + assert isinstance(events[2], h2.events.StreamEnded) + assert events[2].stream_id == 1 + + # Send our response. + events = c.send_headers(1, response_headers, end_stream=True) + assert not events + yield c.data_to_send() + + self.run_until_complete(client(), server()) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_invalid_content_lengths.py b/testing/web-platform/tests/tools/third_party/h2/test/test_invalid_content_lengths.py new file mode 100644 index 0000000000..fe682fcc27 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_invalid_content_lengths.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +""" +test_invalid_content_lengths.py +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains tests that use invalid content lengths, and validates that +they fail appropriately. +""" +import pytest + +import h2.config +import h2.connection +import h2.errors +import h2.events +import h2.exceptions + + +class TestInvalidContentLengths(object): + """ + Hyper-h2 raises Protocol Errors when the content-length sent by a remote + peer is not valid. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'POST'), + ('content-length', '15'), + ] + example_response_headers = [ + (':status', '200'), + ('server', 'fake-serv/0.1.0') + ] + server_config = h2.config.H2Configuration(client_side=False) + + def test_too_much_data(self, frame_factory): + """ + Remote peers sending data in excess of content-length causes Protocol + Errors. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + headers = frame_factory.build_headers_frame( + headers=self.example_request_headers + ) + first_data = frame_factory.build_data_frame(data=b'\x01'*15) + c.receive_data(headers.serialize() + first_data.serialize()) + c.clear_outbound_data_buffer() + + second_data = frame_factory.build_data_frame(data=b'\x01') + with pytest.raises(h2.exceptions.InvalidBodyLengthError) as exp: + c.receive_data(second_data.serialize()) + + assert exp.value.expected_length == 15 + assert exp.value.actual_length == 16 + assert str(exp.value) == ( + "InvalidBodyLengthError: Expected 15 bytes, received 16" + ) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_insufficient_data(self, frame_factory): + """ + Remote peers sending less data than content-length causes Protocol + Errors. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + headers = frame_factory.build_headers_frame( + headers=self.example_request_headers + ) + first_data = frame_factory.build_data_frame(data=b'\x01'*13) + c.receive_data(headers.serialize() + first_data.serialize()) + c.clear_outbound_data_buffer() + + second_data = frame_factory.build_data_frame( + data=b'\x01', + flags=['END_STREAM'], + ) + with pytest.raises(h2.exceptions.InvalidBodyLengthError) as exp: + c.receive_data(second_data.serialize()) + + assert exp.value.expected_length == 15 + assert exp.value.actual_length == 14 + assert str(exp.value) == ( + "InvalidBodyLengthError: Expected 15 bytes, received 14" + ) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_insufficient_data_empty_frame(self, frame_factory): + """ + Remote peers sending less data than content-length where the last data + frame is empty causes Protocol Errors. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + headers = frame_factory.build_headers_frame( + headers=self.example_request_headers + ) + first_data = frame_factory.build_data_frame(data=b'\x01'*14) + c.receive_data(headers.serialize() + first_data.serialize()) + c.clear_outbound_data_buffer() + + second_data = frame_factory.build_data_frame( + data=b'', + flags=['END_STREAM'], + ) + with pytest.raises(h2.exceptions.InvalidBodyLengthError) as exp: + c.receive_data(second_data.serialize()) + + assert exp.value.expected_length == 15 + assert exp.value.actual_length == 14 + assert str(exp.value) == ( + "InvalidBodyLengthError: Expected 15 bytes, received 14" + ) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_invalid_frame_sequences.py b/testing/web-platform/tests/tools/third_party/h2/test/test_invalid_frame_sequences.py new file mode 100644 index 0000000000..12b70c4a6b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_invalid_frame_sequences.py @@ -0,0 +1,488 @@ +# -*- coding: utf-8 -*- +""" +test_invalid_frame_sequences.py +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains tests that use invalid frame sequences, and validates that +they fail appropriately. +""" +import pytest + +import h2.config +import h2.connection +import h2.errors +import h2.events +import h2.exceptions + + +class TestInvalidFrameSequences(object): + """ + Invalid frame sequences, either sent or received, cause ProtocolErrors to + be thrown. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + example_response_headers = [ + (':status', '200'), + ('server', 'fake-serv/0.1.0') + ] + server_config = h2.config.H2Configuration(client_side=False) + + def test_cannot_send_on_closed_stream(self): + """ + When we've closed a stream locally, we cannot send further data. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + + with pytest.raises(h2.exceptions.ProtocolError): + c.send_data(1, b'some data') + + def test_missing_preamble_errors(self): + """ + Server side connections require the preamble. + """ + c = h2.connection.H2Connection(config=self.server_config) + encoded_headers_frame = ( + b'\x00\x00\r\x01\x04\x00\x00\x00\x01' + b'A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x87\x82' + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(encoded_headers_frame) + + def test_server_connections_reject_even_streams(self, frame_factory): + """ + Servers do not allow clients to initiate even-numbered streams. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + self.example_request_headers, stream_id=2 + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + def test_clients_reject_odd_stream_pushes(self, frame_factory): + """ + Clients do not allow servers to push odd numbered streams. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(1, self.example_request_headers, end_stream=True) + + f = frame_factory.build_push_promise_frame( + stream_id=1, + headers=self.example_request_headers, + promised_stream_id=3 + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + def test_can_handle_frames_with_invalid_padding(self, frame_factory): + """ + Frames with invalid padding cause connection teardown. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame(self.example_request_headers) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + invalid_data_frame = ( + b'\x00\x00\x05\x00\x0b\x00\x00\x00\x01\x06\x54\x65\x73\x74' + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(invalid_data_frame) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, error_code=1 + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_receiving_frames_with_insufficent_size(self, frame_factory): + """ + Frames with not enough data cause connection teardown. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + invalid_window_update_frame = ( + b'\x00\x00\x03\x08\x00\x00\x00\x00\x00\x00\x00\x02' + ) + + with pytest.raises(h2.exceptions.FrameDataMissingError): + c.receive_data(invalid_window_update_frame) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, error_code=h2.errors.ErrorCodes.FRAME_SIZE_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_reject_data_on_closed_streams(self, frame_factory): + """ + When a stream is not open to the remote peer, we reject receiving data + frames from them. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + self.example_request_headers, + flags=['END_STREAM'] + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + bad_frame = frame_factory.build_data_frame( + data=b'some data' + ) + c.receive_data(bad_frame.serialize()) + + expected = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ).serialize() + assert c.data_to_send() == expected + + def test_unexpected_continuation_on_closed_stream(self, frame_factory): + """ + CONTINUATION frames received on closed streams cause connection errors + of type PROTOCOL_ERROR. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + self.example_request_headers, + flags=['END_STREAM'] + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + bad_frame = frame_factory.build_continuation_frame( + header_block=b'hello' + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(bad_frame.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + last_stream_id=1 + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_prevent_continuation_dos(self, frame_factory): + """ + Receiving too many CONTINUATION frames in one block causes a protocol + error. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + self.example_request_headers, + ) + f.flags = {'END_STREAM'} + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + # Send 63 additional frames. + for _ in range(0, 63): + extra_frame = frame_factory.build_continuation_frame( + header_block=b'hello' + ) + c.receive_data(extra_frame.serialize()) + + # The final continuation frame should cause a protocol error. + extra_frame = frame_factory.build_continuation_frame( + header_block=b'hello' + ) + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(extra_frame.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, + error_code=0x1, + ) + assert c.data_to_send() == expected_frame.serialize() + + # These settings are a bit annoyingly anonymous, but trust me, they're bad. + @pytest.mark.parametrize( + "settings", + [ + {0x2: 5}, + {0x4: 2**31}, + {0x5: 5}, + {0x5: 2**24}, + ] + ) + def test_reject_invalid_settings_values(self, frame_factory, settings): + """ + When a SETTINGS frame is received with invalid settings values it + causes connection teardown with the appropriate error code. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_settings_frame(settings=settings) + + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + c.receive_data(f.serialize()) + + assert e.value.error_code == ( + h2.errors.ErrorCodes.FLOW_CONTROL_ERROR if 0x4 in settings else + h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + + def test_invalid_frame_headers_are_protocol_errors(self, frame_factory): + """ + When invalid frame headers are received they cause ProtocolErrors to be + raised. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + headers=self.example_request_headers + ) + + # Do some annoying bit twiddling here: the stream ID is currently set + # to '1', change it to '0'. Grab the first 9 bytes (the frame header), + # replace any instances of the byte '\x01', and then graft it onto the + # remaining bytes. + frame_data = f.serialize() + frame_data = frame_data[:9].replace(b'\x01', b'\x00') + frame_data[9:] + + with pytest.raises(h2.exceptions.ProtocolError) as e: + c.receive_data(frame_data) + + assert "Stream ID must be non-zero" in str(e.value) + + def test_get_stream_reset_event_on_auto_reset(self, frame_factory): + """ + When hyper-h2 resets a stream automatically, a StreamReset event fires. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + self.example_request_headers, + flags=['END_STREAM'] + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + bad_frame = frame_factory.build_data_frame( + data=b'some data' + ) + events = c.receive_data(bad_frame.serialize()) + + expected = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ).serialize() + assert c.data_to_send() == expected + + assert len(events) == 1 + event = events[0] + assert isinstance(event, h2.events.StreamReset) + assert event.stream_id == 1 + assert event.error_code == h2.errors.ErrorCodes.STREAM_CLOSED + assert not event.remote_reset + + def test_one_one_stream_reset(self, frame_factory): + """ + When hyper-h2 resets a stream automatically, a StreamReset event fires, + but only for the first reset: the others are silent. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + self.example_request_headers, + flags=['END_STREAM'] + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + bad_frame = frame_factory.build_data_frame( + data=b'some data' + ) + # Receive 5 frames. + events = c.receive_data(bad_frame.serialize() * 5) + + expected = frame_factory.build_rst_stream_frame( + stream_id=1, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ).serialize() + assert c.data_to_send() == expected * 5 + + assert len(events) == 1 + event = events[0] + assert isinstance(event, h2.events.StreamReset) + assert event.stream_id == 1 + assert event.error_code == h2.errors.ErrorCodes.STREAM_CLOSED + assert not event.remote_reset + + @pytest.mark.parametrize('value', ['', 'twelve']) + def test_error_on_invalid_content_length(self, frame_factory, value): + """ + When an invalid content-length is received, a ProtocolError is thrown. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + stream_id=1, + headers=self.example_request_headers + [('content-length', value)] + ) + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_invalid_header_data_protocol_error(self, frame_factory): + """ + If an invalid header block is received, we raise a ProtocolError. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + stream_id=1, + headers=self.example_request_headers + ) + f.data = b'\x00\x00\x00\x00' + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_invalid_push_promise_data_protocol_error(self, frame_factory): + """ + If an invalid header block is received on a PUSH_PROMISE, we raise a + ProtocolError. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + c.clear_outbound_data_buffer() + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers + ) + f.data = b'\x00\x00\x00\x00' + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_cannot_receive_push_on_pushed_stream(self, frame_factory): + """ + If a PUSH_PROMISE frame is received with the parent stream ID being a + pushed stream, this is rejected with a PROTOCOL_ERROR. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, + headers=self.example_request_headers, + end_stream=True + ) + + f1 = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers, + ) + f2 = frame_factory.build_headers_frame( + stream_id=2, + headers=self.example_response_headers, + ) + c.receive_data(f1.serialize() + f2.serialize()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_push_promise_frame( + stream_id=2, + promised_stream_id=4, + headers=self.example_request_headers, + ) + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=2, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_cannot_send_push_on_pushed_stream(self, frame_factory): + """ + If a user tries to send a PUSH_PROMISE frame with the parent stream ID + being a pushed stream, this is rejected with a PROTOCOL_ERROR. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame( + stream_id=1, headers=self.example_request_headers + ) + c.receive_data(f.serialize()) + + c.push_stream( + stream_id=1, + promised_stream_id=2, + request_headers=self.example_request_headers + ) + c.send_headers(stream_id=2, headers=self.example_response_headers) + + with pytest.raises(h2.exceptions.ProtocolError): + c.push_stream( + stream_id=2, + promised_stream_id=4, + request_headers=self.example_request_headers + ) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_invalid_headers.py b/testing/web-platform/tests/tools/third_party/h2/test/test_invalid_headers.py new file mode 100644 index 0000000000..a379950733 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_invalid_headers.py @@ -0,0 +1,952 @@ +# -*- coding: utf-8 -*- +""" +test_invalid_headers.py +~~~~~~~~~~~~~~~~~~~~~~~ + +This module contains tests that use invalid header blocks, and validates that +they fail appropriately. +""" +import itertools + +import pytest + +import h2.config +import h2.connection +import h2.errors +import h2.events +import h2.exceptions +import h2.settings +import h2.utilities + +import hyperframe.frame + +from hypothesis import given +from hypothesis.strategies import binary, lists, tuples + +HEADERS_STRATEGY = lists(tuples(binary(min_size=1), binary())) + + +class TestInvalidFrameSequences(object): + """ + Invalid header sequences cause ProtocolErrors to be thrown when received. + """ + base_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ('user-agent', 'someua/0.0.1'), + ] + invalid_header_blocks = [ + base_request_headers + [('Uppercase', 'name')], + base_request_headers + [(':late', 'pseudo-header')], + [(':path', 'duplicate-pseudo-header')] + base_request_headers, + base_request_headers + [('connection', 'close')], + base_request_headers + [('proxy-connection', 'close')], + base_request_headers + [('keep-alive', 'close')], + base_request_headers + [('transfer-encoding', 'gzip')], + base_request_headers + [('upgrade', 'super-protocol/1.1')], + base_request_headers + [('te', 'chunked')], + base_request_headers + [('host', 'notexample.com')], + base_request_headers + [(' name', 'name with leading space')], + base_request_headers + [('name ', 'name with trailing space')], + base_request_headers + [('name', ' value with leading space')], + base_request_headers + [('name', 'value with trailing space ')], + [header for header in base_request_headers + if header[0] != ':authority'], + [(':protocol', 'websocket')] + base_request_headers, + ] + server_config = h2.config.H2Configuration( + client_side=False, header_encoding='utf-8' + ) + + @pytest.mark.parametrize('headers', invalid_header_blocks) + def test_headers_event(self, frame_factory, headers): + """ + Test invalid headers are rejected with PROTOCOL_ERROR. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame(headers) + data = f.serialize() + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(data) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + @pytest.mark.parametrize('headers', invalid_header_blocks) + def test_push_promise_event(self, frame_factory, headers): + """ + If a PUSH_PROMISE header frame is received with an invalid header block + it is rejected with a PROTOCOL_ERROR. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, headers=self.base_request_headers, end_stream=True + ) + c.clear_outbound_data_buffer() + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=headers + ) + data = f.serialize() + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(data) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + @pytest.mark.parametrize('headers', invalid_header_blocks) + def test_push_promise_skipping_validation(self, frame_factory, headers): + """ + If we have ``validate_inbound_headers`` disabled, then invalid header + blocks in push promise frames are allowed to pass. + """ + config = h2.config.H2Configuration( + client_side=True, + validate_inbound_headers=False, + header_encoding='utf-8' + ) + + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.send_headers( + stream_id=1, headers=self.base_request_headers, end_stream=True + ) + c.clear_outbound_data_buffer() + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=headers + ) + data = f.serialize() + + events = c.receive_data(data) + assert len(events) == 1 + pp_event = events[0] + assert pp_event.headers == headers + + @pytest.mark.parametrize('headers', invalid_header_blocks) + def test_headers_event_skipping_validation(self, frame_factory, headers): + """ + If we have ``validate_inbound_headers`` disabled, then all of these + invalid header blocks are allowed to pass. + """ + config = h2.config.H2Configuration( + client_side=False, + validate_inbound_headers=False, + header_encoding='utf-8' + ) + + c = h2.connection.H2Connection(config=config) + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame(headers) + data = f.serialize() + + events = c.receive_data(data) + assert len(events) == 1 + request_event = events[0] + assert request_event.headers == headers + + def test_transfer_encoding_trailers_is_valid(self, frame_factory): + """ + Transfer-Encoding trailers is allowed by the filter. + """ + headers = ( + self.base_request_headers + [('te', 'trailers')] + ) + + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame(headers) + data = f.serialize() + + events = c.receive_data(data) + assert len(events) == 1 + request_event = events[0] + assert request_event.headers == headers + + def test_pseudo_headers_rejected_in_trailer(self, frame_factory): + """ + Ensure we reject pseudo headers included in trailers + """ + trailers = [(':path', '/'), ('extra', 'value')] + + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + header_frame = frame_factory.build_headers_frame( + self.base_request_headers + ) + trailer_frame = frame_factory.build_headers_frame( + trailers, flags=["END_STREAM"] + ) + head = header_frame.serialize() + trailer = trailer_frame.serialize() + + c.receive_data(head) + # Raise exception if pseudo header in trailer + with pytest.raises(h2.exceptions.ProtocolError) as e: + c.receive_data(trailer) + assert "pseudo-header in trailer" in str(e.value) + + # Test appropriate response frame is generated + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + +class TestSendingInvalidFrameSequences(object): + """ + Trying to send invalid header sequences cause ProtocolErrors to + be thrown. + """ + base_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ('user-agent', 'someua/0.0.1'), + ] + invalid_header_blocks = [ + base_request_headers + [(':late', 'pseudo-header')], + [(':path', 'duplicate-pseudo-header')] + base_request_headers, + base_request_headers + [('te', 'chunked')], + base_request_headers + [('host', 'notexample.com')], + [header for header in base_request_headers + if header[0] != ':authority'], + ] + strippable_header_blocks = [ + base_request_headers + [('connection', 'close')], + base_request_headers + [('proxy-connection', 'close')], + base_request_headers + [('keep-alive', 'close')], + base_request_headers + [('transfer-encoding', 'gzip')], + base_request_headers + [('upgrade', 'super-protocol/1.1')] + ] + all_header_blocks = invalid_header_blocks + strippable_header_blocks + + server_config = h2.config.H2Configuration(client_side=False) + + @pytest.mark.parametrize('headers', invalid_header_blocks) + def test_headers_event(self, frame_factory, headers): + """ + Test sending invalid headers raise a ProtocolError. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + # Clear the data, then try to send headers. + c.clear_outbound_data_buffer() + with pytest.raises(h2.exceptions.ProtocolError): + c.send_headers(1, headers) + + @pytest.mark.parametrize('headers', invalid_header_blocks) + def test_send_push_promise(self, frame_factory, headers): + """ + Sending invalid headers in a push promise raises a ProtocolError. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + header_frame = frame_factory.build_headers_frame( + self.base_request_headers + ) + c.receive_data(header_frame.serialize()) + + # Clear the data, then try to send a push promise. + c.clear_outbound_data_buffer() + with pytest.raises(h2.exceptions.ProtocolError): + c.push_stream( + stream_id=1, promised_stream_id=2, request_headers=headers + ) + + @pytest.mark.parametrize('headers', all_header_blocks) + def test_headers_event_skipping_validation(self, frame_factory, headers): + """ + If we have ``validate_outbound_headers`` disabled, then all of these + invalid header blocks are allowed to pass. + """ + config = h2.config.H2Configuration( + validate_outbound_headers=False + ) + + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + + # Clear the data, then send headers. + c.clear_outbound_data_buffer() + c.send_headers(1, headers) + + # Ensure headers are still normalized. + norm_headers = h2.utilities.normalize_outbound_headers(headers, None) + f = frame_factory.build_headers_frame(norm_headers) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize('headers', all_header_blocks) + def test_push_promise_skipping_validation(self, frame_factory, headers): + """ + If we have ``validate_outbound_headers`` disabled, then all of these + invalid header blocks are allowed to pass. + """ + config = h2.config.H2Configuration( + client_side=False, + validate_outbound_headers=False, + ) + + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + header_frame = frame_factory.build_headers_frame( + self.base_request_headers + ) + c.receive_data(header_frame.serialize()) + + # Create push promise frame with normalized headers. + frame_factory.refresh_encoder() + norm_headers = h2.utilities.normalize_outbound_headers(headers, None) + pp_frame = frame_factory.build_push_promise_frame( + stream_id=1, promised_stream_id=2, headers=norm_headers + ) + + # Clear the data, then send a push promise. + c.clear_outbound_data_buffer() + c.push_stream( + stream_id=1, promised_stream_id=2, request_headers=headers + ) + assert c.data_to_send() == pp_frame.serialize() + + @pytest.mark.parametrize('headers', all_header_blocks) + def test_headers_event_skip_normalization(self, frame_factory, headers): + """ + If we have ``normalize_outbound_headers`` disabled, then all of these + invalid header blocks are sent through unmodified. + """ + config = h2.config.H2Configuration( + validate_outbound_headers=False, + normalize_outbound_headers=False + ) + + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + + f = frame_factory.build_headers_frame( + headers, + stream_id=1, + ) + + # Clear the data, then send headers. + c.clear_outbound_data_buffer() + c.send_headers(1, headers) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize('headers', all_header_blocks) + def test_push_promise_skip_normalization(self, frame_factory, headers): + """ + If we have ``normalize_outbound_headers`` disabled, then all of these + invalid header blocks are allowed to pass unmodified. + """ + config = h2.config.H2Configuration( + client_side=False, + validate_outbound_headers=False, + normalize_outbound_headers=False, + ) + + c = h2.connection.H2Connection(config=config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + header_frame = frame_factory.build_headers_frame( + self.base_request_headers + ) + c.receive_data(header_frame.serialize()) + + frame_factory.refresh_encoder() + pp_frame = frame_factory.build_push_promise_frame( + stream_id=1, promised_stream_id=2, headers=headers + ) + + # Clear the data, then send a push promise. + c.clear_outbound_data_buffer() + c.push_stream( + stream_id=1, promised_stream_id=2, request_headers=headers + ) + assert c.data_to_send() == pp_frame.serialize() + + @pytest.mark.parametrize('headers', strippable_header_blocks) + def test_strippable_headers(self, frame_factory, headers): + """ + Test connection related headers are removed before sending. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + # Clear the data, then try to send headers. + c.clear_outbound_data_buffer() + c.send_headers(1, headers) + + f = frame_factory.build_headers_frame(self.base_request_headers) + assert c.data_to_send() == f.serialize() + + +class TestFilter(object): + """ + Test the filter function directly. + + These tests exists to confirm the behaviour of the filter function in a + wide range of scenarios. Many of these scenarios may not be legal for + HTTP/2 and so may never hit the function, but it's worth validating that it + behaves as expected anyway. + """ + validation_functions = [ + h2.utilities.validate_headers, + h2.utilities.validate_outbound_headers + ] + + hdr_validation_combos = [ + h2.utilities.HeaderValidationFlags( + is_client, is_trailer, is_response_header, is_push_promise + ) + for is_client, is_trailer, is_response_header, is_push_promise in ( + itertools.product([True, False], repeat=4) + ) + ] + + hdr_validation_response_headers = [ + flags for flags in hdr_validation_combos + if flags.is_response_header + ] + + hdr_validation_request_headers_no_trailer = [ + flags for flags in hdr_validation_combos + if not (flags.is_trailer or flags.is_response_header) + ] + + invalid_request_header_blocks_bytes = ( + # First, missing :method + ( + (b':authority', b'google.com'), + (b':path', b'/'), + (b':scheme', b'https'), + ), + # Next, missing :path + ( + (b':authority', b'google.com'), + (b':method', b'GET'), + (b':scheme', b'https'), + ), + # Next, missing :scheme + ( + (b':authority', b'google.com'), + (b':method', b'GET'), + (b':path', b'/'), + ), + # Finally, path present but empty. + ( + (b':authority', b'google.com'), + (b':method', b'GET'), + (b':scheme', b'https'), + (b':path', b''), + ), + ) + invalid_request_header_blocks_unicode = ( + # First, missing :method + ( + (u':authority', u'google.com'), + (u':path', u'/'), + (u':scheme', u'https'), + ), + # Next, missing :path + ( + (u':authority', u'google.com'), + (u':method', u'GET'), + (u':scheme', u'https'), + ), + # Next, missing :scheme + ( + (u':authority', u'google.com'), + (u':method', u'GET'), + (u':path', u'/'), + ), + # Finally, path present but empty. + ( + (u':authority', u'google.com'), + (u':method', u'GET'), + (u':scheme', u'https'), + (u':path', u''), + ), + ) + + # All headers that are forbidden from either request or response blocks. + forbidden_request_headers_bytes = (b':status',) + forbidden_request_headers_unicode = (u':status',) + forbidden_response_headers_bytes = ( + b':path', b':scheme', b':authority', b':method' + ) + forbidden_response_headers_unicode = ( + u':path', u':scheme', u':authority', u':method' + ) + + @pytest.mark.parametrize('validation_function', validation_functions) + @pytest.mark.parametrize('hdr_validation_flags', hdr_validation_combos) + @given(headers=HEADERS_STRATEGY) + def test_range_of_acceptable_outputs(self, + headers, + validation_function, + hdr_validation_flags): + """ + The header validation functions either return the data unchanged + or throw a ProtocolError. + """ + try: + assert headers == list(validation_function( + headers, hdr_validation_flags)) + except h2.exceptions.ProtocolError: + assert True + + @pytest.mark.parametrize('hdr_validation_flags', hdr_validation_combos) + def test_invalid_pseudo_headers(self, hdr_validation_flags): + headers = [(b':custom', b'value')] + with pytest.raises(h2.exceptions.ProtocolError): + list(h2.utilities.validate_headers(headers, hdr_validation_flags)) + + @pytest.mark.parametrize('validation_function', validation_functions) + @pytest.mark.parametrize( + 'hdr_validation_flags', hdr_validation_request_headers_no_trailer + ) + def test_matching_authority_host_headers(self, + validation_function, + hdr_validation_flags): + """ + If a header block has :authority and Host headers and they match, + the headers should pass through unchanged. + """ + headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + (b'host', b'example.com'), + ] + assert headers == list(h2.utilities.validate_headers( + headers, hdr_validation_flags + )) + + @pytest.mark.parametrize( + 'hdr_validation_flags', hdr_validation_response_headers + ) + def test_response_header_without_status(self, hdr_validation_flags): + headers = [(b'content-length', b'42')] + with pytest.raises(h2.exceptions.ProtocolError): + list(h2.utilities.validate_headers(headers, hdr_validation_flags)) + + @pytest.mark.parametrize( + 'hdr_validation_flags', hdr_validation_request_headers_no_trailer + ) + @pytest.mark.parametrize( + 'header_block', + ( + invalid_request_header_blocks_bytes + + invalid_request_header_blocks_unicode + ) + ) + def test_outbound_req_header_missing_pseudo_headers(self, + hdr_validation_flags, + header_block): + with pytest.raises(h2.exceptions.ProtocolError): + list( + h2.utilities.validate_outbound_headers( + header_block, hdr_validation_flags + ) + ) + + @pytest.mark.parametrize( + 'hdr_validation_flags', hdr_validation_request_headers_no_trailer + ) + @pytest.mark.parametrize( + 'header_block', invalid_request_header_blocks_bytes + ) + def test_inbound_req_header_missing_pseudo_headers(self, + hdr_validation_flags, + header_block): + with pytest.raises(h2.exceptions.ProtocolError): + list( + h2.utilities.validate_headers( + header_block, hdr_validation_flags + ) + ) + + @pytest.mark.parametrize( + 'hdr_validation_flags', hdr_validation_request_headers_no_trailer + ) + @pytest.mark.parametrize( + 'invalid_header', + forbidden_request_headers_bytes + forbidden_request_headers_unicode + ) + def test_outbound_req_header_extra_pseudo_headers(self, + hdr_validation_flags, + invalid_header): + """ + Outbound request header blocks containing the forbidden request headers + fail validation. + """ + headers = [ + (b':path', b'/'), + (b':scheme', b'https'), + (b':authority', b'google.com'), + (b':method', b'GET'), + ] + headers.append((invalid_header, b'some value')) + with pytest.raises(h2.exceptions.ProtocolError): + list( + h2.utilities.validate_outbound_headers( + headers, hdr_validation_flags + ) + ) + + @pytest.mark.parametrize( + 'hdr_validation_flags', hdr_validation_request_headers_no_trailer + ) + @pytest.mark.parametrize( + 'invalid_header', + forbidden_request_headers_bytes + ) + def test_inbound_req_header_extra_pseudo_headers(self, + hdr_validation_flags, + invalid_header): + """ + Inbound request header blocks containing the forbidden request headers + fail validation. + """ + headers = [ + (b':path', b'/'), + (b':scheme', b'https'), + (b':authority', b'google.com'), + (b':method', b'GET'), + ] + headers.append((invalid_header, b'some value')) + with pytest.raises(h2.exceptions.ProtocolError): + list(h2.utilities.validate_headers(headers, hdr_validation_flags)) + + @pytest.mark.parametrize( + 'hdr_validation_flags', hdr_validation_response_headers + ) + @pytest.mark.parametrize( + 'invalid_header', + forbidden_response_headers_bytes + forbidden_response_headers_unicode + ) + def test_outbound_resp_header_extra_pseudo_headers(self, + hdr_validation_flags, + invalid_header): + """ + Outbound response header blocks containing the forbidden response + headers fail validation. + """ + headers = [(b':status', b'200')] + headers.append((invalid_header, b'some value')) + with pytest.raises(h2.exceptions.ProtocolError): + list( + h2.utilities.validate_outbound_headers( + headers, hdr_validation_flags + ) + ) + + @pytest.mark.parametrize( + 'hdr_validation_flags', hdr_validation_response_headers + ) + @pytest.mark.parametrize( + 'invalid_header', + forbidden_response_headers_bytes + ) + def test_inbound_resp_header_extra_pseudo_headers(self, + hdr_validation_flags, + invalid_header): + """ + Inbound response header blocks containing the forbidden response + headers fail validation. + """ + headers = [(b':status', b'200')] + headers.append((invalid_header, b'some value')) + with pytest.raises(h2.exceptions.ProtocolError): + list(h2.utilities.validate_headers(headers, hdr_validation_flags)) + + +class TestOversizedHeaders(object): + """ + Tests that oversized header blocks are correctly rejected. This replicates + the "HPACK Bomb" attack, and confirms that we're resistant against it. + """ + request_header_block = [ + (b':method', b'GET'), + (b':authority', b'example.com'), + (b':scheme', b'https'), + (b':path', b'/'), + ] + + response_header_block = [ + (b':status', b'200'), + ] + + # The first header block contains a single header that fills the header + # table. To do that, we'll give it a single-character header name and a + # 4063 byte header value. This will make it exactly the size of the header + # table. It must come last, so that it evicts all other headers. + # This block must be appended to either a request or response block. + first_header_block = [ + (b'a', b'a' * 4063), + ] + + # The second header "block" is actually a custom HEADERS frame body that + # simply repeatedly refers to the first entry for 16kB. Each byte has the + # high bit set (0x80), and then uses the remaining 7 bits to encode the + # number 62 (0x3e), leading to a repeat of the byte 0xbe. + second_header_block = b'\xbe' * 2**14 + + server_config = h2.config.H2Configuration(client_side=False) + + def test_hpack_bomb_request(self, frame_factory): + """ + A HPACK bomb request causes the connection to be torn down with the + error code ENHANCE_YOUR_CALM. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + self.request_header_block + self.first_header_block + ) + data = f.serialize() + c.receive_data(data) + + # Build the attack payload. + attack_frame = hyperframe.frame.HeadersFrame(stream_id=3) + attack_frame.data = self.second_header_block + attack_frame.flags.add('END_HEADERS') + data = attack_frame.serialize() + + with pytest.raises(h2.exceptions.DenialOfServiceError): + c.receive_data(data) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_hpack_bomb_response(self, frame_factory): + """ + A HPACK bomb response causes the connection to be torn down with the + error code ENHANCE_YOUR_CALM. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, headers=self.request_header_block + ) + c.send_headers( + stream_id=3, headers=self.request_header_block + ) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + self.response_header_block + self.first_header_block + ) + data = f.serialize() + c.receive_data(data) + + # Build the attack payload. + attack_frame = hyperframe.frame.HeadersFrame(stream_id=3) + attack_frame.data = self.second_header_block + attack_frame.flags.add('END_HEADERS') + data = attack_frame.serialize() + + with pytest.raises(h2.exceptions.DenialOfServiceError): + c.receive_data(data) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_hpack_bomb_push(self, frame_factory): + """ + A HPACK bomb push causes the connection to be torn down with the + error code ENHANCE_YOUR_CALM. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, headers=self.request_header_block + ) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + self.response_header_block + self.first_header_block + ) + data = f.serialize() + c.receive_data(data) + + # Build the attack payload. We need to shrink it by four bytes because + # the promised_stream_id consumes four bytes of body. + attack_frame = hyperframe.frame.PushPromiseFrame(stream_id=3) + attack_frame.promised_stream_id = 2 + attack_frame.data = self.second_header_block[:-4] + attack_frame.flags.add('END_HEADERS') + data = attack_frame.serialize() + + with pytest.raises(h2.exceptions.DenialOfServiceError): + c.receive_data(data) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=0, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_reject_headers_when_list_size_shrunk(self, frame_factory): + """ + When we've shrunk the header list size, we reject new header blocks + that violate the new size. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + # Receive the first request, which causes no problem. + f = frame_factory.build_headers_frame( + stream_id=1, + headers=self.request_header_block + ) + data = f.serialize() + c.receive_data(data) + + # Now, send a settings change. It's un-ACKed at this time. A new + # request arrives, also without incident. + c.update_settings({h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 50}) + c.clear_outbound_data_buffer() + f = frame_factory.build_headers_frame( + stream_id=3, + headers=self.request_header_block + ) + data = f.serialize() + c.receive_data(data) + + # We get a SETTINGS ACK. + f = frame_factory.build_settings_frame({}, ack=True) + data = f.serialize() + c.receive_data(data) + + # Now a third request comes in. This explodes. + f = frame_factory.build_headers_frame( + stream_id=5, + headers=self.request_header_block + ) + data = f.serialize() + + with pytest.raises(h2.exceptions.DenialOfServiceError): + c.receive_data(data) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=3, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_reject_headers_when_table_size_shrunk(self, frame_factory): + """ + When we've shrunk the header table size, we reject header blocks that + do not respect the change. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + # Receive the first request, which causes no problem. + f = frame_factory.build_headers_frame( + stream_id=1, + headers=self.request_header_block + ) + data = f.serialize() + c.receive_data(data) + + # Now, send a settings change. It's un-ACKed at this time. A new + # request arrives, also without incident. + c.update_settings({h2.settings.SettingCodes.HEADER_TABLE_SIZE: 128}) + c.clear_outbound_data_buffer() + f = frame_factory.build_headers_frame( + stream_id=3, + headers=self.request_header_block + ) + data = f.serialize() + c.receive_data(data) + + # We get a SETTINGS ACK. + f = frame_factory.build_settings_frame({}, ack=True) + data = f.serialize() + c.receive_data(data) + + # Now a third request comes in. This explodes, as it does not contain + # a dynamic table size update. + f = frame_factory.build_headers_frame( + stream_id=5, + headers=self.request_header_block + ) + data = f.serialize() + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(data) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=3, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() + + def test_reject_headers_exceeding_table_size(self, frame_factory): + """ + When the remote peer sends a dynamic table size update that exceeds our + setting, we reject it. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + # Receive the first request, which causes no problem. + f = frame_factory.build_headers_frame( + stream_id=1, + headers=self.request_header_block + ) + data = f.serialize() + c.receive_data(data) + + # Now a second request comes in that sets the table size too high. + # This explodes. + frame_factory.change_table_size(c.local_settings.header_table_size + 1) + f = frame_factory.build_headers_frame( + stream_id=5, + headers=self.request_header_block + ) + data = f.serialize() + + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(data) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + ) + assert c.data_to_send() == expected_frame.serialize() diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_priority.py b/testing/web-platform/tests/tools/third_party/h2/test/test_priority.py new file mode 100644 index 0000000000..cbc7332253 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_priority.py @@ -0,0 +1,358 @@ +# -*- coding: utf-8 -*- +""" +test_priority +~~~~~~~~~~~~~ + +Test the priority logic of Hyper-h2. +""" +import pytest + +import h2.config +import h2.connection +import h2.errors +import h2.events +import h2.exceptions +import h2.stream + + +class TestPriority(object): + """ + Basic priority tests. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + example_response_headers = [ + (':status', '200'), + ('server', 'pytest-h2'), + ] + server_config = h2.config.H2Configuration(client_side=False) + + def test_receiving_priority_emits_priority_update(self, frame_factory): + """ + Receiving a priority frame emits a PriorityUpdated event. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_priority_frame( + stream_id=1, + weight=255, + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + assert not c.data_to_send() + + event = events[0] + assert isinstance(event, h2.events.PriorityUpdated) + assert event.stream_id == 1 + assert event.depends_on == 0 + assert event.weight == 256 + assert event.exclusive is False + + def test_headers_with_priority_info(self, frame_factory): + """ + Receiving a HEADERS frame with priority information on it emits a + PriorityUpdated event. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=3, + flags=['PRIORITY'], + stream_weight=15, + depends_on=1, + exclusive=True, + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 2 + assert not c.data_to_send() + + event = events[1] + assert isinstance(event, h2.events.PriorityUpdated) + assert event.stream_id == 3 + assert event.depends_on == 1 + assert event.weight == 16 + assert event.exclusive is True + + def test_streams_may_not_depend_on_themselves(self, frame_factory): + """ + A stream adjusted to depend on itself causes a Protocol Error. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=3, + flags=['PRIORITY'], + stream_weight=15, + depends_on=1, + exclusive=True, + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_priority_frame( + stream_id=3, + depends_on=3, + weight=15 + ) + with pytest.raises(h2.exceptions.ProtocolError): + c.receive_data(f.serialize()) + + expected_frame = frame_factory.build_goaway_frame( + last_stream_id=3, + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, + ) + assert c.data_to_send() == expected_frame.serialize() + + @pytest.mark.parametrize( + 'depends_on,weight,exclusive', + [ + (0, 256, False), + (3, 128, False), + (3, 128, True), + ] + ) + def test_can_prioritize_stream(self, depends_on, weight, exclusive, + frame_factory): + """ + hyper-h2 can emit priority frames. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + c.send_headers(headers=self.example_request_headers, stream_id=1) + c.send_headers(headers=self.example_request_headers, stream_id=3) + c.clear_outbound_data_buffer() + + c.prioritize( + stream_id=1, + depends_on=depends_on, + weight=weight, + exclusive=exclusive + ) + + f = frame_factory.build_priority_frame( + stream_id=1, + weight=weight - 1, + depends_on=depends_on, + exclusive=exclusive, + ) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize( + 'depends_on,weight,exclusive', + [ + (0, 256, False), + (1, 128, False), + (1, 128, True), + ] + ) + def test_emit_headers_with_priority_info(self, depends_on, weight, + exclusive, frame_factory): + """ + It is possible to send a headers frame with priority information on + it. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + c.send_headers( + headers=self.example_request_headers, + stream_id=3, + priority_weight=weight, + priority_depends_on=depends_on, + priority_exclusive=exclusive, + ) + + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=3, + flags=['PRIORITY'], + stream_weight=weight - 1, + depends_on=depends_on, + exclusive=exclusive, + ) + assert c.data_to_send() == f.serialize() + + def test_may_not_prioritize_stream_to_depend_on_self(self, frame_factory): + """ + A stream adjusted to depend on itself causes a Protocol Error. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.send_headers( + headers=self.example_request_headers, + stream_id=3, + priority_weight=255, + priority_depends_on=0, + priority_exclusive=False, + ) + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.prioritize( + stream_id=3, + depends_on=3, + ) + + assert not c.data_to_send() + + def test_may_not_initially_set_stream_depend_on_self(self, frame_factory): + """ + A stream that starts by depending on itself causes a Protocol Error. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.send_headers( + headers=self.example_request_headers, + stream_id=3, + priority_depends_on=3, + ) + + assert not c.data_to_send() + + @pytest.mark.parametrize('weight', [0, -15, 257]) + def test_prioritize_requires_valid_weight(self, weight): + """ + A call to prioritize with an invalid weight causes a ProtocolError. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.prioritize(stream_id=1, weight=weight) + + assert not c.data_to_send() + + @pytest.mark.parametrize('weight', [0, -15, 257]) + def test_send_headers_requires_valid_weight(self, weight): + """ + A call to send_headers with an invalid weight causes a ProtocolError. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.send_headers( + stream_id=1, + headers=self.example_request_headers, + priority_weight=weight + ) + + assert not c.data_to_send() + + def test_prioritize_defaults(self, frame_factory): + """ + When prioritize() is called with no explicit arguments, it emits a + weight of 16, depending on stream zero non-exclusively. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + c.prioritize(stream_id=1) + + f = frame_factory.build_priority_frame( + stream_id=1, + weight=15, + depends_on=0, + exclusive=False, + ) + assert c.data_to_send() == f.serialize() + + @pytest.mark.parametrize( + 'priority_kwargs', + [ + {'priority_weight': 16}, + {'priority_depends_on': 0}, + {'priority_exclusive': False}, + ] + ) + def test_send_headers_defaults(self, priority_kwargs, frame_factory): + """ + When send_headers() is called with only one explicit argument, it emits + default values for everything else. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + c.send_headers( + stream_id=1, + headers=self.example_request_headers, + **priority_kwargs + ) + + f = frame_factory.build_headers_frame( + headers=self.example_request_headers, + stream_id=1, + flags=['PRIORITY'], + stream_weight=15, + depends_on=0, + exclusive=False, + ) + assert c.data_to_send() == f.serialize() + + def test_servers_cannot_prioritize(self, frame_factory): + """ + Server stacks are not allowed to call ``prioritize()``. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + stream_id=1, + headers=self.example_request_headers, + ) + c.receive_data(f.serialize()) + + with pytest.raises(h2.exceptions.RFC1122Error): + c.prioritize(stream_id=1) + + def test_servers_cannot_prioritize_with_headers(self, frame_factory): + """ + Server stacks are not allowed to prioritize on headers either. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + stream_id=1, + headers=self.example_request_headers, + ) + c.receive_data(f.serialize()) + + with pytest.raises(h2.exceptions.RFC1122Error): + c.send_headers( + stream_id=1, + headers=self.example_response_headers, + priority_weight=16, + ) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_related_events.py b/testing/web-platform/tests/tools/third_party/h2/test/test_related_events.py new file mode 100644 index 0000000000..eb6b878905 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_related_events.py @@ -0,0 +1,370 @@ +# -*- coding: utf-8 -*- +""" +test_related_events.py +~~~~~~~~~~~~~~~~~~~~~~ + +Specific tests to validate the "related events" logic used by certain events +inside hyper-h2. +""" +import h2.config +import h2.connection +import h2.events + + +class TestRelatedEvents(object): + """ + Related events correlate all those events that happen on a single frame. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + + example_response_headers = [ + (':status', '200'), + ('server', 'fake-serv/0.1.0') + ] + + informational_response_headers = [ + (':status', '100'), + ('server', 'fake-serv/0.1.0') + ] + + example_trailers = [ + ('another', 'field'), + ] + + server_config = h2.config.H2Configuration(client_side=False) + + def test_request_received_related_all(self, frame_factory): + """ + RequestReceived has two possible related events: PriorityUpdated and + StreamEnded, all fired when a single HEADERS frame is received. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + input_frame = frame_factory.build_headers_frame( + headers=self.example_request_headers, + flags=['END_STREAM', 'PRIORITY'], + stream_weight=15, + depends_on=0, + exclusive=False, + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 3 + base_event = events[0] + other_events = events[1:] + + assert base_event.stream_ended in other_events + assert isinstance(base_event.stream_ended, h2.events.StreamEnded) + assert base_event.priority_updated in other_events + assert isinstance( + base_event.priority_updated, h2.events.PriorityUpdated + ) + + def test_request_received_related_priority(self, frame_factory): + """ + RequestReceived can be related to PriorityUpdated. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + input_frame = frame_factory.build_headers_frame( + headers=self.example_request_headers, + flags=['PRIORITY'], + stream_weight=15, + depends_on=0, + exclusive=False, + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 2 + base_event = events[0] + priority_updated_event = events[1] + + assert base_event.priority_updated is priority_updated_event + assert base_event.stream_ended is None + assert isinstance( + base_event.priority_updated, h2.events.PriorityUpdated + ) + + def test_request_received_related_stream_ended(self, frame_factory): + """ + RequestReceived can be related to StreamEnded. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + input_frame = frame_factory.build_headers_frame( + headers=self.example_request_headers, + flags=['END_STREAM'], + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 2 + base_event = events[0] + stream_ended_event = events[1] + + assert base_event.stream_ended is stream_ended_event + assert base_event.priority_updated is None + assert isinstance(base_event.stream_ended, h2.events.StreamEnded) + + def test_response_received_related_nothing(self, frame_factory): + """ + ResponseReceived is ordinarily related to no events. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + input_frame = frame_factory.build_headers_frame( + headers=self.example_response_headers, + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 1 + base_event = events[0] + + assert base_event.stream_ended is None + assert base_event.priority_updated is None + + def test_response_received_related_all(self, frame_factory): + """ + ResponseReceived has two possible related events: PriorityUpdated and + StreamEnded, all fired when a single HEADERS frame is received. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + input_frame = frame_factory.build_headers_frame( + headers=self.example_response_headers, + flags=['END_STREAM', 'PRIORITY'], + stream_weight=15, + depends_on=0, + exclusive=False, + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 3 + base_event = events[0] + other_events = events[1:] + + assert base_event.stream_ended in other_events + assert isinstance(base_event.stream_ended, h2.events.StreamEnded) + assert base_event.priority_updated in other_events + assert isinstance( + base_event.priority_updated, h2.events.PriorityUpdated + ) + + def test_response_received_related_priority(self, frame_factory): + """ + ResponseReceived can be related to PriorityUpdated. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + input_frame = frame_factory.build_headers_frame( + headers=self.example_response_headers, + flags=['PRIORITY'], + stream_weight=15, + depends_on=0, + exclusive=False, + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 2 + base_event = events[0] + priority_updated_event = events[1] + + assert base_event.priority_updated is priority_updated_event + assert base_event.stream_ended is None + assert isinstance( + base_event.priority_updated, h2.events.PriorityUpdated + ) + + def test_response_received_related_stream_ended(self, frame_factory): + """ + ResponseReceived can be related to StreamEnded. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + input_frame = frame_factory.build_headers_frame( + headers=self.example_response_headers, + flags=['END_STREAM'], + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 2 + base_event = events[0] + stream_ended_event = events[1] + + assert base_event.stream_ended is stream_ended_event + assert base_event.priority_updated is None + assert isinstance(base_event.stream_ended, h2.events.StreamEnded) + + def test_trailers_received_related_all(self, frame_factory): + """ + TrailersReceived has two possible related events: PriorityUpdated and + StreamEnded, all fired when a single HEADERS frame is received. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + f = frame_factory.build_headers_frame( + headers=self.example_response_headers, + ) + c.receive_data(f.serialize()) + + input_frame = frame_factory.build_headers_frame( + headers=self.example_trailers, + flags=['END_STREAM', 'PRIORITY'], + stream_weight=15, + depends_on=0, + exclusive=False, + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 3 + base_event = events[0] + other_events = events[1:] + + assert base_event.stream_ended in other_events + assert isinstance(base_event.stream_ended, h2.events.StreamEnded) + assert base_event.priority_updated in other_events + assert isinstance( + base_event.priority_updated, h2.events.PriorityUpdated + ) + + def test_trailers_received_related_stream_ended(self, frame_factory): + """ + TrailersReceived can be related to StreamEnded by itself. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + f = frame_factory.build_headers_frame( + headers=self.example_response_headers, + ) + c.receive_data(f.serialize()) + + input_frame = frame_factory.build_headers_frame( + headers=self.example_trailers, + flags=['END_STREAM'], + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 2 + base_event = events[0] + stream_ended_event = events[1] + + assert base_event.stream_ended is stream_ended_event + assert base_event.priority_updated is None + assert isinstance(base_event.stream_ended, h2.events.StreamEnded) + + def test_informational_response_related_nothing(self, frame_factory): + """ + InformationalResponseReceived in the standard case is related to + nothing. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + input_frame = frame_factory.build_headers_frame( + headers=self.informational_response_headers, + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 1 + base_event = events[0] + + assert base_event.priority_updated is None + + def test_informational_response_received_related_all(self, frame_factory): + """ + InformationalResponseReceived has one possible related event: + PriorityUpdated, fired when a single HEADERS frame is received. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + input_frame = frame_factory.build_headers_frame( + headers=self.informational_response_headers, + flags=['PRIORITY'], + stream_weight=15, + depends_on=0, + exclusive=False, + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 2 + base_event = events[0] + priority_updated_event = events[1] + + assert base_event.priority_updated is priority_updated_event + assert isinstance( + base_event.priority_updated, h2.events.PriorityUpdated + ) + + def test_data_received_normally_relates_to_nothing(self, frame_factory): + """ + A plain DATA frame leads to DataReceieved with no related events. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + f = frame_factory.build_headers_frame( + headers=self.example_response_headers, + ) + c.receive_data(f.serialize()) + + input_frame = frame_factory.build_data_frame( + data=b'some data', + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 1 + base_event = events[0] + + assert base_event.stream_ended is None + + def test_data_received_related_stream_ended(self, frame_factory): + """ + DataReceived can be related to StreamEnded by itself. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + f = frame_factory.build_headers_frame( + headers=self.example_response_headers, + ) + c.receive_data(f.serialize()) + + input_frame = frame_factory.build_data_frame( + data=b'some data', + flags=['END_STREAM'], + ) + events = c.receive_data(input_frame.serialize()) + + assert len(events) == 2 + base_event = events[0] + stream_ended_event = events[1] + + assert base_event.stream_ended is stream_ended_event + assert isinstance(base_event.stream_ended, h2.events.StreamEnded) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_rfc7838.py b/testing/web-platform/tests/tools/third_party/h2/test/test_rfc7838.py new file mode 100644 index 0000000000..d7704e2345 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_rfc7838.py @@ -0,0 +1,447 @@ +# -*- coding: utf-8 -*- +""" +test_rfc7838 +~~~~~~~~~~~~ + +Test the RFC 7838 ALTSVC support. +""" +import pytest + +import h2.config +import h2.connection +import h2.events +import h2.exceptions + + +class TestRFC7838Client(object): + """ + Tests that the client supports receiving the RFC 7838 AltSvc frame. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + example_response_headers = [ + (u':status', u'200'), + (u'server', u'fake-serv/0.1.0') + ] + + def test_receiving_altsvc_stream_zero(self, frame_factory): + """ + An ALTSVC frame received on stream zero correctly transposes all the + fields from the frames. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + f = frame_factory.build_alt_svc_frame( + stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.AlternativeServiceAvailable) + assert event.origin == b"example.com" + assert event.field_value == b'h2=":8000"; ma=60' + + # No data gets sent. + assert not c.data_to_send() + + def test_receiving_altsvc_stream_zero_no_origin(self, frame_factory): + """ + An ALTSVC frame received on stream zero without an origin field is + ignored. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + f = frame_factory.build_alt_svc_frame( + stream_id=0, origin=b"", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f.serialize()) + + assert not events + assert not c.data_to_send() + + def test_receiving_altsvc_on_stream(self, frame_factory): + """ + An ALTSVC frame received on a stream correctly transposes all the + fields from the frame and attaches the expected origin. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + c.clear_outbound_data_buffer() + + f = frame_factory.build_alt_svc_frame( + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.AlternativeServiceAvailable) + assert event.origin == b"example.com" + assert event.field_value == b'h2=":8000"; ma=60' + + # No data gets sent. + assert not c.data_to_send() + + def test_receiving_altsvc_on_stream_with_origin(self, frame_factory): + """ + An ALTSVC frame received on a stream with an origin field present gets + ignored. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + c.clear_outbound_data_buffer() + + f = frame_factory.build_alt_svc_frame( + stream_id=1, origin=b"example.com", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 0 + assert not c.data_to_send() + + def test_receiving_altsvc_on_stream_not_yet_opened(self, frame_factory): + """ + When an ALTSVC frame is received on a stream the client hasn't yet + opened, the frame is ignored. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.clear_outbound_data_buffer() + + # We'll test this twice, once on a client-initiated stream ID and once + # on a server initiated one. + f1 = frame_factory.build_alt_svc_frame( + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + ) + f2 = frame_factory.build_alt_svc_frame( + stream_id=2, origin=b"", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f1.serialize() + f2.serialize()) + + assert len(events) == 0 + assert not c.data_to_send() + + def test_receiving_altsvc_before_sending_headers(self, frame_factory): + """ + When an ALTSVC frame is received but the client hasn't sent headers yet + it gets ignored. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + # We need to create the idle stream. We have to do it by calling + # a private API. While this can't naturally happen in hyper-h2 (we + # don't currently have a mechanism by which this could occur), it could + # happen in the future and we defend against it. + c._begin_new_stream( + stream_id=1, allowed_ids=h2.connection.AllowedStreamIDs.ODD + ) + c.clear_outbound_data_buffer() + + f = frame_factory.build_alt_svc_frame( + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 0 + assert not c.data_to_send() + + def test_receiving_altsvc_after_receiving_headers(self, frame_factory): + """ + When an ALTSVC frame is received but the server has already sent + headers it gets ignored. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + f = frame_factory.build_headers_frame( + headers=self.example_response_headers + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_alt_svc_frame( + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 0 + assert not c.data_to_send() + + def test_receiving_altsvc_on_closed_stream(self, frame_factory): + """ + When an ALTSVC frame is received on a closed stream, we ignore it. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers( + stream_id=1, headers=self.example_request_headers, end_stream=True + ) + + f = frame_factory.build_headers_frame( + headers=self.example_response_headers, + flags=['END_STREAM'], + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_alt_svc_frame( + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 0 + assert not c.data_to_send() + + def test_receiving_altsvc_on_pushed_stream(self, frame_factory): + """ + When an ALTSVC frame is received on a stream that the server pushed, + the frame is accepted. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_alt_svc_frame( + stream_id=2, origin=b"", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 1 + event = events[0] + + assert isinstance(event, h2.events.AlternativeServiceAvailable) + assert event.origin == b"example.com" + assert event.field_value == b'h2=":8000"; ma=60' + + # No data gets sent. + assert not c.data_to_send() + + def test_cannot_send_explicit_alternative_service(self, frame_factory): + """ + A client cannot send an explicit alternative service. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.advertise_alternative_service( + field_value=b'h2=":8000"; ma=60', + origin=b"example.com", + ) + + def test_cannot_send_implicit_alternative_service(self, frame_factory): + """ + A client cannot send an implicit alternative service. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.advertise_alternative_service( + field_value=b'h2=":8000"; ma=60', + stream_id=1, + ) + + +class TestRFC7838Server(object): + """ + Tests that the server supports sending the RFC 7838 AltSvc frame. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + example_response_headers = [ + (u':status', u'200'), + (u'server', u'fake-serv/0.1.0') + ] + + server_config = h2.config.H2Configuration(client_side=False) + + def test_receiving_altsvc_as_server_stream_zero(self, frame_factory): + """ + When an ALTSVC frame is received on stream zero and we are a server, + we ignore it. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_alt_svc_frame( + stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 0 + assert not c.data_to_send() + + def test_receiving_altsvc_as_server_on_stream(self, frame_factory): + """ + When an ALTSVC frame is received on a stream and we are a server, we + ignore it. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + headers=self.example_request_headers + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + f = frame_factory.build_alt_svc_frame( + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + ) + events = c.receive_data(f.serialize()) + + assert len(events) == 0 + assert not c.data_to_send() + + def test_sending_explicit_alternative_service(self, frame_factory): + """ + A server can send an explicit alternative service. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + c.advertise_alternative_service( + field_value=b'h2=":8000"; ma=60', + origin=b"example.com", + ) + + f = frame_factory.build_alt_svc_frame( + stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60' + ) + assert c.data_to_send() == f.serialize() + + def test_sending_implicit_alternative_service(self, frame_factory): + """ + A server can send an implicit alternative service. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + headers=self.example_request_headers + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + c.advertise_alternative_service( + field_value=b'h2=":8000"; ma=60', + stream_id=1, + ) + + f = frame_factory.build_alt_svc_frame( + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + ) + assert c.data_to_send() == f.serialize() + + def test_no_implicit_alternative_service_before_headers(self, + frame_factory): + """ + If headers haven't been received yet, the server forbids sending an + implicit alternative service. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.advertise_alternative_service( + field_value=b'h2=":8000"; ma=60', + stream_id=1, + ) + + def test_no_implicit_alternative_service_after_response(self, + frame_factory): + """ + If the server has sent response headers, hyper-h2 forbids sending an + implicit alternative service. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + f = frame_factory.build_headers_frame( + headers=self.example_request_headers + ) + c.receive_data(f.serialize()) + c.send_headers(stream_id=1, headers=self.example_response_headers) + c.clear_outbound_data_buffer() + + with pytest.raises(h2.exceptions.ProtocolError): + c.advertise_alternative_service( + field_value=b'h2=":8000"; ma=60', + stream_id=1, + ) + + def test_cannot_provide_origin_and_stream_id(self, frame_factory): + """ + The user cannot provide both the origin and stream_id arguments when + advertising alternative services. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame( + headers=self.example_request_headers + ) + c.receive_data(f.serialize()) + + with pytest.raises(ValueError): + c.advertise_alternative_service( + field_value=b'h2=":8000"; ma=60', + origin=b"example.com", + stream_id=1, + ) + + def test_cannot_provide_unicode_altsvc_field(self, frame_factory): + """ + The user cannot provide the field value for alternative services as a + unicode string. + """ + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + + with pytest.raises(ValueError): + c.advertise_alternative_service( + field_value=u'h2=":8000"; ma=60', + origin=b"example.com", + ) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_settings.py b/testing/web-platform/tests/tools/third_party/h2/test/test_settings.py new file mode 100644 index 0000000000..d19f93a7c2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_settings.py @@ -0,0 +1,470 @@ +# -*- coding: utf-8 -*- +""" +test_settings +~~~~~~~~~~~~~ + +Test the Settings object. +""" +import pytest + +import h2.errors +import h2.exceptions +import h2.settings + +from hypothesis import given, assume +from hypothesis.strategies import ( + integers, booleans, fixed_dictionaries, builds +) + + +class TestSettings(object): + """ + Test the Settings object behaves as expected. + """ + def test_settings_defaults_client(self): + """ + The Settings object begins with the appropriate defaults for clients. + """ + s = h2.settings.Settings(client=True) + + assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096 + assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 1 + assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535 + assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384 + assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 0 + + def test_settings_defaults_server(self): + """ + The Settings object begins with the appropriate defaults for servers. + """ + s = h2.settings.Settings(client=False) + + assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096 + assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 0 + assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535 + assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384 + assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 0 + + @pytest.mark.parametrize('client', [True, False]) + def test_can_set_initial_values(self, client): + """ + The Settings object can be provided initial values that override the + defaults. + """ + overrides = { + h2.settings.SettingCodes.HEADER_TABLE_SIZE: 8080, + h2.settings.SettingCodes.MAX_FRAME_SIZE: 16388, + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100, + h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 2**16, + h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL: 1, + } + s = h2.settings.Settings(client=client, initial_values=overrides) + + assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 8080 + assert s[h2.settings.SettingCodes.ENABLE_PUSH] == bool(client) + assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535 + assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16388 + assert s[h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS] == 100 + assert s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] == 2**16 + assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 1 + + @pytest.mark.parametrize( + 'setting,value', + [ + (h2.settings.SettingCodes.ENABLE_PUSH, 2), + (h2.settings.SettingCodes.ENABLE_PUSH, -1), + (h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, -1), + (h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, 2**34), + (h2.settings.SettingCodes.MAX_FRAME_SIZE, 1), + (h2.settings.SettingCodes.MAX_FRAME_SIZE, 2**30), + (h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE, -1), + (h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL, -1), + ] + ) + def test_cannot_set_invalid_initial_values(self, setting, value): + """ + The Settings object can be provided initial values that override the + defaults. + """ + overrides = {setting: value} + + with pytest.raises(h2.exceptions.InvalidSettingsValueError): + h2.settings.Settings(initial_values=overrides) + + def test_applying_value_doesnt_take_effect_immediately(self): + """ + When a value is applied to the settings object, it doesn't immediately + take effect. + """ + s = h2.settings.Settings(client=True) + s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 8000 + + assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096 + + def test_acknowledging_values(self): + """ + When we acknowledge settings, the values change. + """ + s = h2.settings.Settings(client=True) + old_settings = dict(s) + + new_settings = { + h2.settings.SettingCodes.HEADER_TABLE_SIZE: 4000, + h2.settings.SettingCodes.ENABLE_PUSH: 0, + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 60, + h2.settings.SettingCodes.MAX_FRAME_SIZE: 16385, + h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL: 1, + } + s.update(new_settings) + + assert dict(s) == old_settings + s.acknowledge() + assert dict(s) == new_settings + + def test_acknowledging_returns_the_changed_settings(self): + """ + Acknowledging settings returns the changes. + """ + s = h2.settings.Settings(client=True) + s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] = 8000 + s[h2.settings.SettingCodes.ENABLE_PUSH] = 0 + + changes = s.acknowledge() + assert len(changes) == 2 + + table_size_change = ( + changes[h2.settings.SettingCodes.HEADER_TABLE_SIZE] + ) + push_change = changes[h2.settings.SettingCodes.ENABLE_PUSH] + + assert table_size_change.setting == ( + h2.settings.SettingCodes.HEADER_TABLE_SIZE + ) + assert table_size_change.original_value == 4096 + assert table_size_change.new_value == 8000 + + assert push_change.setting == h2.settings.SettingCodes.ENABLE_PUSH + assert push_change.original_value == 1 + assert push_change.new_value == 0 + + def test_acknowledging_only_returns_changed_settings(self): + """ + Acknowledging settings does not return unchanged settings. + """ + s = h2.settings.Settings(client=True) + s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] = 70 + + changes = s.acknowledge() + assert len(changes) == 1 + assert list(changes.keys()) == [ + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE + ] + + def test_deleting_values_deletes_all_of_them(self): + """ + When we delete a key we lose all state about it. + """ + s = h2.settings.Settings(client=True) + s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 8000 + + del s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] + + with pytest.raises(KeyError): + s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] + + def test_length_correctly_reported(self): + """ + Length is related only to the number of keys. + """ + s = h2.settings.Settings(client=True) + assert len(s) == 5 + + s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 8000 + assert len(s) == 5 + + s.acknowledge() + assert len(s) == 5 + + del s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] + assert len(s) == 4 + + def test_new_values_work(self): + """ + New values initially don't appear + """ + s = h2.settings.Settings(client=True) + s[80] = 81 + + with pytest.raises(KeyError): + s[80] + + def test_new_values_follow_basic_acknowledgement_rules(self): + """ + A new value properly appears when acknowledged. + """ + s = h2.settings.Settings(client=True) + s[80] = 81 + changed_settings = s.acknowledge() + + assert s[80] == 81 + assert len(changed_settings) == 1 + + changed = changed_settings[80] + assert changed.setting == 80 + assert changed.original_value is None + assert changed.new_value == 81 + + def test_single_values_arent_affected_by_acknowledgement(self): + """ + When acknowledged, unchanged settings remain unchanged. + """ + s = h2.settings.Settings(client=True) + assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096 + + s.acknowledge() + assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096 + + def test_settings_getters(self): + """ + Getters exist for well-known settings. + """ + s = h2.settings.Settings(client=True) + + assert s.header_table_size == ( + s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] + ) + assert s.enable_push == s[h2.settings.SettingCodes.ENABLE_PUSH] + assert s.initial_window_size == ( + s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] + ) + assert s.max_frame_size == s[h2.settings.SettingCodes.MAX_FRAME_SIZE] + assert s.max_concurrent_streams == 2**32 + 1 # A sensible default. + assert s.max_header_list_size is None + assert s.enable_connect_protocol == s[ + h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL + ] + + def test_settings_setters(self): + """ + Setters exist for well-known settings. + """ + s = h2.settings.Settings(client=True) + + s.header_table_size = 0 + s.enable_push = 1 + s.initial_window_size = 2 + s.max_frame_size = 16385 + s.max_concurrent_streams = 4 + s.max_header_list_size = 2**16 + s.enable_connect_protocol = 1 + + s.acknowledge() + assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 0 + assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 1 + assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 2 + assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16385 + assert s[h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS] == 4 + assert s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] == 2**16 + assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 1 + + @given(integers()) + def test_cannot_set_invalid_values_for_enable_push(self, val): + """ + SETTINGS_ENABLE_PUSH only allows two values: 0, 1. + """ + assume(val not in (0, 1)) + s = h2.settings.Settings() + + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s.enable_push = val + + s.acknowledge() + assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR + assert s.enable_push == 1 + + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s[h2.settings.SettingCodes.ENABLE_PUSH] = val + + s.acknowledge() + assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR + assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 1 + + @given(integers()) + def test_cannot_set_invalid_vals_for_initial_window_size(self, val): + """ + SETTINGS_INITIAL_WINDOW_SIZE only allows values between 0 and 2**32 - 1 + inclusive. + """ + s = h2.settings.Settings() + + if 0 <= val <= 2**31 - 1: + s.initial_window_size = val + s.acknowledge() + assert s.initial_window_size == val + else: + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s.initial_window_size = val + + s.acknowledge() + assert ( + e.value.error_code == h2.errors.ErrorCodes.FLOW_CONTROL_ERROR + ) + assert s.initial_window_size == 65535 + + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] = val + + s.acknowledge() + assert ( + e.value.error_code == h2.errors.ErrorCodes.FLOW_CONTROL_ERROR + ) + assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535 + + @given(integers()) + def test_cannot_set_invalid_values_for_max_frame_size(self, val): + """ + SETTINGS_MAX_FRAME_SIZE only allows values between 2**14 and 2**24 - 1. + """ + s = h2.settings.Settings() + + if 2**14 <= val <= 2**24 - 1: + s.max_frame_size = val + s.acknowledge() + assert s.max_frame_size == val + else: + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s.max_frame_size = val + + s.acknowledge() + assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR + assert s.max_frame_size == 16384 + + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s[h2.settings.SettingCodes.MAX_FRAME_SIZE] = val + + s.acknowledge() + assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR + assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384 + + @given(integers()) + def test_cannot_set_invalid_values_for_max_header_list_size(self, val): + """ + SETTINGS_MAX_HEADER_LIST_SIZE only allows non-negative values. + """ + s = h2.settings.Settings() + + if val >= 0: + s.max_header_list_size = val + s.acknowledge() + assert s.max_header_list_size == val + else: + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s.max_header_list_size = val + + s.acknowledge() + assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR + assert s.max_header_list_size is None + + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] = val + + s.acknowledge() + assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR + + with pytest.raises(KeyError): + s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] + + @given(integers()) + def test_cannot_set_invalid_values_for_enable_connect_protocol(self, val): + """ + SETTINGS_ENABLE_CONNECT_PROTOCOL only allows two values: 0, 1. + """ + assume(val not in (0, 1)) + s = h2.settings.Settings() + + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s.enable_connect_protocol = val + + s.acknowledge() + assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR + assert s.enable_connect_protocol == 0 + + with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e: + s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] = val + + s.acknowledge() + assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR + assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 0 + + +class TestSettingsEquality(object): + """ + A class defining tests for the standard implementation of == and != . + """ + + SettingsStrategy = builds( + h2.settings.Settings, + client=booleans(), + initial_values=fixed_dictionaries({ + h2.settings.SettingCodes.HEADER_TABLE_SIZE: + integers(0, 2**32 - 1), + h2.settings.SettingCodes.ENABLE_PUSH: integers(0, 1), + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: + integers(0, 2**31 - 1), + h2.settings.SettingCodes.MAX_FRAME_SIZE: + integers(2**14, 2**24 - 1), + h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: + integers(0, 2**32 - 1), + h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: + integers(0, 2**32 - 1), + }) + ) + + @given(settings=SettingsStrategy) + def test_equality_reflexive(self, settings): + """ + An object compares equal to itself using the == operator and the != + operator. + """ + assert (settings == settings) + assert not (settings != settings) + + @given(settings=SettingsStrategy, o_settings=SettingsStrategy) + def test_equality_multiple(self, settings, o_settings): + """ + Two objects compare themselves using the == operator and the != + operator. + """ + if settings == o_settings: + assert settings == o_settings + assert not (settings != o_settings) + else: + assert settings != o_settings + assert not (settings == o_settings) + + @given(settings=SettingsStrategy) + def test_another_type_equality(self, settings): + """ + The object does not compare equal to an object of an unrelated type + (which does not implement the comparison) using the == operator. + """ + obj = object() + assert (settings != obj) + assert not (settings == obj) + + @given(settings=SettingsStrategy) + def test_delegated_eq(self, settings): + """ + The result of comparison is delegated to the right-hand operand if + it is of an unrelated type. + """ + class Delegate(object): + def __eq__(self, other): + return [self] + + def __ne__(self, other): + return [self] + + delg = Delegate() + assert (settings == delg) == [delg] + assert (settings != delg) == [delg] diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_state_machines.py b/testing/web-platform/tests/tools/third_party/h2/test/test_state_machines.py new file mode 100644 index 0000000000..034ae909d2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_state_machines.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +""" +test_state_machines +~~~~~~~~~~~~~~~~~~~ + +These tests validate the state machines directly. Writing meaningful tests for +this case can be tricky, so the majority of these tests use Hypothesis to try +to talk about general behaviours rather than specific cases. +""" +import pytest + +import h2.connection +import h2.exceptions +import h2.stream + +from hypothesis import given +from hypothesis.strategies import sampled_from + + +class TestConnectionStateMachine(object): + """ + Tests of the connection state machine. + """ + @given(state=sampled_from(h2.connection.ConnectionState), + input_=sampled_from(h2.connection.ConnectionInputs)) + def test_state_transitions(self, state, input_): + c = h2.connection.H2ConnectionStateMachine() + c.state = state + + try: + c.process_input(input_) + except h2.exceptions.ProtocolError: + assert c.state == h2.connection.ConnectionState.CLOSED + else: + assert c.state in h2.connection.ConnectionState + + def test_state_machine_only_allows_connection_states(self): + """ + The Connection state machine only allows ConnectionState inputs. + """ + c = h2.connection.H2ConnectionStateMachine() + + with pytest.raises(ValueError): + c.process_input(1) + + @pytest.mark.parametrize( + "state", + ( + s for s in h2.connection.ConnectionState + if s != h2.connection.ConnectionState.CLOSED + ), + ) + @pytest.mark.parametrize( + "input_", + [ + h2.connection.ConnectionInputs.RECV_PRIORITY, + h2.connection.ConnectionInputs.SEND_PRIORITY + ] + ) + def test_priority_frames_allowed_in_all_states(self, state, input_): + """ + Priority frames can be sent/received in all connection states except + closed. + """ + c = h2.connection.H2ConnectionStateMachine() + c.state = state + + c.process_input(input_) + + +class TestStreamStateMachine(object): + """ + Tests of the stream state machine. + """ + @given(state=sampled_from(h2.stream.StreamState), + input_=sampled_from(h2.stream.StreamInputs)) + def test_state_transitions(self, state, input_): + s = h2.stream.H2StreamStateMachine(stream_id=1) + s.state = state + + try: + s.process_input(input_) + except h2.exceptions.StreamClosedError: + # This can only happen for streams that started in the closed + # state OR where the input was RECV_DATA and the state was not + # OPEN or HALF_CLOSED_LOCAL OR where the state was + # HALF_CLOSED_REMOTE and a frame was received. + if state == h2.stream.StreamState.CLOSED: + assert s.state == h2.stream.StreamState.CLOSED + elif input_ == h2.stream.StreamInputs.RECV_DATA: + assert s.state == h2.stream.StreamState.CLOSED + assert state not in ( + h2.stream.StreamState.OPEN, + h2.stream.StreamState.HALF_CLOSED_LOCAL, + ) + elif state == h2.stream.StreamState.HALF_CLOSED_REMOTE: + assert input_ in ( + h2.stream.StreamInputs.RECV_HEADERS, + h2.stream.StreamInputs.RECV_PUSH_PROMISE, + h2.stream.StreamInputs.RECV_DATA, + h2.stream.StreamInputs.RECV_CONTINUATION, + ) + except h2.exceptions.ProtocolError: + assert s.state == h2.stream.StreamState.CLOSED + else: + assert s.state in h2.stream.StreamState + + def test_state_machine_only_allows_stream_states(self): + """ + The Stream state machine only allows StreamState inputs. + """ + s = h2.stream.H2StreamStateMachine(stream_id=1) + + with pytest.raises(ValueError): + s.process_input(1) + + def test_stream_state_machine_forbids_pushes_on_server_streams(self): + """ + Streams where this peer is a server do not allow receiving pushed + frames. + """ + s = h2.stream.H2StreamStateMachine(stream_id=1) + s.process_input(h2.stream.StreamInputs.RECV_HEADERS) + + with pytest.raises(h2.exceptions.ProtocolError): + s.process_input(h2.stream.StreamInputs.RECV_PUSH_PROMISE) + + def test_stream_state_machine_forbids_sending_pushes_from_clients(self): + """ + Streams where this peer is a client do not allow sending pushed frames. + """ + s = h2.stream.H2StreamStateMachine(stream_id=1) + s.process_input(h2.stream.StreamInputs.SEND_HEADERS) + + with pytest.raises(h2.exceptions.ProtocolError): + s.process_input(h2.stream.StreamInputs.SEND_PUSH_PROMISE) + + @pytest.mark.parametrize( + "input_", + [ + h2.stream.StreamInputs.SEND_HEADERS, + h2.stream.StreamInputs.SEND_PUSH_PROMISE, + h2.stream.StreamInputs.SEND_RST_STREAM, + h2.stream.StreamInputs.SEND_DATA, + h2.stream.StreamInputs.SEND_WINDOW_UPDATE, + h2.stream.StreamInputs.SEND_END_STREAM, + ] + ) + def test_cannot_send_on_closed_streams(self, input_): + """ + Sending anything but a PRIORITY frame is forbidden on closed streams. + """ + c = h2.stream.H2StreamStateMachine(stream_id=1) + c.state = h2.stream.StreamState.CLOSED + + expected_error = ( + h2.exceptions.ProtocolError + if input_ == h2.stream.StreamInputs.SEND_PUSH_PROMISE + else h2.exceptions.StreamClosedError + ) + + with pytest.raises(expected_error): + c.process_input(input_) diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_stream_reset.py b/testing/web-platform/tests/tools/third_party/h2/test/test_stream_reset.py new file mode 100644 index 0000000000..778445515f --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_stream_reset.py @@ -0,0 +1,137 @@ +# -*- coding: utf-8 -*- +""" +test_stream_reset +~~~~~~~~~~~~~~~~~ + +More complex tests that exercise stream resetting functionality to validate +that connection state is appropriately maintained. + +Specifically, these tests validate that streams that have been reset accurately +keep track of connection-level state. +""" +import pytest + +import h2.connection +import h2.errors +import h2.events + + +class TestStreamReset(object): + """ + Tests for resetting streams. + """ + example_request_headers = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + ] + example_response_headers = [ + (b':status', b'200'), + (b'server', b'fake-serv/0.1.0'), + (b'content-length', b'0') + ] + + def test_reset_stream_keeps_header_state_correct(self, frame_factory): + """ + A stream that has been reset still affects the header decoder. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + c.reset_stream(stream_id=1) + c.send_headers(stream_id=3, headers=self.example_request_headers) + c.clear_outbound_data_buffer() + + f = frame_factory.build_headers_frame( + headers=self.example_response_headers, stream_id=1 + ) + rst_frame = frame_factory.build_rst_stream_frame( + 1, h2.errors.ErrorCodes.STREAM_CLOSED + ) + events = c.receive_data(f.serialize()) + assert not events + assert c.data_to_send() == rst_frame.serialize() + + # This works because the header state should be intact from the headers + # frame that was send on stream 1, so they should decode cleanly. + f = frame_factory.build_headers_frame( + headers=self.example_response_headers, stream_id=3 + ) + event = c.receive_data(f.serialize())[0] + + assert isinstance(event, h2.events.ResponseReceived) + assert event.stream_id == 3 + assert event.headers == self.example_response_headers + + @pytest.mark.parametrize('close_id,other_id', [(1, 3), (3, 1)]) + def test_reset_stream_keeps_flow_control_correct(self, + close_id, + other_id, + frame_factory): + """ + A stream that has been reset does not affect the connection flow + control window. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + c.send_headers(stream_id=3, headers=self.example_request_headers) + + # Record the initial window size. + initial_window = c.remote_flow_control_window(stream_id=other_id) + + f = frame_factory.build_headers_frame( + headers=self.example_response_headers, stream_id=close_id + ) + c.receive_data(f.serialize()) + c.reset_stream(stream_id=close_id) + c.clear_outbound_data_buffer() + + f = frame_factory.build_data_frame( + data=b'some data', + stream_id=close_id + ) + c.receive_data(f.serialize()) + + expected = frame_factory.build_rst_stream_frame( + stream_id=close_id, + error_code=h2.errors.ErrorCodes.STREAM_CLOSED, + ).serialize() + assert c.data_to_send() == expected + + new_window = c.remote_flow_control_window(stream_id=other_id) + assert initial_window - len(b'some data') == new_window + + @pytest.mark.parametrize('clear_streams', [True, False]) + def test_reset_stream_automatically_resets_pushed_streams(self, + frame_factory, + clear_streams): + """ + Resetting a stream causes RST_STREAM frames to be automatically emitted + to close any streams pushed after the reset. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + c.send_headers(stream_id=1, headers=self.example_request_headers) + c.reset_stream(stream_id=1) + c.clear_outbound_data_buffer() + + if clear_streams: + # Call open_outbound_streams to force the connection to clean + # closed streams. + c.open_outbound_streams + + f = frame_factory.build_push_promise_frame( + stream_id=1, + promised_stream_id=2, + headers=self.example_request_headers, + ) + events = c.receive_data(f.serialize()) + assert not events + + f = frame_factory.build_rst_stream_frame( + stream_id=2, + error_code=h2.errors.ErrorCodes.REFUSED_STREAM, + ) + assert c.data_to_send() == f.serialize() diff --git a/testing/web-platform/tests/tools/third_party/h2/test/test_utility_functions.py b/testing/web-platform/tests/tools/third_party/h2/test/test_utility_functions.py new file mode 100644 index 0000000000..4cb0b2ae60 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test/test_utility_functions.py @@ -0,0 +1,226 @@ +# -*- coding: utf-8 -*- +""" +test_utility_functions +~~~~~~~~~~~~~~~~~~~~~~ + +Tests for the various utility functions provided by hyper-h2. +""" +import pytest + +import h2.config +import h2.connection +import h2.errors +import h2.events +import h2.exceptions +from h2.utilities import SizeLimitDict, extract_method_header + +# These tests require a non-list-returning range function. +try: + range = xrange +except NameError: + range = range + + +class TestGetNextAvailableStreamID(object): + """ + Tests for the ``H2Connection.get_next_available_stream_id`` method. + """ + example_request_headers = [ + (':authority', 'example.com'), + (':path', '/'), + (':scheme', 'https'), + (':method', 'GET'), + ] + example_response_headers = [ + (':status', '200'), + ('server', 'fake-serv/0.1.0') + ] + server_config = h2.config.H2Configuration(client_side=False) + + def test_returns_correct_sequence_for_clients(self, frame_factory): + """ + For a client connection, the correct sequence of stream IDs is + returned. + """ + # Running the exhaustive version of this test (all 1 billion available + # stream IDs) is too painful. For that reason, we validate that the + # original sequence is right for the first few thousand, and then just + # check that it terminates properly. + # + # Make sure that the streams get cleaned up: 8k streams floating + # around would make this test memory-hard, and it's not supposed to be + # a test of how much RAM your machine has. + c = h2.connection.H2Connection() + c.initiate_connection() + initial_sequence = range(1, 2**13, 2) + + for expected_stream_id in initial_sequence: + stream_id = c.get_next_available_stream_id() + assert stream_id == expected_stream_id + + c.send_headers( + stream_id=stream_id, + headers=self.example_request_headers, + end_stream=True + ) + f = frame_factory.build_headers_frame( + headers=self.example_response_headers, + stream_id=stream_id, + flags=['END_STREAM'], + ) + c.receive_data(f.serialize()) + c.clear_outbound_data_buffer() + + # Jump up to the last available stream ID. Don't clean up the stream + # here because who cares about one stream. + last_client_id = 2**31 - 1 + c.send_headers( + stream_id=last_client_id, + headers=self.example_request_headers, + end_stream=True + ) + + with pytest.raises(h2.exceptions.NoAvailableStreamIDError): + c.get_next_available_stream_id() + + def test_returns_correct_sequence_for_servers(self, frame_factory): + """ + For a server connection, the correct sequence of stream IDs is + returned. + """ + # Running the exhaustive version of this test (all 1 billion available + # stream IDs) is too painful. For that reason, we validate that the + # original sequence is right for the first few thousand, and then just + # check that it terminates properly. + # + # Make sure that the streams get cleaned up: 8k streams floating + # around would make this test memory-hard, and it's not supposed to be + # a test of how much RAM your machine has. + c = h2.connection.H2Connection(config=self.server_config) + c.initiate_connection() + c.receive_data(frame_factory.preamble()) + f = frame_factory.build_headers_frame( + headers=self.example_request_headers + ) + c.receive_data(f.serialize()) + + initial_sequence = range(2, 2**13, 2) + + for expected_stream_id in initial_sequence: + stream_id = c.get_next_available_stream_id() + assert stream_id == expected_stream_id + + c.push_stream( + stream_id=1, + promised_stream_id=stream_id, + request_headers=self.example_request_headers + ) + c.send_headers( + stream_id=stream_id, + headers=self.example_response_headers, + end_stream=True + ) + c.clear_outbound_data_buffer() + + # Jump up to the last available stream ID. Don't clean up the stream + # here because who cares about one stream. + last_server_id = 2**31 - 2 + c.push_stream( + stream_id=1, + promised_stream_id=last_server_id, + request_headers=self.example_request_headers, + ) + + with pytest.raises(h2.exceptions.NoAvailableStreamIDError): + c.get_next_available_stream_id() + + def test_does_not_increment_without_stream_send(self): + """ + If a new stream isn't actually created, the next stream ID doesn't + change. + """ + c = h2.connection.H2Connection() + c.initiate_connection() + + first_stream_id = c.get_next_available_stream_id() + second_stream_id = c.get_next_available_stream_id() + + assert first_stream_id == second_stream_id + + c.send_headers( + stream_id=first_stream_id, + headers=self.example_request_headers + ) + + third_stream_id = c.get_next_available_stream_id() + assert third_stream_id == (first_stream_id + 2) + + +class TestExtractHeader(object): + + example_request_headers = [ + (u':authority', u'example.com'), + (u':path', u'/'), + (u':scheme', u'https'), + (u':method', u'GET'), + ] + example_headers_with_bytes = [ + (b':authority', b'example.com'), + (b':path', b'/'), + (b':scheme', b'https'), + (b':method', b'GET'), + ] + + @pytest.mark.parametrize( + 'headers', [example_request_headers, example_headers_with_bytes] + ) + def test_extract_header_method(self, headers): + assert extract_method_header(headers) == b'GET' + + +def test_size_limit_dict_limit(): + dct = SizeLimitDict(size_limit=2) + + dct[1] = 1 + dct[2] = 2 + + assert len(dct) == 2 + assert dct[1] == 1 + assert dct[2] == 2 + + dct[3] = 3 + + assert len(dct) == 2 + assert dct[2] == 2 + assert dct[3] == 3 + assert 1 not in dct + + +def test_size_limit_dict_limit_init(): + initial_dct = { + 1: 1, + 2: 2, + 3: 3, + } + + dct = SizeLimitDict(initial_dct, size_limit=2) + + assert len(dct) == 2 + + +def test_size_limit_dict_no_limit(): + dct = SizeLimitDict(size_limit=None) + + dct[1] = 1 + dct[2] = 2 + + assert len(dct) == 2 + assert dct[1] == 1 + assert dct[2] == 2 + + dct[3] = 3 + + assert len(dct) == 3 + assert dct[1] == 1 + assert dct[2] == 2 + assert dct[3] == 3 diff --git a/testing/web-platform/tests/tools/third_party/h2/test_requirements.txt b/testing/web-platform/tests/tools/third_party/h2/test_requirements.txt new file mode 100644 index 0000000000..e0eefa28ac --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/test_requirements.txt @@ -0,0 +1,5 @@ +pytest==4.6.5 # rq.filter: < 5 +pytest-cov==2.8.1 +pytest-xdist==1.31.0 +coverage==4.5.4 +hypothesis diff --git a/testing/web-platform/tests/tools/third_party/h2/tox.ini b/testing/web-platform/tests/tools/third_party/h2/tox.ini new file mode 100644 index 0000000000..971f9a07e9 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/tox.ini @@ -0,0 +1,48 @@ +[tox] +envlist = py27, py34, py35, py36, py37, py38, pypy, lint, packaging, docs + +[testenv] +deps= -r{toxinidir}/test_requirements.txt +commands= + coverage run -m py.test {posargs} + coverage report + +[testenv:pypy] +# temporarily disable coverage testing on PyPy due to performance problems +commands= py.test {posargs} + +[testenv:lint] +basepython=python3.7 +deps = flake8==3.7.8 +commands = flake8 --max-complexity 10 h2 test + +[testenv:docs] +basepython=python3.7 +deps = sphinx==2.2.0 +changedir = {toxinidir}/docs +whitelist_externals = rm +commands = + rm -rf build + sphinx-build -nW -b html -d build/doctrees source build/html + +[testenv:graphs] +basepython=python2.7 +deps = graphviz==0.13 +commands = + python visualizer/visualize.py -i docs/source/_static + +[testenv:packaging] +basepython=python3.7 +deps = + check-manifest==0.39 + readme-renderer==24.0 +commands = + check-manifest + python setup.py check --metadata --restructuredtext --strict + +[testenv:h2spec] +basepython=python3.6 +deps = twisted[tls]==19.7.0 +whitelist_externals = {toxinidir}/test/h2spectest.sh +commands = + {toxinidir}/test/h2spectest.sh diff --git a/testing/web-platform/tests/tools/third_party/h2/utils/backport.sh b/testing/web-platform/tests/tools/third_party/h2/utils/backport.sh new file mode 100755 index 0000000000..273c1479b2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/utils/backport.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +# This script is invoked as follows: the first argument is the target branch +# for the backport. All following arguments are considered the "commit spec", +# and will be passed to cherry-pick. + +TARGET_BRANCH="$1" +PR_BRANCH="backport-${TARGET_BRANCH}" +COMMIT_SPEC="${@:2}" + +if ! git checkout "$TARGET_BRANCH"; then + echo "Failed to checkout $TARGET_BRANCH" + exit 1 +fi + +if ! git pull --ff-only; then + echo "Unable to update $TARGET_BRANCH" + exit 2 +fi + +if ! git checkout -b "$PR_BRANCH"; then + echo "Failed to open new branch $PR_BRANCH" + exit 3 +fi + +if ! git cherry-pick -x $COMMIT_SPEC; then + echo "Cherry-pick failed. Please fix up manually." +else + echo "Clean backport. Add changelog and open PR." +fi + diff --git a/testing/web-platform/tests/tools/third_party/h2/visualizer/NOTICES.visualizer b/testing/web-platform/tests/tools/third_party/h2/visualizer/NOTICES.visualizer new file mode 100644 index 0000000000..202ca64e17 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/visualizer/NOTICES.visualizer @@ -0,0 +1,24 @@ +This module contains code inspired by and borrowed from Automat. That code was +made available under the following license: + +Copyright (c) 2014 +Rackspace + +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/testing/web-platform/tests/tools/third_party/h2/visualizer/visualize.py b/testing/web-platform/tests/tools/third_party/h2/visualizer/visualize.py new file mode 100644 index 0000000000..1fd3f179cb --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/h2/visualizer/visualize.py @@ -0,0 +1,252 @@ +# -*- coding: utf-8 -*- +""" +State Machine Visualizer +~~~~~~~~~~~~~~~~~~~~~~~~ + +This code provides a module that can use graphviz to visualise the state +machines included in hyper-h2. These visualisations can be used as part of the +documentation of hyper-h2, and as a reference material to understand how the +state machines function. + +The code in this module is heavily inspired by code in Automat, which can be +found here: https://github.com/glyph/automat. For details on the licensing of +Automat, please see the NOTICES.visualizer file in this folder. + +This module is very deliberately not shipped with the rest of hyper-h2. This is +because it is of minimal value to users who are installing hyper-h2: its use +is only really for the developers of hyper-h2. +""" +from __future__ import print_function +import argparse +import collections +import sys + +import graphviz +import graphviz.files + +import h2.connection +import h2.stream + + +StateMachine = collections.namedtuple( + 'StateMachine', ['fqdn', 'machine', 'states', 'inputs', 'transitions'] +) + + +# This is all the state machines we currently know about and will render. +# If any new state machines are added, they should be inserted here. +STATE_MACHINES = [ + StateMachine( + fqdn='h2.connection.H2ConnectionStateMachine', + machine=h2.connection.H2ConnectionStateMachine, + states=h2.connection.ConnectionState, + inputs=h2.connection.ConnectionInputs, + transitions=h2.connection.H2ConnectionStateMachine._transitions, + ), + StateMachine( + fqdn='h2.stream.H2StreamStateMachine', + machine=h2.stream.H2StreamStateMachine, + states=h2.stream.StreamState, + inputs=h2.stream.StreamInputs, + transitions=h2.stream._transitions, + ), +] + + +def quote(s): + return '"{}"'.format(s.replace('"', r'\"')) + + +def html(s): + return '<{}>'.format(s) + + +def element(name, *children, **attrs): + """ + Construct a string from the HTML element description. + """ + formatted_attributes = ' '.join( + '{}={}'.format(key, quote(str(value))) + for key, value in sorted(attrs.items()) + ) + formatted_children = ''.join(children) + return u'<{name} {attrs}>{children}'.format( + name=name, + attrs=formatted_attributes, + children=formatted_children + ) + + +def row_for_output(event, side_effect): + """ + Given an output tuple (an event and its side effect), generates a table row + from it. + """ + point_size = {'point-size': '9'} + event_cell = element( + "td", + element("font", enum_member_name(event), **point_size) + ) + side_effect_name = ( + function_name(side_effect) if side_effect is not None else "None" + ) + side_effect_cell = element( + "td", + element("font", side_effect_name, **point_size) + ) + return element("tr", event_cell, side_effect_cell) + + +def table_maker(initial_state, final_state, outputs, port): + """ + Construct an HTML table to label a state transition. + """ + header = "{} -> {}".format( + enum_member_name(initial_state), enum_member_name(final_state) + ) + header_row = element( + "tr", + element( + "td", + element( + "font", + header, + face="menlo-italic" + ), + port=port, + colspan="2", + ) + ) + rows = [header_row] + rows.extend(row_for_output(*output) for output in outputs) + return element("table", *rows) + + +def enum_member_name(state): + """ + All enum member names have the form .. For + our rendering we only want the member name, so we take their representation + and split it. + """ + return str(state).split('.', 1)[1] + + +def function_name(func): + """ + Given a side-effect function, return its string name. + """ + return func.__name__ + + +def build_digraph(state_machine): + """ + Produce a L{graphviz.Digraph} object from a state machine. + """ + digraph = graphviz.Digraph(node_attr={'fontname': 'Menlo'}, + edge_attr={'fontname': 'Menlo'}, + graph_attr={'dpi': '200'}) + + # First, add the states as nodes. + seen_first_state = False + for state in state_machine.states: + if not seen_first_state: + state_shape = "bold" + font_name = "Menlo-Bold" + else: + state_shape = "" + font_name = "Menlo" + digraph.node(enum_member_name(state), + fontame=font_name, + shape="ellipse", + style=state_shape, + color="blue") + seen_first_state = True + + # We frequently have vary many inputs that all trigger the same state + # transition, and only differ in terms of their input and side-effect. It + # would be polite to say that graphviz does not handle this very well. So + # instead we *collapse* the state transitions all into the one edge, and + # then provide a label that displays a table of all the inputs and their + # associated side effects. + transitions = collections.defaultdict(list) + for transition in state_machine.transitions.items(): + initial_state, event = transition[0] + side_effect, final_state = transition[1] + transition_key = (initial_state, final_state) + transitions[transition_key].append((event, side_effect)) + + for n, (transition_key, outputs) in enumerate(transitions.items()): + this_transition = "t{}".format(n) + initial_state, final_state = transition_key + + port = "tableport" + table = table_maker( + initial_state=initial_state, + final_state=final_state, + outputs=outputs, + port=port + ) + + digraph.node(this_transition, + label=html(table), margin="0.2", shape="none") + + digraph.edge(enum_member_name(initial_state), + '{}:{}:w'.format(this_transition, port), + arrowhead="none") + digraph.edge('{}:{}:e'.format(this_transition, port), + enum_member_name(final_state)) + + return digraph + + +def main(): + """ + Renders all the state machines in hyper-h2 into images. + """ + program_name = sys.argv[0] + argv = sys.argv[1:] + + description = """ + Visualize hyper-h2 state machines as graphs. + """ + epilog = """ + You must have the graphviz tool suite installed. Please visit + http://www.graphviz.org for more information. + """ + + argument_parser = argparse.ArgumentParser( + prog=program_name, + description=description, + epilog=epilog + ) + argument_parser.add_argument( + '--image-directory', + '-i', + help="Where to write out image files.", + default=".h2_visualize" + ) + argument_parser.add_argument( + '--view', + '-v', + help="View rendered graphs with default image viewer", + default=False, + action="store_true" + ) + args = argument_parser.parse_args(argv) + + for state_machine in STATE_MACHINES: + print(state_machine.fqdn, '...discovered') + + digraph = build_digraph(state_machine) + + if args.image_directory: + digraph.format = "png" + digraph.render(filename="{}.dot".format(state_machine.fqdn), + directory=args.image_directory, + view=args.view, + cleanup=True) + print(state_machine.fqdn, "...wrote image into", args.image_directory) + + +if __name__ == '__main__': + main() diff --git a/testing/web-platform/tests/tools/third_party/hpack/CONTRIBUTORS.rst b/testing/web-platform/tests/tools/third_party/hpack/CONTRIBUTORS.rst new file mode 100644 index 0000000000..f56d156eea --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/CONTRIBUTORS.rst @@ -0,0 +1,62 @@ +Hyper is written and maintained by Cory Benfield and various contributors: + +Development Lead +```````````````` + +- Cory Benfield + +Contributors (hpack) +```````````````````` +In chronological order: + +- Sriram Ganesan (@elricL) + + - Implemented the Huffman encoding/decoding logic. + +- Tatsuhiro Tsujikawa (@tatsuhiro-t) + + - Improved compression efficiency. + +- Jim Carreer (@jimcarreer) + + - Support for 'never indexed' header fields. + - Refactor of header table code. + - Add support for returning bytestring headers instead of UTF-8 decoded ones. + +- Eugene Obukhov (@irvind) + + - Improved decoding efficiency. + +- Ian Foote (@Ian-Foote) + + - 25% performance improvement to integer decode. + +- Davey Shafik (@dshafik) + + - More testing. + +- Seth Michael Larson (@SethMichaelLarson) + + - Code cleanups. + +Contributors (hyper) +```````````````````` + +In chronological order: + +- Alek Storm (@alekstorm) + + - Implemented Python 2.7 support. + - Implemented HTTP/2 draft 10 support. + - Implemented server push. + +- Tetsuya Morimoto (@t2y) + + - Fixed a bug where large or incomplete frames were not handled correctly. + - Added hyper command-line tool. + - General code cleanups. + +- Jerome De Cuyper (@jdecuyper) + + - Updated documentation and tests. + diff --git a/testing/web-platform/tests/tools/third_party/hpack/HISTORY.rst b/testing/web-platform/tests/tools/third_party/hpack/HISTORY.rst new file mode 100644 index 0000000000..37b2d9c009 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/HISTORY.rst @@ -0,0 +1,134 @@ +Release History +=============== + +3.0.0 (2017-03-29) +------------------ + +**API Changes (Backward Incompatible)** + +- Removed nghttp2 support. This support had rotted and was essentially + non-functional, so it has now been removed until someone has time to re-add + the support in a functional form. +- Attempts by the encoder to exceed the maximum allowed header table size via + dynamic table size updates (or the absence thereof) are now forbidden. + +**API Changes (Backward Compatible)** + +- Added a new ``InvalidTableSizeError`` thrown when the encoder does not + respect the maximum table size set by the user. +- Added a ``Decoder.max_allowed_table_size`` field that sets the maximum + allowed size of the decoder header table. See the documentation for an + indication of how this should be used. + +**Bugfixes** + +- Up to 25% performance improvement decoding HPACK-packed integers, depending + on the platform. +- HPACK now tolerates receiving multiple header table size changes in sequence, + rather than only one. +- HPACK now forbids header table size changes anywhere but first in a header + block, as required by RFC 7541 § 4.2. +- Other miscellaneous performance improvements. + +2.3.0 (2016-08-04) +------------------ + +**Security Fixes** + +- CVE-2016-6581: HPACK Bomb. This release now enforces a maximum value of the + decompressed size of the header list. This is to avoid the so-called "HPACK + Bomb" vulnerability, which is caused when a malicious peer sends a compressed + HPACK body that decompresses to a gigantic header list size. + + This also adds a ``OversizedHeaderListError``, which is thrown by the + ``decode`` method if the maximum header list size is being violated. This + places the HPACK decoder into a broken state: it must not be used after this + exception is thrown. + + This also adds a ``max_header_list_size`` to the ``Decoder`` object. This + controls the maximum allowable decompressed size of the header list. By + default this is set to 64kB. + +2.2.0 (2016-04-20) +------------------ + +**API Changes (Backward Compatible)** + +- Added ``HeaderTuple`` and ``NeverIndexedHeaderTuple`` classes that signal + whether a given header field may ever be indexed in HTTP/2 header + compression. +- Changed ``Decoder.decode()`` to return the newly added ``HeaderTuple`` class + and subclass. These objects behave like two-tuples, so this change does not + break working code. + +**Bugfixes** + +- Improve Huffman decoding speed by 4x using an approach borrowed from nghttp2. +- Improve HPACK decoding speed by 10% by caching header table sizes. + +2.1.1 (2016-03-16) +------------------ + +**Bugfixes** + +- When passing a dictionary or dictionary subclass to ``Encoder.encode``, HPACK + now ensures that HTTP/2 special headers (headers whose names begin with + ``:`` characters) appear first in the header block. + +2.1.0 (2016-02-02) +------------------ + +**API Changes (Backward Compatible)** + +- Added new ``InvalidTableIndex`` exception, a subclass of + ``HPACKDecodingError``. +- Instead of throwing ``IndexError`` when encountering invalid encoded integers + HPACK now throws ``HPACKDecodingError``. +- Instead of throwing ``UnicodeDecodeError`` when encountering headers that are + not UTF-8 encoded, HPACK now throws ``HPACKDecodingError``. +- Instead of throwing ``IndexError`` when encountering invalid table offsets, + HPACK now throws ``InvalidTableIndex``. +- Added ``raw`` flag to ``decode``, allowing ``decode`` to return bytes instead + of attempting to decode the headers as UTF-8. + +**Bugfixes** + +- ``memoryview`` objects are now used when decoding HPACK, improving the + performance by avoiding unnecessary data copies. + +2.0.1 (2015-11-09) +------------------ + +- Fixed a bug where the Python HPACK implementation would only emit header + table size changes for the total change between one header block and another, + rather than for the entire sequence of changes. + +2.0.0 (2015-10-12) +------------------ + +- Remove unused ``HPACKEncodingError``. +- Add the shortcut ability to import the public API (``Encoder``, ``Decoder``, + ``HPACKError``, ``HPACKDecodingError``) directly, rather than from + ``hpack.hpack``. + +1.1.0 (2015-07-07) +------------------ + +- Add support for emitting 'never indexed' header fields, by using an optional + third element in the header tuple. With thanks to @jimcarreer! + +1.0.1 (2015-04-19) +------------------ + +- Header fields that have names matching header table entries are now added to + the header table. This improves compression efficiency at the cost of + slightly more table operations. With thanks to `Tatsuhiro Tsujikawa`_. + +.. _Tatsuhiro Tsujikawa: https://github.com/tatsuhiro-t + +1.0.0 (2015-04-13) +------------------ + +- Initial fork of the code from `hyper`_. + +.. _hyper: https://hyper.readthedocs.org/ diff --git a/testing/web-platform/tests/tools/third_party/hpack/LICENSE b/testing/web-platform/tests/tools/third_party/hpack/LICENSE new file mode 100644 index 0000000000..d24c351e18 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Cory Benfield + +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/testing/web-platform/tests/tools/third_party/hpack/MANIFEST.in b/testing/web-platform/tests/tools/third_party/hpack/MANIFEST.in new file mode 100644 index 0000000000..2f464676cb --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/MANIFEST.in @@ -0,0 +1,2 @@ +include README.rst LICENSE CONTRIBUTORS.rst HISTORY.rst + diff --git a/testing/web-platform/tests/tools/third_party/hpack/PKG-INFO b/testing/web-platform/tests/tools/third_party/hpack/PKG-INFO new file mode 100644 index 0000000000..c2a3a1a7f5 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/PKG-INFO @@ -0,0 +1,199 @@ +Metadata-Version: 1.1 +Name: hpack +Version: 3.0.0 +Summary: Pure-Python HPACK header compression +Home-page: http://hyper.rtfd.org +Author: Cory Benfield +Author-email: cory@lukasa.co.uk +License: MIT License +Description: ======================================== + hpack: HTTP/2 Header Encoding for Python + ======================================== + + .. image:: https://raw.github.com/Lukasa/hyper/development/docs/source/images/hyper.png + + .. image:: https://travis-ci.org/python-hyper/hpack.png?branch=master + :target: https://travis-ci.org/python-hyper/hpack + + This module contains a pure-Python HTTP/2 header encoding (HPACK) logic for use + in Python programs that implement HTTP/2. It also contains a compatibility + layer that automatically enables the use of ``nghttp2`` if it's available. + + Documentation + ============= + + Documentation is available at http://python-hyper.org/hpack/. + + Contributing + ============ + + ``hpack`` welcomes contributions from anyone! Unlike many other projects we are + happy to accept cosmetic contributions and small contributions, in addition to + large feature requests and changes. + + Before you contribute (either by opening an issue or filing a pull request), + please `read the contribution guidelines`_. + + .. _read the contribution guidelines: http://hyper.readthedocs.org/en/development/contributing.html + + License + ======= + + ``hpack`` is made available under the MIT License. For more details, see the + ``LICENSE`` file in the repository. + + Authors + ======= + + ``hpack`` is maintained by Cory Benfield, with contributions from others. For + more details about the contributors, please see ``CONTRIBUTORS.rst``. + + + Release History + =============== + + 3.0.0 (2017-03-29) + ------------------ + + **API Changes (Backward Incompatible)** + + - Removed nghttp2 support. This support had rotted and was essentially + non-functional, so it has now been removed until someone has time to re-add + the support in a functional form. + - Attempts by the encoder to exceed the maximum allowed header table size via + dynamic table size updates (or the absence thereof) are now forbidden. + + **API Changes (Backward Compatible)** + + - Added a new ``InvalidTableSizeError`` thrown when the encoder does not + respect the maximum table size set by the user. + - Added a ``Decoder.max_allowed_table_size`` field that sets the maximum + allowed size of the decoder header table. See the documentation for an + indication of how this should be used. + + **Bugfixes** + + - Up to 25% performance improvement decoding HPACK-packed integers, depending + on the platform. + - HPACK now tolerates receiving multiple header table size changes in sequence, + rather than only one. + - HPACK now forbids header table size changes anywhere but first in a header + block, as required by RFC 7541 § 4.2. + - Other miscellaneous performance improvements. + + 2.3.0 (2016-08-04) + ------------------ + + **Security Fixes** + + - CVE-2016-6581: HPACK Bomb. This release now enforces a maximum value of the + decompressed size of the header list. This is to avoid the so-called "HPACK + Bomb" vulnerability, which is caused when a malicious peer sends a compressed + HPACK body that decompresses to a gigantic header list size. + + This also adds a ``OversizedHeaderListError``, which is thrown by the + ``decode`` method if the maximum header list size is being violated. This + places the HPACK decoder into a broken state: it must not be used after this + exception is thrown. + + This also adds a ``max_header_list_size`` to the ``Decoder`` object. This + controls the maximum allowable decompressed size of the header list. By + default this is set to 64kB. + + 2.2.0 (2016-04-20) + ------------------ + + **API Changes (Backward Compatible)** + + - Added ``HeaderTuple`` and ``NeverIndexedHeaderTuple`` classes that signal + whether a given header field may ever be indexed in HTTP/2 header + compression. + - Changed ``Decoder.decode()`` to return the newly added ``HeaderTuple`` class + and subclass. These objects behave like two-tuples, so this change does not + break working code. + + **Bugfixes** + + - Improve Huffman decoding speed by 4x using an approach borrowed from nghttp2. + - Improve HPACK decoding speed by 10% by caching header table sizes. + + 2.1.1 (2016-03-16) + ------------------ + + **Bugfixes** + + - When passing a dictionary or dictionary subclass to ``Encoder.encode``, HPACK + now ensures that HTTP/2 special headers (headers whose names begin with + ``:`` characters) appear first in the header block. + + 2.1.0 (2016-02-02) + ------------------ + + **API Changes (Backward Compatible)** + + - Added new ``InvalidTableIndex`` exception, a subclass of + ``HPACKDecodingError``. + - Instead of throwing ``IndexError`` when encountering invalid encoded integers + HPACK now throws ``HPACKDecodingError``. + - Instead of throwing ``UnicodeDecodeError`` when encountering headers that are + not UTF-8 encoded, HPACK now throws ``HPACKDecodingError``. + - Instead of throwing ``IndexError`` when encountering invalid table offsets, + HPACK now throws ``InvalidTableIndex``. + - Added ``raw`` flag to ``decode``, allowing ``decode`` to return bytes instead + of attempting to decode the headers as UTF-8. + + **Bugfixes** + + - ``memoryview`` objects are now used when decoding HPACK, improving the + performance by avoiding unnecessary data copies. + + 2.0.1 (2015-11-09) + ------------------ + + - Fixed a bug where the Python HPACK implementation would only emit header + table size changes for the total change between one header block and another, + rather than for the entire sequence of changes. + + 2.0.0 (2015-10-12) + ------------------ + + - Remove unused ``HPACKEncodingError``. + - Add the shortcut ability to import the public API (``Encoder``, ``Decoder``, + ``HPACKError``, ``HPACKDecodingError``) directly, rather than from + ``hpack.hpack``. + + 1.1.0 (2015-07-07) + ------------------ + + - Add support for emitting 'never indexed' header fields, by using an optional + third element in the header tuple. With thanks to @jimcarreer! + + 1.0.1 (2015-04-19) + ------------------ + + - Header fields that have names matching header table entries are now added to + the header table. This improves compression efficiency at the cost of + slightly more table operations. With thanks to `Tatsuhiro Tsujikawa`_. + + .. _Tatsuhiro Tsujikawa: https://github.com/tatsuhiro-t + + 1.0.0 (2015-04-13) + ------------------ + + - Initial fork of the code from `hyper`_. + + .. _hyper: https://hyper.readthedocs.org/ + +Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +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.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: Implementation :: CPython diff --git a/testing/web-platform/tests/tools/third_party/hpack/README.rst b/testing/web-platform/tests/tools/third_party/hpack/README.rst new file mode 100644 index 0000000000..1a04397b94 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/README.rst @@ -0,0 +1,41 @@ +======================================== +hpack: HTTP/2 Header Encoding for Python +======================================== + +.. image:: https://raw.github.com/Lukasa/hyper/development/docs/source/images/hyper.png + +.. image:: https://travis-ci.org/python-hyper/hpack.png?branch=master + :target: https://travis-ci.org/python-hyper/hpack + +This module contains a pure-Python HTTP/2 header encoding (HPACK) logic for use +in Python programs that implement HTTP/2. It also contains a compatibility +layer that automatically enables the use of ``nghttp2`` if it's available. + +Documentation +============= + +Documentation is available at http://python-hyper.org/hpack/. + +Contributing +============ + +``hpack`` welcomes contributions from anyone! Unlike many other projects we are +happy to accept cosmetic contributions and small contributions, in addition to +large feature requests and changes. + +Before you contribute (either by opening an issue or filing a pull request), +please `read the contribution guidelines`_. + +.. _read the contribution guidelines: http://hyper.readthedocs.org/en/development/contributing.html + +License +======= + +``hpack`` is made available under the MIT License. For more details, see the +``LICENSE`` file in the repository. + +Authors +======= + +``hpack`` is maintained by Cory Benfield, with contributions from others. For +more details about the contributors, please see ``CONTRIBUTORS.rst``. diff --git a/testing/web-platform/tests/tools/third_party/hpack/hpack/__init__.py b/testing/web-platform/tests/tools/third_party/hpack/hpack/__init__.py new file mode 100644 index 0000000000..22edde2ce3 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/hpack/__init__.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- +""" +hpack +~~~~~ + +HTTP/2 header encoding for Python. +""" +from .hpack import Encoder, Decoder +from .struct import HeaderTuple, NeverIndexedHeaderTuple +from .exceptions import ( + HPACKError, HPACKDecodingError, InvalidTableIndex, OversizedHeaderListError +) + +__all__ = [ + 'Encoder', 'Decoder', 'HPACKError', 'HPACKDecodingError', + 'InvalidTableIndex', 'HeaderTuple', 'NeverIndexedHeaderTuple', + 'OversizedHeaderListError' +] + +__version__ = '3.0.0' diff --git a/testing/web-platform/tests/tools/third_party/hpack/hpack/compat.py b/testing/web-platform/tests/tools/third_party/hpack/hpack/compat.py new file mode 100644 index 0000000000..4fcaad439f --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/hpack/compat.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" +hpack/compat +~~~~~~~~~~~~ + +Normalizes the Python 2/3 API for internal use. +""" +import sys + + +_ver = sys.version_info +is_py2 = _ver[0] == 2 +is_py3 = _ver[0] == 3 + +if is_py2: + def to_byte(char): + return ord(char) + + def decode_hex(b): + return b.decode('hex') + + def to_bytes(b): + if isinstance(b, memoryview): + return b.tobytes() + else: + return bytes(b) + + unicode = unicode # noqa + bytes = str + +elif is_py3: + def to_byte(char): + return char + + def decode_hex(b): + return bytes.fromhex(b) + + def to_bytes(b): + return bytes(b) + + unicode = str + bytes = bytes diff --git a/testing/web-platform/tests/tools/third_party/hpack/hpack/exceptions.py b/testing/web-platform/tests/tools/third_party/hpack/hpack/exceptions.py new file mode 100644 index 0000000000..571ba98f2c --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/hpack/exceptions.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +""" +hyper/http20/exceptions +~~~~~~~~~~~~~~~~~~~~~~~ + +This defines exceptions used in the HTTP/2 portion of hyper. +""" + + +class HPACKError(Exception): + """ + The base class for all ``hpack`` exceptions. + """ + pass + + +class HPACKDecodingError(HPACKError): + """ + An error has been encountered while performing HPACK decoding. + """ + pass + + +class InvalidTableIndex(HPACKDecodingError): + """ + An invalid table index was received. + """ + pass + + +class OversizedHeaderListError(HPACKDecodingError): + """ + A header list that was larger than we allow has been received. This may be + a DoS attack. + + .. versionadded:: 2.3.0 + """ + pass + + +class InvalidTableSizeError(HPACKDecodingError): + """ + An attempt was made to change the decoder table size to a value larger than + allowed, or the list was shrunk and the remote peer didn't shrink their + table size. + + .. versionadded:: 3.0.0 + """ + pass diff --git a/testing/web-platform/tests/tools/third_party/hpack/hpack/hpack.py b/testing/web-platform/tests/tools/third_party/hpack/hpack/hpack.py new file mode 100644 index 0000000000..f8e808bec9 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/hpack/hpack.py @@ -0,0 +1,629 @@ +# -*- coding: utf-8 -*- +""" +hpack/hpack +~~~~~~~~~~~ + +Implements the HPACK header compression algorithm as detailed by the IETF. +""" +import logging + +from .table import HeaderTable, table_entry_size +from .compat import to_byte, to_bytes +from .exceptions import ( + HPACKDecodingError, OversizedHeaderListError, InvalidTableSizeError +) +from .huffman import HuffmanEncoder +from .huffman_constants import ( + REQUEST_CODES, REQUEST_CODES_LENGTH +) +from .huffman_table import decode_huffman +from .struct import HeaderTuple, NeverIndexedHeaderTuple + +log = logging.getLogger(__name__) + +INDEX_NONE = b'\x00' +INDEX_NEVER = b'\x10' +INDEX_INCREMENTAL = b'\x40' + +# Precompute 2^i for 1-8 for use in prefix calcs. +# Zero index is not used but there to save a subtraction +# as prefix numbers are not zero indexed. +_PREFIX_BIT_MAX_NUMBERS = [(2 ** i) - 1 for i in range(9)] + +try: # pragma: no cover + basestring = basestring +except NameError: # pragma: no cover + basestring = (str, bytes) + + +# We default the maximum header list we're willing to accept to 64kB. That's a +# lot of headers, but if applications want to raise it they can do. +DEFAULT_MAX_HEADER_LIST_SIZE = 2 ** 16 + + +def _unicode_if_needed(header, raw): + """ + Provides a header as a unicode string if raw is False, otherwise returns + it as a bytestring. + """ + name = to_bytes(header[0]) + value = to_bytes(header[1]) + if not raw: + name = name.decode('utf-8') + value = value.decode('utf-8') + return header.__class__(name, value) + + +def encode_integer(integer, prefix_bits): + """ + This encodes an integer according to the wacky integer encoding rules + defined in the HPACK spec. + """ + log.debug("Encoding %d with %d bits", integer, prefix_bits) + + if integer < 0: + raise ValueError( + "Can only encode positive integers, got %s" % integer + ) + + if prefix_bits < 1 or prefix_bits > 8: + raise ValueError( + "Prefix bits must be between 1 and 8, got %s" % prefix_bits + ) + + max_number = _PREFIX_BIT_MAX_NUMBERS[prefix_bits] + + if integer < max_number: + return bytearray([integer]) # Seriously? + else: + elements = [max_number] + integer -= max_number + + while integer >= 128: + elements.append((integer & 127) + 128) + integer >>= 7 + + elements.append(integer) + + return bytearray(elements) + + +def decode_integer(data, prefix_bits): + """ + This decodes an integer according to the wacky integer encoding rules + defined in the HPACK spec. Returns a tuple of the decoded integer and the + number of bytes that were consumed from ``data`` in order to get that + integer. + """ + if prefix_bits < 1 or prefix_bits > 8: + raise ValueError( + "Prefix bits must be between 1 and 8, got %s" % prefix_bits + ) + + max_number = _PREFIX_BIT_MAX_NUMBERS[prefix_bits] + index = 1 + shift = 0 + mask = (0xFF >> (8 - prefix_bits)) + + try: + number = to_byte(data[0]) & mask + if number == max_number: + while True: + next_byte = to_byte(data[index]) + index += 1 + + if next_byte >= 128: + number += (next_byte - 128) << shift + else: + number += next_byte << shift + break + shift += 7 + + except IndexError: + raise HPACKDecodingError( + "Unable to decode HPACK integer representation from %r" % data + ) + + log.debug("Decoded %d, consumed %d bytes", number, index) + + return number, index + + +def _dict_to_iterable(header_dict): + """ + This converts a dictionary to an iterable of two-tuples. This is a + HPACK-specific function becuase it pulls "special-headers" out first and + then emits them. + """ + assert isinstance(header_dict, dict) + keys = sorted( + header_dict.keys(), + key=lambda k: not _to_bytes(k).startswith(b':') + ) + for key in keys: + yield key, header_dict[key] + + +def _to_bytes(string): + """ + Convert string to bytes. + """ + if not isinstance(string, basestring): # pragma: no cover + string = str(string) + + return string if isinstance(string, bytes) else string.encode('utf-8') + + +class Encoder(object): + """ + An HPACK encoder object. This object takes HTTP headers and emits encoded + HTTP/2 header blocks. + """ + + def __init__(self): + self.header_table = HeaderTable() + self.huffman_coder = HuffmanEncoder( + REQUEST_CODES, REQUEST_CODES_LENGTH + ) + self.table_size_changes = [] + + @property + def header_table_size(self): + """ + Controls the size of the HPACK header table. + """ + return self.header_table.maxsize + + @header_table_size.setter + def header_table_size(self, value): + self.header_table.maxsize = value + if self.header_table.resized: + self.table_size_changes.append(value) + + def encode(self, headers, huffman=True): + """ + Takes a set of headers and encodes them into a HPACK-encoded header + block. + + :param headers: The headers to encode. Must be either an iterable of + tuples, an iterable of :class:`HeaderTuple + `, or a ``dict``. + + If an iterable of tuples, the tuples may be either + two-tuples or three-tuples. If they are two-tuples, the + tuples must be of the format ``(name, value)``. If they + are three-tuples, they must be of the format + ``(name, value, sensitive)``, where ``sensitive`` is a + boolean value indicating whether the header should be + added to header tables anywhere. If not present, + ``sensitive`` defaults to ``False``. + + If an iterable of :class:`HeaderTuple + `, the tuples must always be + two-tuples. Instead of using ``sensitive`` as a third + tuple entry, use :class:`NeverIndexedHeaderTuple + ` to request that + the field never be indexed. + + .. warning:: HTTP/2 requires that all special headers + (headers whose names begin with ``:`` characters) + appear at the *start* of the header block. While + this method will ensure that happens for ``dict`` + subclasses, callers using any other iterable of + tuples **must** ensure they place their special + headers at the start of the iterable. + + For efficiency reasons users should prefer to use + iterables of two-tuples: fixing the ordering of + dictionary headers is an expensive operation that + should be avoided if possible. + + :param huffman: (optional) Whether to Huffman-encode any header sent as + a literal value. Except for use when debugging, it is + recommended that this be left enabled. + + :returns: A bytestring containing the HPACK-encoded header block. + """ + # Transforming the headers into a header block is a procedure that can + # be modeled as a chain or pipe. First, the headers are encoded. This + # encoding can be done a number of ways. If the header name-value pair + # are already in the header table we can represent them using the + # indexed representation: the same is true if they are in the static + # table. Otherwise, a literal representation will be used. + header_block = [] + + # Turn the headers into a list of tuples if possible. This is the + # natural way to interact with them in HPACK. Because dictionaries are + # un-ordered, we need to make sure we grab the "special" headers first. + if isinstance(headers, dict): + headers = _dict_to_iterable(headers) + + # Before we begin, if the header table size has been changed we need + # to signal all changes since last emission appropriately. + if self.header_table.resized: + header_block.append(self._encode_table_size_change()) + self.header_table.resized = False + + # Add each header to the header block + for header in headers: + sensitive = False + if isinstance(header, HeaderTuple): + sensitive = not header.indexable + elif len(header) > 2: + sensitive = header[2] + + header = (_to_bytes(header[0]), _to_bytes(header[1])) + header_block.append(self.add(header, sensitive, huffman)) + + header_block = b''.join(header_block) + + log.debug("Encoded header block to %s", header_block) + + return header_block + + def add(self, to_add, sensitive, huffman=False): + """ + This function takes a header key-value tuple and serializes it. + """ + log.debug("Adding %s to the header table", to_add) + + name, value = to_add + + # Set our indexing mode + indexbit = INDEX_INCREMENTAL if not sensitive else INDEX_NEVER + + # Search for a matching header in the header table. + match = self.header_table.search(name, value) + + if match is None: + # Not in the header table. Encode using the literal syntax, + # and add it to the header table. + encoded = self._encode_literal(name, value, indexbit, huffman) + if not sensitive: + self.header_table.add(name, value) + return encoded + + # The header is in the table, break out the values. If we matched + # perfectly, we can use the indexed representation: otherwise we + # can use the indexed literal. + index, name, perfect = match + + if perfect: + # Indexed representation. + encoded = self._encode_indexed(index) + else: + # Indexed literal. We are going to add header to the + # header table unconditionally. It is a future todo to + # filter out headers which are known to be ineffective for + # indexing since they just take space in the table and + # pushed out other valuable headers. + encoded = self._encode_indexed_literal( + index, value, indexbit, huffman + ) + if not sensitive: + self.header_table.add(name, value) + + return encoded + + def _encode_indexed(self, index): + """ + Encodes a header using the indexed representation. + """ + field = encode_integer(index, 7) + field[0] |= 0x80 # we set the top bit + return bytes(field) + + def _encode_literal(self, name, value, indexbit, huffman=False): + """ + Encodes a header with a literal name and literal value. If ``indexing`` + is True, the header will be added to the header table: otherwise it + will not. + """ + if huffman: + name = self.huffman_coder.encode(name) + value = self.huffman_coder.encode(value) + + name_len = encode_integer(len(name), 7) + value_len = encode_integer(len(value), 7) + + if huffman: + name_len[0] |= 0x80 + value_len[0] |= 0x80 + + return b''.join( + [indexbit, bytes(name_len), name, bytes(value_len), value] + ) + + def _encode_indexed_literal(self, index, value, indexbit, huffman=False): + """ + Encodes a header with an indexed name and a literal value and performs + incremental indexing. + """ + if indexbit != INDEX_INCREMENTAL: + prefix = encode_integer(index, 4) + else: + prefix = encode_integer(index, 6) + + prefix[0] |= ord(indexbit) + + if huffman: + value = self.huffman_coder.encode(value) + + value_len = encode_integer(len(value), 7) + + if huffman: + value_len[0] |= 0x80 + + return b''.join([bytes(prefix), bytes(value_len), value]) + + def _encode_table_size_change(self): + """ + Produces the encoded form of all header table size change context + updates. + """ + block = b'' + for size_bytes in self.table_size_changes: + size_bytes = encode_integer(size_bytes, 5) + size_bytes[0] |= 0x20 + block += bytes(size_bytes) + self.table_size_changes = [] + return block + + +class Decoder(object): + """ + An HPACK decoder object. + + .. versionchanged:: 2.3.0 + Added ``max_header_list_size`` argument. + + :param max_header_list_size: The maximum decompressed size we will allow + for any single header block. This is a protection against DoS attacks + that attempt to force the application to expand a relatively small + amount of data into a really large header list, allowing enormous + amounts of memory to be allocated. + + If this amount of data is exceeded, a `OversizedHeaderListError + ` exception will be raised. At this + point the connection should be shut down, as the HPACK state will no + longer be useable. + + Defaults to 64kB. + :type max_header_list_size: ``int`` + """ + def __init__(self, max_header_list_size=DEFAULT_MAX_HEADER_LIST_SIZE): + self.header_table = HeaderTable() + + #: The maximum decompressed size we will allow for any single header + #: block. This is a protection against DoS attacks that attempt to + #: force the application to expand a relatively small amount of data + #: into a really large header list, allowing enormous amounts of memory + #: to be allocated. + #: + #: If this amount of data is exceeded, a `OversizedHeaderListError + #: ` exception will be raised. At this + #: point the connection should be shut down, as the HPACK state will no + #: longer be usable. + #: + #: Defaults to 64kB. + #: + #: .. versionadded:: 2.3.0 + self.max_header_list_size = max_header_list_size + + #: Maximum allowed header table size. + #: + #: A HTTP/2 implementation should set this to the most recent value of + #: SETTINGS_HEADER_TABLE_SIZE that it sent *and has received an ACK + #: for*. Once this setting is set, the actual header table size will be + #: checked at the end of each decoding run and whenever it is changed, + #: to confirm that it fits in this size. + self.max_allowed_table_size = self.header_table.maxsize + + @property + def header_table_size(self): + """ + Controls the size of the HPACK header table. + """ + return self.header_table.maxsize + + @header_table_size.setter + def header_table_size(self, value): + self.header_table.maxsize = value + + def decode(self, data, raw=False): + """ + Takes an HPACK-encoded header block and decodes it into a header set. + + :param data: A bytestring representing a complete HPACK-encoded header + block. + :param raw: (optional) Whether to return the headers as tuples of raw + byte strings or to decode them as UTF-8 before returning + them. The default value is False, which returns tuples of + Unicode strings + :returns: A list of two-tuples of ``(name, value)`` representing the + HPACK-encoded headers, in the order they were decoded. + :raises HPACKDecodingError: If an error is encountered while decoding + the header block. + """ + log.debug("Decoding %s", data) + + data_mem = memoryview(data) + headers = [] + data_len = len(data) + inflated_size = 0 + current_index = 0 + + while current_index < data_len: + # Work out what kind of header we're decoding. + # If the high bit is 1, it's an indexed field. + current = to_byte(data[current_index]) + indexed = True if current & 0x80 else False + + # Otherwise, if the second-highest bit is 1 it's a field that does + # alter the header table. + literal_index = True if current & 0x40 else False + + # Otherwise, if the third-highest bit is 1 it's an encoding context + # update. + encoding_update = True if current & 0x20 else False + + if indexed: + header, consumed = self._decode_indexed( + data_mem[current_index:] + ) + elif literal_index: + # It's a literal header that does affect the header table. + header, consumed = self._decode_literal_index( + data_mem[current_index:] + ) + elif encoding_update: + # It's an update to the encoding context. These are forbidden + # in a header block after any actual header. + if headers: + raise HPACKDecodingError( + "Table size update not at the start of the block" + ) + consumed = self._update_encoding_context( + data_mem[current_index:] + ) + header = None + else: + # It's a literal header that does not affect the header table. + header, consumed = self._decode_literal_no_index( + data_mem[current_index:] + ) + + if header: + headers.append(header) + inflated_size += table_entry_size(*header) + + if inflated_size > self.max_header_list_size: + raise OversizedHeaderListError( + "A header list larger than %d has been received" % + self.max_header_list_size + ) + + current_index += consumed + + # Confirm that the table size is lower than the maximum. We do this + # here to ensure that we catch when the max has been *shrunk* and the + # remote peer hasn't actually done that. + self._assert_valid_table_size() + + try: + return [_unicode_if_needed(h, raw) for h in headers] + except UnicodeDecodeError: + raise HPACKDecodingError("Unable to decode headers as UTF-8.") + + def _assert_valid_table_size(self): + """ + Check that the table size set by the encoder is lower than the maximum + we expect to have. + """ + if self.header_table_size > self.max_allowed_table_size: + raise InvalidTableSizeError( + "Encoder did not shrink table size to within the max" + ) + + def _update_encoding_context(self, data): + """ + Handles a byte that updates the encoding context. + """ + # We've been asked to resize the header table. + new_size, consumed = decode_integer(data, 5) + if new_size > self.max_allowed_table_size: + raise InvalidTableSizeError( + "Encoder exceeded max allowable table size" + ) + self.header_table_size = new_size + return consumed + + def _decode_indexed(self, data): + """ + Decodes a header represented using the indexed representation. + """ + index, consumed = decode_integer(data, 7) + header = HeaderTuple(*self.header_table.get_by_index(index)) + log.debug("Decoded %s, consumed %d", header, consumed) + return header, consumed + + def _decode_literal_no_index(self, data): + return self._decode_literal(data, False) + + def _decode_literal_index(self, data): + return self._decode_literal(data, True) + + def _decode_literal(self, data, should_index): + """ + Decodes a header represented with a literal. + """ + total_consumed = 0 + + # When should_index is true, if the low six bits of the first byte are + # nonzero, the header name is indexed. + # When should_index is false, if the low four bits of the first byte + # are nonzero the header name is indexed. + if should_index: + indexed_name = to_byte(data[0]) & 0x3F + name_len = 6 + not_indexable = False + else: + high_byte = to_byte(data[0]) + indexed_name = high_byte & 0x0F + name_len = 4 + not_indexable = high_byte & 0x10 + + if indexed_name: + # Indexed header name. + index, consumed = decode_integer(data, name_len) + name = self.header_table.get_by_index(index)[0] + + total_consumed = consumed + length = 0 + else: + # Literal header name. The first byte was consumed, so we need to + # move forward. + data = data[1:] + + length, consumed = decode_integer(data, 7) + name = data[consumed:consumed + length] + if len(name) != length: + raise HPACKDecodingError("Truncated header block") + + if to_byte(data[0]) & 0x80: + name = decode_huffman(name) + total_consumed = consumed + length + 1 # Since we moved forward 1. + + data = data[consumed + length:] + + # The header value is definitely length-based. + length, consumed = decode_integer(data, 7) + value = data[consumed:consumed + length] + if len(value) != length: + raise HPACKDecodingError("Truncated header block") + + if to_byte(data[0]) & 0x80: + value = decode_huffman(value) + + # Updated the total consumed length. + total_consumed += length + consumed + + # If we have been told never to index the header field, encode that in + # the tuple we use. + if not_indexable: + header = NeverIndexedHeaderTuple(name, value) + else: + header = HeaderTuple(name, value) + + # If we've been asked to index this, add it to the header table. + if should_index: + self.header_table.add(name, value) + + log.debug( + "Decoded %s, total consumed %d bytes, indexed %s", + header, + total_consumed, + should_index + ) + + return header, total_consumed diff --git a/testing/web-platform/tests/tools/third_party/hpack/hpack/huffman.py b/testing/web-platform/tests/tools/third_party/hpack/hpack/huffman.py new file mode 100644 index 0000000000..159569cf63 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/hpack/huffman.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +""" +hpack/huffman_decoder +~~~~~~~~~~~~~~~~~~~~~ + +An implementation of a bitwise prefix tree specially built for decoding +Huffman-coded content where we already know the Huffman table. +""" +from .compat import to_byte, decode_hex + + +class HuffmanEncoder(object): + """ + Encodes a string according to the Huffman encoding table defined in the + HPACK specification. + """ + def __init__(self, huffman_code_list, huffman_code_list_lengths): + self.huffman_code_list = huffman_code_list + self.huffman_code_list_lengths = huffman_code_list_lengths + + def encode(self, bytes_to_encode): + """ + Given a string of bytes, encodes them according to the HPACK Huffman + specification. + """ + # If handed the empty string, just immediately return. + if not bytes_to_encode: + return b'' + + final_num = 0 + final_int_len = 0 + + # Turn each byte into its huffman code. These codes aren't necessarily + # octet aligned, so keep track of how far through an octet we are. To + # handle this cleanly, just use a single giant integer. + for char in bytes_to_encode: + byte = to_byte(char) + bin_int_len = self.huffman_code_list_lengths[byte] + bin_int = self.huffman_code_list[byte] & ( + 2 ** (bin_int_len + 1) - 1 + ) + final_num <<= bin_int_len + final_num |= bin_int + final_int_len += bin_int_len + + # Pad out to an octet with ones. + bits_to_be_padded = (8 - (final_int_len % 8)) % 8 + final_num <<= bits_to_be_padded + final_num |= (1 << bits_to_be_padded) - 1 + + # Convert the number to hex and strip off the leading '0x' and the + # trailing 'L', if present. + final_num = hex(final_num)[2:].rstrip('L') + + # If this is odd, prepend a zero. + final_num = '0' + final_num if len(final_num) % 2 != 0 else final_num + + # This number should have twice as many digits as bytes. If not, we're + # missing some leading zeroes. Work out how many bytes we want and how + # many digits we have, then add the missing zero digits to the front. + total_bytes = (final_int_len + bits_to_be_padded) // 8 + expected_digits = total_bytes * 2 + + if len(final_num) != expected_digits: + missing_digits = expected_digits - len(final_num) + final_num = ('0' * missing_digits) + final_num + + return decode_hex(final_num) diff --git a/testing/web-platform/tests/tools/third_party/hpack/hpack/huffman_constants.py b/testing/web-platform/tests/tools/third_party/hpack/hpack/huffman_constants.py new file mode 100644 index 0000000000..c2b3bb283e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/hpack/huffman_constants.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +""" +hpack/huffman_constants +~~~~~~~~~~~~~~~~~~~~~~~ + +Defines the constant Huffman table. This takes up an upsetting amount of space, +but c'est la vie. +""" + +REQUEST_CODES = [ + 0x1ff8, + 0x7fffd8, + 0xfffffe2, + 0xfffffe3, + 0xfffffe4, + 0xfffffe5, + 0xfffffe6, + 0xfffffe7, + 0xfffffe8, + 0xffffea, + 0x3ffffffc, + 0xfffffe9, + 0xfffffea, + 0x3ffffffd, + 0xfffffeb, + 0xfffffec, + 0xfffffed, + 0xfffffee, + 0xfffffef, + 0xffffff0, + 0xffffff1, + 0xffffff2, + 0x3ffffffe, + 0xffffff3, + 0xffffff4, + 0xffffff5, + 0xffffff6, + 0xffffff7, + 0xffffff8, + 0xffffff9, + 0xffffffa, + 0xffffffb, + 0x14, + 0x3f8, + 0x3f9, + 0xffa, + 0x1ff9, + 0x15, + 0xf8, + 0x7fa, + 0x3fa, + 0x3fb, + 0xf9, + 0x7fb, + 0xfa, + 0x16, + 0x17, + 0x18, + 0x0, + 0x1, + 0x2, + 0x19, + 0x1a, + 0x1b, + 0x1c, + 0x1d, + 0x1e, + 0x1f, + 0x5c, + 0xfb, + 0x7ffc, + 0x20, + 0xffb, + 0x3fc, + 0x1ffa, + 0x21, + 0x5d, + 0x5e, + 0x5f, + 0x60, + 0x61, + 0x62, + 0x63, + 0x64, + 0x65, + 0x66, + 0x67, + 0x68, + 0x69, + 0x6a, + 0x6b, + 0x6c, + 0x6d, + 0x6e, + 0x6f, + 0x70, + 0x71, + 0x72, + 0xfc, + 0x73, + 0xfd, + 0x1ffb, + 0x7fff0, + 0x1ffc, + 0x3ffc, + 0x22, + 0x7ffd, + 0x3, + 0x23, + 0x4, + 0x24, + 0x5, + 0x25, + 0x26, + 0x27, + 0x6, + 0x74, + 0x75, + 0x28, + 0x29, + 0x2a, + 0x7, + 0x2b, + 0x76, + 0x2c, + 0x8, + 0x9, + 0x2d, + 0x77, + 0x78, + 0x79, + 0x7a, + 0x7b, + 0x7ffe, + 0x7fc, + 0x3ffd, + 0x1ffd, + 0xffffffc, + 0xfffe6, + 0x3fffd2, + 0xfffe7, + 0xfffe8, + 0x3fffd3, + 0x3fffd4, + 0x3fffd5, + 0x7fffd9, + 0x3fffd6, + 0x7fffda, + 0x7fffdb, + 0x7fffdc, + 0x7fffdd, + 0x7fffde, + 0xffffeb, + 0x7fffdf, + 0xffffec, + 0xffffed, + 0x3fffd7, + 0x7fffe0, + 0xffffee, + 0x7fffe1, + 0x7fffe2, + 0x7fffe3, + 0x7fffe4, + 0x1fffdc, + 0x3fffd8, + 0x7fffe5, + 0x3fffd9, + 0x7fffe6, + 0x7fffe7, + 0xffffef, + 0x3fffda, + 0x1fffdd, + 0xfffe9, + 0x3fffdb, + 0x3fffdc, + 0x7fffe8, + 0x7fffe9, + 0x1fffde, + 0x7fffea, + 0x3fffdd, + 0x3fffde, + 0xfffff0, + 0x1fffdf, + 0x3fffdf, + 0x7fffeb, + 0x7fffec, + 0x1fffe0, + 0x1fffe1, + 0x3fffe0, + 0x1fffe2, + 0x7fffed, + 0x3fffe1, + 0x7fffee, + 0x7fffef, + 0xfffea, + 0x3fffe2, + 0x3fffe3, + 0x3fffe4, + 0x7ffff0, + 0x3fffe5, + 0x3fffe6, + 0x7ffff1, + 0x3ffffe0, + 0x3ffffe1, + 0xfffeb, + 0x7fff1, + 0x3fffe7, + 0x7ffff2, + 0x3fffe8, + 0x1ffffec, + 0x3ffffe2, + 0x3ffffe3, + 0x3ffffe4, + 0x7ffffde, + 0x7ffffdf, + 0x3ffffe5, + 0xfffff1, + 0x1ffffed, + 0x7fff2, + 0x1fffe3, + 0x3ffffe6, + 0x7ffffe0, + 0x7ffffe1, + 0x3ffffe7, + 0x7ffffe2, + 0xfffff2, + 0x1fffe4, + 0x1fffe5, + 0x3ffffe8, + 0x3ffffe9, + 0xffffffd, + 0x7ffffe3, + 0x7ffffe4, + 0x7ffffe5, + 0xfffec, + 0xfffff3, + 0xfffed, + 0x1fffe6, + 0x3fffe9, + 0x1fffe7, + 0x1fffe8, + 0x7ffff3, + 0x3fffea, + 0x3fffeb, + 0x1ffffee, + 0x1ffffef, + 0xfffff4, + 0xfffff5, + 0x3ffffea, + 0x7ffff4, + 0x3ffffeb, + 0x7ffffe6, + 0x3ffffec, + 0x3ffffed, + 0x7ffffe7, + 0x7ffffe8, + 0x7ffffe9, + 0x7ffffea, + 0x7ffffeb, + 0xffffffe, + 0x7ffffec, + 0x7ffffed, + 0x7ffffee, + 0x7ffffef, + 0x7fffff0, + 0x3ffffee, + 0x3fffffff, +] + +REQUEST_CODES_LENGTH = [ + 13, 23, 28, 28, 28, 28, 28, 28, 28, 24, 30, 28, 28, 30, 28, 28, + 28, 28, 28, 28, 28, 28, 30, 28, 28, 28, 28, 28, 28, 28, 28, 28, + 6, 10, 10, 12, 13, 6, 8, 11, 10, 10, 8, 11, 8, 6, 6, 6, + 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 8, 15, 6, 12, 10, + 13, 6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, + 7, 7, 7, 7, 7, 7, 7, 7, 8, 7, 8, 13, 19, 13, 14, 6, + 15, 5, 6, 5, 6, 5, 6, 6, 6, 5, 7, 7, 6, 6, 6, 5, + 6, 7, 6, 5, 5, 6, 7, 7, 7, 7, 7, 15, 11, 14, 13, 28, + 20, 22, 20, 20, 22, 22, 22, 23, 22, 23, 23, 23, 23, 23, 24, 23, + 24, 24, 22, 23, 24, 23, 23, 23, 23, 21, 22, 23, 22, 23, 23, 24, + 22, 21, 20, 22, 22, 23, 23, 21, 23, 22, 22, 24, 21, 22, 23, 23, + 21, 21, 22, 21, 23, 22, 23, 23, 20, 22, 22, 22, 23, 22, 22, 23, + 26, 26, 20, 19, 22, 23, 22, 25, 26, 26, 26, 27, 27, 26, 24, 25, + 19, 21, 26, 27, 27, 26, 27, 24, 21, 21, 26, 26, 28, 27, 27, 27, + 20, 24, 20, 21, 22, 21, 21, 23, 22, 22, 25, 25, 24, 24, 26, 23, + 26, 27, 26, 26, 27, 27, 27, 27, 27, 28, 27, 27, 27, 27, 27, 26, + 30, +] diff --git a/testing/web-platform/tests/tools/third_party/hpack/hpack/huffman_table.py b/testing/web-platform/tests/tools/third_party/hpack/hpack/huffman_table.py new file mode 100644 index 0000000000..c199ef5a3f --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/hpack/huffman_table.py @@ -0,0 +1,4739 @@ +# -*- coding: utf-8 -*- +""" +hpack/huffman_table +~~~~~~~~~~~~~~~~~~~ + +This implementation of a Huffman decoding table for HTTP/2 is essentially a +Python port of the work originally done for nghttp2's Huffman decoding. For +this reason, while this file is made available under the MIT license as is the +rest of this module, this file is undoubtedly a derivative work of the nghttp2 +file ``nghttp2_hd_huffman_data.c``, obtained from +https://github.com/tatsuhiro-t/nghttp2/ at commit +d2b55ad1a245e1d1964579fa3fac36ebf3939e72. That work is made available under +the Apache 2.0 license under the following terms: + + Copyright (c) 2013 Tatsuhiro Tsujikawa + + 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. + +The essence of this approach is that it builds a finite state machine out of +4-bit nibbles of Huffman coded data. The input function passes 4 bits worth of +data to the state machine each time, which uses those 4 bits of data along with +the current accumulated state data to process the data given. + +For the sake of efficiency, the in-memory representation of the states, +transitions, and result values of the state machine are represented as a long +list containing three-tuples. This list is enormously long, and viewing it as +an in-memory representation is not very clear, but it is laid out here in a way +that is intended to be *somewhat* more clear. + +Essentially, the list is structured as 256 collections of 16 entries (one for +each nibble) of three-tuples. Each collection is called a "node", and the +zeroth collection is called the "root node". The state machine tracks one +value: the "state" byte. + +For each nibble passed to the state machine, it first multiplies the "state" +byte by 16 and adds the numerical value of the nibble. This number is the index +into the large flat list. + +The three-tuple that is found by looking up that index consists of three +values: + +- a new state value, used for subsequent decoding +- a collection of flags, used to determine whether data is emitted or whether + the state machine is complete. +- the byte value to emit, assuming that emitting a byte is required. + +The flags are consulted, if necessary a byte is emitted, and then the next +nibble is used. This continues until the state machine believes it has +completely Huffman-decoded the data. + +This approach has relatively little indirection, and therefore performs +relatively well, particularly on implementations like PyPy where the cost of +loops at the Python-level is not too expensive. The total number of loop +iterations is 4x the number of bytes passed to the decoder. +""" +from .exceptions import HPACKDecodingError + + +# This defines the state machine "class" at the top of the file. The reason we +# do this is to keep the terrifing monster state table at the *bottom* of the +# file so you don't have to actually *look* at the damn thing. +def decode_huffman(huffman_string): + """ + Given a bytestring of Huffman-encoded data for HPACK, returns a bytestring + of the decompressed data. + """ + if not huffman_string: + return b'' + + state = 0 + flags = 0 + decoded_bytes = bytearray() + + # Perversely, bytearrays are a lot more convenient across Python 2 and + # Python 3 because they behave *the same way* on both platforms. Given that + # we really do want numerical bytes when we iterate here, let's use a + # bytearray. + huffman_string = bytearray(huffman_string) + + # This loop is unrolled somewhat. Because we use a nibble, not a byte, we + # need to handle each nibble twice. We unroll that: it makes the loop body + # a bit longer, but that's ok. + for input_byte in huffman_string: + index = (state * 16) + (input_byte >> 4) + state, flags, output_byte = HUFFMAN_TABLE[index] + + if flags & HUFFMAN_FAIL: + raise HPACKDecodingError("Invalid Huffman String") + + if flags & HUFFMAN_EMIT_SYMBOL: + decoded_bytes.append(output_byte) + + index = (state * 16) + (input_byte & 0x0F) + state, flags, output_byte = HUFFMAN_TABLE[index] + + if flags & HUFFMAN_FAIL: + raise HPACKDecodingError("Invalid Huffman String") + + if flags & HUFFMAN_EMIT_SYMBOL: + decoded_bytes.append(output_byte) + + if not (flags & HUFFMAN_COMPLETE): + raise HPACKDecodingError("Incomplete Huffman string") + + return bytes(decoded_bytes) + + +# Some decoder flags to control state transitions. +HUFFMAN_COMPLETE = 1 +HUFFMAN_EMIT_SYMBOL = (1 << 1) +HUFFMAN_FAIL = (1 << 2) + +# This is the monster table. Avert your eyes, children. +HUFFMAN_TABLE = [ + # Node 0 (Root Node, never emits symbols.) + (4, 0, 0), + (5, 0, 0), + (7, 0, 0), + (8, 0, 0), + (11, 0, 0), + (12, 0, 0), + (16, 0, 0), + (19, 0, 0), + (25, 0, 0), + (28, 0, 0), + (32, 0, 0), + (35, 0, 0), + (42, 0, 0), + (49, 0, 0), + (57, 0, 0), + (64, HUFFMAN_COMPLETE, 0), + + # Node 1 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 48), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 49), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 50), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 97), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 99), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 101), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 105), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 111), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 115), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 116), + (13, 0, 0), + (14, 0, 0), + (17, 0, 0), + (18, 0, 0), + (20, 0, 0), + (21, 0, 0), + + # Node 2 + (1, HUFFMAN_EMIT_SYMBOL, 48), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 48), + (1, HUFFMAN_EMIT_SYMBOL, 49), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 49), + (1, HUFFMAN_EMIT_SYMBOL, 50), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 50), + (1, HUFFMAN_EMIT_SYMBOL, 97), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 97), + (1, HUFFMAN_EMIT_SYMBOL, 99), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 99), + (1, HUFFMAN_EMIT_SYMBOL, 101), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 101), + (1, HUFFMAN_EMIT_SYMBOL, 105), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 105), + (1, HUFFMAN_EMIT_SYMBOL, 111), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 111), + + # Node 3 + (2, HUFFMAN_EMIT_SYMBOL, 48), + (9, HUFFMAN_EMIT_SYMBOL, 48), + (23, HUFFMAN_EMIT_SYMBOL, 48), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 48), + (2, HUFFMAN_EMIT_SYMBOL, 49), + (9, HUFFMAN_EMIT_SYMBOL, 49), + (23, HUFFMAN_EMIT_SYMBOL, 49), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 49), + (2, HUFFMAN_EMIT_SYMBOL, 50), + (9, HUFFMAN_EMIT_SYMBOL, 50), + (23, HUFFMAN_EMIT_SYMBOL, 50), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 50), + (2, HUFFMAN_EMIT_SYMBOL, 97), + (9, HUFFMAN_EMIT_SYMBOL, 97), + (23, HUFFMAN_EMIT_SYMBOL, 97), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 97), + + # Node 4 + (3, HUFFMAN_EMIT_SYMBOL, 48), + (6, HUFFMAN_EMIT_SYMBOL, 48), + (10, HUFFMAN_EMIT_SYMBOL, 48), + (15, HUFFMAN_EMIT_SYMBOL, 48), + (24, HUFFMAN_EMIT_SYMBOL, 48), + (31, HUFFMAN_EMIT_SYMBOL, 48), + (41, HUFFMAN_EMIT_SYMBOL, 48), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 48), + (3, HUFFMAN_EMIT_SYMBOL, 49), + (6, HUFFMAN_EMIT_SYMBOL, 49), + (10, HUFFMAN_EMIT_SYMBOL, 49), + (15, HUFFMAN_EMIT_SYMBOL, 49), + (24, HUFFMAN_EMIT_SYMBOL, 49), + (31, HUFFMAN_EMIT_SYMBOL, 49), + (41, HUFFMAN_EMIT_SYMBOL, 49), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 49), + + # Node 5 + (3, HUFFMAN_EMIT_SYMBOL, 50), + (6, HUFFMAN_EMIT_SYMBOL, 50), + (10, HUFFMAN_EMIT_SYMBOL, 50), + (15, HUFFMAN_EMIT_SYMBOL, 50), + (24, HUFFMAN_EMIT_SYMBOL, 50), + (31, HUFFMAN_EMIT_SYMBOL, 50), + (41, HUFFMAN_EMIT_SYMBOL, 50), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 50), + (3, HUFFMAN_EMIT_SYMBOL, 97), + (6, HUFFMAN_EMIT_SYMBOL, 97), + (10, HUFFMAN_EMIT_SYMBOL, 97), + (15, HUFFMAN_EMIT_SYMBOL, 97), + (24, HUFFMAN_EMIT_SYMBOL, 97), + (31, HUFFMAN_EMIT_SYMBOL, 97), + (41, HUFFMAN_EMIT_SYMBOL, 97), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 97), + + # Node 6 + (2, HUFFMAN_EMIT_SYMBOL, 99), + (9, HUFFMAN_EMIT_SYMBOL, 99), + (23, HUFFMAN_EMIT_SYMBOL, 99), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 99), + (2, HUFFMAN_EMIT_SYMBOL, 101), + (9, HUFFMAN_EMIT_SYMBOL, 101), + (23, HUFFMAN_EMIT_SYMBOL, 101), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 101), + (2, HUFFMAN_EMIT_SYMBOL, 105), + (9, HUFFMAN_EMIT_SYMBOL, 105), + (23, HUFFMAN_EMIT_SYMBOL, 105), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 105), + (2, HUFFMAN_EMIT_SYMBOL, 111), + (9, HUFFMAN_EMIT_SYMBOL, 111), + (23, HUFFMAN_EMIT_SYMBOL, 111), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 111), + + # Node 7 + (3, HUFFMAN_EMIT_SYMBOL, 99), + (6, HUFFMAN_EMIT_SYMBOL, 99), + (10, HUFFMAN_EMIT_SYMBOL, 99), + (15, HUFFMAN_EMIT_SYMBOL, 99), + (24, HUFFMAN_EMIT_SYMBOL, 99), + (31, HUFFMAN_EMIT_SYMBOL, 99), + (41, HUFFMAN_EMIT_SYMBOL, 99), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 99), + (3, HUFFMAN_EMIT_SYMBOL, 101), + (6, HUFFMAN_EMIT_SYMBOL, 101), + (10, HUFFMAN_EMIT_SYMBOL, 101), + (15, HUFFMAN_EMIT_SYMBOL, 101), + (24, HUFFMAN_EMIT_SYMBOL, 101), + (31, HUFFMAN_EMIT_SYMBOL, 101), + (41, HUFFMAN_EMIT_SYMBOL, 101), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 101), + + # Node 8 + (3, HUFFMAN_EMIT_SYMBOL, 105), + (6, HUFFMAN_EMIT_SYMBOL, 105), + (10, HUFFMAN_EMIT_SYMBOL, 105), + (15, HUFFMAN_EMIT_SYMBOL, 105), + (24, HUFFMAN_EMIT_SYMBOL, 105), + (31, HUFFMAN_EMIT_SYMBOL, 105), + (41, HUFFMAN_EMIT_SYMBOL, 105), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 105), + (3, HUFFMAN_EMIT_SYMBOL, 111), + (6, HUFFMAN_EMIT_SYMBOL, 111), + (10, HUFFMAN_EMIT_SYMBOL, 111), + (15, HUFFMAN_EMIT_SYMBOL, 111), + (24, HUFFMAN_EMIT_SYMBOL, 111), + (31, HUFFMAN_EMIT_SYMBOL, 111), + (41, HUFFMAN_EMIT_SYMBOL, 111), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 111), + + # Node 9 + (1, HUFFMAN_EMIT_SYMBOL, 115), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 115), + (1, HUFFMAN_EMIT_SYMBOL, 116), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 116), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 32), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 37), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 45), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 46), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 47), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 51), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 52), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 53), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 54), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 55), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 56), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 57), + + # Node 10 + (2, HUFFMAN_EMIT_SYMBOL, 115), + (9, HUFFMAN_EMIT_SYMBOL, 115), + (23, HUFFMAN_EMIT_SYMBOL, 115), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 115), + (2, HUFFMAN_EMIT_SYMBOL, 116), + (9, HUFFMAN_EMIT_SYMBOL, 116), + (23, HUFFMAN_EMIT_SYMBOL, 116), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 116), + (1, HUFFMAN_EMIT_SYMBOL, 32), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 32), + (1, HUFFMAN_EMIT_SYMBOL, 37), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 37), + (1, HUFFMAN_EMIT_SYMBOL, 45), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 45), + (1, HUFFMAN_EMIT_SYMBOL, 46), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 46), + + # Node 11 + (3, HUFFMAN_EMIT_SYMBOL, 115), + (6, HUFFMAN_EMIT_SYMBOL, 115), + (10, HUFFMAN_EMIT_SYMBOL, 115), + (15, HUFFMAN_EMIT_SYMBOL, 115), + (24, HUFFMAN_EMIT_SYMBOL, 115), + (31, HUFFMAN_EMIT_SYMBOL, 115), + (41, HUFFMAN_EMIT_SYMBOL, 115), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 115), + (3, HUFFMAN_EMIT_SYMBOL, 116), + (6, HUFFMAN_EMIT_SYMBOL, 116), + (10, HUFFMAN_EMIT_SYMBOL, 116), + (15, HUFFMAN_EMIT_SYMBOL, 116), + (24, HUFFMAN_EMIT_SYMBOL, 116), + (31, HUFFMAN_EMIT_SYMBOL, 116), + (41, HUFFMAN_EMIT_SYMBOL, 116), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 116), + + # Node 12 + (2, HUFFMAN_EMIT_SYMBOL, 32), + (9, HUFFMAN_EMIT_SYMBOL, 32), + (23, HUFFMAN_EMIT_SYMBOL, 32), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 32), + (2, HUFFMAN_EMIT_SYMBOL, 37), + (9, HUFFMAN_EMIT_SYMBOL, 37), + (23, HUFFMAN_EMIT_SYMBOL, 37), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 37), + (2, HUFFMAN_EMIT_SYMBOL, 45), + (9, HUFFMAN_EMIT_SYMBOL, 45), + (23, HUFFMAN_EMIT_SYMBOL, 45), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 45), + (2, HUFFMAN_EMIT_SYMBOL, 46), + (9, HUFFMAN_EMIT_SYMBOL, 46), + (23, HUFFMAN_EMIT_SYMBOL, 46), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 46), + + # Node 13 + (3, HUFFMAN_EMIT_SYMBOL, 32), + (6, HUFFMAN_EMIT_SYMBOL, 32), + (10, HUFFMAN_EMIT_SYMBOL, 32), + (15, HUFFMAN_EMIT_SYMBOL, 32), + (24, HUFFMAN_EMIT_SYMBOL, 32), + (31, HUFFMAN_EMIT_SYMBOL, 32), + (41, HUFFMAN_EMIT_SYMBOL, 32), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 32), + (3, HUFFMAN_EMIT_SYMBOL, 37), + (6, HUFFMAN_EMIT_SYMBOL, 37), + (10, HUFFMAN_EMIT_SYMBOL, 37), + (15, HUFFMAN_EMIT_SYMBOL, 37), + (24, HUFFMAN_EMIT_SYMBOL, 37), + (31, HUFFMAN_EMIT_SYMBOL, 37), + (41, HUFFMAN_EMIT_SYMBOL, 37), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 37), + + # Node 14 + (3, HUFFMAN_EMIT_SYMBOL, 45), + (6, HUFFMAN_EMIT_SYMBOL, 45), + (10, HUFFMAN_EMIT_SYMBOL, 45), + (15, HUFFMAN_EMIT_SYMBOL, 45), + (24, HUFFMAN_EMIT_SYMBOL, 45), + (31, HUFFMAN_EMIT_SYMBOL, 45), + (41, HUFFMAN_EMIT_SYMBOL, 45), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 45), + (3, HUFFMAN_EMIT_SYMBOL, 46), + (6, HUFFMAN_EMIT_SYMBOL, 46), + (10, HUFFMAN_EMIT_SYMBOL, 46), + (15, HUFFMAN_EMIT_SYMBOL, 46), + (24, HUFFMAN_EMIT_SYMBOL, 46), + (31, HUFFMAN_EMIT_SYMBOL, 46), + (41, HUFFMAN_EMIT_SYMBOL, 46), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 46), + + # Node 15 + (1, HUFFMAN_EMIT_SYMBOL, 47), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 47), + (1, HUFFMAN_EMIT_SYMBOL, 51), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 51), + (1, HUFFMAN_EMIT_SYMBOL, 52), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 52), + (1, HUFFMAN_EMIT_SYMBOL, 53), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 53), + (1, HUFFMAN_EMIT_SYMBOL, 54), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 54), + (1, HUFFMAN_EMIT_SYMBOL, 55), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 55), + (1, HUFFMAN_EMIT_SYMBOL, 56), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 56), + (1, HUFFMAN_EMIT_SYMBOL, 57), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 57), + + # Node 16 + (2, HUFFMAN_EMIT_SYMBOL, 47), + (9, HUFFMAN_EMIT_SYMBOL, 47), + (23, HUFFMAN_EMIT_SYMBOL, 47), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 47), + (2, HUFFMAN_EMIT_SYMBOL, 51), + (9, HUFFMAN_EMIT_SYMBOL, 51), + (23, HUFFMAN_EMIT_SYMBOL, 51), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 51), + (2, HUFFMAN_EMIT_SYMBOL, 52), + (9, HUFFMAN_EMIT_SYMBOL, 52), + (23, HUFFMAN_EMIT_SYMBOL, 52), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 52), + (2, HUFFMAN_EMIT_SYMBOL, 53), + (9, HUFFMAN_EMIT_SYMBOL, 53), + (23, HUFFMAN_EMIT_SYMBOL, 53), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 53), + + # Node 17 + (3, HUFFMAN_EMIT_SYMBOL, 47), + (6, HUFFMAN_EMIT_SYMBOL, 47), + (10, HUFFMAN_EMIT_SYMBOL, 47), + (15, HUFFMAN_EMIT_SYMBOL, 47), + (24, HUFFMAN_EMIT_SYMBOL, 47), + (31, HUFFMAN_EMIT_SYMBOL, 47), + (41, HUFFMAN_EMIT_SYMBOL, 47), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 47), + (3, HUFFMAN_EMIT_SYMBOL, 51), + (6, HUFFMAN_EMIT_SYMBOL, 51), + (10, HUFFMAN_EMIT_SYMBOL, 51), + (15, HUFFMAN_EMIT_SYMBOL, 51), + (24, HUFFMAN_EMIT_SYMBOL, 51), + (31, HUFFMAN_EMIT_SYMBOL, 51), + (41, HUFFMAN_EMIT_SYMBOL, 51), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 51), + + # Node 18 + (3, HUFFMAN_EMIT_SYMBOL, 52), + (6, HUFFMAN_EMIT_SYMBOL, 52), + (10, HUFFMAN_EMIT_SYMBOL, 52), + (15, HUFFMAN_EMIT_SYMBOL, 52), + (24, HUFFMAN_EMIT_SYMBOL, 52), + (31, HUFFMAN_EMIT_SYMBOL, 52), + (41, HUFFMAN_EMIT_SYMBOL, 52), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 52), + (3, HUFFMAN_EMIT_SYMBOL, 53), + (6, HUFFMAN_EMIT_SYMBOL, 53), + (10, HUFFMAN_EMIT_SYMBOL, 53), + (15, HUFFMAN_EMIT_SYMBOL, 53), + (24, HUFFMAN_EMIT_SYMBOL, 53), + (31, HUFFMAN_EMIT_SYMBOL, 53), + (41, HUFFMAN_EMIT_SYMBOL, 53), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 53), + + # Node 19 + (2, HUFFMAN_EMIT_SYMBOL, 54), + (9, HUFFMAN_EMIT_SYMBOL, 54), + (23, HUFFMAN_EMIT_SYMBOL, 54), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 54), + (2, HUFFMAN_EMIT_SYMBOL, 55), + (9, HUFFMAN_EMIT_SYMBOL, 55), + (23, HUFFMAN_EMIT_SYMBOL, 55), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 55), + (2, HUFFMAN_EMIT_SYMBOL, 56), + (9, HUFFMAN_EMIT_SYMBOL, 56), + (23, HUFFMAN_EMIT_SYMBOL, 56), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 56), + (2, HUFFMAN_EMIT_SYMBOL, 57), + (9, HUFFMAN_EMIT_SYMBOL, 57), + (23, HUFFMAN_EMIT_SYMBOL, 57), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 57), + + # Node 20 + (3, HUFFMAN_EMIT_SYMBOL, 54), + (6, HUFFMAN_EMIT_SYMBOL, 54), + (10, HUFFMAN_EMIT_SYMBOL, 54), + (15, HUFFMAN_EMIT_SYMBOL, 54), + (24, HUFFMAN_EMIT_SYMBOL, 54), + (31, HUFFMAN_EMIT_SYMBOL, 54), + (41, HUFFMAN_EMIT_SYMBOL, 54), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 54), + (3, HUFFMAN_EMIT_SYMBOL, 55), + (6, HUFFMAN_EMIT_SYMBOL, 55), + (10, HUFFMAN_EMIT_SYMBOL, 55), + (15, HUFFMAN_EMIT_SYMBOL, 55), + (24, HUFFMAN_EMIT_SYMBOL, 55), + (31, HUFFMAN_EMIT_SYMBOL, 55), + (41, HUFFMAN_EMIT_SYMBOL, 55), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 55), + + # Node 21 + (3, HUFFMAN_EMIT_SYMBOL, 56), + (6, HUFFMAN_EMIT_SYMBOL, 56), + (10, HUFFMAN_EMIT_SYMBOL, 56), + (15, HUFFMAN_EMIT_SYMBOL, 56), + (24, HUFFMAN_EMIT_SYMBOL, 56), + (31, HUFFMAN_EMIT_SYMBOL, 56), + (41, HUFFMAN_EMIT_SYMBOL, 56), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 56), + (3, HUFFMAN_EMIT_SYMBOL, 57), + (6, HUFFMAN_EMIT_SYMBOL, 57), + (10, HUFFMAN_EMIT_SYMBOL, 57), + (15, HUFFMAN_EMIT_SYMBOL, 57), + (24, HUFFMAN_EMIT_SYMBOL, 57), + (31, HUFFMAN_EMIT_SYMBOL, 57), + (41, HUFFMAN_EMIT_SYMBOL, 57), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 57), + + # Node 22 + (26, 0, 0), + (27, 0, 0), + (29, 0, 0), + (30, 0, 0), + (33, 0, 0), + (34, 0, 0), + (36, 0, 0), + (37, 0, 0), + (43, 0, 0), + (46, 0, 0), + (50, 0, 0), + (53, 0, 0), + (58, 0, 0), + (61, 0, 0), + (65, 0, 0), + (68, HUFFMAN_COMPLETE, 0), + + # Node 23 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 61), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 65), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 95), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 98), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 100), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 102), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 103), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 104), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 108), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 109), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 110), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 112), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 114), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 117), + (38, 0, 0), + (39, 0, 0), + + # Node 24 + (1, HUFFMAN_EMIT_SYMBOL, 61), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 61), + (1, HUFFMAN_EMIT_SYMBOL, 65), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 65), + (1, HUFFMAN_EMIT_SYMBOL, 95), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 95), + (1, HUFFMAN_EMIT_SYMBOL, 98), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 98), + (1, HUFFMAN_EMIT_SYMBOL, 100), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 100), + (1, HUFFMAN_EMIT_SYMBOL, 102), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 102), + (1, HUFFMAN_EMIT_SYMBOL, 103), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 103), + (1, HUFFMAN_EMIT_SYMBOL, 104), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 104), + + # Node 25 + (2, HUFFMAN_EMIT_SYMBOL, 61), + (9, HUFFMAN_EMIT_SYMBOL, 61), + (23, HUFFMAN_EMIT_SYMBOL, 61), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 61), + (2, HUFFMAN_EMIT_SYMBOL, 65), + (9, HUFFMAN_EMIT_SYMBOL, 65), + (23, HUFFMAN_EMIT_SYMBOL, 65), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 65), + (2, HUFFMAN_EMIT_SYMBOL, 95), + (9, HUFFMAN_EMIT_SYMBOL, 95), + (23, HUFFMAN_EMIT_SYMBOL, 95), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 95), + (2, HUFFMAN_EMIT_SYMBOL, 98), + (9, HUFFMAN_EMIT_SYMBOL, 98), + (23, HUFFMAN_EMIT_SYMBOL, 98), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 98), + + # Node 26 + (3, HUFFMAN_EMIT_SYMBOL, 61), + (6, HUFFMAN_EMIT_SYMBOL, 61), + (10, HUFFMAN_EMIT_SYMBOL, 61), + (15, HUFFMAN_EMIT_SYMBOL, 61), + (24, HUFFMAN_EMIT_SYMBOL, 61), + (31, HUFFMAN_EMIT_SYMBOL, 61), + (41, HUFFMAN_EMIT_SYMBOL, 61), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 61), + (3, HUFFMAN_EMIT_SYMBOL, 65), + (6, HUFFMAN_EMIT_SYMBOL, 65), + (10, HUFFMAN_EMIT_SYMBOL, 65), + (15, HUFFMAN_EMIT_SYMBOL, 65), + (24, HUFFMAN_EMIT_SYMBOL, 65), + (31, HUFFMAN_EMIT_SYMBOL, 65), + (41, HUFFMAN_EMIT_SYMBOL, 65), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 65), + + # Node 27 + (3, HUFFMAN_EMIT_SYMBOL, 95), + (6, HUFFMAN_EMIT_SYMBOL, 95), + (10, HUFFMAN_EMIT_SYMBOL, 95), + (15, HUFFMAN_EMIT_SYMBOL, 95), + (24, HUFFMAN_EMIT_SYMBOL, 95), + (31, HUFFMAN_EMIT_SYMBOL, 95), + (41, HUFFMAN_EMIT_SYMBOL, 95), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 95), + (3, HUFFMAN_EMIT_SYMBOL, 98), + (6, HUFFMAN_EMIT_SYMBOL, 98), + (10, HUFFMAN_EMIT_SYMBOL, 98), + (15, HUFFMAN_EMIT_SYMBOL, 98), + (24, HUFFMAN_EMIT_SYMBOL, 98), + (31, HUFFMAN_EMIT_SYMBOL, 98), + (41, HUFFMAN_EMIT_SYMBOL, 98), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 98), + + # Node 28 + (2, HUFFMAN_EMIT_SYMBOL, 100), + (9, HUFFMAN_EMIT_SYMBOL, 100), + (23, HUFFMAN_EMIT_SYMBOL, 100), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 100), + (2, HUFFMAN_EMIT_SYMBOL, 102), + (9, HUFFMAN_EMIT_SYMBOL, 102), + (23, HUFFMAN_EMIT_SYMBOL, 102), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 102), + (2, HUFFMAN_EMIT_SYMBOL, 103), + (9, HUFFMAN_EMIT_SYMBOL, 103), + (23, HUFFMAN_EMIT_SYMBOL, 103), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 103), + (2, HUFFMAN_EMIT_SYMBOL, 104), + (9, HUFFMAN_EMIT_SYMBOL, 104), + (23, HUFFMAN_EMIT_SYMBOL, 104), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 104), + + # Node 29 + (3, HUFFMAN_EMIT_SYMBOL, 100), + (6, HUFFMAN_EMIT_SYMBOL, 100), + (10, HUFFMAN_EMIT_SYMBOL, 100), + (15, HUFFMAN_EMIT_SYMBOL, 100), + (24, HUFFMAN_EMIT_SYMBOL, 100), + (31, HUFFMAN_EMIT_SYMBOL, 100), + (41, HUFFMAN_EMIT_SYMBOL, 100), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 100), + (3, HUFFMAN_EMIT_SYMBOL, 102), + (6, HUFFMAN_EMIT_SYMBOL, 102), + (10, HUFFMAN_EMIT_SYMBOL, 102), + (15, HUFFMAN_EMIT_SYMBOL, 102), + (24, HUFFMAN_EMIT_SYMBOL, 102), + (31, HUFFMAN_EMIT_SYMBOL, 102), + (41, HUFFMAN_EMIT_SYMBOL, 102), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 102), + + # Node 30 + (3, HUFFMAN_EMIT_SYMBOL, 103), + (6, HUFFMAN_EMIT_SYMBOL, 103), + (10, HUFFMAN_EMIT_SYMBOL, 103), + (15, HUFFMAN_EMIT_SYMBOL, 103), + (24, HUFFMAN_EMIT_SYMBOL, 103), + (31, HUFFMAN_EMIT_SYMBOL, 103), + (41, HUFFMAN_EMIT_SYMBOL, 103), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 103), + (3, HUFFMAN_EMIT_SYMBOL, 104), + (6, HUFFMAN_EMIT_SYMBOL, 104), + (10, HUFFMAN_EMIT_SYMBOL, 104), + (15, HUFFMAN_EMIT_SYMBOL, 104), + (24, HUFFMAN_EMIT_SYMBOL, 104), + (31, HUFFMAN_EMIT_SYMBOL, 104), + (41, HUFFMAN_EMIT_SYMBOL, 104), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 104), + + # Node 31 + (1, HUFFMAN_EMIT_SYMBOL, 108), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 108), + (1, HUFFMAN_EMIT_SYMBOL, 109), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 109), + (1, HUFFMAN_EMIT_SYMBOL, 110), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 110), + (1, HUFFMAN_EMIT_SYMBOL, 112), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 112), + (1, HUFFMAN_EMIT_SYMBOL, 114), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 114), + (1, HUFFMAN_EMIT_SYMBOL, 117), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 117), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 58), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 66), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 67), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 68), + + # Node 32 + (2, HUFFMAN_EMIT_SYMBOL, 108), + (9, HUFFMAN_EMIT_SYMBOL, 108), + (23, HUFFMAN_EMIT_SYMBOL, 108), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 108), + (2, HUFFMAN_EMIT_SYMBOL, 109), + (9, HUFFMAN_EMIT_SYMBOL, 109), + (23, HUFFMAN_EMIT_SYMBOL, 109), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 109), + (2, HUFFMAN_EMIT_SYMBOL, 110), + (9, HUFFMAN_EMIT_SYMBOL, 110), + (23, HUFFMAN_EMIT_SYMBOL, 110), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 110), + (2, HUFFMAN_EMIT_SYMBOL, 112), + (9, HUFFMAN_EMIT_SYMBOL, 112), + (23, HUFFMAN_EMIT_SYMBOL, 112), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 112), + + # Node 33 + (3, HUFFMAN_EMIT_SYMBOL, 108), + (6, HUFFMAN_EMIT_SYMBOL, 108), + (10, HUFFMAN_EMIT_SYMBOL, 108), + (15, HUFFMAN_EMIT_SYMBOL, 108), + (24, HUFFMAN_EMIT_SYMBOL, 108), + (31, HUFFMAN_EMIT_SYMBOL, 108), + (41, HUFFMAN_EMIT_SYMBOL, 108), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 108), + (3, HUFFMAN_EMIT_SYMBOL, 109), + (6, HUFFMAN_EMIT_SYMBOL, 109), + (10, HUFFMAN_EMIT_SYMBOL, 109), + (15, HUFFMAN_EMIT_SYMBOL, 109), + (24, HUFFMAN_EMIT_SYMBOL, 109), + (31, HUFFMAN_EMIT_SYMBOL, 109), + (41, HUFFMAN_EMIT_SYMBOL, 109), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 109), + + # Node 34 + (3, HUFFMAN_EMIT_SYMBOL, 110), + (6, HUFFMAN_EMIT_SYMBOL, 110), + (10, HUFFMAN_EMIT_SYMBOL, 110), + (15, HUFFMAN_EMIT_SYMBOL, 110), + (24, HUFFMAN_EMIT_SYMBOL, 110), + (31, HUFFMAN_EMIT_SYMBOL, 110), + (41, HUFFMAN_EMIT_SYMBOL, 110), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 110), + (3, HUFFMAN_EMIT_SYMBOL, 112), + (6, HUFFMAN_EMIT_SYMBOL, 112), + (10, HUFFMAN_EMIT_SYMBOL, 112), + (15, HUFFMAN_EMIT_SYMBOL, 112), + (24, HUFFMAN_EMIT_SYMBOL, 112), + (31, HUFFMAN_EMIT_SYMBOL, 112), + (41, HUFFMAN_EMIT_SYMBOL, 112), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 112), + + # Node 35 + (2, HUFFMAN_EMIT_SYMBOL, 114), + (9, HUFFMAN_EMIT_SYMBOL, 114), + (23, HUFFMAN_EMIT_SYMBOL, 114), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 114), + (2, HUFFMAN_EMIT_SYMBOL, 117), + (9, HUFFMAN_EMIT_SYMBOL, 117), + (23, HUFFMAN_EMIT_SYMBOL, 117), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 117), + (1, HUFFMAN_EMIT_SYMBOL, 58), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 58), + (1, HUFFMAN_EMIT_SYMBOL, 66), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 66), + (1, HUFFMAN_EMIT_SYMBOL, 67), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 67), + (1, HUFFMAN_EMIT_SYMBOL, 68), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 68), + + # Node 36 + (3, HUFFMAN_EMIT_SYMBOL, 114), + (6, HUFFMAN_EMIT_SYMBOL, 114), + (10, HUFFMAN_EMIT_SYMBOL, 114), + (15, HUFFMAN_EMIT_SYMBOL, 114), + (24, HUFFMAN_EMIT_SYMBOL, 114), + (31, HUFFMAN_EMIT_SYMBOL, 114), + (41, HUFFMAN_EMIT_SYMBOL, 114), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 114), + (3, HUFFMAN_EMIT_SYMBOL, 117), + (6, HUFFMAN_EMIT_SYMBOL, 117), + (10, HUFFMAN_EMIT_SYMBOL, 117), + (15, HUFFMAN_EMIT_SYMBOL, 117), + (24, HUFFMAN_EMIT_SYMBOL, 117), + (31, HUFFMAN_EMIT_SYMBOL, 117), + (41, HUFFMAN_EMIT_SYMBOL, 117), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 117), + + # Node 37 + (2, HUFFMAN_EMIT_SYMBOL, 58), + (9, HUFFMAN_EMIT_SYMBOL, 58), + (23, HUFFMAN_EMIT_SYMBOL, 58), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 58), + (2, HUFFMAN_EMIT_SYMBOL, 66), + (9, HUFFMAN_EMIT_SYMBOL, 66), + (23, HUFFMAN_EMIT_SYMBOL, 66), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 66), + (2, HUFFMAN_EMIT_SYMBOL, 67), + (9, HUFFMAN_EMIT_SYMBOL, 67), + (23, HUFFMAN_EMIT_SYMBOL, 67), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 67), + (2, HUFFMAN_EMIT_SYMBOL, 68), + (9, HUFFMAN_EMIT_SYMBOL, 68), + (23, HUFFMAN_EMIT_SYMBOL, 68), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 68), + + # Node 38 + (3, HUFFMAN_EMIT_SYMBOL, 58), + (6, HUFFMAN_EMIT_SYMBOL, 58), + (10, HUFFMAN_EMIT_SYMBOL, 58), + (15, HUFFMAN_EMIT_SYMBOL, 58), + (24, HUFFMAN_EMIT_SYMBOL, 58), + (31, HUFFMAN_EMIT_SYMBOL, 58), + (41, HUFFMAN_EMIT_SYMBOL, 58), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 58), + (3, HUFFMAN_EMIT_SYMBOL, 66), + (6, HUFFMAN_EMIT_SYMBOL, 66), + (10, HUFFMAN_EMIT_SYMBOL, 66), + (15, HUFFMAN_EMIT_SYMBOL, 66), + (24, HUFFMAN_EMIT_SYMBOL, 66), + (31, HUFFMAN_EMIT_SYMBOL, 66), + (41, HUFFMAN_EMIT_SYMBOL, 66), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 66), + + # Node 39 + (3, HUFFMAN_EMIT_SYMBOL, 67), + (6, HUFFMAN_EMIT_SYMBOL, 67), + (10, HUFFMAN_EMIT_SYMBOL, 67), + (15, HUFFMAN_EMIT_SYMBOL, 67), + (24, HUFFMAN_EMIT_SYMBOL, 67), + (31, HUFFMAN_EMIT_SYMBOL, 67), + (41, HUFFMAN_EMIT_SYMBOL, 67), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 67), + (3, HUFFMAN_EMIT_SYMBOL, 68), + (6, HUFFMAN_EMIT_SYMBOL, 68), + (10, HUFFMAN_EMIT_SYMBOL, 68), + (15, HUFFMAN_EMIT_SYMBOL, 68), + (24, HUFFMAN_EMIT_SYMBOL, 68), + (31, HUFFMAN_EMIT_SYMBOL, 68), + (41, HUFFMAN_EMIT_SYMBOL, 68), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 68), + + # Node 40 + (44, 0, 0), + (45, 0, 0), + (47, 0, 0), + (48, 0, 0), + (51, 0, 0), + (52, 0, 0), + (54, 0, 0), + (55, 0, 0), + (59, 0, 0), + (60, 0, 0), + (62, 0, 0), + (63, 0, 0), + (66, 0, 0), + (67, 0, 0), + (69, 0, 0), + (72, HUFFMAN_COMPLETE, 0), + + # Node 41 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 69), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 70), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 71), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 72), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 73), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 74), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 75), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 76), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 77), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 78), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 79), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 80), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 81), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 82), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 83), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 84), + + # Node 42 + (1, HUFFMAN_EMIT_SYMBOL, 69), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 69), + (1, HUFFMAN_EMIT_SYMBOL, 70), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 70), + (1, HUFFMAN_EMIT_SYMBOL, 71), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 71), + (1, HUFFMAN_EMIT_SYMBOL, 72), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 72), + (1, HUFFMAN_EMIT_SYMBOL, 73), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 73), + (1, HUFFMAN_EMIT_SYMBOL, 74), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 74), + (1, HUFFMAN_EMIT_SYMBOL, 75), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 75), + (1, HUFFMAN_EMIT_SYMBOL, 76), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 76), + + # Node 43 + (2, HUFFMAN_EMIT_SYMBOL, 69), + (9, HUFFMAN_EMIT_SYMBOL, 69), + (23, HUFFMAN_EMIT_SYMBOL, 69), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 69), + (2, HUFFMAN_EMIT_SYMBOL, 70), + (9, HUFFMAN_EMIT_SYMBOL, 70), + (23, HUFFMAN_EMIT_SYMBOL, 70), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 70), + (2, HUFFMAN_EMIT_SYMBOL, 71), + (9, HUFFMAN_EMIT_SYMBOL, 71), + (23, HUFFMAN_EMIT_SYMBOL, 71), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 71), + (2, HUFFMAN_EMIT_SYMBOL, 72), + (9, HUFFMAN_EMIT_SYMBOL, 72), + (23, HUFFMAN_EMIT_SYMBOL, 72), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 72), + + # Node 44 + (3, HUFFMAN_EMIT_SYMBOL, 69), + (6, HUFFMAN_EMIT_SYMBOL, 69), + (10, HUFFMAN_EMIT_SYMBOL, 69), + (15, HUFFMAN_EMIT_SYMBOL, 69), + (24, HUFFMAN_EMIT_SYMBOL, 69), + (31, HUFFMAN_EMIT_SYMBOL, 69), + (41, HUFFMAN_EMIT_SYMBOL, 69), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 69), + (3, HUFFMAN_EMIT_SYMBOL, 70), + (6, HUFFMAN_EMIT_SYMBOL, 70), + (10, HUFFMAN_EMIT_SYMBOL, 70), + (15, HUFFMAN_EMIT_SYMBOL, 70), + (24, HUFFMAN_EMIT_SYMBOL, 70), + (31, HUFFMAN_EMIT_SYMBOL, 70), + (41, HUFFMAN_EMIT_SYMBOL, 70), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 70), + + # Node 45 + (3, HUFFMAN_EMIT_SYMBOL, 71), + (6, HUFFMAN_EMIT_SYMBOL, 71), + (10, HUFFMAN_EMIT_SYMBOL, 71), + (15, HUFFMAN_EMIT_SYMBOL, 71), + (24, HUFFMAN_EMIT_SYMBOL, 71), + (31, HUFFMAN_EMIT_SYMBOL, 71), + (41, HUFFMAN_EMIT_SYMBOL, 71), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 71), + (3, HUFFMAN_EMIT_SYMBOL, 72), + (6, HUFFMAN_EMIT_SYMBOL, 72), + (10, HUFFMAN_EMIT_SYMBOL, 72), + (15, HUFFMAN_EMIT_SYMBOL, 72), + (24, HUFFMAN_EMIT_SYMBOL, 72), + (31, HUFFMAN_EMIT_SYMBOL, 72), + (41, HUFFMAN_EMIT_SYMBOL, 72), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 72), + + # Node 46 + (2, HUFFMAN_EMIT_SYMBOL, 73), + (9, HUFFMAN_EMIT_SYMBOL, 73), + (23, HUFFMAN_EMIT_SYMBOL, 73), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 73), + (2, HUFFMAN_EMIT_SYMBOL, 74), + (9, HUFFMAN_EMIT_SYMBOL, 74), + (23, HUFFMAN_EMIT_SYMBOL, 74), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 74), + (2, HUFFMAN_EMIT_SYMBOL, 75), + (9, HUFFMAN_EMIT_SYMBOL, 75), + (23, HUFFMAN_EMIT_SYMBOL, 75), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 75), + (2, HUFFMAN_EMIT_SYMBOL, 76), + (9, HUFFMAN_EMIT_SYMBOL, 76), + (23, HUFFMAN_EMIT_SYMBOL, 76), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 76), + + # Node 47 + (3, HUFFMAN_EMIT_SYMBOL, 73), + (6, HUFFMAN_EMIT_SYMBOL, 73), + (10, HUFFMAN_EMIT_SYMBOL, 73), + (15, HUFFMAN_EMIT_SYMBOL, 73), + (24, HUFFMAN_EMIT_SYMBOL, 73), + (31, HUFFMAN_EMIT_SYMBOL, 73), + (41, HUFFMAN_EMIT_SYMBOL, 73), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 73), + (3, HUFFMAN_EMIT_SYMBOL, 74), + (6, HUFFMAN_EMIT_SYMBOL, 74), + (10, HUFFMAN_EMIT_SYMBOL, 74), + (15, HUFFMAN_EMIT_SYMBOL, 74), + (24, HUFFMAN_EMIT_SYMBOL, 74), + (31, HUFFMAN_EMIT_SYMBOL, 74), + (41, HUFFMAN_EMIT_SYMBOL, 74), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 74), + + # Node 48 + (3, HUFFMAN_EMIT_SYMBOL, 75), + (6, HUFFMAN_EMIT_SYMBOL, 75), + (10, HUFFMAN_EMIT_SYMBOL, 75), + (15, HUFFMAN_EMIT_SYMBOL, 75), + (24, HUFFMAN_EMIT_SYMBOL, 75), + (31, HUFFMAN_EMIT_SYMBOL, 75), + (41, HUFFMAN_EMIT_SYMBOL, 75), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 75), + (3, HUFFMAN_EMIT_SYMBOL, 76), + (6, HUFFMAN_EMIT_SYMBOL, 76), + (10, HUFFMAN_EMIT_SYMBOL, 76), + (15, HUFFMAN_EMIT_SYMBOL, 76), + (24, HUFFMAN_EMIT_SYMBOL, 76), + (31, HUFFMAN_EMIT_SYMBOL, 76), + (41, HUFFMAN_EMIT_SYMBOL, 76), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 76), + + # Node 49 + (1, HUFFMAN_EMIT_SYMBOL, 77), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 77), + (1, HUFFMAN_EMIT_SYMBOL, 78), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 78), + (1, HUFFMAN_EMIT_SYMBOL, 79), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 79), + (1, HUFFMAN_EMIT_SYMBOL, 80), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 80), + (1, HUFFMAN_EMIT_SYMBOL, 81), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 81), + (1, HUFFMAN_EMIT_SYMBOL, 82), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 82), + (1, HUFFMAN_EMIT_SYMBOL, 83), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 83), + (1, HUFFMAN_EMIT_SYMBOL, 84), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 84), + + # Node 50 + (2, HUFFMAN_EMIT_SYMBOL, 77), + (9, HUFFMAN_EMIT_SYMBOL, 77), + (23, HUFFMAN_EMIT_SYMBOL, 77), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 77), + (2, HUFFMAN_EMIT_SYMBOL, 78), + (9, HUFFMAN_EMIT_SYMBOL, 78), + (23, HUFFMAN_EMIT_SYMBOL, 78), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 78), + (2, HUFFMAN_EMIT_SYMBOL, 79), + (9, HUFFMAN_EMIT_SYMBOL, 79), + (23, HUFFMAN_EMIT_SYMBOL, 79), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 79), + (2, HUFFMAN_EMIT_SYMBOL, 80), + (9, HUFFMAN_EMIT_SYMBOL, 80), + (23, HUFFMAN_EMIT_SYMBOL, 80), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 80), + + # Node 51 + (3, HUFFMAN_EMIT_SYMBOL, 77), + (6, HUFFMAN_EMIT_SYMBOL, 77), + (10, HUFFMAN_EMIT_SYMBOL, 77), + (15, HUFFMAN_EMIT_SYMBOL, 77), + (24, HUFFMAN_EMIT_SYMBOL, 77), + (31, HUFFMAN_EMIT_SYMBOL, 77), + (41, HUFFMAN_EMIT_SYMBOL, 77), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 77), + (3, HUFFMAN_EMIT_SYMBOL, 78), + (6, HUFFMAN_EMIT_SYMBOL, 78), + (10, HUFFMAN_EMIT_SYMBOL, 78), + (15, HUFFMAN_EMIT_SYMBOL, 78), + (24, HUFFMAN_EMIT_SYMBOL, 78), + (31, HUFFMAN_EMIT_SYMBOL, 78), + (41, HUFFMAN_EMIT_SYMBOL, 78), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 78), + + # Node 52 + (3, HUFFMAN_EMIT_SYMBOL, 79), + (6, HUFFMAN_EMIT_SYMBOL, 79), + (10, HUFFMAN_EMIT_SYMBOL, 79), + (15, HUFFMAN_EMIT_SYMBOL, 79), + (24, HUFFMAN_EMIT_SYMBOL, 79), + (31, HUFFMAN_EMIT_SYMBOL, 79), + (41, HUFFMAN_EMIT_SYMBOL, 79), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 79), + (3, HUFFMAN_EMIT_SYMBOL, 80), + (6, HUFFMAN_EMIT_SYMBOL, 80), + (10, HUFFMAN_EMIT_SYMBOL, 80), + (15, HUFFMAN_EMIT_SYMBOL, 80), + (24, HUFFMAN_EMIT_SYMBOL, 80), + (31, HUFFMAN_EMIT_SYMBOL, 80), + (41, HUFFMAN_EMIT_SYMBOL, 80), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 80), + + # Node 53 + (2, HUFFMAN_EMIT_SYMBOL, 81), + (9, HUFFMAN_EMIT_SYMBOL, 81), + (23, HUFFMAN_EMIT_SYMBOL, 81), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 81), + (2, HUFFMAN_EMIT_SYMBOL, 82), + (9, HUFFMAN_EMIT_SYMBOL, 82), + (23, HUFFMAN_EMIT_SYMBOL, 82), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 82), + (2, HUFFMAN_EMIT_SYMBOL, 83), + (9, HUFFMAN_EMIT_SYMBOL, 83), + (23, HUFFMAN_EMIT_SYMBOL, 83), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 83), + (2, HUFFMAN_EMIT_SYMBOL, 84), + (9, HUFFMAN_EMIT_SYMBOL, 84), + (23, HUFFMAN_EMIT_SYMBOL, 84), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 84), + + # Node 54 + (3, HUFFMAN_EMIT_SYMBOL, 81), + (6, HUFFMAN_EMIT_SYMBOL, 81), + (10, HUFFMAN_EMIT_SYMBOL, 81), + (15, HUFFMAN_EMIT_SYMBOL, 81), + (24, HUFFMAN_EMIT_SYMBOL, 81), + (31, HUFFMAN_EMIT_SYMBOL, 81), + (41, HUFFMAN_EMIT_SYMBOL, 81), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 81), + (3, HUFFMAN_EMIT_SYMBOL, 82), + (6, HUFFMAN_EMIT_SYMBOL, 82), + (10, HUFFMAN_EMIT_SYMBOL, 82), + (15, HUFFMAN_EMIT_SYMBOL, 82), + (24, HUFFMAN_EMIT_SYMBOL, 82), + (31, HUFFMAN_EMIT_SYMBOL, 82), + (41, HUFFMAN_EMIT_SYMBOL, 82), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 82), + + # Node 55 + (3, HUFFMAN_EMIT_SYMBOL, 83), + (6, HUFFMAN_EMIT_SYMBOL, 83), + (10, HUFFMAN_EMIT_SYMBOL, 83), + (15, HUFFMAN_EMIT_SYMBOL, 83), + (24, HUFFMAN_EMIT_SYMBOL, 83), + (31, HUFFMAN_EMIT_SYMBOL, 83), + (41, HUFFMAN_EMIT_SYMBOL, 83), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 83), + (3, HUFFMAN_EMIT_SYMBOL, 84), + (6, HUFFMAN_EMIT_SYMBOL, 84), + (10, HUFFMAN_EMIT_SYMBOL, 84), + (15, HUFFMAN_EMIT_SYMBOL, 84), + (24, HUFFMAN_EMIT_SYMBOL, 84), + (31, HUFFMAN_EMIT_SYMBOL, 84), + (41, HUFFMAN_EMIT_SYMBOL, 84), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 84), + + # Node 56 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 85), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 86), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 87), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 89), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 106), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 107), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 113), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 118), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 119), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 120), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 121), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 122), + (70, 0, 0), + (71, 0, 0), + (73, 0, 0), + (74, HUFFMAN_COMPLETE, 0), + + # Node 57 + (1, HUFFMAN_EMIT_SYMBOL, 85), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 85), + (1, HUFFMAN_EMIT_SYMBOL, 86), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 86), + (1, HUFFMAN_EMIT_SYMBOL, 87), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 87), + (1, HUFFMAN_EMIT_SYMBOL, 89), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 89), + (1, HUFFMAN_EMIT_SYMBOL, 106), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 106), + (1, HUFFMAN_EMIT_SYMBOL, 107), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 107), + (1, HUFFMAN_EMIT_SYMBOL, 113), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 113), + (1, HUFFMAN_EMIT_SYMBOL, 118), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 118), + + # Node 58 + (2, HUFFMAN_EMIT_SYMBOL, 85), + (9, HUFFMAN_EMIT_SYMBOL, 85), + (23, HUFFMAN_EMIT_SYMBOL, 85), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 85), + (2, HUFFMAN_EMIT_SYMBOL, 86), + (9, HUFFMAN_EMIT_SYMBOL, 86), + (23, HUFFMAN_EMIT_SYMBOL, 86), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 86), + (2, HUFFMAN_EMIT_SYMBOL, 87), + (9, HUFFMAN_EMIT_SYMBOL, 87), + (23, HUFFMAN_EMIT_SYMBOL, 87), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 87), + (2, HUFFMAN_EMIT_SYMBOL, 89), + (9, HUFFMAN_EMIT_SYMBOL, 89), + (23, HUFFMAN_EMIT_SYMBOL, 89), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 89), + + # Node 59 + (3, HUFFMAN_EMIT_SYMBOL, 85), + (6, HUFFMAN_EMIT_SYMBOL, 85), + (10, HUFFMAN_EMIT_SYMBOL, 85), + (15, HUFFMAN_EMIT_SYMBOL, 85), + (24, HUFFMAN_EMIT_SYMBOL, 85), + (31, HUFFMAN_EMIT_SYMBOL, 85), + (41, HUFFMAN_EMIT_SYMBOL, 85), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 85), + (3, HUFFMAN_EMIT_SYMBOL, 86), + (6, HUFFMAN_EMIT_SYMBOL, 86), + (10, HUFFMAN_EMIT_SYMBOL, 86), + (15, HUFFMAN_EMIT_SYMBOL, 86), + (24, HUFFMAN_EMIT_SYMBOL, 86), + (31, HUFFMAN_EMIT_SYMBOL, 86), + (41, HUFFMAN_EMIT_SYMBOL, 86), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 86), + + # Node 60 + (3, HUFFMAN_EMIT_SYMBOL, 87), + (6, HUFFMAN_EMIT_SYMBOL, 87), + (10, HUFFMAN_EMIT_SYMBOL, 87), + (15, HUFFMAN_EMIT_SYMBOL, 87), + (24, HUFFMAN_EMIT_SYMBOL, 87), + (31, HUFFMAN_EMIT_SYMBOL, 87), + (41, HUFFMAN_EMIT_SYMBOL, 87), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 87), + (3, HUFFMAN_EMIT_SYMBOL, 89), + (6, HUFFMAN_EMIT_SYMBOL, 89), + (10, HUFFMAN_EMIT_SYMBOL, 89), + (15, HUFFMAN_EMIT_SYMBOL, 89), + (24, HUFFMAN_EMIT_SYMBOL, 89), + (31, HUFFMAN_EMIT_SYMBOL, 89), + (41, HUFFMAN_EMIT_SYMBOL, 89), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 89), + + # Node 61 + (2, HUFFMAN_EMIT_SYMBOL, 106), + (9, HUFFMAN_EMIT_SYMBOL, 106), + (23, HUFFMAN_EMIT_SYMBOL, 106), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 106), + (2, HUFFMAN_EMIT_SYMBOL, 107), + (9, HUFFMAN_EMIT_SYMBOL, 107), + (23, HUFFMAN_EMIT_SYMBOL, 107), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 107), + (2, HUFFMAN_EMIT_SYMBOL, 113), + (9, HUFFMAN_EMIT_SYMBOL, 113), + (23, HUFFMAN_EMIT_SYMBOL, 113), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 113), + (2, HUFFMAN_EMIT_SYMBOL, 118), + (9, HUFFMAN_EMIT_SYMBOL, 118), + (23, HUFFMAN_EMIT_SYMBOL, 118), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 118), + + # Node 62 + (3, HUFFMAN_EMIT_SYMBOL, 106), + (6, HUFFMAN_EMIT_SYMBOL, 106), + (10, HUFFMAN_EMIT_SYMBOL, 106), + (15, HUFFMAN_EMIT_SYMBOL, 106), + (24, HUFFMAN_EMIT_SYMBOL, 106), + (31, HUFFMAN_EMIT_SYMBOL, 106), + (41, HUFFMAN_EMIT_SYMBOL, 106), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 106), + (3, HUFFMAN_EMIT_SYMBOL, 107), + (6, HUFFMAN_EMIT_SYMBOL, 107), + (10, HUFFMAN_EMIT_SYMBOL, 107), + (15, HUFFMAN_EMIT_SYMBOL, 107), + (24, HUFFMAN_EMIT_SYMBOL, 107), + (31, HUFFMAN_EMIT_SYMBOL, 107), + (41, HUFFMAN_EMIT_SYMBOL, 107), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 107), + + # Node 63 + (3, HUFFMAN_EMIT_SYMBOL, 113), + (6, HUFFMAN_EMIT_SYMBOL, 113), + (10, HUFFMAN_EMIT_SYMBOL, 113), + (15, HUFFMAN_EMIT_SYMBOL, 113), + (24, HUFFMAN_EMIT_SYMBOL, 113), + (31, HUFFMAN_EMIT_SYMBOL, 113), + (41, HUFFMAN_EMIT_SYMBOL, 113), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 113), + (3, HUFFMAN_EMIT_SYMBOL, 118), + (6, HUFFMAN_EMIT_SYMBOL, 118), + (10, HUFFMAN_EMIT_SYMBOL, 118), + (15, HUFFMAN_EMIT_SYMBOL, 118), + (24, HUFFMAN_EMIT_SYMBOL, 118), + (31, HUFFMAN_EMIT_SYMBOL, 118), + (41, HUFFMAN_EMIT_SYMBOL, 118), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 118), + + # Node 64 + (1, HUFFMAN_EMIT_SYMBOL, 119), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 119), + (1, HUFFMAN_EMIT_SYMBOL, 120), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 120), + (1, HUFFMAN_EMIT_SYMBOL, 121), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 121), + (1, HUFFMAN_EMIT_SYMBOL, 122), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 122), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 38), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 42), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 44), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 59), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 88), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 90), + (75, 0, 0), + (78, 0, 0), + + # Node 65 + (2, HUFFMAN_EMIT_SYMBOL, 119), + (9, HUFFMAN_EMIT_SYMBOL, 119), + (23, HUFFMAN_EMIT_SYMBOL, 119), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 119), + (2, HUFFMAN_EMIT_SYMBOL, 120), + (9, HUFFMAN_EMIT_SYMBOL, 120), + (23, HUFFMAN_EMIT_SYMBOL, 120), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 120), + (2, HUFFMAN_EMIT_SYMBOL, 121), + (9, HUFFMAN_EMIT_SYMBOL, 121), + (23, HUFFMAN_EMIT_SYMBOL, 121), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 121), + (2, HUFFMAN_EMIT_SYMBOL, 122), + (9, HUFFMAN_EMIT_SYMBOL, 122), + (23, HUFFMAN_EMIT_SYMBOL, 122), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 122), + + # Node 66 + (3, HUFFMAN_EMIT_SYMBOL, 119), + (6, HUFFMAN_EMIT_SYMBOL, 119), + (10, HUFFMAN_EMIT_SYMBOL, 119), + (15, HUFFMAN_EMIT_SYMBOL, 119), + (24, HUFFMAN_EMIT_SYMBOL, 119), + (31, HUFFMAN_EMIT_SYMBOL, 119), + (41, HUFFMAN_EMIT_SYMBOL, 119), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 119), + (3, HUFFMAN_EMIT_SYMBOL, 120), + (6, HUFFMAN_EMIT_SYMBOL, 120), + (10, HUFFMAN_EMIT_SYMBOL, 120), + (15, HUFFMAN_EMIT_SYMBOL, 120), + (24, HUFFMAN_EMIT_SYMBOL, 120), + (31, HUFFMAN_EMIT_SYMBOL, 120), + (41, HUFFMAN_EMIT_SYMBOL, 120), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 120), + + # Node 67 + (3, HUFFMAN_EMIT_SYMBOL, 121), + (6, HUFFMAN_EMIT_SYMBOL, 121), + (10, HUFFMAN_EMIT_SYMBOL, 121), + (15, HUFFMAN_EMIT_SYMBOL, 121), + (24, HUFFMAN_EMIT_SYMBOL, 121), + (31, HUFFMAN_EMIT_SYMBOL, 121), + (41, HUFFMAN_EMIT_SYMBOL, 121), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 121), + (3, HUFFMAN_EMIT_SYMBOL, 122), + (6, HUFFMAN_EMIT_SYMBOL, 122), + (10, HUFFMAN_EMIT_SYMBOL, 122), + (15, HUFFMAN_EMIT_SYMBOL, 122), + (24, HUFFMAN_EMIT_SYMBOL, 122), + (31, HUFFMAN_EMIT_SYMBOL, 122), + (41, HUFFMAN_EMIT_SYMBOL, 122), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 122), + + # Node 68 + (1, HUFFMAN_EMIT_SYMBOL, 38), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 38), + (1, HUFFMAN_EMIT_SYMBOL, 42), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 42), + (1, HUFFMAN_EMIT_SYMBOL, 44), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 44), + (1, HUFFMAN_EMIT_SYMBOL, 59), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 59), + (1, HUFFMAN_EMIT_SYMBOL, 88), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 88), + (1, HUFFMAN_EMIT_SYMBOL, 90), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 90), + (76, 0, 0), + (77, 0, 0), + (79, 0, 0), + (81, 0, 0), + + # Node 69 + (2, HUFFMAN_EMIT_SYMBOL, 38), + (9, HUFFMAN_EMIT_SYMBOL, 38), + (23, HUFFMAN_EMIT_SYMBOL, 38), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 38), + (2, HUFFMAN_EMIT_SYMBOL, 42), + (9, HUFFMAN_EMIT_SYMBOL, 42), + (23, HUFFMAN_EMIT_SYMBOL, 42), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 42), + (2, HUFFMAN_EMIT_SYMBOL, 44), + (9, HUFFMAN_EMIT_SYMBOL, 44), + (23, HUFFMAN_EMIT_SYMBOL, 44), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 44), + (2, HUFFMAN_EMIT_SYMBOL, 59), + (9, HUFFMAN_EMIT_SYMBOL, 59), + (23, HUFFMAN_EMIT_SYMBOL, 59), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 59), + + # Node 70 + (3, HUFFMAN_EMIT_SYMBOL, 38), + (6, HUFFMAN_EMIT_SYMBOL, 38), + (10, HUFFMAN_EMIT_SYMBOL, 38), + (15, HUFFMAN_EMIT_SYMBOL, 38), + (24, HUFFMAN_EMIT_SYMBOL, 38), + (31, HUFFMAN_EMIT_SYMBOL, 38), + (41, HUFFMAN_EMIT_SYMBOL, 38), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 38), + (3, HUFFMAN_EMIT_SYMBOL, 42), + (6, HUFFMAN_EMIT_SYMBOL, 42), + (10, HUFFMAN_EMIT_SYMBOL, 42), + (15, HUFFMAN_EMIT_SYMBOL, 42), + (24, HUFFMAN_EMIT_SYMBOL, 42), + (31, HUFFMAN_EMIT_SYMBOL, 42), + (41, HUFFMAN_EMIT_SYMBOL, 42), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 42), + + # Node 71 + (3, HUFFMAN_EMIT_SYMBOL, 44), + (6, HUFFMAN_EMIT_SYMBOL, 44), + (10, HUFFMAN_EMIT_SYMBOL, 44), + (15, HUFFMAN_EMIT_SYMBOL, 44), + (24, HUFFMAN_EMIT_SYMBOL, 44), + (31, HUFFMAN_EMIT_SYMBOL, 44), + (41, HUFFMAN_EMIT_SYMBOL, 44), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 44), + (3, HUFFMAN_EMIT_SYMBOL, 59), + (6, HUFFMAN_EMIT_SYMBOL, 59), + (10, HUFFMAN_EMIT_SYMBOL, 59), + (15, HUFFMAN_EMIT_SYMBOL, 59), + (24, HUFFMAN_EMIT_SYMBOL, 59), + (31, HUFFMAN_EMIT_SYMBOL, 59), + (41, HUFFMAN_EMIT_SYMBOL, 59), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 59), + + # Node 72 + (2, HUFFMAN_EMIT_SYMBOL, 88), + (9, HUFFMAN_EMIT_SYMBOL, 88), + (23, HUFFMAN_EMIT_SYMBOL, 88), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 88), + (2, HUFFMAN_EMIT_SYMBOL, 90), + (9, HUFFMAN_EMIT_SYMBOL, 90), + (23, HUFFMAN_EMIT_SYMBOL, 90), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 90), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 33), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 34), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 40), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 41), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 63), + (80, 0, 0), + (82, 0, 0), + (84, 0, 0), + + # Node 73 + (3, HUFFMAN_EMIT_SYMBOL, 88), + (6, HUFFMAN_EMIT_SYMBOL, 88), + (10, HUFFMAN_EMIT_SYMBOL, 88), + (15, HUFFMAN_EMIT_SYMBOL, 88), + (24, HUFFMAN_EMIT_SYMBOL, 88), + (31, HUFFMAN_EMIT_SYMBOL, 88), + (41, HUFFMAN_EMIT_SYMBOL, 88), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 88), + (3, HUFFMAN_EMIT_SYMBOL, 90), + (6, HUFFMAN_EMIT_SYMBOL, 90), + (10, HUFFMAN_EMIT_SYMBOL, 90), + (15, HUFFMAN_EMIT_SYMBOL, 90), + (24, HUFFMAN_EMIT_SYMBOL, 90), + (31, HUFFMAN_EMIT_SYMBOL, 90), + (41, HUFFMAN_EMIT_SYMBOL, 90), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 90), + + # Node 74 + (1, HUFFMAN_EMIT_SYMBOL, 33), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 33), + (1, HUFFMAN_EMIT_SYMBOL, 34), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 34), + (1, HUFFMAN_EMIT_SYMBOL, 40), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 40), + (1, HUFFMAN_EMIT_SYMBOL, 41), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 41), + (1, HUFFMAN_EMIT_SYMBOL, 63), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 63), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 39), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 43), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 124), + (83, 0, 0), + (85, 0, 0), + (88, 0, 0), + + # Node 75 + (2, HUFFMAN_EMIT_SYMBOL, 33), + (9, HUFFMAN_EMIT_SYMBOL, 33), + (23, HUFFMAN_EMIT_SYMBOL, 33), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 33), + (2, HUFFMAN_EMIT_SYMBOL, 34), + (9, HUFFMAN_EMIT_SYMBOL, 34), + (23, HUFFMAN_EMIT_SYMBOL, 34), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 34), + (2, HUFFMAN_EMIT_SYMBOL, 40), + (9, HUFFMAN_EMIT_SYMBOL, 40), + (23, HUFFMAN_EMIT_SYMBOL, 40), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 40), + (2, HUFFMAN_EMIT_SYMBOL, 41), + (9, HUFFMAN_EMIT_SYMBOL, 41), + (23, HUFFMAN_EMIT_SYMBOL, 41), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 41), + + # Node 76 + (3, HUFFMAN_EMIT_SYMBOL, 33), + (6, HUFFMAN_EMIT_SYMBOL, 33), + (10, HUFFMAN_EMIT_SYMBOL, 33), + (15, HUFFMAN_EMIT_SYMBOL, 33), + (24, HUFFMAN_EMIT_SYMBOL, 33), + (31, HUFFMAN_EMIT_SYMBOL, 33), + (41, HUFFMAN_EMIT_SYMBOL, 33), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 33), + (3, HUFFMAN_EMIT_SYMBOL, 34), + (6, HUFFMAN_EMIT_SYMBOL, 34), + (10, HUFFMAN_EMIT_SYMBOL, 34), + (15, HUFFMAN_EMIT_SYMBOL, 34), + (24, HUFFMAN_EMIT_SYMBOL, 34), + (31, HUFFMAN_EMIT_SYMBOL, 34), + (41, HUFFMAN_EMIT_SYMBOL, 34), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 34), + + # Node 77 + (3, HUFFMAN_EMIT_SYMBOL, 40), + (6, HUFFMAN_EMIT_SYMBOL, 40), + (10, HUFFMAN_EMIT_SYMBOL, 40), + (15, HUFFMAN_EMIT_SYMBOL, 40), + (24, HUFFMAN_EMIT_SYMBOL, 40), + (31, HUFFMAN_EMIT_SYMBOL, 40), + (41, HUFFMAN_EMIT_SYMBOL, 40), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 40), + (3, HUFFMAN_EMIT_SYMBOL, 41), + (6, HUFFMAN_EMIT_SYMBOL, 41), + (10, HUFFMAN_EMIT_SYMBOL, 41), + (15, HUFFMAN_EMIT_SYMBOL, 41), + (24, HUFFMAN_EMIT_SYMBOL, 41), + (31, HUFFMAN_EMIT_SYMBOL, 41), + (41, HUFFMAN_EMIT_SYMBOL, 41), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 41), + + # Node 78 + (2, HUFFMAN_EMIT_SYMBOL, 63), + (9, HUFFMAN_EMIT_SYMBOL, 63), + (23, HUFFMAN_EMIT_SYMBOL, 63), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 63), + (1, HUFFMAN_EMIT_SYMBOL, 39), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 39), + (1, HUFFMAN_EMIT_SYMBOL, 43), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 43), + (1, HUFFMAN_EMIT_SYMBOL, 124), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 124), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 35), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 62), + (86, 0, 0), + (87, 0, 0), + (89, 0, 0), + (90, 0, 0), + + # Node 79 + (3, HUFFMAN_EMIT_SYMBOL, 63), + (6, HUFFMAN_EMIT_SYMBOL, 63), + (10, HUFFMAN_EMIT_SYMBOL, 63), + (15, HUFFMAN_EMIT_SYMBOL, 63), + (24, HUFFMAN_EMIT_SYMBOL, 63), + (31, HUFFMAN_EMIT_SYMBOL, 63), + (41, HUFFMAN_EMIT_SYMBOL, 63), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 63), + (2, HUFFMAN_EMIT_SYMBOL, 39), + (9, HUFFMAN_EMIT_SYMBOL, 39), + (23, HUFFMAN_EMIT_SYMBOL, 39), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 39), + (2, HUFFMAN_EMIT_SYMBOL, 43), + (9, HUFFMAN_EMIT_SYMBOL, 43), + (23, HUFFMAN_EMIT_SYMBOL, 43), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 43), + + # Node 80 + (3, HUFFMAN_EMIT_SYMBOL, 39), + (6, HUFFMAN_EMIT_SYMBOL, 39), + (10, HUFFMAN_EMIT_SYMBOL, 39), + (15, HUFFMAN_EMIT_SYMBOL, 39), + (24, HUFFMAN_EMIT_SYMBOL, 39), + (31, HUFFMAN_EMIT_SYMBOL, 39), + (41, HUFFMAN_EMIT_SYMBOL, 39), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 39), + (3, HUFFMAN_EMIT_SYMBOL, 43), + (6, HUFFMAN_EMIT_SYMBOL, 43), + (10, HUFFMAN_EMIT_SYMBOL, 43), + (15, HUFFMAN_EMIT_SYMBOL, 43), + (24, HUFFMAN_EMIT_SYMBOL, 43), + (31, HUFFMAN_EMIT_SYMBOL, 43), + (41, HUFFMAN_EMIT_SYMBOL, 43), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 43), + + # Node 81 + (2, HUFFMAN_EMIT_SYMBOL, 124), + (9, HUFFMAN_EMIT_SYMBOL, 124), + (23, HUFFMAN_EMIT_SYMBOL, 124), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 124), + (1, HUFFMAN_EMIT_SYMBOL, 35), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 35), + (1, HUFFMAN_EMIT_SYMBOL, 62), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 62), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 0), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 36), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 64), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 91), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 93), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 126), + (91, 0, 0), + (92, 0, 0), + + # Node 82 + (3, HUFFMAN_EMIT_SYMBOL, 124), + (6, HUFFMAN_EMIT_SYMBOL, 124), + (10, HUFFMAN_EMIT_SYMBOL, 124), + (15, HUFFMAN_EMIT_SYMBOL, 124), + (24, HUFFMAN_EMIT_SYMBOL, 124), + (31, HUFFMAN_EMIT_SYMBOL, 124), + (41, HUFFMAN_EMIT_SYMBOL, 124), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 124), + (2, HUFFMAN_EMIT_SYMBOL, 35), + (9, HUFFMAN_EMIT_SYMBOL, 35), + (23, HUFFMAN_EMIT_SYMBOL, 35), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 35), + (2, HUFFMAN_EMIT_SYMBOL, 62), + (9, HUFFMAN_EMIT_SYMBOL, 62), + (23, HUFFMAN_EMIT_SYMBOL, 62), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 62), + + # Node 83 + (3, HUFFMAN_EMIT_SYMBOL, 35), + (6, HUFFMAN_EMIT_SYMBOL, 35), + (10, HUFFMAN_EMIT_SYMBOL, 35), + (15, HUFFMAN_EMIT_SYMBOL, 35), + (24, HUFFMAN_EMIT_SYMBOL, 35), + (31, HUFFMAN_EMIT_SYMBOL, 35), + (41, HUFFMAN_EMIT_SYMBOL, 35), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 35), + (3, HUFFMAN_EMIT_SYMBOL, 62), + (6, HUFFMAN_EMIT_SYMBOL, 62), + (10, HUFFMAN_EMIT_SYMBOL, 62), + (15, HUFFMAN_EMIT_SYMBOL, 62), + (24, HUFFMAN_EMIT_SYMBOL, 62), + (31, HUFFMAN_EMIT_SYMBOL, 62), + (41, HUFFMAN_EMIT_SYMBOL, 62), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 62), + + # Node 84 + (1, HUFFMAN_EMIT_SYMBOL, 0), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 0), + (1, HUFFMAN_EMIT_SYMBOL, 36), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 36), + (1, HUFFMAN_EMIT_SYMBOL, 64), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 64), + (1, HUFFMAN_EMIT_SYMBOL, 91), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 91), + (1, HUFFMAN_EMIT_SYMBOL, 93), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 93), + (1, HUFFMAN_EMIT_SYMBOL, 126), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 126), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 94), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 125), + (93, 0, 0), + (94, 0, 0), + + # Node 85 + (2, HUFFMAN_EMIT_SYMBOL, 0), + (9, HUFFMAN_EMIT_SYMBOL, 0), + (23, HUFFMAN_EMIT_SYMBOL, 0), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 0), + (2, HUFFMAN_EMIT_SYMBOL, 36), + (9, HUFFMAN_EMIT_SYMBOL, 36), + (23, HUFFMAN_EMIT_SYMBOL, 36), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 36), + (2, HUFFMAN_EMIT_SYMBOL, 64), + (9, HUFFMAN_EMIT_SYMBOL, 64), + (23, HUFFMAN_EMIT_SYMBOL, 64), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 64), + (2, HUFFMAN_EMIT_SYMBOL, 91), + (9, HUFFMAN_EMIT_SYMBOL, 91), + (23, HUFFMAN_EMIT_SYMBOL, 91), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 91), + + # Node 86 + (3, HUFFMAN_EMIT_SYMBOL, 0), + (6, HUFFMAN_EMIT_SYMBOL, 0), + (10, HUFFMAN_EMIT_SYMBOL, 0), + (15, HUFFMAN_EMIT_SYMBOL, 0), + (24, HUFFMAN_EMIT_SYMBOL, 0), + (31, HUFFMAN_EMIT_SYMBOL, 0), + (41, HUFFMAN_EMIT_SYMBOL, 0), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 0), + (3, HUFFMAN_EMIT_SYMBOL, 36), + (6, HUFFMAN_EMIT_SYMBOL, 36), + (10, HUFFMAN_EMIT_SYMBOL, 36), + (15, HUFFMAN_EMIT_SYMBOL, 36), + (24, HUFFMAN_EMIT_SYMBOL, 36), + (31, HUFFMAN_EMIT_SYMBOL, 36), + (41, HUFFMAN_EMIT_SYMBOL, 36), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 36), + + # Node 87 + (3, HUFFMAN_EMIT_SYMBOL, 64), + (6, HUFFMAN_EMIT_SYMBOL, 64), + (10, HUFFMAN_EMIT_SYMBOL, 64), + (15, HUFFMAN_EMIT_SYMBOL, 64), + (24, HUFFMAN_EMIT_SYMBOL, 64), + (31, HUFFMAN_EMIT_SYMBOL, 64), + (41, HUFFMAN_EMIT_SYMBOL, 64), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 64), + (3, HUFFMAN_EMIT_SYMBOL, 91), + (6, HUFFMAN_EMIT_SYMBOL, 91), + (10, HUFFMAN_EMIT_SYMBOL, 91), + (15, HUFFMAN_EMIT_SYMBOL, 91), + (24, HUFFMAN_EMIT_SYMBOL, 91), + (31, HUFFMAN_EMIT_SYMBOL, 91), + (41, HUFFMAN_EMIT_SYMBOL, 91), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 91), + + # Node 88 + (2, HUFFMAN_EMIT_SYMBOL, 93), + (9, HUFFMAN_EMIT_SYMBOL, 93), + (23, HUFFMAN_EMIT_SYMBOL, 93), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 93), + (2, HUFFMAN_EMIT_SYMBOL, 126), + (9, HUFFMAN_EMIT_SYMBOL, 126), + (23, HUFFMAN_EMIT_SYMBOL, 126), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 126), + (1, HUFFMAN_EMIT_SYMBOL, 94), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 94), + (1, HUFFMAN_EMIT_SYMBOL, 125), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 125), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 60), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 96), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 123), + (95, 0, 0), + + # Node 89 + (3, HUFFMAN_EMIT_SYMBOL, 93), + (6, HUFFMAN_EMIT_SYMBOL, 93), + (10, HUFFMAN_EMIT_SYMBOL, 93), + (15, HUFFMAN_EMIT_SYMBOL, 93), + (24, HUFFMAN_EMIT_SYMBOL, 93), + (31, HUFFMAN_EMIT_SYMBOL, 93), + (41, HUFFMAN_EMIT_SYMBOL, 93), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 93), + (3, HUFFMAN_EMIT_SYMBOL, 126), + (6, HUFFMAN_EMIT_SYMBOL, 126), + (10, HUFFMAN_EMIT_SYMBOL, 126), + (15, HUFFMAN_EMIT_SYMBOL, 126), + (24, HUFFMAN_EMIT_SYMBOL, 126), + (31, HUFFMAN_EMIT_SYMBOL, 126), + (41, HUFFMAN_EMIT_SYMBOL, 126), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 126), + + # Node 90 + (2, HUFFMAN_EMIT_SYMBOL, 94), + (9, HUFFMAN_EMIT_SYMBOL, 94), + (23, HUFFMAN_EMIT_SYMBOL, 94), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 94), + (2, HUFFMAN_EMIT_SYMBOL, 125), + (9, HUFFMAN_EMIT_SYMBOL, 125), + (23, HUFFMAN_EMIT_SYMBOL, 125), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 125), + (1, HUFFMAN_EMIT_SYMBOL, 60), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 60), + (1, HUFFMAN_EMIT_SYMBOL, 96), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 96), + (1, HUFFMAN_EMIT_SYMBOL, 123), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 123), + (96, 0, 0), + (110, 0, 0), + + # Node 91 + (3, HUFFMAN_EMIT_SYMBOL, 94), + (6, HUFFMAN_EMIT_SYMBOL, 94), + (10, HUFFMAN_EMIT_SYMBOL, 94), + (15, HUFFMAN_EMIT_SYMBOL, 94), + (24, HUFFMAN_EMIT_SYMBOL, 94), + (31, HUFFMAN_EMIT_SYMBOL, 94), + (41, HUFFMAN_EMIT_SYMBOL, 94), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 94), + (3, HUFFMAN_EMIT_SYMBOL, 125), + (6, HUFFMAN_EMIT_SYMBOL, 125), + (10, HUFFMAN_EMIT_SYMBOL, 125), + (15, HUFFMAN_EMIT_SYMBOL, 125), + (24, HUFFMAN_EMIT_SYMBOL, 125), + (31, HUFFMAN_EMIT_SYMBOL, 125), + (41, HUFFMAN_EMIT_SYMBOL, 125), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 125), + + # Node 92 + (2, HUFFMAN_EMIT_SYMBOL, 60), + (9, HUFFMAN_EMIT_SYMBOL, 60), + (23, HUFFMAN_EMIT_SYMBOL, 60), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 60), + (2, HUFFMAN_EMIT_SYMBOL, 96), + (9, HUFFMAN_EMIT_SYMBOL, 96), + (23, HUFFMAN_EMIT_SYMBOL, 96), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 96), + (2, HUFFMAN_EMIT_SYMBOL, 123), + (9, HUFFMAN_EMIT_SYMBOL, 123), + (23, HUFFMAN_EMIT_SYMBOL, 123), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 123), + (97, 0, 0), + (101, 0, 0), + (111, 0, 0), + (133, 0, 0), + + # Node 93 + (3, HUFFMAN_EMIT_SYMBOL, 60), + (6, HUFFMAN_EMIT_SYMBOL, 60), + (10, HUFFMAN_EMIT_SYMBOL, 60), + (15, HUFFMAN_EMIT_SYMBOL, 60), + (24, HUFFMAN_EMIT_SYMBOL, 60), + (31, HUFFMAN_EMIT_SYMBOL, 60), + (41, HUFFMAN_EMIT_SYMBOL, 60), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 60), + (3, HUFFMAN_EMIT_SYMBOL, 96), + (6, HUFFMAN_EMIT_SYMBOL, 96), + (10, HUFFMAN_EMIT_SYMBOL, 96), + (15, HUFFMAN_EMIT_SYMBOL, 96), + (24, HUFFMAN_EMIT_SYMBOL, 96), + (31, HUFFMAN_EMIT_SYMBOL, 96), + (41, HUFFMAN_EMIT_SYMBOL, 96), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 96), + + # Node 94 + (3, HUFFMAN_EMIT_SYMBOL, 123), + (6, HUFFMAN_EMIT_SYMBOL, 123), + (10, HUFFMAN_EMIT_SYMBOL, 123), + (15, HUFFMAN_EMIT_SYMBOL, 123), + (24, HUFFMAN_EMIT_SYMBOL, 123), + (31, HUFFMAN_EMIT_SYMBOL, 123), + (41, HUFFMAN_EMIT_SYMBOL, 123), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 123), + (98, 0, 0), + (99, 0, 0), + (102, 0, 0), + (105, 0, 0), + (112, 0, 0), + (119, 0, 0), + (134, 0, 0), + (153, 0, 0), + + # Node 95 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 92), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 195), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 208), + (100, 0, 0), + (103, 0, 0), + (104, 0, 0), + (106, 0, 0), + (107, 0, 0), + (113, 0, 0), + (116, 0, 0), + (120, 0, 0), + (126, 0, 0), + (135, 0, 0), + (142, 0, 0), + (154, 0, 0), + (169, 0, 0), + + # Node 96 + (1, HUFFMAN_EMIT_SYMBOL, 92), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 92), + (1, HUFFMAN_EMIT_SYMBOL, 195), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 195), + (1, HUFFMAN_EMIT_SYMBOL, 208), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 208), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 128), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 130), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 131), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 162), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 184), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 194), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 224), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 226), + (108, 0, 0), + (109, 0, 0), + + # Node 97 + (2, HUFFMAN_EMIT_SYMBOL, 92), + (9, HUFFMAN_EMIT_SYMBOL, 92), + (23, HUFFMAN_EMIT_SYMBOL, 92), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 92), + (2, HUFFMAN_EMIT_SYMBOL, 195), + (9, HUFFMAN_EMIT_SYMBOL, 195), + (23, HUFFMAN_EMIT_SYMBOL, 195), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 195), + (2, HUFFMAN_EMIT_SYMBOL, 208), + (9, HUFFMAN_EMIT_SYMBOL, 208), + (23, HUFFMAN_EMIT_SYMBOL, 208), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 208), + (1, HUFFMAN_EMIT_SYMBOL, 128), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 128), + (1, HUFFMAN_EMIT_SYMBOL, 130), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 130), + + # Node 98 + (3, HUFFMAN_EMIT_SYMBOL, 92), + (6, HUFFMAN_EMIT_SYMBOL, 92), + (10, HUFFMAN_EMIT_SYMBOL, 92), + (15, HUFFMAN_EMIT_SYMBOL, 92), + (24, HUFFMAN_EMIT_SYMBOL, 92), + (31, HUFFMAN_EMIT_SYMBOL, 92), + (41, HUFFMAN_EMIT_SYMBOL, 92), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 92), + (3, HUFFMAN_EMIT_SYMBOL, 195), + (6, HUFFMAN_EMIT_SYMBOL, 195), + (10, HUFFMAN_EMIT_SYMBOL, 195), + (15, HUFFMAN_EMIT_SYMBOL, 195), + (24, HUFFMAN_EMIT_SYMBOL, 195), + (31, HUFFMAN_EMIT_SYMBOL, 195), + (41, HUFFMAN_EMIT_SYMBOL, 195), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 195), + + # Node 99 + (3, HUFFMAN_EMIT_SYMBOL, 208), + (6, HUFFMAN_EMIT_SYMBOL, 208), + (10, HUFFMAN_EMIT_SYMBOL, 208), + (15, HUFFMAN_EMIT_SYMBOL, 208), + (24, HUFFMAN_EMIT_SYMBOL, 208), + (31, HUFFMAN_EMIT_SYMBOL, 208), + (41, HUFFMAN_EMIT_SYMBOL, 208), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 208), + (2, HUFFMAN_EMIT_SYMBOL, 128), + (9, HUFFMAN_EMIT_SYMBOL, 128), + (23, HUFFMAN_EMIT_SYMBOL, 128), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 128), + (2, HUFFMAN_EMIT_SYMBOL, 130), + (9, HUFFMAN_EMIT_SYMBOL, 130), + (23, HUFFMAN_EMIT_SYMBOL, 130), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 130), + + # Node 100 + (3, HUFFMAN_EMIT_SYMBOL, 128), + (6, HUFFMAN_EMIT_SYMBOL, 128), + (10, HUFFMAN_EMIT_SYMBOL, 128), + (15, HUFFMAN_EMIT_SYMBOL, 128), + (24, HUFFMAN_EMIT_SYMBOL, 128), + (31, HUFFMAN_EMIT_SYMBOL, 128), + (41, HUFFMAN_EMIT_SYMBOL, 128), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 128), + (3, HUFFMAN_EMIT_SYMBOL, 130), + (6, HUFFMAN_EMIT_SYMBOL, 130), + (10, HUFFMAN_EMIT_SYMBOL, 130), + (15, HUFFMAN_EMIT_SYMBOL, 130), + (24, HUFFMAN_EMIT_SYMBOL, 130), + (31, HUFFMAN_EMIT_SYMBOL, 130), + (41, HUFFMAN_EMIT_SYMBOL, 130), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 130), + + # Node 101 + (1, HUFFMAN_EMIT_SYMBOL, 131), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 131), + (1, HUFFMAN_EMIT_SYMBOL, 162), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 162), + (1, HUFFMAN_EMIT_SYMBOL, 184), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 184), + (1, HUFFMAN_EMIT_SYMBOL, 194), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 194), + (1, HUFFMAN_EMIT_SYMBOL, 224), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 224), + (1, HUFFMAN_EMIT_SYMBOL, 226), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 226), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 153), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 161), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 167), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 172), + + # Node 102 + (2, HUFFMAN_EMIT_SYMBOL, 131), + (9, HUFFMAN_EMIT_SYMBOL, 131), + (23, HUFFMAN_EMIT_SYMBOL, 131), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 131), + (2, HUFFMAN_EMIT_SYMBOL, 162), + (9, HUFFMAN_EMIT_SYMBOL, 162), + (23, HUFFMAN_EMIT_SYMBOL, 162), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 162), + (2, HUFFMAN_EMIT_SYMBOL, 184), + (9, HUFFMAN_EMIT_SYMBOL, 184), + (23, HUFFMAN_EMIT_SYMBOL, 184), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 184), + (2, HUFFMAN_EMIT_SYMBOL, 194), + (9, HUFFMAN_EMIT_SYMBOL, 194), + (23, HUFFMAN_EMIT_SYMBOL, 194), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 194), + + # Node 103 + (3, HUFFMAN_EMIT_SYMBOL, 131), + (6, HUFFMAN_EMIT_SYMBOL, 131), + (10, HUFFMAN_EMIT_SYMBOL, 131), + (15, HUFFMAN_EMIT_SYMBOL, 131), + (24, HUFFMAN_EMIT_SYMBOL, 131), + (31, HUFFMAN_EMIT_SYMBOL, 131), + (41, HUFFMAN_EMIT_SYMBOL, 131), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 131), + (3, HUFFMAN_EMIT_SYMBOL, 162), + (6, HUFFMAN_EMIT_SYMBOL, 162), + (10, HUFFMAN_EMIT_SYMBOL, 162), + (15, HUFFMAN_EMIT_SYMBOL, 162), + (24, HUFFMAN_EMIT_SYMBOL, 162), + (31, HUFFMAN_EMIT_SYMBOL, 162), + (41, HUFFMAN_EMIT_SYMBOL, 162), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 162), + + # Node 104 + (3, HUFFMAN_EMIT_SYMBOL, 184), + (6, HUFFMAN_EMIT_SYMBOL, 184), + (10, HUFFMAN_EMIT_SYMBOL, 184), + (15, HUFFMAN_EMIT_SYMBOL, 184), + (24, HUFFMAN_EMIT_SYMBOL, 184), + (31, HUFFMAN_EMIT_SYMBOL, 184), + (41, HUFFMAN_EMIT_SYMBOL, 184), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 184), + (3, HUFFMAN_EMIT_SYMBOL, 194), + (6, HUFFMAN_EMIT_SYMBOL, 194), + (10, HUFFMAN_EMIT_SYMBOL, 194), + (15, HUFFMAN_EMIT_SYMBOL, 194), + (24, HUFFMAN_EMIT_SYMBOL, 194), + (31, HUFFMAN_EMIT_SYMBOL, 194), + (41, HUFFMAN_EMIT_SYMBOL, 194), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 194), + + # Node 105 + (2, HUFFMAN_EMIT_SYMBOL, 224), + (9, HUFFMAN_EMIT_SYMBOL, 224), + (23, HUFFMAN_EMIT_SYMBOL, 224), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 224), + (2, HUFFMAN_EMIT_SYMBOL, 226), + (9, HUFFMAN_EMIT_SYMBOL, 226), + (23, HUFFMAN_EMIT_SYMBOL, 226), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 226), + (1, HUFFMAN_EMIT_SYMBOL, 153), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 153), + (1, HUFFMAN_EMIT_SYMBOL, 161), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 161), + (1, HUFFMAN_EMIT_SYMBOL, 167), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 167), + (1, HUFFMAN_EMIT_SYMBOL, 172), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 172), + + # Node 106 + (3, HUFFMAN_EMIT_SYMBOL, 224), + (6, HUFFMAN_EMIT_SYMBOL, 224), + (10, HUFFMAN_EMIT_SYMBOL, 224), + (15, HUFFMAN_EMIT_SYMBOL, 224), + (24, HUFFMAN_EMIT_SYMBOL, 224), + (31, HUFFMAN_EMIT_SYMBOL, 224), + (41, HUFFMAN_EMIT_SYMBOL, 224), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 224), + (3, HUFFMAN_EMIT_SYMBOL, 226), + (6, HUFFMAN_EMIT_SYMBOL, 226), + (10, HUFFMAN_EMIT_SYMBOL, 226), + (15, HUFFMAN_EMIT_SYMBOL, 226), + (24, HUFFMAN_EMIT_SYMBOL, 226), + (31, HUFFMAN_EMIT_SYMBOL, 226), + (41, HUFFMAN_EMIT_SYMBOL, 226), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 226), + + # Node 107 + (2, HUFFMAN_EMIT_SYMBOL, 153), + (9, HUFFMAN_EMIT_SYMBOL, 153), + (23, HUFFMAN_EMIT_SYMBOL, 153), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 153), + (2, HUFFMAN_EMIT_SYMBOL, 161), + (9, HUFFMAN_EMIT_SYMBOL, 161), + (23, HUFFMAN_EMIT_SYMBOL, 161), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 161), + (2, HUFFMAN_EMIT_SYMBOL, 167), + (9, HUFFMAN_EMIT_SYMBOL, 167), + (23, HUFFMAN_EMIT_SYMBOL, 167), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 167), + (2, HUFFMAN_EMIT_SYMBOL, 172), + (9, HUFFMAN_EMIT_SYMBOL, 172), + (23, HUFFMAN_EMIT_SYMBOL, 172), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 172), + + # Node 108 + (3, HUFFMAN_EMIT_SYMBOL, 153), + (6, HUFFMAN_EMIT_SYMBOL, 153), + (10, HUFFMAN_EMIT_SYMBOL, 153), + (15, HUFFMAN_EMIT_SYMBOL, 153), + (24, HUFFMAN_EMIT_SYMBOL, 153), + (31, HUFFMAN_EMIT_SYMBOL, 153), + (41, HUFFMAN_EMIT_SYMBOL, 153), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 153), + (3, HUFFMAN_EMIT_SYMBOL, 161), + (6, HUFFMAN_EMIT_SYMBOL, 161), + (10, HUFFMAN_EMIT_SYMBOL, 161), + (15, HUFFMAN_EMIT_SYMBOL, 161), + (24, HUFFMAN_EMIT_SYMBOL, 161), + (31, HUFFMAN_EMIT_SYMBOL, 161), + (41, HUFFMAN_EMIT_SYMBOL, 161), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 161), + + # Node 109 + (3, HUFFMAN_EMIT_SYMBOL, 167), + (6, HUFFMAN_EMIT_SYMBOL, 167), + (10, HUFFMAN_EMIT_SYMBOL, 167), + (15, HUFFMAN_EMIT_SYMBOL, 167), + (24, HUFFMAN_EMIT_SYMBOL, 167), + (31, HUFFMAN_EMIT_SYMBOL, 167), + (41, HUFFMAN_EMIT_SYMBOL, 167), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 167), + (3, HUFFMAN_EMIT_SYMBOL, 172), + (6, HUFFMAN_EMIT_SYMBOL, 172), + (10, HUFFMAN_EMIT_SYMBOL, 172), + (15, HUFFMAN_EMIT_SYMBOL, 172), + (24, HUFFMAN_EMIT_SYMBOL, 172), + (31, HUFFMAN_EMIT_SYMBOL, 172), + (41, HUFFMAN_EMIT_SYMBOL, 172), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 172), + + # Node 110 + (114, 0, 0), + (115, 0, 0), + (117, 0, 0), + (118, 0, 0), + (121, 0, 0), + (123, 0, 0), + (127, 0, 0), + (130, 0, 0), + (136, 0, 0), + (139, 0, 0), + (143, 0, 0), + (146, 0, 0), + (155, 0, 0), + (162, 0, 0), + (170, 0, 0), + (180, 0, 0), + + # Node 111 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 176), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 177), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 179), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 209), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 216), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 217), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 227), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 229), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 230), + (122, 0, 0), + (124, 0, 0), + (125, 0, 0), + (128, 0, 0), + (129, 0, 0), + (131, 0, 0), + (132, 0, 0), + + # Node 112 + (1, HUFFMAN_EMIT_SYMBOL, 176), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 176), + (1, HUFFMAN_EMIT_SYMBOL, 177), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 177), + (1, HUFFMAN_EMIT_SYMBOL, 179), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 179), + (1, HUFFMAN_EMIT_SYMBOL, 209), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 209), + (1, HUFFMAN_EMIT_SYMBOL, 216), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 216), + (1, HUFFMAN_EMIT_SYMBOL, 217), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 217), + (1, HUFFMAN_EMIT_SYMBOL, 227), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 227), + (1, HUFFMAN_EMIT_SYMBOL, 229), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 229), + + # Node 113 + (2, HUFFMAN_EMIT_SYMBOL, 176), + (9, HUFFMAN_EMIT_SYMBOL, 176), + (23, HUFFMAN_EMIT_SYMBOL, 176), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 176), + (2, HUFFMAN_EMIT_SYMBOL, 177), + (9, HUFFMAN_EMIT_SYMBOL, 177), + (23, HUFFMAN_EMIT_SYMBOL, 177), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 177), + (2, HUFFMAN_EMIT_SYMBOL, 179), + (9, HUFFMAN_EMIT_SYMBOL, 179), + (23, HUFFMAN_EMIT_SYMBOL, 179), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 179), + (2, HUFFMAN_EMIT_SYMBOL, 209), + (9, HUFFMAN_EMIT_SYMBOL, 209), + (23, HUFFMAN_EMIT_SYMBOL, 209), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 209), + + # Node 114 + (3, HUFFMAN_EMIT_SYMBOL, 176), + (6, HUFFMAN_EMIT_SYMBOL, 176), + (10, HUFFMAN_EMIT_SYMBOL, 176), + (15, HUFFMAN_EMIT_SYMBOL, 176), + (24, HUFFMAN_EMIT_SYMBOL, 176), + (31, HUFFMAN_EMIT_SYMBOL, 176), + (41, HUFFMAN_EMIT_SYMBOL, 176), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 176), + (3, HUFFMAN_EMIT_SYMBOL, 177), + (6, HUFFMAN_EMIT_SYMBOL, 177), + (10, HUFFMAN_EMIT_SYMBOL, 177), + (15, HUFFMAN_EMIT_SYMBOL, 177), + (24, HUFFMAN_EMIT_SYMBOL, 177), + (31, HUFFMAN_EMIT_SYMBOL, 177), + (41, HUFFMAN_EMIT_SYMBOL, 177), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 177), + + # Node 115 + (3, HUFFMAN_EMIT_SYMBOL, 179), + (6, HUFFMAN_EMIT_SYMBOL, 179), + (10, HUFFMAN_EMIT_SYMBOL, 179), + (15, HUFFMAN_EMIT_SYMBOL, 179), + (24, HUFFMAN_EMIT_SYMBOL, 179), + (31, HUFFMAN_EMIT_SYMBOL, 179), + (41, HUFFMAN_EMIT_SYMBOL, 179), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 179), + (3, HUFFMAN_EMIT_SYMBOL, 209), + (6, HUFFMAN_EMIT_SYMBOL, 209), + (10, HUFFMAN_EMIT_SYMBOL, 209), + (15, HUFFMAN_EMIT_SYMBOL, 209), + (24, HUFFMAN_EMIT_SYMBOL, 209), + (31, HUFFMAN_EMIT_SYMBOL, 209), + (41, HUFFMAN_EMIT_SYMBOL, 209), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 209), + + # Node 116 + (2, HUFFMAN_EMIT_SYMBOL, 216), + (9, HUFFMAN_EMIT_SYMBOL, 216), + (23, HUFFMAN_EMIT_SYMBOL, 216), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 216), + (2, HUFFMAN_EMIT_SYMBOL, 217), + (9, HUFFMAN_EMIT_SYMBOL, 217), + (23, HUFFMAN_EMIT_SYMBOL, 217), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 217), + (2, HUFFMAN_EMIT_SYMBOL, 227), + (9, HUFFMAN_EMIT_SYMBOL, 227), + (23, HUFFMAN_EMIT_SYMBOL, 227), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 227), + (2, HUFFMAN_EMIT_SYMBOL, 229), + (9, HUFFMAN_EMIT_SYMBOL, 229), + (23, HUFFMAN_EMIT_SYMBOL, 229), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 229), + + # Node 117 + (3, HUFFMAN_EMIT_SYMBOL, 216), + (6, HUFFMAN_EMIT_SYMBOL, 216), + (10, HUFFMAN_EMIT_SYMBOL, 216), + (15, HUFFMAN_EMIT_SYMBOL, 216), + (24, HUFFMAN_EMIT_SYMBOL, 216), + (31, HUFFMAN_EMIT_SYMBOL, 216), + (41, HUFFMAN_EMIT_SYMBOL, 216), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 216), + (3, HUFFMAN_EMIT_SYMBOL, 217), + (6, HUFFMAN_EMIT_SYMBOL, 217), + (10, HUFFMAN_EMIT_SYMBOL, 217), + (15, HUFFMAN_EMIT_SYMBOL, 217), + (24, HUFFMAN_EMIT_SYMBOL, 217), + (31, HUFFMAN_EMIT_SYMBOL, 217), + (41, HUFFMAN_EMIT_SYMBOL, 217), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 217), + + # Node 118 + (3, HUFFMAN_EMIT_SYMBOL, 227), + (6, HUFFMAN_EMIT_SYMBOL, 227), + (10, HUFFMAN_EMIT_SYMBOL, 227), + (15, HUFFMAN_EMIT_SYMBOL, 227), + (24, HUFFMAN_EMIT_SYMBOL, 227), + (31, HUFFMAN_EMIT_SYMBOL, 227), + (41, HUFFMAN_EMIT_SYMBOL, 227), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 227), + (3, HUFFMAN_EMIT_SYMBOL, 229), + (6, HUFFMAN_EMIT_SYMBOL, 229), + (10, HUFFMAN_EMIT_SYMBOL, 229), + (15, HUFFMAN_EMIT_SYMBOL, 229), + (24, HUFFMAN_EMIT_SYMBOL, 229), + (31, HUFFMAN_EMIT_SYMBOL, 229), + (41, HUFFMAN_EMIT_SYMBOL, 229), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 229), + + # Node 119 + (1, HUFFMAN_EMIT_SYMBOL, 230), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 230), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 129), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 132), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 133), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 134), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 136), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 146), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 154), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 156), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 160), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 163), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 164), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 169), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 170), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 173), + + # Node 120 + (2, HUFFMAN_EMIT_SYMBOL, 230), + (9, HUFFMAN_EMIT_SYMBOL, 230), + (23, HUFFMAN_EMIT_SYMBOL, 230), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 230), + (1, HUFFMAN_EMIT_SYMBOL, 129), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 129), + (1, HUFFMAN_EMIT_SYMBOL, 132), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 132), + (1, HUFFMAN_EMIT_SYMBOL, 133), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 133), + (1, HUFFMAN_EMIT_SYMBOL, 134), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 134), + (1, HUFFMAN_EMIT_SYMBOL, 136), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 136), + (1, HUFFMAN_EMIT_SYMBOL, 146), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 146), + + # Node 121 + (3, HUFFMAN_EMIT_SYMBOL, 230), + (6, HUFFMAN_EMIT_SYMBOL, 230), + (10, HUFFMAN_EMIT_SYMBOL, 230), + (15, HUFFMAN_EMIT_SYMBOL, 230), + (24, HUFFMAN_EMIT_SYMBOL, 230), + (31, HUFFMAN_EMIT_SYMBOL, 230), + (41, HUFFMAN_EMIT_SYMBOL, 230), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 230), + (2, HUFFMAN_EMIT_SYMBOL, 129), + (9, HUFFMAN_EMIT_SYMBOL, 129), + (23, HUFFMAN_EMIT_SYMBOL, 129), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 129), + (2, HUFFMAN_EMIT_SYMBOL, 132), + (9, HUFFMAN_EMIT_SYMBOL, 132), + (23, HUFFMAN_EMIT_SYMBOL, 132), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 132), + + # Node 122 + (3, HUFFMAN_EMIT_SYMBOL, 129), + (6, HUFFMAN_EMIT_SYMBOL, 129), + (10, HUFFMAN_EMIT_SYMBOL, 129), + (15, HUFFMAN_EMIT_SYMBOL, 129), + (24, HUFFMAN_EMIT_SYMBOL, 129), + (31, HUFFMAN_EMIT_SYMBOL, 129), + (41, HUFFMAN_EMIT_SYMBOL, 129), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 129), + (3, HUFFMAN_EMIT_SYMBOL, 132), + (6, HUFFMAN_EMIT_SYMBOL, 132), + (10, HUFFMAN_EMIT_SYMBOL, 132), + (15, HUFFMAN_EMIT_SYMBOL, 132), + (24, HUFFMAN_EMIT_SYMBOL, 132), + (31, HUFFMAN_EMIT_SYMBOL, 132), + (41, HUFFMAN_EMIT_SYMBOL, 132), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 132), + + # Node 123 + (2, HUFFMAN_EMIT_SYMBOL, 133), + (9, HUFFMAN_EMIT_SYMBOL, 133), + (23, HUFFMAN_EMIT_SYMBOL, 133), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 133), + (2, HUFFMAN_EMIT_SYMBOL, 134), + (9, HUFFMAN_EMIT_SYMBOL, 134), + (23, HUFFMAN_EMIT_SYMBOL, 134), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 134), + (2, HUFFMAN_EMIT_SYMBOL, 136), + (9, HUFFMAN_EMIT_SYMBOL, 136), + (23, HUFFMAN_EMIT_SYMBOL, 136), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 136), + (2, HUFFMAN_EMIT_SYMBOL, 146), + (9, HUFFMAN_EMIT_SYMBOL, 146), + (23, HUFFMAN_EMIT_SYMBOL, 146), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 146), + + # Node 124 + (3, HUFFMAN_EMIT_SYMBOL, 133), + (6, HUFFMAN_EMIT_SYMBOL, 133), + (10, HUFFMAN_EMIT_SYMBOL, 133), + (15, HUFFMAN_EMIT_SYMBOL, 133), + (24, HUFFMAN_EMIT_SYMBOL, 133), + (31, HUFFMAN_EMIT_SYMBOL, 133), + (41, HUFFMAN_EMIT_SYMBOL, 133), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 133), + (3, HUFFMAN_EMIT_SYMBOL, 134), + (6, HUFFMAN_EMIT_SYMBOL, 134), + (10, HUFFMAN_EMIT_SYMBOL, 134), + (15, HUFFMAN_EMIT_SYMBOL, 134), + (24, HUFFMAN_EMIT_SYMBOL, 134), + (31, HUFFMAN_EMIT_SYMBOL, 134), + (41, HUFFMAN_EMIT_SYMBOL, 134), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 134), + + # Node 125 + (3, HUFFMAN_EMIT_SYMBOL, 136), + (6, HUFFMAN_EMIT_SYMBOL, 136), + (10, HUFFMAN_EMIT_SYMBOL, 136), + (15, HUFFMAN_EMIT_SYMBOL, 136), + (24, HUFFMAN_EMIT_SYMBOL, 136), + (31, HUFFMAN_EMIT_SYMBOL, 136), + (41, HUFFMAN_EMIT_SYMBOL, 136), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 136), + (3, HUFFMAN_EMIT_SYMBOL, 146), + (6, HUFFMAN_EMIT_SYMBOL, 146), + (10, HUFFMAN_EMIT_SYMBOL, 146), + (15, HUFFMAN_EMIT_SYMBOL, 146), + (24, HUFFMAN_EMIT_SYMBOL, 146), + (31, HUFFMAN_EMIT_SYMBOL, 146), + (41, HUFFMAN_EMIT_SYMBOL, 146), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 146), + + # Node 126 + (1, HUFFMAN_EMIT_SYMBOL, 154), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 154), + (1, HUFFMAN_EMIT_SYMBOL, 156), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 156), + (1, HUFFMAN_EMIT_SYMBOL, 160), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 160), + (1, HUFFMAN_EMIT_SYMBOL, 163), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 163), + (1, HUFFMAN_EMIT_SYMBOL, 164), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 164), + (1, HUFFMAN_EMIT_SYMBOL, 169), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 169), + (1, HUFFMAN_EMIT_SYMBOL, 170), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 170), + (1, HUFFMAN_EMIT_SYMBOL, 173), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 173), + + # Node 127 + (2, HUFFMAN_EMIT_SYMBOL, 154), + (9, HUFFMAN_EMIT_SYMBOL, 154), + (23, HUFFMAN_EMIT_SYMBOL, 154), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 154), + (2, HUFFMAN_EMIT_SYMBOL, 156), + (9, HUFFMAN_EMIT_SYMBOL, 156), + (23, HUFFMAN_EMIT_SYMBOL, 156), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 156), + (2, HUFFMAN_EMIT_SYMBOL, 160), + (9, HUFFMAN_EMIT_SYMBOL, 160), + (23, HUFFMAN_EMIT_SYMBOL, 160), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 160), + (2, HUFFMAN_EMIT_SYMBOL, 163), + (9, HUFFMAN_EMIT_SYMBOL, 163), + (23, HUFFMAN_EMIT_SYMBOL, 163), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 163), + + # Node 128 + (3, HUFFMAN_EMIT_SYMBOL, 154), + (6, HUFFMAN_EMIT_SYMBOL, 154), + (10, HUFFMAN_EMIT_SYMBOL, 154), + (15, HUFFMAN_EMIT_SYMBOL, 154), + (24, HUFFMAN_EMIT_SYMBOL, 154), + (31, HUFFMAN_EMIT_SYMBOL, 154), + (41, HUFFMAN_EMIT_SYMBOL, 154), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 154), + (3, HUFFMAN_EMIT_SYMBOL, 156), + (6, HUFFMAN_EMIT_SYMBOL, 156), + (10, HUFFMAN_EMIT_SYMBOL, 156), + (15, HUFFMAN_EMIT_SYMBOL, 156), + (24, HUFFMAN_EMIT_SYMBOL, 156), + (31, HUFFMAN_EMIT_SYMBOL, 156), + (41, HUFFMAN_EMIT_SYMBOL, 156), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 156), + + # Node 129 + (3, HUFFMAN_EMIT_SYMBOL, 160), + (6, HUFFMAN_EMIT_SYMBOL, 160), + (10, HUFFMAN_EMIT_SYMBOL, 160), + (15, HUFFMAN_EMIT_SYMBOL, 160), + (24, HUFFMAN_EMIT_SYMBOL, 160), + (31, HUFFMAN_EMIT_SYMBOL, 160), + (41, HUFFMAN_EMIT_SYMBOL, 160), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 160), + (3, HUFFMAN_EMIT_SYMBOL, 163), + (6, HUFFMAN_EMIT_SYMBOL, 163), + (10, HUFFMAN_EMIT_SYMBOL, 163), + (15, HUFFMAN_EMIT_SYMBOL, 163), + (24, HUFFMAN_EMIT_SYMBOL, 163), + (31, HUFFMAN_EMIT_SYMBOL, 163), + (41, HUFFMAN_EMIT_SYMBOL, 163), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 163), + + # Node 130 + (2, HUFFMAN_EMIT_SYMBOL, 164), + (9, HUFFMAN_EMIT_SYMBOL, 164), + (23, HUFFMAN_EMIT_SYMBOL, 164), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 164), + (2, HUFFMAN_EMIT_SYMBOL, 169), + (9, HUFFMAN_EMIT_SYMBOL, 169), + (23, HUFFMAN_EMIT_SYMBOL, 169), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 169), + (2, HUFFMAN_EMIT_SYMBOL, 170), + (9, HUFFMAN_EMIT_SYMBOL, 170), + (23, HUFFMAN_EMIT_SYMBOL, 170), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 170), + (2, HUFFMAN_EMIT_SYMBOL, 173), + (9, HUFFMAN_EMIT_SYMBOL, 173), + (23, HUFFMAN_EMIT_SYMBOL, 173), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 173), + + # Node 131 + (3, HUFFMAN_EMIT_SYMBOL, 164), + (6, HUFFMAN_EMIT_SYMBOL, 164), + (10, HUFFMAN_EMIT_SYMBOL, 164), + (15, HUFFMAN_EMIT_SYMBOL, 164), + (24, HUFFMAN_EMIT_SYMBOL, 164), + (31, HUFFMAN_EMIT_SYMBOL, 164), + (41, HUFFMAN_EMIT_SYMBOL, 164), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 164), + (3, HUFFMAN_EMIT_SYMBOL, 169), + (6, HUFFMAN_EMIT_SYMBOL, 169), + (10, HUFFMAN_EMIT_SYMBOL, 169), + (15, HUFFMAN_EMIT_SYMBOL, 169), + (24, HUFFMAN_EMIT_SYMBOL, 169), + (31, HUFFMAN_EMIT_SYMBOL, 169), + (41, HUFFMAN_EMIT_SYMBOL, 169), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 169), + + # Node 132 + (3, HUFFMAN_EMIT_SYMBOL, 170), + (6, HUFFMAN_EMIT_SYMBOL, 170), + (10, HUFFMAN_EMIT_SYMBOL, 170), + (15, HUFFMAN_EMIT_SYMBOL, 170), + (24, HUFFMAN_EMIT_SYMBOL, 170), + (31, HUFFMAN_EMIT_SYMBOL, 170), + (41, HUFFMAN_EMIT_SYMBOL, 170), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 170), + (3, HUFFMAN_EMIT_SYMBOL, 173), + (6, HUFFMAN_EMIT_SYMBOL, 173), + (10, HUFFMAN_EMIT_SYMBOL, 173), + (15, HUFFMAN_EMIT_SYMBOL, 173), + (24, HUFFMAN_EMIT_SYMBOL, 173), + (31, HUFFMAN_EMIT_SYMBOL, 173), + (41, HUFFMAN_EMIT_SYMBOL, 173), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 173), + + # Node 133 + (137, 0, 0), + (138, 0, 0), + (140, 0, 0), + (141, 0, 0), + (144, 0, 0), + (145, 0, 0), + (147, 0, 0), + (150, 0, 0), + (156, 0, 0), + (159, 0, 0), + (163, 0, 0), + (166, 0, 0), + (171, 0, 0), + (174, 0, 0), + (181, 0, 0), + (190, 0, 0), + + # Node 134 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 178), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 181), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 185), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 186), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 187), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 189), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 190), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 196), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 198), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 228), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 232), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 233), + (148, 0, 0), + (149, 0, 0), + (151, 0, 0), + (152, 0, 0), + + # Node 135 + (1, HUFFMAN_EMIT_SYMBOL, 178), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 178), + (1, HUFFMAN_EMIT_SYMBOL, 181), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 181), + (1, HUFFMAN_EMIT_SYMBOL, 185), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 185), + (1, HUFFMAN_EMIT_SYMBOL, 186), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 186), + (1, HUFFMAN_EMIT_SYMBOL, 187), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 187), + (1, HUFFMAN_EMIT_SYMBOL, 189), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 189), + (1, HUFFMAN_EMIT_SYMBOL, 190), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 190), + (1, HUFFMAN_EMIT_SYMBOL, 196), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 196), + + # Node 136 + (2, HUFFMAN_EMIT_SYMBOL, 178), + (9, HUFFMAN_EMIT_SYMBOL, 178), + (23, HUFFMAN_EMIT_SYMBOL, 178), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 178), + (2, HUFFMAN_EMIT_SYMBOL, 181), + (9, HUFFMAN_EMIT_SYMBOL, 181), + (23, HUFFMAN_EMIT_SYMBOL, 181), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 181), + (2, HUFFMAN_EMIT_SYMBOL, 185), + (9, HUFFMAN_EMIT_SYMBOL, 185), + (23, HUFFMAN_EMIT_SYMBOL, 185), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 185), + (2, HUFFMAN_EMIT_SYMBOL, 186), + (9, HUFFMAN_EMIT_SYMBOL, 186), + (23, HUFFMAN_EMIT_SYMBOL, 186), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 186), + + # Node 137 + (3, HUFFMAN_EMIT_SYMBOL, 178), + (6, HUFFMAN_EMIT_SYMBOL, 178), + (10, HUFFMAN_EMIT_SYMBOL, 178), + (15, HUFFMAN_EMIT_SYMBOL, 178), + (24, HUFFMAN_EMIT_SYMBOL, 178), + (31, HUFFMAN_EMIT_SYMBOL, 178), + (41, HUFFMAN_EMIT_SYMBOL, 178), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 178), + (3, HUFFMAN_EMIT_SYMBOL, 181), + (6, HUFFMAN_EMIT_SYMBOL, 181), + (10, HUFFMAN_EMIT_SYMBOL, 181), + (15, HUFFMAN_EMIT_SYMBOL, 181), + (24, HUFFMAN_EMIT_SYMBOL, 181), + (31, HUFFMAN_EMIT_SYMBOL, 181), + (41, HUFFMAN_EMIT_SYMBOL, 181), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 181), + + # Node 138 + (3, HUFFMAN_EMIT_SYMBOL, 185), + (6, HUFFMAN_EMIT_SYMBOL, 185), + (10, HUFFMAN_EMIT_SYMBOL, 185), + (15, HUFFMAN_EMIT_SYMBOL, 185), + (24, HUFFMAN_EMIT_SYMBOL, 185), + (31, HUFFMAN_EMIT_SYMBOL, 185), + (41, HUFFMAN_EMIT_SYMBOL, 185), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 185), + (3, HUFFMAN_EMIT_SYMBOL, 186), + (6, HUFFMAN_EMIT_SYMBOL, 186), + (10, HUFFMAN_EMIT_SYMBOL, 186), + (15, HUFFMAN_EMIT_SYMBOL, 186), + (24, HUFFMAN_EMIT_SYMBOL, 186), + (31, HUFFMAN_EMIT_SYMBOL, 186), + (41, HUFFMAN_EMIT_SYMBOL, 186), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 186), + + # Node 139 + (2, HUFFMAN_EMIT_SYMBOL, 187), + (9, HUFFMAN_EMIT_SYMBOL, 187), + (23, HUFFMAN_EMIT_SYMBOL, 187), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 187), + (2, HUFFMAN_EMIT_SYMBOL, 189), + (9, HUFFMAN_EMIT_SYMBOL, 189), + (23, HUFFMAN_EMIT_SYMBOL, 189), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 189), + (2, HUFFMAN_EMIT_SYMBOL, 190), + (9, HUFFMAN_EMIT_SYMBOL, 190), + (23, HUFFMAN_EMIT_SYMBOL, 190), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 190), + (2, HUFFMAN_EMIT_SYMBOL, 196), + (9, HUFFMAN_EMIT_SYMBOL, 196), + (23, HUFFMAN_EMIT_SYMBOL, 196), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 196), + + # Node 140 + (3, HUFFMAN_EMIT_SYMBOL, 187), + (6, HUFFMAN_EMIT_SYMBOL, 187), + (10, HUFFMAN_EMIT_SYMBOL, 187), + (15, HUFFMAN_EMIT_SYMBOL, 187), + (24, HUFFMAN_EMIT_SYMBOL, 187), + (31, HUFFMAN_EMIT_SYMBOL, 187), + (41, HUFFMAN_EMIT_SYMBOL, 187), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 187), + (3, HUFFMAN_EMIT_SYMBOL, 189), + (6, HUFFMAN_EMIT_SYMBOL, 189), + (10, HUFFMAN_EMIT_SYMBOL, 189), + (15, HUFFMAN_EMIT_SYMBOL, 189), + (24, HUFFMAN_EMIT_SYMBOL, 189), + (31, HUFFMAN_EMIT_SYMBOL, 189), + (41, HUFFMAN_EMIT_SYMBOL, 189), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 189), + + # Node 141 + (3, HUFFMAN_EMIT_SYMBOL, 190), + (6, HUFFMAN_EMIT_SYMBOL, 190), + (10, HUFFMAN_EMIT_SYMBOL, 190), + (15, HUFFMAN_EMIT_SYMBOL, 190), + (24, HUFFMAN_EMIT_SYMBOL, 190), + (31, HUFFMAN_EMIT_SYMBOL, 190), + (41, HUFFMAN_EMIT_SYMBOL, 190), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 190), + (3, HUFFMAN_EMIT_SYMBOL, 196), + (6, HUFFMAN_EMIT_SYMBOL, 196), + (10, HUFFMAN_EMIT_SYMBOL, 196), + (15, HUFFMAN_EMIT_SYMBOL, 196), + (24, HUFFMAN_EMIT_SYMBOL, 196), + (31, HUFFMAN_EMIT_SYMBOL, 196), + (41, HUFFMAN_EMIT_SYMBOL, 196), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 196), + + # Node 142 + (1, HUFFMAN_EMIT_SYMBOL, 198), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 198), + (1, HUFFMAN_EMIT_SYMBOL, 228), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 228), + (1, HUFFMAN_EMIT_SYMBOL, 232), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 232), + (1, HUFFMAN_EMIT_SYMBOL, 233), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 233), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 1), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 135), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 137), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 138), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 139), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 140), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 141), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 143), + + # Node 143 + (2, HUFFMAN_EMIT_SYMBOL, 198), + (9, HUFFMAN_EMIT_SYMBOL, 198), + (23, HUFFMAN_EMIT_SYMBOL, 198), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 198), + (2, HUFFMAN_EMIT_SYMBOL, 228), + (9, HUFFMAN_EMIT_SYMBOL, 228), + (23, HUFFMAN_EMIT_SYMBOL, 228), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 228), + (2, HUFFMAN_EMIT_SYMBOL, 232), + (9, HUFFMAN_EMIT_SYMBOL, 232), + (23, HUFFMAN_EMIT_SYMBOL, 232), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 232), + (2, HUFFMAN_EMIT_SYMBOL, 233), + (9, HUFFMAN_EMIT_SYMBOL, 233), + (23, HUFFMAN_EMIT_SYMBOL, 233), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 233), + + # Node 144 + (3, HUFFMAN_EMIT_SYMBOL, 198), + (6, HUFFMAN_EMIT_SYMBOL, 198), + (10, HUFFMAN_EMIT_SYMBOL, 198), + (15, HUFFMAN_EMIT_SYMBOL, 198), + (24, HUFFMAN_EMIT_SYMBOL, 198), + (31, HUFFMAN_EMIT_SYMBOL, 198), + (41, HUFFMAN_EMIT_SYMBOL, 198), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 198), + (3, HUFFMAN_EMIT_SYMBOL, 228), + (6, HUFFMAN_EMIT_SYMBOL, 228), + (10, HUFFMAN_EMIT_SYMBOL, 228), + (15, HUFFMAN_EMIT_SYMBOL, 228), + (24, HUFFMAN_EMIT_SYMBOL, 228), + (31, HUFFMAN_EMIT_SYMBOL, 228), + (41, HUFFMAN_EMIT_SYMBOL, 228), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 228), + + # Node 145 + (3, HUFFMAN_EMIT_SYMBOL, 232), + (6, HUFFMAN_EMIT_SYMBOL, 232), + (10, HUFFMAN_EMIT_SYMBOL, 232), + (15, HUFFMAN_EMIT_SYMBOL, 232), + (24, HUFFMAN_EMIT_SYMBOL, 232), + (31, HUFFMAN_EMIT_SYMBOL, 232), + (41, HUFFMAN_EMIT_SYMBOL, 232), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 232), + (3, HUFFMAN_EMIT_SYMBOL, 233), + (6, HUFFMAN_EMIT_SYMBOL, 233), + (10, HUFFMAN_EMIT_SYMBOL, 233), + (15, HUFFMAN_EMIT_SYMBOL, 233), + (24, HUFFMAN_EMIT_SYMBOL, 233), + (31, HUFFMAN_EMIT_SYMBOL, 233), + (41, HUFFMAN_EMIT_SYMBOL, 233), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 233), + + # Node 146 + (1, HUFFMAN_EMIT_SYMBOL, 1), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 1), + (1, HUFFMAN_EMIT_SYMBOL, 135), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 135), + (1, HUFFMAN_EMIT_SYMBOL, 137), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 137), + (1, HUFFMAN_EMIT_SYMBOL, 138), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 138), + (1, HUFFMAN_EMIT_SYMBOL, 139), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 139), + (1, HUFFMAN_EMIT_SYMBOL, 140), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 140), + (1, HUFFMAN_EMIT_SYMBOL, 141), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 141), + (1, HUFFMAN_EMIT_SYMBOL, 143), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 143), + + # Node 147 + (2, HUFFMAN_EMIT_SYMBOL, 1), + (9, HUFFMAN_EMIT_SYMBOL, 1), + (23, HUFFMAN_EMIT_SYMBOL, 1), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 1), + (2, HUFFMAN_EMIT_SYMBOL, 135), + (9, HUFFMAN_EMIT_SYMBOL, 135), + (23, HUFFMAN_EMIT_SYMBOL, 135), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 135), + (2, HUFFMAN_EMIT_SYMBOL, 137), + (9, HUFFMAN_EMIT_SYMBOL, 137), + (23, HUFFMAN_EMIT_SYMBOL, 137), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 137), + (2, HUFFMAN_EMIT_SYMBOL, 138), + (9, HUFFMAN_EMIT_SYMBOL, 138), + (23, HUFFMAN_EMIT_SYMBOL, 138), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 138), + + # Node 148 + (3, HUFFMAN_EMIT_SYMBOL, 1), + (6, HUFFMAN_EMIT_SYMBOL, 1), + (10, HUFFMAN_EMIT_SYMBOL, 1), + (15, HUFFMAN_EMIT_SYMBOL, 1), + (24, HUFFMAN_EMIT_SYMBOL, 1), + (31, HUFFMAN_EMIT_SYMBOL, 1), + (41, HUFFMAN_EMIT_SYMBOL, 1), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 1), + (3, HUFFMAN_EMIT_SYMBOL, 135), + (6, HUFFMAN_EMIT_SYMBOL, 135), + (10, HUFFMAN_EMIT_SYMBOL, 135), + (15, HUFFMAN_EMIT_SYMBOL, 135), + (24, HUFFMAN_EMIT_SYMBOL, 135), + (31, HUFFMAN_EMIT_SYMBOL, 135), + (41, HUFFMAN_EMIT_SYMBOL, 135), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 135), + + # Node 149 + (3, HUFFMAN_EMIT_SYMBOL, 137), + (6, HUFFMAN_EMIT_SYMBOL, 137), + (10, HUFFMAN_EMIT_SYMBOL, 137), + (15, HUFFMAN_EMIT_SYMBOL, 137), + (24, HUFFMAN_EMIT_SYMBOL, 137), + (31, HUFFMAN_EMIT_SYMBOL, 137), + (41, HUFFMAN_EMIT_SYMBOL, 137), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 137), + (3, HUFFMAN_EMIT_SYMBOL, 138), + (6, HUFFMAN_EMIT_SYMBOL, 138), + (10, HUFFMAN_EMIT_SYMBOL, 138), + (15, HUFFMAN_EMIT_SYMBOL, 138), + (24, HUFFMAN_EMIT_SYMBOL, 138), + (31, HUFFMAN_EMIT_SYMBOL, 138), + (41, HUFFMAN_EMIT_SYMBOL, 138), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 138), + + # Node 150 + (2, HUFFMAN_EMIT_SYMBOL, 139), + (9, HUFFMAN_EMIT_SYMBOL, 139), + (23, HUFFMAN_EMIT_SYMBOL, 139), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 139), + (2, HUFFMAN_EMIT_SYMBOL, 140), + (9, HUFFMAN_EMIT_SYMBOL, 140), + (23, HUFFMAN_EMIT_SYMBOL, 140), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 140), + (2, HUFFMAN_EMIT_SYMBOL, 141), + (9, HUFFMAN_EMIT_SYMBOL, 141), + (23, HUFFMAN_EMIT_SYMBOL, 141), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 141), + (2, HUFFMAN_EMIT_SYMBOL, 143), + (9, HUFFMAN_EMIT_SYMBOL, 143), + (23, HUFFMAN_EMIT_SYMBOL, 143), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 143), + + # Node 151 + (3, HUFFMAN_EMIT_SYMBOL, 139), + (6, HUFFMAN_EMIT_SYMBOL, 139), + (10, HUFFMAN_EMIT_SYMBOL, 139), + (15, HUFFMAN_EMIT_SYMBOL, 139), + (24, HUFFMAN_EMIT_SYMBOL, 139), + (31, HUFFMAN_EMIT_SYMBOL, 139), + (41, HUFFMAN_EMIT_SYMBOL, 139), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 139), + (3, HUFFMAN_EMIT_SYMBOL, 140), + (6, HUFFMAN_EMIT_SYMBOL, 140), + (10, HUFFMAN_EMIT_SYMBOL, 140), + (15, HUFFMAN_EMIT_SYMBOL, 140), + (24, HUFFMAN_EMIT_SYMBOL, 140), + (31, HUFFMAN_EMIT_SYMBOL, 140), + (41, HUFFMAN_EMIT_SYMBOL, 140), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 140), + + # Node 152 + (3, HUFFMAN_EMIT_SYMBOL, 141), + (6, HUFFMAN_EMIT_SYMBOL, 141), + (10, HUFFMAN_EMIT_SYMBOL, 141), + (15, HUFFMAN_EMIT_SYMBOL, 141), + (24, HUFFMAN_EMIT_SYMBOL, 141), + (31, HUFFMAN_EMIT_SYMBOL, 141), + (41, HUFFMAN_EMIT_SYMBOL, 141), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 141), + (3, HUFFMAN_EMIT_SYMBOL, 143), + (6, HUFFMAN_EMIT_SYMBOL, 143), + (10, HUFFMAN_EMIT_SYMBOL, 143), + (15, HUFFMAN_EMIT_SYMBOL, 143), + (24, HUFFMAN_EMIT_SYMBOL, 143), + (31, HUFFMAN_EMIT_SYMBOL, 143), + (41, HUFFMAN_EMIT_SYMBOL, 143), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 143), + + # Node 153 + (157, 0, 0), + (158, 0, 0), + (160, 0, 0), + (161, 0, 0), + (164, 0, 0), + (165, 0, 0), + (167, 0, 0), + (168, 0, 0), + (172, 0, 0), + (173, 0, 0), + (175, 0, 0), + (177, 0, 0), + (182, 0, 0), + (185, 0, 0), + (191, 0, 0), + (207, 0, 0), + + # Node 154 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 147), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 149), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 150), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 151), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 152), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 155), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 157), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 158), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 165), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 166), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 168), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 174), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 175), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 180), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 182), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 183), + + # Node 155 + (1, HUFFMAN_EMIT_SYMBOL, 147), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 147), + (1, HUFFMAN_EMIT_SYMBOL, 149), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 149), + (1, HUFFMAN_EMIT_SYMBOL, 150), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 150), + (1, HUFFMAN_EMIT_SYMBOL, 151), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 151), + (1, HUFFMAN_EMIT_SYMBOL, 152), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 152), + (1, HUFFMAN_EMIT_SYMBOL, 155), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 155), + (1, HUFFMAN_EMIT_SYMBOL, 157), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 157), + (1, HUFFMAN_EMIT_SYMBOL, 158), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 158), + + # Node 156 + (2, HUFFMAN_EMIT_SYMBOL, 147), + (9, HUFFMAN_EMIT_SYMBOL, 147), + (23, HUFFMAN_EMIT_SYMBOL, 147), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 147), + (2, HUFFMAN_EMIT_SYMBOL, 149), + (9, HUFFMAN_EMIT_SYMBOL, 149), + (23, HUFFMAN_EMIT_SYMBOL, 149), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 149), + (2, HUFFMAN_EMIT_SYMBOL, 150), + (9, HUFFMAN_EMIT_SYMBOL, 150), + (23, HUFFMAN_EMIT_SYMBOL, 150), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 150), + (2, HUFFMAN_EMIT_SYMBOL, 151), + (9, HUFFMAN_EMIT_SYMBOL, 151), + (23, HUFFMAN_EMIT_SYMBOL, 151), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 151), + + # Node 157 + (3, HUFFMAN_EMIT_SYMBOL, 147), + (6, HUFFMAN_EMIT_SYMBOL, 147), + (10, HUFFMAN_EMIT_SYMBOL, 147), + (15, HUFFMAN_EMIT_SYMBOL, 147), + (24, HUFFMAN_EMIT_SYMBOL, 147), + (31, HUFFMAN_EMIT_SYMBOL, 147), + (41, HUFFMAN_EMIT_SYMBOL, 147), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 147), + (3, HUFFMAN_EMIT_SYMBOL, 149), + (6, HUFFMAN_EMIT_SYMBOL, 149), + (10, HUFFMAN_EMIT_SYMBOL, 149), + (15, HUFFMAN_EMIT_SYMBOL, 149), + (24, HUFFMAN_EMIT_SYMBOL, 149), + (31, HUFFMAN_EMIT_SYMBOL, 149), + (41, HUFFMAN_EMIT_SYMBOL, 149), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 149), + + # Node 158 + (3, HUFFMAN_EMIT_SYMBOL, 150), + (6, HUFFMAN_EMIT_SYMBOL, 150), + (10, HUFFMAN_EMIT_SYMBOL, 150), + (15, HUFFMAN_EMIT_SYMBOL, 150), + (24, HUFFMAN_EMIT_SYMBOL, 150), + (31, HUFFMAN_EMIT_SYMBOL, 150), + (41, HUFFMAN_EMIT_SYMBOL, 150), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 150), + (3, HUFFMAN_EMIT_SYMBOL, 151), + (6, HUFFMAN_EMIT_SYMBOL, 151), + (10, HUFFMAN_EMIT_SYMBOL, 151), + (15, HUFFMAN_EMIT_SYMBOL, 151), + (24, HUFFMAN_EMIT_SYMBOL, 151), + (31, HUFFMAN_EMIT_SYMBOL, 151), + (41, HUFFMAN_EMIT_SYMBOL, 151), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 151), + + # Node 159 + (2, HUFFMAN_EMIT_SYMBOL, 152), + (9, HUFFMAN_EMIT_SYMBOL, 152), + (23, HUFFMAN_EMIT_SYMBOL, 152), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 152), + (2, HUFFMAN_EMIT_SYMBOL, 155), + (9, HUFFMAN_EMIT_SYMBOL, 155), + (23, HUFFMAN_EMIT_SYMBOL, 155), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 155), + (2, HUFFMAN_EMIT_SYMBOL, 157), + (9, HUFFMAN_EMIT_SYMBOL, 157), + (23, HUFFMAN_EMIT_SYMBOL, 157), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 157), + (2, HUFFMAN_EMIT_SYMBOL, 158), + (9, HUFFMAN_EMIT_SYMBOL, 158), + (23, HUFFMAN_EMIT_SYMBOL, 158), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 158), + + # Node 160 + (3, HUFFMAN_EMIT_SYMBOL, 152), + (6, HUFFMAN_EMIT_SYMBOL, 152), + (10, HUFFMAN_EMIT_SYMBOL, 152), + (15, HUFFMAN_EMIT_SYMBOL, 152), + (24, HUFFMAN_EMIT_SYMBOL, 152), + (31, HUFFMAN_EMIT_SYMBOL, 152), + (41, HUFFMAN_EMIT_SYMBOL, 152), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 152), + (3, HUFFMAN_EMIT_SYMBOL, 155), + (6, HUFFMAN_EMIT_SYMBOL, 155), + (10, HUFFMAN_EMIT_SYMBOL, 155), + (15, HUFFMAN_EMIT_SYMBOL, 155), + (24, HUFFMAN_EMIT_SYMBOL, 155), + (31, HUFFMAN_EMIT_SYMBOL, 155), + (41, HUFFMAN_EMIT_SYMBOL, 155), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 155), + + # Node 161 + (3, HUFFMAN_EMIT_SYMBOL, 157), + (6, HUFFMAN_EMIT_SYMBOL, 157), + (10, HUFFMAN_EMIT_SYMBOL, 157), + (15, HUFFMAN_EMIT_SYMBOL, 157), + (24, HUFFMAN_EMIT_SYMBOL, 157), + (31, HUFFMAN_EMIT_SYMBOL, 157), + (41, HUFFMAN_EMIT_SYMBOL, 157), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 157), + (3, HUFFMAN_EMIT_SYMBOL, 158), + (6, HUFFMAN_EMIT_SYMBOL, 158), + (10, HUFFMAN_EMIT_SYMBOL, 158), + (15, HUFFMAN_EMIT_SYMBOL, 158), + (24, HUFFMAN_EMIT_SYMBOL, 158), + (31, HUFFMAN_EMIT_SYMBOL, 158), + (41, HUFFMAN_EMIT_SYMBOL, 158), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 158), + + # Node 162 + (1, HUFFMAN_EMIT_SYMBOL, 165), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 165), + (1, HUFFMAN_EMIT_SYMBOL, 166), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 166), + (1, HUFFMAN_EMIT_SYMBOL, 168), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 168), + (1, HUFFMAN_EMIT_SYMBOL, 174), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 174), + (1, HUFFMAN_EMIT_SYMBOL, 175), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 175), + (1, HUFFMAN_EMIT_SYMBOL, 180), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 180), + (1, HUFFMAN_EMIT_SYMBOL, 182), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 182), + (1, HUFFMAN_EMIT_SYMBOL, 183), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 183), + + # Node 163 + (2, HUFFMAN_EMIT_SYMBOL, 165), + (9, HUFFMAN_EMIT_SYMBOL, 165), + (23, HUFFMAN_EMIT_SYMBOL, 165), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 165), + (2, HUFFMAN_EMIT_SYMBOL, 166), + (9, HUFFMAN_EMIT_SYMBOL, 166), + (23, HUFFMAN_EMIT_SYMBOL, 166), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 166), + (2, HUFFMAN_EMIT_SYMBOL, 168), + (9, HUFFMAN_EMIT_SYMBOL, 168), + (23, HUFFMAN_EMIT_SYMBOL, 168), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 168), + (2, HUFFMAN_EMIT_SYMBOL, 174), + (9, HUFFMAN_EMIT_SYMBOL, 174), + (23, HUFFMAN_EMIT_SYMBOL, 174), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 174), + + # Node 164 + (3, HUFFMAN_EMIT_SYMBOL, 165), + (6, HUFFMAN_EMIT_SYMBOL, 165), + (10, HUFFMAN_EMIT_SYMBOL, 165), + (15, HUFFMAN_EMIT_SYMBOL, 165), + (24, HUFFMAN_EMIT_SYMBOL, 165), + (31, HUFFMAN_EMIT_SYMBOL, 165), + (41, HUFFMAN_EMIT_SYMBOL, 165), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 165), + (3, HUFFMAN_EMIT_SYMBOL, 166), + (6, HUFFMAN_EMIT_SYMBOL, 166), + (10, HUFFMAN_EMIT_SYMBOL, 166), + (15, HUFFMAN_EMIT_SYMBOL, 166), + (24, HUFFMAN_EMIT_SYMBOL, 166), + (31, HUFFMAN_EMIT_SYMBOL, 166), + (41, HUFFMAN_EMIT_SYMBOL, 166), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 166), + + # Node 165 + (3, HUFFMAN_EMIT_SYMBOL, 168), + (6, HUFFMAN_EMIT_SYMBOL, 168), + (10, HUFFMAN_EMIT_SYMBOL, 168), + (15, HUFFMAN_EMIT_SYMBOL, 168), + (24, HUFFMAN_EMIT_SYMBOL, 168), + (31, HUFFMAN_EMIT_SYMBOL, 168), + (41, HUFFMAN_EMIT_SYMBOL, 168), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 168), + (3, HUFFMAN_EMIT_SYMBOL, 174), + (6, HUFFMAN_EMIT_SYMBOL, 174), + (10, HUFFMAN_EMIT_SYMBOL, 174), + (15, HUFFMAN_EMIT_SYMBOL, 174), + (24, HUFFMAN_EMIT_SYMBOL, 174), + (31, HUFFMAN_EMIT_SYMBOL, 174), + (41, HUFFMAN_EMIT_SYMBOL, 174), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 174), + + # Node 166 + (2, HUFFMAN_EMIT_SYMBOL, 175), + (9, HUFFMAN_EMIT_SYMBOL, 175), + (23, HUFFMAN_EMIT_SYMBOL, 175), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 175), + (2, HUFFMAN_EMIT_SYMBOL, 180), + (9, HUFFMAN_EMIT_SYMBOL, 180), + (23, HUFFMAN_EMIT_SYMBOL, 180), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 180), + (2, HUFFMAN_EMIT_SYMBOL, 182), + (9, HUFFMAN_EMIT_SYMBOL, 182), + (23, HUFFMAN_EMIT_SYMBOL, 182), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 182), + (2, HUFFMAN_EMIT_SYMBOL, 183), + (9, HUFFMAN_EMIT_SYMBOL, 183), + (23, HUFFMAN_EMIT_SYMBOL, 183), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 183), + + # Node 167 + (3, HUFFMAN_EMIT_SYMBOL, 175), + (6, HUFFMAN_EMIT_SYMBOL, 175), + (10, HUFFMAN_EMIT_SYMBOL, 175), + (15, HUFFMAN_EMIT_SYMBOL, 175), + (24, HUFFMAN_EMIT_SYMBOL, 175), + (31, HUFFMAN_EMIT_SYMBOL, 175), + (41, HUFFMAN_EMIT_SYMBOL, 175), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 175), + (3, HUFFMAN_EMIT_SYMBOL, 180), + (6, HUFFMAN_EMIT_SYMBOL, 180), + (10, HUFFMAN_EMIT_SYMBOL, 180), + (15, HUFFMAN_EMIT_SYMBOL, 180), + (24, HUFFMAN_EMIT_SYMBOL, 180), + (31, HUFFMAN_EMIT_SYMBOL, 180), + (41, HUFFMAN_EMIT_SYMBOL, 180), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 180), + + # Node 168 + (3, HUFFMAN_EMIT_SYMBOL, 182), + (6, HUFFMAN_EMIT_SYMBOL, 182), + (10, HUFFMAN_EMIT_SYMBOL, 182), + (15, HUFFMAN_EMIT_SYMBOL, 182), + (24, HUFFMAN_EMIT_SYMBOL, 182), + (31, HUFFMAN_EMIT_SYMBOL, 182), + (41, HUFFMAN_EMIT_SYMBOL, 182), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 182), + (3, HUFFMAN_EMIT_SYMBOL, 183), + (6, HUFFMAN_EMIT_SYMBOL, 183), + (10, HUFFMAN_EMIT_SYMBOL, 183), + (15, HUFFMAN_EMIT_SYMBOL, 183), + (24, HUFFMAN_EMIT_SYMBOL, 183), + (31, HUFFMAN_EMIT_SYMBOL, 183), + (41, HUFFMAN_EMIT_SYMBOL, 183), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 183), + + # Node 169 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 188), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 191), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 197), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 231), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 239), + (176, 0, 0), + (178, 0, 0), + (179, 0, 0), + (183, 0, 0), + (184, 0, 0), + (186, 0, 0), + (187, 0, 0), + (192, 0, 0), + (199, 0, 0), + (208, 0, 0), + (223, 0, 0), + + # Node 170 + (1, HUFFMAN_EMIT_SYMBOL, 188), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 188), + (1, HUFFMAN_EMIT_SYMBOL, 191), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 191), + (1, HUFFMAN_EMIT_SYMBOL, 197), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 197), + (1, HUFFMAN_EMIT_SYMBOL, 231), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 231), + (1, HUFFMAN_EMIT_SYMBOL, 239), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 239), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 9), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 142), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 144), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 145), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 148), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 159), + + # Node 171 + (2, HUFFMAN_EMIT_SYMBOL, 188), + (9, HUFFMAN_EMIT_SYMBOL, 188), + (23, HUFFMAN_EMIT_SYMBOL, 188), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 188), + (2, HUFFMAN_EMIT_SYMBOL, 191), + (9, HUFFMAN_EMIT_SYMBOL, 191), + (23, HUFFMAN_EMIT_SYMBOL, 191), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 191), + (2, HUFFMAN_EMIT_SYMBOL, 197), + (9, HUFFMAN_EMIT_SYMBOL, 197), + (23, HUFFMAN_EMIT_SYMBOL, 197), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 197), + (2, HUFFMAN_EMIT_SYMBOL, 231), + (9, HUFFMAN_EMIT_SYMBOL, 231), + (23, HUFFMAN_EMIT_SYMBOL, 231), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 231), + + # Node 172 + (3, HUFFMAN_EMIT_SYMBOL, 188), + (6, HUFFMAN_EMIT_SYMBOL, 188), + (10, HUFFMAN_EMIT_SYMBOL, 188), + (15, HUFFMAN_EMIT_SYMBOL, 188), + (24, HUFFMAN_EMIT_SYMBOL, 188), + (31, HUFFMAN_EMIT_SYMBOL, 188), + (41, HUFFMAN_EMIT_SYMBOL, 188), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 188), + (3, HUFFMAN_EMIT_SYMBOL, 191), + (6, HUFFMAN_EMIT_SYMBOL, 191), + (10, HUFFMAN_EMIT_SYMBOL, 191), + (15, HUFFMAN_EMIT_SYMBOL, 191), + (24, HUFFMAN_EMIT_SYMBOL, 191), + (31, HUFFMAN_EMIT_SYMBOL, 191), + (41, HUFFMAN_EMIT_SYMBOL, 191), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 191), + + # Node 173 + (3, HUFFMAN_EMIT_SYMBOL, 197), + (6, HUFFMAN_EMIT_SYMBOL, 197), + (10, HUFFMAN_EMIT_SYMBOL, 197), + (15, HUFFMAN_EMIT_SYMBOL, 197), + (24, HUFFMAN_EMIT_SYMBOL, 197), + (31, HUFFMAN_EMIT_SYMBOL, 197), + (41, HUFFMAN_EMIT_SYMBOL, 197), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 197), + (3, HUFFMAN_EMIT_SYMBOL, 231), + (6, HUFFMAN_EMIT_SYMBOL, 231), + (10, HUFFMAN_EMIT_SYMBOL, 231), + (15, HUFFMAN_EMIT_SYMBOL, 231), + (24, HUFFMAN_EMIT_SYMBOL, 231), + (31, HUFFMAN_EMIT_SYMBOL, 231), + (41, HUFFMAN_EMIT_SYMBOL, 231), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 231), + + # Node 174 + (2, HUFFMAN_EMIT_SYMBOL, 239), + (9, HUFFMAN_EMIT_SYMBOL, 239), + (23, HUFFMAN_EMIT_SYMBOL, 239), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 239), + (1, HUFFMAN_EMIT_SYMBOL, 9), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 9), + (1, HUFFMAN_EMIT_SYMBOL, 142), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 142), + (1, HUFFMAN_EMIT_SYMBOL, 144), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 144), + (1, HUFFMAN_EMIT_SYMBOL, 145), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 145), + (1, HUFFMAN_EMIT_SYMBOL, 148), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 148), + (1, HUFFMAN_EMIT_SYMBOL, 159), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 159), + + # Node 175 + (3, HUFFMAN_EMIT_SYMBOL, 239), + (6, HUFFMAN_EMIT_SYMBOL, 239), + (10, HUFFMAN_EMIT_SYMBOL, 239), + (15, HUFFMAN_EMIT_SYMBOL, 239), + (24, HUFFMAN_EMIT_SYMBOL, 239), + (31, HUFFMAN_EMIT_SYMBOL, 239), + (41, HUFFMAN_EMIT_SYMBOL, 239), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 239), + (2, HUFFMAN_EMIT_SYMBOL, 9), + (9, HUFFMAN_EMIT_SYMBOL, 9), + (23, HUFFMAN_EMIT_SYMBOL, 9), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 9), + (2, HUFFMAN_EMIT_SYMBOL, 142), + (9, HUFFMAN_EMIT_SYMBOL, 142), + (23, HUFFMAN_EMIT_SYMBOL, 142), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 142), + + # Node 176 + (3, HUFFMAN_EMIT_SYMBOL, 9), + (6, HUFFMAN_EMIT_SYMBOL, 9), + (10, HUFFMAN_EMIT_SYMBOL, 9), + (15, HUFFMAN_EMIT_SYMBOL, 9), + (24, HUFFMAN_EMIT_SYMBOL, 9), + (31, HUFFMAN_EMIT_SYMBOL, 9), + (41, HUFFMAN_EMIT_SYMBOL, 9), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 9), + (3, HUFFMAN_EMIT_SYMBOL, 142), + (6, HUFFMAN_EMIT_SYMBOL, 142), + (10, HUFFMAN_EMIT_SYMBOL, 142), + (15, HUFFMAN_EMIT_SYMBOL, 142), + (24, HUFFMAN_EMIT_SYMBOL, 142), + (31, HUFFMAN_EMIT_SYMBOL, 142), + (41, HUFFMAN_EMIT_SYMBOL, 142), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 142), + + # Node 177 + (2, HUFFMAN_EMIT_SYMBOL, 144), + (9, HUFFMAN_EMIT_SYMBOL, 144), + (23, HUFFMAN_EMIT_SYMBOL, 144), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 144), + (2, HUFFMAN_EMIT_SYMBOL, 145), + (9, HUFFMAN_EMIT_SYMBOL, 145), + (23, HUFFMAN_EMIT_SYMBOL, 145), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 145), + (2, HUFFMAN_EMIT_SYMBOL, 148), + (9, HUFFMAN_EMIT_SYMBOL, 148), + (23, HUFFMAN_EMIT_SYMBOL, 148), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 148), + (2, HUFFMAN_EMIT_SYMBOL, 159), + (9, HUFFMAN_EMIT_SYMBOL, 159), + (23, HUFFMAN_EMIT_SYMBOL, 159), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 159), + + # Node 178 + (3, HUFFMAN_EMIT_SYMBOL, 144), + (6, HUFFMAN_EMIT_SYMBOL, 144), + (10, HUFFMAN_EMIT_SYMBOL, 144), + (15, HUFFMAN_EMIT_SYMBOL, 144), + (24, HUFFMAN_EMIT_SYMBOL, 144), + (31, HUFFMAN_EMIT_SYMBOL, 144), + (41, HUFFMAN_EMIT_SYMBOL, 144), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 144), + (3, HUFFMAN_EMIT_SYMBOL, 145), + (6, HUFFMAN_EMIT_SYMBOL, 145), + (10, HUFFMAN_EMIT_SYMBOL, 145), + (15, HUFFMAN_EMIT_SYMBOL, 145), + (24, HUFFMAN_EMIT_SYMBOL, 145), + (31, HUFFMAN_EMIT_SYMBOL, 145), + (41, HUFFMAN_EMIT_SYMBOL, 145), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 145), + + # Node 179 + (3, HUFFMAN_EMIT_SYMBOL, 148), + (6, HUFFMAN_EMIT_SYMBOL, 148), + (10, HUFFMAN_EMIT_SYMBOL, 148), + (15, HUFFMAN_EMIT_SYMBOL, 148), + (24, HUFFMAN_EMIT_SYMBOL, 148), + (31, HUFFMAN_EMIT_SYMBOL, 148), + (41, HUFFMAN_EMIT_SYMBOL, 148), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 148), + (3, HUFFMAN_EMIT_SYMBOL, 159), + (6, HUFFMAN_EMIT_SYMBOL, 159), + (10, HUFFMAN_EMIT_SYMBOL, 159), + (15, HUFFMAN_EMIT_SYMBOL, 159), + (24, HUFFMAN_EMIT_SYMBOL, 159), + (31, HUFFMAN_EMIT_SYMBOL, 159), + (41, HUFFMAN_EMIT_SYMBOL, 159), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 159), + + # Node 180 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 171), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 206), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 215), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 225), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 236), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 237), + (188, 0, 0), + (189, 0, 0), + (193, 0, 0), + (196, 0, 0), + (200, 0, 0), + (203, 0, 0), + (209, 0, 0), + (216, 0, 0), + (224, 0, 0), + (238, 0, 0), + + # Node 181 + (1, HUFFMAN_EMIT_SYMBOL, 171), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 171), + (1, HUFFMAN_EMIT_SYMBOL, 206), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 206), + (1, HUFFMAN_EMIT_SYMBOL, 215), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 215), + (1, HUFFMAN_EMIT_SYMBOL, 225), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 225), + (1, HUFFMAN_EMIT_SYMBOL, 236), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 236), + (1, HUFFMAN_EMIT_SYMBOL, 237), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 237), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 199), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 207), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 234), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 235), + + # Node 182 + (2, HUFFMAN_EMIT_SYMBOL, 171), + (9, HUFFMAN_EMIT_SYMBOL, 171), + (23, HUFFMAN_EMIT_SYMBOL, 171), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 171), + (2, HUFFMAN_EMIT_SYMBOL, 206), + (9, HUFFMAN_EMIT_SYMBOL, 206), + (23, HUFFMAN_EMIT_SYMBOL, 206), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 206), + (2, HUFFMAN_EMIT_SYMBOL, 215), + (9, HUFFMAN_EMIT_SYMBOL, 215), + (23, HUFFMAN_EMIT_SYMBOL, 215), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 215), + (2, HUFFMAN_EMIT_SYMBOL, 225), + (9, HUFFMAN_EMIT_SYMBOL, 225), + (23, HUFFMAN_EMIT_SYMBOL, 225), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 225), + + # Node 183 + (3, HUFFMAN_EMIT_SYMBOL, 171), + (6, HUFFMAN_EMIT_SYMBOL, 171), + (10, HUFFMAN_EMIT_SYMBOL, 171), + (15, HUFFMAN_EMIT_SYMBOL, 171), + (24, HUFFMAN_EMIT_SYMBOL, 171), + (31, HUFFMAN_EMIT_SYMBOL, 171), + (41, HUFFMAN_EMIT_SYMBOL, 171), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 171), + (3, HUFFMAN_EMIT_SYMBOL, 206), + (6, HUFFMAN_EMIT_SYMBOL, 206), + (10, HUFFMAN_EMIT_SYMBOL, 206), + (15, HUFFMAN_EMIT_SYMBOL, 206), + (24, HUFFMAN_EMIT_SYMBOL, 206), + (31, HUFFMAN_EMIT_SYMBOL, 206), + (41, HUFFMAN_EMIT_SYMBOL, 206), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 206), + + # Node 184 + (3, HUFFMAN_EMIT_SYMBOL, 215), + (6, HUFFMAN_EMIT_SYMBOL, 215), + (10, HUFFMAN_EMIT_SYMBOL, 215), + (15, HUFFMAN_EMIT_SYMBOL, 215), + (24, HUFFMAN_EMIT_SYMBOL, 215), + (31, HUFFMAN_EMIT_SYMBOL, 215), + (41, HUFFMAN_EMIT_SYMBOL, 215), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 215), + (3, HUFFMAN_EMIT_SYMBOL, 225), + (6, HUFFMAN_EMIT_SYMBOL, 225), + (10, HUFFMAN_EMIT_SYMBOL, 225), + (15, HUFFMAN_EMIT_SYMBOL, 225), + (24, HUFFMAN_EMIT_SYMBOL, 225), + (31, HUFFMAN_EMIT_SYMBOL, 225), + (41, HUFFMAN_EMIT_SYMBOL, 225), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 225), + + # Node 185 + (2, HUFFMAN_EMIT_SYMBOL, 236), + (9, HUFFMAN_EMIT_SYMBOL, 236), + (23, HUFFMAN_EMIT_SYMBOL, 236), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 236), + (2, HUFFMAN_EMIT_SYMBOL, 237), + (9, HUFFMAN_EMIT_SYMBOL, 237), + (23, HUFFMAN_EMIT_SYMBOL, 237), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 237), + (1, HUFFMAN_EMIT_SYMBOL, 199), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 199), + (1, HUFFMAN_EMIT_SYMBOL, 207), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 207), + (1, HUFFMAN_EMIT_SYMBOL, 234), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 234), + (1, HUFFMAN_EMIT_SYMBOL, 235), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 235), + + # Node 186 + (3, HUFFMAN_EMIT_SYMBOL, 236), + (6, HUFFMAN_EMIT_SYMBOL, 236), + (10, HUFFMAN_EMIT_SYMBOL, 236), + (15, HUFFMAN_EMIT_SYMBOL, 236), + (24, HUFFMAN_EMIT_SYMBOL, 236), + (31, HUFFMAN_EMIT_SYMBOL, 236), + (41, HUFFMAN_EMIT_SYMBOL, 236), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 236), + (3, HUFFMAN_EMIT_SYMBOL, 237), + (6, HUFFMAN_EMIT_SYMBOL, 237), + (10, HUFFMAN_EMIT_SYMBOL, 237), + (15, HUFFMAN_EMIT_SYMBOL, 237), + (24, HUFFMAN_EMIT_SYMBOL, 237), + (31, HUFFMAN_EMIT_SYMBOL, 237), + (41, HUFFMAN_EMIT_SYMBOL, 237), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 237), + + # Node 187 + (2, HUFFMAN_EMIT_SYMBOL, 199), + (9, HUFFMAN_EMIT_SYMBOL, 199), + (23, HUFFMAN_EMIT_SYMBOL, 199), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 199), + (2, HUFFMAN_EMIT_SYMBOL, 207), + (9, HUFFMAN_EMIT_SYMBOL, 207), + (23, HUFFMAN_EMIT_SYMBOL, 207), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 207), + (2, HUFFMAN_EMIT_SYMBOL, 234), + (9, HUFFMAN_EMIT_SYMBOL, 234), + (23, HUFFMAN_EMIT_SYMBOL, 234), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 234), + (2, HUFFMAN_EMIT_SYMBOL, 235), + (9, HUFFMAN_EMIT_SYMBOL, 235), + (23, HUFFMAN_EMIT_SYMBOL, 235), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 235), + + # Node 188 + (3, HUFFMAN_EMIT_SYMBOL, 199), + (6, HUFFMAN_EMIT_SYMBOL, 199), + (10, HUFFMAN_EMIT_SYMBOL, 199), + (15, HUFFMAN_EMIT_SYMBOL, 199), + (24, HUFFMAN_EMIT_SYMBOL, 199), + (31, HUFFMAN_EMIT_SYMBOL, 199), + (41, HUFFMAN_EMIT_SYMBOL, 199), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 199), + (3, HUFFMAN_EMIT_SYMBOL, 207), + (6, HUFFMAN_EMIT_SYMBOL, 207), + (10, HUFFMAN_EMIT_SYMBOL, 207), + (15, HUFFMAN_EMIT_SYMBOL, 207), + (24, HUFFMAN_EMIT_SYMBOL, 207), + (31, HUFFMAN_EMIT_SYMBOL, 207), + (41, HUFFMAN_EMIT_SYMBOL, 207), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 207), + + # Node 189 + (3, HUFFMAN_EMIT_SYMBOL, 234), + (6, HUFFMAN_EMIT_SYMBOL, 234), + (10, HUFFMAN_EMIT_SYMBOL, 234), + (15, HUFFMAN_EMIT_SYMBOL, 234), + (24, HUFFMAN_EMIT_SYMBOL, 234), + (31, HUFFMAN_EMIT_SYMBOL, 234), + (41, HUFFMAN_EMIT_SYMBOL, 234), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 234), + (3, HUFFMAN_EMIT_SYMBOL, 235), + (6, HUFFMAN_EMIT_SYMBOL, 235), + (10, HUFFMAN_EMIT_SYMBOL, 235), + (15, HUFFMAN_EMIT_SYMBOL, 235), + (24, HUFFMAN_EMIT_SYMBOL, 235), + (31, HUFFMAN_EMIT_SYMBOL, 235), + (41, HUFFMAN_EMIT_SYMBOL, 235), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 235), + + # Node 190 + (194, 0, 0), + (195, 0, 0), + (197, 0, 0), + (198, 0, 0), + (201, 0, 0), + (202, 0, 0), + (204, 0, 0), + (205, 0, 0), + (210, 0, 0), + (213, 0, 0), + (217, 0, 0), + (220, 0, 0), + (225, 0, 0), + (231, 0, 0), + (239, 0, 0), + (246, 0, 0), + + # Node 191 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 192), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 193), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 200), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 201), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 202), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 205), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 210), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 213), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 218), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 219), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 238), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 240), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 242), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 243), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 255), + (206, 0, 0), + + # Node 192 + (1, HUFFMAN_EMIT_SYMBOL, 192), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 192), + (1, HUFFMAN_EMIT_SYMBOL, 193), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 193), + (1, HUFFMAN_EMIT_SYMBOL, 200), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 200), + (1, HUFFMAN_EMIT_SYMBOL, 201), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 201), + (1, HUFFMAN_EMIT_SYMBOL, 202), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 202), + (1, HUFFMAN_EMIT_SYMBOL, 205), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 205), + (1, HUFFMAN_EMIT_SYMBOL, 210), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 210), + (1, HUFFMAN_EMIT_SYMBOL, 213), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 213), + + # Node 193 + (2, HUFFMAN_EMIT_SYMBOL, 192), + (9, HUFFMAN_EMIT_SYMBOL, 192), + (23, HUFFMAN_EMIT_SYMBOL, 192), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 192), + (2, HUFFMAN_EMIT_SYMBOL, 193), + (9, HUFFMAN_EMIT_SYMBOL, 193), + (23, HUFFMAN_EMIT_SYMBOL, 193), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 193), + (2, HUFFMAN_EMIT_SYMBOL, 200), + (9, HUFFMAN_EMIT_SYMBOL, 200), + (23, HUFFMAN_EMIT_SYMBOL, 200), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 200), + (2, HUFFMAN_EMIT_SYMBOL, 201), + (9, HUFFMAN_EMIT_SYMBOL, 201), + (23, HUFFMAN_EMIT_SYMBOL, 201), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 201), + + # Node 194 + (3, HUFFMAN_EMIT_SYMBOL, 192), + (6, HUFFMAN_EMIT_SYMBOL, 192), + (10, HUFFMAN_EMIT_SYMBOL, 192), + (15, HUFFMAN_EMIT_SYMBOL, 192), + (24, HUFFMAN_EMIT_SYMBOL, 192), + (31, HUFFMAN_EMIT_SYMBOL, 192), + (41, HUFFMAN_EMIT_SYMBOL, 192), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 192), + (3, HUFFMAN_EMIT_SYMBOL, 193), + (6, HUFFMAN_EMIT_SYMBOL, 193), + (10, HUFFMAN_EMIT_SYMBOL, 193), + (15, HUFFMAN_EMIT_SYMBOL, 193), + (24, HUFFMAN_EMIT_SYMBOL, 193), + (31, HUFFMAN_EMIT_SYMBOL, 193), + (41, HUFFMAN_EMIT_SYMBOL, 193), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 193), + + # Node 195 + (3, HUFFMAN_EMIT_SYMBOL, 200), + (6, HUFFMAN_EMIT_SYMBOL, 200), + (10, HUFFMAN_EMIT_SYMBOL, 200), + (15, HUFFMAN_EMIT_SYMBOL, 200), + (24, HUFFMAN_EMIT_SYMBOL, 200), + (31, HUFFMAN_EMIT_SYMBOL, 200), + (41, HUFFMAN_EMIT_SYMBOL, 200), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 200), + (3, HUFFMAN_EMIT_SYMBOL, 201), + (6, HUFFMAN_EMIT_SYMBOL, 201), + (10, HUFFMAN_EMIT_SYMBOL, 201), + (15, HUFFMAN_EMIT_SYMBOL, 201), + (24, HUFFMAN_EMIT_SYMBOL, 201), + (31, HUFFMAN_EMIT_SYMBOL, 201), + (41, HUFFMAN_EMIT_SYMBOL, 201), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 201), + + # Node 196 + (2, HUFFMAN_EMIT_SYMBOL, 202), + (9, HUFFMAN_EMIT_SYMBOL, 202), + (23, HUFFMAN_EMIT_SYMBOL, 202), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 202), + (2, HUFFMAN_EMIT_SYMBOL, 205), + (9, HUFFMAN_EMIT_SYMBOL, 205), + (23, HUFFMAN_EMIT_SYMBOL, 205), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 205), + (2, HUFFMAN_EMIT_SYMBOL, 210), + (9, HUFFMAN_EMIT_SYMBOL, 210), + (23, HUFFMAN_EMIT_SYMBOL, 210), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 210), + (2, HUFFMAN_EMIT_SYMBOL, 213), + (9, HUFFMAN_EMIT_SYMBOL, 213), + (23, HUFFMAN_EMIT_SYMBOL, 213), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 213), + + # Node 197 + (3, HUFFMAN_EMIT_SYMBOL, 202), + (6, HUFFMAN_EMIT_SYMBOL, 202), + (10, HUFFMAN_EMIT_SYMBOL, 202), + (15, HUFFMAN_EMIT_SYMBOL, 202), + (24, HUFFMAN_EMIT_SYMBOL, 202), + (31, HUFFMAN_EMIT_SYMBOL, 202), + (41, HUFFMAN_EMIT_SYMBOL, 202), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 202), + (3, HUFFMAN_EMIT_SYMBOL, 205), + (6, HUFFMAN_EMIT_SYMBOL, 205), + (10, HUFFMAN_EMIT_SYMBOL, 205), + (15, HUFFMAN_EMIT_SYMBOL, 205), + (24, HUFFMAN_EMIT_SYMBOL, 205), + (31, HUFFMAN_EMIT_SYMBOL, 205), + (41, HUFFMAN_EMIT_SYMBOL, 205), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 205), + + # Node 198 + (3, HUFFMAN_EMIT_SYMBOL, 210), + (6, HUFFMAN_EMIT_SYMBOL, 210), + (10, HUFFMAN_EMIT_SYMBOL, 210), + (15, HUFFMAN_EMIT_SYMBOL, 210), + (24, HUFFMAN_EMIT_SYMBOL, 210), + (31, HUFFMAN_EMIT_SYMBOL, 210), + (41, HUFFMAN_EMIT_SYMBOL, 210), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 210), + (3, HUFFMAN_EMIT_SYMBOL, 213), + (6, HUFFMAN_EMIT_SYMBOL, 213), + (10, HUFFMAN_EMIT_SYMBOL, 213), + (15, HUFFMAN_EMIT_SYMBOL, 213), + (24, HUFFMAN_EMIT_SYMBOL, 213), + (31, HUFFMAN_EMIT_SYMBOL, 213), + (41, HUFFMAN_EMIT_SYMBOL, 213), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 213), + + # Node 199 + (1, HUFFMAN_EMIT_SYMBOL, 218), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 218), + (1, HUFFMAN_EMIT_SYMBOL, 219), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 219), + (1, HUFFMAN_EMIT_SYMBOL, 238), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 238), + (1, HUFFMAN_EMIT_SYMBOL, 240), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 240), + (1, HUFFMAN_EMIT_SYMBOL, 242), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 242), + (1, HUFFMAN_EMIT_SYMBOL, 243), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 243), + (1, HUFFMAN_EMIT_SYMBOL, 255), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 255), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 203), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 204), + + # Node 200 + (2, HUFFMAN_EMIT_SYMBOL, 218), + (9, HUFFMAN_EMIT_SYMBOL, 218), + (23, HUFFMAN_EMIT_SYMBOL, 218), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 218), + (2, HUFFMAN_EMIT_SYMBOL, 219), + (9, HUFFMAN_EMIT_SYMBOL, 219), + (23, HUFFMAN_EMIT_SYMBOL, 219), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 219), + (2, HUFFMAN_EMIT_SYMBOL, 238), + (9, HUFFMAN_EMIT_SYMBOL, 238), + (23, HUFFMAN_EMIT_SYMBOL, 238), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 238), + (2, HUFFMAN_EMIT_SYMBOL, 240), + (9, HUFFMAN_EMIT_SYMBOL, 240), + (23, HUFFMAN_EMIT_SYMBOL, 240), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 240), + + # Node 201 + (3, HUFFMAN_EMIT_SYMBOL, 218), + (6, HUFFMAN_EMIT_SYMBOL, 218), + (10, HUFFMAN_EMIT_SYMBOL, 218), + (15, HUFFMAN_EMIT_SYMBOL, 218), + (24, HUFFMAN_EMIT_SYMBOL, 218), + (31, HUFFMAN_EMIT_SYMBOL, 218), + (41, HUFFMAN_EMIT_SYMBOL, 218), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 218), + (3, HUFFMAN_EMIT_SYMBOL, 219), + (6, HUFFMAN_EMIT_SYMBOL, 219), + (10, HUFFMAN_EMIT_SYMBOL, 219), + (15, HUFFMAN_EMIT_SYMBOL, 219), + (24, HUFFMAN_EMIT_SYMBOL, 219), + (31, HUFFMAN_EMIT_SYMBOL, 219), + (41, HUFFMAN_EMIT_SYMBOL, 219), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 219), + + # Node 202 + (3, HUFFMAN_EMIT_SYMBOL, 238), + (6, HUFFMAN_EMIT_SYMBOL, 238), + (10, HUFFMAN_EMIT_SYMBOL, 238), + (15, HUFFMAN_EMIT_SYMBOL, 238), + (24, HUFFMAN_EMIT_SYMBOL, 238), + (31, HUFFMAN_EMIT_SYMBOL, 238), + (41, HUFFMAN_EMIT_SYMBOL, 238), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 238), + (3, HUFFMAN_EMIT_SYMBOL, 240), + (6, HUFFMAN_EMIT_SYMBOL, 240), + (10, HUFFMAN_EMIT_SYMBOL, 240), + (15, HUFFMAN_EMIT_SYMBOL, 240), + (24, HUFFMAN_EMIT_SYMBOL, 240), + (31, HUFFMAN_EMIT_SYMBOL, 240), + (41, HUFFMAN_EMIT_SYMBOL, 240), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 240), + + # Node 203 + (2, HUFFMAN_EMIT_SYMBOL, 242), + (9, HUFFMAN_EMIT_SYMBOL, 242), + (23, HUFFMAN_EMIT_SYMBOL, 242), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 242), + (2, HUFFMAN_EMIT_SYMBOL, 243), + (9, HUFFMAN_EMIT_SYMBOL, 243), + (23, HUFFMAN_EMIT_SYMBOL, 243), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 243), + (2, HUFFMAN_EMIT_SYMBOL, 255), + (9, HUFFMAN_EMIT_SYMBOL, 255), + (23, HUFFMAN_EMIT_SYMBOL, 255), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 255), + (1, HUFFMAN_EMIT_SYMBOL, 203), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 203), + (1, HUFFMAN_EMIT_SYMBOL, 204), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 204), + + # Node 204 + (3, HUFFMAN_EMIT_SYMBOL, 242), + (6, HUFFMAN_EMIT_SYMBOL, 242), + (10, HUFFMAN_EMIT_SYMBOL, 242), + (15, HUFFMAN_EMIT_SYMBOL, 242), + (24, HUFFMAN_EMIT_SYMBOL, 242), + (31, HUFFMAN_EMIT_SYMBOL, 242), + (41, HUFFMAN_EMIT_SYMBOL, 242), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 242), + (3, HUFFMAN_EMIT_SYMBOL, 243), + (6, HUFFMAN_EMIT_SYMBOL, 243), + (10, HUFFMAN_EMIT_SYMBOL, 243), + (15, HUFFMAN_EMIT_SYMBOL, 243), + (24, HUFFMAN_EMIT_SYMBOL, 243), + (31, HUFFMAN_EMIT_SYMBOL, 243), + (41, HUFFMAN_EMIT_SYMBOL, 243), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 243), + + # Node 205 + (3, HUFFMAN_EMIT_SYMBOL, 255), + (6, HUFFMAN_EMIT_SYMBOL, 255), + (10, HUFFMAN_EMIT_SYMBOL, 255), + (15, HUFFMAN_EMIT_SYMBOL, 255), + (24, HUFFMAN_EMIT_SYMBOL, 255), + (31, HUFFMAN_EMIT_SYMBOL, 255), + (41, HUFFMAN_EMIT_SYMBOL, 255), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 255), + (2, HUFFMAN_EMIT_SYMBOL, 203), + (9, HUFFMAN_EMIT_SYMBOL, 203), + (23, HUFFMAN_EMIT_SYMBOL, 203), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 203), + (2, HUFFMAN_EMIT_SYMBOL, 204), + (9, HUFFMAN_EMIT_SYMBOL, 204), + (23, HUFFMAN_EMIT_SYMBOL, 204), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 204), + + # Node 206 + (3, HUFFMAN_EMIT_SYMBOL, 203), + (6, HUFFMAN_EMIT_SYMBOL, 203), + (10, HUFFMAN_EMIT_SYMBOL, 203), + (15, HUFFMAN_EMIT_SYMBOL, 203), + (24, HUFFMAN_EMIT_SYMBOL, 203), + (31, HUFFMAN_EMIT_SYMBOL, 203), + (41, HUFFMAN_EMIT_SYMBOL, 203), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 203), + (3, HUFFMAN_EMIT_SYMBOL, 204), + (6, HUFFMAN_EMIT_SYMBOL, 204), + (10, HUFFMAN_EMIT_SYMBOL, 204), + (15, HUFFMAN_EMIT_SYMBOL, 204), + (24, HUFFMAN_EMIT_SYMBOL, 204), + (31, HUFFMAN_EMIT_SYMBOL, 204), + (41, HUFFMAN_EMIT_SYMBOL, 204), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 204), + + # Node 207 + (211, 0, 0), + (212, 0, 0), + (214, 0, 0), + (215, 0, 0), + (218, 0, 0), + (219, 0, 0), + (221, 0, 0), + (222, 0, 0), + (226, 0, 0), + (228, 0, 0), + (232, 0, 0), + (235, 0, 0), + (240, 0, 0), + (243, 0, 0), + (247, 0, 0), + (250, 0, 0), + + # Node 208 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 211), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 212), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 214), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 221), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 222), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 223), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 241), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 244), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 245), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 246), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 247), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 248), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 250), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 251), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 252), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 253), + + # Node 209 + (1, HUFFMAN_EMIT_SYMBOL, 211), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 211), + (1, HUFFMAN_EMIT_SYMBOL, 212), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 212), + (1, HUFFMAN_EMIT_SYMBOL, 214), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 214), + (1, HUFFMAN_EMIT_SYMBOL, 221), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 221), + (1, HUFFMAN_EMIT_SYMBOL, 222), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 222), + (1, HUFFMAN_EMIT_SYMBOL, 223), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 223), + (1, HUFFMAN_EMIT_SYMBOL, 241), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 241), + (1, HUFFMAN_EMIT_SYMBOL, 244), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 244), + + # Node 210 + (2, HUFFMAN_EMIT_SYMBOL, 211), + (9, HUFFMAN_EMIT_SYMBOL, 211), + (23, HUFFMAN_EMIT_SYMBOL, 211), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 211), + (2, HUFFMAN_EMIT_SYMBOL, 212), + (9, HUFFMAN_EMIT_SYMBOL, 212), + (23, HUFFMAN_EMIT_SYMBOL, 212), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 212), + (2, HUFFMAN_EMIT_SYMBOL, 214), + (9, HUFFMAN_EMIT_SYMBOL, 214), + (23, HUFFMAN_EMIT_SYMBOL, 214), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 214), + (2, HUFFMAN_EMIT_SYMBOL, 221), + (9, HUFFMAN_EMIT_SYMBOL, 221), + (23, HUFFMAN_EMIT_SYMBOL, 221), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 221), + + # Node 211 + (3, HUFFMAN_EMIT_SYMBOL, 211), + (6, HUFFMAN_EMIT_SYMBOL, 211), + (10, HUFFMAN_EMIT_SYMBOL, 211), + (15, HUFFMAN_EMIT_SYMBOL, 211), + (24, HUFFMAN_EMIT_SYMBOL, 211), + (31, HUFFMAN_EMIT_SYMBOL, 211), + (41, HUFFMAN_EMIT_SYMBOL, 211), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 211), + (3, HUFFMAN_EMIT_SYMBOL, 212), + (6, HUFFMAN_EMIT_SYMBOL, 212), + (10, HUFFMAN_EMIT_SYMBOL, 212), + (15, HUFFMAN_EMIT_SYMBOL, 212), + (24, HUFFMAN_EMIT_SYMBOL, 212), + (31, HUFFMAN_EMIT_SYMBOL, 212), + (41, HUFFMAN_EMIT_SYMBOL, 212), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 212), + + # Node 212 + (3, HUFFMAN_EMIT_SYMBOL, 214), + (6, HUFFMAN_EMIT_SYMBOL, 214), + (10, HUFFMAN_EMIT_SYMBOL, 214), + (15, HUFFMAN_EMIT_SYMBOL, 214), + (24, HUFFMAN_EMIT_SYMBOL, 214), + (31, HUFFMAN_EMIT_SYMBOL, 214), + (41, HUFFMAN_EMIT_SYMBOL, 214), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 214), + (3, HUFFMAN_EMIT_SYMBOL, 221), + (6, HUFFMAN_EMIT_SYMBOL, 221), + (10, HUFFMAN_EMIT_SYMBOL, 221), + (15, HUFFMAN_EMIT_SYMBOL, 221), + (24, HUFFMAN_EMIT_SYMBOL, 221), + (31, HUFFMAN_EMIT_SYMBOL, 221), + (41, HUFFMAN_EMIT_SYMBOL, 221), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 221), + + # Node 213 + (2, HUFFMAN_EMIT_SYMBOL, 222), + (9, HUFFMAN_EMIT_SYMBOL, 222), + (23, HUFFMAN_EMIT_SYMBOL, 222), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 222), + (2, HUFFMAN_EMIT_SYMBOL, 223), + (9, HUFFMAN_EMIT_SYMBOL, 223), + (23, HUFFMAN_EMIT_SYMBOL, 223), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 223), + (2, HUFFMAN_EMIT_SYMBOL, 241), + (9, HUFFMAN_EMIT_SYMBOL, 241), + (23, HUFFMAN_EMIT_SYMBOL, 241), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 241), + (2, HUFFMAN_EMIT_SYMBOL, 244), + (9, HUFFMAN_EMIT_SYMBOL, 244), + (23, HUFFMAN_EMIT_SYMBOL, 244), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 244), + + # Node 214 + (3, HUFFMAN_EMIT_SYMBOL, 222), + (6, HUFFMAN_EMIT_SYMBOL, 222), + (10, HUFFMAN_EMIT_SYMBOL, 222), + (15, HUFFMAN_EMIT_SYMBOL, 222), + (24, HUFFMAN_EMIT_SYMBOL, 222), + (31, HUFFMAN_EMIT_SYMBOL, 222), + (41, HUFFMAN_EMIT_SYMBOL, 222), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 222), + (3, HUFFMAN_EMIT_SYMBOL, 223), + (6, HUFFMAN_EMIT_SYMBOL, 223), + (10, HUFFMAN_EMIT_SYMBOL, 223), + (15, HUFFMAN_EMIT_SYMBOL, 223), + (24, HUFFMAN_EMIT_SYMBOL, 223), + (31, HUFFMAN_EMIT_SYMBOL, 223), + (41, HUFFMAN_EMIT_SYMBOL, 223), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 223), + + # Node 215 + (3, HUFFMAN_EMIT_SYMBOL, 241), + (6, HUFFMAN_EMIT_SYMBOL, 241), + (10, HUFFMAN_EMIT_SYMBOL, 241), + (15, HUFFMAN_EMIT_SYMBOL, 241), + (24, HUFFMAN_EMIT_SYMBOL, 241), + (31, HUFFMAN_EMIT_SYMBOL, 241), + (41, HUFFMAN_EMIT_SYMBOL, 241), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 241), + (3, HUFFMAN_EMIT_SYMBOL, 244), + (6, HUFFMAN_EMIT_SYMBOL, 244), + (10, HUFFMAN_EMIT_SYMBOL, 244), + (15, HUFFMAN_EMIT_SYMBOL, 244), + (24, HUFFMAN_EMIT_SYMBOL, 244), + (31, HUFFMAN_EMIT_SYMBOL, 244), + (41, HUFFMAN_EMIT_SYMBOL, 244), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 244), + + # Node 216 + (1, HUFFMAN_EMIT_SYMBOL, 245), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 245), + (1, HUFFMAN_EMIT_SYMBOL, 246), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 246), + (1, HUFFMAN_EMIT_SYMBOL, 247), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 247), + (1, HUFFMAN_EMIT_SYMBOL, 248), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 248), + (1, HUFFMAN_EMIT_SYMBOL, 250), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 250), + (1, HUFFMAN_EMIT_SYMBOL, 251), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 251), + (1, HUFFMAN_EMIT_SYMBOL, 252), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 252), + (1, HUFFMAN_EMIT_SYMBOL, 253), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 253), + + # Node 217 + (2, HUFFMAN_EMIT_SYMBOL, 245), + (9, HUFFMAN_EMIT_SYMBOL, 245), + (23, HUFFMAN_EMIT_SYMBOL, 245), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 245), + (2, HUFFMAN_EMIT_SYMBOL, 246), + (9, HUFFMAN_EMIT_SYMBOL, 246), + (23, HUFFMAN_EMIT_SYMBOL, 246), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 246), + (2, HUFFMAN_EMIT_SYMBOL, 247), + (9, HUFFMAN_EMIT_SYMBOL, 247), + (23, HUFFMAN_EMIT_SYMBOL, 247), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 247), + (2, HUFFMAN_EMIT_SYMBOL, 248), + (9, HUFFMAN_EMIT_SYMBOL, 248), + (23, HUFFMAN_EMIT_SYMBOL, 248), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 248), + + # Node 218 + (3, HUFFMAN_EMIT_SYMBOL, 245), + (6, HUFFMAN_EMIT_SYMBOL, 245), + (10, HUFFMAN_EMIT_SYMBOL, 245), + (15, HUFFMAN_EMIT_SYMBOL, 245), + (24, HUFFMAN_EMIT_SYMBOL, 245), + (31, HUFFMAN_EMIT_SYMBOL, 245), + (41, HUFFMAN_EMIT_SYMBOL, 245), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 245), + (3, HUFFMAN_EMIT_SYMBOL, 246), + (6, HUFFMAN_EMIT_SYMBOL, 246), + (10, HUFFMAN_EMIT_SYMBOL, 246), + (15, HUFFMAN_EMIT_SYMBOL, 246), + (24, HUFFMAN_EMIT_SYMBOL, 246), + (31, HUFFMAN_EMIT_SYMBOL, 246), + (41, HUFFMAN_EMIT_SYMBOL, 246), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 246), + + # Node 219 + (3, HUFFMAN_EMIT_SYMBOL, 247), + (6, HUFFMAN_EMIT_SYMBOL, 247), + (10, HUFFMAN_EMIT_SYMBOL, 247), + (15, HUFFMAN_EMIT_SYMBOL, 247), + (24, HUFFMAN_EMIT_SYMBOL, 247), + (31, HUFFMAN_EMIT_SYMBOL, 247), + (41, HUFFMAN_EMIT_SYMBOL, 247), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 247), + (3, HUFFMAN_EMIT_SYMBOL, 248), + (6, HUFFMAN_EMIT_SYMBOL, 248), + (10, HUFFMAN_EMIT_SYMBOL, 248), + (15, HUFFMAN_EMIT_SYMBOL, 248), + (24, HUFFMAN_EMIT_SYMBOL, 248), + (31, HUFFMAN_EMIT_SYMBOL, 248), + (41, HUFFMAN_EMIT_SYMBOL, 248), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 248), + + # Node 220 + (2, HUFFMAN_EMIT_SYMBOL, 250), + (9, HUFFMAN_EMIT_SYMBOL, 250), + (23, HUFFMAN_EMIT_SYMBOL, 250), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 250), + (2, HUFFMAN_EMIT_SYMBOL, 251), + (9, HUFFMAN_EMIT_SYMBOL, 251), + (23, HUFFMAN_EMIT_SYMBOL, 251), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 251), + (2, HUFFMAN_EMIT_SYMBOL, 252), + (9, HUFFMAN_EMIT_SYMBOL, 252), + (23, HUFFMAN_EMIT_SYMBOL, 252), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 252), + (2, HUFFMAN_EMIT_SYMBOL, 253), + (9, HUFFMAN_EMIT_SYMBOL, 253), + (23, HUFFMAN_EMIT_SYMBOL, 253), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 253), + + # Node 221 + (3, HUFFMAN_EMIT_SYMBOL, 250), + (6, HUFFMAN_EMIT_SYMBOL, 250), + (10, HUFFMAN_EMIT_SYMBOL, 250), + (15, HUFFMAN_EMIT_SYMBOL, 250), + (24, HUFFMAN_EMIT_SYMBOL, 250), + (31, HUFFMAN_EMIT_SYMBOL, 250), + (41, HUFFMAN_EMIT_SYMBOL, 250), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 250), + (3, HUFFMAN_EMIT_SYMBOL, 251), + (6, HUFFMAN_EMIT_SYMBOL, 251), + (10, HUFFMAN_EMIT_SYMBOL, 251), + (15, HUFFMAN_EMIT_SYMBOL, 251), + (24, HUFFMAN_EMIT_SYMBOL, 251), + (31, HUFFMAN_EMIT_SYMBOL, 251), + (41, HUFFMAN_EMIT_SYMBOL, 251), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 251), + + # Node 222 + (3, HUFFMAN_EMIT_SYMBOL, 252), + (6, HUFFMAN_EMIT_SYMBOL, 252), + (10, HUFFMAN_EMIT_SYMBOL, 252), + (15, HUFFMAN_EMIT_SYMBOL, 252), + (24, HUFFMAN_EMIT_SYMBOL, 252), + (31, HUFFMAN_EMIT_SYMBOL, 252), + (41, HUFFMAN_EMIT_SYMBOL, 252), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 252), + (3, HUFFMAN_EMIT_SYMBOL, 253), + (6, HUFFMAN_EMIT_SYMBOL, 253), + (10, HUFFMAN_EMIT_SYMBOL, 253), + (15, HUFFMAN_EMIT_SYMBOL, 253), + (24, HUFFMAN_EMIT_SYMBOL, 253), + (31, HUFFMAN_EMIT_SYMBOL, 253), + (41, HUFFMAN_EMIT_SYMBOL, 253), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 253), + + # Node 223 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 254), + (227, 0, 0), + (229, 0, 0), + (230, 0, 0), + (233, 0, 0), + (234, 0, 0), + (236, 0, 0), + (237, 0, 0), + (241, 0, 0), + (242, 0, 0), + (244, 0, 0), + (245, 0, 0), + (248, 0, 0), + (249, 0, 0), + (251, 0, 0), + (252, 0, 0), + + # Node 224 + (1, HUFFMAN_EMIT_SYMBOL, 254), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 254), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 2), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 3), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 4), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 5), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 6), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 7), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 8), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 11), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 12), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 14), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 15), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 16), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 17), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 18), + + # Node 225 + (2, HUFFMAN_EMIT_SYMBOL, 254), + (9, HUFFMAN_EMIT_SYMBOL, 254), + (23, HUFFMAN_EMIT_SYMBOL, 254), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 254), + (1, HUFFMAN_EMIT_SYMBOL, 2), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 2), + (1, HUFFMAN_EMIT_SYMBOL, 3), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 3), + (1, HUFFMAN_EMIT_SYMBOL, 4), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 4), + (1, HUFFMAN_EMIT_SYMBOL, 5), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 5), + (1, HUFFMAN_EMIT_SYMBOL, 6), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 6), + (1, HUFFMAN_EMIT_SYMBOL, 7), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 7), + + # Node 226 + (3, HUFFMAN_EMIT_SYMBOL, 254), + (6, HUFFMAN_EMIT_SYMBOL, 254), + (10, HUFFMAN_EMIT_SYMBOL, 254), + (15, HUFFMAN_EMIT_SYMBOL, 254), + (24, HUFFMAN_EMIT_SYMBOL, 254), + (31, HUFFMAN_EMIT_SYMBOL, 254), + (41, HUFFMAN_EMIT_SYMBOL, 254), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 254), + (2, HUFFMAN_EMIT_SYMBOL, 2), + (9, HUFFMAN_EMIT_SYMBOL, 2), + (23, HUFFMAN_EMIT_SYMBOL, 2), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 2), + (2, HUFFMAN_EMIT_SYMBOL, 3), + (9, HUFFMAN_EMIT_SYMBOL, 3), + (23, HUFFMAN_EMIT_SYMBOL, 3), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 3), + + # Node 227 + (3, HUFFMAN_EMIT_SYMBOL, 2), + (6, HUFFMAN_EMIT_SYMBOL, 2), + (10, HUFFMAN_EMIT_SYMBOL, 2), + (15, HUFFMAN_EMIT_SYMBOL, 2), + (24, HUFFMAN_EMIT_SYMBOL, 2), + (31, HUFFMAN_EMIT_SYMBOL, 2), + (41, HUFFMAN_EMIT_SYMBOL, 2), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 2), + (3, HUFFMAN_EMIT_SYMBOL, 3), + (6, HUFFMAN_EMIT_SYMBOL, 3), + (10, HUFFMAN_EMIT_SYMBOL, 3), + (15, HUFFMAN_EMIT_SYMBOL, 3), + (24, HUFFMAN_EMIT_SYMBOL, 3), + (31, HUFFMAN_EMIT_SYMBOL, 3), + (41, HUFFMAN_EMIT_SYMBOL, 3), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 3), + + # Node 228 + (2, HUFFMAN_EMIT_SYMBOL, 4), + (9, HUFFMAN_EMIT_SYMBOL, 4), + (23, HUFFMAN_EMIT_SYMBOL, 4), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 4), + (2, HUFFMAN_EMIT_SYMBOL, 5), + (9, HUFFMAN_EMIT_SYMBOL, 5), + (23, HUFFMAN_EMIT_SYMBOL, 5), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 5), + (2, HUFFMAN_EMIT_SYMBOL, 6), + (9, HUFFMAN_EMIT_SYMBOL, 6), + (23, HUFFMAN_EMIT_SYMBOL, 6), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 6), + (2, HUFFMAN_EMIT_SYMBOL, 7), + (9, HUFFMAN_EMIT_SYMBOL, 7), + (23, HUFFMAN_EMIT_SYMBOL, 7), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 7), + + # Node 229 + (3, HUFFMAN_EMIT_SYMBOL, 4), + (6, HUFFMAN_EMIT_SYMBOL, 4), + (10, HUFFMAN_EMIT_SYMBOL, 4), + (15, HUFFMAN_EMIT_SYMBOL, 4), + (24, HUFFMAN_EMIT_SYMBOL, 4), + (31, HUFFMAN_EMIT_SYMBOL, 4), + (41, HUFFMAN_EMIT_SYMBOL, 4), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 4), + (3, HUFFMAN_EMIT_SYMBOL, 5), + (6, HUFFMAN_EMIT_SYMBOL, 5), + (10, HUFFMAN_EMIT_SYMBOL, 5), + (15, HUFFMAN_EMIT_SYMBOL, 5), + (24, HUFFMAN_EMIT_SYMBOL, 5), + (31, HUFFMAN_EMIT_SYMBOL, 5), + (41, HUFFMAN_EMIT_SYMBOL, 5), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 5), + + # Node 230 + (3, HUFFMAN_EMIT_SYMBOL, 6), + (6, HUFFMAN_EMIT_SYMBOL, 6), + (10, HUFFMAN_EMIT_SYMBOL, 6), + (15, HUFFMAN_EMIT_SYMBOL, 6), + (24, HUFFMAN_EMIT_SYMBOL, 6), + (31, HUFFMAN_EMIT_SYMBOL, 6), + (41, HUFFMAN_EMIT_SYMBOL, 6), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 6), + (3, HUFFMAN_EMIT_SYMBOL, 7), + (6, HUFFMAN_EMIT_SYMBOL, 7), + (10, HUFFMAN_EMIT_SYMBOL, 7), + (15, HUFFMAN_EMIT_SYMBOL, 7), + (24, HUFFMAN_EMIT_SYMBOL, 7), + (31, HUFFMAN_EMIT_SYMBOL, 7), + (41, HUFFMAN_EMIT_SYMBOL, 7), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 7), + + # Node 231 + (1, HUFFMAN_EMIT_SYMBOL, 8), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 8), + (1, HUFFMAN_EMIT_SYMBOL, 11), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 11), + (1, HUFFMAN_EMIT_SYMBOL, 12), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 12), + (1, HUFFMAN_EMIT_SYMBOL, 14), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 14), + (1, HUFFMAN_EMIT_SYMBOL, 15), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 15), + (1, HUFFMAN_EMIT_SYMBOL, 16), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 16), + (1, HUFFMAN_EMIT_SYMBOL, 17), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 17), + (1, HUFFMAN_EMIT_SYMBOL, 18), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 18), + + # Node 232 + (2, HUFFMAN_EMIT_SYMBOL, 8), + (9, HUFFMAN_EMIT_SYMBOL, 8), + (23, HUFFMAN_EMIT_SYMBOL, 8), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 8), + (2, HUFFMAN_EMIT_SYMBOL, 11), + (9, HUFFMAN_EMIT_SYMBOL, 11), + (23, HUFFMAN_EMIT_SYMBOL, 11), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 11), + (2, HUFFMAN_EMIT_SYMBOL, 12), + (9, HUFFMAN_EMIT_SYMBOL, 12), + (23, HUFFMAN_EMIT_SYMBOL, 12), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 12), + (2, HUFFMAN_EMIT_SYMBOL, 14), + (9, HUFFMAN_EMIT_SYMBOL, 14), + (23, HUFFMAN_EMIT_SYMBOL, 14), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 14), + + # Node 233 + (3, HUFFMAN_EMIT_SYMBOL, 8), + (6, HUFFMAN_EMIT_SYMBOL, 8), + (10, HUFFMAN_EMIT_SYMBOL, 8), + (15, HUFFMAN_EMIT_SYMBOL, 8), + (24, HUFFMAN_EMIT_SYMBOL, 8), + (31, HUFFMAN_EMIT_SYMBOL, 8), + (41, HUFFMAN_EMIT_SYMBOL, 8), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 8), + (3, HUFFMAN_EMIT_SYMBOL, 11), + (6, HUFFMAN_EMIT_SYMBOL, 11), + (10, HUFFMAN_EMIT_SYMBOL, 11), + (15, HUFFMAN_EMIT_SYMBOL, 11), + (24, HUFFMAN_EMIT_SYMBOL, 11), + (31, HUFFMAN_EMIT_SYMBOL, 11), + (41, HUFFMAN_EMIT_SYMBOL, 11), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 11), + + # Node 234 + (3, HUFFMAN_EMIT_SYMBOL, 12), + (6, HUFFMAN_EMIT_SYMBOL, 12), + (10, HUFFMAN_EMIT_SYMBOL, 12), + (15, HUFFMAN_EMIT_SYMBOL, 12), + (24, HUFFMAN_EMIT_SYMBOL, 12), + (31, HUFFMAN_EMIT_SYMBOL, 12), + (41, HUFFMAN_EMIT_SYMBOL, 12), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 12), + (3, HUFFMAN_EMIT_SYMBOL, 14), + (6, HUFFMAN_EMIT_SYMBOL, 14), + (10, HUFFMAN_EMIT_SYMBOL, 14), + (15, HUFFMAN_EMIT_SYMBOL, 14), + (24, HUFFMAN_EMIT_SYMBOL, 14), + (31, HUFFMAN_EMIT_SYMBOL, 14), + (41, HUFFMAN_EMIT_SYMBOL, 14), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 14), + + # Node 235 + (2, HUFFMAN_EMIT_SYMBOL, 15), + (9, HUFFMAN_EMIT_SYMBOL, 15), + (23, HUFFMAN_EMIT_SYMBOL, 15), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 15), + (2, HUFFMAN_EMIT_SYMBOL, 16), + (9, HUFFMAN_EMIT_SYMBOL, 16), + (23, HUFFMAN_EMIT_SYMBOL, 16), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 16), + (2, HUFFMAN_EMIT_SYMBOL, 17), + (9, HUFFMAN_EMIT_SYMBOL, 17), + (23, HUFFMAN_EMIT_SYMBOL, 17), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 17), + (2, HUFFMAN_EMIT_SYMBOL, 18), + (9, HUFFMAN_EMIT_SYMBOL, 18), + (23, HUFFMAN_EMIT_SYMBOL, 18), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 18), + + # Node 236 + (3, HUFFMAN_EMIT_SYMBOL, 15), + (6, HUFFMAN_EMIT_SYMBOL, 15), + (10, HUFFMAN_EMIT_SYMBOL, 15), + (15, HUFFMAN_EMIT_SYMBOL, 15), + (24, HUFFMAN_EMIT_SYMBOL, 15), + (31, HUFFMAN_EMIT_SYMBOL, 15), + (41, HUFFMAN_EMIT_SYMBOL, 15), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 15), + (3, HUFFMAN_EMIT_SYMBOL, 16), + (6, HUFFMAN_EMIT_SYMBOL, 16), + (10, HUFFMAN_EMIT_SYMBOL, 16), + (15, HUFFMAN_EMIT_SYMBOL, 16), + (24, HUFFMAN_EMIT_SYMBOL, 16), + (31, HUFFMAN_EMIT_SYMBOL, 16), + (41, HUFFMAN_EMIT_SYMBOL, 16), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 16), + + # Node 237 + (3, HUFFMAN_EMIT_SYMBOL, 17), + (6, HUFFMAN_EMIT_SYMBOL, 17), + (10, HUFFMAN_EMIT_SYMBOL, 17), + (15, HUFFMAN_EMIT_SYMBOL, 17), + (24, HUFFMAN_EMIT_SYMBOL, 17), + (31, HUFFMAN_EMIT_SYMBOL, 17), + (41, HUFFMAN_EMIT_SYMBOL, 17), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 17), + (3, HUFFMAN_EMIT_SYMBOL, 18), + (6, HUFFMAN_EMIT_SYMBOL, 18), + (10, HUFFMAN_EMIT_SYMBOL, 18), + (15, HUFFMAN_EMIT_SYMBOL, 18), + (24, HUFFMAN_EMIT_SYMBOL, 18), + (31, HUFFMAN_EMIT_SYMBOL, 18), + (41, HUFFMAN_EMIT_SYMBOL, 18), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 18), + + # Node 238 + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 19), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 20), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 21), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 23), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 24), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 25), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 26), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 27), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 28), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 29), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 30), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 31), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 127), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 220), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 249), + (253, 0, 0), + + # Node 239 + (1, HUFFMAN_EMIT_SYMBOL, 19), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 19), + (1, HUFFMAN_EMIT_SYMBOL, 20), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 20), + (1, HUFFMAN_EMIT_SYMBOL, 21), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 21), + (1, HUFFMAN_EMIT_SYMBOL, 23), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 23), + (1, HUFFMAN_EMIT_SYMBOL, 24), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 24), + (1, HUFFMAN_EMIT_SYMBOL, 25), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 25), + (1, HUFFMAN_EMIT_SYMBOL, 26), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 26), + (1, HUFFMAN_EMIT_SYMBOL, 27), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 27), + + # Node 240 + (2, HUFFMAN_EMIT_SYMBOL, 19), + (9, HUFFMAN_EMIT_SYMBOL, 19), + (23, HUFFMAN_EMIT_SYMBOL, 19), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 19), + (2, HUFFMAN_EMIT_SYMBOL, 20), + (9, HUFFMAN_EMIT_SYMBOL, 20), + (23, HUFFMAN_EMIT_SYMBOL, 20), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 20), + (2, HUFFMAN_EMIT_SYMBOL, 21), + (9, HUFFMAN_EMIT_SYMBOL, 21), + (23, HUFFMAN_EMIT_SYMBOL, 21), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 21), + (2, HUFFMAN_EMIT_SYMBOL, 23), + (9, HUFFMAN_EMIT_SYMBOL, 23), + (23, HUFFMAN_EMIT_SYMBOL, 23), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 23), + + # Node 241 + (3, HUFFMAN_EMIT_SYMBOL, 19), + (6, HUFFMAN_EMIT_SYMBOL, 19), + (10, HUFFMAN_EMIT_SYMBOL, 19), + (15, HUFFMAN_EMIT_SYMBOL, 19), + (24, HUFFMAN_EMIT_SYMBOL, 19), + (31, HUFFMAN_EMIT_SYMBOL, 19), + (41, HUFFMAN_EMIT_SYMBOL, 19), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 19), + (3, HUFFMAN_EMIT_SYMBOL, 20), + (6, HUFFMAN_EMIT_SYMBOL, 20), + (10, HUFFMAN_EMIT_SYMBOL, 20), + (15, HUFFMAN_EMIT_SYMBOL, 20), + (24, HUFFMAN_EMIT_SYMBOL, 20), + (31, HUFFMAN_EMIT_SYMBOL, 20), + (41, HUFFMAN_EMIT_SYMBOL, 20), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 20), + + # Node 242 + (3, HUFFMAN_EMIT_SYMBOL, 21), + (6, HUFFMAN_EMIT_SYMBOL, 21), + (10, HUFFMAN_EMIT_SYMBOL, 21), + (15, HUFFMAN_EMIT_SYMBOL, 21), + (24, HUFFMAN_EMIT_SYMBOL, 21), + (31, HUFFMAN_EMIT_SYMBOL, 21), + (41, HUFFMAN_EMIT_SYMBOL, 21), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 21), + (3, HUFFMAN_EMIT_SYMBOL, 23), + (6, HUFFMAN_EMIT_SYMBOL, 23), + (10, HUFFMAN_EMIT_SYMBOL, 23), + (15, HUFFMAN_EMIT_SYMBOL, 23), + (24, HUFFMAN_EMIT_SYMBOL, 23), + (31, HUFFMAN_EMIT_SYMBOL, 23), + (41, HUFFMAN_EMIT_SYMBOL, 23), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 23), + + # Node 243 + (2, HUFFMAN_EMIT_SYMBOL, 24), + (9, HUFFMAN_EMIT_SYMBOL, 24), + (23, HUFFMAN_EMIT_SYMBOL, 24), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 24), + (2, HUFFMAN_EMIT_SYMBOL, 25), + (9, HUFFMAN_EMIT_SYMBOL, 25), + (23, HUFFMAN_EMIT_SYMBOL, 25), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 25), + (2, HUFFMAN_EMIT_SYMBOL, 26), + (9, HUFFMAN_EMIT_SYMBOL, 26), + (23, HUFFMAN_EMIT_SYMBOL, 26), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 26), + (2, HUFFMAN_EMIT_SYMBOL, 27), + (9, HUFFMAN_EMIT_SYMBOL, 27), + (23, HUFFMAN_EMIT_SYMBOL, 27), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 27), + + # Node 244 + (3, HUFFMAN_EMIT_SYMBOL, 24), + (6, HUFFMAN_EMIT_SYMBOL, 24), + (10, HUFFMAN_EMIT_SYMBOL, 24), + (15, HUFFMAN_EMIT_SYMBOL, 24), + (24, HUFFMAN_EMIT_SYMBOL, 24), + (31, HUFFMAN_EMIT_SYMBOL, 24), + (41, HUFFMAN_EMIT_SYMBOL, 24), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 24), + (3, HUFFMAN_EMIT_SYMBOL, 25), + (6, HUFFMAN_EMIT_SYMBOL, 25), + (10, HUFFMAN_EMIT_SYMBOL, 25), + (15, HUFFMAN_EMIT_SYMBOL, 25), + (24, HUFFMAN_EMIT_SYMBOL, 25), + (31, HUFFMAN_EMIT_SYMBOL, 25), + (41, HUFFMAN_EMIT_SYMBOL, 25), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 25), + + # Node 245 + (3, HUFFMAN_EMIT_SYMBOL, 26), + (6, HUFFMAN_EMIT_SYMBOL, 26), + (10, HUFFMAN_EMIT_SYMBOL, 26), + (15, HUFFMAN_EMIT_SYMBOL, 26), + (24, HUFFMAN_EMIT_SYMBOL, 26), + (31, HUFFMAN_EMIT_SYMBOL, 26), + (41, HUFFMAN_EMIT_SYMBOL, 26), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 26), + (3, HUFFMAN_EMIT_SYMBOL, 27), + (6, HUFFMAN_EMIT_SYMBOL, 27), + (10, HUFFMAN_EMIT_SYMBOL, 27), + (15, HUFFMAN_EMIT_SYMBOL, 27), + (24, HUFFMAN_EMIT_SYMBOL, 27), + (31, HUFFMAN_EMIT_SYMBOL, 27), + (41, HUFFMAN_EMIT_SYMBOL, 27), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 27), + + # Node 246 + (1, HUFFMAN_EMIT_SYMBOL, 28), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 28), + (1, HUFFMAN_EMIT_SYMBOL, 29), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 29), + (1, HUFFMAN_EMIT_SYMBOL, 30), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 30), + (1, HUFFMAN_EMIT_SYMBOL, 31), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 31), + (1, HUFFMAN_EMIT_SYMBOL, 127), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 127), + (1, HUFFMAN_EMIT_SYMBOL, 220), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 220), + (1, HUFFMAN_EMIT_SYMBOL, 249), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 249), + (254, 0, 0), + (255, 0, 0), + + # Node 247 + (2, HUFFMAN_EMIT_SYMBOL, 28), + (9, HUFFMAN_EMIT_SYMBOL, 28), + (23, HUFFMAN_EMIT_SYMBOL, 28), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 28), + (2, HUFFMAN_EMIT_SYMBOL, 29), + (9, HUFFMAN_EMIT_SYMBOL, 29), + (23, HUFFMAN_EMIT_SYMBOL, 29), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 29), + (2, HUFFMAN_EMIT_SYMBOL, 30), + (9, HUFFMAN_EMIT_SYMBOL, 30), + (23, HUFFMAN_EMIT_SYMBOL, 30), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 30), + (2, HUFFMAN_EMIT_SYMBOL, 31), + (9, HUFFMAN_EMIT_SYMBOL, 31), + (23, HUFFMAN_EMIT_SYMBOL, 31), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 31), + + # Node 248 + (3, HUFFMAN_EMIT_SYMBOL, 28), + (6, HUFFMAN_EMIT_SYMBOL, 28), + (10, HUFFMAN_EMIT_SYMBOL, 28), + (15, HUFFMAN_EMIT_SYMBOL, 28), + (24, HUFFMAN_EMIT_SYMBOL, 28), + (31, HUFFMAN_EMIT_SYMBOL, 28), + (41, HUFFMAN_EMIT_SYMBOL, 28), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 28), + (3, HUFFMAN_EMIT_SYMBOL, 29), + (6, HUFFMAN_EMIT_SYMBOL, 29), + (10, HUFFMAN_EMIT_SYMBOL, 29), + (15, HUFFMAN_EMIT_SYMBOL, 29), + (24, HUFFMAN_EMIT_SYMBOL, 29), + (31, HUFFMAN_EMIT_SYMBOL, 29), + (41, HUFFMAN_EMIT_SYMBOL, 29), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 29), + + # Node 249 + (3, HUFFMAN_EMIT_SYMBOL, 30), + (6, HUFFMAN_EMIT_SYMBOL, 30), + (10, HUFFMAN_EMIT_SYMBOL, 30), + (15, HUFFMAN_EMIT_SYMBOL, 30), + (24, HUFFMAN_EMIT_SYMBOL, 30), + (31, HUFFMAN_EMIT_SYMBOL, 30), + (41, HUFFMAN_EMIT_SYMBOL, 30), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 30), + (3, HUFFMAN_EMIT_SYMBOL, 31), + (6, HUFFMAN_EMIT_SYMBOL, 31), + (10, HUFFMAN_EMIT_SYMBOL, 31), + (15, HUFFMAN_EMIT_SYMBOL, 31), + (24, HUFFMAN_EMIT_SYMBOL, 31), + (31, HUFFMAN_EMIT_SYMBOL, 31), + (41, HUFFMAN_EMIT_SYMBOL, 31), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 31), + + # Node 250 + (2, HUFFMAN_EMIT_SYMBOL, 127), + (9, HUFFMAN_EMIT_SYMBOL, 127), + (23, HUFFMAN_EMIT_SYMBOL, 127), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 127), + (2, HUFFMAN_EMIT_SYMBOL, 220), + (9, HUFFMAN_EMIT_SYMBOL, 220), + (23, HUFFMAN_EMIT_SYMBOL, 220), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 220), + (2, HUFFMAN_EMIT_SYMBOL, 249), + (9, HUFFMAN_EMIT_SYMBOL, 249), + (23, HUFFMAN_EMIT_SYMBOL, 249), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 249), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 10), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 13), + (0, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 22), + (0, HUFFMAN_FAIL, 0), + + # Node 251 + (3, HUFFMAN_EMIT_SYMBOL, 127), + (6, HUFFMAN_EMIT_SYMBOL, 127), + (10, HUFFMAN_EMIT_SYMBOL, 127), + (15, HUFFMAN_EMIT_SYMBOL, 127), + (24, HUFFMAN_EMIT_SYMBOL, 127), + (31, HUFFMAN_EMIT_SYMBOL, 127), + (41, HUFFMAN_EMIT_SYMBOL, 127), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 127), + (3, HUFFMAN_EMIT_SYMBOL, 220), + (6, HUFFMAN_EMIT_SYMBOL, 220), + (10, HUFFMAN_EMIT_SYMBOL, 220), + (15, HUFFMAN_EMIT_SYMBOL, 220), + (24, HUFFMAN_EMIT_SYMBOL, 220), + (31, HUFFMAN_EMIT_SYMBOL, 220), + (41, HUFFMAN_EMIT_SYMBOL, 220), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 220), + + # Node 252 + (3, HUFFMAN_EMIT_SYMBOL, 249), + (6, HUFFMAN_EMIT_SYMBOL, 249), + (10, HUFFMAN_EMIT_SYMBOL, 249), + (15, HUFFMAN_EMIT_SYMBOL, 249), + (24, HUFFMAN_EMIT_SYMBOL, 249), + (31, HUFFMAN_EMIT_SYMBOL, 249), + (41, HUFFMAN_EMIT_SYMBOL, 249), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 249), + (1, HUFFMAN_EMIT_SYMBOL, 10), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 10), + (1, HUFFMAN_EMIT_SYMBOL, 13), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 13), + (1, HUFFMAN_EMIT_SYMBOL, 22), + (22, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 22), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), + + # Node 253 + (2, HUFFMAN_EMIT_SYMBOL, 10), + (9, HUFFMAN_EMIT_SYMBOL, 10), + (23, HUFFMAN_EMIT_SYMBOL, 10), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 10), + (2, HUFFMAN_EMIT_SYMBOL, 13), + (9, HUFFMAN_EMIT_SYMBOL, 13), + (23, HUFFMAN_EMIT_SYMBOL, 13), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 13), + (2, HUFFMAN_EMIT_SYMBOL, 22), + (9, HUFFMAN_EMIT_SYMBOL, 22), + (23, HUFFMAN_EMIT_SYMBOL, 22), + (40, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 22), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), + + # Node 254 + (3, HUFFMAN_EMIT_SYMBOL, 10), + (6, HUFFMAN_EMIT_SYMBOL, 10), + (10, HUFFMAN_EMIT_SYMBOL, 10), + (15, HUFFMAN_EMIT_SYMBOL, 10), + (24, HUFFMAN_EMIT_SYMBOL, 10), + (31, HUFFMAN_EMIT_SYMBOL, 10), + (41, HUFFMAN_EMIT_SYMBOL, 10), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 10), + (3, HUFFMAN_EMIT_SYMBOL, 13), + (6, HUFFMAN_EMIT_SYMBOL, 13), + (10, HUFFMAN_EMIT_SYMBOL, 13), + (15, HUFFMAN_EMIT_SYMBOL, 13), + (24, HUFFMAN_EMIT_SYMBOL, 13), + (31, HUFFMAN_EMIT_SYMBOL, 13), + (41, HUFFMAN_EMIT_SYMBOL, 13), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 13), + + # Node 255 + (3, HUFFMAN_EMIT_SYMBOL, 22), + (6, HUFFMAN_EMIT_SYMBOL, 22), + (10, HUFFMAN_EMIT_SYMBOL, 22), + (15, HUFFMAN_EMIT_SYMBOL, 22), + (24, HUFFMAN_EMIT_SYMBOL, 22), + (31, HUFFMAN_EMIT_SYMBOL, 22), + (41, HUFFMAN_EMIT_SYMBOL, 22), + (56, HUFFMAN_COMPLETE | HUFFMAN_EMIT_SYMBOL, 22), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), + (0, HUFFMAN_FAIL, 0), +] diff --git a/testing/web-platform/tests/tools/third_party/hpack/hpack/struct.py b/testing/web-platform/tests/tools/third_party/hpack/hpack/struct.py new file mode 100644 index 0000000000..e860cd756e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/hpack/struct.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +""" +hpack/struct +~~~~~~~~~~~~ + +Contains structures for representing header fields with associated metadata. +""" + + +class HeaderTuple(tuple): + """ + A data structure that stores a single header field. + + HTTP headers can be thought of as tuples of ``(field name, field value)``. + A single header block is a sequence of such tuples. + + In HTTP/2, however, certain bits of additional information are required for + compressing these headers: in particular, whether the header field can be + safely added to the HPACK compression context. + + This class stores a header that can be added to the compression context. In + all other ways it behaves exactly like a tuple. + """ + __slots__ = () + + indexable = True + + def __new__(_cls, *args): + return tuple.__new__(_cls, args) + + +class NeverIndexedHeaderTuple(HeaderTuple): + """ + A data structure that stores a single header field that cannot be added to + a HTTP/2 header compression context. + """ + __slots__ = () + + indexable = False diff --git a/testing/web-platform/tests/tools/third_party/hpack/hpack/table.py b/testing/web-platform/tests/tools/third_party/hpack/hpack/table.py new file mode 100644 index 0000000000..9a89c72118 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/hpack/table.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- +# flake8: noqa +from collections import deque +import logging + +from .exceptions import InvalidTableIndex + +log = logging.getLogger(__name__) + + +def table_entry_size(name, value): + """ + Calculates the size of a single entry + + This size is mostly irrelevant to us and defined + specifically to accommodate memory management for + lower level implementations. The 32 extra bytes are + considered the "maximum" overhead that would be + required to represent each entry in the table. + + See RFC7541 Section 4.1 + """ + return 32 + len(name) + len(value) + + +class HeaderTable(object): + """ + Implements the combined static and dynamic header table + + The name and value arguments for all the functions + should ONLY be byte strings (b'') however this is not + strictly enforced in the interface. + + See RFC7541 Section 2.3 + """ + #: Default maximum size of the dynamic table. See + #: RFC7540 Section 6.5.2. + DEFAULT_SIZE = 4096 + + #: Constant list of static headers. See RFC7541 Section + #: 2.3.1 and Appendix A + STATIC_TABLE = ( + (b':authority' , b'' ), # noqa + (b':method' , b'GET' ), # noqa + (b':method' , b'POST' ), # noqa + (b':path' , b'/' ), # noqa + (b':path' , b'/index.html' ), # noqa + (b':scheme' , b'http' ), # noqa + (b':scheme' , b'https' ), # noqa + (b':status' , b'200' ), # noqa + (b':status' , b'204' ), # noqa + (b':status' , b'206' ), # noqa + (b':status' , b'304' ), # noqa + (b':status' , b'400' ), # noqa + (b':status' , b'404' ), # noqa + (b':status' , b'500' ), # noqa + (b'accept-charset' , b'' ), # noqa + (b'accept-encoding' , b'gzip, deflate'), # noqa + (b'accept-language' , b'' ), # noqa + (b'accept-ranges' , b'' ), # noqa + (b'accept' , b'' ), # noqa + (b'access-control-allow-origin' , b'' ), # noqa + (b'age' , b'' ), # noqa + (b'allow' , b'' ), # noqa + (b'authorization' , b'' ), # noqa + (b'cache-control' , b'' ), # noqa + (b'content-disposition' , b'' ), # noqa + (b'content-encoding' , b'' ), # noqa + (b'content-language' , b'' ), # noqa + (b'content-length' , b'' ), # noqa + (b'content-location' , b'' ), # noqa + (b'content-range' , b'' ), # noqa + (b'content-type' , b'' ), # noqa + (b'cookie' , b'' ), # noqa + (b'date' , b'' ), # noqa + (b'etag' , b'' ), # noqa + (b'expect' , b'' ), # noqa + (b'expires' , b'' ), # noqa + (b'from' , b'' ), # noqa + (b'host' , b'' ), # noqa + (b'if-match' , b'' ), # noqa + (b'if-modified-since' , b'' ), # noqa + (b'if-none-match' , b'' ), # noqa + (b'if-range' , b'' ), # noqa + (b'if-unmodified-since' , b'' ), # noqa + (b'last-modified' , b'' ), # noqa + (b'link' , b'' ), # noqa + (b'location' , b'' ), # noqa + (b'max-forwards' , b'' ), # noqa + (b'proxy-authenticate' , b'' ), # noqa + (b'proxy-authorization' , b'' ), # noqa + (b'range' , b'' ), # noqa + (b'referer' , b'' ), # noqa + (b'refresh' , b'' ), # noqa + (b'retry-after' , b'' ), # noqa + (b'server' , b'' ), # noqa + (b'set-cookie' , b'' ), # noqa + (b'strict-transport-security' , b'' ), # noqa + (b'transfer-encoding' , b'' ), # noqa + (b'user-agent' , b'' ), # noqa + (b'vary' , b'' ), # noqa + (b'via' , b'' ), # noqa + (b'www-authenticate' , b'' ), # noqa + ) # noqa + + STATIC_TABLE_LENGTH = len(STATIC_TABLE) + + def __init__(self): + self._maxsize = HeaderTable.DEFAULT_SIZE + self._current_size = 0 + self.resized = False + self.dynamic_entries = deque() + + def get_by_index(self, index): + """ + Returns the entry specified by index + + Note that the table is 1-based ie an index of 0 is + invalid. This is due to the fact that a zero value + index signals that a completely unindexed header + follows. + + The entry will either be from the static table or + the dynamic table depending on the value of index. + """ + original_index = index + index -= 1 + if 0 <= index: + if index < HeaderTable.STATIC_TABLE_LENGTH: + return HeaderTable.STATIC_TABLE[index] + + index -= HeaderTable.STATIC_TABLE_LENGTH + if index < len(self.dynamic_entries): + return self.dynamic_entries[index] + + raise InvalidTableIndex("Invalid table index %d" % original_index) + + def __repr__(self): + return "HeaderTable(%d, %s, %r)" % ( + self._maxsize, + self.resized, + self.dynamic_entries + ) + + def add(self, name, value): + """ + Adds a new entry to the table + + We reduce the table size if the entry will make the + table size greater than maxsize. + """ + # We just clear the table if the entry is too big + size = table_entry_size(name, value) + if size > self._maxsize: + self.dynamic_entries.clear() + self._current_size = 0 + else: + # Add new entry + self.dynamic_entries.appendleft((name, value)) + self._current_size += size + self._shrink() + + def search(self, name, value): + """ + Searches the table for the entry specified by name + and value + + Returns one of the following: + - ``None``, no match at all + - ``(index, name, None)`` for partial matches on name only. + - ``(index, name, value)`` for perfect matches. + """ + offset = HeaderTable.STATIC_TABLE_LENGTH + 1 + partial = None + for (i, (n, v)) in enumerate(HeaderTable.STATIC_TABLE): + if n == name: + if v == value: + return i + 1, n, v + elif partial is None: + partial = (i + 1, n, None) + for (i, (n, v)) in enumerate(self.dynamic_entries): + if n == name: + if v == value: + return i + offset, n, v + elif partial is None: + partial = (i + offset, n, None) + return partial + + @property + def maxsize(self): + return self._maxsize + + @maxsize.setter + def maxsize(self, newmax): + newmax = int(newmax) + log.debug("Resizing header table to %d from %d", newmax, self._maxsize) + oldmax = self._maxsize + self._maxsize = newmax + self.resized = (newmax != oldmax) + if newmax <= 0: + self.dynamic_entries.clear() + self._current_size = 0 + elif oldmax > newmax: + self._shrink() + + def _shrink(self): + """ + Shrinks the dynamic table to be at or below maxsize + """ + cursize = self._current_size + while cursize > self._maxsize: + name, value = self.dynamic_entries.pop() + cursize -= table_entry_size(name, value) + log.debug("Evicting %s: %s from the header table", name, value) + self._current_size = cursize diff --git a/testing/web-platform/tests/tools/third_party/hpack/setup.cfg b/testing/web-platform/tests/tools/third_party/hpack/setup.cfg new file mode 100644 index 0000000000..b1d2b88c0a --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/setup.cfg @@ -0,0 +1,12 @@ +[wheel] +universal = 1 + +[flake8] +max-complexity = 10 +exclude = + hpack/huffman_constants.py + +[egg_info] +tag_build = +tag_date = 0 + diff --git a/testing/web-platform/tests/tools/third_party/hpack/setup.py b/testing/web-platform/tests/tools/third_party/hpack/setup.py new file mode 100644 index 0000000000..7ffc4beb3d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/setup.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import os +import re +import sys + +try: + from setuptools import setup +except ImportError: + from distutils.core import setup + +# Get the version +version_regex = r'__version__ = ["\']([^"\']*)["\']' +with open('hpack/__init__.py', 'r') as f: + text = f.read() + match = re.search(version_regex, text) + + if match: + version = match.group(1) + else: + raise RuntimeError("No version number found!") + +# Stealing this from Kenneth Reitz +if sys.argv[-1] == 'publish': + os.system('python setup.py sdist upload') + sys.exit() + +packages = ['hpack'] + +setup( + name='hpack', + version=version, + description='Pure-Python HPACK header compression', + long_description=open('README.rst').read() + '\n\n' + open('HISTORY.rst').read(), + author='Cory Benfield', + author_email='cory@lukasa.co.uk', + url='http://hyper.rtfd.org', + packages=packages, + package_data={'': ['LICENSE', 'README.rst', 'CONTRIBUTORS.rst', 'HISTORY.rst', 'NOTICES']}, + package_dir={'hpack': 'hpack'}, + include_package_data=True, + license='MIT License', + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: MIT License', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: Implementation :: CPython', + ], +) diff --git a/testing/web-platform/tests/tools/third_party/hpack/test/test_encode_decode.py b/testing/web-platform/tests/tools/third_party/hpack/test/test_encode_decode.py new file mode 100644 index 0000000000..94820f2e9e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/test/test_encode_decode.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +""" +Test for the integer encoding/decoding functionality in the HPACK library. +""" +import pytest + +from hypothesis import given +from hypothesis.strategies import integers, binary, one_of + +from hpack.hpack import encode_integer, decode_integer +from hpack.exceptions import HPACKDecodingError + + +class TestIntegerEncoding(object): + # These tests are stolen from the HPACK spec. + def test_encoding_10_with_5_bit_prefix(self): + val = encode_integer(10, 5) + assert len(val) == 1 + assert val == bytearray(b'\x0a') + + def test_encoding_1337_with_5_bit_prefix(self): + val = encode_integer(1337, 5) + assert len(val) == 3 + assert val == bytearray(b'\x1f\x9a\x0a') + + def test_encoding_42_with_8_bit_prefix(self): + val = encode_integer(42, 8) + assert len(val) == 1 + assert val == bytearray(b'\x2a') + + +class TestIntegerDecoding(object): + # These tests are stolen from the HPACK spec. + def test_decoding_10_with_5_bit_prefix(self): + val = decode_integer(b'\x0a', 5) + assert val == (10, 1) + + def test_encoding_1337_with_5_bit_prefix(self): + val = decode_integer(b'\x1f\x9a\x0a', 5) + assert val == (1337, 3) + + def test_encoding_42_with_8_bit_prefix(self): + val = decode_integer(b'\x2a', 8) + assert val == (42, 1) + + def test_decode_empty_string_fails(self): + with pytest.raises(HPACKDecodingError): + decode_integer(b'', 8) + + def test_decode_insufficient_data_fails(self): + with pytest.raises(HPACKDecodingError): + decode_integer(b'\x1f', 5) + + +class TestEncodingProperties(object): + """ + Property-based tests for our integer encoder and decoder. + """ + @given( + integer=integers(min_value=0), + prefix_bits=integers(min_value=1, max_value=8) + ) + def test_encode_positive_integer_always_valid(self, integer, prefix_bits): + """ + So long as the prefix bits are between 1 and 8, any positive integer + can be represented. + """ + result = encode_integer(integer, prefix_bits) + assert isinstance(result, bytearray) + assert len(result) > 0 + + @given( + integer=integers(max_value=-1), + prefix_bits=integers(min_value=1, max_value=8) + ) + def test_encode_fails_for_negative_integers(self, integer, prefix_bits): + """ + If the integer to encode is negative, the encoder fails. + """ + with pytest.raises(ValueError): + encode_integer(integer, prefix_bits) + + @given( + integer=integers(min_value=0), + prefix_bits=one_of( + integers(max_value=0), + integers(min_value=9) + ) + ) + def test_encode_fails_for_invalid_prefixes(self, integer, prefix_bits): + """ + If the prefix is out of the range [1,8], the encoder fails. + """ + with pytest.raises(ValueError): + encode_integer(integer, prefix_bits) + + @given( + prefix_bits=one_of( + integers(max_value=0), + integers(min_value=9) + ) + ) + def test_decode_fails_for_invalid_prefixes(self, prefix_bits): + """ + If the prefix is out of the range [1,8], the encoder fails. + """ + with pytest.raises(ValueError): + decode_integer(b'\x00', prefix_bits) + + @given( + data=binary(), + prefix_bits=integers(min_value=1, max_value=8) + ) + def test_decode_either_succeeds_or_raises_error(self, data, prefix_bits): + """ + Attempting to decode data either returns a positive integer or throws a + HPACKDecodingError. + """ + try: + result, consumed = decode_integer(data, prefix_bits) + except HPACKDecodingError: + pass + else: + assert isinstance(result, int) + assert result >= 0 + assert consumed > 0 + + @given( + integer=integers(min_value=0), + prefix_bits=integers(min_value=1, max_value=8) + ) + def test_encode_decode_round_trips(self, integer, prefix_bits): + """ + Given valid data, the encoder and decoder can round trip. + """ + encoded_result = encode_integer(integer, prefix_bits) + decoded_integer, consumed = decode_integer( + bytes(encoded_result), prefix_bits + ) + assert integer == decoded_integer + assert consumed > 0 diff --git a/testing/web-platform/tests/tools/third_party/hpack/test/test_hpack.py b/testing/web-platform/tests/tools/third_party/hpack/test/test_hpack.py new file mode 100644 index 0000000000..c3333b4144 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/test/test_hpack.py @@ -0,0 +1,828 @@ +# -*- coding: utf-8 -*- +from hpack.hpack import Encoder, Decoder, _dict_to_iterable, _to_bytes +from hpack.exceptions import ( + HPACKDecodingError, InvalidTableIndex, OversizedHeaderListError, + InvalidTableSizeError +) +from hpack.struct import HeaderTuple, NeverIndexedHeaderTuple +import itertools +import pytest + +from hypothesis import given +from hypothesis.strategies import text, binary, sets, one_of + +try: + unicode = unicode +except NameError: + unicode = str + + +class TestHPACKEncoder(object): + # These tests are stolen entirely from the IETF specification examples. + def test_literal_header_field_with_indexing(self): + """ + The header field representation uses a literal name and a literal + value. + """ + e = Encoder() + header_set = {'custom-key': 'custom-header'} + result = b'\x40\x0acustom-key\x0dcustom-header' + + assert e.encode(header_set, huffman=False) == result + assert list(e.header_table.dynamic_entries) == [ + (n.encode('utf-8'), v.encode('utf-8')) + for n, v in header_set.items() + ] + + def test_sensitive_headers(self): + """ + Test encoding header values + """ + e = Encoder() + result = (b'\x82\x14\x88\x63\xa1\xa9' + + b'\x32\x08\x73\xd0\xc7\x10' + + b'\x87\x25\xa8\x49\xe9\xea' + + b'\x5f\x5f\x89\x41\x6a\x41' + + b'\x92\x6e\xe5\x35\x52\x9f') + header_set = [ + (':method', 'GET', True), + (':path', '/jimiscool/', True), + ('customkey', 'sensitiveinfo', True), + ] + assert e.encode(header_set, huffman=True) == result + + def test_non_sensitive_headers_with_header_tuples(self): + """ + A header field stored in a HeaderTuple emits a representation that + allows indexing. + """ + e = Encoder() + result = (b'\x82\x44\x88\x63\xa1\xa9' + + b'\x32\x08\x73\xd0\xc7\x40' + + b'\x87\x25\xa8\x49\xe9\xea' + + b'\x5f\x5f\x89\x41\x6a\x41' + + b'\x92\x6e\xe5\x35\x52\x9f') + header_set = [ + HeaderTuple(':method', 'GET'), + HeaderTuple(':path', '/jimiscool/'), + HeaderTuple('customkey', 'sensitiveinfo'), + ] + assert e.encode(header_set, huffman=True) == result + + def test_sensitive_headers_with_header_tuples(self): + """ + A header field stored in a NeverIndexedHeaderTuple emits a + representation that forbids indexing. + """ + e = Encoder() + result = (b'\x82\x14\x88\x63\xa1\xa9' + + b'\x32\x08\x73\xd0\xc7\x10' + + b'\x87\x25\xa8\x49\xe9\xea' + + b'\x5f\x5f\x89\x41\x6a\x41' + + b'\x92\x6e\xe5\x35\x52\x9f') + header_set = [ + NeverIndexedHeaderTuple(':method', 'GET'), + NeverIndexedHeaderTuple(':path', '/jimiscool/'), + NeverIndexedHeaderTuple('customkey', 'sensitiveinfo'), + ] + assert e.encode(header_set, huffman=True) == result + + def test_header_table_size_getter(self): + e = Encoder() + assert e.header_table_size == 4096 + + def test_indexed_literal_header_field_with_indexing(self): + """ + The header field representation uses an indexed name and a literal + value and performs incremental indexing. + """ + e = Encoder() + header_set = {':path': '/sample/path'} + result = b'\x44\x0c/sample/path' + + assert e.encode(header_set, huffman=False) == result + assert list(e.header_table.dynamic_entries) == [ + (n.encode('utf-8'), v.encode('utf-8')) + for n, v in header_set.items() + ] + + def test_indexed_header_field(self): + """ + The header field representation uses an indexed header field, from + the static table. + """ + e = Encoder() + header_set = {':method': 'GET'} + result = b'\x82' + + assert e.encode(header_set, huffman=False) == result + assert list(e.header_table.dynamic_entries) == [] + + def test_indexed_header_field_from_static_table(self): + e = Encoder() + e.header_table_size = 0 + header_set = {':method': 'GET'} + result = b'\x82' + + # Make sure we don't emit an encoding context update. + e.header_table.resized = False + + assert e.encode(header_set, huffman=False) == result + assert list(e.header_table.dynamic_entries) == [] + + def test_request_examples_without_huffman(self): + """ + This section shows several consecutive header sets, corresponding to + HTTP requests, on the same connection. + """ + e = Encoder() + first_header_set = [ + (':method', 'GET',), + (':scheme', 'http',), + (':path', '/',), + (':authority', 'www.example.com'), + ] + # We should have :authority in first_header_table since we index it + first_header_table = [(':authority', 'www.example.com')] + first_result = b'\x82\x86\x84\x41\x0fwww.example.com' + + assert e.encode(first_header_set, huffman=False) == first_result + assert list(e.header_table.dynamic_entries) == [ + (n.encode('utf-8'), v.encode('utf-8')) + for n, v in first_header_table + ] + + second_header_set = [ + (':method', 'GET',), + (':scheme', 'http',), + (':path', '/',), + (':authority', 'www.example.com',), + ('cache-control', 'no-cache'), + ] + second_header_table = [ + ('cache-control', 'no-cache'), + (':authority', 'www.example.com') + ] + second_result = b'\x82\x86\x84\xbeX\x08no-cache' + + assert e.encode(second_header_set, huffman=False) == second_result + assert list(e.header_table.dynamic_entries) == [ + (n.encode('utf-8'), v.encode('utf-8')) + for n, v in second_header_table + ] + + third_header_set = [ + (':method', 'GET',), + (':scheme', 'https',), + (':path', '/index.html',), + (':authority', 'www.example.com',), + ('custom-key', 'custom-value'), + ] + third_result = ( + b'\x82\x87\x85\xbf@\ncustom-key\x0ccustom-value' + ) + + assert e.encode(third_header_set, huffman=False) == third_result + # Don't check the header table here, it's just too complex to be + # reliable. Check its length though. + assert len(e.header_table.dynamic_entries) == 3 + + def test_request_examples_with_huffman(self): + """ + This section shows the same examples as the previous section, but + using Huffman encoding for the literal values. + """ + e = Encoder() + first_header_set = [ + (':method', 'GET',), + (':scheme', 'http',), + (':path', '/',), + (':authority', 'www.example.com'), + ] + first_header_table = [(':authority', 'www.example.com')] + first_result = ( + b'\x82\x86\x84\x41\x8c\xf1\xe3\xc2\xe5\xf2:k\xa0\xab\x90\xf4\xff' + ) + + assert e.encode(first_header_set, huffman=True) == first_result + assert list(e.header_table.dynamic_entries) == [ + (n.encode('utf-8'), v.encode('utf-8')) + for n, v in first_header_table + ] + + second_header_table = [ + ('cache-control', 'no-cache'), + (':authority', 'www.example.com') + ] + second_header_set = [ + (':method', 'GET',), + (':scheme', 'http',), + (':path', '/',), + (':authority', 'www.example.com',), + ('cache-control', 'no-cache'), + ] + second_result = b'\x82\x86\x84\xbeX\x86\xa8\xeb\x10d\x9c\xbf' + + assert e.encode(second_header_set, huffman=True) == second_result + assert list(e.header_table.dynamic_entries) == [ + (n.encode('utf-8'), v.encode('utf-8')) + for n, v in second_header_table + ] + + third_header_set = [ + (':method', 'GET',), + (':scheme', 'https',), + (':path', '/index.html',), + (':authority', 'www.example.com',), + ('custom-key', 'custom-value'), + ] + third_result = ( + b'\x82\x87\x85\xbf' + b'@\x88%\xa8I\xe9[\xa9}\x7f\x89%\xa8I\xe9[\xb8\xe8\xb4\xbf' + ) + + assert e.encode(third_header_set, huffman=True) == third_result + assert len(e.header_table.dynamic_entries) == 3 + + # These tests are custom, for hyper. + def test_resizing_header_table(self): + # We need to encode a substantial number of headers, to populate the + # header table. + e = Encoder() + header_set = [ + (':method', 'GET'), + (':scheme', 'https'), + (':path', '/some/path'), + (':authority', 'www.example.com'), + ('custom-key', 'custom-value'), + ( + "user-agent", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) " + "Gecko/20100101 Firefox/16.0", + ), + ( + "accept", + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;" + "q=0.8", + ), + ('X-Lukasa-Test', '88989'), + ] + e.encode(header_set, huffman=True) + + # Resize the header table to a size so small that nothing can be in it. + e.header_table_size = 40 + assert len(e.header_table.dynamic_entries) == 0 + + def test_resizing_header_table_sends_multiple_updates(self): + e = Encoder() + + e.header_table_size = 40 + e.header_table_size = 100 + e.header_table_size = 40 + + header_set = [(':method', 'GET')] + out = e.encode(header_set, huffman=True) + assert out == b'\x3F\x09\x3F\x45\x3F\x09\x82' + + def test_resizing_header_table_to_same_size_ignored(self): + e = Encoder() + + # These size changes should be ignored + e.header_table_size = 4096 + e.header_table_size = 4096 + e.header_table_size = 4096 + + # These size changes should be encoded + e.header_table_size = 40 + e.header_table_size = 100 + e.header_table_size = 40 + + header_set = [(':method', 'GET')] + out = e.encode(header_set, huffman=True) + assert out == b'\x3F\x09\x3F\x45\x3F\x09\x82' + + def test_resizing_header_table_sends_context_update(self): + e = Encoder() + + # Resize the header table to a size so small that nothing can be in it. + e.header_table_size = 40 + + # Now, encode a header set. Just a small one, with a well-defined + # output. + header_set = [(':method', 'GET')] + out = e.encode(header_set, huffman=True) + + assert out == b'?\t\x82' + + def test_setting_table_size_to_the_same_does_nothing(self): + e = Encoder() + + # Set the header table size to the default. + e.header_table_size = 4096 + + # Now encode a header set. Just a small one, with a well-defined + # output. + header_set = [(':method', 'GET')] + out = e.encode(header_set, huffman=True) + + assert out == b'\x82' + + def test_evicting_header_table_objects(self): + e = Encoder() + + # Set the header table size large enough to include one header. + e.header_table_size = 66 + header_set = [('a', 'b'), ('long-custom-header', 'longish value')] + e.encode(header_set) + + assert len(e.header_table.dynamic_entries) == 1 + + +class TestHPACKDecoder(object): + # These tests are stolen entirely from the IETF specification examples. + def test_literal_header_field_with_indexing(self): + """ + The header field representation uses a literal name and a literal + value. + """ + d = Decoder() + header_set = [('custom-key', 'custom-header')] + data = b'\x40\x0acustom-key\x0dcustom-header' + + assert d.decode(data) == header_set + assert list(d.header_table.dynamic_entries) == [ + (n.encode('utf-8'), v.encode('utf-8')) for n, v in header_set + ] + + def test_raw_decoding(self): + """ + The header field representation is decoded as a raw byte string instead + of UTF-8 + """ + d = Decoder() + header_set = [ + (b'\x00\x01\x99\x30\x11\x22\x55\x21\x89\x14', b'custom-header') + ] + data = ( + b'\x40\x0a\x00\x01\x99\x30\x11\x22\x55\x21\x89\x14\x0d' + b'custom-header' + ) + + assert d.decode(data, raw=True) == header_set + + def test_literal_header_field_without_indexing(self): + """ + The header field representation uses an indexed name and a literal + value. + """ + d = Decoder() + header_set = [(':path', '/sample/path')] + data = b'\x04\x0c/sample/path' + + assert d.decode(data) == header_set + assert list(d.header_table.dynamic_entries) == [] + + def test_header_table_size_getter(self): + d = Decoder() + assert d.header_table_size + + def test_indexed_header_field(self): + """ + The header field representation uses an indexed header field, from + the static table. + """ + d = Decoder() + header_set = [(':method', 'GET')] + data = b'\x82' + + assert d.decode(data) == header_set + assert list(d.header_table.dynamic_entries) == [] + + def test_request_examples_without_huffman(self): + """ + This section shows several consecutive header sets, corresponding to + HTTP requests, on the same connection. + """ + d = Decoder() + first_header_set = [ + (':method', 'GET',), + (':scheme', 'http',), + (':path', '/',), + (':authority', 'www.example.com'), + ] + # The first_header_table doesn't contain 'authority' + first_data = b'\x82\x86\x84\x01\x0fwww.example.com' + + assert d.decode(first_data) == first_header_set + assert list(d.header_table.dynamic_entries) == [] + + # This request takes advantage of the differential encoding of header + # sets. + second_header_set = [ + (':method', 'GET',), + (':scheme', 'http',), + (':path', '/',), + (':authority', 'www.example.com',), + ('cache-control', 'no-cache'), + ] + second_data = ( + b'\x82\x86\x84\x01\x0fwww.example.com\x0f\t\x08no-cache' + ) + + assert d.decode(second_data) == second_header_set + assert list(d.header_table.dynamic_entries) == [] + + third_header_set = [ + (':method', 'GET',), + (':scheme', 'https',), + (':path', '/index.html',), + (':authority', 'www.example.com',), + ('custom-key', 'custom-value'), + ] + third_data = ( + b'\x82\x87\x85\x01\x0fwww.example.com@\ncustom-key\x0ccustom-value' + ) + + assert d.decode(third_data) == third_header_set + # Don't check the header table here, it's just too complex to be + # reliable. Check its length though. + assert len(d.header_table.dynamic_entries) == 1 + + def test_request_examples_with_huffman(self): + """ + This section shows the same examples as the previous section, but + using Huffman encoding for the literal values. + """ + d = Decoder() + + first_header_set = [ + (':method', 'GET',), + (':scheme', 'http',), + (':path', '/',), + (':authority', 'www.example.com'), + ] + first_data = ( + b'\x82\x86\x84\x01\x8c\xf1\xe3\xc2\xe5\xf2:k\xa0\xab\x90\xf4\xff' + ) + + assert d.decode(first_data) == first_header_set + assert list(d.header_table.dynamic_entries) == [] + + second_header_set = [ + (':method', 'GET',), + (':scheme', 'http',), + (':path', '/',), + (':authority', 'www.example.com',), + ('cache-control', 'no-cache'), + ] + second_data = ( + b'\x82\x86\x84\x01\x8c\xf1\xe3\xc2\xe5\xf2:k\xa0\xab\x90\xf4\xff' + b'\x0f\t\x86\xa8\xeb\x10d\x9c\xbf' + ) + + assert d.decode(second_data) == second_header_set + assert list(d.header_table.dynamic_entries) == [] + + third_header_set = [ + (':method', 'GET',), + (':scheme', 'https',), + (':path', '/index.html',), + (':authority', 'www.example.com',), + ('custom-key', 'custom-value'), + ] + third_data = ( + b'\x82\x87\x85\x01\x8c\xf1\xe3\xc2\xe5\xf2:k\xa0\xab\x90\xf4\xff@' + b'\x88%\xa8I\xe9[\xa9}\x7f\x89%\xa8I\xe9[\xb8\xe8\xb4\xbf' + ) + + assert d.decode(third_data) == third_header_set + assert len(d.header_table.dynamic_entries) == 1 + + # These tests are custom, for hyper. + def test_resizing_header_table(self): + # We need to decode a substantial number of headers, to populate the + # header table. This string isn't magic: it's the output from the + # equivalent test for the Encoder. + d = Decoder() + data = ( + b'\x82\x87D\x87a\x07\xa4\xacV4\xcfA\x8c\xf1\xe3\xc2\xe5\xf2:k\xa0' + b'\xab\x90\xf4\xff@\x88%\xa8I\xe9[\xa9}\x7f\x89%\xa8I\xe9[\xb8\xe8' + b'\xb4\xbfz\xbc\xd0\x7ff\xa2\x81\xb0\xda\xe0S\xfa\xd02\x1a\xa4\x9d' + b'\x13\xfd\xa9\x92\xa4\x96\x854\x0c\x8aj\xdc\xa7\xe2\x81\x02\xef}' + b'\xa9g{\x81qp\x7fjb):\x9d\x81\x00 \x00@\x150\x9a\xc2\xca\x7f,\x05' + b'\xc5\xc1S\xb0I|\xa5\x89\xd3M\x1fC\xae\xba\x0cA\xa4\xc7\xa9\x8f3' + b'\xa6\x9a?\xdf\x9ah\xfa\x1du\xd0b\r&=Ly\xa6\x8f\xbe\xd0\x01w\xfe' + b'\xbeX\xf9\xfb\xed\x00\x17{@\x8a\xfc[=\xbdF\x81\xad\xbc\xa8O\x84y' + b'\xe7\xde\x7f' + ) + d.decode(data) + + # Resize the header table to a size so small that nothing can be in it. + d.header_table_size = 40 + assert len(d.header_table.dynamic_entries) == 0 + + def test_apache_trafficserver(self): + # This test reproduces the bug in #110, using exactly the same header + # data. + d = Decoder() + data = ( + b'\x10\x07:status\x03200@\x06server\tATS/6.0.0' + b'@\x04date\x1dTue, 31 Mar 2015 08:09:51 GMT' + b'@\x0ccontent-type\ttext/html@\x0econtent-length\x0542468' + b'@\rlast-modified\x1dTue, 31 Mar 2015 01:55:51 GMT' + b'@\x04vary\x0fAccept-Encoding@\x04etag\x0f"5519fea7-a5e4"' + b'@\x08x-served\x05Nginx@\x14x-subdomain-tryfiles\x04True' + b'@\x07x-deity\thydra-lts@\raccept-ranges\x05bytes@\x03age\x010' + b'@\x19strict-transport-security\rmax-age=86400' + b'@\x03via2https/1.1 ATS (ApacheTrafficServer/6.0.0 [cSsNfU])' + ) + expect = [ + (':status', '200'), + ('server', 'ATS/6.0.0'), + ('date', 'Tue, 31 Mar 2015 08:09:51 GMT'), + ('content-type', 'text/html'), + ('content-length', '42468'), + ('last-modified', 'Tue, 31 Mar 2015 01:55:51 GMT'), + ('vary', 'Accept-Encoding'), + ('etag', '"5519fea7-a5e4"'), + ('x-served', 'Nginx'), + ('x-subdomain-tryfiles', 'True'), + ('x-deity', 'hydra-lts'), + ('accept-ranges', 'bytes'), + ('age', '0'), + ('strict-transport-security', 'max-age=86400'), + ('via', 'https/1.1 ATS (ApacheTrafficServer/6.0.0 [cSsNfU])'), + ] + + result = d.decode(data) + + assert result == expect + # The status header shouldn't be indexed. + assert len(d.header_table.dynamic_entries) == len(expect) - 1 + + def test_utf8_errors_raise_hpack_decoding_error(self): + d = Decoder() + + # Invalid UTF-8 data. + data = b'\x82\x86\x84\x01\x10www.\x07\xaa\xd7\x95\xd7\xa8\xd7\x94.com' + + with pytest.raises(HPACKDecodingError): + d.decode(data) + + def test_invalid_indexed_literal(self): + d = Decoder() + + # Refer to an index that is too large. + data = b'\x82\x86\x84\x7f\x0a\x0fwww.example.com' + with pytest.raises(InvalidTableIndex): + d.decode(data) + + def test_invalid_indexed_header(self): + d = Decoder() + + # Refer to an indexed header that is too large. + data = b'\xBE\x86\x84\x01\x0fwww.example.com' + with pytest.raises(InvalidTableIndex): + d.decode(data) + + def test_literal_header_field_with_indexing_emits_headertuple(self): + """ + A header field with indexing emits a HeaderTuple. + """ + d = Decoder() + data = b'\x00\x0acustom-key\x0dcustom-header' + + headers = d.decode(data) + assert len(headers) == 1 + + header = headers[0] + assert isinstance(header, HeaderTuple) + assert not isinstance(header, NeverIndexedHeaderTuple) + + def test_literal_never_indexed_emits_neverindexedheadertuple(self): + """ + A literal header field that must never be indexed emits a + NeverIndexedHeaderTuple. + """ + d = Decoder() + data = b'\x10\x0acustom-key\x0dcustom-header' + + headers = d.decode(data) + assert len(headers) == 1 + + header = headers[0] + assert isinstance(header, NeverIndexedHeaderTuple) + + def test_indexed_never_indexed_emits_neverindexedheadertuple(self): + """ + A header field with an indexed name that must never be indexed emits a + NeverIndexedHeaderTuple. + """ + d = Decoder() + data = b'\x14\x0c/sample/path' + + headers = d.decode(data) + assert len(headers) == 1 + + header = headers[0] + assert isinstance(header, NeverIndexedHeaderTuple) + + def test_max_header_list_size(self): + """ + If the header block is larger than the max_header_list_size, the HPACK + decoder throws an OversizedHeaderListError. + """ + d = Decoder(max_header_list_size=44) + data = b'\x14\x0c/sample/path' + + with pytest.raises(OversizedHeaderListError): + d.decode(data) + + def test_can_decode_multiple_header_table_size_changes(self): + """ + If multiple header table size changes are sent in at once, they are + successfully decoded. + """ + d = Decoder() + data = b'?a?\xe1\x1f\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' + expect = [ + (':method', 'GET'), + (':scheme', 'https'), + (':path', '/'), + (':authority', '127.0.0.1:8443') + ] + + assert d.decode(data) == expect + + def test_header_table_size_change_above_maximum(self): + """ + If a header table size change is received that exceeds the maximum + allowed table size, it is rejected. + """ + d = Decoder() + d.max_allowed_table_size = 127 + data = b'?a\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' + + with pytest.raises(InvalidTableSizeError): + d.decode(data) + + def test_table_size_not_adjusting(self): + """ + If the header table size is shrunk, and then the remote peer doesn't + join in the shrinking, then an error is raised. + """ + d = Decoder() + d.max_allowed_table_size = 128 + data = b'\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' + + with pytest.raises(InvalidTableSizeError): + d.decode(data) + + def test_table_size_last_rejected(self): + """ + If a header table size change comes last in the header block, it is + forbidden. + """ + d = Decoder() + data = b'\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99?a' + + with pytest.raises(HPACKDecodingError): + d.decode(data) + + def test_table_size_middle_rejected(self): + """ + If a header table size change comes anywhere but first in the header + block, it is forbidden. + """ + d = Decoder() + data = b'\x82?a\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' + + with pytest.raises(HPACKDecodingError): + d.decode(data) + + def test_truncated_header_name(self): + """ + If a header name is truncated an error is raised. + """ + d = Decoder() + # This is a simple header block that has a bad ending. The interesting + # part begins on the second line. This indicates a string that has + # literal name and value. The name is a 5 character huffman-encoded + # string that is only three bytes long. + data = ( + b'\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' + b'\x00\x85\xf2\xb2J' + ) + + with pytest.raises(HPACKDecodingError): + d.decode(data) + + def test_truncated_header_value(self): + """ + If a header value is truncated an error is raised. + """ + d = Decoder() + # This is a simple header block that has a bad ending. The interesting + # part begins on the second line. This indicates a string that has + # literal name and value. The name is a 5 character huffman-encoded + # string, but the entire EOS character has been written over the end. + # This causes hpack to see the header value as being supposed to be + # 622462 bytes long, which it clearly is not, and so this must fail. + data = ( + b'\x82\x87\x84A\x8a\x08\x9d\\\x0b\x81p\xdcy\xa6\x99' + b'\x00\x85\xf2\xb2J\x87\xff\xff\xff\xfd%B\x7f' + ) + + with pytest.raises(HPACKDecodingError): + d.decode(data) + + +class TestDictToIterable(object): + """ + The dict_to_iterable function has some subtle requirements: validates that + everything behaves as expected. + + As much as possible this tries to be exhaustive. + """ + keys = one_of( + text().filter(lambda k: k and not k.startswith(u':')), + binary().filter(lambda k: k and not k.startswith(b':')) + ) + + @given( + special_keys=sets(keys), + boring_keys=sets(keys), + ) + def test_ordering(self, special_keys, boring_keys): + """ + _dict_to_iterable produces an iterable where all the keys beginning + with a colon are emitted first. + """ + def _prepend_colon(k): + if isinstance(k, unicode): + return u':' + k + else: + return b':' + k + + special_keys = set(map(_prepend_colon, special_keys)) + input_dict = { + k: b'testval' for k in itertools.chain( + special_keys, + boring_keys + ) + } + filtered = _dict_to_iterable(input_dict) + + received_special = set() + received_boring = set() + + for _ in special_keys: + k, _ = next(filtered) + received_special.add(k) + for _ in boring_keys: + k, _ = next(filtered) + received_boring.add(k) + + assert special_keys == received_special + assert boring_keys == received_boring + + @given( + special_keys=sets(keys), + boring_keys=sets(keys), + ) + def test_ordering_applies_to_encoding(self, special_keys, boring_keys): + """ + When encoding a dictionary the special keys all appear first. + """ + def _prepend_colon(k): + if isinstance(k, unicode): + return u':' + k + else: + return b':' + k + + special_keys = set(map(_prepend_colon, special_keys)) + input_dict = { + k: b'testval' for k in itertools.chain( + special_keys, + boring_keys + ) + } + e = Encoder() + d = Decoder() + encoded = e.encode(input_dict) + decoded = iter(d.decode(encoded, raw=True)) + + received_special = set() + received_boring = set() + expected_special = set(map(_to_bytes, special_keys)) + expected_boring = set(map(_to_bytes, boring_keys)) + + for _ in special_keys: + k, _ = next(decoded) + received_special.add(k) + for _ in boring_keys: + k, _ = next(decoded) + received_boring.add(k) + + assert expected_special == received_special + assert expected_boring == received_boring diff --git a/testing/web-platform/tests/tools/third_party/hpack/test/test_hpack_integration.py b/testing/web-platform/tests/tools/third_party/hpack/test/test_hpack_integration.py new file mode 100644 index 0000000000..8b8de650d2 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/test/test_hpack_integration.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +""" +This module defines substantial HPACK integration tests. These can take a very +long time to run, so they're outside the main test suite, but they need to be +run before every change to HPACK. +""" +from hpack.hpack import Decoder, Encoder +from hpack.struct import HeaderTuple +from binascii import unhexlify +from pytest import skip + + +class TestHPACKDecoderIntegration(object): + def test_can_decode_a_story(self, story): + d = Decoder() + + # We test against draft 9 of the HPACK spec. + if story['draft'] != 9: + skip("We test against draft 9, not draft %d" % story['draft']) + + for case in story['cases']: + try: + d.header_table_size = case['header_table_size'] + except KeyError: + pass + decoded_headers = d.decode(unhexlify(case['wire'])) + + # The correct headers are a list of dicts, which is annoying. + correct_headers = [ + (item[0], item[1]) + for header in case['headers'] + for item in header.items() + ] + correct_headers = correct_headers + assert correct_headers == decoded_headers + assert all( + isinstance(header, HeaderTuple) for header in decoded_headers + ) + + def test_can_encode_a_story_no_huffman(self, raw_story): + d = Decoder() + e = Encoder() + + for case in raw_story['cases']: + # The input headers are a list of dicts, which is annoying. + input_headers = [ + (item[0], item[1]) + for header in case['headers'] + for item in header.items() + ] + + encoded = e.encode(input_headers, huffman=False) + decoded_headers = d.decode(encoded) + + assert input_headers == decoded_headers + assert all( + isinstance(header, HeaderTuple) for header in decoded_headers + ) + + def test_can_encode_a_story_with_huffman(self, raw_story): + d = Decoder() + e = Encoder() + + for case in raw_story['cases']: + # The input headers are a list of dicts, which is annoying. + input_headers = [ + (item[0], item[1]) + for header in case['headers'] + for item in header.items() + ] + + encoded = e.encode(input_headers, huffman=True) + decoded_headers = d.decode(encoded) + + assert input_headers == decoded_headers diff --git a/testing/web-platform/tests/tools/third_party/hpack/test/test_huffman.py b/testing/web-platform/tests/tools/third_party/hpack/test/test_huffman.py new file mode 100644 index 0000000000..1b8c2f1238 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/test/test_huffman.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +from hpack.exceptions import HPACKDecodingError +from hpack.huffman_table import decode_huffman +from hpack.huffman import HuffmanEncoder +from hpack.huffman_constants import REQUEST_CODES, REQUEST_CODES_LENGTH + +from hypothesis import given, example +from hypothesis.strategies import binary + + +class TestHuffman(object): + + def test_request_huffman_decoder(self): + assert ( + decode_huffman(b'\xf1\xe3\xc2\xe5\xf2:k\xa0\xab\x90\xf4\xff') == + b"www.example.com" + ) + assert decode_huffman(b'\xa8\xeb\x10d\x9c\xbf') == b"no-cache" + assert decode_huffman(b'%\xa8I\xe9[\xa9}\x7f') == b"custom-key" + assert ( + decode_huffman(b'%\xa8I\xe9[\xb8\xe8\xb4\xbf') == b"custom-value" + ) + + def test_request_huffman_encode(self): + encoder = HuffmanEncoder(REQUEST_CODES, REQUEST_CODES_LENGTH) + assert ( + encoder.encode(b"www.example.com") == + b'\xf1\xe3\xc2\xe5\xf2:k\xa0\xab\x90\xf4\xff' + ) + assert encoder.encode(b"no-cache") == b'\xa8\xeb\x10d\x9c\xbf' + assert encoder.encode(b"custom-key") == b'%\xa8I\xe9[\xa9}\x7f' + assert ( + encoder.encode(b"custom-value") == b'%\xa8I\xe9[\xb8\xe8\xb4\xbf' + ) + + +class TestHuffmanDecoder(object): + @given(data=binary()) + @example(b'\xff') + @example(b'\x5f\xff\xff\xff\xff') + @example(b'\x00\x3f\xff\xff\xff') + def test_huffman_decoder_properly_handles_all_bytestrings(self, data): + """ + When given random bytestrings, either we get HPACKDecodingError or we + get a bytestring back. + """ + # The examples aren't special, they're just known to hit specific error + # paths through the state machine. Basically, they are strings that are + # definitely invalid. + try: + result = decode_huffman(data) + except HPACKDecodingError: + result = b'' + + assert isinstance(result, bytes) diff --git a/testing/web-platform/tests/tools/third_party/hpack/test/test_struct.py b/testing/web-platform/tests/tools/third_party/hpack/test/test_struct.py new file mode 100644 index 0000000000..613b8c6bae --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/test/test_struct.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- +""" +test_struct +~~~~~~~~~~~ + +Tests for the Header tuples. +""" +import pytest + +from hpack.struct import HeaderTuple, NeverIndexedHeaderTuple + + +class TestHeaderTuple(object): + def test_is_tuple(self): + """ + HeaderTuple objects are tuples. + """ + h = HeaderTuple('name', 'value') + assert isinstance(h, tuple) + + def test_unpacks_properly(self): + """ + HeaderTuple objects unpack like tuples. + """ + h = HeaderTuple('name', 'value') + k, v = h + + assert k == 'name' + assert v == 'value' + + def test_header_tuples_are_indexable(self): + """ + HeaderTuple objects can be indexed. + """ + h = HeaderTuple('name', 'value') + assert h.indexable + + def test_never_indexed_tuples_are_not_indexable(self): + """ + NeverIndexedHeaderTuple objects cannot be indexed. + """ + h = NeverIndexedHeaderTuple('name', 'value') + assert not h.indexable + + @pytest.mark.parametrize('cls', (HeaderTuple, NeverIndexedHeaderTuple)) + def test_equal_to_tuples(self, cls): + """ + HeaderTuples and NeverIndexedHeaderTuples are equal to equivalent + tuples. + """ + t1 = ('name', 'value') + t2 = cls('name', 'value') + + assert t1 == t2 + assert t1 is not t2 + + @pytest.mark.parametrize('cls', (HeaderTuple, NeverIndexedHeaderTuple)) + def test_equal_to_self(self, cls): + """ + HeaderTuples and NeverIndexedHeaderTuples are always equal when + compared to the same class. + """ + t1 = cls('name', 'value') + t2 = cls('name', 'value') + + assert t1 == t2 + assert t1 is not t2 + + def test_equal_for_different_indexes(self): + """ + HeaderTuples compare equal to equivalent NeverIndexedHeaderTuples. + """ + t1 = HeaderTuple('name', 'value') + t2 = NeverIndexedHeaderTuple('name', 'value') + + assert t1 == t2 + assert t1 is not t2 diff --git a/testing/web-platform/tests/tools/third_party/hpack/test/test_table.py b/testing/web-platform/tests/tools/third_party/hpack/test/test_table.py new file mode 100644 index 0000000000..d77c30a2fb --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/hpack/test/test_table.py @@ -0,0 +1,158 @@ +# -*- coding: utf-8 -*- +from hpack.table import HeaderTable, table_entry_size +from hpack.exceptions import InvalidTableIndex +import pytest +import sys +_ver = sys.version_info +is_py2 = _ver[0] == 2 +is_py3 = _ver[0] == 3 + + +class TestPackageFunctions(object): + def test_table_entry_size(self): + res = table_entry_size(b'TestName', b'TestValue') + assert res == 49 + + +class TestHeaderTable(object): + def test_get_by_index_dynamic_table(self): + tbl = HeaderTable() + off = len(HeaderTable.STATIC_TABLE) + val = (b'TestName', b'TestValue') + tbl.add(*val) + res = tbl.get_by_index(off + 1) + assert res == val + + def test_get_by_index_static_table(self): + tbl = HeaderTable() + exp = (b':authority', b'') + res = tbl.get_by_index(1) + assert res == exp + idx = len(HeaderTable.STATIC_TABLE) + exp = (b'www-authenticate', b'') + res = tbl.get_by_index(idx) + assert res == exp + + def test_get_by_index_zero_index(self): + tbl = HeaderTable() + with pytest.raises(InvalidTableIndex): + tbl.get_by_index(0) + + def test_get_by_index_out_of_range(self): + tbl = HeaderTable() + off = len(HeaderTable.STATIC_TABLE) + tbl.add(b'TestName', b'TestValue') + with pytest.raises(InvalidTableIndex) as e: + tbl.get_by_index(off + 2) + + assert ( + "InvalidTableIndex: Invalid table index %d" % (off + 2) in str(e) + ) + + def test_repr(self): + tbl = HeaderTable() + tbl.add(b'TestName1', b'TestValue1') + tbl.add(b'TestName2', b'TestValue2') + tbl.add(b'TestName2', b'TestValue2') + # Meh, I hate that I have to do this to test + # repr + if is_py3: + exp = ( + "HeaderTable(4096, False, deque([" + "(b'TestName2', b'TestValue2'), " + "(b'TestName2', b'TestValue2'), " + "(b'TestName1', b'TestValue1')" + "]))" + ) + else: + exp = ( + "HeaderTable(4096, False, deque([" + "('TestName2', 'TestValue2'), " + "('TestName2', 'TestValue2'), " + "('TestName1', 'TestValue1')" + "]))" + ) + res = repr(tbl) + assert res == exp + + def test_add_to_large(self): + tbl = HeaderTable() + # Max size to small to hold the value we specify + tbl.maxsize = 1 + tbl.add(b'TestName', b'TestValue') + # Table length should be 0 + assert len(tbl.dynamic_entries) == 0 + + def test_search_in_static_full(self): + tbl = HeaderTable() + itm = (b':authority', b'') + exp = (1, itm[0], itm[1]) + res = tbl.search(itm[0], itm[1]) + assert res == exp + + def test_search_in_static_partial(self): + tbl = HeaderTable() + exp = (1, b':authority', None) + res = tbl.search(b':authority', b'NotInTable') + assert res == exp + + def test_search_in_dynamic_full(self): + tbl = HeaderTable() + idx = len(HeaderTable.STATIC_TABLE) + 1 + tbl.add(b'TestName', b'TestValue') + exp = (idx, b'TestName', b'TestValue') + res = tbl.search(b'TestName', b'TestValue') + assert res == exp + + def test_search_in_dynamic_partial(self): + tbl = HeaderTable() + idx = len(HeaderTable.STATIC_TABLE) + 1 + tbl.add(b'TestName', b'TestValue') + exp = (idx, b'TestName', None) + res = tbl.search(b'TestName', b'NotInTable') + assert res == exp + + def test_search_no_match(self): + tbl = HeaderTable() + tbl.add(b'TestName', b'TestValue') + res = tbl.search(b'NotInTable', b'NotInTable') + assert res is None + + def test_maxsize_prop_getter(self): + tbl = HeaderTable() + assert tbl.maxsize == HeaderTable.DEFAULT_SIZE + + def test_maxsize_prop_setter(self): + tbl = HeaderTable() + exp = int(HeaderTable.DEFAULT_SIZE / 2) + tbl.maxsize = exp + assert tbl.resized is True + assert tbl.maxsize == exp + tbl.resized = False + tbl.maxsize = exp + assert tbl.resized is False + assert tbl.maxsize == exp + + def test_size(self): + tbl = HeaderTable() + for i in range(3): + tbl.add(b'TestName', b'TestValue') + res = tbl._current_size + assert res == 147 + + def test_shrink_maxsize_is_zero(self): + tbl = HeaderTable() + tbl.add(b'TestName', b'TestValue') + assert len(tbl.dynamic_entries) == 1 + tbl.maxsize = 0 + assert len(tbl.dynamic_entries) == 0 + + def test_shrink_maxsize(self): + tbl = HeaderTable() + for i in range(3): + tbl.add(b'TestName', b'TestValue') + + assert tbl._current_size == 147 + tbl.maxsize = 146 + assert len(tbl.dynamic_entries) == 2 + assert tbl._current_size == 98 diff --git a/testing/web-platform/tests/tools/third_party/html5lib/.appveyor.yml b/testing/web-platform/tests/tools/third_party/html5lib/.appveyor.yml new file mode 100644 index 0000000000..984e2b7fa5 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/html5lib/.appveyor.yml @@ -0,0 +1,31 @@ +# To activate, change the Appveyor settings to use `.appveyor.yml`. +environment: + global: + PATH: "C:\\Python27\\Scripts\\;%PATH%" + PYTEST_COMMAND: "coverage run -m pytest" + matrix: + - TOXENV: py27-base + - TOXENV: py27-optional + - TOXENV: py33-base + - TOXENV: py33-optional + - TOXENV: py34-base + - TOXENV: py34-optional + - TOXENV: py35-base + - TOXENV: py35-optional + - TOXENV: py36-base + - TOXENV: py36-optional + +install: + - git submodule update --init --recursive + - python -m pip install tox codecov + +build: off + +test_script: + - tox + +after_test: + - python debug-info.py + +on_success: + - codecov diff --git a/testing/web-platform/tests/tools/third_party/html5lib/.coveragerc b/testing/web-platform/tests/tools/third_party/html5lib/.coveragerc new file mode 100644 index 0000000000..6facf35239 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/html5lib/.coveragerc @@ -0,0 +1,8 @@ +[run] +branch = True +source = html5lib + +[paths] +source = + html5lib + .tox/*/lib/python*/site-packages/html5lib diff --git a/testing/web-platform/tests/tools/third_party/html5lib/.gitignore b/testing/web-platform/tests/tools/third_party/html5lib/.gitignore new file mode 100644 index 0000000000..ecd62df31b --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/html5lib/.gitignore @@ -0,0 +1,85 @@ +# Copyright (c) 2014 GitHub, Inc. +# +# 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. + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +doc/_build/ + +# PyBuilder +target/ + +# Generated by parse.py -p +stats.prof + +# IDE +.idea diff --git a/testing/web-platform/tests/tools/third_party/html5lib/.prospector.yaml b/testing/web-platform/tests/tools/third_party/html5lib/.prospector.yaml new file mode 100644 index 0000000000..7e8efe1a62 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/html5lib/.prospector.yaml @@ -0,0 +1,21 @@ +strictness: veryhigh +doc-warnings: false +test-warnings: false + +max-line-length: 139 + +requirements: + - requirements.txt + - requirements-test.txt + - requirements-optional.txt + +ignore-paths: + - parse.py + - utils/ + +python-targets: + - 2 + - 3 + +mccabe: + run: false diff --git a/testing/web-platform/tests/tools/third_party/html5lib/.pylintrc b/testing/web-platform/tests/tools/third_party/html5lib/.pylintrc new file mode 100644 index 0000000000..ea74d5db3f --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/html5lib/.pylintrc @@ -0,0 +1,10 @@ +[MASTER] +ignore=tests + +[MESSAGES CONTROL] +# messages up to fixme should probably be fixed somehow +disable = redefined-builtin,attribute-defined-outside-init,anomalous-backslash-in-string,no-self-use,redefined-outer-name,bad-continuation,wrong-import-order,superfluous-parens,no-member,duplicate-code,super-init-not-called,abstract-method,property-on-old-class,wrong-import-position,no-name-in-module,no-init,bad-mcs-classmethod-argument,bad-classmethod-argument,fixme,invalid-name,import-error,too-few-public-methods,too-many-ancestors,too-many-arguments,too-many-boolean-expressions,too-many-branches,too-many-instance-attributes,too-many-locals,too-many-lines,too-many-public-methods,too-many-return-statements,too-many-statements,missing-docstring,line-too-long,locally-disabled,locally-enabled,bad-builtin,deprecated-lambda + +[FORMAT] +max-line-length=139 +single-line-if-stmt=no diff --git a/testing/web-platform/tests/tools/third_party/html5lib/.pytest.expect b/testing/web-platform/tests/tools/third_party/html5lib/.pytest.expect new file mode 100644 index 0000000000..0fa326f035 --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/html5lib/.pytest.expect @@ -0,0 +1,1322 @@ +pytest-expect file v1 +(2, 7, 11, 'final', 0) +b'html5lib/tests/test_encoding.py::test_encoding::[110]': FAIL +b'html5lib/tests/test_encoding.py::test_encoding::[111]': FAIL +u'html5lib/tests/testdata/tokenizer/test2.test::0::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::228::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::231::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::232::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::234::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::235::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::237::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::240::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::241::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::243::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::244::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::246::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::258::dataState': FAIL +u'html5lib/tests/testdata/tokenizer/test3.test::656::dataState': FAIL +u'html5lib/tests/testdata/tree-construction/adoption01.dat::17::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/adoption01.dat::17::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/adoption01.dat::17::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/adoption01.dat::17::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/adoption01.dat::17::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/adoption01.dat::17::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/adoption01.dat::17::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/adoption01.dat::17::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::0::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::0::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::0::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::0::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::0::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::0::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::0::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::0::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::18::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::18::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::18::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::18::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::18::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::18::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::18::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::18::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::19::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::19::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::19::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::19::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::19::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::19::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::19::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::19::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::1::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::1::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::1::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::1::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::1::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::1::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::1::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::1::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::22::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::22::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::22::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::22::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::22::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::22::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::22::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::22::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::23::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::23::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::23::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::23::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::23::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::23::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::23::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::23::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::26::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::26::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::26::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::26::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::26::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::26::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::26::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::26::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::27::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::27::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::27::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::27::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::27::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::27::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::27::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::27::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::2::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::2::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::2::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::2::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::2::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::2::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::2::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::2::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::30::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::30::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::30::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::30::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::30::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::30::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::30::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::30::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::31::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::31::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::31::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::31::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::31::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::31::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::31::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::31::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::34::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::34::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::34::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::34::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::34::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::34::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::34::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::34::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::35::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::35::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::35::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::35::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::35::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::35::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::35::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::35::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::38::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::38::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::38::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::38::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::38::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::38::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::38::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::38::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::39::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::39::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::39::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::39::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::39::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::39::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::39::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::39::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::3::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::3::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::3::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::3::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::3::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::3::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::3::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::3::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::40::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::40::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::40::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::40::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::40::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::40::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::40::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::40::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::41::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::41::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::41::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::41::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::41::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::41::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::41::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::41::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::47::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::47::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::47::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::47::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::47::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::47::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::47::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::47::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::48::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::48::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::48::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::48::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::48::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::48::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::48::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/foreign-fragment.dat::48::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::0::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::0::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::0::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::0::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::0::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::0::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::0::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::0::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::1::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::1::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::1::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::1::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::1::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::1::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::1::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::1::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::2::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::2::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::2::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::2::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::2::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::2::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::2::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::2::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::3::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::3::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::3::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::3::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::3::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::3::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::3::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/isindex.dat::3::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::3::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::3::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::3::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::3::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::3::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::3::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::3::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::3::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::4::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::4::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::4::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::4::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::4::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::4::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::4::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::4::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::5::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::5::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::5::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::5::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::5::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::5::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::5::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/menuitem-element.dat::5::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/namespace-sensitivity.dat::0::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/namespace-sensitivity.dat::0::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/namespace-sensitivity.dat::0::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/namespace-sensitivity.dat::0::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/namespace-sensitivity.dat::0::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/namespace-sensitivity.dat::0::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/namespace-sensitivity.dat::0::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/namespace-sensitivity.dat::0::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::0::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::0::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::0::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::0::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::0::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::0::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::0::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::0::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::10::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::10::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::10::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::10::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::10::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::10::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::10::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::10::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::12::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::12::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::12::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::12::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::12::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::12::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::12::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::12::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::15::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::15::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::15::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::15::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::15::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::15::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::15::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::15::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::17::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::17::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::17::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::17::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::17::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::17::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::17::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::17::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::1::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::1::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::1::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::1::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::1::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::1::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::1::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::1::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::20::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::20::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::20::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::20::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::20::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::20::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::20::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::20::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::2::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::2::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::2::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::2::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::2::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::2::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::2::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::2::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::3::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::3::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::3::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::3::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::3::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::3::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::3::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::3::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::5::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::5::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::5::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::5::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::5::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::5::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::5::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::5::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::7::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::7::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::7::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::7::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::7::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::7::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::7::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/ruby.dat::7::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/adoption01.dat::0::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/adoption01.dat::0::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/adoption01.dat::0::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/adoption01.dat::0::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/adoption01.dat::0::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/adoption01.dat::0::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/adoption01.dat::0::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/adoption01.dat::0::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/ark.dat::0::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/ark.dat::0::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/ark.dat::0::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/ark.dat::0::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/ark.dat::0::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/ark.dat::0::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/ark.dat::0::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/ark.dat::0::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::0::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::0::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::0::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::0::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::0::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::0::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::0::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::0::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::1::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::1::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::1::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::1::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::1::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::1::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::1::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/scripted/webkit01.dat::1::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::0::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::0::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::0::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::0::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::0::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::0::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::0::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::0::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::100::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::100::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::100::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::100::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::100::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::100::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::100::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::100::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::101::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::101::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::101::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::101::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::101::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::101::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::101::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::101::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::102::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::102::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::102::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::102::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::102::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::102::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::102::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::102::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::103::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::103::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::103::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::103::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::103::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::103::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::103::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::103::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::104::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::104::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::104::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::104::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::104::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::104::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::104::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::104::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::105::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::105::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::105::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::105::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::105::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::105::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::105::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::105::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::106::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::106::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::106::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::106::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::106::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::106::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::106::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::106::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::107::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::107::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::107::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::107::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::107::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::107::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::107::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::107::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::10::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::10::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::10::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::10::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::10::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::10::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::10::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::10::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::11::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::11::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::11::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::11::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::11::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::11::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::11::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::11::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::12::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::12::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::12::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::12::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::12::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::12::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::12::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::12::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::13::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::13::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::13::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::13::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::13::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::13::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::13::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::13::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::14::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::14::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::14::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::14::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::14::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::14::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::14::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::14::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::15::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::15::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::15::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::15::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::15::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::15::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::15::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::15::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::16::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::16::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::16::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::16::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::16::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::16::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::16::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::16::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::17::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::17::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::17::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::17::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::17::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::17::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::17::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::17::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::18::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::18::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::18::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::18::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::18::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::18::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::18::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::18::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::19::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::19::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::19::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::19::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::19::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::19::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::19::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::19::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::1::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::1::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::1::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::1::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::1::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::1::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::1::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::1::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::20::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::20::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::20::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::20::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::20::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::20::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::20::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::20::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::21::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::21::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::21::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::21::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::21::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::21::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::21::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::21::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::22::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::22::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::22::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::22::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::22::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::22::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::22::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::22::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::23::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::23::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::23::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::23::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::23::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::23::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::23::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::23::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::24::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::24::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::24::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::24::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::24::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::24::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::24::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::24::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::25::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::25::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::25::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::25::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::25::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::25::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::25::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::25::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::26::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::26::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::26::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::26::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::26::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::26::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::26::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::26::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::27::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::27::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::27::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::27::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::27::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::27::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::27::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::27::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::28::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::28::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::28::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::28::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::28::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::28::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::28::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::28::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::29::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::29::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::29::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::29::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::29::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::29::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::29::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::29::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::2::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::2::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::2::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::2::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::2::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::2::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::2::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::2::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::30::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::30::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::30::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::30::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::30::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::30::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::30::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::30::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::31::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::31::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::31::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::31::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::31::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::31::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::31::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::31::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::32::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::32::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::32::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::32::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::32::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::32::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::32::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::32::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::33::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::33::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::33::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::33::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::33::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::33::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::33::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::33::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::34::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::34::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::34::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::34::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::34::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::34::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::34::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::34::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::35::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::35::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::35::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::35::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::35::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::35::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::35::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::35::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::36::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::36::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::36::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::36::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::36::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::36::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::36::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::36::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::37::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::37::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::37::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::37::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::37::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::37::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::37::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::37::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::38::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::38::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::38::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::38::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::38::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::38::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::38::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::38::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::3::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::3::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::3::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::3::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::3::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::3::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::3::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::3::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::40::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::40::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::40::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::40::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::40::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::40::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::40::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::40::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::41::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::41::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::41::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::41::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::41::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::41::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::41::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::41::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::42::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::42::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::42::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::42::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::42::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::42::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::42::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::42::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::43::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::43::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::43::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::43::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::43::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::43::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::43::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::43::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::44::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::44::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::44::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::44::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::44::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::44::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::44::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::44::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::45::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::45::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::45::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::45::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::45::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::45::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::45::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::45::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::46::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::46::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::46::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::46::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::46::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::46::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::46::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::46::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::47::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::47::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::47::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::47::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::47::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::47::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::47::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::47::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::48::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::48::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::48::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::48::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::48::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::48::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::48::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::48::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::49::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::49::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::49::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::49::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::49::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::49::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::49::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::49::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::4::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::4::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::4::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::4::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::4::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::4::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::4::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::4::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::50::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::50::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::50::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::50::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::50::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::50::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::50::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::50::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::51::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::51::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::51::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::51::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::51::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::51::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::51::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::51::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::52::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::52::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::52::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::52::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::52::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::52::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::52::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::52::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::53::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::53::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::53::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::53::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::53::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::53::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::53::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::53::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::54::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::54::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::54::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::54::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::54::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::54::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::54::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::54::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::55::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::55::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::55::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::55::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::55::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::55::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::55::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::55::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::56::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::56::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::56::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::56::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::56::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::56::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::56::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::56::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::57::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::57::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::57::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::57::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::57::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::57::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::57::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::57::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::58::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::58::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::58::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::58::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::58::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::58::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::58::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::58::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::59::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::59::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::59::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::59::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::59::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::59::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::59::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::59::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::5::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::5::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::5::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::5::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::5::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::5::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::5::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::5::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::60::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::60::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::60::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::60::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::60::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::60::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::60::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::60::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::61::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::61::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::61::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::61::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::61::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::61::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::61::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::61::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::62::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::62::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::62::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::62::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::62::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::62::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::62::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::62::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::63::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::63::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::63::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::63::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::63::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::63::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::63::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::63::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::64::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::64::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::64::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::64::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::64::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::64::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::64::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::64::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::65::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::65::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::65::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::65::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::65::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::65::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::65::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::65::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::66::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::66::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::66::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::66::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::66::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::66::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::66::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::66::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::67::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::67::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::67::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::67::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::67::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::67::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::67::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::67::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::68::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::68::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::68::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::68::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::68::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::68::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::68::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::68::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::69::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::69::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::69::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::69::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::69::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::69::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::69::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::69::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::6::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::6::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::6::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::6::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::6::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::6::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::6::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::6::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::70::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::70::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::70::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::70::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::70::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::70::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::70::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::70::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::71::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::71::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::71::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::71::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::71::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::71::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::71::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::71::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::72::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::72::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::72::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::72::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::72::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::72::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::72::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::72::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::73::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::73::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::73::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::73::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::73::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::73::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::73::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::73::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::74::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::74::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::74::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::74::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::74::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::74::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::74::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::74::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::75::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::75::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::75::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::75::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::75::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::75::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::75::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::75::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::76::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::76::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::76::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::76::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::76::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::76::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::76::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::76::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::77::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::77::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::77::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::77::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::77::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::77::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::77::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::77::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::78::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::78::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::78::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::78::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::78::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::78::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::78::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::78::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::79::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::79::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::79::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::79::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::79::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::79::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::79::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::79::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::80::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::80::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::80::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::80::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::80::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::80::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::80::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::80::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::81::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::81::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::81::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::81::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::81::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::81::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::81::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::81::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::82::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::82::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::82::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::82::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::82::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::82::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::82::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::82::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::83::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::83::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::83::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::83::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::83::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::83::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::83::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::83::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::84::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::84::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::84::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::84::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::84::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::84::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::84::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::84::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::85::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::85::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::85::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::85::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::85::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::85::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::85::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::85::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::86::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::86::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::86::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::86::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::86::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::86::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::86::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::86::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::87::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::87::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::87::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::87::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::87::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::87::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::87::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::87::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::88::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::88::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::88::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::88::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::88::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::88::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::88::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::88::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::89::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::89::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::89::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::89::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::89::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::89::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::89::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::89::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::8::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::8::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::8::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::8::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::8::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::8::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::8::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::8::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::90::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::90::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::90::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::90::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::90::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::90::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::90::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::90::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::91::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::91::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::91::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::91::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::91::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::91::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::91::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::91::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::92::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::92::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::92::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::92::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::92::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::92::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::92::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::92::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::93::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::93::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::93::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::93::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::93::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::93::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::93::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::93::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::94::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::94::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::94::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::94::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::94::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::94::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::94::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::94::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::95::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::95::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::95::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::95::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::95::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::95::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::95::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::95::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::96::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::96::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::96::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::96::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::96::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::96::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::96::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::96::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::97::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::97::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::97::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::97::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::97::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::97::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::97::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::97::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::98::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::98::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::98::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::98::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::98::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::98::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::98::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::98::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::99::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::99::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::99::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::99::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::99::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::99::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::99::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::99::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::9::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::9::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::9::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::9::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::9::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::9::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::9::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/template.dat::9::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::2::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::2::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::2::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::2::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::2::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::2::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::2::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::2::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::4::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::4::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::4::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::4::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::4::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::4::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::4::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::4::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::5::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::5::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::5::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::5::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::5::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::5::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::5::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::5::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::6::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::6::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::6::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::6::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::6::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::6::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::6::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests11.dat::6::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::14::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::14::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::14::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::14::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::14::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::14::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::14::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::14::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::17::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::17::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::17::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::17::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::17::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::17::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::17::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::17::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::7::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::7::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::7::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::7::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::7::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::7::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::7::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests19.dat::7::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::6::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::6::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::6::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::6::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::6::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::6::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::6::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::6::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::7::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::7::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::7::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::7::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::7::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::7::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::7::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests2.dat::7::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests25.dat::7::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests25.dat::7::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests25.dat::7::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests25.dat::7::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests25.dat::7::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests25.dat::7::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/tests25.dat::7::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/tests25.dat::7::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::14::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::14::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::14::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::14::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::14::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::14::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::14::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::14::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::15::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::15::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::15::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::15::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::15::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::15::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::15::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::15::lxml::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::16::DOM::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::16::DOM::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::16::ElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::16::ElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::16::cElementTree::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::16::cElementTree::parser::void-namespace': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::16::lxml::parser::namespaced': FAIL +u'html5lib/tests/testdata/tree-construction/webkit02.dat::16::lxml::parser::void-namespace': FAIL diff --git a/testing/web-platform/tests/tools/third_party/html5lib/.travis.yml b/testing/web-platform/tests/tools/third_party/html5lib/.travis.yml new file mode 100644 index 0000000000..5727e0947e --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/html5lib/.travis.yml @@ -0,0 +1,32 @@ +language: python +python: + - "pypy" + - "3.6" + - "3.5" + - "3.4" + - "3.3" + - "2.7" + +sudo: false + +cache: pip + +env: + global: + - PYTEST_COMMAND="coverage run -m pytest" + matrix: + - TOXENV=optional + - TOXENV=base + - TOXENV=six19-optional + +install: + - pip install tox codecov + +script: + - tox + +after_script: + - python debug-info.py + +after_success: + - codecov diff --git a/testing/web-platform/tests/tools/third_party/html5lib/AUTHORS.rst b/testing/web-platform/tests/tools/third_party/html5lib/AUTHORS.rst new file mode 100644 index 0000000000..904013908d --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/html5lib/AUTHORS.rst @@ -0,0 +1,66 @@ +Credits +======= + +``html5lib`` is written and maintained by: + +- James Graham +- Sam Sneddon +- Łukasz Langa +- Will Kahn-Greene + + +Patches and suggestions +----------------------- +(In chronological order, by first commit:) + +- Anne van Kesteren +- Lachlan Hunt +- lantis63 +- Sam Ruby +- Thomas Broyer +- Tim Fletcher +- Mark Pilgrim +- Ryan King +- Philip Taylor +- Edward Z. Yang +- fantasai +- Philip Jägenstedt +- Ms2ger +- Mohammad Taha Jahangir +- Andy Wingo +- Andreas Madsack +- Karim Valiev +- Juan Carlos Garcia Segovia +- Mike West +- Marc DM +- Simon Sapin +- Michael[tm] Smith +- Ritwik Gupta +- Marc Abramowitz +- Tony Lopes +- lilbludevil +- Kevin +- Drew Hubl +- Austin Kumbera +- Jim Baker +- Jon Dufresne +- Donald Stufft +- Alex Gaynor +- Nik Nyby +- Jakub Wilk +- Sigmund Cherem +- Gabi Davar +- Florian Mounier +- neumond +- Vitalik Verhovodov +- Kovid Goyal +- Adam Chainz +- John Vandenberg +- Eric Amorde +- Benedikt Morbach +- Jonathan Vanasco +- Tom Most +- Ville Skyttä +- Hugo van Kemenade +- Mark Vasilkov + diff --git a/testing/web-platform/tests/tools/third_party/html5lib/CHANGES.rst b/testing/web-platform/tests/tools/third_party/html5lib/CHANGES.rst new file mode 100644 index 0000000000..fcb22475cd --- /dev/null +++ b/testing/web-platform/tests/tools/third_party/html5lib/CHANGES.rst @@ -0,0 +1,359 @@ +Change Log +---------- + +1.1 +~~~ + +UNRELEASED + +Breaking changes: + +* Drop support for Python 3.3. (#358) +* Drop support for Python 3.4. (#421) + +Deprecations: + +* Deprecate the ``html5lib`` sanitizer (``html5lib.serialize(sanitize=True)`` and + ``html5lib.filters.sanitizer``). We recommend users migrate to `Bleach + `. Please let us know if Bleach doesn't suffice for your + use. (#443) + +Other changes: + +* Try to import from ``collections.abc`` to remove DeprecationWarning and ensure + ``html5lib`` keeps working in future Python versions. (#403) +* Drop optional ``datrie`` dependency. (#442) + + +1.0.1 +~~~~~ + +Released on December 7, 2017 + +Breaking changes: + +* Drop support for Python 2.6. (#330) (Thank you, Hugo, Will Kahn-Greene!) +* Remove ``utils/spider.py`` (#353) (Thank you, Jon Dufresne!) + +Features: + +* Improve documentation. (#300, #307) (Thank you, Jon Dufresne, Tom Most, + Will Kahn-Greene!) +* Add iframe seamless boolean attribute. (Thank you, Ritwik Gupta!) +* Add itemscope as a boolean attribute. (#194) (Thank you, Jonathan Vanasco!) +* Support Python 3.6. (#333) (Thank you, Jon Dufresne!) +* Add CI support for Windows using AppVeyor. (Thank you, John Vandenberg!) +* Improve testing and CI and add code coverage (#323, #334), (Thank you, Jon + Dufresne, John Vandenberg, Sam Sneddon, Will Kahn-Greene!) +* Semver-compliant version number. + +Bug fixes: + +* Add support for setuptools < 18.5 to support environment markers. (Thank you, + John Vandenberg!) +* Add explicit dependency for six >= 1.9. (Thank you, Eric Amorde!) +* Fix regexes to work with Python 3.7 regex adjustments. (#318, #379) (Thank + you, Benedikt Morbach, Ville Skyttä, Mark Vasilkov!) +* Fix alphabeticalattributes filter namespace bug. (#324) (Thank you, Will + Kahn-Greene!) +* Include license file in generated wheel package. (#350) (Thank you, Jon + Dufresne!) +* Fix annotation-xml typo. (#339) (Thank you, Will Kahn-Greene!) +* Allow uppercase hex chararcters in CSS colour check. (#377) (Thank you, + Komal Dembla, Hugo!) + + +1.0 +~~~ + +Released and unreleased on December 7, 2017. Badly packaged release. + + +0.999999999/1.0b10 +~~~~~~~~~~~~~~~~~~ + +Released on July 15, 2016 + +* Fix attribute order going to the tree builder to be document order + instead of reverse document order(!). + + +0.99999999/1.0b9 +~~~~~~~~~~~~~~~~ + +Released on July 14, 2016 + +* **Added ordereddict as a mandatory dependency on Python 2.6.** + +* Added ``lxml``, ``genshi``, ``datrie``, ``charade``, and ``all`` + extras that will do the right thing based on the specific + interpreter implementation. + +* Now requires the ``mock`` package for the testsuite. + +* Cease supporting DATrie under PyPy. + +* **Remove PullDOM support, as this hasn't ever been properly + tested, doesn't entirely work, and as far as I can tell is + completely unused by anyone.** + +* Move testsuite to ``py.test``. + +* **Fix #124: move to webencodings for decoding the input byte stream; + this makes html5lib compliant with the Encoding Standard, and + introduces a required dependency on webencodings.** + +* **Cease supporting Python 3.2 (in both CPython and PyPy forms).** + +* **Fix comments containing double-dash with lxml 3.5 and above.** + +* **Use scripting disabled by default (as we don't implement + scripting).** + +* **Fix #11, avoiding the XSS bug potentially caused by serializer + allowing attribute values to be escaped out of in old browser versions, + changing the quote_attr_values option on serializer to take one of + three values, "always" (the old True value), "legacy" (the new option, + and the new default), and "spec" (the old False value, and the old + default).** + +* **Fix #72 by rewriting the sanitizer to apply only to treewalkers + (instead of the tokenizer); as such, this will require amending all + callers of it to use it via the treewalker API.** + +* **Drop support of charade, now that chardet is supported once more.** + +* **Replace the charset keyword argument on parse and related methods + with a set of keyword arguments: override_encoding, transport_encoding, + same_origin_parent_encoding, likely_encoding, and default_encoding.** + +* **Move filters._base, treebuilder._base, and treewalkers._base to .base + to clarify their status as public.** + +* **Get rid of the sanitizer package. Merge sanitizer.sanitize into the + sanitizer.htmlsanitizer module and move that to sanitizer. This means + anyone who used sanitizer.sanitize or sanitizer.HTMLSanitizer needs no + code changes.** + +* **Rename treewalkers.lxmletree to .etree_lxml and + treewalkers.genshistream to .genshi to have a consistent API.** + +* Move a whole load of stuff (inputstream, ihatexml, trie, tokenizer, + utils) to be underscore prefixed to clarify their status as private. + + +0.9999999/1.0b8 +~~~~~~~~~~~~~~~ + +Released on September 10, 2015 + +* Fix #195: fix the sanitizer to drop broken URLs (it threw an + exception between 0.9999 and 0.999999). + + +0.999999/1.0b7 +~~~~~~~~~~~~~~ + +Released on July 7, 2015 + +* Fix #189: fix the sanitizer to allow relative URLs again (as it did + prior to 0.9999/1.0b5). + + +0.99999/1.0b6 +~~~~~~~~~~~~~ + +Released on April 30, 2015 + +* Fix #188: fix the sanitizer to not throw an exception when sanitizing + bogus data URLs. + + +0.9999/1.0b5 +~~~~~~~~~~~~ + +Released on April 29, 2015 + +* Fix #153: Sanitizer fails to treat some attributes as URLs. Despite how + this sounds, this has no known security implications. No known version + of IE (5.5 to current), Firefox (3 to current), Safari (6 to current), + Chrome (1 to current), or Opera (12 to current) will run any script + provided in these attributes. + +* Pass error message to the ParseError exception in strict parsing mode. + +* Allow data URIs in the sanitizer, with a whitelist of content-types. + +* Add support for Python implementations that don't support lone + surrogates (read: Jython). Fixes #2. + +* Remove localization of error messages. This functionality was totally + unused (and untested that everything was localizable), so we may as + well follow numerous browsers in not supporting translating technical + strings. + +* Expose treewalkers.pprint as a public API. + +* Add a documentEncoding property to HTML5Parser, fix #121. + + +0.999 +~~~~~ + +Released on December 23, 2013 + +* Fix #127: add work-around for CPython issue #20007: .read(0) on + http.client.HTTPResponse drops the rest of the content. + +* Fix #115: lxml treewalker can now deal with fragments containing, at + their root level, text nodes with non-ASCII characters on Python 2. + + +0.99 +~~~~ + +Released on September 10, 2013 + +* No library changes from 1.0b3; released as 0.99 as pip has changed + behaviour from 1.4 to avoid installing pre-release versions per + PEP 440. + + +1.0b3 +~~~~~ + +Released on July 24, 2013 + +* Removed ``RecursiveTreeWalker`` from ``treewalkers._base``. Any + implementation using it should be moved to + ``NonRecursiveTreeWalker``, as everything bundled with html5lib has + for years. + +* Fix #67 so that ``BufferedStream`` to correctly returns a bytes + object, thereby fixing any case where html5lib is passed a + non-seekable RawIOBase-like object. + + +1.0b2 +~~~~~ + +Released on June 27, 2013 + +* Removed reordering of attributes within the serializer. There is now + an ``alphabetical_attributes`` option which preserves the previous + behaviour through a new filter. This allows attribute order to be + preserved through html5lib if the tree builder preserves order. + +* Removed ``dom2sax`` from DOM treebuilders. It has been replaced by + ``treeadapters.sax.to_sax`` which is generic and supports any + treewalker; it also resolves all known bugs with ``dom2sax``. + +* Fix treewalker assertions on hitting bytes strings on + Python 2. Previous to 1.0b1, treewalkers coped with mixed + bytes/unicode data on Python 2; this reintroduces this prior + behaviour on Python 2. Behaviour is unchanged on Python 3. + + +1.0b1 +~~~~~ + +Released on May 17, 2013 + +* Implementation updated to implement the `HTML specification + `_ as of 5th May + 2013 (`SVN `_ revision r7867). + +* Python 3.2+ supported in a single codebase using the ``six`` library. + +* Removed support for Python 2.5 and older. + +* Removed the deprecated Beautiful Soup 3 treebuilder. + ``beautifulsoup4`` can use ``html5lib`` as a parser instead. Note that + since it doesn't support namespaces, foreign content like SVG and + MathML is parsed incorrectly. + +* Removed ``simpletree`` from the package. The default tree builder is + now ``etree`` (using the ``xml.etree.cElementTree`` implementation if + available, and ``xml.etree.ElementTree`` otherwise). + +* Removed the ``XHTMLSerializer`` as it never actually guaranteed its + output was well-formed XML, and hence provided little of use. + +* Removed default DOM treebuilder, so ``html5lib.treebuilders.dom`` is no + longer supported. ``html5lib.treebuilders.getTreeBuilder("dom")`` will + return the default DOM treebuilder, which uses ``xml.dom.minidom``. + +* Optional heuristic character encoding detection now based on + ``charade`` for Python 2.6 - 3.3 compatibility. + +* Optional ``Genshi`` treewalker support fixed. + +* Many bugfixes, including: + + * #33: null in attribute value breaks XML AttValue; + + * #4: nested, indirect descendant,