From 85eaee9b41dc16283d3a2a619ac4cc7cbdb8e6e0 Mon Sep 17 00:00:00 2001 From: David Lord Date: Wed, 5 Feb 2020 18:10:38 -0800 Subject: [PATCH] range request always returns 206 status --- CHANGES.rst | 3 +++ src/werkzeug/wrappers/etag.py | 29 ++++++++++++++++------------- tests/test_wrappers.py | 4 ++-- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 54ac12db3..ae5fba0aa 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -98,6 +98,9 @@ Unreleased implementations. :pr:`1702` - Support matching and building WebSocket rules in the routing system, for use by async frameworks. :pr:`1709` +- Range requests that span an entire file respond with 206 instead of + 200, to be more compliant with :rfc:`7233`. This may help serving + media to older browsers. :issue:`410, 1704` Version 0.16.1 diff --git a/src/werkzeug/wrappers/etag.py b/src/werkzeug/wrappers/etag.py index ac2860a05..460629bdb 100644 --- a/src/werkzeug/wrappers/etag.py +++ b/src/werkzeug/wrappers/etag.py @@ -142,28 +142,31 @@ def _process_range_request(self, environ, complete_length=None, accept_ranges=No """ from ..exceptions import RequestedRangeNotSatisfiable - if accept_ranges is None: - return False - self.headers["Accept-Ranges"] = accept_ranges - if not self._is_range_request_processable(environ) or complete_length is None: + if ( + accept_ranges is None + or complete_length is None + or not self._is_range_request_processable(environ) + ): return False + parsed_range = parse_range_header(environ.get("HTTP_RANGE")) + if parsed_range is None: raise RequestedRangeNotSatisfiable(complete_length) + range_tuple = parsed_range.range_for_length(complete_length) content_range_header = parsed_range.to_content_range_header(complete_length) + if range_tuple is None or content_range_header is None: raise RequestedRangeNotSatisfiable(complete_length) + content_length = range_tuple[1] - range_tuple[0] - # Be sure not to send 206 response - # if requested range is the full content. - if content_length != complete_length: - self.headers["Content-Length"] = content_length - self.content_range = content_range_header - self.status_code = 206 - self._wrap_response(range_tuple[0], content_length) - return True - return False + self.headers["Content-Length"] = content_length + self.headers["Accept-Ranges"] = accept_ranges + self.content_range = content_range_header + self.status_code = 206 + self._wrap_response(range_tuple[0], content_length) + return True def make_conditional( self, request_or_environ, accept_ranges=False, complete_length=None diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py index 6973a1c57..7e21eda75 100644 --- a/tests/test_wrappers.py +++ b/tests/test_wrappers.py @@ -878,9 +878,9 @@ def test_range_request_with_complete_file(): response = wrappers.Response(wrap_file(env, f)) env["HTTP_RANGE"] = "bytes=0-%d" % (fsize - 1) response.make_conditional(env, accept_ranges=True, complete_length=fsize) - assert response.status_code == 200 + assert response.status_code == 206 assert response.headers["Accept-Ranges"] == "bytes" - assert "Content-Range" not in response.headers + assert response.headers["Content-Range"] == "bytes 0-%d/%d" % (fsize - 1, fsize) assert response.headers["Content-Length"] == str(fsize) assert response.data == fcontent