From fe3a47f2d54a880af500776c0c99b8c093d8b325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gina=20H=C3=A4u=C3=9Fge?= Date: Wed, 13 Aug 2014 20:07:02 +0200 Subject: [PATCH] Resolved issue with tornado 4.0.1 (incompatibility between existing and custom http1connection implementation) Compare #544 and #546. Pinned tornado version to 4.0.1 (since the change in this commit is not backwards compatible), dependency update is mandatory. --- requirements.txt | 2 +- src/octoprint/server/util/tornado.py | 31 ++++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index af3e1062..2b2773cd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ flask==0.9 werkzeug==0.8.3 -tornado==4.0.0 +tornado==4.0.1 sockjs-tornado>=1.0.0 PyYAML==3.10 Flask-Login==0.2.2 diff --git a/src/octoprint/server/util/tornado.py b/src/octoprint/server/util/tornado.py index 5da04a79..735489d1 100644 --- a/src/octoprint/server/util/tornado.py +++ b/src/octoprint/server/util/tornado.py @@ -12,6 +12,7 @@ import stat import mimetypes import email import time +import re import tornado import tornado.web @@ -622,7 +623,7 @@ class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection): self._max_body_sizes = map(lambda x: (x[0], re.compile(x[1]), x[2]), self.params.max_body_sizes or dict()) self._default_max_body_size = self.params.default_max_body_size or self.stream.max_buffer_size - def _read_body(self, headers, delegate): + def _read_body(self, code, headers, delegate): """ Basically the same as `tornado.http1connection.HTTP1Connection._read_body`, but determines the maximum content length individually for the request (utilizing `._get_max_content_length`). @@ -632,11 +633,37 @@ class CustomHTTP1Connection(tornado.http1connection.HTTP1Connection): `HTTPInputError` is raised. """ content_length = headers.get("Content-Length") - if content_length: + if "Content-Length" in headers: + if "," in headers["Content-Length"]: + # Proxies sometimes cause Content-Length headers to get + # duplicated. If all the values are identical then we can + # use them but if they differ it's an error. + pieces = re.split(r',\s*', headers["Content-Length"]) + if any(i != pieces[0] for i in pieces): + raise tornado.httputil.HTTPInputError( + "Multiple unequal Content-Lengths: %r" % + headers["Content-Length"]) + headers["Content-Length"] = pieces[0] + content_length = int(headers["Content-Length"]) + content_length = int(content_length) max_content_length = self._get_max_content_length(self._request_start_line.method, self._request_start_line.path) if 0 <= max_content_length < content_length: raise tornado.httputil.HTTPInputError("Content-Length too long") + else: + content_length = None + + if code == 204: + # This response code is not allowed to have a non-empty body, + # and has an implicit length of zero instead of read-until-close. + # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3 + if ("Transfer-Encoding" in headers or + content_length not in (None, 0)): + raise tornado.httputil.HTTPInputError( + "Response with code %d should not have body" % code) + content_length = 0 + + if content_length is not None: return self._read_fixed_body(content_length, delegate) if headers.get("Transfer-Encoding") == "chunked": return self._read_chunked_body(delegate)