Hello community, here is the log from the commit of package python-h11 for openSUSE:Factory checked in at 2020-11-10 13:46:36 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-h11 (Old) and /work/SRC/openSUSE:Factory/.python-h11.new.11331 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-h11" Tue Nov 10 13:46:36 2020 rev:7 rq:847415 version:0.11.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-h11/python-h11.changes 2020-08-14 09:34:38.348438703 +0200 +++ /work/SRC/openSUSE:Factory/.python-h11.new.11331/python-h11.changes 2020-11-10 13:53:31.658841567 +0100 @@ -1,0 +2,12 @@ +Tue Nov 10 08:02:09 UTC 2020 - Dirk Mueller <dmuel...@suse.com> + +- update to 0.11.0: + * h11 now stores and makes available the raw header name as + received. In addition h11 will write out header names with the same + casing as passed to it. This allows compatibility with systems that + expect titlecased header names. See `#31 + * Multiple content length headers are now merged into a single header + if all the values are equal, if any are unequal a LocalProtocol + error is raised (as before). See `#92 + +------------------------------------------------------------------- Old: ---- h11-0.10.0.tar.gz New: ---- h11-0.11.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-h11.spec ++++++ --- /var/tmp/diff_new_pack.ftYpP8/_old 2020-11-10 13:53:32.150840636 +0100 +++ /var/tmp/diff_new_pack.ftYpP8/_new 2020-11-10 13:53:32.154840630 +0100 @@ -18,7 +18,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} Name: python-h11 -Version: 0.10.0 +Version: 0.11.0 Release: 0 Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/11 License: MIT ++++++ h11-0.10.0.tar.gz -> h11-0.11.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/PKG-INFO new/h11-0.11.0/PKG-INFO --- old/h11-0.10.0/PKG-INFO 2020-08-13 11:50:23.070536600 +0200 +++ new/h11-0.11.0/PKG-INFO 2020-10-05 20:23:10.682039700 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: h11 -Version: 0.10.0 +Version: 0.11.0 Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1 Home-page: https://github.com/python-hyper/h11 Author: Nathaniel J. Smith diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/docs/source/api.rst new/h11-0.11.0/docs/source/api.rst --- old/h11-0.10.0/docs/source/api.rst 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/docs/source/api.rst 2020-10-05 20:22:54.000000000 +0200 @@ -128,6 +128,21 @@ an error because `Content-Length` should always be an integer. We may add additional checks in the future. +While we make sure to expose header names as lowercased bytes, we also +preserve the original header casing that is used. Compliant HTTP +agents should always treat headers in a case insensitive manner, but +this may not always be the case. When sending bytes over the wire we +send headers preserving whatever original header casing was used. + +It is possible to access the headers in their raw original casing, +which may be useful for some user output or debugging purposes. + +.. ipython:: python + + original_headers = [("Host", "example.com")] + req = h11.Request(method="GET", target="/", headers=original_headers) + req.headers.raw_items() + .. _http_version-format: It's not just headers we normalize to being byte-strings: the same diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/docs/source/changes.rst new/h11-0.11.0/docs/source/changes.rst --- old/h11-0.10.0/docs/source/changes.rst 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/docs/source/changes.rst 2020-10-05 20:22:54.000000000 +0200 @@ -5,6 +5,27 @@ .. towncrier release notes start +v0.11.0 (2020-10-05) +-------------------- + +New features: + +* h11 now stores and makes available the raw header name as + received. In addition h11 will write out header names with the same + casing as passed to it. This allows compatibility with systems that + expect titlecased header names. See `#31 + <https://github.com/python-hyper/h11/issues/31>`__. +* Multiple content length headers are now merged into a single header + if all the values are equal, if any are unequal a LocalProtocol + error is raised (as before). See `#92 + <https://github.com/python-hyper/h11/issues/92>`__. + +Backwards **in**\compatible changes: + +* Headers added by h11, rather than passed to it, now have titlecased + names. Whilst this should help compatibility it replaces the + previous lowercased header names. + v0.10.0 (2020-08-14) -------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/fuzz/afl-server.py new/h11-0.11.0/fuzz/afl-server.py --- old/h11-0.10.0/fuzz/afl-server.py 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/fuzz/afl-server.py 2020-10-05 20:22:54.000000000 +0200 @@ -6,6 +6,7 @@ import sys import afl + import h11 if sys.version_info[0] >= 3: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/h11/_connection.py new/h11-0.11.0/h11/_connection.py --- old/h11-0.10.0/h11/_connection.py 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/h11/_connection.py 2020-10-05 20:22:54.000000000 +0200 @@ -534,7 +534,7 @@ def _clean_up_response_headers_for_sending(self, response): assert type(response) is Response - headers = list(response.headers) + headers = response.headers need_close = False # HEAD requests need some special handling: they always act like they @@ -560,13 +560,13 @@ # but the HTTP spec says that if our peer does this then we have # to fix it instead of erroring out, so we'll accord the user the # same respect). - set_comma_header(headers, b"content-length", []) + headers = set_comma_header(headers, b"content-length", []) if self.their_http_version is None or self.their_http_version < b"1.1": # Either we never got a valid request and are sending back an # error (their_http_version is None), so we assume the worst; # or else we did get a valid HTTP/1.0 request, so we know that # they don't understand chunked encoding. - set_comma_header(headers, b"transfer-encoding", []) + headers = set_comma_header(headers, b"transfer-encoding", []) # This is actually redundant ATM, since currently we # unconditionally disable keep-alive when talking to HTTP/1.0 # peers. But let's be defensive just in case we add @@ -574,13 +574,13 @@ if self._request_method != b"HEAD": need_close = True else: - set_comma_header(headers, b"transfer-encoding", ["chunked"]) + headers = set_comma_header(headers, b"transfer-encoding", ["chunked"]) if not self._cstate.keep_alive or need_close: # Make sure Connection: close is set connection = set(get_comma_header(headers, b"connection")) connection.discard(b"keep-alive") connection.add(b"close") - set_comma_header(headers, b"connection", sorted(connection)) + headers = set_comma_header(headers, b"connection", sorted(connection)) response.headers = headers diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/h11/_headers.py new/h11-0.11.0/h11/_headers.py --- old/h11-0.10.0/h11/_headers.py 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/h11/_headers.py 2020-10-05 20:22:54.000000000 +0200 @@ -62,9 +62,63 @@ _field_value_re = re.compile(field_value.encode("ascii")) +class Headers: + """ + A list-like interface that allows iterating over headers as byte-pairs + of (lowercased-name, value). + + Internally we actually store the representation as three-tuples, + including both the raw original casing, in order to preserve casing + over-the-wire, and the lowercased name, for case-insensitive comparisions. + + r = Request( + method="GET", + target="/", + headers=[("Host", "example.org"), ("Connection", "keep-alive")], + http_version="1.1", + ) + assert r.headers == [ + (b"host", b"example.org"), + (b"connection", b"keep-alive") + ] + assert r.headers.raw_items() == [ + (b"Host", b"example.org"), + (b"Connection", b"keep-alive") + ] + """ + + __slots__ = "_full_items" + + def __init__(self, full_items): + self._full_items = full_items + + def __iter__(self): + for _, name, value in self._full_items: + yield name, value + + def __bool__(self): + return bool(self._full_items) + + def __eq__(self, other): + return list(self) == list(other) + + def __len__(self): + return len(self._full_items) + + def __repr__(self): + return "<Headers(%s)>" % repr(list(self)) + + def __getitem__(self, idx): + _, name, value = self._full_items[idx] + return (name, value) + + def raw_items(self): + return [(raw_name, value) for raw_name, _, value in self._full_items] + + def normalize_and_validate(headers, _parsed=False): new_headers = [] - saw_content_length = False + seen_content_length = None saw_transfer_encoding = False for name, value in headers: # For headers coming out of the parser, we can safely skip some steps, @@ -75,13 +129,20 @@ value = bytesify(value) validate(_field_name_re, name, "Illegal header name {!r}", name) validate(_field_value_re, value, "Illegal header value {!r}", value) + raw_name = name name = name.lower() if name == b"content-length": - if saw_content_length: - raise LocalProtocolError("multiple Content-Length headers") + lengths = set(length.strip() for length in value.split(b",")) + if len(lengths) != 1: + raise LocalProtocolError("conflicting Content-Length headers") + value = lengths.pop() validate(_content_length_re, value, "bad Content-Length") - saw_content_length = True - if name == b"transfer-encoding": + if seen_content_length is None: + seen_content_length = value + new_headers.append((raw_name, name, value)) + elif seen_content_length != value: + raise LocalProtocolError("conflicting Content-Length headers") + elif name == b"transfer-encoding": # "A server that receives a request message with a transfer coding # it does not understand SHOULD respond with 501 (Not # Implemented)." @@ -99,8 +160,10 @@ error_status_hint=501, ) saw_transfer_encoding = True - new_headers.append((name, value)) - return new_headers + new_headers.append((raw_name, name, value)) + else: + new_headers.append((raw_name, name, value)) + return Headers(new_headers) def get_comma_header(headers, name): @@ -140,7 +203,7 @@ # "100-continue". Splitting on commas is harmless. Case insensitive. # out = [] - for found_name, found_raw_value in headers: + for _, found_name, found_raw_value in headers._full_items: if found_name == name: found_raw_value = found_raw_value.lower() for found_split_value in found_raw_value.split(b","): @@ -152,13 +215,21 @@ def set_comma_header(headers, name, new_values): # The header name `name` is expected to be lower-case bytes. + # + # Note that when we store the header we use title casing for the header + # names, in order to match the conventional HTTP header style. + # + # Simply calling `.title()` is a blunt approach, but it's correct + # here given the cases where we're using `set_comma_header`... + # + # Connection, Content-Length, Transfer-Encoding. new_headers = [] - for found_name, found_raw_value in headers: + for found_raw_name, found_name, found_raw_value in headers._full_items: if found_name != name: - new_headers.append((found_name, found_raw_value)) + new_headers.append((found_raw_name, found_raw_value)) for new_value in new_values: - new_headers.append((name, new_value)) - headers[:] = normalize_and_validate(new_headers) + new_headers.append((name.title(), new_value)) + return normalize_and_validate(new_headers) def has_expect_100_continue(request): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/h11/_readers.py new/h11-0.11.0/h11/_readers.py --- old/h11-0.10.0/h11/_readers.py 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/h11/_readers.py 2020-10-05 20:22:54.000000000 +0200 @@ -58,7 +58,9 @@ # Python 3, validate() takes either and returns matches as bytes. But # on Python 2, validate can return matches as bytearrays, so we have # to explicitly cast back. - matches = validate(header_field_re, bytes(line), "illegal header line: {!r}", bytes(line)) + matches = validate( + header_field_re, bytes(line), "illegal header line: {!r}", bytes(line) + ) yield (matches["field_name"], matches["field_value"]) @@ -71,7 +73,9 @@ return None if not lines: raise LocalProtocolError("no request line received") - matches = validate(request_line_re, lines[0], "illegal request line: {!r}", lines[0]) + matches = validate( + request_line_re, lines[0], "illegal request line: {!r}", lines[0] + ) return Request( headers=list(_decode_header_lines(lines[1:])), _parsed=True, **matches ) @@ -152,7 +156,12 @@ chunk_header = buf.maybe_extract_until_next(b"\r\n") if chunk_header is None: return None - matches = validate(chunk_header_re, chunk_header, "illegal chunk header: {!r}", chunk_header) + matches = validate( + chunk_header_re, + chunk_header, + "illegal chunk header: {!r}", + chunk_header, + ) # XX FIXME: we discard chunk extensions. Does anyone care? # We convert to bytes because Python 2's `int()` function doesn't # work properly on bytearray objects. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/h11/_version.py new/h11-0.11.0/h11/_version.py --- old/h11-0.10.0/h11/_version.py 2020-08-13 11:49:25.000000000 +0200 +++ new/h11-0.11.0/h11/_version.py 2020-10-05 20:23:01.000000000 +0200 @@ -13,4 +13,4 @@ # want. (Contrast with the special suffix 1.0.0.dev, which sorts *before* # 1.0.0.) -__version__ = "0.10.0" +__version__ = "0.11.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/h11/_writers.py new/h11-0.11.0/h11/_writers.py --- old/h11-0.10.0/h11/_writers.py 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/h11/_writers.py 2020-10-05 20:22:54.000000000 +0200 @@ -38,12 +38,13 @@ # "Since the Host field-value is critical information for handling a # request, a user agent SHOULD generate Host as the first header field # following the request-line." - RFC 7230 - for name, value in headers: + raw_items = headers._full_items + for raw_name, name, value in raw_items: if name == b"host": - write(bytesmod(b"%s: %s\r\n", (name, value))) - for name, value in headers: + write(bytesmod(b"%s: %s\r\n", (raw_name, value))) + for raw_name, name, value in raw_items: if name != b"host": - write(bytesmod(b"%s: %s\r\n", (name, value))) + write(bytesmod(b"%s: %s\r\n", (raw_name, value))) write(b"\r\n") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/h11/tests/test_connection.py new/h11-0.11.0/h11/tests/test_connection.py --- old/h11-0.10.0/h11/tests/test_connection.py 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/h11/tests/test_connection.py 2020-10-05 20:22:54.000000000 +0200 @@ -96,7 +96,7 @@ ), ) assert data == ( - b"GET / HTTP/1.1\r\n" b"host: example.com\r\n" b"content-length: 10\r\n\r\n" + b"GET / HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 10\r\n\r\n" ) for conn in p.conns: @@ -113,7 +113,7 @@ assert data == b"HTTP/1.1 100 \r\n\r\n" data = p.send(SERVER, Response(status_code=200, headers=[("Content-Length", "11")])) - assert data == b"HTTP/1.1 200 \r\ncontent-length: 11\r\n\r\n" + assert data == b"HTTP/1.1 200 \r\nContent-Length: 11\r\n\r\n" for conn in p.conns: assert conn.states == {CLIENT: SEND_BODY, SERVER: SEND_BODY} @@ -243,7 +243,7 @@ # We automatically Connection: close back at them assert ( c.send(Response(status_code=200, headers=[])) - == b"HTTP/1.1 200 \r\nconnection: close\r\n\r\n" + == b"HTTP/1.1 200 \r\nConnection: close\r\n\r\n" ) assert c.send(Data(data=b"12345")) == b"12345" @@ -303,7 +303,7 @@ receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n") assert ( c.send(Response(status_code=200, headers=user_headers)) - == b"HTTP/1.1 200 \r\nconnection: close\r\n\r\n" + == b"HTTP/1.1 200 \r\nConnection: close\r\n\r\n" ) assert c.send(Data(data=b"12345")) == b"12345" @@ -876,7 +876,7 @@ if role is SERVER: assert ( c.send(Response(status_code=400, headers=[])) - == b"HTTP/1.1 400 \r\nconnection: close\r\n\r\n" + == b"HTTP/1.1 400 \r\nConnection: close\r\n\r\n" ) # After an error sending, you can no longer send @@ -988,14 +988,14 @@ c = setup(method, b"1.1") assert ( c.send(Response(status_code=200, headers=[])) == b"HTTP/1.1 200 \r\n" - b"transfer-encoding: chunked\r\n\r\n" + b"Transfer-Encoding: chunked\r\n\r\n" ) # No Content-Length, HTTP/1.0 peer, frame with connection: close c = setup(method, b"1.0") assert ( c.send(Response(status_code=200, headers=[])) == b"HTTP/1.1 200 \r\n" - b"connection: close\r\n\r\n" + b"Connection: close\r\n\r\n" ) # Content-Length + Transfer-Encoding, TE wins @@ -1011,7 +1011,7 @@ ) ) == b"HTTP/1.1 200 \r\n" - b"transfer-encoding: chunked\r\n\r\n" + b"Transfer-Encoding: chunked\r\n\r\n" ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/h11/tests/test_events.py new/h11-0.11.0/h11/tests/test_events.py --- old/h11-0.10.0/h11/tests/test_events.py 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/h11/tests/test_events.py 2020-10-05 20:22:54.000000000 +0200 @@ -163,3 +163,19 @@ assert r.status_code == HTTPStatus.OK assert type(r.status_code) is not type(HTTPStatus.OK) assert type(r.status_code) is int + + +def test_header_casing(): + r = Request( + method="GET", + target="/", + headers=[("Host", "example.org"), ("Connection", "keep-alive")], + http_version="1.1", + ) + assert len(r.headers) == 2 + assert r.headers[0] == (b"host", b"example.org") + assert r.headers == [(b"host", b"example.org"), (b"connection", b"keep-alive")] + assert r.headers.raw_items() == [ + (b"Host", b"example.org"), + (b"Connection", b"keep-alive"), + ] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/h11/tests/test_headers.py new/h11-0.11.0/h11/tests/test_headers.py --- old/h11-0.10.0/h11/tests/test_headers.py 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/h11/tests/test_headers.py 2020-10-05 20:22:54.000000000 +0200 @@ -54,6 +54,18 @@ normalize_and_validate([("Content-Length", "1x")]) with pytest.raises(LocalProtocolError): normalize_and_validate([("Content-Length", "1"), ("Content-Length", "2")]) + assert normalize_and_validate( + [("Content-Length", "0"), ("Content-Length", "0")] + ) == [(b"content-length", b"0")] + assert normalize_and_validate([("Content-Length", "0 , 0")]) == [ + (b"content-length", b"0") + ] + with pytest.raises(LocalProtocolError): + normalize_and_validate( + [("Content-Length", "1"), ("Content-Length", "1"), ("Content-Length", "2")] + ) + with pytest.raises(LocalProtocolError): + normalize_and_validate([("Content-Length", "1 , 1,2")]) # transfer-encoding assert normalize_and_validate([("Transfer-Encoding", "chunked")]) == [ @@ -83,7 +95,7 @@ assert get_comma_header(headers, b"connection") == [b"close", b"foo", b"bar"] - set_comma_header(headers, b"newthing", ["a", "b"]) + headers = set_comma_header(headers, b"newthing", ["a", "b"]) with pytest.raises(LocalProtocolError): set_comma_header(headers, b"newthing", [" a", "b"]) @@ -96,7 +108,7 @@ (b"newthing", b"b"), ] - set_comma_header(headers, b"whatever", ["different thing"]) + headers = set_comma_header(headers, b"whatever", ["different thing"]) assert headers == [ (b"connection", b"close"), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/h11/tests/test_io.py new/h11-0.11.0/h11/tests/test_io.py --- old/h11-0.10.0/h11/tests/test_io.py 2020-08-13 11:49:16.000000000 +0200 +++ new/h11-0.11.0/h11/tests/test_io.py 2020-10-05 20:22:54.000000000 +0200 @@ -1,7 +1,7 @@ import pytest from .._events import * -from .._headers import normalize_and_validate +from .._headers import Headers, normalize_and_validate from .._readers import ( _obsolete_line_fold, ChunkedReader, @@ -31,12 +31,12 @@ target="/a", headers=[("Host", "foo"), ("Connection", "close")], ), - b"GET /a HTTP/1.1\r\nhost: foo\r\nconnection: close\r\n\r\n", + b"GET /a HTTP/1.1\r\nHost: foo\r\nConnection: close\r\n\r\n", ), ( (SERVER, SEND_RESPONSE), Response(status_code=200, headers=[("Connection", "close")], reason=b"OK"), - b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\n", + b"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", ), ( (SERVER, SEND_RESPONSE), @@ -48,7 +48,7 @@ InformationalResponse( status_code=101, headers=[("Upgrade", "websocket")], reason=b"Upgrade" ), - b"HTTP/1.1 101 Upgrade\r\nupgrade: websocket\r\n\r\n", + b"HTTP/1.1 101 Upgrade\r\nUpgrade: websocket\r\n\r\n", ), ( (SERVER, SEND_RESPONSE), @@ -121,7 +121,7 @@ normalize_and_validate([("foo", "bar"), ("baz", "quux")]), b"foo: bar\r\nbaz: quux\r\n\r\n", ) - tw(write_headers, [], b"\r\n") + tw(write_headers, Headers([]), b"\r\n") # We understand HTTP/1.0, but we don't speak it with pytest.raises(LocalProtocolError): @@ -435,7 +435,7 @@ assert ( dowrite(w, EndOfMessage(headers=[("Etag", "asdf"), ("a", "b")])) - == b"0\r\netag: asdf\r\na: b\r\n\r\n" + == b"0\r\nEtag: asdf\r\na: b\r\n\r\n" ) @@ -503,5 +503,5 @@ tw( write_headers, normalize_and_validate([("foo", "bar"), ("Host", "example.com")]), - b"host: example.com\r\nfoo: bar\r\n\r\n", + b"Host: example.com\r\nfoo: bar\r\n\r\n", ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/h11-0.10.0/h11.egg-info/PKG-INFO new/h11-0.11.0/h11.egg-info/PKG-INFO --- old/h11-0.10.0/h11.egg-info/PKG-INFO 2020-08-13 11:50:20.000000000 +0200 +++ new/h11-0.11.0/h11.egg-info/PKG-INFO 2020-10-05 20:23:10.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: h11 -Version: 0.10.0 +Version: 0.11.0 Summary: A pure-Python, bring-your-own-I/O implementation of HTTP/1.1 Home-page: https://github.com/python-hyper/h11 Author: Nathaniel J. Smith