Commit 4fb34ff9 authored by Michael Fladischer's avatar Michael Fladischer

Update upstream source from tag 'upstream/1.4.2'

Update to upstream version '1.4.2'
with Debian dir 1b693af631b6c36745cb455f05e8245083c134db
parents 69d3a121 0b8b502f
sudo: false
dist: trusty
language: python
......
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)
------------------
......
......@@ -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::
......
__version__ = "1.3.0"
__version__ = "1.4.2"
......@@ -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()
......@@ -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
......
......@@ -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
from hypothesis import HealthCheck, settings
settings.register_profile(
'daphne',
settings(suppress_health_check=[HealthCheck.too_slow]),
)
settings.load_profile('daphne')
......@@ -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" +
......
......@@ -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):
"""
......
......@@ -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)
)
# 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)
......
......@@ -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
# 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
......@@ -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
)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment