From db7ad6da128609d2c14e480a9f54383b839d77e2 Mon Sep 17 00:00:00 2001
From: francois <francois@ctrlaltdel.ch>
Date: Tue, 18 Nov 2008 07:41:35 -0700
Subject: [PATCH] filenode.py: Fix partial HTTP Range header handling according
 to RFC2616

Tahoe webapi was failing on HTTP request containing a partial Range header.
This change allows movies players like mplayer to seek in movie files stored in
tahoe.

Associated tests for GET and HEAD methods are also included
---
 src/allmydata/test/test_web.py | 27 +++++++++++++++++++++++++++
 src/allmydata/web/filenode.py  |  9 ++++++++-
 2 files changed, 35 insertions(+), 1 deletion(-)

diff --git a/src/allmydata/test/test_web.py b/src/allmydata/test/test_web.py
index 6e7a1175..64e6a59e 100644
--- a/src/allmydata/test/test_web.py
+++ b/src/allmydata/test/test_web.py
@@ -552,6 +552,20 @@ class Web(WebMixin, testutil.StallMixin, unittest.TestCase):
         d.addCallback(_got)
         return d
 
+    def test_GET_FILEURL_partial_range(self):
+        headers = {"range": "bytes=5-"}
+        length  = len(self.BAR_CONTENTS)
+        d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
+                     return_response=True)
+        def _got((res, status, headers)):
+            self.failUnlessEqual(int(status), 206)
+            self.failUnless(headers.has_key("content-range"))
+            self.failUnlessEqual(headers["content-range"][0],
+                                 "bytes 5-%d/%d" % (length-1, length))
+            self.failUnlessEqual(res, self.BAR_CONTENTS[5:])
+        d.addCallback(_got)
+        return d
+
     def test_HEAD_FILEURL_range(self):
         headers = {"range": "bytes=1-10"}
         d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
@@ -565,6 +579,19 @@ class Web(WebMixin, testutil.StallMixin, unittest.TestCase):
         d.addCallback(_got)
         return d
 
+    def test_HEAD_FILEURL_partial_range(self):
+        headers = {"range": "bytes=5-"}
+        length  = len(self.BAR_CONTENTS)
+        d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
+                     return_response=True)
+        def _got((res, status, headers)):
+            self.failUnlessEqual(int(status), 206)
+            self.failUnless(headers.has_key("content-range"))
+            self.failUnlessEqual(headers["content-range"][0],
+                                 "bytes 5-%d/%d" % (length-1, length))
+        d.addCallback(_got)
+        return d
+
     def test_GET_FILEURL_range_bad(self):
         headers = {"range": "BOGUS=fizbop-quarnak"}
         d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad",
diff --git a/src/allmydata/web/filenode.py b/src/allmydata/web/filenode.py
index 4c200794..e8d52229 100644
--- a/src/allmydata/web/filenode.py
+++ b/src/allmydata/web/filenode.py
@@ -374,7 +374,14 @@ class FileDownloader(rend.Page):
             start, end = bytesrange[1].split('-')
             if start:
                 offset = int(start)
-            if end:
+                if not end:
+                    # RFC 2616 says:
+                    #
+                    # "If the last-byte-pos value is absent, or if the value is
+                    # greater than or equal to the current length of the
+                    # entity-body, last-byte-pos is taken to be equal to one less
+                    # than the current length of the entity- body in bytes."
+                    end = filesize - 1
                 size = int(end) - offset + 1
             req.setResponseCode(http.PARTIAL_CONTENT)
             req.setHeader('content-range',"bytes %s-%s/%s" %
-- 
2.45.2