- 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 ''