[Python-modules-commits] [python-daphne] 02/07: New upstream version 1.4.2

Michael Fladischer fladi at moszumanska.debian.org
Mon Jan 8 16:55:07 UTC 2018


This is an automated email from the git hooks/post-receive script.

fladi pushed a commit to branch debian/master
in repository python-daphne.

commit 0b8b502ffa80153c33b1bfa3cb6b25f3aeebf919
Author: Michael Fladischer <FladischerMichael at fladi.at>
Date:   Mon Jan 8 16:18:48 2018 +0100

    New upstream version 1.4.2
---
 .travis.yml                        |  1 +
 CHANGELOG.txt                      | 22 ++++++++++++++
 README.rst                         |  2 +-
 daphne/__init__.py                 |  2 +-
 daphne/cli.py                      |  1 +
 daphne/http_protocol.py            | 13 ++++++---
 daphne/server.py                   | 16 +++++++++--
 daphne/tests/__init__.py           |  7 +++++
 daphne/tests/test_http_request.py  |  5 ++--
 daphne/tests/test_http_response.py |  2 +-
 daphne/tests/test_utils.py         | 59 +++++++++++++++++++++++++++-----------
 daphne/tests/test_ws.py            |  3 +-
 daphne/utils.py                    | 41 +++++++++++++++++---------
 daphne/ws_protocol.py              |  5 ++--
 14 files changed, 135 insertions(+), 44 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index 9672140..da96f02 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,5 @@
 sudo: false
+dist: trusty
 
 language: python
 
diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 5a1b551..269cece 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -1,3 +1,25 @@
+1.4.2 (2018-01-05)
+------------------
+
+* Bugfix for WebSocket protocol when X-Forwarded-For is turned on.
+
+
+1.4.1 (2018-01-02)
+------------------
+
+* Bugfix for a bad merge of HTTPFactory for X-Forwarded-Proto causing Daphne
+  to not start.
+
+
+1.4.0 (2018-01-02)
+------------------
+
+* The X-Forwarded-Proto header can now be used to pass along protocol from
+  a reverse proxy.
+
+* WebSocket headers are now correctly always passed as bytestrings.
+
+
 1.3.0 (2017-06-16)
 ------------------
 
diff --git a/README.rst b/README.rst
index bab5110..aa285b6 100644
--- a/README.rst
+++ b/README.rst
@@ -57,7 +57,7 @@ Daphne 1.1 and above supports terminating HTTP/2 connections natively. You'll
 need to do a couple of things to get it working, though. First, you need to
 make sure you install the Twisted ``http2`` and ``tls`` extras::
 
-    pip install -U Twisted[tls,http2]
+    pip install -U 'Twisted[tls,http2]'
 
 Next, because all current browsers only support HTTP/2 when using TLS, you will
 need to start Daphne with TLS turned on, which can be done using the Twisted endpoint syntax::
diff --git a/daphne/__init__.py b/daphne/__init__.py
index 67bc602..daa50c7 100755
--- a/daphne/__init__.py
+++ b/daphne/__init__.py
@@ -1 +1 @@
-__version__ = "1.3.0"
+__version__ = "1.4.2"
diff --git a/daphne/cli.py b/daphne/cli.py
index b27cb06..a149632 100755
--- a/daphne/cli.py
+++ b/daphne/cli.py
@@ -213,6 +213,7 @@ class CommandLineInterface(object):
             verbosity=args.verbosity,
             proxy_forwarded_address_header='X-Forwarded-For' if args.proxy_headers else None,
             proxy_forwarded_port_header='X-Forwarded-Port' if args.proxy_headers else None,
+            proxy_forwarded_proto_header='X-Forwarded-Proto' if args.proxy_headers else None,
             force_sync=args.force_sync,
         )
         self.server.run()
diff --git a/daphne/http_protocol.py b/daphne/http_protocol.py
index 32dc276..41e8552 100755
--- a/daphne/http_protocol.py
+++ b/daphne/http_protocol.py
@@ -80,12 +80,16 @@ class WebRequest(http.Request):
                 self.client_addr = None
                 self.server_addr = None
 
+            self.client_scheme = 'https' if self.isSecure() else 'http'
+
             if self.factory.proxy_forwarded_address_header:
