[Zope] Zope 2.4.3 & PDFs = BUG?
Dirk Datzert
Dirk.Datzert@rasselstein-hoesch.de
Tue, 8 Jan 2002 10:10:13 +0100
This is a multi-part message in MIME format.
------=_NextPart_000_0047_01C1982C.A9E2F190
Content-Type: text/plain;
charset="iso-8859-1"
Content-Transfer-Encoding: 7bit
This bug-fix works for mine 2.4.3
----- Original Message -----
From: "Martijn Pieters" <mj@zope.com>
To: "Joachim Werner" <joe@iuveno-net.de>
Cc: <zope-dev@zope.org>
Sent: Monday, January 07, 2002 10:12 PM
Subject: Re: [Zope-dev] PDF-specific Bug in the ZServer implementation??? Or
just strange behavoiur of IE?
> On Mon, Jan 07, 2002 at 09:56:40PM +0100, Joachim Werner wrote:
> > What is the best approach to upgrading to the new code? Replacing the
> > ZServer code by the CVS one?
>
> The code only applies to OFS/Image.py (only File and Image objects support
> HTTP Range) and ZPublisher/HTTPRangeSupport.py. I've attached the versions
> for a Zope 2.4.x installation, just drop them in the correct places in
your
> current Zope setup.
>
> --
> Martijn Pieters
> | Software Engineer mailto:mj@zope.com
> | Zope Corporation http://www.zope.com/
> | Creators of Zope http://www.zope.org/
> ---------------------------------------------
>
------=_NextPart_000_0047_01C1982C.A9E2F190
Content-Type: application/octet-stream;
name="Image.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="Image.py"
#########################################################################=
#####
#=20
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#=20
# Copyright (c) Digital Creations. All rights reserved.
#=20
# This license has been certified as Open Source(tm).
#=20
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#=20
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#=20
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#=20
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#=20
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#=20
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#=20
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#=20
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#=20
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#=20
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#=20
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#=20
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#=20
#=20
# Disclaimer
#=20
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#=20
#=20
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#=20
#########################################################################=
#####
"""Image object"""
__version__=3D'$Revision: 1.130.4.2 $'[11:-2]
import Globals, string, struct
from OFS.content_types import guess_content_type
from Globals import DTMLFile
from PropertyManager import PropertyManager
from AccessControl.Role import RoleManager
from webdav.common import rfc1123_date
from webdav.Lockable import ResourceLockedError
from webdav.WriteLockInterface import WriteLockInterface
from SimpleItem import Item_w__name__
from cStringIO import StringIO
from Globals import Persistent
from Acquisition import Implicit
from DateTime import DateTime
from Cache import Cacheable
from mimetools import choose_boundary
from ZPublisher import HTTPRangeSupport
StringType=3Dtype('')
manage_addFileForm=3DDTMLFile('dtml/imageAdd', =
globals(),Kind=3D'File',kind=3D'file')
def manage_addFile(self,id,file=3D'',title=3D'',precondition=3D'', =
content_type=3D'',
REQUEST=3DNone):
"""Add a new File object.
Creates a new File object 'id' with the contents of 'file'"""
id=3Dstr(id)
title=3Dstr(title)
content_type=3Dstr(content_type)
precondition=3Dstr(precondition)
=20
id, title =3D cookId(id, title, file)
self=3Dself.this()
# First, we create the file without data:
self._setObject(id, File(id,title,'',content_type, precondition))
# Now we "upload" the data. By doing this in two steps, we
# can use a database trick to make the upload more efficient.
self._getOb(id).manage_upload(file)
if content_type:
self._getOb(id).content_type=3Dcontent_type
if REQUEST is not None:
REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
class File(Persistent, Implicit, PropertyManager,
RoleManager, Item_w__name__, Cacheable):
"""A File object is a content object for arbitrary files."""
=20
__implements__ =3D (WriteLockInterface, =
HTTPRangeSupport.HTTPRangeInterface)
meta_type=3D'File'
=20
precondition=3D''
size=3DNone
manage_editForm =3DDTMLFile('dtml/fileEdit',globals(),
Kind=3D'File',kind=3D'file')
manage_editForm._setName('manage_editForm')
manage=3Dmanage_main=3Dmanage_editForm
manage_uploadForm=3Dmanage_editForm
=20
manage_options=3D(
(
{'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
)
__ac_permissions__=3D(
('View management screens',
('manage', 'manage_main',)),
('Change Images and Files',
('manage_edit','manage_upload','PUT')),
('View',
('index_html', 'view_image_or_file', 'get_size',
'getContentType', '')),
('FTP access',
('manage_FTPstat','manage_FTPget','manage_FTPlist')),
('Delete objects',
('DELETE',)),
)
=20
_properties=3D({'id':'title', 'type': 'string'},
{'id':'content_type', 'type':'string'},
)
def __init__(self, id, title, file, content_type=3D'', =
precondition=3D''):
self.__name__=3Did
self.title=3Dtitle
self.precondition=3Dprecondition
=20
data, size =3D self._read_data(file)
content_type=3Dself._get_content_type(file, data, id, =
content_type)
self.update_data(data, content_type, size)
def id(self):
return self.__name__
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.
"""
# HTTP If-Modified-Since header handling.
header=3DREQUEST.get_header('If-Modified-Since', None)
if header is not None:
header=3Dstring.split(header, ';')[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=3Dlong(DateTime(header).timeTime())
except: mod_since=3DNone
if mod_since is not None:
if self._p_mtime:
last_mod =3D long(self._p_mtime)
else:
last_mod =3D long(0)
if last_mod > 0 and last_mod <=3D mod_since:
# Set header values since apache caching will return =
Content-Length
# of 0 in response if size is not set here
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')
RESPONSE.setStatus(304)
return ''
if self.precondition and hasattr(self,self.precondition):
# Grab whatever precondition was defined and then=20
# execute it. The precondition will raise an exception=20
# if something violates its terms.
c=3Dgetattr(self,self.precondition)
if hasattr(c,'isDocTemp') and c.isDocTemp:
c(REQUEST['PARENTS'][1],REQUEST)
else:
c()
# HTTP Range header handling
range =3D REQUEST.get_header('Range', None)
request_range =3D 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 =3D request_range
if_range =3D REQUEST.get_header('If-Range', None)
if range is not None:
ranges =3D 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] =3D=3D 'ts':
# ETag:
if if_range !=3D self.http__etag():
# Modified, so send a normal response. We delete
# the ranges, which causes us to skip to the 200
# response.
ranges =3D None
else:
# Date
date =3D string.split(if_range, ';')[0]
try: mod_since=3Dlong(DateTime(date).timeTime())
except: mod_since=3DNone
if mod_since is not None:
if self._p_mtime:
last_mod =3D long(self._p_mtime)
else:
last_mod =3D 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 =3D None
if ranges:
# Search for satisfiable ranges.
satisfiable =3D 0
for start, end in ranges:
if start < self.size:
satisfiable =3D 1
break
if not satisfiable:
RESPONSE.setHeader('Content-Range',=20
'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 ''
# Can we optimize?
ranges =3D HTTPRangeSupport.optimizeRanges(ranges, =
self.size)
=20
if len(ranges) =3D=3D 1:
# Easy case, set extra header and return partial =
set.
start, end =3D ranges[0]
size =3D end - start
=20
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',=20
'bytes %d-%d/%d' % (start, end - 1, self.size))
RESPONSE.setStatus(206) # Partial content
data =3D self.data
if type(data) is StringType:
return data[start:end]
# Linked Pdata objects. Urgh.
pos =3D 0
while data is not None:
l =3D len(data.data)
pos =3D pos + l
if pos > start:
# We are within the range
lstart =3D l - (pos - start)
if lstart < 0: lstart =3D 0
=20
# find the endpoint
if end <=3D pos:
lend =3D l - (pos - end)
=20
# Send and end transmission
RESPONSE.write(data[lstart:lend])
break
# Not yet at the end, transmit what we have.
RESPONSE.write(data[lstart:])
data =3D data.next
=20
return ''
=20
else:
# When we get here, ranges have been optimized, so =
they are
# in order, non-overlapping, and start and end =
values are
# positive integers.
boundary =3D choose_boundary()
=20
# Calculate the content length
size =3D (8 + len(boundary) + # End marker length
len(ranges) * ( # Constant lenght per =
set
49 + len(boundary) + len(self.content_type) =
+=20
len('%d' % self.size)))
for start, end in ranges:
# Variable length per set
size =3D (size + len('%d%d' % (start, end - 1)) =
+=20
end - start)
=20
=20
# Some clients implement an earlier draft of the =
spec, they
# will only accept x-byteranges.
draftprefix =3D (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=3D%s' % (
draftprefix, boundary))
RESPONSE.setStatus(206) # Partial content
pos =3D 0
data =3D self.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))=20
if type(data) is StringType:
RESPONSE.write(data[start:end])
else:
# Yippee. Linked Pdata objects.
while data is not None:
l =3D len(data.data)
pos =3D pos + l
if pos > start:
# We are within the range
lstart =3D l - (pos - start)
if lstart < 0: lstart =3D 0
=20
# find the endpoint
if end <=3D pos:
lend =3D l - (pos - end)
=20
# Send and loop to next range
=
RESPONSE.write(data[lstart:lend])
# Back up the position marker, =
it will
# be incremented again for the =
next
# part.
pos =3D pos - l
break
# Not yet at the end, transmit what =
we have.
RESPONSE.write(data[lstart:])
data =3D data.next
RESPONSE.write('\r\n--%s--\r\n' % boundary)
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')
# Don't cache the data itself, but provide an opportunity
# for a cache manager to set response headers.
self.ZCacheable_set(None)
data=3Dself.data
if type(data) is type(''): return data
while data is not None:
RESPONSE.write(data.data)
data=3Ddata.next
return ''
def view_image_or_file(self, URL1):
"""
The default view of the contents of the File or Image.
"""
raise 'Redirect', URL1
# private
update_data__roles__=3D()
def update_data(self, data, content_type=3DNone, size=3DNone):
if content_type is not None: self.content_type=3Dcontent_type
if size is None: size=3Dlen(data)
self.size=3Dsize
self.data=3Ddata
self.ZCacheable_invalidate()
self.http__refreshEtag()
def manage_edit(self, title, content_type, precondition=3D'', =
REQUEST=3DNone):
"""
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=3Dstr(title)
self.content_type=3Dstr(content_type)
if precondition: self.precondition=3Dstr(precondition)
elif self.precondition: del self.precondition
self.ZCacheable_invalidate()
if REQUEST:
message=3D"Saved changes."
return =
self.manage_main(self,REQUEST,manage_tabs_message=3Dmessage)
def manage_upload(self,file=3D'',REQUEST=3DNone):
"""
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 =3D self._read_data(file)
content_type=3Dself._get_content_type(file, data, self.__name__,
'application/octet-stream')
self.update_data(data, content_type, size)
if REQUEST:
message=3D"Saved changes."
return =
self.manage_main(self,REQUEST,manage_tabs_message=3Dmessage)
=20
def _get_content_type(self, file, body, id, content_type=3DNone):
headers=3Dgetattr(file, 'headers', None)
if headers and headers.has_key('content-type'):
content_type=3Dheaders['content-type']
else:
if type(body) is not type(''): body=3Dbody.data
content_type, enc=3Dguess_content_type(
getattr(file, 'filename',id), body, content_type)
return content_type
def _read_data(self, file):
=20
n=3D1 << 16
=20
if type(file) is StringType:
size=3Dlen(file)
if size < n: return file, size
return Pdata(file), size
if hasattr(file, '__class__') and file.__class__ is Pdata:
size=3Dlen(file)
return file, size
seek=3Dfile.seek
read=3Dfile.read
=20
seek(0,2)
size=3Dend=3Dfile.tell()
if size <=3D 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.
get_transaction().commit(1)
=20
jar=3Dself._p_jar
=20
if 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=3DNone
while end > 0:
pos=3Dend-n
if pos < n: pos=3D0 # we always want at least n bytes
seek(pos)
data=3DPdata(read(end-pos))
=20
# Woooop Woooop Woooop! This is a trick.
# We stuff the data directly into our jar to reduce the
# number of updates necessary.
data._p_jar=3Djar
# This is needed and has side benefit of getting
# the thing registered:
data.next=3Dnext
=20
# Now make it get saved in a sub-transaction!
get_transaction().commit(1)
# Now make it a ghost to free the memory. We
# don't need it anymore!
data._p_changed=3DNone
=20
next=3Ddata
end=3Dpos
=20
return next, size
def PUT(self, REQUEST, RESPONSE):
"""Handle HTTP PUT requests"""
self.dav__init(REQUEST, RESPONSE)
self.dav__simpleifhandler(REQUEST, RESPONSE, refresh=3D1)
type=3DREQUEST.get_header('content-type', None)
file=3DREQUEST['BODYFILE']
=20
data, size =3D self._read_data(file)
content_type=3Dself._get_content_type(file, data, self.__name__,
type or self.content_type)
self.update_data(data, content_type, size)
RESPONSE.setStatus(204)
return RESPONSE
=20
def get_size(self):
"""Get the size of a file or image.
Returns the size of the file or image.
"""
size=3Dself.size
if size is None: size=3Dlen(self.data)
return size
# deprecated; use get_size!
getSize=3Dget_size
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
=20
manage_FTPget=3Dindex_html
manage_addImageForm=3DDTMLFile('dtml/imageAdd',globals(),
Kind=3D'Image',kind=3D'image')
def manage_addImage(self, id, file, title=3D'', precondition=3D'', =
content_type=3D'',
REQUEST=3DNone):
"""
Add a new Image object.
Creates a new Image object 'id' with the contents of 'file'.
"""
id=3Dstr(id)
title=3Dstr(title)
content_type=3Dstr(content_type)
precondition=3Dstr(precondition)
id, title =3D cookId(id, title, file)
self=3Dself.this()
# First, we create the image without data:
self._setObject(id, Image(id,title,'',content_type, precondition))
=20
# Now we "upload" the data. By doing this in two steps, we
# can use a database trick to make the upload more efficient.
self._getOb(id).manage_upload(file)
if content_type:
self._getOb(id).content_type=3Dcontent_type
=20
if REQUEST is not None:
try: url=3Dself.DestinationURL()
except: url=3DREQUEST['URL1']
REQUEST.RESPONSE.redirect('%s/manage_main' % url)
return id
def getImageInfo(data):
data =3D str(data)
size =3D len(data)
height =3D -1
width =3D -1
content_type =3D ''
# handle GIFs =20
if (size >=3D 10) and data[:6] in ('GIF87a', 'GIF89a'):
# Check to see if content_type is correct
content_type =3D 'image/gif'
w, h =3D struct.unpack("<HH", data[6:10])
width =3D int(w)
height =3D int(h)
# See PNG v1.2 spec (http://www.cdrom.com/pub/png/spec/)
# Bytes 0-7 are below, 4-byte chunk length, then 'IHDR'
# and finally the 4-byte width, height
elif ((size >=3D 24) and (data[:8] =3D=3D '\211PNG\r\n\032\n')
and (data[12:16] =3D=3D 'IHDR')):
content_type =3D 'image/png'
w, h =3D struct.unpack(">LL", data[16:24])
width =3D int(w)
height =3D int(h)
=20
# Maybe this is for an older PNG version.
elif (size >=3D 16) and (data[:8] =3D=3D '\211PNG\r\n\032\n'):
# Check to see if we have the right content type
content_type =3D 'image/png'
w, h =3D struct.unpack(">LL", data[8:16])
width =3D int(w)
height =3D int(h)
# handle JPEGs
elif (size >=3D 2) and (data[:2] =3D=3D '\377\330'):
content_type =3D 'image/jpeg'
jpeg =3D StringIO(data)
jpeg.read(2)
b =3D jpeg.read(1)
try:
while (b and ord(b) !=3D 0xDA):
while (ord(b) !=3D 0xFF): b =3D jpeg.read(1)
while (ord(b) =3D=3D 0xFF): b =3D jpeg.read(1)
if (ord(b) >=3D 0xC0 and ord(b) <=3D 0xC3):
jpeg.read(3)
h, w =3D struct.unpack(">HH", jpeg.read(4))
break
else:
jpeg.read(int(struct.unpack(">H", =
jpeg.read(2))[0])-2)
b =3D jpeg.read(1)
width =3D int(w)
height =3D int(h)
except: pass
return content_type, width, height
class Image(File):
"""Image objects can be GIF, PNG or JPEG and have the same methods
as File objects. Images also have a string representation that
renders an HTML 'IMG' tag.
"""
__implements__ =3D (WriteLockInterface,)
meta_type=3D'Image'
=20
height=3D''
width=3D''
__ac_permissions__=3D(
('View management screens',
('manage', 'manage_main',)),
('Change Images and Files',
('manage_edit','manage_upload','PUT')),
('View',
('index_html', 'tag', 'view_image_or_file', 'get_size',
'getContentType', '')),
('FTP access',
('manage_FTPstat','manage_FTPget','manage_FTPlist')),
('Delete objects',
('DELETE',)),
)
_properties=3D({'id':'title', 'type': 'string'},
{'id':'content_type', 'type':'string'},
{'id':'height', 'type':'string'},
{'id':'width', 'type':'string'},
)
manage_options=3D(
({'label':'Edit', 'action':'manage_main',
'help':('OFSP','Image_Edit.stx')},
{'label':'View', 'action':'view_image_or_file',
'help':('OFSP','Image_View.stx')},)
+ PropertyManager.manage_options
+ RoleManager.manage_options
+ Item_w__name__.manage_options
+ Cacheable.manage_options
)
manage_editForm =3DDTMLFile('dtml/imageEdit',globals(),
Kind=3D'Image',kind=3D'image')
view_image_or_file =3DDTMLFile('dtml/imageView',globals())
manage_editForm._setName('manage_editForm')
manage=3Dmanage_main=3Dmanage_editForm
manage_uploadForm=3Dmanage_editForm
=20
# private
update_data__roles__=3D()
def update_data(self, data, content_type=3DNone, size=3DNone):
if size is None: size=3Dlen(data)
self.size=3Dsize
self.data=3Ddata
ct, width, height =3D getImageInfo(data)
if ct:
content_type =3D ct
if width >=3D 0 and height >=3D 0:
self.width =3D width
self.height =3D height
# Now we should have the correct content type, or still None
if content_type is not None: self.content_type =3D content_type
self.ZCacheable_invalidate()
def __str__(self):
return self.tag()
def tag(self, height=3DNone, width=3DNone, alt=3DNone,
scale=3D0, xscale=3D0, yscale=3D0, css_class=3DNone, =
**args):
"""
Generate an HTML IMG tag for this image, with customization.
Arguments to self.tag() can be any valid attributes of an IMG =
tag.
'src' will always be an absolute pathname, to prevent redundant
downloading of images. Defaults are applied intelligently for
'height', 'width', and 'alt'. If specified, the 'scale', =
'xscale',
and 'yscale' keyword arguments will be used to automatically =
adjust
the output height and width values of the image tag.
Since 'class' is a Python reserved word, it cannot be passed in
directly in keyword arguments which is a problem if you are
trying to use 'tag()' to include a CSS class. The tag() method
will accept a 'css_class' argument that will be converted to
'class' in the output tag to work around this.
"""
if height is None: height=3Dself.height
if width is None: width=3Dself.width
# Auto-scaling support
xdelta =3D xscale or scale
ydelta =3D yscale or scale
if xdelta and width:
width =3D str(int(round(int(width) * xdelta)))
if ydelta and height:
height =3D str(int(round(int(height) * ydelta)))
result=3D'<img src=3D"%s"' % (self.absolute_url())
if alt is None:
alt=3Dgetattr(self, 'title', '')
result =3D '%s alt=3D"%s"' % (result, alt)
if height:
result =3D '%s height=3D"%s"' % (result, height)
if width:
result =3D '%s width=3D"%s"' % (result, width)
if not 'border' in map(string.lower, args.keys()):
result =3D '%s border=3D"0"' % result
if css_class is not None:
result =3D '%s class=3D"%s"' % (result, css_class)
for key in args.keys():
value =3D args.get(key)
result =3D '%s %s=3D"%s"' % (result, key, value)
return '%s />' % result
def cookId(id, title, file):
if not id and hasattr(file,'filename'):
filename=3Dfile.filename
title=3Dtitle or filename
id=3Dfilename[max(string.rfind(filename, '/'),
string.rfind(filename, '\\'),
string.rfind(filename, ':'),
)+1:] =20
return id, title
class Pdata(Persistent, Implicit):
# Wrapper for possibly large data
next=3DNone
=20
def __init__(self, data):
self.data=3Ddata
def __getslice__(self, i, j):
return self.data[i:j]
def __len__(self):
data =3D str(self)
return len(data)
def __str__(self):
next=3Dself.next
if next is None: return self.data
r=3D[self.data]
while next is not None:
self=3Dnext
r.append(self.data)
next=3Dself.next
=20
return string.join(r,'')
------=_NextPart_000_0047_01C1982C.A9E2F190
Content-Type: application/octet-stream;
name="HTTPRangeSupport.py"
Content-Transfer-Encoding: quoted-printable
Content-Disposition: attachment;
filename="HTTPRangeSupport.py"
#########################################################################=
#####
#=20
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#=20
# Copyright (c) Digital Creations. All rights reserved.
#=20
# This license has been certified as Open Source(tm).
#=20
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#=20
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#=20
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#=20
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#=20
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#=20
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#=20
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#=20
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#=20
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#=20
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#=20
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#=20
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#=20
#=20
# Disclaimer
#=20
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#=20
#=20
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#=20
#########################################################################=
#####
"""HTTP Range support utilities.
The RFC 2616 specification defines the 'Range' and 'If-Range' headers =
for
enabeling partial download of published resources. This module provides =
a
flag-interface and some support functions for implementing this =
functionality.
For an implementation example, see the File class in OFS/Image.py.
"""
__version__=3D'$Revision'[11:-2]
import re, string, sys
import Interface
WHITESPACE =3D re.compile('\s*', re.MULTILINE)
def parseRange(header):
"""RFC 2616 (HTTP 1.1) Range header parsing.
Convert a range header to a list of slice indexes, returned as =
(start, end)
tuples. If no end was given, end is None. Note that the RFC =
specifies the
end offset to be inclusive, we return python convention indexes, =
where the
end is exclusive. Syntactically incorrect headers are to be ignored, =
so if
we encounter one we return None.
"""
ranges =3D []
add =3D ranges.append
# First, clean out *all* whitespace. This is slightly more tolerant
# than the spec asks for, but hey, it makes this function much =
easier.
header =3D WHITESPACE.sub('', header)
# A range header only can specify a byte range
try: spec, sets =3D string.split(header, '=3D')
except ValueError: return None
if spec !=3D 'bytes':
return None
# The sets are delimited by commas.
sets =3D string.split(sets, ',')
# Filter out empty values, things like ',,' are allowed in the spec
sets =3D filter(None, sets)
# We need at least one set
if not sets:
return None
for set in sets:
try: start, end =3D string.split(set, '-')
except ValueError: return None
# Catch empty sets
if not start and not end:
return None
# Convert to integers or None (which will raise errors if
# non-integers were used (which is what we want)).
try:
if start =3D=3D '': start =3D None
else: start =3D int(start)
if end =3D=3D '': end =3D None
else: end =3D int(end)
except ValueError:
return None
# Special case: No start means the suffix format was used, which
# means the end value is actually a negative start value.
# Convert this by making it absolute.
# A -0 range is converted to sys.maxint, which will result in a
# Unsatisfiable response if no other ranges can by satisfied =
either.
if start is None:
start, end =3D -end, None
if not start:
start =3D sys.maxint
elif end is not None:
end =3D end + 1 # Make the end of the range exclusive
if end is not None and end <=3D start:
return None
# And store
add((start, end))
return ranges
def optimizeRanges(ranges, size):
"""Optimize Range sets, given those sets and the length of the =
resource.
Optimisation is done by first expanding relative start values and =
open
ends, then sorting and combining overlapping ranges. We also remove
unsatisfiable ranges (where the start lies beyond the size of the =
resource).
"""
expanded =3D []
add =3D expanded.append
for start, end in ranges:
if start < 0:
start =3D size + start
end =3D end or size
if end > size: end =3D size
# Only use satisfiable ranges
if start < size:
add((start, end))
ranges =3D expanded
ranges.sort()
ranges.reverse()
optimized =3D []
add =3D optimized.append
start, end =3D ranges.pop()
=20
while ranges:
nextstart, nextend =3D ranges.pop()
# If the next range overlaps
if nextstart < end:
# If it falls within the current range, discard
if nextend <=3D end:
continue
=20
# Overlap, adjust end
end =3D nextend
else:
add((start, end))
start, end =3D nextstart, nextend
# Add the remaining optimized range
add((start, end))
=20
return optimized
class HTTPRangeInterface(Interface.Base):
"""Objects implementing this Interface support the HTTP Range =
header.
Objects implementing support for the HTTP Range header will return =
partial
content as specified in RFC 2616. Note that the'If-Range' header =
must
either be implemented correctly or result in a normal '200 OK' =
response at
all times.
This interface specifies no methods, as this functionality can =
either be
implemented in the index_html or __call__ methods of a published =
object.
"""
------=_NextPart_000_0047_01C1982C.A9E2F190--