Back to index

plone3  3.1.7
rfc822header.py
Go to the documentation of this file.
00001 # Copyright (c) 2002-2006, Benjamin Saller <bcsaller@ideasuite.com>, and 
00002 #                      the respective authors.
00003 # All rights reserved.
00004 #
00005 # Redistribution and use in source and binary forms, with or without
00006 # modification, are permitted provided that the following conditions are
00007 # met:
00008 # 
00009 #     * Redistributions of source code must retain the above copyright
00010 #       notice, this list of conditions and the following disclaimer. 
00011 #     * Redistributions in binary form must reproduce the above
00012 #       copyright notice, this list of conditions and the following disclaimer
00013 #       in the documentation and/or other materials provided with the
00014 #       distribution. 
00015 #     * Neither the name of Archetypes nor the names of its contributors
00016 #       may be used to endorse or promote products derived from this software
00017 #       without specific prior written permission. 
00018 # 
00019 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
00020 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
00021 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
00022 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
00023 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
00024 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
00025 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00026 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
00027 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00028 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
00029 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00030 
00031 import re
00032 from types import ListType, TupleType
00033 from cStringIO import StringIO
00034 from rfc822 import Message
00035 
00036 from AccessControl import ClassSecurityInfo
00037 from Acquisition import aq_base
00038 from Globals import InitializeClass
00039 from OFS.Image import File
00040 from Products.Archetypes.Field import TextField, FileField
00041 from Products.Archetypes.interfaces.marshall import IMarshall
00042 from Products.Archetypes.interfaces.layer import ILayer
00043 from Products.Archetypes.interfaces.base import IBaseUnit
00044 from Products.Archetypes.utils import shasattr
00045 from Products.Archetypes.utils import mapply
00046 
00047 try:
00048     from zope.contenttype import guess_content_type
00049 except ImportError: # BBB: Zope < 2.10
00050     try:
00051         from zope.app.content_types import guess_content_type
00052     except ImportError: # BBB: Zope < 2.9
00053         from OFS.content_types import guess_content_type
00054 
00055 from base import Marshaller
00056 
00057 sample_data = r"""title: a title
00058 content-type: text/plain
00059 keywords: foo
00060 mixedCase: a MiXeD case keyword
00061 
00062 This is the body.
00063 """
00064 
00065 class NonLoweringMessage(Message):
00066     """A RFC 822 Message class that doesn't lower header names
00067     
00068     IMPORTANT: Only a small subset of the available methods aren't lowering the
00069                header names!
00070     """
00071 
00072     def isheader(self, line):
00073         """Determine whether a given line is a legal header.
00074         """
00075         i = line.find(':')
00076         if i > 0:
00077             return line[:i]
00078             #return line[:i].lower()
00079         else:
00080             return None        
00081 
00082     def getheader(self, name, default=None):
00083         """Get the header value for a name.
00084         """
00085         try:
00086             return self.dict[name]
00087             # return self.dict[name.lower()]
00088         except KeyError:
00089             return default
00090     get = getheader  
00091 
00092 
00093 
00094 def formatRFC822Headers( headers ):
00095 
00096     """ Convert the key-value pairs in 'headers' to valid RFC822-style
00097         headers, including adding leading whitespace to elements which
00098         contain newlines in order to preserve continuation-line semantics.
00099 
00100         code based on old cmf1.4 impl 
00101     """
00102     munged = []
00103     linesplit = re.compile( r'[\n\r]+?' )
00104 
00105     for key, value in headers:
00106 
00107         vallines = linesplit.split( value )
00108         munged.append( '%s: %s' % ( key, '\r\n  '.join( vallines ) ) )
00109 
00110     return '\r\n'.join( munged )
00111 
00112 def parseRFC822(body):
00113     """Parse a RFC 822 (email) style string
00114     
00115     The code is mostly based on CMFDefault.utils.parseHeadersBody. It doesn't
00116     capitalize the headers as the CMF function.
00117     
00118     >>> headers, body = parseRFC822(sample_data)
00119     >>> keys = headers.keys(); keys.sort()
00120     >>> for key in keys:
00121     ...     key, headers[key]
00122     
00123     
00124     ('content-type', 'text/plain')
00125     ('keywords', 'foo')
00126     ('mixedCase', 'a MiXeD case keyword')
00127     ('title', 'a title')
00128     
00129     >>> print body
00130     This is the body.
00131     <BLANKLINE>
00132     """
00133     buffer = StringIO(body)
00134     message = NonLoweringMessage(buffer)
00135     headers = {}
00136 
00137     for key in message.keys():
00138         headers[key] = '\n'.join(message.getheaders(key))
00139 
00140     return headers, buffer.read() 
00141 
00142 class RFC822Marshaller(Marshaller):
00143 
00144     security = ClassSecurityInfo()
00145     security.declareObjectPrivate()
00146     security.setDefaultAccess('deny')
00147 
00148     def demarshall(self, instance, data, **kwargs):
00149         # We don't want to pass file forward.
00150         if kwargs.has_key('file'):
00151             if not data:
00152                 # XXX Yuck! Shouldn't read the whole file, never.
00153                 # OTOH, if you care about large files, you should be
00154                 # using the PrimaryFieldMarshaller or something
00155                 # similar.
00156                 data = kwargs['file'].read()
00157             del kwargs['file']
00158         headers, body = parseRFC822(data)
00159         for k, v in headers.items():
00160             if v.strip() == 'None':
00161                 v = None
00162             field = instance.getField(k)
00163             if field is not None:
00164                 mutator = field.getMutator(instance)
00165                 if mutator is not None:
00166                     mutator(v)
00167         content_type = headers.get('Content-Type')
00168         if not kwargs.get('mimetype', None):
00169             kwargs.update({'mimetype': content_type})
00170         p = instance.getPrimaryField()
00171         if p is not None:
00172             mutator = p.getMutator(instance)
00173             if mutator is not None:
00174                 mutator(body, **kwargs)
00175 
00176     def marshall(self, instance, **kwargs):
00177         p = instance.getPrimaryField()
00178         body = p and instance[p.getName()] or ''
00179         pname = p and p.getName() or None
00180         content_type = length = None
00181         # Gather/Guess content type
00182         if IBaseUnit.isImplementedBy(body):
00183             content_type = str(body.getContentType())
00184             body   = body.getRaw()
00185         else:
00186             if p and hasattr(p, 'getContentType'):
00187                 content_type = p.getContentType(instance) or 'text/plain'
00188             else:
00189                 content_type = body and guess_content_type(body) or 'text/plain'
00190 
00191         headers = []
00192         fields = [f for f in instance.Schema().fields()
00193                   if f.getName() != pname]
00194         for field in fields:
00195             if field.type in ('file', 'image', 'object'):
00196                 continue
00197             accessor = field.getEditAccessor(instance)
00198             if not accessor:
00199                 continue
00200             kw = {'raw':1, 'field': field.__name__}
00201             value = mapply(accessor, **kw)
00202             if type(value) in [ListType, TupleType]:
00203                 value = '\n'.join([str(v) for v in value])
00204             headers.append((field.getName(), str(value)))
00205 
00206         headers.append(('Content-Type', content_type or 'text/plain'))
00207 
00208         header = formatRFC822Headers(headers)
00209         data = '%s\n\n%s' % (header, body)
00210         length = len(data)
00211 
00212         return (content_type, length, data)
00213 
00214 InitializeClass(RFC822Marshaller)