-                self.client_addr = parse_x_forwarded_for(
+                self.client_addr, self.client_scheme = parse_x_forwarded_for(
                     self.requestHeaders,
                     self.factory.proxy_forwarded_address_header,
                     self.factory.proxy_forwarded_port_header,
-                    self.client_addr
+                    self.factory.proxy_forwarded_proto_header,
+                    self.client_addr,
+                    self.client_scheme
                 )
 
             # Check for unicodeish path (or it'll crash when trying to parse)
@@ -166,7 +170,7 @@ class WebRequest(http.Request):
                         "method": self.method.decode("ascii"),
                         "path": self.unquote(self.path),
                         "root_path": self.root_path,
-                        "scheme": "https" if self.isSecure() else "http",
+                        "scheme": self.client_scheme,
                         "query_string": self.query_string,
                         "headers": self.clean_headers,
                         "body": self.content.read(),
@@ -308,7 +312,7 @@ class HTTPFactory(http.HTTPFactory):
     routed appropriately.
     """
 
-    def __init__(self, channel_layer, action_logger=None, send_channel=None, timeout=120, websocket_timeout=86400, ping_interval=20, ping_timeout=30, ws_protocols=None, root_path="", websocket_connect_timeout=30, proxy_forwarded_address_header=None, proxy_forwarded_port_header=None, websocket_handshake_timeout=5):
+    def __init__(self, channel_layer, action_logger=None, send_channel=None, timeout=120, websocket_timeout=86400, ping_interval=20, ping_timeout=30, ws_protocols=None, root_path="", websocket_connect_timeout=30, proxy_forwarded_address_header=None, proxy_forwarded_port_header=None, proxy_forwarded_proto_header=None, websocket_handshake_timeout=5):
         http.HTTPFactory.__init__(self)
         self.channel_layer = channel_layer
         self.action_logger = action_logger
@@ -320,6 +324,7 @@ class HTTPFactory(http.HTTPFactory):
         self.ping_interval = ping_interval
         self.proxy_forwarded_address_header = proxy_forwarded_address_header
         self.proxy_forwarded_port_header = proxy_forwarded_port_header
+        self.proxy_forwarded_proto_header = proxy_forwarded_proto_header
         # We track all sub-protocols for response channel mapping
         self.reply_protocols = {}
         # Make a factory for WebSocket protocols
diff --git a/daphne/server.py b/daphne/server.py
index 20b19cb..fc9f761 100755
--- a/daphne/server.py
+++ b/daphne/server.py
@@ -36,6 +36,7 @@ class Server(object):
         root_path="",
         proxy_forwarded_address_header=None,
         proxy_forwarded_port_header=None,
+        proxy_forwarded_proto_header=None,
         force_sync=False,
         verbosity=1,
         websocket_handshake_timeout=5
@@ -66,6 +67,7 @@ class Server(object):
         self.ping_timeout = ping_timeout
         self.proxy_forwarded_address_header = proxy_forwarded_address_header
         self.proxy_forwarded_port_header = proxy_forwarded_port_header
+        self.proxy_forwarded_proto_header = proxy_forwarded_proto_header
         # If they did not provide a websocket timeout, default it to the
         # channel layer's group_expiry value if present, or one day if not.
         self.websocket_timeout = websocket_timeout or getattr(channel_layer, "group_expiry", 86400)
@@ -95,6 +97,7 @@ class Server(object):
             root_path=self.root_path,
             proxy_forwarded_address_header=self.proxy_forwarded_address_header,
             proxy_forwarded_port_header=self.proxy_forwarded_port_header,
+            proxy_forwarded_proto_header=self.proxy_forwarded_proto_header,
             websocket_handshake_timeout=self.websocket_handshake_timeout
         )
         if self.verbosity <= 1:
@@ -121,10 +124,19 @@ class Server(object):
             logger.info("Listening on endpoint %s" % socket_description)
             # Twisted requires str on python2 (not unicode) and str on python3 (not bytes)
             ep = serverFromString(reactor, str(socket_description))
-            self.listeners.append(ep.listen(self.factory))
+            listener = ep.listen(self.factory)
+            listener.addErrback(self.on_listener_error)
+            self.listeners.append(listener)
 
         reactor.run(installSignalHandlers=self.signal_handlers)
 
+    def on_listener_error(self, failure):
+        """
+        Callback function used to process interface listener errors.
+        """
+
+        logger.error(failure.getErrorMessage())
+
     def backend_reader_sync(self):
         """
         Runs as an-often-as-possible task with the reactor, unless there was
@@ -215,7 +227,7 @@ def build_endpoint_description_strings(
     if unix_socket:
         socket_descriptions.append('unix:%s' % unix_socket)
 
-    if file_descriptor:
+    if file_descriptor is not None:
         socket_descriptions.append('fd:fileno=%d' % int(file_descriptor))
 
     return socket_descriptions
diff --git a/daphne/tests/__init__.py b/daphne/tests/__init__.py
index e69de29..3f43747 100644
--- a/daphne/tests/__init__.py
+++ b/daphne/tests/__init__.py
@@ -0,0 +1,7 @@
+from hypothesis import HealthCheck, settings
+
+settings.register_profile(
+    'daphne',
+    settings(suppress_health_check=[HealthCheck.too_slow]),
+)
+settings.load_profile('daphne')
diff --git a/daphne/tests/test_http_request.py b/daphne/tests/test_http_request.py
index ee38ab9..dd64ae3 100644
--- a/daphne/tests/test_http_request.py
+++ b/daphne/tests/test_http_request.py
@@ -8,7 +8,7 @@ import unittest
 from six.moves.urllib import parse
 
 from asgiref.inmemory import ChannelLayer
-from hypothesis import given, assume, settings, HealthCheck
+from hypothesis import given, assume
 from twisted.test import proto_helpers
 
 from daphne.http_protocol import HTTPFactory
@@ -99,7 +99,6 @@ class TestHTTPRequestSpec(testcases.ASGIHTTPTestCase):
         request_body=http_strategies.http_body(),
     )
     # This test is slow enough that on Travis, hypothesis sometimes complains.
-    @settings(suppress_health_check=[HealthCheck.too_slow])
     def test_kitchen_sink(
             self, request_method, request_path, request_params, request_headers, request_body):
         """
@@ -172,6 +171,7 @@ class TestProxyHandling(unittest.TestCase):
     def test_x_forwarded_for_parsed(self):
         self.factory.proxy_forwarded_address_header = 'X-Forwarded-For'
         self.factory.proxy_forwarded_port_header = 'X-Forwarded-Port'
+        self.factory.proxy_forwarded_proto_header = 'X-Forwarded-Proto'
         self.proto.dataReceived(
             b"GET /te%20st-%C3%A0/?foo=+bar HTTP/1.1\r\n" +
             b"Host: somewhere.com\r\n" +
@@ -186,6 +186,7 @@ class TestProxyHandling(unittest.TestCase):
     def test_x_forwarded_for_port_missing(self):
         self.factory.proxy_forwarded_address_header = 'X-Forwarded-For'
         self.factory.proxy_forwarded_port_header = 'X-Forwarded-Port'
+        self.factory.proxy_forwarded_proto_header = 'X-Forwarded-Proto'
         self.proto.dataReceived(
             b"GET /te%20st-%C3%A0/?foo=+bar HTTP/1.1\r\n" +
             b"Host: somewhere.com\r\n" +
diff --git a/daphne/tests/test_http_response.py b/daphne/tests/test_http_response.py
index a650deb..f5f5a28 100644
--- a/daphne/tests/test_http_response.py
+++ b/daphne/tests/test_http_response.py
@@ -70,7 +70,7 @@ class TestHTTPResponseSpec(testcases.ASGIHTTPTestCase):
 
     @given(
         headers=http_strategies.headers(),
-        body=http_strategies.http_body()
+        body=http_strategies.http_body(),
     )
     def test_kitchen_sink(self, headers, body):
         """
diff --git a/daphne/tests/test_utils.py b/daphne/tests/test_utils.py
index 525fec9..9e962a6 100644
--- a/daphne/tests/test_utils.py
+++ b/daphne/tests/test_utils.py
@@ -16,11 +16,13 @@ class TestXForwardedForHttpParsing(TestCase):
     def test_basic(self):
         headers = Headers({
             b'X-Forwarded-For': [b'10.1.2.3'],
-            b'X-Forwarded-Port': [b'1234']
+            b'X-Forwarded-Port': [b'1234'],
+            b'X-Forwarded-Proto': [b'https']
         })
         result = parse_x_forwarded_for(headers)
-        self.assertEqual(result, ['10.1.2.3', 1234])
-        self.assertIsInstance(result[0], six.text_type)
+        self.assertEqual(result, (['10.1.2.3', 1234], 'https'))
+        self.assertIsInstance(result[0][0], six.text_type)
+        self.assertIsInstance(result[1], six.text_type)
 
     def test_address_only(self):
         headers = Headers({
@@ -28,7 +30,7 @@ class TestXForwardedForHttpParsing(TestCase):
         })
         self.assertEqual(
             parse_x_forwarded_for(headers),
-            ['10.1.2.3', 0]
+            (['10.1.2.3', 0], None)
         )
 
     def test_v6_address(self):
@@ -37,7 +39,7 @@ class TestXForwardedForHttpParsing(TestCase):
         })
         self.assertEqual(
             parse_x_forwarded_for(headers),
-            ['1043::a321:0001', 0]
+            (['1043::a321:0001', 0], None)
         )
 
     def test_multiple_proxys(self):
