From 00f696ee7c4dc0949d8abfe33f9384e5500db03e Mon Sep 17 00:00:00 2001
From: Romain Courteaud <romain@nexedi.com>
Date: Mon, 4 Jun 2012 11:04:52 +0200
Subject: [PATCH] Allow to upload in chunk.

Data can only be appended.
---
 product/ERP5/Document/BigFile.py | 84 ++++++++++++++++++++++++++++++--
 1 file changed, 79 insertions(+), 5 deletions(-)

diff --git a/product/ERP5/Document/BigFile.py b/product/ERP5/Document/BigFile.py
index 4769812343..c18d64af8b 100644
--- a/product/ERP5/Document/BigFile.py
+++ b/product/ERP5/Document/BigFile.py
@@ -25,6 +25,7 @@ from webdav.common import rfc1123_date
 from mimetools import choose_boundary
 from Products.CMFCore.utils import getToolByName, _setCacheHeaders,\
     _ViewEmulator
+import re
 
 class BigFile(File):
   """
@@ -73,7 +74,7 @@ class BigFile(File):
     """
     self._setContentMd5(None)
 
-  def _read_data(self, file):
+  def _read_data(self, file, data=None):
 
     n=1 << 20
 
@@ -88,21 +89,26 @@ class BigFile(File):
     read=file.read
 
     seek(0,2)
-    size=end=file.tell()
+    end=file.tell()
 
-    btree = BTreeData()
+    if data is None:
+      btree = BTreeData()
+    else:
+      btree = data
     seek(0)
     pos = file.tell()
+    offset = len(btree)
 
     while pos < end:
       next = pos + n
       if next > end:
         next = end
 
-      btree.write(read(next), pos)
+      btree.write(read(next-pos), offset+pos)
       pos = file.tell()
 
-    return btree, size
+    self.serialize()
+    return btree, len(btree)
 
   def _range_request_handler(self, REQUEST, RESPONSE):
     # HTTP Range header handling: return True if we've served a range
@@ -287,6 +293,74 @@ class BigFile(File):
       pass
     return ''
 
+  security.declareProtected(Permissions.ModifyPortalContent,'PUT')
+  def PUT(self, REQUEST, RESPONSE):
+    """Handle HTTP PUT requests"""
+    self.dav__init(REQUEST, RESPONSE)
+    self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=1)
+
+    type=REQUEST.get_header('content-type', None)
+
+    file=REQUEST['BODYFILE']
+
+    content_range = REQUEST.get_header('Content-Range', None)
+    if content_range is None:
+      btree = None
+    else:
+      current_size = int(self.getSize())
+      query_range = re.compile('bytes \*/\*')
+      append_range = re.compile('bytes (?P<first_byte>[0-9]+)-' \
+                                '(?P<last_byte>[0-9]+)/' \
+                                '(?P<total_content_length>[0-9]+)')
+      if query_range.match(content_range):
+        data = self._baseGetData()
+
+        RESPONSE.setHeader('X-Explanation', 'Resume incomplete')
+        RESPONSE.setHeader('Range', 'bytes 0-%s' % (current_size-1))
+        RESPONSE.setStatus(308)
+        return RESPONSE
+
+      if append_range.match(content_range):
+
+        result_dict = append_range.search(content_range).groupdict()
+        first_byte = int(result_dict['first_byte'])
+        last_byte = int(result_dict['last_byte'])
+        total_content_length = int(result_dict['total_content_length'])
+        content_length= int(REQUEST.get_header('Content-Length', '0'))
+
+        if (first_byte != current_size):
+          RESPONSE.setHeader('X-Explanation', 'Can only append data')
+          RESPONSE.setStatus(400)
+          return RESPONSE
+        elif (last_byte+1 != total_content_length):
+          RESPONSE.setHeader('X-Explanation', 'Total size unexpected')
+          RESPONSE.setStatus(400)
+          return RESPONSE
+        elif (last_byte+1-first_byte != content_length):
+          RESPONSE.setHeader('X-Explanation', 'Content length unexpected')
+          RESPONSE.setStatus(400)
+          return RESPONSE
+
+        else:
+
+          btree = self._baseGetData()
+          if btree is None:
+            btree = BTreeData()
+
+      else:
+        RESPONSE.setHeader('X-Explanation', 'Can not parse range')
+        RESPONSE.setStatus(400) # Partial content
+        return RESPONSE
+
+    data, size = self._read_data(file, data=btree)
+
+    content_type=self._get_content_type(file, data, self.__name__,
+                                        type or self.content_type)
+    self.update_data(data, content_type, size)
+
+    RESPONSE.setStatus(204)
+    return RESPONSE
+
 # CMFFile also brings the IContentishInterface on CMF 2.2, remove it.
 removeIContentishInterface(BigFile)
 
-- 
2.30.9