Repository: huge-success/sanic
Pull request #1402 (2018-11-06 18:32:50)

Fix Range header handling for Static Files
This commit fixes the issue in the `Range` header handling that was done while serving the file contents. As per the HTTP response standards, a status code of 206 will be used in case if the Range is returning a partial value and default of 200 in other cases.

This replaces the stale PR #1333  (Cherry picked changes from there and modified to make it cleaner)

codecov-io: # [Codecov](https://codecov.io/gh/huge-success/sanic/pull/1402?src=pr&el=h1) Report
> Merging [#1402](https://codecov.io/gh/huge-success/sanic/pull/1402?src=pr&el=desc) into [master](https://codecov.io/gh/huge-success/sanic/commit/b63c06c75a54752d7f3115d3c635580db44b8399?src=pr&el=desc) will **increase** coverage by `0.02%`.
> The diff coverage is `80%`.

[![Impacted file tree graph](https://codecov.io/gh/huge-success/sanic/pull/1402/graphs/tree.svg?width=650&token=NqLbJHUqoR&height=150&src=pr)](https://codecov.io/gh/huge-success/sanic/pull/1402?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1402      +/-   ##
==========================================
+ Coverage   81.34%   81.36%   +0.02%     
==========================================
  Files          17       17              
  Lines        1694     1696       +2     
  Branches      322      322              
==========================================
+ Hits         1378     1380       +2     
- Misses        247      248       +1     
+ Partials       69       68       -1
```


| [Impacted Files](https://codecov.io/gh/huge-success/sanic/pull/1402?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [sanic/handlers.py](https://codecov.io/gh/huge-success/sanic/pull/1402/diff?src=pr&el=tree#diff-c2FuaWMvaGFuZGxlcnMucHk=) | `89.52% <100%> (ø)` | :arrow_up: |
| [sanic/response.py](https://codecov.io/gh/huge-success/sanic/pull/1402/diff?src=pr&el=tree#diff-c2FuaWMvcmVzcG9uc2UucHk=) | `87.24% <50%> (-0.51%)` | :arrow_down: |
| [sanic/router.py](https://codecov.io/gh/huge-success/sanic/pull/1402/diff?src=pr&el=tree#diff-c2FuaWMvcm91dGVyLnB5) | `92.69% <0%> (+0.45%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/huge-success/sanic/pull/1402?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/huge-success/sanic/pull/1402?src=pr&el=footer). Last update [b63c06c...3685ab0](https://codecov.io/gh/huge-success/sanic/pull/1402?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/sanic/handlers.py b/sanic/handlers.py
index 811ffeac..ee12ce36 100644
--- a/sanic/handlers.py
+++ b/sanic/handlers.py
@@ -167,17 +167,17 @@ class ContentRangeHandler:
                 )
             else:
                 # this case represents `Content-Range: bytes 5-`
-                self.end = self.total
+                self.end = self.total - 1
         else:
             if self.start is None:
                 # this case represents `Content-Range: bytes -5`
                 self.start = self.total - self.end
-                self.end = self.total
+                self.end = self.total - 1
         if self.start >= self.end:
             raise ContentRangeError(
                 "Invalid for Content Range parameters", self
             )
-        self.size = self.end - self.start
+        self.size = self.end - self.start + 1
         self.headers = {
             "Content-Range": "bytes %s-%s/%s"
             % (self.start, self.end, self.total)
diff --git a/sanic/response.py b/sanic/response.py
index fb9b3d6c..09c92225 100644
--- a/sanic/response.py
+++ b/sanic/response.py
@@ -302,6 +302,7 @@ async def file(
                 _range.end,
                 _range.total,
             )
+            status = 206
         else:
             out_stream = await _file.read()
 
@@ -371,6 +372,7 @@ async def file_stream(
             _range.end,
             _range.total,
         )
+        status = 206
     return StreamingHTTPResponse(
         streaming_fn=_streaming_fn,
         status=status,


Test patch:
diff --git a/tests/test_static.py b/tests/test_static.py
index 80952538..91b7e454 100644
--- a/tests/test_static.py
+++ b/tests/test_static.py
@@ -84,11 +84,11 @@ def test_static_content_range_correct(app, file_name, static_file_directory):
         'Range': 'bytes=12-19'
     }
     request, response = app.test_client.get('/testing.file', headers=headers)
-    assert response.status == 200
+    assert response.status == 206
     assert 'Content-Length' in response.headers
     assert 'Content-Range' in response.headers
     static_content = bytes(get_file_content(
-        static_file_directory, file_name))[12:19]
+        static_file_directory, file_name))[12:20]
     assert int(response.headers[
                'Content-Length']) == len(static_content)
     assert response.body == static_content
@@ -104,7 +104,7 @@ def test_static_content_range_front(app, file_name, static_file_directory):
         'Range': 'bytes=12-'
     }
     request, response = app.test_client.get('/testing.file', headers=headers)
-    assert response.status == 200
+    assert response.status == 206
     assert 'Content-Length' in response.headers
     assert 'Content-Range' in response.headers
     static_content = bytes(get_file_content(
@@ -124,7 +124,7 @@ def test_static_content_range_back(app, file_name, static_file_directory):
         'Range': 'bytes=-12'
     }
     request, response = app.test_client.get('/testing.file', headers=headers)
-    assert response.status == 200
+    assert response.status == 206
     assert 'Content-Length' in response.headers
     assert 'Content-Range' in response.headers
     static_content = bytes(get_file_content(
diff --git a/tests/test_url_for_static.py b/tests/test_url_for_static.py
index 32a39e48..7b316aef 100644
--- a/tests/test_url_for_static.py
+++ b/tests/test_url_for_static.py
@@ -212,11 +212,11 @@ def test_static_content_range_correct(app, file_name, static_file_directory):
     assert uri == app.url_for('static', name='static', filename='any')
 
     request, response = app.test_client.get(uri, headers=headers)
-    assert response.status == 200
+    assert response.status == 206
     assert 'Content-Length' in response.headers
     assert 'Content-Range' in response.headers
     static_content = bytes(get_file_content(
-        static_file_directory, file_name))[12:19]
+        static_file_directory, file_name))[12:20]
     assert int(response.headers[
                'Content-Length']) == len(static_content)
     assert response.body == static_content
@@ -233,11 +233,11 @@ def test_static_content_range_correct(app, file_name, static_file_directory):
                               filename='any')
 
     request, response = app.test_client.get(uri, headers=headers)
-    assert response.status == 200
+    assert response.status == 206
     assert 'Content-Length' in response.headers
     assert 'Content-Range' in response.headers
     static_content = bytes(get_file_content(
-        static_file_directory, file_name))[12:19]
+        static_file_directory, file_name))[12:20]
     assert int(response.headers[
                'Content-Length']) == len(static_content)
     assert response.body == static_content