@@ -46,19 +48,39 @@ class TestXForwardedForHttpParsing(TestCase):
         })
         self.assertEqual(
             parse_x_forwarded_for(headers),
-            ['10.1.2.3', 0]
+            (['10.1.2.3', 0], None)
         )
 
-    def test_original(self):
+    def test_original_addr(self):
+        headers = Headers({})
+        self.assertEqual(
+            parse_x_forwarded_for(headers, original_addr=['127.0.0.1', 80]),
+            (['127.0.0.1', 80], None)
+        )
+
+    def test_original_proto(self):
         headers = Headers({})
         self.assertEqual(
-            parse_x_forwarded_for(headers, original=['127.0.0.1', 80]),
-            ['127.0.0.1', 80]
+            parse_x_forwarded_for(headers, original_scheme='http'),
+            (None, 'http')
         )
 
     def test_no_original(self):
         headers = Headers({})
-        self.assertIsNone(parse_x_forwarded_for(headers))
+        self.assertEqual(
+            parse_x_forwarded_for(headers),
+            (None, None)
+        )
+
+    def test_address_and_proto(self):
+        headers = Headers({
+            b'X-Forwarded-For': [b'10.1.2.3'],
+            b'X-Forwarded-Proto': [b'https'],
+        })
+        self.assertEqual(
+            parse_x_forwarded_for(headers),
+            (['10.1.2.3', 0], 'https')
+        )
 
 
 class TestXForwardedForWsParsing(TestCase):
