- 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='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')
+# 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))
+
+
+ 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()
+
+ 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"
+
+ content_type=self._get_content_type(file, self.__name__,
+ 'application/octet-stream')
+ self.update_data(file, content_type)
+ notify(ObjectModifiedEvent(self))
+
+ 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