-                  RoleManager, Item_w__name__, Cacheable):
-       """A File object is a content object for arbitrary files."""
-
-       __implements__ = (WriteLockInterface, HTTPRangeSupport.HTTPRangeInterface)
-       meta_type='Blob File'
-
-       security = ClassSecurityInfo()
-       security.declareObjectProtected(View)
-
-       precondition=''
-       size=None
-
-       manage_editForm  =DTMLFile('dtml/fileEdit',globals(),
-                                                          Kind='File',kind='file')
-       manage_editForm._setName('manage_editForm')
-
-       security.declareProtected(view_management_screens, 'manage')
-       security.declareProtected(view_management_screens, 'manage_main')
-       manage=manage_main=manage_editForm
-       manage_uploadForm=manage_editForm
-
-       manage_options=(
-               (
-               {'label':'Edit', 'action':'manage_main',
-                'help':('OFSP','File_Edit.stx')},
-               {'label':'View', 'action':'',
-                'help':('OFSP','File_View.stx')},
-               )
-               + PropertyManager.manage_options
-               + RoleManager.manage_options
-               + Item_w__name__.manage_options
-               + Cacheable.manage_options
-               )
-
-       _properties=({'id':'title', 'type': 'string'},
-                                {'id':'content_type', 'type':'string'},
-                                )
-
-       def __init__(self, id, title, file, content_type='', precondition=''):
-               self.__name__=id
-               self.title=title
-               self.precondition=precondition
-               self.uploaded_filename = cookId('', '', file)[0]
-               self.bdata = Blob()
-
-               content_type=self._get_content_type(file, id, content_type)
-               self.update_data(file, content_type)
-       
-       security.declarePrivate('save')
-       def save(self, file):
-               bf = self.bdata.open('w')
-               bf.write(file.read())
-               self.size = bf.tell()
-               bf.close()
-       
-       security.declarePrivate('open')
-       def open(self, mode='r'):
-               bf = self.bdata.open(mode)
-               return bf
-       
-       security.declarePrivate('updateSize')
-       def updateSize(self, size=None):
-               if size is None :
-                       bf = self.open('r')
-                       bf.seek(0,2)
-                       self.size = bf.tell()
-                       bf.close()
-               else :
-                       self.size = size
-
-       def _getLegacyData(self) :
-               warn("Accessing 'data' attribute may be inefficient with "
-                        "this blob based file. You should refactor your product "
-                        "by accessing data like: "
-                        "f = self.open('r') "
-                        "data = f.read()",
-                       DeprecationWarning, stacklevel=2)
-               f = self.open()
-               data = f.read()
-               f.close()
-               return data
-       
-       def _setLegacyData(self, data) :
-               warn("Accessing 'data' attribute may be inefficient with "
-                        "this blob based file. You should refactor your product "
-                        "by accessing data like: "
-                        "f = self.save(data)",
-                       DeprecationWarning, stacklevel=2)
-               if isinstance(data, str) :
-                       sio = StringIO()
-                       sio.write(data)
-                       sio.seek(0)
-                       data = sio
-               self.save(data)
-               
-       data = property(_getLegacyData, _setLegacyData,
-                                       "Data Legacy attribute to ensure compatibility "
-                                       "with derived classes that access data by this way.")
-
-       def id(self):
-               return self.__name__
-
-       def _if_modified_since_request_handler(self, REQUEST, RESPONSE):
-               # HTTP If-Modified-Since header handling: return True if
-               # we can handle this request by returning a 304 response
-               header=REQUEST.get_header('If-Modified-Since', None)
-               if header is not None:
-                       header=header.split( ';')[0]
-                       # Some proxies seem to send invalid date strings for this
-                       # header. If the date string is not valid, we ignore it
-                       # rather than raise an error to be generally consistent
-                       # with common servers such as Apache (which can usually
-                       # understand the screwy date string as a lucky side effect
-                       # of the way they parse it).
-                       # This happens to be what RFC2616 tells us to do in the face of an
-                       # invalid date.
-                       try:    mod_since=long(DateTime(header).timeTime())
-                       except: mod_since=None
-                       if mod_since is not None:
-                               if self._p_mtime:
-                                       last_mod = long(self._p_mtime)
-                               else:
-                                       last_mod = long(0)
-                               if last_mod > 0 and last_mod <= mod_since:
-                                       RESPONSE.setHeader('Last-Modified',
-                                                                          rfc1123_date(self._p_mtime))
-                                       RESPONSE.setHeader('Content-Type', self.content_type)
-                                       RESPONSE.setHeader('Accept-Ranges', 'bytes')
-                                       RESPONSE.setStatus(304)
-                                       return True
-
-       def _range_request_handler(self, REQUEST, RESPONSE):
-               # HTTP Range header handling: return True if we've served a range
-               # chunk out of our data.
-               range = REQUEST.get_header('Range', None)
-               request_range = REQUEST.get_header('Request-Range', None)
-               if request_range is not None:
-                       # Netscape 2 through 4 and MSIE 3 implement a draft version
-                       # Later on, we need to serve a different mime-type as well.
-                       range = request_range
-               if_range = REQUEST.get_header('If-Range', None)
-               if range is not None:
-                       ranges = HTTPRangeSupport.parseRange(range)
-
-                       if if_range is not None:
-                               # Only send ranges if the data isn't modified, otherwise send
-                               # the whole object. Support both ETags and Last-Modified dates!
-                               if len(if_range) > 1 and if_range[:2] == 'ts':
-                                       # ETag:
-                                       if if_range != self.http__etag():
-                                               # Modified, so send a normal response. We delete
-                                               # the ranges, which causes us to skip to the 200
-                                               # response.
-                                               ranges = None
-                               else:
-                                       # Date
-                                       date = if_range.split( ';')[0]
-                                       try: mod_since=long(DateTime(date).timeTime())
-                                       except: mod_since=None
-                                       if mod_since is not None:
-                                               if self._p_mtime:
-                                                       last_mod = long(self._p_mtime)
-                                               else:
-                                                       last_mod = long(0)
-                                               if last_mod > mod_since:
-                                                       # Modified, so send a normal response. We delete
-                                                       # the ranges, which causes us to skip to the 200
-                                                       # response.
-                                                       ranges = None
-
-                       if ranges:
-                               # Search for satisfiable ranges.
-                               satisfiable = 0
-                               for start, end in ranges:
-                                       if start < self.size:
-                                               satisfiable = 1
-                                               break
-
-                               if not satisfiable:
-                                       RESPONSE.setHeader('Content-Range',
-                                               'bytes */%d' % self.size)
-                                       RESPONSE.setHeader('Accept-Ranges', 'bytes')
-                                       RESPONSE.setHeader('Last-Modified',
-                                               rfc1123_date(self._p_mtime))
-                                       RESPONSE.setHeader('Content-Type', self.content_type)
-                                       RESPONSE.setHeader('Content-Length', self.size)
-                                       RESPONSE.setStatus(416)
-                                       return True
-
-                               ranges = HTTPRangeSupport.expandRanges(ranges, self.size)
-
-                               if len(ranges) == 1:
-                                       # Easy case, set extra header and return partial set.
-                                       start, end = ranges[0]
-                                       size = end - start
-
-                                       RESPONSE.setHeader('Last-Modified',
-                                               rfc1123_date(self._p_mtime))
-                                       RESPONSE.setHeader('Content-Type', self.content_type)
-                                       RESPONSE.setHeader('Content-Length', size)
-                                       RESPONSE.setHeader('Accept-Ranges', 'bytes')
-                                       RESPONSE.setHeader('Content-Range',
-                                               'bytes %d-%d/%d' % (start, end - 1, self.size))
-                                       RESPONSE.setStatus(206) # Partial content
-
-                                       bf = self.open('r')
-                                       bf.seek(start)
-                                       RESPONSE.write(bf.read(size))
-                                       bf.close()
-                                       return True
-
-                               else:
-                                       boundary = choose_boundary()
-
-                                       # Calculate the content length
-                                       size = (8 + len(boundary) + # End marker length
-                                               len(ranges) * (                 # Constant lenght per set
-                                                       49 + len(boundary) + len(self.content_type) +
-                                                       len('%d' % self.size)))
-                                       for start, end in ranges:
-                                               # Variable length per set
-                                               size = (size + len('%d%d' % (start, end - 1)) +
-                                                       end - start)
-
-
-                                       # Some clients implement an earlier draft of the spec, they
-                                       # will only accept x-byteranges.
-                                       draftprefix = (request_range is not None) and 'x-' or ''
-
-                                       RESPONSE.setHeader('Content-Length', size)
-                                       RESPONSE.setHeader('Accept-Ranges', 'bytes')
-                                       RESPONSE.setHeader('Last-Modified',
-                                               rfc1123_date(self._p_mtime))
-                                       RESPONSE.setHeader('Content-Type',
-                                               'multipart/%sbyteranges; boundary=%s' % (
-                                                       draftprefix, boundary))
-                                       RESPONSE.setStatus(206) # Partial content
-
-                                       bf = self.open('r')
-
-                                       for start, end in ranges:
-                                               RESPONSE.write('\r\n--%s\r\n' % boundary)
-                                               RESPONSE.write('Content-Type: %s\r\n' %
-                                                       self.content_type)
-                                               RESPONSE.write(
-                                                       'Content-Range: bytes %d-%d/%d\r\n\r\n' % (
-                                                               start, end - 1, self.size))
-
-                                               
-                                               size = end - start
-                                               bf.seek(start)
-                                               RESPONSE.write(bf.read(size))
-                                       
-                                       bf.close()
-
-                                       RESPONSE.write('\r\n--%s--\r\n' % boundary)
-                                       return True
-
-       security.declareProtected(View, 'index_html')
-       def index_html(self, REQUEST, RESPONSE):
-               """
-               The default view of the contents of a File or Image.
-
-               Returns the contents of the file or image.      Also, sets the
-               Content-Type HTTP header to the objects content type.
-               """
-
-               if self._if_modified_since_request_handler(REQUEST, RESPONSE):
-                       # we were able to handle this by returning a 304
-                       # unfortunately, because the HTTP cache manager uses the cache
-                       # API, and because 304 responses are required to carry the Expires
-                       # header for HTTP/1.1, we need to call ZCacheable_set here.
-                       # This is nonsensical for caches other than the HTTP cache manager
-                       # unfortunately.
-                       self.ZCacheable_set(None)
-                       return ''
-
-               if self.precondition and hasattr(self, str(self.precondition)):
-                       # Grab whatever precondition was defined and then
-                       # execute it.  The precondition will raise an exception
-                       # if something violates its terms.
-                       c=getattr(self, str(self.precondition))
-                       if hasattr(c,'isDocTemp') and c.isDocTemp:
-                               c(REQUEST['PARENTS'][1],REQUEST)
-                       else:
-                               c()
-
-               if self._range_request_handler(REQUEST, RESPONSE):
-                       # we served a chunk of content in response to a range request.
-                       return ''
-
-               RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime))
-               RESPONSE.setHeader('Content-Type', self.content_type)
-               RESPONSE.setHeader('Content-Length', self.size)
-               RESPONSE.setHeader('Accept-Ranges', 'bytes')
-
-               if self.ZCacheable_isCachingEnabled():
-                       result = self.ZCacheable_get(default=None)
-                       if result is not None:
-                               # We will always get None from RAMCacheManager and HTTP
-                               # Accelerated Cache Manager but we will get
-                               # something implementing the IStreamIterator interface
-                               # from a "FileCacheManager"
-                               return result
-
-               self.ZCacheable_set(None)
-
-               bf = self.open('r')
-               chunk = bf.read(CHUNK_SIZE)
-               while chunk :
-                       RESPONSE.write(chunk)
-                       chunk = bf.read(CHUNK_SIZE)
-               bf.close()
-               return ''               
-
-       security.declareProtected(View, 'view_image_or_file')
-       def view_image_or_file(self, URL1):
-               """
-               The default view of the contents of the File or Image.
-               """
-               raise Redirect, URL1
-
-       security.declareProtected(View, 'PrincipiaSearchSource')
-       def PrincipiaSearchSource(self):
-               """ Allow file objects to be searched.
-               """
-               if self.content_type.startswith('text/'):
-                       bf = self.open('r')
-                       data = bf.read()
-                       bf.close()
-                       return data
-               return ''
-
-       security.declarePrivate('update_data')
-       def update_data(self, file, content_type=None):
-               if isinstance(file, unicode):
-                       raise TypeError('Data can only be str or file-like.      '
-                                                       'Unicode objects are expressly forbidden.')
-               elif isinstance(file, str) :
-                       sio = StringIO()
-                       sio.write(file)
-                       sio.seek(0)
-                       file = sio
-
-               if content_type is not None: self.content_type=content_type
-               self.save(file)
-               self.ZCacheable_invalidate()
-               self.ZCacheable_set(None)
-               self.http__refreshEtag()
-
-       security.declareProtected(change_images_and_files, 'manage_edit')
-       def manage_edit(self, title, content_type, precondition='',
-                                       filedata=None, REQUEST=None):
-               """
-               Changes the title and content type attributes of the File or Image.
-               """
-               if self.wl_isLocked():
-                       raise ResourceLockedError, "File is locked via WebDAV"
-
-               self.title=str(title)
-               self.content_type=str(content_type)
-               if precondition: self.precondition=str(precondition)
-               elif self.precondition: del self.precondition
-               if filedata is not None:
-                       self.update_data(filedata, content_type)
-               else:
-                       self.ZCacheable_invalidate()
-               if REQUEST:
-                       message="Saved changes."
-                       return self.manage_main(self,REQUEST,manage_tabs_message=message)
-
-       security.declareProtected(change_images_and_files, 'manage_upload')
-       def manage_upload(self,file='',REQUEST=None):
-               """
-               Replaces the current contents of the File or Image object with file.
-
-               The file or images contents are replaced with the contents of 'file'.
-               """
-               if self.wl_isLocked():
-                       raise ResourceLockedError, "File is locked via WebDAV"
-
-               content_type=self._get_content_type(file, self.__name__,
-                                                                                       'application/octet-stream')
-               self.update_data(file, content_type)
-
-               if REQUEST:
-                       message="Saved changes."
-                       return self.manage_main(self,REQUEST,manage_tabs_message=message)
-
-       def _get_content_type(self, file, id, content_type=None):
-               headers=getattr(file, 'headers', None)
-               if headers and headers.has_key('content-type'):
-                       content_type=headers['content-type']
-               else:
-                       name = getattr(file, 'filename', self.uploaded_filename) or id
-                       content_type, enc=guess_content_type(name, '', content_type)
-               return content_type
-
-       security.declareProtected(delete_objects, 'DELETE')
-
-       security.declareProtected(change_images_and_files, '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_type = self._get_content_type(file, self.__name__,
-                                                                                         type or self.content_type)
-               self.update_data(file, content_type)
-
-               RESPONSE.setStatus(204)
-               return RESPONSE
-
-       security.declareProtected(View, 'get_size')
-       def get_size(self):
-               """Get the size of a file or image.
-
-               Returns the size of the file or image.
-               """
-               size=self.size
-               if size is None :
-                       bf = self.open('r')
-                       bf.seek(0,2)
-                       self.size = size = bf.tell()
-                       bf.close()
-               return size
-
-       # deprecated; use get_size!
-       getSize=get_size
-
-       security.declareProtected(View, 'getContentType')
-       def getContentType(self):
-               """Get the content type of a file or image.
-
-               Returns the content type (MIME type) of a file or image.
-               """
-               return self.content_type
-
-
-       def __str__(self): return str(self.data)
-       def __len__(self): return 1
-
-       security.declareProtected(ftp_access, 'manage_FTPstat')
-       security.declareProtected(ftp_access, 'manage_FTPlist')
-
-       security.declareProtected(ftp_access, 'manage_FTPget')
-       def manage_FTPget(self):
-               """Return body for ftp."""
-               RESPONSE = self.REQUEST.RESPONSE
-
-               if self.ZCacheable_isCachingEnabled():
-                       result = self.ZCacheable_get(default=None)
-                       if result is not None:
-                               # We will always get None from RAMCacheManager but we will get
-                               # something implementing the IStreamIterator interface
-                               # from FileCacheManager.
-                               # the content-length is required here by HTTPResponse, even
-                               # though FTP doesn't use it.
-                               RESPONSE.setHeader('Content-Length', self.size)
-                               return result
-
-               bf = self.open('r')
-               data = bf.read()
-               bf.close()
-               RESPONSE.setBase(None)
-               return data
+           RoleManager, Item_w__name__, Cacheable):
+    """A File object is a content object for arbitrary files."""
+
+    implements(implementedBy(Persistent),
+               implementedBy(Implicit),
+               implementedBy(PropertyManager),
+               implementedBy(RoleManager),
+               implementedBy(Item_w__name__),
+               implementedBy(Cacheable),
+               IWriteLock,
+               HTTPRangeSupport.HTTPRangeInterface,
+              )
+    meta_type='File'
+
+    security = ClassSecurityInfo()
+    security.declareObjectProtected(View)
+
+    precondition=''
+    size=None
+
+    manage_editForm  =DTMLFile('dtml/fileEdit',globals(),
+                               Kind='File',kind='file')
+    manage_editForm._setName('manage_editForm')
+
+    security.declareProtected(view_management_screens, 'manage')
+    security.declareProtected(view_management_screens, 'manage_main')
+    manage=manage_main=manage_editForm
+    manage_uploadForm=manage_editForm
+
+    manage_options=(
+        (
+        {'label':'Edit', 'action':'manage_main',
+         'help':('OFSP','File_Edit.stx')},
+        {'label':'View', 'action':'',
+         'help':('OFSP','File_View.stx')},
+        )
+        + PropertyManager.manage_options
+        + RoleManager.manage_options
+        + Item_w__name__.manage_options
+        + Cacheable.manage_options
+        )
+
+    _properties=({'id':'title', 'type': 'string'},
+                 {'id':'content_type', 'type':'string'},
+                 )
+
+    def __init__(self, id, title, file, content_type='', precondition=''):
+        self.__name__=id
+        self.title=title
+        self.precondition=precondition
+
+        data, size = self._read_data(file)
+        content_type=self._get_content_type(file, data, id, content_type)
+        self.update_data(data, content_type, size)
+
+    def id(self):
+        return self.__name__
+
+    def _if_modified_since_request_handler(self, REQUEST, RESPONSE):
+        # HTTP If-Modified-Since header handling: return True if
+        # we can handle this request by returning a 304 response
+        header=REQUEST.get_header('If-Modified-Since', None)
+        if header is not None:
+            header=header.split( ';')[0]
+            # Some proxies seem to send invalid date strings for this
+            # header. If the date string is not valid, we ignore it
+            # rather than raise an error to be generally consistent
+            # with common servers such as Apache (which can usually
+            # understand the screwy date string as a lucky side effect
+            # of the way they parse it).
+            # This happens to be what RFC2616 tells us to do in the face of an
+            # invalid date.
+            try:    mod_since=long(DateTime(header).timeTime())
+            except: mod_since=None
+            if mod_since is not None:
+                if self._p_mtime:
+                    last_mod = long(self._p_mtime)
+                else:
+                    last_mod = long(0)
+                if last_mod > 0 and last_mod <= mod_since:
+                    RESPONSE.setHeader('Last-Modified',
+                                       rfc1123_date(self._p_mtime))
+                    RESPONSE.setHeader('Content-Type', self.content_type)
+                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                    RESPONSE.setStatus(304)
+                    return True
+
+    def _range_request_handler(self, REQUEST, RESPONSE):
+        # HTTP Range header handling: return True if we've served a range
+        # chunk out of our data.
+        range = REQUEST.get_header('Range', None)
+        request_range = REQUEST.get_header('Request-Range', None)
+        if request_range is not None:
+            # Netscape 2 through 4 and MSIE 3 implement a draft version
+            # Later on, we need to serve a different mime-type as well.
+            range = request_range
+        if_range = REQUEST.get_header('If-Range', None)
+        if range is not None:
+            ranges = HTTPRangeSupport.parseRange(range)
+
+            if if_range is not None:
+                # Only send ranges if the data isn't modified, otherwise send
+                # the whole object. Support both ETags and Last-Modified dates!
+                if len(if_range) > 1 and if_range[:2] == 'ts':
+                    # ETag:
+                    if if_range != self.http__etag():
+                        # Modified, so send a normal response. We delete
+                        # the ranges, which causes us to skip to the 200
+                        # response.
+                        ranges = None
+                else:
+                    # Date
+                    date = if_range.split( ';')[0]
+                    try: mod_since=long(DateTime(date).timeTime())
+                    except: mod_since=None
+                    if mod_since is not None:
+                        if self._p_mtime:
+                            last_mod = long(self._p_mtime)
+                        else:
+                            last_mod = long(0)
+                        if last_mod > mod_since:
+                            # Modified, so send a normal response. We delete
+                            # the ranges, which causes us to skip to the 200
+                            # response.
+                            ranges = None
+
+            if ranges:
+                # Search for satisfiable ranges.
+                satisfiable = 0
+                for start, end in ranges:
+                    if start < self.size:
+                        satisfiable = 1
+                        break
+
+                if not satisfiable:
+                    RESPONSE.setHeader('Content-Range',
+                        'bytes */%d' % self.size)
+                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                    RESPONSE.setHeader('Last-Modified',
+                        rfc1123_date(self._p_mtime))
+                    RESPONSE.setHeader('Content-Type', self.content_type)
+                    RESPONSE.setHeader('Content-Length', self.size)
+                    RESPONSE.setStatus(416)
+                    return True
+
+                ranges = HTTPRangeSupport.expandRanges(ranges, self.size)
+
+                if len(ranges) == 1:
+                    # Easy case, set extra header and return partial set.
+                    start, end = ranges[0]
+                    size = end - start
+
+                    RESPONSE.setHeader('Last-Modified',
+                        rfc1123_date(self._p_mtime))
+                    RESPONSE.setHeader('Content-Type', self.content_type)
+                    RESPONSE.setHeader('Content-Length', size)
+                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                    RESPONSE.setHeader('Content-Range',
+                        'bytes %d-%d/%d' % (start, end - 1, self.size))
+                    RESPONSE.setStatus(206) # Partial content
+
+                    data = self.data
+                    if isinstance(data, str):
+                        RESPONSE.write(data[start:end])
+                        return True
+
+                    # Linked Pdata objects. Urgh.
+                    pos = 0
+                    while data is not None:
+                        l = len(data.data)
+                        pos = pos + l
+                        if pos > start:
+                            # We are within the range
+                            lstart = l - (pos - start)
+
+                            if lstart < 0: lstart = 0
+
+                            # find the endpoint
+                            if end <= pos:
+                                lend = l - (pos - end)
+
+                                # Send and end transmission
+                                RESPONSE.write(data[lstart:lend])
+                                break
+
+                            # Not yet at the end, transmit what we have.
+                            RESPONSE.write(data[lstart:])
+
+                        data = data.next
+
+                    return True
+
+                else:
+                    boundary = choose_boundary()
+
+                    # Calculate the content length
+                    size = (8 + len(boundary) + # End marker length
+                        len(ranges) * (         # Constant lenght per set
+                            49 + len(boundary) + len(self.content_type) +
+                            len('%d' % self.size)))
+                    for start, end in ranges:
+                        # Variable length per set
+                        size = (size + len('%d%d' % (start, end - 1)) +
+                            end - start)
+
+
+                    # Some clients implement an earlier draft of the spec, they
+                    # will only accept x-byteranges.
+                    draftprefix = (request_range is not None) and 'x-' or ''
+
+                    RESPONSE.setHeader('Content-Length', size)
+                    RESPONSE.setHeader('Accept-Ranges', 'bytes')
+                    RESPONSE.setHeader('Last-Modified',
+                        rfc1123_date(self._p_mtime))
+                    RESPONSE.setHeader('Content-Type',
+                        'multipart/%sbyteranges; boundary=%s' % (
+                            draftprefix, boundary))
+                    RESPONSE.setStatus(206) # Partial content
+
+                    data = self.data
+                    # The Pdata map allows us to jump into the Pdata chain
+                    # arbitrarily during out-of-order range searching.
+                    pdata_map = {}
+                    pdata_map[0] = data
+
+                    for start, end in ranges:
+                        RESPONSE.write('\r\n--%s\r\n' % boundary)
+                        RESPONSE.write('Content-Type: %s\r\n' %
+                            self.content_type)
+                        RESPONSE.write(
+                            'Content-Range: bytes %d-%d/%d\r\n\r\n' % (
+                                start, end - 1, self.size))
+
+                        if isinstance(data, str):
+                            RESPONSE.write(data[start:end])
+
+                        else:
+                            # Yippee. Linked Pdata objects. The following
+                            # calculations allow us to fast-forward through the
+                            # Pdata chain without a lot of dereferencing if we
+                            # did the work already.
+                            first_size = len(pdata_map[0].data)
+                            if start < first_size:
+                                closest_pos = 0
+                            else:
+                                closest_pos = (
+                                    ((start - first_size) >> 16 << 16) +
+                                    first_size)
+                            pos = min(closest_pos, max(pdata_map.keys()))
+                            data = pdata_map[pos]
+
+                            while data is not None:
+                                l = len(data.data)
+                                pos = pos + l
+                                if pos > start:
+                                    # We are within the range
+                                    lstart = l - (pos - start)
+
+                                    if lstart < 0: lstart = 0
+
+                                    # find the endpoint
+                                    if end <= pos:
+                                        lend = l - (pos - end)
+
+                                        # Send and loop to next range
+                                        RESPONSE.write(data[lstart:lend])
+                                        break
+
+                                    # Not yet at the end, transmit what we have.
+                                    RESPONSE.write(data[lstart:])
+
+                                data = data.next
+                                # Store a reference to a Pdata chain link so we
+                                # don't have to deref during this request again.
+                                pdata_map[pos] = data
+
+                    # Do not keep the link references around.
+                    del pdata_map
+
+                    RESPONSE.write('\r\n--%s--\r\n' % boundary)
+                    return True
+
+    security.declareProtected(View, 'index_html')
+    def index_html(self, REQUEST, RESPONSE):
+        """
+        The default view of the contents of a File or Image.
+
+        Returns the contents of the file or image.  Also, sets the
+        Content-Type HTTP header to the objects content type.
+        """
+
+        if self._if_modified_since_request_handler(REQUEST, RESPONSE):
+            # we were able to handle this by returning a 304
+            # unfortunately, because the HTTP cache manager uses the cache
+            # API, and because 304 responses are required to carry the Expires
+            # header for HTTP/1.1, we need to call ZCacheable_set here.
+            # This is nonsensical for caches other than the HTTP cache manager
+            # unfortunately.
+            self.ZCacheable_set(None)
+            return ''
+
+        if self.precondition and hasattr(self, str(self.precondition)):
+            # Grab whatever precondition was defined and then
+            # execute it.  The precondition will raise an exception
+            # if something violates its terms.
+            c=getattr(self, str(self.precondition))
+            if hasattr(c,'isDocTemp') and c.isDocTemp:
+                c(REQUEST['PARENTS'][1],REQUEST)
+            else:
+                c()
+
+        if self._range_request_handler(REQUEST, RESPONSE):
+            # we served a chunk of content in response to a range request.
+            return ''
+
+        RESPONSE.setHeader('Last-Modified', rfc1123_date(self._p_mtime))
+        RESPONSE.setHeader('Content-Type', self.content_type)
+        RESPONSE.setHeader('Content-Length', self.size)
+        RESPONSE.setHeader('Accept-Ranges', 'bytes')
+
+        if self.ZCacheable_isCachingEnabled():
+            result = self.ZCacheable_get(default=None)
+            if result is not None:
+                # We will always get None from RAMCacheManager and HTTP
+                # Accelerated Cache Manager but we will get
+                # something implementing the IStreamIterator interface
+                # from a "FileCacheManager"
+                return result
+
+        self.ZCacheable_set(None)
+
+        data=self.data
+        if isinstance(data, str):
+            RESPONSE.setBase(None)
+            return data
+
+        while data is not None:
+            RESPONSE.write(data.data)
+            data=data.next
+
+        return ''
+
+    security.declareProtected(View, 'view_image_or_file')
+    def view_image_or_file(self, URL1):
+        """
+        The default view of the contents of the File or Image.
+        """
+        raise Redirect, URL1
+
+    security.declareProtected(View, 'PrincipiaSearchSource')
+    def PrincipiaSearchSource(self):
+        """ Allow file objects to be searched.
+        """
+        if self.content_type.startswith('text/'):
+            return str(self.data)
+        return ''
+
+    security.declarePrivate('update_data')
+    def update_data(self, data, content_type=None, size=None):
+        if isinstance(data, unicode):
+            raise TypeError('Data can only be str or file-like.  '
+                            'Unicode objects are expressly forbidden.')
+
+        if content_type is not None: self.content_type=content_type
+        if size is None: size=len(data)
+        self.size=size
+        self.data=data
+        self.ZCacheable_invalidate()
+        self.ZCacheable_set(None)
+        self.http__refreshEtag()
+
+    security.declareProtected(change_images_and_files, 'manage_edit')
+    def manage_edit(self, title, content_type, precondition='',
+                    filedata=None, REQUEST=None):
+        """
+        Changes the title and content type attributes of the File or Image.
+        """
+        if self.wl_isLocked():
+            raise ResourceLockedError, "File is locked via WebDAV"
+
+        self.title=str(title)
+        self.content_type=str(content_type)
+        if precondition: self.precondition=str(precondition)
+        elif self.precondition: del self.precondition
+        if filedata is not None:
+            self.update_data(filedata, content_type, len(filedata))
+        else:
+            self.ZCacheable_invalidate()
+        
+        notify(ObjectModifiedEvent(self))
+        
+        if REQUEST:
+            message="Saved changes."
+            return self.manage_main(self,REQUEST,manage_tabs_message=message)
+
+    security.declareProtected(change_images_and_files, 'manage_upload')
+    def manage_upload(self,file='',REQUEST=None):
+        """
+        Replaces the current contents of the File or Image object with file.
+
+        The file or images contents are replaced with the contents of 'file'.
+        """
+        if self.wl_isLocked():
+            raise ResourceLockedError, "File is locked via WebDAV"
+
+        data, size = self._read_data(file)
+        content_type=self._get_content_type(file, data, self.__name__,
+                                            'application/octet-stream')
+        self.update_data(data, content_type, size)
+        
+        notify(ObjectModifiedEvent(self))
+        
+        if REQUEST:
+            message="Saved changes."
+            return self.manage_main(self,REQUEST,manage_tabs_message=message)
+
+    def _get_content_type(self, file, body, id, content_type=None):
+        headers=getattr(file, 'headers', None)
+        if headers and headers.has_key('content-type'):
+            content_type=headers['content-type']
+        else:
+            if not isinstance(body, str): body=body.data
+            content_type, enc=guess_content_type(
+                getattr(file, 'filename',id), body, content_type)
+        return content_type
+
+    def _read_data(self, file):
+        import transaction
+
+        n=1 << 16
+
+        if isinstance(file, str):
+            size=len(file)
+            if size < n: return file, size
+            # Big string: cut it into smaller chunks
+            file = StringIO(file)
+
+        if isinstance(file, FileUpload) and not file:
+            raise ValueError, 'File not specified'
+
+        if hasattr(file, '__class__') and file.__class__ is Pdata:
+            size=len(file)
+            return file, size
+
+        seek=file.seek
+        read=file.read
+
+        seek(0,2)
+        size=end=file.tell()
+
+        if size <= 2*n:
+            seek(0)
+            if size < n: return read(size), size
+            return Pdata(read(size)), size
+
+        # Make sure we have an _p_jar, even if we are a new object, by
+        # doing a sub-transaction commit.
+        transaction.savepoint(optimistic=True)
+
+        if self._p_jar is None:
+            # Ugh
+            seek(0)
+            return Pdata(read(size)), size
+
+        # Now we're going to build a linked list from back
+        # to front to minimize the number of database updates
+        # and to allow us to get things out of memory as soon as
+        # possible.
+        next = None
+        while end > 0:
+            pos = end-n
+            if pos < n:
+                pos = 0 # we always want at least n bytes
+            seek(pos)
+
+            # Create the object and assign it a next pointer
+            # in the same transaction, so that there is only
+            # a single database update for it.
+            data = Pdata(read(end-pos))
+            self._p_jar.add(data)
+            data.next = next
+
+            # Save the object so that we can release its memory.
+            transaction.savepoint(optimistic=True)
+            data._p_deactivate()
+            # The object should be assigned an oid and be a ghost.
+            assert data._p_oid is not None
+            assert data._p_state == -1
+
+            next = data
+            end = pos
+
+        return next, size
+
+    security.declareProtected(delete_objects, 'DELETE')
+
+    security.declareProtected(change_images_and_files, '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']
+
+        data, size = self._read_data(file)
+        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
+
+    security.declareProtected(View, 'get_size')
+    def get_size(self):
+        """Get the size of a file or image.
+
+        Returns the size of the file or image.
+        """
+        size=self.size
+        if size is None: size=len(self.data)
+        return size
+
+    # deprecated; use get_size!
+    getSize=get_size
+
+    security.declareProtected(View, 'getContentType')
+    def getContentType(self):
+        """Get the content type of a file or image.
+
+        Returns the content type (MIME type) of a file or image.
+        """
+        return self.content_type
+
+
+    def __str__(self): return str(self.data)
+    def __len__(self): return 1
+
+    security.declareProtected(ftp_access, 'manage_FTPstat')
+    security.declareProtected(ftp_access, 'manage_FTPlist')
+
+    security.declareProtected(ftp_access, 'manage_FTPget')
+    def manage_FTPget(self):
+        """Return body for ftp."""
+        RESPONSE = self.REQUEST.RESPONSE
+
+        if self.ZCacheable_isCachingEnabled():
+            result = self.ZCacheable_get(default=None)
+            if result is not None:
+                # We will always get None from RAMCacheManager but we will get
+                # something implementing the IStreamIterator interface
+                # from FileCacheManager.
+                # the content-length is required here by HTTPResponse, even
+                # though FTP doesn't use it.
+                RESPONSE.setHeader('Content-Length', self.size)
+                return result
+
+        data = self.data
+        if isinstance(data, str):
+            RESPONSE.setBase(None)
+            return data
+
+        while data is not None:
+            RESPONSE.write(data.data)
+            data = data.next
+
+        return ''