@@ -73,7 +95,7 @@ class TestXForwardedForWsParsing(TestCase):
         }
         self.assertEqual(
             parse_x_forwarded_for(headers),
-            ['10.1.2.3', 1234]
+            (['10.1.2.3', 1234], None)
         )
 
     def test_address_only(self):
@@ -82,7 +104,7 @@ class TestXForwardedForWsParsing(TestCase):
         }
         self.assertEqual(
             parse_x_forwarded_for(headers),
-            ['10.1.2.3', 0]
+            (['10.1.2.3', 0], None)
         )
 
     def test_v6_address(self):
@@ -91,7 +113,7 @@ class TestXForwardedForWsParsing(TestCase):
         }
         self.assertEqual(
             parse_x_forwarded_for(headers),
-            ['1043::a321:0001', 0]
+            (['1043::a321:0001', 0], None)
         )
 
     def test_multiple_proxys(self):
@@ -100,16 +122,19 @@ class TestXForwardedForWsParsing(TestCase):
         }
         self.assertEqual(
             parse_x_forwarded_for(headers),
-            ['10.1.2.3', 0]
+            (['10.1.2.3', 0], None)
         )
 
     def test_original(self):
         headers = {}
         self.assertEqual(
-            parse_x_forwarded_for(headers, original=['127.0.0.1', 80]),
-            ['127.0.0.1', 80]
+            parse_x_forwarded_for(headers, original_addr=['127.0.0.1', 80]),
+            (['127.0.0.1', 80], None)
         )
 
     def test_no_original(self):
         headers = {}
-        self.assertIsNone(parse_x_forwarded_for(headers))
+        self.assertEqual(
+            parse_x_forwarded_for(headers),
+            (None, None)
+        )
diff --git a/daphne/tests/test_ws.py b/daphne/tests/test_ws.py
index ceb1deb..f03655a 100644
--- a/daphne/tests/test_ws.py
+++ b/daphne/tests/test_ws.py
@@ -1,7 +1,7 @@
 # coding: utf8
 from __future__ import unicode_literals
 
-from hypothesis import assume, given, strategies
+from hypothesis import assume, given, strategies, settings
 from twisted.test import proto_helpers
 
 from asgiref.inmemory import ChannelLayer
@@ -66,6 +66,7 @@ class TestHandshake(testcases.ASGIWebSocketTestCase):
         params=http_strategies.query_params(),
         headers=http_strategies.headers(),
     )
+    @settings(perform_health_check=False)
     def test_connection(self, path, params, headers):
         message = WebSocketConnection().connect(path, params, headers)
         self.assert_valid_websocket_connect_message(message, path, params, headers)
