diff -Nru python-hyperframe-5.2.0/CHANGELOG.rst python-hyperframe-6.0.0/CHANGELOG.rst --- python-hyperframe-5.2.0/CHANGELOG.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/CHANGELOG.rst 2020-09-06 10:18:31.000000000 +0000 @@ -0,0 +1,209 @@ +Release History +=============== + +6.0.0 (2020-09-06) +--------- + +**API Changes (Backward-incompatible)** + +- Introduce ``HyperframeError`` base exception class for all errors raised within hyperframe. +- Change exception base class of ``UnknownFrameError`` to ``HyperframeError`` +- Change exception base class of ``InvalidPaddingError`` to ``HyperframeError`` +- Change exception base class of ``InvalidFrameError`` to ``HyperframeError`` +- Invalid frames with wrong stream id (zero vs. non-zero) now raise ``InvalidDataError``. +- Invalid SETTINGS frames (non-empty but ACK) now raise ``InvalidDataError``. +- Invalid ALTSVC frames with non-bytestring field or origin now raise ``InvalidDataError``. + +**API Changes (Backward-compatible)** + +- Deprecate ``total_padding`` - use `pad_length` instead. +- Improve repr() output for all frame classes. +- Introduce Frame.explain(data) for quick introspection of raw data. + +**Bugfixes** + +- Fixed padding parsing for ``PushPromiseFrame``. +- Fixed unchecked frame length for ``PriorityFrame``. It now correctly raises ``InvalidFrameError``. +- Fixed promised stream id validation for ``PushPromiseFrame``. It now raises ``InvalidDataError``. +- Fixed unchecked frame length for ``WindowUpdateFrame``. It now correctly raises ``InvalidFrameError``. +- Fixed window increment value range validation. It now raises ``InvalidDataError``. +- Fixed parsing of ``SettingsFrame`` with mutual exclusion of ACK flag and payload. + +**Other Changes** + +- Removed support for Python 2.7, 3.4, 3.5, pypy. +- Added support for Python 3.8. + +5.2.0 (2019-01-18) +------------------ + +**API Changes (Backward-compatible)** + +- Add a new ENABLE_CONNECT_PROTOCOL settings parameter. + +**Other Changes** + +- Fix collections.abc deprecation. +- Drop support for Python 3.3 and support 3.7. + +5.1.0 (2017-04-24) +------------------ + +**API Changes (Backward-compatible)** + +- Added support for ``DataFrame.data`` being a ``memoryview`` object. + +5.0.0 (2017-03-07) +------------------ + +**Backwards Incompatible API Changes** + +- Added support for unknown extension frames. These will be returned in the new + ``ExtensionFrame`` object. The flag information for these frames is persisted + in ``flag_byte`` if needed. + +4.0.2 (2017-02-20) +------------------ + +**Bugfixes** + +- Fixed AltSvc stream association, which was incorrectly set to ``'both'``: + should have been ``'either'``. +- Fixed a bug where stream IDs on received frames were allowed to be 32-bit, + instead of 31-bit. +- Fixed a bug with frames that had the ``PADDING`` flag set but zero-length + padding, whose flow-controlled length was calculated wrongly. +- Miscellaneous performance improvements to serialization and parsing logic. + +4.0.1 (2016-03-13) +------------------ + +**Bugfixes** + +- Fixed bug with the repr of ``AltSvcFrame``, where building it could throw + exceptions if the frame had been received from the network. + +4.0.0 (2016-03-13) +------------------ + +**Backwards Incompatible API Changes** + +- Updated old ALTSVC frame definition to match the newly specified RFC 7838. +- Remove BLOCKED frame, which was never actually specified. +- Removed previously deprecated ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and + ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``. + +3.2.0 (2016-02-02) +------------------ + +**API Changes (Backward-compatible)** + +- Invalid PING frame bodies now raise ``InvalidFrameError``, not + ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass. +- Invalid RST_STREAM frame bodies now raise ``InvalidFramError``, not + ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass. +- Canonicalized the names of ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and + ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE`` to match their peers, by + adding new properties ``SettingsFrame.MAX_FRAME_SIZE`` and + ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``. The old names are still + present, but will be deprecated in 4.0.0. + +**Bugfixes** + +- The change in ``3.1.0`` that ensured that ``InvalidFrameError`` would be + thrown did not affect certain invalid values in ALT_SVC frames. This has been + fixed: ``ValueError`` will no longer be thrown from invalid ALT_SVC bodies. + +3.1.1 (2016-01-18) +------------------ + +**Bugfixes** + +- Correctly error when receiving Ping frames that have insufficient data. + +3.1.0 (2016-01-13) +------------------ + +**API Changes** + +- Added new ``InvalidFrameError`` that is thrown instead of ``struct.error`` + when parsing a frame. + +**Bugfixes** + +- Fixed error when trying to serialize frames that use Priority information + with the defaults for that information. +- Fixed errors when displaying the repr of frames with non-printable bodies. + +3.0.1 (2016-01-08) +------------------ + +**Bugfixes** + +- Fix issue where unpadded DATA, PUSH_PROMISE and HEADERS frames that had empty + bodies would raise ``InvalidPaddingError`` exceptions when parsed. + +3.0.0 (2016-01-08) +------------------ + +**Backwards Incompatible API Changes** + +- Parsing padded frames that have invalid padding sizes now throws an + ``InvalidPaddingError``. + +2.2.0 (2015-10-15) +------------------ + +**API Changes** + +- When an unknown frame is encountered, ``parse_frame_header`` now throws a + ``ValueError`` subclass: ``UnknownFrameError``. This subclass contains the + frame type and the length of the frame body. + +2.1.0 (2015-10-06) +------------------ + +**API Changes** + +- Frames parsed from binary data now carry a ``body_len`` attribute that + matches the frame length (minus the frame header). + +2.0.0 (2015-09-21) +------------------ + +**API Changes** + +- Attempting to parse unrecognised frames now throws ``ValueError`` instead of + ``KeyError``. Thanks to @Kriechi! +- Flags are now validated for correctness, preventing setting flags that + ``hyperframe`` does not recognise and that would not serialize. Thanks to + @mhils! +- Frame properties can now be initialized in the constructors. Thanks to @mhils + and @Kriechi! +- Frames that cannot be sent on a stream now have their stream ID defaulted + to ``0``. Thanks to @Kriechi! + +**Other Changes** + +- Frames have a more useful repr. Thanks to @mhils! + +1.1.1 (2015-07-20) +------------------ + +- Fix a bug where ``FRAME_MAX_LEN`` was one byte too small. + +1.1.0 (2015-06-28) +------------------ + +- Add ``body_len`` property to frames to enable introspection of the actual + frame length. Thanks to @jdecuyper! + +1.0.1 (2015-06-27) +------------------ + +- Fix bug where the frame header would have an incorrect length added to it. + +1.0.0 (2015-04-12) +------------------ + +- Initial extraction from hyper. diff -Nru python-hyperframe-5.2.0/debian/changelog python-hyperframe-6.0.0/debian/changelog --- python-hyperframe-5.2.0/debian/changelog 2019-12-19 17:28:51.000000000 +0000 +++ python-hyperframe-6.0.0/debian/changelog 2020-11-29 08:44:10.000000000 +0000 @@ -1,3 +1,16 @@ +python-hyperframe (6.0.0-1) unstable; urgency=medium + + * Maintain within the Python team (Closes: #947013). + * Update copyright years. + * New upstream release. + * Bump debhelper from deprecated 9 to 12. + * Set debhelper-compat version in Build-Depends. + * Set upstream metadata fields: Bug-Database, Bug-Submit, Repository, + Repository-Browse. + * Update standards version to 4.5.0, no changes needed. + + -- Andrej Shadura Sun, 29 Nov 2020 09:44:10 +0100 + python-hyperframe (5.2.0-4) unstable; urgency=medium * Update changelog for 5.2.0-3 release diff -Nru python-hyperframe-5.2.0/debian/compat python-hyperframe-6.0.0/debian/compat --- python-hyperframe-5.2.0/debian/compat 2019-12-19 17:28:51.000000000 +0000 +++ python-hyperframe-6.0.0/debian/compat 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -9 diff -Nru python-hyperframe-5.2.0/debian/control python-hyperframe-6.0.0/debian/control --- python-hyperframe-5.2.0/debian/control 2019-12-19 17:28:51.000000000 +0000 +++ python-hyperframe-6.0.0/debian/control 2020-11-29 08:44:10.000000000 +0000 @@ -1,9 +1,10 @@ Source: python-hyperframe Section: python Priority: optional -Maintainer: Debian QA Group -Build-Depends: debhelper (>= 9), dh-python, python3-all, python3-setuptools, python3-pytest -Standards-Version: 4.3.0 +Maintainer: Debian Python Team +Uploaders: Andrej Shadura +Build-Depends: debhelper-compat (= 12), dh-python, python3-all, python3-setuptools, python3-pytest +Standards-Version: 4.5.0 Homepage: https://github.com/Lukasa/hyper Vcs-Git: https://salsa.debian.org/debian/python-hyperframe.git Vcs-Browser: https://salsa.debian.org/debian/python-hyperframe diff -Nru python-hyperframe-5.2.0/debian/copyright python-hyperframe-6.0.0/debian/copyright --- python-hyperframe-5.2.0/debian/copyright 2019-12-19 17:28:51.000000000 +0000 +++ python-hyperframe-6.0.0/debian/copyright 2020-11-29 08:44:10.000000000 +0000 @@ -3,11 +3,11 @@ Source: https://github.com/Lukasa/hyper Files: * -Copyright: 2014 Cory Benfield +Copyright: 2014—2020 Cory Benfield License: MIT Files: debian/* -Copyright: 2016 Sebastien Delafond +Copyright: 2016—2019 Sebastien Delafond License: MIT License: MIT diff -Nru python-hyperframe-5.2.0/debian/rules python-hyperframe-6.0.0/debian/rules --- python-hyperframe-5.2.0/debian/rules 2019-12-19 17:28:51.000000000 +0000 +++ python-hyperframe-6.0.0/debian/rules 2020-11-29 08:44:10.000000000 +0000 @@ -6,4 +6,4 @@ dh $@ --with python3 --buildsystem=pybuild override_dh_installchangelogs: - dh_installchangelogs HISTORY.rst + dh_installchangelogs CHANGELOG.rst diff -Nru python-hyperframe-5.2.0/debian/upstream/metadata python-hyperframe-6.0.0/debian/upstream/metadata --- python-hyperframe-5.2.0/debian/upstream/metadata 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/debian/upstream/metadata 2020-11-29 08:44:10.000000000 +0000 @@ -0,0 +1,5 @@ +--- +Bug-Database: https://github.com/python-hyper/hyperframe/issues +Bug-Submit: https://github.com/python-hyper/hyperframe/issues/new +Repository: https://github.com/python-hyper/hyperframe.git +Repository-Browse: https://github.com/python-hyper/hyperframe diff -Nru python-hyperframe-5.2.0/docs/make.bat python-hyperframe-6.0.0/docs/make.bat --- python-hyperframe-5.2.0/docs/make.bat 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/docs/make.bat 2020-08-02 16:16:59.000000000 +0000 @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +if "%1" == "" goto help + +%SPHINXBUILD% >NUL 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 +) + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff -Nru python-hyperframe-5.2.0/docs/Makefile python-hyperframe-6.0.0/docs/Makefile --- python-hyperframe-5.2.0/docs/Makefile 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/docs/Makefile 2020-08-02 16:16:59.000000000 +0000 @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff -Nru python-hyperframe-5.2.0/docs/source/api.rst python-hyperframe-6.0.0/docs/source/api.rst --- python-hyperframe-5.2.0/docs/source/api.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/docs/source/api.rst 2020-08-19 21:43:22.000000000 +0000 @@ -0,0 +1,88 @@ +hyperframe API +============== + +This document provides the hyperframe API. + +All frame classes are subclasses of :class:`Frame `, +and provide the methods and attributes defined there. Additionally, some frames +use the :class:`Priority ` and +:class:`Padding ` mixins, and make the methods and +attributes defined on *those* mixins available as well. + +Rather than clutter up the documentation repeatedly documenting those methods +and objects, we explicitly show the inheritance hierarchy of the frames: don't +forget to consult the parent classes before deciding if a method or attribute +you need is not present! + +.. autoclass:: hyperframe.frame.Frame + :members: + +.. autoclass:: hyperframe.frame.Padding + :members: + +.. autoclass:: hyperframe.frame.Priority + :members: + +.. autoclass:: hyperframe.frame.DataFrame + :show-inheritance: + :members: + +.. autoclass:: hyperframe.frame.PriorityFrame + :show-inheritance: + :members: + +.. autoclass:: hyperframe.frame.RstStreamFrame + :show-inheritance: + :members: + +.. autoclass:: hyperframe.frame.SettingsFrame + :show-inheritance: + :members: + +.. autoclass:: hyperframe.frame.PushPromiseFrame + :show-inheritance: + :members: + +.. autoclass:: hyperframe.frame.PingFrame + :show-inheritance: + :members: + +.. autoclass:: hyperframe.frame.GoAwayFrame + :show-inheritance: + :members: + +.. autoclass:: hyperframe.frame.WindowUpdateFrame + :show-inheritance: + :members: + +.. autoclass:: hyperframe.frame.HeadersFrame + :show-inheritance: + :members: + +.. autoclass:: hyperframe.frame.ContinuationFrame + :show-inheritance: + :members: + +.. autoclass:: hyperframe.frame.ExtensionFrame + :show-inheritance: + :members: + +.. autodata:: hyperframe.frame.FRAMES + +Exceptions +---------- + +.. autoclass:: hyperframe.exceptions.HyperframeError + :members: + +.. autoclass:: hyperframe.exceptions.UnknownFrameError + :members: + +.. autoclass:: hyperframe.exceptions.InvalidPaddingError + :members: + +.. autoclass:: hyperframe.exceptions.InvalidFrameError + :members: + +.. autoclass:: hyperframe.exceptions.InvalidDataError + :members: diff -Nru python-hyperframe-5.2.0/docs/source/conf.py python-hyperframe-6.0.0/docs/source/conf.py --- python-hyperframe-5.2.0/docs/source/conf.py 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/docs/source/conf.py 2020-09-06 10:19:36.000000000 +0000 @@ -0,0 +1,72 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# 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. +# +import os +import sys +import re + +sys.path.insert(0, os.path.abspath('../..')) + +PROJECT_ROOT = os.path.dirname(__file__) +# Get the version +version_regex = r'__version__ = ["\']([^"\']*)["\']' +with open(os.path.join(PROJECT_ROOT, '../../', 'src/hyperframe/__init__.py')) as file_: + text = file_.read() + match = re.search(version_regex, text) + version = match.group(1) + + +# -- Project information ----------------------------------------------------- + +project = 'hyperframe' +copyright = '2020, Cory Benfield' +author = 'Cory Benfield' +release = version + +# -- General configuration --------------------------------------------------- + +# 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'] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = [] + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'python': ('https://docs.python.org/', None), +} + +master_doc = 'index' + + +# -- 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' + +# 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'] diff -Nru python-hyperframe-5.2.0/docs/source/index.rst python-hyperframe-6.0.0/docs/source/index.rst --- python-hyperframe-5.2.0/docs/source/index.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/docs/source/index.rst 2020-07-22 09:31:33.000000000 +0000 @@ -0,0 +1,34 @@ +hyperframe: HTTP/2 Framing for Python +===================================== + +hyperframe is a pure-Python tool for working with HTTP/2 frames. This library +allows you to create, serialize, and parse HTTP/2 frames. + +Working with it is easy: + +.. code-block:: python + + import hyperframe.frame + + f = hyperframe.frame.DataFrame(stream_id=5) + f.data = b'some binary data' + f.flags.add('END_STREAM') + f.flags.add('PADDED') + f.padding_length = 30 + + data = f.serialize() + + new_frame, length = hyperframe.frame.Frame.parse_frame_header(data[:9]) + new_frame.parse_body(memoryview(data[9:9 + length])) + +hyperframe is pure-Python, contains no external dependencies, and runs on a +wide variety of Python interpreters and platforms. Made available under the MIT +license, why write your own frame parser? + +Contents: + +.. toctree:: + :maxdepth: 2 + + installation + api diff -Nru python-hyperframe-5.2.0/docs/source/installation.rst python-hyperframe-6.0.0/docs/source/installation.rst --- python-hyperframe-5.2.0/docs/source/installation.rst 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/docs/source/installation.rst 2016-01-10 17:43:09.000000000 +0000 @@ -0,0 +1,21 @@ +Installing hyperframe +===================== + +hyperframe is trivial to install from the Python Package Index. Simply run: + +.. code-block:: console + + $ pip install hyperframe + +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 + +hyperframe has no external dependencies. + + + +.. _our GitHub page: https://github.com/python-hyper/hyperframe diff -Nru python-hyperframe-5.2.0/.gitmodules python-hyperframe-6.0.0/.gitmodules --- python-hyperframe-5.2.0/.gitmodules 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/.gitmodules 2020-07-29 22:16:00.000000000 +0000 @@ -0,0 +1,3 @@ +[submodule "test/http2-frame-test-case"] + path = test/http2-frame-test-case + url = https://github.com/http2jp/http2-frame-test-case.git diff -Nru python-hyperframe-5.2.0/HISTORY.rst python-hyperframe-6.0.0/HISTORY.rst --- python-hyperframe-5.2.0/HISTORY.rst 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/HISTORY.rst 1970-01-01 00:00:00.000000000 +0000 @@ -1,179 +0,0 @@ -Release History -=============== - -6.0.0dev0 ---------- - -5.2.0 (2019-01-18) ------------------- - -**API Changes (Backward-compatible)** - -- Add a new ENABLE_CONNECT_PROTOCOL settings paramter. - -**Other Changes** - -- Fix collections.abc deprecation. -- Drop support for Python 3.3 and support 3.7. - -5.1.0 (2017-04-24) ------------------- - -**API Changes (Backward-compatible)** - -- Added support for ``DataFrame.data`` being a ``memoryview`` object. - -5.0.0 (2017-03-07) ------------------- - -**Backwards Incompatible API Changes** - -- Added support for unknown extension frames. These will be returned in the new - ``ExtensionFrame`` object. The flag information for these frames is persisted - in ``flag_byte`` if needed. - -4.0.2 (2017-02-20) ------------------- - -**Bugfixes** - -- Fixed AltSvc stream association, which was incorrectly set to ``'both'``: - should have been ``'either'``. -- Fixed a bug where stream IDs on received frames were allowed to be 32-bit, - instead of 31-bit. -- Fixed a bug with frames that had the ``PADDING`` flag set but zero-length - padding, whose flow-controlled length was calculated wrongly. -- Miscellaneous performance improvements to serialization and parsing logic. - -4.0.1 (2016-03-13) ------------------- - -**Bugfixes** - -- Fixed bug with the repr of ``AltSvcFrame``, where building it could throw - exceptions if the frame had been received from the network. - -4.0.0 (2016-03-13) ------------------- - -**Backwards Incompatible API Changes** - -- Updated old ALTSVC frame definition to match the newly specified RFC 7838. -- Remove BLOCKED frame, which was never actually specified. -- Removed previously deprecated ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and - ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``. - -3.2.0 (2016-02-02) ------------------- - -**API Changes (Backward-compatible)** - -- Invalid PING frame bodies now raise ``InvalidFrameError``, not - ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass. -- Invalid RST_STREAM frame bodies now raise ``InvalidFramError``, not - ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass. -- Canonicalized the names of ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and - ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE`` to match their peers, by - adding new properties ``SettingsFrame.MAX_FRAME_SIZE`` and - ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``. The old names are still - present, but will be deprecated in 4.0.0. - -**Bugfixes** - -- The change in ``3.1.0`` that ensured that ``InvalidFrameError`` would be - thrown did not affect certain invalid values in ALT_SVC frames. This has been - fixed: ``ValueError`` will no longer be thrown from invalid ALT_SVC bodies. - -3.1.1 (2016-01-18) ------------------- - -**Bugfixes** - -- Correctly error when receiving Ping frames that have insufficient data. - -3.1.0 (2016-01-13) ------------------- - -**API Changes** - -- Added new ``InvalidFrameError`` that is thrown instead of ``struct.error`` - when parsing a frame. - -**Bugfixes** - -- Fixed error when trying to serialize frames that use Priority information - with the defaults for that information. -- Fixed errors when displaying the repr of frames with non-printable bodies. - -3.0.1 (2016-01-08) ------------------- - -**Bugfixes** - -- Fix issue where unpadded DATA, PUSH_PROMISE and HEADERS frames that had empty - bodies would raise ``InvalidPaddingError`` exceptions when parsed. - -3.0.0 (2016-01-08) ------------------- - -**Backwards Incompatible API Changes** - -- Parsing padded frames that have invalid padding sizes now throws an - ``InvalidPaddingError``. - -2.2.0 (2015-10-15) ------------------- - -**API Changes** - -- When an unknown frame is encountered, ``parse_frame_header`` now throws a - ``ValueError`` subclass: ``UnknownFrameError``. This subclass contains the - frame type and the length of the frame body. - -2.1.0 (2015-10-06) ------------------- - -**API Changes** - -- Frames parsed from binary data now carry a ``body_len`` attribute that - matches the frame length (minus the frame header). - -2.0.0 (2015-09-21) ------------------- - -**API Changes** - -- Attempting to parse unrecognised frames now throws ``ValueError`` instead of - ``KeyError``. Thanks to @Kriechi! -- Flags are now validated for correctness, preventing setting flags that - ``hyperframe`` does not recognise and that would not serialize. Thanks to - @mhils! -- Frame properties can now be initialized in the constructors. Thanks to @mhils - and @Kriechi! -- Frames that cannot be sent on a stream now have their stream ID defaulted - to ``0``. Thanks to @Kriechi! - -**Other Changes** - -- Frames have a more useful repr. Thanks to @mhils! - -1.1.1 (2015-07-20) ------------------- - -- Fix a bug where ``FRAME_MAX_LEN`` was one byte too small. - -1.1.0 (2015-06-28) ------------------- - -- Add ``body_len`` property to frames to enable introspection of the actual - frame length. Thanks to @jdecuyper! - -1.0.1 (2015-06-27) ------------------- - -- Fix bug where the frame header would have an incorrect length added to it. - -1.0.0 (2015-04-12) ------------------- - -- Initial extraction from hyper. diff -Nru python-hyperframe-5.2.0/hyperframe/exceptions.py python-hyperframe-6.0.0/hyperframe/exceptions.py --- python-hyperframe-5.2.0/hyperframe/exceptions.py 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/hyperframe/exceptions.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,41 +0,0 @@ -# -*- coding: utf-8 -*- -""" -hyperframe/exceptions -~~~~~~~~~~~~~~~~~~~~~ - -Defines the exceptions that can be thrown by hyperframe. -""" - - -class UnknownFrameError(ValueError): - """ - An frame of unknown type was received. - """ - def __init__(self, frame_type, length): - #: The type byte of the unknown frame that was received. - self.frame_type = frame_type - - #: The length of the data portion of the unknown frame. - self.length = length - - def __str__(self): - return ( - "UnknownFrameError: Unknown frame type 0x%X received, " - "length %d bytes" % (self.frame_type, self.length) - ) - - -class InvalidPaddingError(ValueError): - """ - A frame with invalid padding was received. - """ - pass - - -class InvalidFrameError(ValueError): - """ - Parsing a frame failed because the data was not laid out appropriately. - - .. versionadded:: 3.0.2 - """ - pass diff -Nru python-hyperframe-5.2.0/hyperframe/flags.py python-hyperframe-6.0.0/hyperframe/flags.py --- python-hyperframe-5.2.0/hyperframe/flags.py 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/hyperframe/flags.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -""" -hyperframe/flags -~~~~~~~~~~~~~~~~ - -Defines basic Flag and Flags data structures. -""" -import collections - -try: - from collections.abc import MutableSet -except ImportError: # pragma: no cover - # Python 2.7 compatibility - from collections import MutableSet - -Flag = collections.namedtuple("Flag", ["name", "bit"]) - - -class Flags(MutableSet): - """ - A simple MutableSet implementation that will only accept known flags as - elements. - - Will behave like a regular set(), except that a ValueError will be thrown - when .add()ing unexpected flags. - """ - def __init__(self, defined_flags): - self._valid_flags = set(flag.name for flag in defined_flags) - self._flags = set() - - def __contains__(self, x): - return self._flags.__contains__(x) - - def __iter__(self): - return self._flags.__iter__() - - def __len__(self): - return self._flags.__len__() - - def discard(self, value): - return self._flags.discard(value) - - def add(self, value): - if value not in self._valid_flags: - raise ValueError( - "Unexpected flag: {}. Valid flags are: {}".format( - value, self._valid_flags - ) - ) - return self._flags.add(value) diff -Nru python-hyperframe-5.2.0/hyperframe/frame.py python-hyperframe-6.0.0/hyperframe/frame.py --- python-hyperframe-5.2.0/hyperframe/frame.py 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/hyperframe/frame.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,822 +0,0 @@ -# -*- coding: utf-8 -*- -""" -hyperframe/frame -~~~~~~~~~~~~~~~~ - -Defines framing logic for HTTP/2. Provides both classes to represent framed -data and logic for aiding the connection when it comes to reading from the -socket. -""" -import struct -import binascii - -from .exceptions import ( - UnknownFrameError, InvalidPaddingError, InvalidFrameError -) -from .flags import Flag, Flags - - -# The maximum initial length of a frame. Some frames have shorter maximum -# lengths. -FRAME_MAX_LEN = (2 ** 14) - -# The maximum allowed length of a frame. -FRAME_MAX_ALLOWED_LEN = (2 ** 24) - 1 - -# Stream association enumerations. -_STREAM_ASSOC_HAS_STREAM = "has-stream" -_STREAM_ASSOC_NO_STREAM = "no-stream" -_STREAM_ASSOC_EITHER = "either" - -# Structs for packing and unpacking -_STRUCT_HBBBL = struct.Struct(">HBBBL") -_STRUCT_LL = struct.Struct(">LL") -_STRUCT_HL = struct.Struct(">HL") -_STRUCT_LB = struct.Struct(">LB") -_STRUCT_L = struct.Struct(">L") -_STRUCT_H = struct.Struct(">H") -_STRUCT_B = struct.Struct(">B") - - -class Frame(object): - """ - The base class for all HTTP/2 frames. - """ - #: The flags defined on this type of frame. - defined_flags = [] - - #: The byte used to define the type of the frame. - type = None - - # If 'has-stream', the frame's stream_id must be non-zero. If 'no-stream', - # it must be zero. If 'either', it's not checked. - stream_association = None - - def __init__(self, stream_id, flags=()): - #: The stream identifier for the stream this frame was received on. - #: Set to 0 for frames sent on the connection (stream-id 0). - self.stream_id = stream_id - - #: The flags set for this frame. - self.flags = Flags(self.defined_flags) - - #: The frame length, excluding the nine-byte header. - self.body_len = 0 - - for flag in flags: - self.flags.add(flag) - - if (not self.stream_id and - self.stream_association == _STREAM_ASSOC_HAS_STREAM): - raise ValueError('Stream ID must be non-zero') - if (self.stream_id and - self.stream_association == _STREAM_ASSOC_NO_STREAM): - raise ValueError('Stream ID must be zero') - - def __repr__(self): - flags = ", ".join(self.flags) or "None" - body = binascii.hexlify(self.serialize_body()).decode('ascii') - if len(body) > 20: - body = body[:20] + "..." - return ( - "{type}(Stream: {stream}; Flags: {flags}): {body}" - ).format( - type=type(self).__name__, - stream=self.stream_id, - flags=flags, - body=body - ) - - @staticmethod - def parse_frame_header(header, strict=False): - """ - Takes a 9-byte frame header and returns a tuple of the appropriate - Frame object and the length that needs to be read from the socket. - - This populates the flags field, and determines how long the body is. - - :param strict: Whether to raise an exception when encountering a frame - not defined by spec and implemented by hyperframe. - - :raises hyperframe.exceptions.UnknownFrameError: If a frame of unknown - type is received. - - .. versionchanged:: 5.0.0 - Added :param:`strict` to accommodate :class:`ExtensionFrame` - """ - try: - fields = _STRUCT_HBBBL.unpack(header) - except struct.error: - raise InvalidFrameError("Invalid frame header") - - # First 24 bits are frame length. - length = (fields[0] << 8) + fields[1] - type = fields[2] - flags = fields[3] - stream_id = fields[4] & 0x7FFFFFFF - - try: - frame = FRAMES[type](stream_id) - except KeyError: - if strict: - raise UnknownFrameError(type, length) - frame = ExtensionFrame(type=type, stream_id=stream_id) - - frame.parse_flags(flags) - return (frame, length) - - def parse_flags(self, flag_byte): - for flag, flag_bit in self.defined_flags: - if flag_byte & flag_bit: - self.flags.add(flag) - - return self.flags - - def serialize(self): - """ - Convert a frame into a bytestring, representing the serialized form of - the frame. - """ - body = self.serialize_body() - self.body_len = len(body) - - # Build the common frame header. - # First, get the flags. - flags = 0 - - for flag, flag_bit in self.defined_flags: - if flag in self.flags: - flags |= flag_bit - - header = _STRUCT_HBBBL.pack( - (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits - self.body_len & 0xFF, - self.type, - flags, - self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits. - ) - - return header + body - - def serialize_body(self): - raise NotImplementedError() - - def parse_body(self, data): - """ - Given the body of a frame, parses it into frame data. This populates - the non-header parts of the frame: that is, it does not populate the - stream ID or flags. - - :param data: A memoryview object containing the body data of the frame. - Must not contain *more* data than the length returned by - :meth:`parse_frame_header - `. - """ - raise NotImplementedError() - - -class Padding(object): - """ - Mixin for frames that contain padding. Defines extra fields that can be - used and set by frames that can be padded. - """ - def __init__(self, stream_id, pad_length=0, **kwargs): - super(Padding, self).__init__(stream_id, **kwargs) - - #: The length of the padding to use. - self.pad_length = pad_length - - def serialize_padding_data(self): - if 'PADDED' in self.flags: - return _STRUCT_B.pack(self.pad_length) - return b'' - - def parse_padding_data(self, data): - if 'PADDED' in self.flags: - try: - self.pad_length = struct.unpack('!B', data[:1])[0] - except struct.error: - raise InvalidFrameError("Invalid Padding data") - return 1 - return 0 - - @property - def total_padding(self): - return self.pad_length - - -class Priority(object): - """ - Mixin for frames that contain priority data. Defines extra fields that can - be used and set by frames that contain priority data. - """ - def __init__(self, - stream_id, - depends_on=0x0, - stream_weight=0x0, - exclusive=False, - **kwargs): - super(Priority, self).__init__(stream_id, **kwargs) - - #: The stream ID of the stream on which this stream depends. - self.depends_on = depends_on - - #: The weight of the stream. This is an integer between 0 and 256. - self.stream_weight = stream_weight - - #: Whether the exclusive bit was set. - self.exclusive = exclusive - - def serialize_priority_data(self): - return _STRUCT_LB.pack( - self.depends_on + (0x80000000 if self.exclusive else 0), - self.stream_weight - ) - - def parse_priority_data(self, data): - try: - self.depends_on, self.stream_weight = _STRUCT_LB.unpack(data[:5]) - except struct.error: - raise InvalidFrameError("Invalid Priority data") - - self.exclusive = True if self.depends_on >> 31 else False - self.depends_on &= 0x7FFFFFFF - return 5 - - -class DataFrame(Padding, Frame): - """ - DATA frames convey arbitrary, variable-length sequences of octets - associated with a stream. One or more DATA frames are used, for instance, - to carry HTTP request or response payloads. - """ - #: The flags defined for DATA frames. - defined_flags = [ - Flag('END_STREAM', 0x01), - Flag('PADDED', 0x08), - ] - - #: The type byte for data frames. - type = 0x0 - - stream_association = _STREAM_ASSOC_HAS_STREAM - - def __init__(self, stream_id, data=b'', **kwargs): - super(DataFrame, self).__init__(stream_id, **kwargs) - - #: The data contained on this frame. - self.data = data - - def serialize_body(self): - padding_data = self.serialize_padding_data() - padding = b'\0' * self.total_padding - if isinstance(self.data, memoryview): - self.data = self.data.tobytes() - return b''.join([padding_data, self.data, padding]) - - def parse_body(self, data): - padding_data_length = self.parse_padding_data(data) - self.data = ( - data[padding_data_length:len(data)-self.total_padding].tobytes() - ) - self.body_len = len(data) - - if self.total_padding and self.total_padding >= self.body_len: - raise InvalidPaddingError("Padding is too long.") - - @property - def flow_controlled_length(self): - """ - The length of the frame that needs to be accounted for when considering - flow control. - """ - padding_len = 0 - if 'PADDED' in self.flags: - # Account for extra 1-byte padding length field, which is still - # present if possibly zero-valued. - padding_len = self.total_padding + 1 - return len(self.data) + padding_len - - -class PriorityFrame(Priority, Frame): - """ - The PRIORITY frame specifies the sender-advised priority of a stream. It - can be sent at any time for an existing stream. This enables - reprioritisation of existing streams. - """ - #: The flags defined for PRIORITY frames. - defined_flags = [] - - #: The type byte defined for PRIORITY frames. - type = 0x02 - - stream_association = _STREAM_ASSOC_HAS_STREAM - - def serialize_body(self): - return self.serialize_priority_data() - - def parse_body(self, data): - self.parse_priority_data(data) - self.body_len = len(data) - - -class RstStreamFrame(Frame): - """ - The RST_STREAM frame allows for abnormal termination of a stream. When sent - by the initiator of a stream, it indicates that they wish to cancel the - stream or that an error condition has occurred. When sent by the receiver - of a stream, it indicates that either the receiver is rejecting the stream, - requesting that the stream be cancelled or that an error condition has - occurred. - """ - #: The flags defined for RST_STREAM frames. - defined_flags = [] - - #: The type byte defined for RST_STREAM frames. - type = 0x03 - - stream_association = _STREAM_ASSOC_HAS_STREAM - - def __init__(self, stream_id, error_code=0, **kwargs): - super(RstStreamFrame, self).__init__(stream_id, **kwargs) - - #: The error code used when resetting the stream. - self.error_code = error_code - - def serialize_body(self): - return _STRUCT_L.pack(self.error_code) - - def parse_body(self, data): - if len(data) != 4: - raise InvalidFrameError( - "RST_STREAM must have 4 byte body: actual length %s." % - len(data) - ) - - try: - self.error_code = _STRUCT_L.unpack(data)[0] - except struct.error: # pragma: no cover - raise InvalidFrameError("Invalid RST_STREAM body") - - self.body_len = 4 - - -class SettingsFrame(Frame): - """ - The SETTINGS frame conveys configuration parameters that affect how - endpoints communicate. The parameters are either constraints on peer - behavior or preferences. - - Settings are not negotiated. Settings describe characteristics of the - sending peer, which are used by the receiving peer. Different values for - the same setting can be advertised by each peer. For example, a client - might set a high initial flow control window, whereas a server might set a - lower value to conserve resources. - """ - #: The flags defined for SETTINGS frames. - defined_flags = [Flag('ACK', 0x01)] - - #: The type byte defined for SETTINGS frames. - type = 0x04 - - stream_association = _STREAM_ASSOC_NO_STREAM - - # We need to define the known settings, they may as well be class - # attributes. - #: The byte that signals the SETTINGS_HEADER_TABLE_SIZE setting. - HEADER_TABLE_SIZE = 0x01 - #: The byte that signals the SETTINGS_ENABLE_PUSH setting. - ENABLE_PUSH = 0x02 - #: The byte that signals the SETTINGS_MAX_CONCURRENT_STREAMS setting. - MAX_CONCURRENT_STREAMS = 0x03 - #: The byte that signals the SETTINGS_INITIAL_WINDOW_SIZE setting. - INITIAL_WINDOW_SIZE = 0x04 - #: The byte that signals the SETTINGS_MAX_FRAME_SIZE setting. - MAX_FRAME_SIZE = 0x05 - #: The byte that signals the SETTINGS_MAX_HEADER_LIST_SIZE setting. - MAX_HEADER_LIST_SIZE = 0x06 - #: The byte that signals SETTINGS_ENABLE_CONNECT_PROTOCOL setting. - ENABLE_CONNECT_PROTOCOL = 0x08 - - def __init__(self, stream_id=0, settings=None, **kwargs): - super(SettingsFrame, self).__init__(stream_id, **kwargs) - - if settings and "ACK" in kwargs.get("flags", ()): - raise ValueError("Settings must be empty if ACK flag is set.") - - #: A dictionary of the setting type byte to the value of the setting. - self.settings = settings or {} - - def serialize_body(self): - return b''.join([_STRUCT_HL.pack(setting & 0xFF, value) - for setting, value in self.settings.items()]) - - def parse_body(self, data): - body_len = 0 - for i in range(0, len(data), 6): - try: - name, value = _STRUCT_HL.unpack(data[i:i+6]) - except struct.error: - raise InvalidFrameError("Invalid SETTINGS body") - - self.settings[name] = value - body_len += 6 - - self.body_len = body_len - - -class PushPromiseFrame(Padding, Frame): - """ - The PUSH_PROMISE frame is used to notify the peer endpoint in advance of - streams the sender intends to initiate. - """ - #: The flags defined for PUSH_PROMISE frames. - defined_flags = [ - Flag('END_HEADERS', 0x04), - Flag('PADDED', 0x08) - ] - - #: The type byte defined for PUSH_PROMISE frames. - type = 0x05 - - stream_association = _STREAM_ASSOC_HAS_STREAM - - def __init__(self, stream_id, promised_stream_id=0, data=b'', **kwargs): - super(PushPromiseFrame, self).__init__(stream_id, **kwargs) - - #: The stream ID that is promised by this frame. - self.promised_stream_id = promised_stream_id - - #: The HPACK-encoded header block for the simulated request on the new - #: stream. - self.data = data - - def serialize_body(self): - padding_data = self.serialize_padding_data() - padding = b'\0' * self.total_padding - data = _STRUCT_L.pack(self.promised_stream_id) - return b''.join([padding_data, data, self.data, padding]) - - def parse_body(self, data): - padding_data_length = self.parse_padding_data(data) - - try: - self.promised_stream_id = _STRUCT_L.unpack( - data[padding_data_length:padding_data_length + 4] - )[0] - except struct.error: - raise InvalidFrameError("Invalid PUSH_PROMISE body") - - self.data = data[padding_data_length + 4:].tobytes() - self.body_len = len(data) - - if self.total_padding and self.total_padding >= self.body_len: - raise InvalidPaddingError("Padding is too long.") - - -class PingFrame(Frame): - """ - The PING frame is a mechanism for measuring a minimal round-trip time from - the sender, as well as determining whether an idle connection is still - functional. PING frames can be sent from any endpoint. - """ - #: The flags defined for PING frames. - defined_flags = [Flag('ACK', 0x01)] - - #: The type byte defined for PING frames. - type = 0x06 - - stream_association = _STREAM_ASSOC_NO_STREAM - - def __init__(self, stream_id=0, opaque_data=b'', **kwargs): - super(PingFrame, self).__init__(stream_id, **kwargs) - - #: The opaque data sent in this PING frame, as a bytestring. - self.opaque_data = opaque_data - - def serialize_body(self): - if len(self.opaque_data) > 8: - raise InvalidFrameError( - "PING frame may not have more than 8 bytes of data, got %s" % - self.opaque_data - ) - - data = self.opaque_data - data += b'\x00' * (8 - len(self.opaque_data)) - return data - - def parse_body(self, data): - if len(data) != 8: - raise InvalidFrameError( - "PING frame must have 8 byte length: got %s" % len(data) - ) - - self.opaque_data = data.tobytes() - self.body_len = 8 - - -class GoAwayFrame(Frame): - """ - The GOAWAY frame informs the remote peer to stop creating streams on this - connection. It can be sent from the client or the server. Once sent, the - sender will ignore frames sent on new streams for the remainder of the - connection. - """ - #: The flags defined for GOAWAY frames. - defined_flags = [] - - #: The type byte defined for GOAWAY frames. - type = 0x07 - - stream_association = _STREAM_ASSOC_NO_STREAM - - def __init__(self, - stream_id=0, - last_stream_id=0, - error_code=0, - additional_data=b'', - **kwargs): - super(GoAwayFrame, self).__init__(stream_id, **kwargs) - - #: The last stream ID definitely seen by the remote peer. - self.last_stream_id = last_stream_id - - #: The error code for connection teardown. - self.error_code = error_code - - #: Any additional data sent in the GOAWAY. - self.additional_data = additional_data - - def serialize_body(self): - data = _STRUCT_LL.pack( - self.last_stream_id & 0x7FFFFFFF, - self.error_code - ) - data += self.additional_data - - return data - - def parse_body(self, data): - try: - self.last_stream_id, self.error_code = _STRUCT_LL.unpack( - data[:8] - ) - except struct.error: - raise InvalidFrameError("Invalid GOAWAY body.") - - self.body_len = len(data) - - if len(data) > 8: - self.additional_data = data[8:].tobytes() - - -class WindowUpdateFrame(Frame): - """ - The WINDOW_UPDATE frame is used to implement flow control. - - Flow control operates at two levels: on each individual stream and on the - entire connection. - - Both types of flow control are hop by hop; that is, only between the two - endpoints. Intermediaries do not forward WINDOW_UPDATE frames between - dependent connections. However, throttling of data transfer by any receiver - can indirectly cause the propagation of flow control information toward the - original sender. - """ - #: The flags defined for WINDOW_UPDATE frames. - defined_flags = [] - - #: The type byte defined for WINDOW_UPDATE frames. - type = 0x08 - - stream_association = _STREAM_ASSOC_EITHER - - def __init__(self, stream_id, window_increment=0, **kwargs): - super(WindowUpdateFrame, self).__init__(stream_id, **kwargs) - - #: The amount the flow control window is to be incremented. - self.window_increment = window_increment - - def serialize_body(self): - return _STRUCT_L.pack(self.window_increment & 0x7FFFFFFF) - - def parse_body(self, data): - try: - self.window_increment = _STRUCT_L.unpack(data)[0] - except struct.error: - raise InvalidFrameError("Invalid WINDOW_UPDATE body") - - self.body_len = 4 - - -class HeadersFrame(Padding, Priority, Frame): - """ - The HEADERS frame carries name-value pairs. It is used to open a stream. - HEADERS frames can be sent on a stream in the "open" or "half closed - (remote)" states. - - The HeadersFrame class is actually basically a data frame in this - implementation, because of the requirement to control the sizes of frames. - A header block fragment that doesn't fit in an entire HEADERS frame needs - to be followed with CONTINUATION frames. From the perspective of the frame - building code the header block is an opaque data segment. - """ - #: The flags defined for HEADERS frames. - defined_flags = [ - Flag('END_STREAM', 0x01), - Flag('END_HEADERS', 0x04), - Flag('PADDED', 0x08), - Flag('PRIORITY', 0x20), - ] - - #: The type byte defined for HEADERS frames. - type = 0x01 - - stream_association = _STREAM_ASSOC_HAS_STREAM - - def __init__(self, stream_id, data=b'', **kwargs): - super(HeadersFrame, self).__init__(stream_id, **kwargs) - - #: The HPACK-encoded header block. - self.data = data - - def serialize_body(self): - padding_data = self.serialize_padding_data() - padding = b'\0' * self.total_padding - - if 'PRIORITY' in self.flags: - priority_data = self.serialize_priority_data() - else: - priority_data = b'' - - return b''.join([padding_data, priority_data, self.data, padding]) - - def parse_body(self, data): - padding_data_length = self.parse_padding_data(data) - data = data[padding_data_length:] - - if 'PRIORITY' in self.flags: - priority_data_length = self.parse_priority_data(data) - else: - priority_data_length = 0 - - self.body_len = len(data) - self.data = ( - data[priority_data_length:len(data)-self.total_padding].tobytes() - ) - - if self.total_padding and self.total_padding >= self.body_len: - raise InvalidPaddingError("Padding is too long.") - - -class ContinuationFrame(Frame): - """ - The CONTINUATION frame is used to continue a sequence of header block - fragments. Any number of CONTINUATION frames can be sent on an existing - stream, as long as the preceding frame on the same stream is one of - HEADERS, PUSH_PROMISE or CONTINUATION without the END_HEADERS flag set. - - Much like the HEADERS frame, hyper treats this as an opaque data frame with - different flags and a different type. - """ - #: The flags defined for CONTINUATION frames. - defined_flags = [Flag('END_HEADERS', 0x04)] - - #: The type byte defined for CONTINUATION frames. - type = 0x09 - - stream_association = _STREAM_ASSOC_HAS_STREAM - - def __init__(self, stream_id, data=b'', **kwargs): - super(ContinuationFrame, self).__init__(stream_id, **kwargs) - - #: The HPACK-encoded header block. - self.data = data - - def serialize_body(self): - return self.data - - def parse_body(self, data): - self.data = data.tobytes() - self.body_len = len(data) - - -class AltSvcFrame(Frame): - """ - The ALTSVC frame is used to advertise alternate services that the current - host, or a different one, can understand. This frame is standardised as - part of RFC 7838. - - This frame does no work to validate that the ALTSVC field parameter is - acceptable per the rules of RFC 7838. - - .. note:: If the ``stream_id`` of this frame is nonzero, the origin field - must have zero length. Conversely, if the ``stream_id`` of this - frame is zero, the origin field must have nonzero length. Put - another way, a valid ALTSVC frame has ``stream_id != 0`` XOR - ``len(origin) != 0``. - """ - type = 0xA - - stream_association = _STREAM_ASSOC_EITHER - - def __init__(self, stream_id, origin=b'', field=b'', **kwargs): - super(AltSvcFrame, self).__init__(stream_id, **kwargs) - - if not isinstance(origin, bytes): - raise ValueError("AltSvc origin must be bytestring.") - if not isinstance(field, bytes): - raise ValueError("AltSvc field must be a bytestring.") - self.origin = origin - self.field = field - - def serialize_body(self): - origin_len = _STRUCT_H.pack(len(self.origin)) - return b''.join([origin_len, self.origin, self.field]) - - def parse_body(self, data): - try: - origin_len = _STRUCT_H.unpack(data[0:2])[0] - self.origin = data[2:2+origin_len].tobytes() - - if len(self.origin) != origin_len: - raise InvalidFrameError("Invalid ALTSVC frame body.") - - self.field = data[2+origin_len:].tobytes() - except (struct.error, ValueError): - raise InvalidFrameError("Invalid ALTSVC frame body.") - - self.body_len = len(data) - - -class ExtensionFrame(Frame): - """ - ExtensionFrame is used to wrap frames which are not natively interpretable - by hyperframe. - - Although certain byte prefixes are ordained by specification to have - certain contextual meanings, frames with other prefixes are not prohibited, - and may be used to communicate arbitrary meaning between HTTP/2 peers. - - Thus, hyperframe, rather than raising an exception when such a frame is - encountered, wraps it in a generic frame to be properly acted upon by - upstream consumers which might have additional context on how to use it. - - .. versionadded:: 5.0.0 - """ - - stream_association = _STREAM_ASSOC_EITHER - - def __init__(self, type, stream_id, **kwargs): - super(ExtensionFrame, self).__init__(stream_id, **kwargs) - self.type = type - self.flag_byte = None - - def parse_flags(self, flag_byte): - """ - For extension frames, we parse the flags by just storing a flag byte. - """ - self.flag_byte = flag_byte - - def parse_body(self, data): - self.body = data.tobytes() - self.body_len = len(data) - - def serialize(self): - """ - A broad override of the serialize method that ensures that the data - comes back out exactly as it came in. This should not be used in most - user code: it exists only as a helper method if frames need to be - reconstituted. - """ - # Build the frame header. - # First, get the flags. - flags = self.flag_byte - - header = _STRUCT_HBBBL.pack( - (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits - self.body_len & 0xFF, - self.type, - flags, - self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits. - ) - - return header + self.body - - -_FRAME_CLASSES = [ - DataFrame, - HeadersFrame, - PriorityFrame, - RstStreamFrame, - SettingsFrame, - PushPromiseFrame, - PingFrame, - GoAwayFrame, - WindowUpdateFrame, - ContinuationFrame, - AltSvcFrame, -] -#: FRAMES maps the type byte for each frame to the class used to represent that -#: frame. -FRAMES = {cls.type: cls for cls in _FRAME_CLASSES} diff -Nru python-hyperframe-5.2.0/hyperframe/__init__.py python-hyperframe-6.0.0/hyperframe/__init__.py --- python-hyperframe-5.2.0/hyperframe/__init__.py 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/hyperframe/__init__.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,8 +0,0 @@ -# -*- coding: utf-8 -*- -""" -hyperframe -~~~~~~~~~~ - -A module for providing a pure-Python HTTP/2 framing layer. -""" -__version__ = '5.2.0' diff -Nru python-hyperframe-5.2.0/hyperframe.egg-info/dependency_links.txt python-hyperframe-6.0.0/hyperframe.egg-info/dependency_links.txt --- python-hyperframe-5.2.0/hyperframe.egg-info/dependency_links.txt 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/hyperframe.egg-info/dependency_links.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ - diff -Nru python-hyperframe-5.2.0/hyperframe.egg-info/PKG-INFO python-hyperframe-6.0.0/hyperframe.egg-info/PKG-INFO --- python-hyperframe-5.2.0/hyperframe.egg-info/PKG-INFO 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/hyperframe.egg-info/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 @@ -1,242 +0,0 @@ -Metadata-Version: 1.1 -Name: hyperframe -Version: 5.2.0 -Summary: HTTP/2 framing layer for Python -Home-page: https://python-hyper.org/hyperframe/en/latest/ -Author: Cory Benfield -Author-email: cory@lukasa.co.uk -License: MIT License -Description: ====================================== - hyperframe: Pure-Python HTTP/2 framing - ====================================== - - .. image:: https://travis-ci.org/python-hyper/hyperframe.png?branch=master - :target: https://travis-ci.org/python-hyper/hyperframe - - This library contains the HTTP/2 framing code used in the `hyper`_ project. It - provides a pure-Python codebase that is capable of decoding a binary stream - into HTTP/2 frames. - - This library is used directly by `hyper`_ and a number of other projects to - provide HTTP/2 frame decoding logic. - - Contributing - ============ - - hyperframe 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 - ======= - - hyperframe is made available under the MIT License. For more details, see the - ``LICENSE`` file in the repository. - - Authors - ======= - - hyperframe is maintained by Cory Benfield, with contributions from others. For - more details about the contributors, please see ``CONTRIBUTORS.rst``. - - .. _hyper: http://python-hyper.org/ - - - Release History - =============== - - 6.0.0dev0 - --------- - - 5.2.0 (2019-01-18) - ------------------ - - **API Changes (Backward-compatible)** - - - Add a new ENABLE_CONNECT_PROTOCOL settings paramter. - - **Other Changes** - - - Fix collections.abc deprecation. - - Drop support for Python 3.3 and support 3.7. - - 5.1.0 (2017-04-24) - ------------------ - - **API Changes (Backward-compatible)** - - - Added support for ``DataFrame.data`` being a ``memoryview`` object. - - 5.0.0 (2017-03-07) - ------------------ - - **Backwards Incompatible API Changes** - - - Added support for unknown extension frames. These will be returned in the new - ``ExtensionFrame`` object. The flag information for these frames is persisted - in ``flag_byte`` if needed. - - 4.0.2 (2017-02-20) - ------------------ - - **Bugfixes** - - - Fixed AltSvc stream association, which was incorrectly set to ``'both'``: - should have been ``'either'``. - - Fixed a bug where stream IDs on received frames were allowed to be 32-bit, - instead of 31-bit. - - Fixed a bug with frames that had the ``PADDING`` flag set but zero-length - padding, whose flow-controlled length was calculated wrongly. - - Miscellaneous performance improvements to serialization and parsing logic. - - 4.0.1 (2016-03-13) - ------------------ - - **Bugfixes** - - - Fixed bug with the repr of ``AltSvcFrame``, where building it could throw - exceptions if the frame had been received from the network. - - 4.0.0 (2016-03-13) - ------------------ - - **Backwards Incompatible API Changes** - - - Updated old ALTSVC frame definition to match the newly specified RFC 7838. - - Remove BLOCKED frame, which was never actually specified. - - Removed previously deprecated ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and - ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``. - - 3.2.0 (2016-02-02) - ------------------ - - **API Changes (Backward-compatible)** - - - Invalid PING frame bodies now raise ``InvalidFrameError``, not - ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass. - - Invalid RST_STREAM frame bodies now raise ``InvalidFramError``, not - ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass. - - Canonicalized the names of ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and - ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE`` to match their peers, by - adding new properties ``SettingsFrame.MAX_FRAME_SIZE`` and - ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``. The old names are still - present, but will be deprecated in 4.0.0. - - **Bugfixes** - - - The change in ``3.1.0`` that ensured that ``InvalidFrameError`` would be - thrown did not affect certain invalid values in ALT_SVC frames. This has been - fixed: ``ValueError`` will no longer be thrown from invalid ALT_SVC bodies. - - 3.1.1 (2016-01-18) - ------------------ - - **Bugfixes** - - - Correctly error when receiving Ping frames that have insufficient data. - - 3.1.0 (2016-01-13) - ------------------ - - **API Changes** - - - Added new ``InvalidFrameError`` that is thrown instead of ``struct.error`` - when parsing a frame. - - **Bugfixes** - - - Fixed error when trying to serialize frames that use Priority information - with the defaults for that information. - - Fixed errors when displaying the repr of frames with non-printable bodies. - - 3.0.1 (2016-01-08) - ------------------ - - **Bugfixes** - - - Fix issue where unpadded DATA, PUSH_PROMISE and HEADERS frames that had empty - bodies would raise ``InvalidPaddingError`` exceptions when parsed. - - 3.0.0 (2016-01-08) - ------------------ - - **Backwards Incompatible API Changes** - - - Parsing padded frames that have invalid padding sizes now throws an - ``InvalidPaddingError``. - - 2.2.0 (2015-10-15) - ------------------ - - **API Changes** - - - When an unknown frame is encountered, ``parse_frame_header`` now throws a - ``ValueError`` subclass: ``UnknownFrameError``. This subclass contains the - frame type and the length of the frame body. - - 2.1.0 (2015-10-06) - ------------------ - - **API Changes** - - - Frames parsed from binary data now carry a ``body_len`` attribute that - matches the frame length (minus the frame header). - - 2.0.0 (2015-09-21) - ------------------ - - **API Changes** - - - Attempting to parse unrecognised frames now throws ``ValueError`` instead of - ``KeyError``. Thanks to @Kriechi! - - Flags are now validated for correctness, preventing setting flags that - ``hyperframe`` does not recognise and that would not serialize. Thanks to - @mhils! - - Frame properties can now be initialized in the constructors. Thanks to @mhils - and @Kriechi! - - Frames that cannot be sent on a stream now have their stream ID defaulted - to ``0``. Thanks to @Kriechi! - - **Other Changes** - - - Frames have a more useful repr. Thanks to @mhils! - - 1.1.1 (2015-07-20) - ------------------ - - - Fix a bug where ``FRAME_MAX_LEN`` was one byte too small. - - 1.1.0 (2015-06-28) - ------------------ - - - Add ``body_len`` property to frames to enable introspection of the actual - frame length. Thanks to @jdecuyper! - - 1.0.1 (2015-06-27) - ------------------ - - - Fix bug where the frame header would have an incorrect length added to it. - - 1.0.0 (2015-04-12) - ------------------ - - - Initial extraction from hyper. - -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.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 -Classifier: Programming Language :: Python :: 3.7 -Classifier: Programming Language :: Python :: Implementation :: CPython diff -Nru python-hyperframe-5.2.0/hyperframe.egg-info/SOURCES.txt python-hyperframe-6.0.0/hyperframe.egg-info/SOURCES.txt --- python-hyperframe-5.2.0/hyperframe.egg-info/SOURCES.txt 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/hyperframe.egg-info/SOURCES.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -CONTRIBUTORS.rst -HISTORY.rst -LICENSE -MANIFEST.in -README.rst -setup.cfg -setup.py -hyperframe/__init__.py -hyperframe/exceptions.py -hyperframe/flags.py -hyperframe/frame.py -hyperframe.egg-info/PKG-INFO -hyperframe.egg-info/SOURCES.txt -hyperframe.egg-info/dependency_links.txt -hyperframe.egg-info/top_level.txt -test/test_flags.py -test/test_frames.py \ No newline at end of file diff -Nru python-hyperframe-5.2.0/hyperframe.egg-info/top_level.txt python-hyperframe-6.0.0/hyperframe.egg-info/top_level.txt --- python-hyperframe-5.2.0/hyperframe.egg-info/top_level.txt 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/hyperframe.egg-info/top_level.txt 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -hyperframe diff -Nru python-hyperframe-5.2.0/MANIFEST.in python-hyperframe-6.0.0/MANIFEST.in --- python-hyperframe-5.2.0/MANIFEST.in 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/MANIFEST.in 2020-08-02 16:16:59.000000000 +0000 @@ -1,2 +1,7 @@ -include README.rst LICENSE CONTRIBUTORS.rst HISTORY.rst - +graft src +graft docs +graft test +prune docs/build +prune test/http2-frame-test-case +include README.rst LICENSE CHANGELOG.rst CONTRIBUTORS.rst tox.ini .gitmodules +global-exclude *.pyc *.pyo *.swo *.swp *.map *.yml *.DS_Store diff -Nru python-hyperframe-5.2.0/PKG-INFO python-hyperframe-6.0.0/PKG-INFO --- python-hyperframe-5.2.0/PKG-INFO 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/PKG-INFO 2020-09-06 10:21:04.228182000 +0000 @@ -1,8 +1,8 @@ -Metadata-Version: 1.1 +Metadata-Version: 2.1 Name: hyperframe -Version: 5.2.0 +Version: 6.0.0 Summary: HTTP/2 framing layer for Python -Home-page: https://python-hyper.org/hyperframe/en/latest/ +Home-page: https://github.com/python-hyper/hyperframe/ Author: Cory Benfield Author-email: cory@lukasa.co.uk License: MIT License @@ -10,8 +10,18 @@ hyperframe: Pure-Python HTTP/2 framing ====================================== - .. image:: https://travis-ci.org/python-hyper/hyperframe.png?branch=master - :target: https://travis-ci.org/python-hyper/hyperframe + .. image:: https://github.com/python-hyper/hyperframe/workflows/CI/badge.svg + :target: https://github.com/python-hyper/hyperframe/actions + :alt: Build Status + .. image:: https://codecov.io/gh/python-hyper/hyperframe/branch/master/graph/badge.svg + :target: https://codecov.io/gh/python-hyper/hyperframe + :alt: Code Coverage + .. image:: https://readthedocs.org/projects/hyperframe/badge/?version=latest + :target: https://hyperframe.readthedocs.io/en/latest/ + :alt: Documentation Status + .. image:: https://img.shields.io/badge/chat-join_now-brightgreen.svg + :target: https://gitter.im/python-hyper/community + :alt: Chat community This library contains the HTTP/2 framing code used in the `hyper`_ project. It provides a pure-Python codebase that is capable of decoding a binary stream @@ -46,197 +56,16 @@ .. _hyper: http://python-hyper.org/ - - Release History - =============== - - 6.0.0dev0 - --------- - - 5.2.0 (2019-01-18) - ------------------ - - **API Changes (Backward-compatible)** - - - Add a new ENABLE_CONNECT_PROTOCOL settings paramter. - - **Other Changes** - - - Fix collections.abc deprecation. - - Drop support for Python 3.3 and support 3.7. - - 5.1.0 (2017-04-24) - ------------------ - - **API Changes (Backward-compatible)** - - - Added support for ``DataFrame.data`` being a ``memoryview`` object. - - 5.0.0 (2017-03-07) - ------------------ - - **Backwards Incompatible API Changes** - - - Added support for unknown extension frames. These will be returned in the new - ``ExtensionFrame`` object. The flag information for these frames is persisted - in ``flag_byte`` if needed. - - 4.0.2 (2017-02-20) - ------------------ - - **Bugfixes** - - - Fixed AltSvc stream association, which was incorrectly set to ``'both'``: - should have been ``'either'``. - - Fixed a bug where stream IDs on received frames were allowed to be 32-bit, - instead of 31-bit. - - Fixed a bug with frames that had the ``PADDING`` flag set but zero-length - padding, whose flow-controlled length was calculated wrongly. - - Miscellaneous performance improvements to serialization and parsing logic. - - 4.0.1 (2016-03-13) - ------------------ - - **Bugfixes** - - - Fixed bug with the repr of ``AltSvcFrame``, where building it could throw - exceptions if the frame had been received from the network. - - 4.0.0 (2016-03-13) - ------------------ - - **Backwards Incompatible API Changes** - - - Updated old ALTSVC frame definition to match the newly specified RFC 7838. - - Remove BLOCKED frame, which was never actually specified. - - Removed previously deprecated ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and - ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``. - - 3.2.0 (2016-02-02) - ------------------ - - **API Changes (Backward-compatible)** - - - Invalid PING frame bodies now raise ``InvalidFrameError``, not - ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass. - - Invalid RST_STREAM frame bodies now raise ``InvalidFramError``, not - ``ValueError``. Note that ``InvalidFrameError`` is a ``ValueError`` subclass. - - Canonicalized the names of ``SettingsFrame.SETTINGS_MAX_FRAME_SIZE`` and - ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE`` to match their peers, by - adding new properties ``SettingsFrame.MAX_FRAME_SIZE`` and - ``SettingsFrame.SETTINGS_MAX_HEADER_LIST_SIZE``. The old names are still - present, but will be deprecated in 4.0.0. - - **Bugfixes** - - - The change in ``3.1.0`` that ensured that ``InvalidFrameError`` would be - thrown did not affect certain invalid values in ALT_SVC frames. This has been - fixed: ``ValueError`` will no longer be thrown from invalid ALT_SVC bodies. - - 3.1.1 (2016-01-18) - ------------------ - - **Bugfixes** - - - Correctly error when receiving Ping frames that have insufficient data. - - 3.1.0 (2016-01-13) - ------------------ - - **API Changes** - - - Added new ``InvalidFrameError`` that is thrown instead of ``struct.error`` - when parsing a frame. - - **Bugfixes** - - - Fixed error when trying to serialize frames that use Priority information - with the defaults for that information. - - Fixed errors when displaying the repr of frames with non-printable bodies. - - 3.0.1 (2016-01-08) - ------------------ - - **Bugfixes** - - - Fix issue where unpadded DATA, PUSH_PROMISE and HEADERS frames that had empty - bodies would raise ``InvalidPaddingError`` exceptions when parsed. - - 3.0.0 (2016-01-08) - ------------------ - - **Backwards Incompatible API Changes** - - - Parsing padded frames that have invalid padding sizes now throws an - ``InvalidPaddingError``. - - 2.2.0 (2015-10-15) - ------------------ - - **API Changes** - - - When an unknown frame is encountered, ``parse_frame_header`` now throws a - ``ValueError`` subclass: ``UnknownFrameError``. This subclass contains the - frame type and the length of the frame body. - - 2.1.0 (2015-10-06) - ------------------ - - **API Changes** - - - Frames parsed from binary data now carry a ``body_len`` attribute that - matches the frame length (minus the frame header). - - 2.0.0 (2015-09-21) - ------------------ - - **API Changes** - - - Attempting to parse unrecognised frames now throws ``ValueError`` instead of - ``KeyError``. Thanks to @Kriechi! - - Flags are now validated for correctness, preventing setting flags that - ``hyperframe`` does not recognise and that would not serialize. Thanks to - @mhils! - - Frame properties can now be initialized in the constructors. Thanks to @mhils - and @Kriechi! - - Frames that cannot be sent on a stream now have their stream ID defaulted - to ``0``. Thanks to @Kriechi! - - **Other Changes** - - - Frames have a more useful repr. Thanks to @mhils! - - 1.1.1 (2015-07-20) - ------------------ - - - Fix a bug where ``FRAME_MAX_LEN`` was one byte too small. - - 1.1.0 (2015-06-28) - ------------------ - - - Add ``body_len`` property to frames to enable introspection of the actual - frame length. Thanks to @jdecuyper! - - 1.0.1 (2015-06-27) - ------------------ - - - Fix bug where the frame header would have an incorrect length added to it. - - 1.0.0 (2015-04-12) - ------------------ - - - Initial extraction from hyper. - 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.4 -Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=3.6.1 +Description-Content-Type: text/x-rst diff -Nru python-hyperframe-5.2.0/README.rst python-hyperframe-6.0.0/README.rst --- python-hyperframe-5.2.0/README.rst 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/README.rst 2020-07-29 22:16:00.000000000 +0000 @@ -2,8 +2,18 @@ hyperframe: Pure-Python HTTP/2 framing ====================================== -.. image:: https://travis-ci.org/python-hyper/hyperframe.png?branch=master - :target: https://travis-ci.org/python-hyper/hyperframe +.. image:: https://github.com/python-hyper/hyperframe/workflows/CI/badge.svg + :target: https://github.com/python-hyper/hyperframe/actions + :alt: Build Status +.. image:: https://codecov.io/gh/python-hyper/hyperframe/branch/master/graph/badge.svg + :target: https://codecov.io/gh/python-hyper/hyperframe + :alt: Code Coverage +.. image:: https://readthedocs.org/projects/hyperframe/badge/?version=latest + :target: https://hyperframe.readthedocs.io/en/latest/ + :alt: Documentation Status +.. image:: https://img.shields.io/badge/chat-join_now-brightgreen.svg + :target: https://gitter.im/python-hyper/community + :alt: Chat community This library contains the HTTP/2 framing code used in the `hyper`_ project. It provides a pure-Python codebase that is capable of decoding a binary stream diff -Nru python-hyperframe-5.2.0/setup.cfg python-hyperframe-6.0.0/setup.cfg --- python-hyperframe-5.2.0/setup.cfg 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/setup.cfg 2020-09-06 10:21:04.229749200 +0000 @@ -1,9 +1,25 @@ -[wheel] -universal = 1 - [tool:pytest] testpaths = test +[coverage:run] +branch = True +source = hyperframe + +[coverage:report] +fail_under = 100 +show_missing = True +exclude_lines = + pragma: no cover + raise NotImplementedError() + +[coverage:paths] +source = + src + .tox/*/site-packages + +[flake8] +max-line-length = 120 + [egg_info] tag_build = tag_date = 0 diff -Nru python-hyperframe-5.2.0/setup.py python-hyperframe-6.0.0/setup.py --- python-hyperframe-5.2.0/setup.py 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/setup.py 2020-08-02 16:16:59.000000000 +0000 @@ -5,15 +5,17 @@ import re import sys -try: - from setuptools import setup -except ImportError: - from distutils.core import setup +from setuptools import setup, find_packages + +PROJECT_ROOT = os.path.dirname(__file__) + +with open(os.path.join(PROJECT_ROOT, 'README.rst')) as file_: + long_description = file_.read() # Get the version version_regex = r'__version__ = ["\']([^"\']*)["\']' -with open('hyperframe/__init__.py', 'r') as f: - text = f.read() +with open(os.path.join(PROJECT_ROOT, 'src/hyperframe/__init__.py')) as file_: + text = file_.read() match = re.search(version_regex, text) if match: @@ -26,20 +28,19 @@ os.system('python setup.py sdist upload') sys.exit() - -packages = ['hyperframe'] - setup( name='hyperframe', version=version, description='HTTP/2 framing layer for Python', - long_description=open('README.rst').read() + '\n\n' + open('HISTORY.rst').read(), + long_description=long_description, + long_description_content_type='text/x-rst', author='Cory Benfield', author_email='cory@lukasa.co.uk', - url='https://python-hyper.org/hyperframe/en/latest/', - packages=packages, - package_data={'': ['LICENSE', 'README.rst', 'CONTRIBUTORS.rst', 'HISTORY.rst']}, - package_dir={'hyperframe': 'hyperframe'}, + url='https://github.com/python-hyper/hyperframe/', + packages=find_packages(where="src"), + package_data={'': ['LICENSE', 'README.rst', 'CHANGELOG.rst']}, + package_dir={'': 'src'}, + python_requires='>=3.6.1', include_package_data=True, license='MIT License', classifiers=[ @@ -47,13 +48,11 @@ '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', ], ) diff -Nru python-hyperframe-5.2.0/src/hyperframe/exceptions.py python-hyperframe-6.0.0/src/hyperframe/exceptions.py --- python-hyperframe-5.2.0/src/hyperframe/exceptions.py 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/src/hyperframe/exceptions.py 2020-08-30 10:04:55.000000000 +0000 @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +""" +hyperframe/exceptions +~~~~~~~~~~~~~~~~~~~~~ + +Defines the exceptions that can be thrown by hyperframe. +""" + + +class HyperframeError(Exception): + """ + The base class for all exceptions for the hyperframe module. + + .. versionadded:: 6.0.0 + """ + + +class UnknownFrameError(HyperframeError): + """ + A frame of unknown type was received. + + .. versionchanged:: 6.0.0 + Changed base class from `ValueError` to :class:`HyperframeError` + """ + def __init__(self, frame_type, length): + #: The type byte of the unknown frame that was received. + self.frame_type = frame_type + + #: The length of the data portion of the unknown frame. + self.length = length + + def __str__(self): + return ( + "UnknownFrameError: Unknown frame type 0x%X received, " + "length %d bytes" % (self.frame_type, self.length) + ) + + +class InvalidPaddingError(HyperframeError): + """ + A frame with invalid padding was received. + + .. versionchanged:: 6.0.0 + Changed base class from `ValueError` to :class:`HyperframeError` + """ + pass + + +class InvalidFrameError(HyperframeError): + """ + Parsing a frame failed because the data was not laid out appropriately. + + .. versionadded:: 3.0.2 + + .. versionchanged:: 6.0.0 + Changed base class from `ValueError` to :class:`HyperframeError` + """ + pass + + +class InvalidDataError(HyperframeError): + """ + Content or data of a frame was is invalid or violates the specification. + + .. versionadded:: 6.0.0 + """ + pass diff -Nru python-hyperframe-5.2.0/src/hyperframe/flags.py python-hyperframe-6.0.0/src/hyperframe/flags.py --- python-hyperframe-5.2.0/src/hyperframe/flags.py 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/src/hyperframe/flags.py 2020-09-05 15:39:12.000000000 +0000 @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +""" +hyperframe/flags +~~~~~~~~~~~~~~~~ + +Defines basic Flag and Flags data structures. +""" +import collections +from collections.abc import MutableSet + +Flag = collections.namedtuple("Flag", ["name", "bit"]) + + +class Flags(MutableSet): + """ + A simple MutableSet implementation that will only accept known flags as + elements. + + Will behave like a regular set(), except that a ValueError will be thrown + when .add()ing unexpected flags. + """ + def __init__(self, defined_flags): + self._valid_flags = set(flag.name for flag in defined_flags) + self._flags = set() + + def __repr__(self): + return repr(sorted(list(self._flags))) + + def __contains__(self, x): + return self._flags.__contains__(x) + + def __iter__(self): + return self._flags.__iter__() + + def __len__(self): + return self._flags.__len__() + + def discard(self, value): + return self._flags.discard(value) + + def add(self, value): + if value not in self._valid_flags: + raise ValueError( + "Unexpected flag: {}. Valid flags are: {}".format( + value, self._valid_flags + ) + ) + return self._flags.add(value) diff -Nru python-hyperframe-5.2.0/src/hyperframe/frame.py python-hyperframe-6.0.0/src/hyperframe/frame.py --- python-hyperframe-5.2.0/src/hyperframe/frame.py 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/src/hyperframe/frame.py 2020-09-05 15:39:12.000000000 +0000 @@ -0,0 +1,968 @@ +# -*- coding: utf-8 -*- +""" +hyperframe/frame +~~~~~~~~~~~~~~~~ + +Defines framing logic for HTTP/2. Provides both classes to represent framed +data and logic for aiding the connection when it comes to reading from the +socket. +""" +import struct +import binascii + +from .exceptions import ( + UnknownFrameError, InvalidPaddingError, InvalidFrameError, InvalidDataError +) +from .flags import Flag, Flags + + +# The maximum initial length of a frame. Some frames have shorter maximum +# lengths. +FRAME_MAX_LEN = (2 ** 14) + +# The maximum allowed length of a frame. +FRAME_MAX_ALLOWED_LEN = (2 ** 24) - 1 + +# Stream association enumerations. +_STREAM_ASSOC_HAS_STREAM = "has-stream" +_STREAM_ASSOC_NO_STREAM = "no-stream" +_STREAM_ASSOC_EITHER = "either" + +# Structs for packing and unpacking +_STRUCT_HBBBL = struct.Struct(">HBBBL") +_STRUCT_LL = struct.Struct(">LL") +_STRUCT_HL = struct.Struct(">HL") +_STRUCT_LB = struct.Struct(">LB") +_STRUCT_L = struct.Struct(">L") +_STRUCT_H = struct.Struct(">H") +_STRUCT_B = struct.Struct(">B") + + +class Frame: + """ + The base class for all HTTP/2 frames. + """ + #: The flags defined on this type of frame. + defined_flags = [] + + #: The byte used to define the type of the frame. + type = None + + # If 'has-stream', the frame's stream_id must be non-zero. If 'no-stream', + # it must be zero. If 'either', it's not checked. + stream_association = None + + def __init__(self, stream_id, flags=()): + #: The stream identifier for the stream this frame was received on. + #: Set to 0 for frames sent on the connection (stream-id 0). + self.stream_id = stream_id + + #: The flags set for this frame. + self.flags = Flags(self.defined_flags) + + #: The frame length, excluding the nine-byte header. + self.body_len = 0 + + for flag in flags: + self.flags.add(flag) + + if (not self.stream_id and + self.stream_association == _STREAM_ASSOC_HAS_STREAM): + raise InvalidDataError( + 'Stream ID must be non-zero for {}'.format( + type(self).__name__, + ) + ) + if (self.stream_id and + self.stream_association == _STREAM_ASSOC_NO_STREAM): + raise InvalidDataError( + 'Stream ID must be zero for {} with stream_id={}'.format( + type(self).__name__, + self.stream_id, + ) + ) + + def __repr__(self): + return ( + "{}(stream_id={}, flags={}): {}" + ).format( + type(self).__name__, + self.stream_id, + repr(self.flags), + self._body_repr(), + ) + + def _body_repr(self): + # More specific implementation may be provided by subclasses of Frame. + # This fallback shows the serialized (and truncated) body content. + return _raw_data_repr(self.serialize_body()) + + @staticmethod + def explain(data): + """ + Takes a bytestring and tries to parse a single frame and print it. + + This function is only provided for debugging purposes. + + :param data: A memoryview object containing the raw data of at least + one complete frame (header and body). + + .. versionadded:: 6.0.0 + """ + frame, length = Frame.parse_frame_header(data[:9]) + frame.parse_body(data[9:9 + length]) + print(frame) + return frame, length + + @staticmethod + def parse_frame_header(header, strict=False): + """ + Takes a 9-byte frame header and returns a tuple of the appropriate + Frame object and the length that needs to be read from the socket. + + This populates the flags field, and determines how long the body is. + + :param header: A memoryview object containing the 9-byte frame header + data of a frame. Must not contain more or less. + + :param strict: Whether to raise an exception when encountering a frame + not defined by spec and implemented by hyperframe. + + :raises hyperframe.exceptions.UnknownFrameError: If a frame of unknown + type is received. + + .. versionchanged:: 5.0.0 + Added :param:`strict` to accommodate :class:`ExtensionFrame` + """ + try: + fields = _STRUCT_HBBBL.unpack(header) + except struct.error: + raise InvalidFrameError("Invalid frame header") + + # First 24 bits are frame length. + length = (fields[0] << 8) + fields[1] + type = fields[2] + flags = fields[3] + stream_id = fields[4] & 0x7FFFFFFF + + try: + frame = FRAMES[type](stream_id) + except KeyError: + if strict: + raise UnknownFrameError(type, length) + frame = ExtensionFrame(type=type, stream_id=stream_id) + + frame.parse_flags(flags) + return (frame, length) + + def parse_flags(self, flag_byte): + for flag, flag_bit in self.defined_flags: + if flag_byte & flag_bit: + self.flags.add(flag) + + return self.flags + + def serialize(self): + """ + Convert a frame into a bytestring, representing the serialized form of + the frame. + """ + body = self.serialize_body() + self.body_len = len(body) + + # Build the common frame header. + # First, get the flags. + flags = 0 + + for flag, flag_bit in self.defined_flags: + if flag in self.flags: + flags |= flag_bit + + header = _STRUCT_HBBBL.pack( + (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits + self.body_len & 0xFF, + self.type, + flags, + self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits. + ) + + return header + body + + def serialize_body(self): + raise NotImplementedError() + + def parse_body(self, data): + """ + Given the body of a frame, parses it into frame data. This populates + the non-header parts of the frame: that is, it does not populate the + stream ID or flags. + + :param data: A memoryview object containing the body data of the frame. + Must not contain *more* data than the length returned by + :meth:`parse_frame_header + `. + """ + raise NotImplementedError() + + +class Padding: + """ + Mixin for frames that contain padding. Defines extra fields that can be + used and set by frames that can be padded. + """ + def __init__(self, stream_id, pad_length=0, **kwargs): + super().__init__(stream_id, **kwargs) + + #: The length of the padding to use. + self.pad_length = pad_length + + def serialize_padding_data(self): + if 'PADDED' in self.flags: + return _STRUCT_B.pack(self.pad_length) + return b'' + + def parse_padding_data(self, data): + if 'PADDED' in self.flags: + try: + self.pad_length = struct.unpack('!B', data[:1])[0] + except struct.error: + raise InvalidFrameError("Invalid Padding data") + return 1 + return 0 + + #: .. deprecated:: 5.2.1 + #: Use self.pad_length instead. + @property + def total_padding(self): # pragma: no cover + import warnings + warnings.warn( + "total_padding contains the same information as pad_length.", + DeprecationWarning + ) + return self.pad_length + + +class Priority: + """ + Mixin for frames that contain priority data. Defines extra fields that can + be used and set by frames that contain priority data. + """ + def __init__(self, + stream_id, + depends_on=0x0, + stream_weight=0x0, + exclusive=False, + **kwargs): + super().__init__(stream_id, **kwargs) + + #: The stream ID of the stream on which this stream depends. + self.depends_on = depends_on + + #: The weight of the stream. This is an integer between 0 and 256. + self.stream_weight = stream_weight + + #: Whether the exclusive bit was set. + self.exclusive = exclusive + + def serialize_priority_data(self): + return _STRUCT_LB.pack( + self.depends_on + (0x80000000 if self.exclusive else 0), + self.stream_weight + ) + + def parse_priority_data(self, data): + try: + self.depends_on, self.stream_weight = _STRUCT_LB.unpack(data[:5]) + except struct.error: + raise InvalidFrameError("Invalid Priority data") + + self.exclusive = True if self.depends_on >> 31 else False + self.depends_on &= 0x7FFFFFFF + return 5 + + +class DataFrame(Padding, Frame): + """ + DATA frames convey arbitrary, variable-length sequences of octets + associated with a stream. One or more DATA frames are used, for instance, + to carry HTTP request or response payloads. + """ + #: The flags defined for DATA frames. + defined_flags = [ + Flag('END_STREAM', 0x01), + Flag('PADDED', 0x08), + ] + + #: The type byte for data frames. + type = 0x0 + + stream_association = _STREAM_ASSOC_HAS_STREAM + + def __init__(self, stream_id, data=b'', **kwargs): + super().__init__(stream_id, **kwargs) + + #: The data contained on this frame. + self.data = data + + def serialize_body(self): + padding_data = self.serialize_padding_data() + padding = b'\0' * self.pad_length + if isinstance(self.data, memoryview): + self.data = self.data.tobytes() + return b''.join([padding_data, self.data, padding]) + + def parse_body(self, data): + padding_data_length = self.parse_padding_data(data) + self.data = ( + data[padding_data_length:len(data)-self.pad_length].tobytes() + ) + self.body_len = len(data) + + if self.pad_length and self.pad_length >= self.body_len: + raise InvalidPaddingError("Padding is too long.") + + @property + def flow_controlled_length(self): + """ + The length of the frame that needs to be accounted for when considering + flow control. + """ + padding_len = 0 + if 'PADDED' in self.flags: + # Account for extra 1-byte padding length field, which is still + # present if possibly zero-valued. + padding_len = self.pad_length + 1 + return len(self.data) + padding_len + + +class PriorityFrame(Priority, Frame): + """ + The PRIORITY frame specifies the sender-advised priority of a stream. It + can be sent at any time for an existing stream. This enables + reprioritisation of existing streams. + """ + #: The flags defined for PRIORITY frames. + defined_flags = [] + + #: The type byte defined for PRIORITY frames. + type = 0x02 + + stream_association = _STREAM_ASSOC_HAS_STREAM + + def _body_repr(self): + return "exclusive={}, depends_on={}, stream_weight={}".format( + self.exclusive, + self.depends_on, + self.stream_weight + ) + + def serialize_body(self): + return self.serialize_priority_data() + + def parse_body(self, data): + if len(data) > 5: + raise InvalidFrameError( + "PRIORITY must have 5 byte body: actual length %s." % + len(data) + ) + + self.parse_priority_data(data) + self.body_len = 5 + + +class RstStreamFrame(Frame): + """ + The RST_STREAM frame allows for abnormal termination of a stream. When sent + by the initiator of a stream, it indicates that they wish to cancel the + stream or that an error condition has occurred. When sent by the receiver + of a stream, it indicates that either the receiver is rejecting the stream, + requesting that the stream be cancelled or that an error condition has + occurred. + """ + #: The flags defined for RST_STREAM frames. + defined_flags = [] + + #: The type byte defined for RST_STREAM frames. + type = 0x03 + + stream_association = _STREAM_ASSOC_HAS_STREAM + + def __init__(self, stream_id, error_code=0, **kwargs): + super().__init__(stream_id, **kwargs) + + #: The error code used when resetting the stream. + self.error_code = error_code + + def _body_repr(self): + return "error_code={}".format( + self.error_code, + ) + + def serialize_body(self): + return _STRUCT_L.pack(self.error_code) + + def parse_body(self, data): + if len(data) != 4: + raise InvalidFrameError( + "RST_STREAM must have 4 byte body: actual length %s." % + len(data) + ) + + try: + self.error_code = _STRUCT_L.unpack(data)[0] + except struct.error: # pragma: no cover + raise InvalidFrameError("Invalid RST_STREAM body") + + self.body_len = 4 + + +class SettingsFrame(Frame): + """ + The SETTINGS frame conveys configuration parameters that affect how + endpoints communicate. The parameters are either constraints on peer + behavior or preferences. + + Settings are not negotiated. Settings describe characteristics of the + sending peer, which are used by the receiving peer. Different values for + the same setting can be advertised by each peer. For example, a client + might set a high initial flow control window, whereas a server might set a + lower value to conserve resources. + """ + #: The flags defined for SETTINGS frames. + defined_flags = [Flag('ACK', 0x01)] + + #: The type byte defined for SETTINGS frames. + type = 0x04 + + stream_association = _STREAM_ASSOC_NO_STREAM + + # We need to define the known settings, they may as well be class + # attributes. + #: The byte that signals the SETTINGS_HEADER_TABLE_SIZE setting. + HEADER_TABLE_SIZE = 0x01 + #: The byte that signals the SETTINGS_ENABLE_PUSH setting. + ENABLE_PUSH = 0x02 + #: The byte that signals the SETTINGS_MAX_CONCURRENT_STREAMS setting. + MAX_CONCURRENT_STREAMS = 0x03 + #: The byte that signals the SETTINGS_INITIAL_WINDOW_SIZE setting. + INITIAL_WINDOW_SIZE = 0x04 + #: The byte that signals the SETTINGS_MAX_FRAME_SIZE setting. + MAX_FRAME_SIZE = 0x05 + #: The byte that signals the SETTINGS_MAX_HEADER_LIST_SIZE setting. + MAX_HEADER_LIST_SIZE = 0x06 + #: The byte that signals SETTINGS_ENABLE_CONNECT_PROTOCOL setting. + ENABLE_CONNECT_PROTOCOL = 0x08 + + def __init__(self, stream_id=0, settings=None, **kwargs): + super().__init__(stream_id, **kwargs) + + if settings and "ACK" in kwargs.get("flags", ()): + raise InvalidDataError( + "Settings must be empty if ACK flag is set." + ) + + #: A dictionary of the setting type byte to the value of the setting. + self.settings = settings or {} + + def _body_repr(self): + return "settings={}".format( + self.settings, + ) + + def serialize_body(self): + return b''.join([_STRUCT_HL.pack(setting & 0xFF, value) + for setting, value in self.settings.items()]) + + def parse_body(self, data): + if 'ACK' in self.flags and len(data) > 0: + raise InvalidDataError( + "SETTINGS ack frame must not have payload: got %s bytes" % + len(data) + ) + + body_len = 0 + for i in range(0, len(data), 6): + try: + name, value = _STRUCT_HL.unpack(data[i:i+6]) + except struct.error: + raise InvalidFrameError("Invalid SETTINGS body") + + self.settings[name] = value + body_len += 6 + + self.body_len = body_len + + +class PushPromiseFrame(Padding, Frame): + """ + The PUSH_PROMISE frame is used to notify the peer endpoint in advance of + streams the sender intends to initiate. + """ + #: The flags defined for PUSH_PROMISE frames. + defined_flags = [ + Flag('END_HEADERS', 0x04), + Flag('PADDED', 0x08) + ] + + #: The type byte defined for PUSH_PROMISE frames. + type = 0x05 + + stream_association = _STREAM_ASSOC_HAS_STREAM + + def __init__(self, stream_id, promised_stream_id=0, data=b'', **kwargs): + super().__init__(stream_id, **kwargs) + + #: The stream ID that is promised by this frame. + self.promised_stream_id = promised_stream_id + + #: The HPACK-encoded header block for the simulated request on the new + #: stream. + self.data = data + + def _body_repr(self): + return "promised_stream_id={}, data={}".format( + self.promised_stream_id, + _raw_data_repr(self.data), + ) + + def serialize_body(self): + padding_data = self.serialize_padding_data() + padding = b'\0' * self.pad_length + data = _STRUCT_L.pack(self.promised_stream_id) + return b''.join([padding_data, data, self.data, padding]) + + def parse_body(self, data): + padding_data_length = self.parse_padding_data(data) + + try: + self.promised_stream_id = _STRUCT_L.unpack( + data[padding_data_length:padding_data_length + 4] + )[0] + except struct.error: + raise InvalidFrameError("Invalid PUSH_PROMISE body") + + self.data = ( + data[padding_data_length + 4:len(data)-self.pad_length].tobytes() + ) + self.body_len = len(data) + + if self.promised_stream_id == 0 or self.promised_stream_id % 2 != 0: + raise InvalidDataError( + "Invalid PUSH_PROMISE promised stream id: %s" % + self.promised_stream_id + ) + + if self.pad_length and self.pad_length >= self.body_len: + raise InvalidPaddingError("Padding is too long.") + + +class PingFrame(Frame): + """ + The PING frame is a mechanism for measuring a minimal round-trip time from + the sender, as well as determining whether an idle connection is still + functional. PING frames can be sent from any endpoint. + """ + #: The flags defined for PING frames. + defined_flags = [Flag('ACK', 0x01)] + + #: The type byte defined for PING frames. + type = 0x06 + + stream_association = _STREAM_ASSOC_NO_STREAM + + def __init__(self, stream_id=0, opaque_data=b'', **kwargs): + super().__init__(stream_id, **kwargs) + + #: The opaque data sent in this PING frame, as a bytestring. + self.opaque_data = opaque_data + + def _body_repr(self): + return "opaque_data={}".format( + self.opaque_data, + ) + + def serialize_body(self): + if len(self.opaque_data) > 8: + raise InvalidFrameError( + "PING frame may not have more than 8 bytes of data, got %s" % + self.opaque_data + ) + + data = self.opaque_data + data += b'\x00' * (8 - len(self.opaque_data)) + return data + + def parse_body(self, data): + if len(data) != 8: + raise InvalidFrameError( + "PING frame must have 8 byte length: got %s" % len(data) + ) + + self.opaque_data = data.tobytes() + self.body_len = 8 + + +class GoAwayFrame(Frame): + """ + The GOAWAY frame informs the remote peer to stop creating streams on this + connection. It can be sent from the client or the server. Once sent, the + sender will ignore frames sent on new streams for the remainder of the + connection. + """ + #: The flags defined for GOAWAY frames. + defined_flags = [] + + #: The type byte defined for GOAWAY frames. + type = 0x07 + + stream_association = _STREAM_ASSOC_NO_STREAM + + def __init__(self, + stream_id=0, + last_stream_id=0, + error_code=0, + additional_data=b'', + **kwargs): + super().__init__(stream_id, **kwargs) + + #: The last stream ID definitely seen by the remote peer. + self.last_stream_id = last_stream_id + + #: The error code for connection teardown. + self.error_code = error_code + + #: Any additional data sent in the GOAWAY. + self.additional_data = additional_data + + def _body_repr(self): + return "last_stream_id={}, error_code={}, additional_data={}".format( + self.last_stream_id, + self.error_code, + self.additional_data, + ) + + def serialize_body(self): + data = _STRUCT_LL.pack( + self.last_stream_id & 0x7FFFFFFF, + self.error_code + ) + data += self.additional_data + + return data + + def parse_body(self, data): + try: + self.last_stream_id, self.error_code = _STRUCT_LL.unpack( + data[:8] + ) + except struct.error: + raise InvalidFrameError("Invalid GOAWAY body.") + + self.body_len = len(data) + + if len(data) > 8: + self.additional_data = data[8:].tobytes() + + +class WindowUpdateFrame(Frame): + """ + The WINDOW_UPDATE frame is used to implement flow control. + + Flow control operates at two levels: on each individual stream and on the + entire connection. + + Both types of flow control are hop by hop; that is, only between the two + endpoints. Intermediaries do not forward WINDOW_UPDATE frames between + dependent connections. However, throttling of data transfer by any receiver + can indirectly cause the propagation of flow control information toward the + original sender. + """ + #: The flags defined for WINDOW_UPDATE frames. + defined_flags = [] + + #: The type byte defined for WINDOW_UPDATE frames. + type = 0x08 + + stream_association = _STREAM_ASSOC_EITHER + + def __init__(self, stream_id, window_increment=0, **kwargs): + super().__init__(stream_id, **kwargs) + + #: The amount the flow control window is to be incremented. + self.window_increment = window_increment + + def _body_repr(self): + return "window_increment={}".format( + self.window_increment, + ) + + def serialize_body(self): + return _STRUCT_L.pack(self.window_increment & 0x7FFFFFFF) + + def parse_body(self, data): + if len(data) > 4: + raise InvalidFrameError( + "WINDOW_UPDATE frame must have 4 byte length: got %s" % + len(data) + ) + + try: + self.window_increment = _STRUCT_L.unpack(data)[0] + except struct.error: + raise InvalidFrameError("Invalid WINDOW_UPDATE body") + + if not 1 <= self.window_increment <= 2**31-1: + raise InvalidDataError( + "WINDOW_UPDATE increment must be between 1 to 2^31-1" + ) + + self.body_len = 4 + + +class HeadersFrame(Padding, Priority, Frame): + """ + The HEADERS frame carries name-value pairs. It is used to open a stream. + HEADERS frames can be sent on a stream in the "open" or "half closed + (remote)" states. + + The HeadersFrame class is actually basically a data frame in this + implementation, because of the requirement to control the sizes of frames. + A header block fragment that doesn't fit in an entire HEADERS frame needs + to be followed with CONTINUATION frames. From the perspective of the frame + building code the header block is an opaque data segment. + """ + #: The flags defined for HEADERS frames. + defined_flags = [ + Flag('END_STREAM', 0x01), + Flag('END_HEADERS', 0x04), + Flag('PADDED', 0x08), + Flag('PRIORITY', 0x20), + ] + + #: The type byte defined for HEADERS frames. + type = 0x01 + + stream_association = _STREAM_ASSOC_HAS_STREAM + + def __init__(self, stream_id, data=b'', **kwargs): + super().__init__(stream_id, **kwargs) + + #: The HPACK-encoded header block. + self.data = data + + def _body_repr(self): + return "exclusive={}, depends_on={}, stream_weight={}, data={}".format( + self.exclusive, + self.depends_on, + self.stream_weight, + _raw_data_repr(self.data), + ) + + def serialize_body(self): + padding_data = self.serialize_padding_data() + padding = b'\0' * self.pad_length + + if 'PRIORITY' in self.flags: + priority_data = self.serialize_priority_data() + else: + priority_data = b'' + + return b''.join([padding_data, priority_data, self.data, padding]) + + def parse_body(self, data): + padding_data_length = self.parse_padding_data(data) + data = data[padding_data_length:] + + if 'PRIORITY' in self.flags: + priority_data_length = self.parse_priority_data(data) + else: + priority_data_length = 0 + + self.body_len = len(data) + self.data = ( + data[priority_data_length:len(data)-self.pad_length].tobytes() + ) + + if self.pad_length and self.pad_length >= self.body_len: + raise InvalidPaddingError("Padding is too long.") + + +class ContinuationFrame(Frame): + """ + The CONTINUATION frame is used to continue a sequence of header block + fragments. Any number of CONTINUATION frames can be sent on an existing + stream, as long as the preceding frame on the same stream is one of + HEADERS, PUSH_PROMISE or CONTINUATION without the END_HEADERS flag set. + + Much like the HEADERS frame, hyper treats this as an opaque data frame with + different flags and a different type. + """ + #: The flags defined for CONTINUATION frames. + defined_flags = [Flag('END_HEADERS', 0x04)] + + #: The type byte defined for CONTINUATION frames. + type = 0x09 + + stream_association = _STREAM_ASSOC_HAS_STREAM + + def __init__(self, stream_id, data=b'', **kwargs): + super().__init__(stream_id, **kwargs) + + #: The HPACK-encoded header block. + self.data = data + + def _body_repr(self): + return "data={}".format( + _raw_data_repr(self.data), + ) + + def serialize_body(self): + return self.data + + def parse_body(self, data): + self.data = data.tobytes() + self.body_len = len(data) + + +class AltSvcFrame(Frame): + """ + The ALTSVC frame is used to advertise alternate services that the current + host, or a different one, can understand. This frame is standardised as + part of RFC 7838. + + This frame does no work to validate that the ALTSVC field parameter is + acceptable per the rules of RFC 7838. + + .. note:: If the ``stream_id`` of this frame is nonzero, the origin field + must have zero length. Conversely, if the ``stream_id`` of this + frame is zero, the origin field must have nonzero length. Put + another way, a valid ALTSVC frame has ``stream_id != 0`` XOR + ``len(origin) != 0``. + """ + type = 0xA + + stream_association = _STREAM_ASSOC_EITHER + + def __init__(self, stream_id, origin=b'', field=b'', **kwargs): + super().__init__(stream_id, **kwargs) + + if not isinstance(origin, bytes): + raise InvalidDataError("AltSvc origin must be bytestring.") + if not isinstance(field, bytes): + raise InvalidDataError("AltSvc field must be a bytestring.") + self.origin = origin + self.field = field + + def _body_repr(self): + return "origin={}, field={}".format( + self.origin, + self.field, + ) + + def serialize_body(self): + origin_len = _STRUCT_H.pack(len(self.origin)) + return b''.join([origin_len, self.origin, self.field]) + + def parse_body(self, data): + try: + origin_len = _STRUCT_H.unpack(data[0:2])[0] + self.origin = data[2:2+origin_len].tobytes() + + if len(self.origin) != origin_len: + raise InvalidFrameError("Invalid ALTSVC frame body.") + + self.field = data[2+origin_len:].tobytes() + except (struct.error, ValueError): + raise InvalidFrameError("Invalid ALTSVC frame body.") + + self.body_len = len(data) + + +class ExtensionFrame(Frame): + """ + ExtensionFrame is used to wrap frames which are not natively interpretable + by hyperframe. + + Although certain byte prefixes are ordained by specification to have + certain contextual meanings, frames with other prefixes are not prohibited, + and may be used to communicate arbitrary meaning between HTTP/2 peers. + + Thus, hyperframe, rather than raising an exception when such a frame is + encountered, wraps it in a generic frame to be properly acted upon by + upstream consumers which might have additional context on how to use it. + + .. versionadded:: 5.0.0 + """ + + stream_association = _STREAM_ASSOC_EITHER + + def __init__(self, type, stream_id, flag_byte=0x0, body=b'', **kwargs): + super().__init__(stream_id, **kwargs) + self.type = type + self.flag_byte = flag_byte + self.body = body + + def _body_repr(self): + return "type={}, flag_byte={}, body={}".format( + self.type, + self.flag_byte, + _raw_data_repr(self.body), + ) + + def parse_flags(self, flag_byte): + """ + For extension frames, we parse the flags by just storing a flag byte. + """ + self.flag_byte = flag_byte + + def parse_body(self, data): + self.body = data.tobytes() + self.body_len = len(data) + + def serialize(self): + """ + A broad override of the serialize method that ensures that the data + comes back out exactly as it came in. This should not be used in most + user code: it exists only as a helper method if frames need to be + reconstituted. + """ + # Build the frame header. + # First, get the flags. + flags = self.flag_byte + + header = _STRUCT_HBBBL.pack( + (self.body_len >> 8) & 0xFFFF, # Length spread over top 24 bits + self.body_len & 0xFF, + self.type, + flags, + self.stream_id & 0x7FFFFFFF # Stream ID is 32 bits. + ) + + return header + self.body + + +def _raw_data_repr(data): + if not data: + return "None" + r = binascii.hexlify(data).decode('ascii') + if len(r) > 20: + r = r[:20] + "..." + return "" + + +_FRAME_CLASSES = [ + DataFrame, + HeadersFrame, + PriorityFrame, + RstStreamFrame, + SettingsFrame, + PushPromiseFrame, + PingFrame, + GoAwayFrame, + WindowUpdateFrame, + ContinuationFrame, + AltSvcFrame, +] +#: FRAMES maps the type byte for each frame to the class used to represent that +#: frame. +FRAMES = {cls.type: cls for cls in _FRAME_CLASSES} diff -Nru python-hyperframe-5.2.0/src/hyperframe/__init__.py python-hyperframe-6.0.0/src/hyperframe/__init__.py --- python-hyperframe-5.2.0/src/hyperframe/__init__.py 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/src/hyperframe/__init__.py 2020-09-06 10:18:31.000000000 +0000 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- +""" +hyperframe +~~~~~~~~~~ + +A module for providing a pure-Python HTTP/2 framing layer. +""" +__version__ = '6.0.0' diff -Nru python-hyperframe-5.2.0/src/hyperframe.egg-info/dependency_links.txt python-hyperframe-6.0.0/src/hyperframe.egg-info/dependency_links.txt --- python-hyperframe-5.2.0/src/hyperframe.egg-info/dependency_links.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/src/hyperframe.egg-info/dependency_links.txt 2020-09-06 10:21:04.000000000 +0000 @@ -0,0 +1 @@ + diff -Nru python-hyperframe-5.2.0/src/hyperframe.egg-info/PKG-INFO python-hyperframe-6.0.0/src/hyperframe.egg-info/PKG-INFO --- python-hyperframe-5.2.0/src/hyperframe.egg-info/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/src/hyperframe.egg-info/PKG-INFO 2020-09-06 10:21:04.000000000 +0000 @@ -0,0 +1,71 @@ +Metadata-Version: 2.1 +Name: hyperframe +Version: 6.0.0 +Summary: HTTP/2 framing layer for Python +Home-page: https://github.com/python-hyper/hyperframe/ +Author: Cory Benfield +Author-email: cory@lukasa.co.uk +License: MIT License +Description: ====================================== + hyperframe: Pure-Python HTTP/2 framing + ====================================== + + .. image:: https://github.com/python-hyper/hyperframe/workflows/CI/badge.svg + :target: https://github.com/python-hyper/hyperframe/actions + :alt: Build Status + .. image:: https://codecov.io/gh/python-hyper/hyperframe/branch/master/graph/badge.svg + :target: https://codecov.io/gh/python-hyper/hyperframe + :alt: Code Coverage + .. image:: https://readthedocs.org/projects/hyperframe/badge/?version=latest + :target: https://hyperframe.readthedocs.io/en/latest/ + :alt: Documentation Status + .. image:: https://img.shields.io/badge/chat-join_now-brightgreen.svg + :target: https://gitter.im/python-hyper/community + :alt: Chat community + + This library contains the HTTP/2 framing code used in the `hyper`_ project. It + provides a pure-Python codebase that is capable of decoding a binary stream + into HTTP/2 frames. + + This library is used directly by `hyper`_ and a number of other projects to + provide HTTP/2 frame decoding logic. + + Contributing + ============ + + hyperframe 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 + ======= + + hyperframe is made available under the MIT License. For more details, see the + ``LICENSE`` file in the repository. + + Authors + ======= + + hyperframe is maintained by Cory Benfield, with contributions from others. For + more details about the contributors, please see ``CONTRIBUTORS.rst``. + + .. _hyper: http://python-hyper.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 :: 3 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Requires-Python: >=3.6.1 +Description-Content-Type: text/x-rst diff -Nru python-hyperframe-5.2.0/src/hyperframe.egg-info/SOURCES.txt python-hyperframe-6.0.0/src/hyperframe.egg-info/SOURCES.txt --- python-hyperframe-5.2.0/src/hyperframe.egg-info/SOURCES.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/src/hyperframe.egg-info/SOURCES.txt 2020-09-06 10:21:04.000000000 +0000 @@ -0,0 +1,27 @@ +.gitmodules +CHANGELOG.rst +CONTRIBUTORS.rst +LICENSE +MANIFEST.in +README.rst +setup.cfg +setup.py +tox.ini +docs/Makefile +docs/make.bat +docs/source/api.rst +docs/source/conf.py +docs/source/index.rst +docs/source/installation.rst +src/hyperframe/__init__.py +src/hyperframe/exceptions.py +src/hyperframe/flags.py +src/hyperframe/frame.py +src/hyperframe.egg-info/PKG-INFO +src/hyperframe.egg-info/SOURCES.txt +src/hyperframe.egg-info/dependency_links.txt +src/hyperframe.egg-info/top_level.txt +test/__init__.py +test/test_external_collection.py +test/test_flags.py +test/test_frames.py \ No newline at end of file diff -Nru python-hyperframe-5.2.0/src/hyperframe.egg-info/top_level.txt python-hyperframe-6.0.0/src/hyperframe.egg-info/top_level.txt --- python-hyperframe-5.2.0/src/hyperframe.egg-info/top_level.txt 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/src/hyperframe.egg-info/top_level.txt 2020-09-06 10:21:04.000000000 +0000 @@ -0,0 +1 @@ +hyperframe diff -Nru python-hyperframe-5.2.0/test/test_external_collection.py python-hyperframe-6.0.0/test/test_external_collection.py --- python-hyperframe-5.2.0/test/test_external_collection.py 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/test/test_external_collection.py 2020-07-29 22:16:00.000000000 +0000 @@ -0,0 +1,85 @@ +# https://github.com/http2jp/http2-frame-test-case + +import os +import json +import pytest +from hyperframe.frame import Frame + +tc_filepaths = [] +root = os.path.dirname(__file__) +path = os.walk(os.path.join(root, "http2-frame-test-case")) +for dirpath, dirnames, filenames in path: + for filename in filenames: + if os.path.splitext(filename)[1] != ".json": + continue + tc_filepaths.append( + os.path.relpath(os.path.join(dirpath, filename), root) + ) + + +def check_valid_frame(tc, data): # noqa: C901 + new_frame, length = Frame.parse_frame_header(data[:9], strict=True) + new_frame.parse_body(memoryview(data[9:9 + length])) + + assert tc["frame"]["length"] == length + assert tc["frame"]["stream_identifier"] == new_frame.stream_id + assert tc["frame"]["type"] == new_frame.type + + flags = 0 + for flag, flag_bit in new_frame.defined_flags: + if flag in new_frame.flags: + flags |= flag_bit + assert tc["frame"]["flags"] == flags + + p = tc["frame"]["frame_payload"] + if "header_block_fragment" in p: + assert p["header_block_fragment"] == new_frame.data.decode() + if "data" in p: + assert p["data"] == new_frame.data.decode() + if "padding" in p: + # the padding data itself is not retained by hyperframe after parsing + pass + if "padding_length" in p and p["padding_length"]: + assert p["padding_length"] == new_frame.pad_length + if "error_code" in p: + assert p["error_code"] == new_frame.error_code + if "additional_debug_data" in p: + assert p["additional_debug_data"].encode() == new_frame.additional_data + if "last_stream_id" in p: + assert p["last_stream_id"] == new_frame.last_stream_id + if "stream_dependency" in p: + assert p["stream_dependency"] or 0 == new_frame.depends_on + if "weight" in p and p["weight"]: + assert p["weight"] - 1 == new_frame.stream_weight + if "exclusive" in p: + assert (p["exclusive"] or False) == new_frame.exclusive + if "opaque_data" in p: + assert p["opaque_data"].encode() == new_frame.opaque_data + if "promised_stream_id" in p: + assert p["promised_stream_id"] == new_frame.promised_stream_id + if "settings" in p: + assert dict(p["settings"]) == new_frame.settings + if "window_size_increment" in p: + assert p["window_size_increment"] == new_frame.window_increment + + +class TestExternalCollection: + @pytest.mark.parametrize('tc_filepath', tc_filepaths) + def test(self, tc_filepath): + with open(os.path.join(root, tc_filepath)) as f: + tc = json.load(f) + + data = bytes.fromhex(tc["wire"]) + + if tc["error"] is None and tc["frame"]: + check_valid_frame(tc, data) + elif tc["error"] and tc["frame"] is None: + with pytest.raises(Exception): + new_frame, length = Frame.parse_frame_header( + data[:9], + strict=True + ) + new_frame.parse_body(memoryview(data[9:9 + length])) + assert length == new_frame.body_len + else: + pytest.fail("unexpected test case: {} {}".format(tc_filepath, tc)) diff -Nru python-hyperframe-5.2.0/test/test_flags.py python-hyperframe-6.0.0/test/test_flags.py --- python-hyperframe-5.2.0/test/test_flags.py 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/test/test_flags.py 2020-09-05 15:39:12.000000000 +0000 @@ -5,7 +5,7 @@ import pytest -class TestFlags(object): +class TestFlags: def test_add(self): flags = Flags([Flag("VALID_FLAG", 0x00)]) assert not flags @@ -33,3 +33,11 @@ flags.add("VALID_FLAG") with pytest.raises(ValueError): flags.add("INVALID_FLAG") + + def test_repr(self): + flags = Flags([Flag("VALID_FLAG", 0x00), Flag("OTHER_FLAG", 0x01)]) + assert repr(flags) == "[]" + flags.add("VALID_FLAG") + assert repr(flags) == "['VALID_FLAG']" + flags.add("OTHER_FLAG") + assert repr(flags) == "['OTHER_FLAG', 'VALID_FLAG']" diff -Nru python-hyperframe-5.2.0/test/test_frames.py python-hyperframe-6.0.0/test/test_frames.py --- python-hyperframe-5.2.0/test/test_frames.py 2019-04-13 09:53:28.000000000 +0000 +++ python-hyperframe-6.0.0/test/test_frames.py 2020-09-05 15:39:12.000000000 +0000 @@ -5,7 +5,7 @@ ContinuationFrame, AltSvcFrame, ExtensionFrame ) from hyperframe.exceptions import ( - UnknownFrameError, InvalidPaddingError, InvalidFrameError + UnknownFrameError, InvalidPaddingError, InvalidFrameError, InvalidDataError ) import pytest @@ -17,21 +17,21 @@ return f -class TestGeneralFrameBehaviour(object): +class TestGeneralFrameBehaviour: def test_base_frame_ignores_flags(self): - f = Frame(stream_id=0) + f = Frame(0) flags = f.parse_flags(0xFF) assert not flags assert isinstance(flags, Flags) def test_base_frame_cant_serialize(self): - f = Frame(stream_id=0) + f = Frame(0) with pytest.raises(NotImplementedError): f.serialize() def test_base_frame_cant_parse_body(self): data = b'' - f = Frame(stream_id=0) + f = Frame(0) with pytest.raises(NotImplementedError): f.parse_body(data) @@ -86,26 +86,44 @@ assert f.serialize() == frame_data def test_repr(self, monkeypatch): - f = Frame(stream_id=0) + f = Frame(0) monkeypatch.setattr(Frame, "serialize_body", lambda _: b"body") - assert repr(f) == "Frame(Stream: 0; Flags: None): 626f6479" + assert repr(f) == ( + "Frame(stream_id=0, flags=[]): " + ) + + f.stream_id = 42 + f.flags = ["END_STREAM", "PADDED"] + assert repr(f) == ( + "Frame(stream_id=42, flags=['END_STREAM', 'PADDED']): " + ) monkeypatch.setattr(Frame, "serialize_body", lambda _: b"A"*25) assert repr(f) == ( - "Frame(Stream: 0; Flags: None): {}...".format("41"*10) + "Frame(stream_id=42, flags=['END_STREAM', 'PADDED']): ".format("41"*10) ) + def test_frame_explain(self, capsys): + d = b'\x00\x00\x08\x00\x01\x00\x00\x00\x01testdata' + Frame.explain(memoryview(d)) + captured = capsys.readouterr() + assert captured.out.strip() == "DataFrame(stream_id=1, flags=['END_STREAM']): " + def test_cannot_parse_invalid_frame_header(self): with pytest.raises(InvalidFrameError): Frame.parse_frame_header(b'\x00\x00\x08\x00\x01\x00\x00\x00') -class TestDataFrame(object): +class TestDataFrame: payload = b'\x00\x00\x08\x00\x01\x00\x00\x00\x01testdata' payload_with_padding = ( b'\x00\x00\x13\x00\x09\x00\x00\x00\x01\x0Atestdata' + b'\0' * 10 ) + def test_repr(self): + f = DataFrame(1, b"testdata") + assert repr(f).endswith("") + def test_data_frame_has_correct_flags(self): f = DataFrame(1) flags = f.parse_flags(0xFF) @@ -179,7 +197,7 @@ assert f.flow_controlled_length == 8 def test_data_frame_comes_on_a_stream(self): - with pytest.raises(ValueError): + with pytest.raises(InvalidDataError): DataFrame(0) def test_long_data_frame(self): @@ -228,9 +246,17 @@ assert new_frame.data == b'' -class TestPriorityFrame(object): +class TestPriorityFrame: payload = b'\x00\x00\x05\x02\x00\x00\x00\x00\x01\x80\x00\x00\x04\x40' + def test_repr(self): + f = PriorityFrame(1) + assert repr(f).endswith("exclusive=False, depends_on=0, stream_weight=0") + f.exclusive = True + f.depends_on = 0x04 + f.stream_weight = 64 + assert repr(f).endswith("exclusive=True, depends_on=4, stream_weight=64") + def test_priority_frame_has_no_flags(self): f = PriorityFrame(1) flags = f.parse_flags(0xFF) @@ -262,8 +288,14 @@ assert f.exclusive is True assert f.body_len == 5 + def test_priority_frame_invalid(self): + with pytest.raises(InvalidFrameError): + decode_frame( + b'\x00\x00\x06\x02\x00\x00\x00\x00\x01\x80\x00\x00\x04\x40\xFF' + ) + def test_priority_frame_comes_on_a_stream(self): - with pytest.raises(ValueError): + with pytest.raises(InvalidDataError): PriorityFrame(0) def test_short_priority_frame_errors(self): @@ -271,7 +303,13 @@ decode_frame(self.payload[:-2]) -class TestRstStreamFrame(object): +class TestRstStreamFrame: + def test_repr(self): + f = RstStreamFrame(1) + assert repr(f).endswith("error_code=0") + f.error_code = 420 + assert repr(f).endswith("error_code=420") + def test_rst_stream_frame_has_no_flags(self): f = RstStreamFrame(1) flags = f.parse_flags(0xFF) @@ -295,16 +333,16 @@ assert f.body_len == 4 def test_rst_stream_frame_comes_on_a_stream(self): - with pytest.raises(ValueError): + with pytest.raises(InvalidDataError): RstStreamFrame(0) def test_rst_stream_frame_must_have_body_length_four(self): f = RstStreamFrame(1) - with pytest.raises(ValueError): + with pytest.raises(InvalidFrameError): f.parse_body(b'\x01') -class TestSettingsFrame(object): +class TestSettingsFrame: serialized = ( b'\x00\x00\x2A\x04\x01\x00\x00\x00\x00' + # Frame header b'\x00\x01\x00\x00\x10\x00' + # HEADER_TABLE_SIZE @@ -326,6 +364,12 @@ SettingsFrame.ENABLE_CONNECT_PROTOCOL: 1, } + def test_repr(self): + f = SettingsFrame() + assert repr(f).endswith("settings={}") + f.settings[SettingsFrame.MAX_FRAME_SIZE] = 16384 + assert repr(f).endswith("settings={5: 16384}") + def test_settings_frame_has_only_one_flag(self): f = SettingsFrame() flags = f.parse_flags(0xFF) @@ -352,27 +396,46 @@ assert 'ACK' in f.flags def test_settings_frame_ack_and_settings(self): - with pytest.raises(ValueError): + with pytest.raises(InvalidDataError): SettingsFrame(settings=self.settings, flags=('ACK',)) + with pytest.raises(InvalidDataError): + decode_frame(self.serialized) + def test_settings_frame_parses_properly(self): - f = decode_frame(self.serialized) + # unset the ACK flag to allow correct parsing + data = self.serialized[:4] + b"\x00" + self.serialized[5:] + + f = decode_frame(data) assert isinstance(f, SettingsFrame) - assert f.flags == set(['ACK']) + assert f.flags == set() assert f.settings == self.settings assert f.body_len == 42 + def test_settings_frame_invalid_body_length(self): + with pytest.raises(InvalidFrameError): + decode_frame( + b'\x00\x00\x2A\x04\x00\x00\x00\x00\x00\xFF\xFF\xFF\xFF' + ) + def test_settings_frames_never_have_streams(self): - with pytest.raises(ValueError): - SettingsFrame(stream_id=1) + with pytest.raises(InvalidDataError): + SettingsFrame(1) def test_short_settings_frame_errors(self): - with pytest.raises(InvalidFrameError): + with pytest.raises(InvalidDataError): decode_frame(self.serialized[:-2]) -class TestPushPromiseFrame(object): +class TestPushPromiseFrame: + def test_repr(self): + f = PushPromiseFrame(1) + assert repr(f).endswith("promised_stream_id=0, data=None") + f.promised_stream_id = 4 + f.data = b"testdata" + assert repr(f).endswith("promised_stream_id=4, data=") + def test_push_promise_frame_flags(self): f = PushPromiseFrame(1) flags = f.parse_flags(0xFF) @@ -406,6 +469,21 @@ assert f.data == b'hello world' assert f.body_len == 15 + def test_push_promise_frame_with_padding(self): + s = ( + b'\x00\x00\x17\x05\x0C\x00\x00\x00\x01' + + b'\x07\x00\x00\x00\x04' + + b'hello world' + + b'padding' + ) + f = decode_frame(s) + + assert isinstance(f, PushPromiseFrame) + assert f.flags == set(['END_HEADERS', 'PADDED']) + assert f.promised_stream_id == 4 + assert f.data == b'hello world' + assert f.body_len == 23 + def test_push_promise_frame_with_invalid_padding_fails_to_parse(self): # This frame has a padding length of 6 bytes, but a total length of # only 5. @@ -416,13 +494,22 @@ def test_push_promise_frame_with_no_length_parses(self): # Fixes issue with empty data frames raising InvalidPaddingError. - f = PushPromiseFrame(1) + f = PushPromiseFrame(1, 2) f.data = b'' data = f.serialize() new_frame = decode_frame(data) assert new_frame.data == b'' + def test_push_promise_frame_invalid(self): + data = PushPromiseFrame(1, 0).serialize() + with pytest.raises(InvalidDataError): + decode_frame(data) + + data = PushPromiseFrame(1, 3).serialize() + with pytest.raises(InvalidDataError): + decode_frame(data) + def test_short_push_promise_errors(self): s = ( b'\x00\x00\x0F\x05\x04\x00\x00\x00\x01' + @@ -433,7 +520,13 @@ decode_frame(s) -class TestPingFrame(object): +class TestPingFrame: + def test_repr(self): + f = PingFrame() + assert repr(f).endswith("opaque_data=b''") + f.opaque_data = b'hello' + assert repr(f).endswith("opaque_data=b'hello'") + def test_ping_frame_has_only_one_flag(self): f = PingFrame() flags = f.parse_flags(0xFF) @@ -455,7 +548,7 @@ f = PingFrame() f.opaque_data = b'\x01\x02\x03\x04\x05\x06\x07\x08\x09' - with pytest.raises(ValueError): + with pytest.raises(InvalidFrameError): f.serialize() def test_ping_frame_parses_properly(self): @@ -471,21 +564,29 @@ assert f.body_len == 8 def test_ping_frame_never_has_a_stream(self): - with pytest.raises(ValueError): - PingFrame(stream_id=1) + with pytest.raises(InvalidDataError): + PingFrame(1) def test_ping_frame_has_no_more_than_body_length_8(self): f = PingFrame() - with pytest.raises(ValueError): + with pytest.raises(InvalidFrameError): f.parse_body(b'\x01\x02\x03\x04\x05\x06\x07\x08\x09') def test_ping_frame_has_no_less_than_body_length_8(self): f = PingFrame() - with pytest.raises(ValueError): + with pytest.raises(InvalidFrameError): f.parse_body(b'\x01\x02\x03\x04\x05\x06\x07') -class TestGoAwayFrame(object): +class TestGoAwayFrame: + def test_repr(self): + f = GoAwayFrame() + assert repr(f).endswith("last_stream_id=0, error_code=0, additional_data=b''") + f.last_stream_id = 64 + f.error_code = 32 + f.additional_data = b'hello' + assert repr(f).endswith("last_stream_id=64, error_code=32, additional_data=b'hello'") + def test_go_away_has_no_flags(self): f = GoAwayFrame() flags = f.parse_flags(0xFF) @@ -535,8 +636,8 @@ assert f.body_len == 8 def test_goaway_frame_never_has_a_stream(self): - with pytest.raises(ValueError): - GoAwayFrame(stream_id=1) + with pytest.raises(InvalidDataError): + GoAwayFrame(1) def test_short_goaway_frame_errors(self): s = ( @@ -548,7 +649,14 @@ decode_frame(s) -class TestWindowUpdateFrame(object): +class TestWindowUpdateFrame: + def test_repr(self): + f = WindowUpdateFrame(0) + assert repr(f).endswith("window_increment=0") + f.stream_id = 1 + f.window_increment = 512 + assert repr(f).endswith("window_increment=512") + def test_window_update_has_no_flags(self): f = WindowUpdateFrame(0) flags = f.parse_flags(0xFF) @@ -574,12 +682,30 @@ def test_short_windowupdate_frame_errors(self): s = b'\x00\x00\x04\x08\x00\x00\x00\x00\x00\x00\x00\x02' # -1 byte + with pytest.raises(InvalidFrameError): + decode_frame(s) + s = b'\x00\x00\x05\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02' with pytest.raises(InvalidFrameError): decode_frame(s) + with pytest.raises(InvalidDataError): + decode_frame(WindowUpdateFrame(0).serialize()) + + with pytest.raises(InvalidDataError): + decode_frame(WindowUpdateFrame(2**31).serialize()) + + +class TestHeadersFrame: + def test_repr(self): + f = HeadersFrame(1) + assert repr(f).endswith("exclusive=False, depends_on=0, stream_weight=0, data=None") + f.data = b'hello' + f.exclusive = True + f.depends_on = 42 + f.stream_weight = 64 + assert repr(f).endswith("exclusive=True, depends_on=42, stream_weight=64, data=") -class TestHeadersFrame(object): def test_headers_frame_flags(self): f = HeadersFrame(1) flags = f.parse_flags(0xFF) @@ -661,7 +787,13 @@ assert new_frame.data == b'' -class TestContinuationFrame(object): +class TestContinuationFrame: + def test_repr(self): + f = ContinuationFrame(1) + assert repr(f).endswith("data=None") + f.data = b'hello' + assert repr(f).endswith("data=") + def test_continuation_frame_flags(self): f = ContinuationFrame(1) flags = f.parse_flags(0xFF) @@ -689,7 +821,7 @@ assert f.body_len == 11 -class TestAltSvcFrame(object): +class TestAltSvcFrame: payload_with_origin = ( b'\x00\x00\x31' # Length b'\x0A' # Type @@ -718,14 +850,22 @@ b'Alt-Svc: h2=":443"; ma=2592000; persist=1' # Field Value ) + def test_repr(self): + f = AltSvcFrame(0) + assert repr(f).endswith("origin=b'', field=b''") + f.field = b'h2="alt.example.com:8000", h2=":443"' + assert repr(f).endswith("origin=b'', field=b'h2=\"alt.example.com:8000\", h2=\":443\"'") + f.origin = b'example.com' + assert repr(f).endswith("origin=b'example.com', field=b'h2=\"alt.example.com:8000\", h2=\":443\"'") + def test_altsvc_frame_flags(self): - f = AltSvcFrame(stream_id=0) + f = AltSvcFrame(0) flags = f.parse_flags(0xFF) assert flags == set() def test_altsvc_frame_with_origin_serializes_properly(self): - f = AltSvcFrame(stream_id=0) + f = AltSvcFrame(0) f.origin = b'example.com' f.field = b'h2="alt.example.com:8000", h2=":443"' @@ -742,7 +882,7 @@ assert f.stream_id == 0 def test_altsvc_frame_without_origin_serializes_properly(self): - f = AltSvcFrame(stream_id=1, origin=b'', field=b'h2=":8000"; ma=60') + f = AltSvcFrame(1, origin=b'', field=b'h2=":8000"; ma=60') s = f.serialize() assert s == self.payload_without_origin @@ -755,16 +895,9 @@ assert f.body_len == 19 assert f.stream_id == 1 - def test_altsvc_frame_without_origin_parses_with_good_repr(self): - f = decode_frame(self.payload_without_origin) - - assert repr(f) == ( - "AltSvcFrame(Stream: 1; Flags: None): 000068323d223a383030..." - ) - def test_altsvc_frame_with_origin_and_stream_serializes_properly(self): # This frame is not valid, but we allow it to be serialized anyway. - f = AltSvcFrame(stream_id=1) + f = AltSvcFrame(1) f.origin = b'example.com' f.field = b'Alt-Svc: h2=":443"; ma=2592000; persist=1' @@ -778,14 +911,20 @@ decode_frame(self.payload_with_origin[:10]) def test_altsvc_with_unicode_origin_fails(self): - with pytest.raises(ValueError): + with pytest.raises(InvalidDataError): AltSvcFrame( stream_id=0, origin=u'hello', field=b'h2=":8000"; ma=60' ) def test_altsvc_with_unicode_field_fails(self): - with pytest.raises(ValueError): + with pytest.raises(InvalidDataError): AltSvcFrame( stream_id=0, origin=b'hello', field=u'h2=":8000"; ma=60' ) + + +class TestExtensionFrame: + def test_repr(self): + f = ExtensionFrame(0xFF, 1, 42, b'hello') + assert repr(f).endswith("type=255, flag_byte=42, body=") diff -Nru python-hyperframe-5.2.0/tox.ini python-hyperframe-6.0.0/tox.ini --- python-hyperframe-5.2.0/tox.ini 1970-01-01 00:00:00.000000000 +0000 +++ python-hyperframe-6.0.0/tox.ini 2020-09-06 10:18:25.000000000 +0000 @@ -0,0 +1,52 @@ +[tox] +envlist = py36, py37, py38, pypy3, lint, docs, packaging + +[gh-actions] +python = + 3.6: py36 + 3.7: py37 + 3.8: py38, lint, docs, packaging + pypy3: pypy3 + +[testenv] +passenv = + GITHUB_* +deps = + pytest==6.0.1 + pytest-cov==2.10.1 + pytest-xdist==2.1.0 +commands = + pytest --cov-report=xml --cov-report=term --cov=hyperframe {posargs} + +[testenv:pypy3] +# temporarily disable coverage testing on PyPy due to performance problems +commands = pytest {posargs} + +[testenv:docs] +basepython = python3.8 +deps = + sphinx==3.2.1 +whitelist_externals = make +changedir = {toxinidir}/docs +commands = + make clean + make html + +[testenv:lint] +basepython = python3.8 +deps = + flake8==3.8.3 +commands = flake8 --max-complexity 10 src test + +[testenv:packaging] +basepython = python3.8 +deps = + check-manifest==0.42 + readme-renderer==26.0 + twine==3.2.0 +whitelist_externals = rm +commands = + rm -rf dist/ + check-manifest + python setup.py sdist bdist_wheel + twine check dist/*