@@ -263,7 +263,7 @@ def test_static_content_range_front(app, file_name, static_file_directory):
     assert uri == app.url_for('static', name='static', filename='any')
 
     request, response = app.test_client.get(uri, headers=headers)
-    assert response.status == 200
+    assert response.status == 206
     assert 'Content-Length' in response.headers
     assert 'Content-Range' in response.headers
     static_content = bytes(get_file_content(
@@ -284,7 +284,7 @@ def test_static_content_range_front(app, file_name, static_file_directory):
                               filename='any')
 
     request, response = app.test_client.get(uri, headers=headers)
-    assert response.status == 200
+    assert response.status == 206
     assert 'Content-Length' in response.headers
     assert 'Content-Range' in response.headers
     static_content = bytes(get_file_content(
@@ -314,7 +314,7 @@ def test_static_content_range_back(app, file_name, static_file_directory):
     assert uri == app.url_for('static', name='static', filename='any')
 
     request, response = app.test_client.get(uri, headers=headers)
-    assert response.status == 200
+    assert response.status == 206
     assert 'Content-Length' in response.headers
     assert 'Content-Range' in response.headers
     static_content = bytes(get_file_content(
@@ -335,7 +335,7 @@ def test_static_content_range_back(app, file_name, static_file_directory):
                               filename='any')
 
     request, response = app.test_client.get(uri, headers=headers)
-    assert response.status == 200
+    assert response.status == 206
     assert 'Content-Length' in response.headers
     assert 'Content-Range' in response.headers
     static_content = bytes(get_file_content(