Back to index

plone3  3.1.7
Marshall.py
Go to the documentation of this file.
00001 import re
00002 from types import ListType, TupleType
00003 from cStringIO import StringIO
00004 from rfc822 import Message
00005 
00006 from zope.contenttype import guess_content_type
00007 
00008 from AccessControl import ClassSecurityInfo
00009 from Acquisition import aq_base
00010 from Globals import InitializeClass
00011 from OFS.Image import File
00012 from Products.Archetypes.Field import TextField, FileField
00013 from Products.Archetypes.interfaces.marshall import IMarshall
00014 from Products.Archetypes.interfaces.layer import ILayer
00015 from Products.Archetypes.interfaces.base import IBaseUnit
00016 from Products.Archetypes.debug import log
00017 from Products.Archetypes.utils import shasattr
00018 from Products.Archetypes.utils import mapply
00019 
00020 sample_data = r"""title: a title
00021 content-type: text/plain
00022 keywords: foo
00023 mixedCase: a MiXeD case keyword
00024 
00025 This is the body.
00026 """
00027 
00028 class NonLoweringMessage(Message):
00029     """A RFC 822 Message class that doesn't lower header names
00030     
00031     IMPORTANT: Only a small subset of the available methods aren't lowering the
00032                header names!
00033     """
00034 
00035     def isheader(self, line):
00036         """Determine whether a given line is a legal header.
00037         """
00038         i = line.find(':')
00039         if i > 0:
00040             return line[:i]
00041             #return line[:i].lower()
00042         else:
00043             return None        
00044 
00045     def getheader(self, name, default=None):
00046         """Get the header value for a name.
00047         """
00048         try:
00049             return self.dict[name]
00050             # return self.dict[name.lower()]
00051         except KeyError:
00052             return default
00053     get = getheader  
00054 
00055 
00056 
00057 def formatRFC822Headers( headers ):
00058 
00059     """ Convert the key-value pairs in 'headers' to valid RFC822-style
00060         headers, including adding leading whitespace to elements which
00061         contain newlines in order to preserve continuation-line semantics.
00062 
00063         code based on old cmf1.4 impl 
00064     """
00065     munged = []
00066     linesplit = re.compile( r'[\n\r]+?' )
00067 
00068     for key, value in headers:
00069 
00070         vallines = linesplit.split( value )
00071         munged.append( '%s: %s' % ( key, '\r\n  '.join( vallines ) ) )
00072 
00073     return '\r\n'.join( munged )
00074 
00075 def parseRFC822(body):
00076     """Parse a RFC 822 (email) style string
00077     
00078     The code is mostly based on CMFDefault.utils.parseHeadersBody. It doesn't
00079     capitalize the headers as the CMF function.
00080     
00081     >>> headers, body = parseRFC822(sample_data)
00082     >>> keys = headers.keys(); keys.sort()
00083     >>> for key in keys:
00084     ...     key, headers[key]
00085     ('content-type', 'text/plain')
00086     ('keywords', 'foo')
00087     ('mixedCase', 'a MiXeD case keyword')
00088     ('title', 'a title')
00089     
00090     >>> print body
00091     This is the body.
00092     <BLANKLINE>
00093     """
00094     buffer = StringIO(body)
00095     message = NonLoweringMessage(buffer)
00096     headers = {}
00097 
00098     for key in message.keys():
00099         headers[key] = '\n'.join(message.getheaders(key))
00100 
00101     return headers, buffer.read()
00102 
00103 class Marshaller:
00104     __implements__ = IMarshall, ILayer
00105 
00106     security = ClassSecurityInfo()
00107     security.declareObjectPrivate()
00108     security.setDefaultAccess('deny')
00109 
00110     def __init__(self, demarshall_hook=None, marshall_hook=None):
00111         self.demarshall_hook = demarshall_hook
00112         self.marshall_hook = marshall_hook
00113 
00114     def initializeInstance(self, instance, item=None, container=None):
00115         dm_hook = None
00116         m_hook = None
00117         if self.demarshall_hook is not None:
00118             dm_hook = getattr(instance, self.demarshall_hook, None)
00119         if self.marshall_hook is not None:
00120             m_hook = getattr(instance, self.marshall_hook, None)
00121         instance.demarshall_hook = dm_hook
00122         instance.marshall_hook = m_hook
00123 
00124     def cleanupInstance(self, instance, item=None, container=None):
00125         if hasattr(aq_base(instance), 'demarshall_hook'):
00126             delattr(instance, 'demarshall_hook')
00127         if hasattr(aq_base(instance), 'marshall_hook'):
00128             delattr(instance, 'marshall_hook')
00129 
00130     def demarshall(self, instance, data, **kwargs):
00131         raise NotImplemented
00132 
00133     def marshall(self, instance, **kwargs):
00134         raise NotImplemented
00135 
00136     def initializeField(self, instance, field):
00137         pass
00138 
00139     def cleanupField(self, instance, field):
00140         pass
00141 
00142 InitializeClass(Marshaller)
00143 
00144 class PrimaryFieldMarshaller(Marshaller):
00145 
00146     security = ClassSecurityInfo()
00147     security.declareObjectPrivate()
00148     security.setDefaultAccess('deny')
00149 
00150     def demarshall(self, instance, data, **kwargs):
00151         p = instance.getPrimaryField()
00152         file = kwargs.get('file')
00153         # TODO Hardcoding field types is bad. :(
00154         if isinstance(p, (FileField, TextField)) and file:
00155             data = file
00156             del kwargs['file']
00157         mutator = p.getMutator(instance)
00158         mutator(data, **kwargs)
00159 
00160     def marshall(self, instance, **kwargs):
00161         p = instance.getPrimaryField()
00162         if not p:
00163             raise TypeError, 'Primary Field could not be found.'
00164         data = p and instance[p.getName()] or ''
00165         content_type = length = None
00166         # Gather/Guess content type
00167         if IBaseUnit.isImplementedBy(data):
00168             content_type = data.getContentType()
00169             length = data.get_size()
00170             data   = data.getRaw()
00171         elif isinstance(data, File):
00172             content_type = data.content_type
00173             length = data.get_size()
00174             data = data.data
00175         else:
00176             log('WARNING: PrimaryFieldMarshaller(%r): '
00177                 'field %r does not return a IBaseUnit '
00178                 'instance.' % (instance, p.getName()))
00179             if hasattr(p, 'getContentType'):
00180                 content_type = p.getContentType(instance) or 'text/plain'
00181             else:
00182                 content_type = (data and guess_content_type(data)
00183                                 or 'text/plain')
00184 
00185             # DM 2004-12-01: "FileField"s represent a major field class
00186             #  that does not use "IBaseUnit" yet.
00187             #  Ensure, the used "File" objects get the correct length.
00188             if hasattr(p, 'get_size'):
00189                 length = p.get_size(instance)
00190             else:
00191                 # DM: this almost surely is stupid!
00192                 length = len(data)
00193 
00194             # ObjectField without IBaseUnit?
00195             if shasattr(data, 'data'):
00196                 data = data.data
00197             else:
00198                 data = str(data)
00199                 # DM 2004-12-01: recompute 'length' as we now know it
00200                 # definitely
00201                 length = len(data)
00202 
00203         return (content_type, length, data)
00204 
00205 InitializeClass(PrimaryFieldMarshaller)
00206 
00207 class RFC822Marshaller(Marshaller):
00208 
00209     security = ClassSecurityInfo()
00210     security.declareObjectPrivate()
00211     security.setDefaultAccess('deny')
00212 
00213     def demarshall(self, instance, data, **kwargs):
00214         # We don't want to pass file forward.
00215         if kwargs.has_key('file'):
00216             if not data:
00217                 # TODO Yuck! Shouldn't read the whole file, never.
00218                 # OTOH, if you care about large files, you should be
00219                 # using the PrimaryFieldMarshaller or something
00220                 # similar.
00221                 data = kwargs['file'].read()
00222             del kwargs['file']
00223         headers, body = parseRFC822(data)
00224         for k, v in headers.items():
00225             if v.strip() == 'None':
00226                 v = None
00227             field = instance.getField(k)
00228             if field is not None:
00229                 mutator = field.getMutator(instance)
00230                 if mutator is not None:
00231                     mutator(v)
00232         content_type = headers.get('Content-Type')
00233         if not kwargs.get('mimetype', None):
00234             kwargs.update({'mimetype': content_type})
00235         p = instance.getPrimaryField()
00236         if p is not None:
00237             mutator = p.getMutator(instance)
00238             if mutator is not None:
00239                 mutator(body, **kwargs)
00240 
00241     def marshall(self, instance, **kwargs):
00242         p = instance.getPrimaryField()
00243         body = p and instance[p.getName()] or ''
00244         pname = p and p.getName() or None
00245         content_type = length = None
00246         # Gather/Guess content type
00247         if IBaseUnit.isImplementedBy(body):
00248             content_type = str(body.getContentType())
00249             body   = body.getRaw()
00250         else:
00251             if p and hasattr(p, 'getContentType'):
00252                 content_type = p.getContentType(instance) or 'text/plain'
00253             else:
00254                 content_type = body and guess_content_type(body) or 'text/plain'
00255 
00256         headers = []
00257         fields = [f for f in instance.Schema().fields()
00258                   if f.getName() != pname]
00259         for field in fields:
00260             if field.type in ('file', 'image', 'object'):
00261                 continue
00262             accessor = field.getEditAccessor(instance)
00263             if not accessor:
00264                 continue
00265             kw = {'raw':1, 'field': field.__name__}
00266             value = mapply(accessor, **kw)
00267             if type(value) in [ListType, TupleType]:
00268                 value = '\n'.join([str(v) for v in value])
00269             headers.append((field.getName(), str(value)))
00270 
00271         headers.append(('Content-Type', content_type or 'text/plain'))
00272 
00273         header = formatRFC822Headers(headers)
00274         data = '%s\n\n%s' % (header, body)
00275         length = len(data)
00276 
00277         return (content_type, length, data)
00278 
00279 InitializeClass(RFC822Marshaller)
00280 
00281 __all__ = ('PrimaryFieldMarshaller', 'RFC822Marshaller', )