summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Merickel <michael@merickel.org>2019-06-29 15:53:50 -0500
committerMichael Merickel <michael@merickel.org>2019-06-29 15:58:45 -0500
commit3e68af8684000ad3b01b51ad12e68e9f48a2d7ee (patch)
tree7bc41c44148202b6d76dff7b4b7129a3849de71e
parente2210c9258702b7a46fa23f3f5e8389d56999748 (diff)
downloadwaitress-3e68af8684000ad3b01b51ad12e68e9f48a2d7ee.tar.gz
move proxy headers to a middleware and return 400 for malformed values
-rw-r--r--waitress/proxy_headers.py309
-rw-r--r--waitress/server.py20
-rw-r--r--waitress/task.py302
-rw-r--r--waitress/utilities.py115
4 files changed, 392 insertions, 354 deletions
diff --git a/waitress/proxy_headers.py b/waitress/proxy_headers.py
new file mode 100644
index 0000000..713e13e
--- /dev/null
+++ b/waitress/proxy_headers.py
@@ -0,0 +1,309 @@
+from collections import namedtuple
+
+from .utilities import logger, undquote, BadRequest
+
+
+PROXY_HEADERS = frozenset({
+ 'HTTP_X_FORWARDED_FOR',
+ 'HTTP_X_FORWARDED_HOST',
+ 'HTTP_X_FORWARDED_PROTO',
+ 'HTTP_X_FORWARDED_PORT',
+ 'HTTP_X_FORWARDED_BY',
+ 'HTTP_FORWARDED',
+})
+
+Forwarded = namedtuple('Forwarded', ['by', 'for_', 'host', 'proto'])
+
+
+def proxy_headers_middleware(
+ app,
+ trusted_proxy,
+ trusted_proxy_count,
+ trusted_proxy_headers,
+ clear_untrusted=True,
+ log_untrusted=False,
+ logger=logger,
+):
+ def translate_proxy_headers(environ, start_response):
+ try:
+ untrusted_headers = PROXY_HEADERS
+ remote_peer = environ['REMOTE_ADDR']
+ if trusted_proxy == '*' or remote_peer == trusted_proxy:
+ untrusted_headers = parse_proxy_headers(
+ environ,
+ trusted_proxy_count=trusted_proxy_count,
+ trusted_proxy_headers=trusted_proxy_headers,
+ logger=logger,
+ )
+
+ # Clear out the untrusted proxy headers
+ if clear_untrusted:
+ clear_untrusted_headers(
+ environ,
+ untrusted_headers,
+ log_warning=log_untrusted,
+ logger=logger,
+ )
+ except ValueError as ex:
+ error = BadRequest(str(ex))
+ return error.wsgi_response(environ, start_response)
+
+ return app(environ, start_response)
+ return translate_proxy_headers
+
+
+def parse_proxy_headers(
+ environ,
+ trusted_proxy_count=1,
+ trusted_proxy_headers=None,
+ logger=logger,
+):
+ if trusted_proxy_headers is None:
+ trusted_proxy_headers = set()
+
+ forwarded_for = []
+ forwarded_host = forwarded_proto = forwarded_port = forwarded = ""
+ client_addr = None
+ untrusted_headers = set(PROXY_HEADERS)
+
+ def warn_unspecified_behavior(header):
+ logger.warning(
+ "Found multiple values in %s, this has unspecified behaviour. "
+ "Ignoring header value.",
+ header,
+ )
+
+ if (
+ "x-forwarded-for" in trusted_proxy_headers
+ and "HTTP_X_FORWARDED_FOR" in environ
+ ):
+ forwarded_for = []
+
+ for forward_hop in environ["HTTP_X_FORWARDED_FOR"].split(","):
+ forward_hop = forward_hop.strip()
+ forward_hop = undquote(forward_hop)
+
+ # Make sure that all IPv6 addresses are surrounded by brackets,
+ # this is assuming that the IPv6 representation here does not
+ # include a port number.
+
+ if "." not in forward_hop and (
+ ":" in forward_hop and forward_hop[-1] != "]"
+ ):
+ forwarded_for.append("[{}]".format(forward_hop))
+ else:
+ forwarded_for.append(forward_hop)
+
+ forwarded_for = forwarded_for[-trusted_proxy_count:]
+ client_addr = forwarded_for[0]
+
+ untrusted_headers.remove("HTTP_X_FORWARDED_FOR")
+
+ if (
+ "x-forwarded-host" in trusted_proxy_headers
+ and "HTTP_X_FORWARDED_HOST" in environ
+ ):
+ forwarded_host_multiple = []
+
+ for forward_host in environ["HTTP_X_FORWARDED_HOST"].split(","):
+ forward_host = forward_host.strip()
+ forward_host = undquote(forward_host)
+ forwarded_host_multiple.append(forward_host)
+
+ forwarded_host_multiple = forwarded_host_multiple[-trusted_proxy_count:]
+ forwarded_host = forwarded_host_multiple[0]
+
+ untrusted_headers.remove("HTTP_X_FORWARDED_HOST")
+
+ if "x-forwarded-proto" in trusted_proxy_headers:
+ forwarded_proto = undquote(environ.get("HTTP_X_FORWARDED_PROTO", ""))
+ untrusted_headers.remove("HTTP_X_FORWARDED_PROTO")
+
+ if "," in forwarded_proto:
+ forwarded_proto = ""
+ warn_unspecified_behavior("X-Forwarded-Proto")
+
+ if "x-forwarded-port" in trusted_proxy_headers:
+ forwarded_port = undquote(environ.get("HTTP_X_FORWARDED_PORT", ""))
+ untrusted_headers.remove("HTTP_X_FORWARDED_PORT")
+
+ if "," in forwarded_port:
+ forwarded_port = ""
+ warn_unspecified_behavior("X-Forwarded-Port")
+
+ if "x-forwarded-by" in trusted_proxy_headers:
+ # Waitress itself does not use X-Forwarded-By, but we can not
+ # remove it so it can get set in the environ
+ untrusted_headers.remove("HTTP_X_FORWARDED_BY")
+
+ if "forwarded" in trusted_proxy_headers:
+ forwarded = environ.get("HTTP_FORWARDED", None)
+ untrusted_headers = PROXY_HEADERS - {"HTTP_FORWARDED"}
+
+ # If the Forwarded header exists, it gets priority
+ if forwarded:
+ proxies = []
+
+ for forwarded_element in forwarded.split(","):
+ # Remove whitespace that may have been introduced when
+ # appending a new entry
+ forwarded_element = forwarded_element.strip()
+
+ forwarded_for = forwarded_host = forwarded_proto = ""
+ forwarded_port = forwarded_by = ""
+
+ for pair in forwarded_element.split(";"):
+ pair = pair.lower()
+
+ if not pair:
+ continue
+
+ token, equals, value = pair.partition("=")
+
+ if equals != "=":
+ raise ValueError("Invalid forwarded-pair in Forwarded element")
+
+ if token.strip() != token:
+ raise ValueError("token may not be surrounded by whitespace")
+
+ if value.strip() != value:
+ raise ValueError("value may not be surrounded by whitespace")
+
+ if token == "by":
+ forwarded_by = undquote(value)
+
+ elif token == "for":
+ forwarded_for = undquote(value)
+
+ elif token == "host":
+ forwarded_host = undquote(value)
+
+ elif token == "proto":
+ forwarded_proto = undquote(value)
+
+ else:
+ logger.warning("Unknown Forwarded token: %s" % token)
+
+ proxies.append(
+ Forwarded(
+ forwarded_by, forwarded_for, forwarded_host, forwarded_proto
+ )
+ )
+
+ proxies = proxies[-trusted_proxy_count:]
+
+ # Iterate backwards and fill in some values, the oldest entry that
+ # contains the information we expect is the one we use. We expect
+ # that intermediate proxies may re-write the host header or proto,
+ # but the oldest entry is the one that contains the information the
+ # client expects when generating URL's
+ #
+ # Forwarded: for="[2001:db8::1]";host="example.com:8443";proto="https"
+ # Forwarded: for=192.0.2.1;host="example.internal:8080"
+ #
+ # (After HTTPS header folding) should mean that we use as values:
+ #
+ # Host: example.com
+ # Protocol: https
+ # Port: 8443
+
+ for proxy in proxies[::-1]:
+ client_addr = proxy.for_ or client_addr
+ forwarded_host = proxy.host or forwarded_host
+ forwarded_proto = proxy.proto or forwarded_proto
+
+ if forwarded_proto:
+ forwarded_proto = forwarded_proto.lower()
+
+ if forwarded_proto not in {"http", "https"}:
+ raise ValueError(
+ 'Invalid "Forwarded Proto=" or "X-Forwarded-Proto" value.'
+ )
+
+ # Set the URL scheme to the proxy provided proto
+ environ["wsgi.url_scheme"] = forwarded_proto
+
+ if not forwarded_port:
+ if forwarded_proto == "http":
+ forwarded_port = "80"
+
+ if forwarded_proto == "https":
+ forwarded_port = "443"
+
+ if forwarded_host:
+ if ":" in forwarded_host and forwarded_host[-1] != "]":
+ host, port = forwarded_host.rsplit(":", 1)
+ host, port = host.strip(), str(port)
+
+ # We trust the port in the Forwarded Host/X-Forwarded-Host over
+ # X-Forwarded-Port, or whatever we got from Forwarded
+ # Proto/X-Forwarded-Proto.
+
+ if forwarded_port != port:
+ forwarded_port = port
+
+ # We trust the proxy server's forwarded Host
+ environ["SERVER_NAME"] = host
+ environ["HTTP_HOST"] = forwarded_host
+ else:
+ # We trust the proxy server's forwarded Host
+ environ["SERVER_NAME"] = forwarded_host
+ environ["HTTP_HOST"] = forwarded_host
+
+ if forwarded_port:
+ if forwarded_port not in {"443", "80"}:
+ environ["HTTP_HOST"] = "{}:{}".format(
+ forwarded_host, forwarded_port
+ )
+ elif (
+ forwarded_port == "80" and environ["wsgi.url_scheme"] != "http"
+ ):
+ environ["HTTP_HOST"] = "{}:{}".format(
+ forwarded_host, forwarded_port
+ )
+ elif (
+ forwarded_port == "443"
+ and environ["wsgi.url_scheme"] != "https"
+ ):
+ environ["HTTP_HOST"] = "{}:{}".format(
+ forwarded_host, forwarded_port
+ )
+
+ if forwarded_port:
+ environ["SERVER_PORT"] = str(forwarded_port)
+
+ if client_addr:
+ def strip_brackets(addr):
+ if addr[0] == "[" and addr[-1] == "]":
+ return addr[1:-1]
+ return addr
+
+ if ":" in client_addr and client_addr[-1] != "]":
+ addr, port = client_addr.rsplit(":", 1)
+ environ["REMOTE_ADDR"] = strip_brackets(addr.strip())
+ environ["REMOTE_PORT"] = port.strip()
+ else:
+ environ["REMOTE_ADDR"] = strip_brackets(client_addr.strip())
+
+ return untrusted_headers
+
+
+def clear_untrusted_headers(
+ environ, untrusted_headers, log_warning=False, logger=logger
+):
+ untrusted_headers_removed = [
+ header
+ for header in untrusted_headers
+ if environ.pop(header, False) is not False
+ ]
+
+ if log_warning and untrusted_headers_removed:
+ untrusted_headers_removed = [
+ "-".join(x.lstrip('HTTP_').capitalize() for x in header.split("_"))
+ for header in untrusted_headers_removed
+ ]
+ logger.warning(
+ "Removed untrusted headers (%s). Waitress recommends these be "
+ "removed upstream.",
+ ", ".join(untrusted_headers_removed),
+ )
diff --git a/waitress/server.py b/waitress/server.py
index 7ef930e..bc68c77 100644
--- a/waitress/server.py
+++ b/waitress/server.py
@@ -28,6 +28,7 @@ from waitress.compat import (
IPV6_V6ONLY,
)
from . import wasyncore
+from .proxy_headers import proxy_headers_middleware
def create_server(application,
map=None,
@@ -178,10 +179,29 @@ class BaseWSGIServer(wasyncore.dispatcher, object):
adj=None, # adjustments
sockinfo=None, # opaque object
bind_socket=True,
+ logger=None, # test shim
**kw
):
if adj is None:
adj = Adjustments(**kw)
+
+ if logger is not None:
+ self.logger = logger
+
+ if adj.trusted_proxy or adj.clear_untrusted_proxy_headers:
+ # wrap the application to deal with proxy headers
+ # we wrap it here because webtest subclasses the TcpWSGIServer
+ # directly and thus doesn't run any code that's in create_server
+ application = proxy_headers_middleware(
+ application,
+ trusted_proxy=adj.trusted_proxy,
+ trusted_proxy_count=adj.trusted_proxy_count,
+ trusted_proxy_headers=adj.trusted_proxy_headers,
+ clear_untrusted=adj.clear_untrusted_proxy_headers,
+ log_untrusted=adj.log_untrusted_proxy_headers,
+ logger=self.logger,
+ )
+
if map is None:
# use a nonglobal socket map by default to hopefully prevent
# conflicts with apps and libs that use the wasyncore global socket
diff --git a/waitress/task.py b/waitress/task.py
index 81e512f..96451a7 100644
--- a/waitress/task.py
+++ b/waitress/task.py
@@ -21,13 +21,9 @@ import time
from .buffers import ReadOnlyFileBasedBuffer
from .compat import reraise, tobytes
from .utilities import (
- Forwarded,
- PROXY_HEADERS,
build_http_date,
- clear_untrusted_headers,
logger,
queue_logger,
- undquote,
)
rename_headers = { # or keep them without the HTTP_ prefix added
@@ -357,23 +353,10 @@ class ErrorTask(Task):
def execute(self):
e = self.request.error
- body = '%s\r\n\r\n%s' % (e.reason, e.body)
- tag = '\r\n\r\n(generated by waitress)'
- body = body + tag
- self.status = '%s %s' % (e.code, e.reason)
- cl = len(body)
- self.content_length = cl
- self.response_headers.append(('Content-Length', str(cl)))
- self.response_headers.append(('Content-Type', 'text/plain'))
- if self.version == '1.1':
- connection = self.request.headers.get('CONNECTION', '').lower()
- if connection == 'close':
- self.response_headers.append(('Connection', 'close'))
- # under HTTP 1.1 keep-alive is default, no need to set the header
- else:
- # HTTP 1.0
- self.response_headers.append(('Connection', 'close'))
- self.close_on_finish = True
+ status, headers, body = e.to_response()
+ self.status = status
+ self.response_headers.extend(headers)
+ self.content_length = len(body)
self.write(tobytes(body))
class WSGITask(Task):
@@ -382,7 +365,7 @@ class WSGITask(Task):
environ = None
def execute(self):
- env = self.get_environment()
+ environ = self.get_environment()
def start_response(status, headers, exc_info=None):
if self.complete and not exc_info:
@@ -444,7 +427,7 @@ class WSGITask(Task):
return self.write
# Call the application to handle the request and write a response
- app_iter = self.channel.server.application(env, start_response)
+ app_iter = self.channel.server.application(environ, start_response)
can_close_app_iter = True
try:
@@ -501,236 +484,6 @@ class WSGITask(Task):
if can_close_app_iter and hasattr(app_iter, 'close'):
app_iter.close()
- def parse_proxy_headers(
- self,
- environ,
- headers,
- trusted_proxy_count=1,
- trusted_proxy_headers=None,
- ):
- if trusted_proxy_headers is None:
- trusted_proxy_headers = set()
-
- forwarded_for = []
- forwarded_host = forwarded_proto = forwarded_port = forwarded = ""
- client_addr = None
- untrusted_headers = set(PROXY_HEADERS)
-
- def warn_unspecified_behavior(header):
- self.logger.warning(
- "Found multiple values in %s, this has unspecified behaviour. "
- "Ignoring header value.",
- header,
- )
-
- if "x-forwarded-for" in trusted_proxy_headers and "X_FORWARDED_FOR" in headers:
- forwarded_for = []
-
- for forward_hop in headers["X_FORWARDED_FOR"].split(","):
- forward_hop = forward_hop.strip()
- forward_hop = undquote(forward_hop)
-
- # Make sure that all IPv6 addresses are surrounded by brackets,
- # this is assuming that the IPv6 representation here does not
- # include a port number.
-
- if "." not in forward_hop and (
- ":" in forward_hop and forward_hop[-1] != "]"
- ):
- forwarded_for.append("[{}]".format(forward_hop))
- else:
- forwarded_for.append(forward_hop)
-
- forwarded_for = forwarded_for[-trusted_proxy_count:]
- client_addr = forwarded_for[0]
-
- untrusted_headers.remove("X_FORWARDED_FOR")
-
- if "x-forwarded-host" in trusted_proxy_headers and "X_FORWARDED_HOST" in headers:
- forwarded_host_multiple = []
-
- for forward_host in headers["X_FORWARDED_HOST"].split(","):
- forward_host = forward_host.strip()
- forward_host = undquote(forward_host)
- forwarded_host_multiple.append(forward_host)
-
- forwarded_host_multiple = forwarded_host_multiple[-trusted_proxy_count:]
- forwarded_host = forwarded_host_multiple[0]
-
- untrusted_headers.remove("X_FORWARDED_HOST")
-
- if "x-forwarded-proto" in trusted_proxy_headers:
- forwarded_proto = undquote(headers.get("X_FORWARDED_PROTO", ""))
- untrusted_headers.remove("X_FORWARDED_PROTO")
-
- if "," in forwarded_proto:
- forwarded_proto = ""
- warn_unspecified_behavior("X-Forwarded-Proto")
-
- if "x-forwarded-port" in trusted_proxy_headers:
- forwarded_port = undquote(headers.get("X_FORWARDED_PORT", ""))
- untrusted_headers.remove("X_FORWARDED_PORT")
-
- if "," in forwarded_port:
- forwarded_port = ""
- warn_unspecified_behavior("X-Forwarded-Port")
-
- if "x-forwarded-by" in trusted_proxy_headers:
- # Waitress itself does not use X-Forwarded-By, but we can not
- # remove it so it can get set in the environ
- untrusted_headers.remove("X_FORWARDED_BY")
-
- if "forwarded" in trusted_proxy_headers:
- forwarded = headers.get("FORWARDED", None)
- untrusted_headers = PROXY_HEADERS - {"FORWARDED"}
-
- # If the Forwarded header exists, it gets priority
- if forwarded:
- proxies = []
-
- for forwarded_element in forwarded.split(","):
- # Remove whitespace that may have been introduced when
- # appending a new entry
- forwarded_element = forwarded_element.strip()
-
- forwarded_for = forwarded_host = forwarded_proto = ""
- forwarded_port = forwarded_by = ""
-
- for pair in forwarded_element.split(";"):
- pair = pair.lower()
-
- if not pair:
- continue
-
- token, equals, value = pair.partition("=")
-
- if equals != "=":
- raise ValueError("Invalid forwarded-pair in Forwarded element")
-
- if token.strip() != token:
- raise ValueError("token may not be surrounded by whitespace")
-
- if value.strip() != value:
- raise ValueError("value may not be surrounded by whitespace")
-
- if token == "by":
- forwarded_by = undquote(value)
-
- elif token == "for":
- forwarded_for = undquote(value)
-
- elif token == "host":
- forwarded_host = undquote(value)
-
- elif token == "proto":
- forwarded_proto = undquote(value)
-
- else:
- self.logger.warning("Unknown Forwarded token: %s" % token)
-
- proxies.append(
- Forwarded(
- forwarded_by, forwarded_for, forwarded_host, forwarded_proto
- )
- )
-
- proxies = proxies[-trusted_proxy_count:]
-
- # Iterate backwards and fill in some values, the oldest entry that
- # contains the information we expect is the one we use. We expect
- # that intermediate proxies may re-write the host header or proto,
- # but the oldest entry is the one that contains the information the
- # client expects when generating URL's
- #
- # Forwarded: for="[2001:db8::1]";host="example.com:8443";proto="https"
- # Forwarded: for=192.0.2.1;host="example.internal:8080"
- #
- # (After HTTPS header folding) should mean that we use as values:
- #
- # Host: example.com
- # Protocol: https
- # Port: 8443
-
- for proxy in proxies[::-1]:
- client_addr = proxy.for_ or client_addr
- forwarded_host = proxy.host or forwarded_host
- forwarded_proto = proxy.proto or forwarded_proto
-
- if forwarded_proto:
- forwarded_proto = forwarded_proto.lower()
-
- if forwarded_proto not in {"http", "https"}:
- raise ValueError(
- 'Invalid "Forwarded Proto=" or "X-Forwarded-Proto" value.'
- )
-
- # Set the URL scheme to the proxy provided proto
- environ["wsgi.url_scheme"] = forwarded_proto
-
- if not forwarded_port:
- if forwarded_proto == "http":
- forwarded_port = "80"
-
- if forwarded_proto == "https":
- forwarded_port = "443"
-
- if forwarded_host:
- if ":" in forwarded_host and forwarded_host[-1] != "]":
- host, port = forwarded_host.rsplit(":", 1)
- host, port = host.strip(), str(port)
-
- # We trust the port in the Forwarded Host/X-Forwarded-Host over
- # X-Forwarded-Port, or whatever we got from Forwarded
- # Proto/X-Forwarded-Proto.
-
- if forwarded_port != port:
- forwarded_port = port
-
- # We trust the proxy server's forwarded Host
- environ["SERVER_NAME"] = host
- environ["HTTP_HOST"] = forwarded_host
- else:
- # We trust the proxy server's forwarded Host
- environ["SERVER_NAME"] = forwarded_host
- environ["HTTP_HOST"] = forwarded_host
-
- if forwarded_port:
- if forwarded_port not in {"443", "80"}:
- environ["HTTP_HOST"] = "{}:{}".format(
- forwarded_host, forwarded_port
- )
- elif (
- forwarded_port == "80" and environ["wsgi.url_scheme"] != "http"
- ):
- environ["HTTP_HOST"] = "{}:{}".format(
- forwarded_host, forwarded_port
- )
- elif (
- forwarded_port == "443"
- and environ["wsgi.url_scheme"] != "https"
- ):
- environ["HTTP_HOST"] = "{}:{}".format(
- forwarded_host, forwarded_port
- )
-
- if forwarded_port:
- environ["SERVER_PORT"] = str(forwarded_port)
-
- if client_addr:
- def strip_brackets(addr):
- if addr[0] == "[" and addr[-1] == "]":
- return addr[1:-1]
- return addr
-
- if ":" in client_addr and client_addr[-1] != "]":
- addr, port = client_addr.rsplit(":", 1)
- environ["REMOTE_ADDR"] = strip_brackets(addr.strip())
- environ["REMOTE_PORT"] = port.strip()
- else:
- environ["REMOTE_ADDR"] = strip_brackets(client_addr.strip())
-
- return untrusted_headers
-
def get_environment(self):
"""Returns a WSGI environment."""
environ = self.environ
@@ -766,6 +519,14 @@ class WSGITask(Task):
path = path[len(url_prefix):]
environ = {
+ 'REMOTE_ADDR': channel.addr[0],
+ # Nah, we aren't actually going to look up the reverse DNS for
+ # REMOTE_ADDR, but we will happily set this environment variable
+ # for the WSGI application. Spec says we can just set this to
+ # REMOTE_ADDR, so we do.
+ 'REMOTE_HOST': channel.addr[0],
+ # try and set the REMOTE_PORT to something useful, but maybe None
+ 'REMOTE_PORT': str(channel.addr[1]),
'REQUEST_METHOD': request.command.upper(),
'SERVER_PORT': str(server.effective_port),
'SERVER_NAME': server.server_name,
@@ -788,43 +549,12 @@ class WSGITask(Task):
'wsgi.file_wrapper': ReadOnlyFileBasedBuffer,
'wsgi.input_terminated': True, # wsgi.input is EOF terminated
}
- remote_peer = environ['REMOTE_ADDR'] = channel.addr[0]
-
- headers = dict(request.headers)
-
- untrusted_headers = PROXY_HEADERS
- if server.adj.trusted_proxy == '*' or remote_peer == server.adj.trusted_proxy:
- untrusted_headers = self.parse_proxy_headers(
- environ,
- headers,
- trusted_proxy_count=server.adj.trusted_proxy_count,
- trusted_proxy_headers=server.adj.trusted_proxy_headers,
- )
- else:
- # If we are not relying on a proxy, we still want to try and set
- # the REMOTE_PORT to something useful, maybe None though.
- environ["REMOTE_PORT"] = str(channel.addr[1])
-
- # Nah, we aren't actually going to look up the reverse DNS for
- # REMOTE_ADDR, but we will happily set this environment variable for
- # the WSGI application. Spec says we can just set this to REMOTE_ADDR,
- # so we do.
- environ["REMOTE_HOST"] = environ["REMOTE_ADDR"]
-
- # Clear out the untrusted proxy headers
- if server.adj.clear_untrusted_proxy_headers:
- clear_untrusted_headers(
- headers,
- untrusted_headers,
- log_warning=server.adj.log_untrusted_proxy_headers,
- logger=self.logger,
- )
- for key, value in headers.items():
+ for key, value in dict(request.headers).items():
value = value.strip()
mykey = rename_headers.get(key, None)
if mykey is None:
- mykey = 'HTTP_%s' % key
+ mykey = 'HTTP_' + key
if mykey not in environ:
environ[mykey] = value
diff --git a/waitress/utilities.py b/waitress/utilities.py
index a21ca4e..d32affc 100644
--- a/waitress/utilities.py
+++ b/waitress/utilities.py
@@ -21,20 +21,10 @@ import os
import re
import stat
import time
-from collections import namedtuple
logger = logging.getLogger('waitress')
queue_logger = logging.getLogger('waitress.queue')
-PROXY_HEADERS = frozenset({
- 'X_FORWARDED_FOR',
- 'X_FORWARDED_HOST',
- 'X_FORWARDED_PROTO',
- 'X_FORWARDED_PORT',
- 'X_FORWARDED_BY',
- 'FORWARDED',
-})
-
def find_double_newline(s):
"""Returns the position just after a double newline in the given string."""
pos1 = s.find(b'\n\r\n') # One kind of double newline
@@ -180,42 +170,6 @@ def parse_http_date(d):
return retval
-def cleanup_unix_socket(path):
- try:
- st = os.stat(path)
- except OSError as exc:
- if exc.errno != errno.ENOENT:
- raise # pragma: no cover
- else:
- if stat.S_ISSOCK(st.st_mode):
- try:
- os.remove(path)
- except OSError: # pragma: no cover
- # avoid race condition error during tests
- pass
-
-class Error(object):
-
- def __init__(self, body):
- self.body = body
-
-class BadRequest(Error):
- code = 400
- reason = 'Bad Request'
-
-class RequestHeaderFieldsTooLarge(BadRequest):
- code = 431
- reason = 'Request Header Fields Too Large'
-
-class RequestEntityTooLarge(BadRequest):
- code = 413
- reason = 'Request Entity Too Large'
-
-class InternalServerError(Error):
- code = 500
- reason = 'Internal Server Error'
-
-
# RFC 5234 Appendix B.1 "Core Rules":
# VCHAR = %x21-7E
# ; visible (printing) characters
@@ -259,25 +213,50 @@ def undquote(value):
raise ValueError('Invalid quoting in value')
-Forwarded = namedtuple('Forwarded', ['by', 'for_', 'host', 'proto'])
-
-
-def clear_untrusted_headers(
- headers, untrusted_headers, log_warning=False, logger=logger
-):
- untrusted_headers_removed = [
- header
- for header in untrusted_headers
- if headers.pop(header, False) is not False
- ]
-
- if log_warning and untrusted_headers_removed:
- untrusted_headers_removed = [
- "-".join([x.capitalize() for x in header.split("_")])
- for header in untrusted_headers_removed
- ]
- logger.warning(
- "Removed untrusted headers (%s). Waitress recommends these be "
- "removed upstream.",
- ", ".join(untrusted_headers_removed),
- )
+def cleanup_unix_socket(path):
+ try:
+ st = os.stat(path)
+ except OSError as exc:
+ if exc.errno != errno.ENOENT:
+ raise # pragma: no cover
+ else:
+ if stat.S_ISSOCK(st.st_mode):
+ try:
+ os.remove(path)
+ except OSError: # pragma: no cover
+ # avoid race condition error during tests
+ pass
+
+class Error(object):
+
+ def __init__(self, body):
+ self.body = body
+
+ def to_response(self):
+ status = '%s %s' % (self.code, self.reason)
+ body = '%s\r\n\r\n%s' % (self.reason, self.body)
+ tag = '\r\n\r\n(generated by waitress)'
+ body = body + tag
+ headers = [('Content-Type', 'text/plain')]
+ return status, headers, body
+
+ def wsgi_response(self, environ, start_response):
+ status, headers, body = self.to_response()
+ start_response(status, headers)
+ yield body
+
+class BadRequest(Error):
+ code = 400
+ reason = 'Bad Request'
+
+class RequestHeaderFieldsTooLarge(BadRequest):
+ code = 431
+ reason = 'Request Header Fields Too Large'
+
+class RequestEntityTooLarge(BadRequest):
+ code = 413
+ reason = 'Request Entity Too Large'
+
+class InternalServerError(Error):
+ code = 500
+ reason = 'Internal Server Error'