diff --git a/daphne/utils.py b/daphne/utils.py
index cb8043c..04cef09 100644
--- a/daphne/utils.py
+++ b/daphne/utils.py
@@ -5,41 +5,51 @@ def header_value(headers, header_name):
     value = headers[header_name]
     if isinstance(value, list):
         value = value[0]
-    return value.decode("utf-8")
+    # decode to urf-8 if value is bytes
+    if isinstance(value, bytes):
+        value = value.decode("utf-8")
+    return value
 
 
 def parse_x_forwarded_for(headers,
                           address_header_name='X-Forwarded-For',
                           port_header_name='X-Forwarded-Port',
-                          original=None):
+                          proto_header_name='X-Forwarded-Proto',
+                          original_addr=None,
+                          original_scheme=None):
     """
     Parses an X-Forwarded-For header and returns a host/port pair as a list.
 
     @param headers: The twisted-style object containing a request's headers
     @param address_header_name: The name of the expected host header
     @param port_header_name: The name of the expected port header
-    @param original: A host/port pair that should be returned if the headers are not in the request
-    @return: A list containing a host (string) as the first entry and a port (int) as the second.
+    @param proto_header_name: The name of the expected protocol header
+    @param original_addr: A host/port pair that should be returned if the headers are not in the request
+    @param original_scheme: A scheme that should be returned if the headers are not in the request
+    @return: A tuple containing a list [host (string), port (int)] as the first entry and a proto (string) as the second
     """
     if not address_header_name:
-        return original
+        return (original_addr, original_scheme)
 
-    # Convert twisted-style headers into dicts
     if isinstance(headers, Headers):
+        # Convert twisted-style headers into a dict
         headers = dict(headers.getAllRawHeaders())
-
-    # Lowercase all header names in the dict
-    headers = {name.lower(): values for name, values in headers.items()}
+        # Lowercase all header keys
+        headers = {name.lower(): values for name, values in headers.items()}
+    else:
+        # Lowercase (and encode to utf-8 where needed) non-twisted header keys
+        headers = {name.lower() if isinstance(name, bytes) else name.lower().encode("utf-8"): values for name, values in headers.items()}
 
     address_header_name = address_header_name.lower().encode("utf-8")
-    result = original
+    result_addr = original_addr
+    result_scheme = original_scheme
     if address_header_name in headers:
         address_value = header_value(headers, address_header_name)
 
         if ',' in address_value:
             address_value = address_value.split(",")[0].strip()
 
-        result = [address_value, 0]
+        result_addr = [address_value, 0]
 
         if port_header_name:
             # We only want to parse the X-Forwarded-Port header if we also parsed the X-Forwarded-For
@@ -48,8 +58,13 @@ def parse_x_forwarded_for(headers,
             if port_header_name in headers:
                 port_value = header_value(headers, port_header_name)
                 try:
-                    result[1] = int(port_value)
+                    result_addr[1] = int(port_value)
                 except ValueError:
                     pass
 
-    return result
+        if proto_header_name:
+            proto_header_name = proto_header_name.lower().encode("utf-8")
+            if proto_header_name in headers:
+                result_scheme = header_value(headers, proto_header_name)
+
+    return result_addr, result_scheme
diff --git a/daphne/ws_protocol.py b/daphne/ws_protocol.py
index 502f340..45a3ab5 100755
--- a/daphne/ws_protocol.py
+++ b/daphne/ws_protocol.py
@@ -4,7 +4,7 @@ import logging
 import six
 import time
 import traceback
-from six.moves.urllib_parse import unquote, urlencode
+from six.moves.urllib_parse import unquote
 from twisted.internet import defer
 
 from autobahn.twisted.websocket import WebSocketServerProtocol, WebSocketServerFactory, ConnectionDeny
@@ -57,10 +57,11 @@ class WebSocketProtocol(WebSocketServerProtocol):
                 self.server_addr = None
 
             if self.main_factory.proxy_forwarded_address_header:
-                self.client_addr = parse_x_forwarded_for(
+                self.client_addr, self.client_scheme = parse_x_forwarded_for(
                     self.http_headers,
                     self.main_factory.proxy_forwarded_address_header,
                     self.main_factory.proxy_forwarded_port_header,
+                    self.main_factory.proxy_forwarded_proto_header,
                     self.client_addr
                 )
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-daphne.git



More information about the Python-modules-commits mailing list