예제 #1
0
class Path(Item):
    """
    Trivial Item class for testing upgrading.
    """
    schemaVersion = 2
    typeName = 'test_upgrade_path'
    thePath = path()
예제 #2
0
class Character(item.Item, _ArticleMixin):
    """A player character or NPC"""
    schemaVersion = 1
    name = A.text(allowNone=False)
    path = A.path(allowNone=False, relative=True)
    top = A.point4decimal()
    left = A.point4decimal()
    scale = A.point4decimal(allowNone=False)
예제 #3
0
class SubStore(Item):

    schemaVersion = 1
    typeName = 'substore'

    storepath = path()
    substore = inmemory()

    implements(IPowerupIndirector)

    def createNew(cls, store, pathSegments):
        """
        Create a new SubStore, allocating a new file space for it.
        """
        storepath = store.newDirectory(*pathSegments)
        self = cls(store=store, storepath=storepath)
        self.open()
        self.close()
        return self

    createNew = classmethod(createNew)

    def close(self):
        self.substore.close()
        del self.substore._openSubStore
        del self.substore

    def open(self, debug=False):
        if hasattr(self, 'substore'):
            return self.substore
        else:
            s = self.substore = Store(self.storepath.path,
                                      parent=self.store,
                                      idInParent=self.storeID,
                                      debug=debug)
            s._openSubStore = self  # don't fall out of cache as long as the
            # store is alive!
            return s

    def __conform__(self, interface):
        """
        I adapt my store object to whatever interface I am adapted to.  This
        allows for avatar adaptation in L{axiom.userbase} to work properly
        without having to know explicitly that all 'avatars' objects are
        SubStore instances, since it is valid to have non-SubStore avatars,
        which are simply adaptable to the cred interfaces they represent.
        """
        ifa = interface(self.open(debug=self.store.debug), None)
        return ifa

    def indirect(self, interface):
        """
        Like __conform__, I adapt my store to whatever interface I am asked to
        produce a powerup for.  This allows for app stores to be installed as
        powerups for their site stores directly, rather than having an
        additional item type for each interface that we might wish to adapt to.
        """
        return interface(self)
예제 #4
0
class UniversalEndpointService(item.Item):
    """
    """
    typeName = 'mantissa_q2q_service'
    schemaVersion = 1

    q2qPortNumber = attributes.integer(default=8787)
    inboundTCPPortNumber = attributes.integer(default=None)
    publicIP = attributes.integer(default=None)
    udpEnabled = attributes.integer(default=False)
    certificatePath = attributes.path(default=None)

    _svcInst = attributes.inmemory()

    def __init__(self, **kw):
        super(UniversalEndpointService, self).__init__(**kw)
        if self.certificatePath is None:
            self.certificatePath = self.store.newDirectory(
                str(self.storeID), 'certificates')
        if not self.certificatePath.exists():
            self.certificatePath.makedirs()

    def activate(self):
        self._svcInst = None

    def _makeService(self):
        self._svcInst = q2qclient.ClientQ2QService(
            self.certificatePath.path,
            publicIP=self.publicIP,
            inboundTCPPortnum=self.inboundTCPPortNumber,
            udpEnabled=self.udpEnabled,
        )

    def _getService(self):
        if self._svcInst is None:
            self._makeService()
        return self._svcInst

    def installOn(self, other):
        other.powerUp(self, ixmantissa.IQ2QService)

    def listenQ2Q(self, fromAddress, protocolsToFactories, serverDescription):
        return self._getService().listenQ2Q(fromAddress, protocolsToFactories,
                                            serverDescription)

    def connectQ2Q(self,
                   fromAddress,
                   toAddress,
                   protocolName,
                   protocolFactory,
                   usePrivateCertificate=None,
                   fakeFromDomain=None,
                   chooser=None):
        return self._getService().connectQ2Q(fromAddress, toAddress,
                                             protocolName, protocolFactory,
                                             usePrivateCertificate,
                                             fakeFromDomain, chooser)
예제 #5
0
class Image(Item):
    typeName = 'quotient_image'
    schemaVersion = 2

    part = attributes.reference()
    thumbnailPath = attributes.path()
    mimeType = attributes.text()
    imageSet = attributes.reference()

    message = attributes.reference()
예제 #6
0
class File(item.Item):
    typeName = 'quotient_file'
    schemaVersion = 1

    type = attributes.text(allowNone=False)
    body = attributes.path(allowNone=False)
    name = attributes.text(allowNone=False)

    message = attributes.reference()
    cabinet = attributes.reference(allowNone=False)
예제 #7
0
파일: store.py 프로젝트: z3n71n3l/entropy
class ImmutableObject(Item):
    """
    An immutable object.

    Immutable objects are addressed by content hash, and consist of the object
    data as a binary blob, and object key/value metadata pairs.
    """
    implements(IContentObject)

    hash = text(allowNone=False)
    contentDigest = text(allowNone=False, indexed=True)
    content = path(allowNone=False)
    contentType = text(allowNone=False)
    created = timestamp(allowNone=False, defaultFactory=lambda: Time())

    _deferToThreadPool = inmemory()

    def activate(self):
        self._deferToThreadPool = execute

    @property
    def metadata(self):
        return {}

    @property
    def objectId(self):
        return u'%s:%s' % (self.hash, self.contentDigest)

    def _getDigest(self):
        fp = self.content.open()
        try:
            h = getHash(self.hash)(fp.read())
            return unicode(h.hexdigest(), 'ascii')
        finally:
            fp.close()

    def verify(self):
        digest = self._getDigest()
        if self.contentDigest != digest:
            raise CorruptObject('expected: %r actual: %r' %
                                (self.contentDigest, digest))

    def getContent(self):
        return self._deferToThreadPool(self.content.getContent)
예제 #8
0
class PathTesterItem(Item):
    schemaVersion = 1
    typeName = 'test_path_thing'

    relpath = path()
    abspath = path(relative=False)
예제 #9
0
파일: port.py 프로젝트: rcarmo/divmod.org
            _listen = self._listen
        else:
            from twisted.internet import reactor
            _listen = reactor.listenSSL
        return _listen(
            self.portNumber,
            self.factory.getFactory(),
            self.getContextFactory(),
            interface=self.interface.encode('ascii'))

declareLegacyItem(
    typeName=normalize(qual(SSLPort)),
    schemaVersion=1,
    attributes=dict(
        portNumber=integer(),
        certificatePath=path(),
        factory=reference(),
        parent=inmemory(),
        _listen=inmemory(),
        listeningPort=inmemory()))

registerAttributeCopyingUpgrader(SSLPort, 1, 2)


class ListOptions(Options):
    """
    I{axiomatic port} subcommand for displaying the ports which are currently
    set up in a store.
    """
    longdesc = "Show the port/factory bindings in an Axiom store."
예제 #10
0
class SiteConfiguration(Item):
    """
    Configuration object for a Mantissa HTTP server.
    """
    powerupInterfaces = (ISiteURLGenerator, IProtocolFactoryFactory)
    implements(*powerupInterfaces)

    loginSystem = dependsOn(LoginSystem)

    # I don't really want this to have a default value at all, but an Item
    # which can't be instantiated with only a store parameter can't be used as
    # a siteRequirement in an Offering.  See #538 about offering configuration.
    # -exarkun
    hostname = text(
        doc="""
        The primary hostname by which this website will be accessible.  This
        will be superceded by a one-to-many relationship in the future,
        allowing a host to have multiple recognized hostnames.  See #2501.
        """, allowNone=False, default=u"localhost")

    httpLog = path(default=None)


    def _root(self, scheme, hostname, portObj, standardPort):
        # TODO - real unicode support (but punycode is so bad)
        if portObj is None:
            return None

        portNumber = portObj.portNumber
        port = portObj.listeningPort

        if hostname is None:
            hostname = self.hostname
        else:
            hostname = hostname.split(':')[0].encode('ascii')

        if portNumber == 0:
            if port is None:
                return None
            else:
                portNumber = port.getHost().port

        # At some future point, we may want to make pathsegs persistently
        # configurable - perhaps scheme and hostname as well - in order to
        # easily support reverse proxying configurations, particularly where
        # Mantissa is being "mounted" somewhere other than /.  See also rootURL
        # which has some overlap with this method (the difference being
        # universal vs absolute URLs - rootURL may want to call cleartextRoot
        # or encryptedRoot in the future).  See #417 and #2309.
        pathsegs = ['']
        if portNumber != standardPort:
            hostname = '%s:%d' % (hostname, portNumber)
        return URL(scheme, hostname, pathsegs)


    def _getPort(self, portType):
        return self.store.findFirst(
            portType, portType.factory == self, default=None)


    def cleartextRoot(self, hostname=None):
        """
        Return a string representing the HTTP URL which is at the root of this
        site.

        @param hostname: An optional unicode string which, if specified, will
            be used as the hostname in the resulting URL, regardless of the
            C{hostname} attribute of this item.
        """
        return self._root('http', hostname, self._getPort(TCPPort), 80)


    def encryptedRoot(self, hostname=None):
        """
        Return a string representing the HTTPS URL which is at the root of this
        site.

        @param hostname: An optional unicode string which, if specified, will
        be used as the hostname in the resulting URL, regardless of the
        C{hostname} attribute of this item.
        """
        return self._root('https', hostname, self._getPort(SSLPort), 443)


    def rootURL(self, request):
        """
        Return the URL for the root of this website which is appropriate to use
        in links generated in response to the given request.

        @type request: L{twisted.web.http.Request}
        @param request: The request which is being responded to.

        @rtype: L{URL}
        @return: The location at which the root of the resource hierarchy for
            this website is available.
        """
        host = request.getHeader('host') or self.hostname
        if ':' in host:
            host = host.split(':', 1)[0]
        if (host == self.hostname or
            host.startswith('www.') and host[len('www.'):] == self.hostname):
            return URL(scheme='', netloc='', pathsegs=[''])
        else:
            if request.isSecure():
                return self.encryptedRoot(self.hostname)
            else:
                return self.cleartextRoot(self.hostname)


    def getFactory(self):
        """
        Create an L{AxiomSite} which supports authenticated and anonymous
        access.
        """
        checkers = [self.loginSystem, AllowAnonymousAccess()]
        guardedRoot = PersistentSessionWrapper(
            self.store,
            Portal(self.loginSystem, checkers),
            domains=[self.hostname])
        unguardedRoot = UnguardedWrapper(self.store, guardedRoot)
        securingRoot = SecuringWrapper(self, unguardedRoot)
        logPath = None
        if self.httpLog is not None:
            logPath = self.httpLog.path
        return AxiomSite(self.store, securingRoot, logPath=logPath)
예제 #11
0
파일: port.py 프로젝트: jonathanj/mantissa
class SSLPort(PortMixin, Item):
    """
    An Axiom Service Item which will bind a TCP port to a protocol factory when
    it is started.
    """
    schemaVersion = 2

    portNumber = integer(doc="""
    The TCP port number on which to listen.
    """)

    interface = text(doc="""
    The hostname to bind to.
    """, default=u'')

    certificatePath = path(doc="""
    Name of the file containing the SSL certificate to use for this server.
    """)

    factory = reference(doc="""
    An Item with a C{getFactory} method which returns a Twisted protocol
    factory.
    """,
                        whenDeleted=reference.CASCADE)

    parent = inmemory(doc="""
    A reference to the parent service of this service, whenever there is a
    parent.
    """)

    _listen = inmemory(doc="""
    An optional reference to a callable implementing the same interface as
    L{IReactorTCP.listenTCP}.  If set, this will be used to bind a network
    port.  If not set, the reactor will be imported and its C{listenTCP} method
    will be used.
    """)

    listeningPort = inmemory(doc="""
    A reference to the L{IListeningPort} returned by C{self.listen} which is
    set whenever there there is one listening.
    """)

    def getContextFactory(self):
        if SSL is None:
            raise RuntimeError("No SSL support: you need to install OpenSSL.")
        cert = PrivateCertificate.loadPEM(self.certificatePath.open().read())
        certOpts = CertificateOptions(cert.privateKey.original,
                                      cert.original,
                                      requireCertificate=False,
                                      method=SSL.SSLv23_METHOD)
        return certOpts

    def listen(self):
        if self._listen is not None:
            _listen = self._listen
        else:
            from twisted.internet import reactor
            _listen = reactor.listenSSL
        return _listen(self.portNumber,
                       self.factory.getFactory(),
                       self.getContextFactory(),
                       interface=self.interface.encode('ascii'))
예제 #12
0
파일: port.py 프로젝트: jonathanj/mantissa
    def listen(self):
        if self._listen is not None:
            _listen = self._listen
        else:
            from twisted.internet import reactor
            _listen = reactor.listenSSL
        return _listen(self.portNumber,
                       self.factory.getFactory(),
                       self.getContextFactory(),
                       interface=self.interface.encode('ascii'))


declareLegacyItem(typeName=normalize(qual(SSLPort)),
                  schemaVersion=1,
                  attributes=dict(portNumber=integer(),
                                  certificatePath=path(),
                                  factory=reference(),
                                  parent=inmemory(),
                                  _listen=inmemory(),
                                  listeningPort=inmemory()))

registerAttributeCopyingUpgrader(SSLPort, 1, 2)


class StringEndpointPort(PortMixin, Item):
    """
    An Axiom Service Item which will listen on an endpoint described by a
    string when started.
    """
    description = text(doc="""
    String description of the endpoint to listen on.
예제 #13
0
class Path(Item):
    schemaVersion = 1
    typeName = 'test_upgrade_path'
    thePath = path()
예제 #14
0
class Part(item.Item):
    """
    Database resident representation of a MIME-part (including the top level
    part, the message itself).
    """
    implements(iquotient.IMessageData)

    typeName = 'quotient_mime_part'
    schemaVersion = 1

    parent = attributes.reference(
        "A reference to another Part object, or None for the top-level part.")
    message = attributes.reference(
        "A reference to the stored top-level L{xquotient.exmess.Message} "
        "object to which this part pertains.",
        reftype=exmess.Message,
        whenDeleted=attributes.reference.CASCADE)
    partID = attributes.integer(
        "A unique identifier for this Part within the context of its L{message}."
    )

    source = attributes.path(
        "The file which contains this part, MIME-encoded.")

    headersOffset = attributes.integer(
        "The byte offset within my source file where my headers begin.")
    headersLength = attributes.integer(
        "The length in bytes that all my headers consume within the source file."
    )
    bodyOffset = attributes.integer(
        "The byte offset within my source file where my body begins (4 bytes "
        "after where my headers end).")
    bodyLength = attributes.integer(
        "The length in bytes that my body consumes within the source file.")

    _partCounter = attributes.inmemory(
        "Temporary Part-ID factory function used to assign IDs to parts "
        "of this message.")
    _headers = attributes.inmemory(
        "Temporary storage for header data before this Part is added to "
        "a database.")
    _children = attributes.inmemory(
        "Temporary storage for child parts before this Part is added to "
        "a database.")

    def __init__(self, *a, **kw):
        super(Part, self).__init__(*a, **kw)
        log.msg(interface=iaxiom.IStatEvent, stat_mimePartsCreated=1)

    def addHeader(self, name, value):
        if self.store is not None:
            raise NotImplementedError(
                "Don't add headers to in-database messages - they aren't mutable [yet?]"
            )
        if not hasattr(self, '_headers'):
            self._headers = []

        self._headers.append(
            Header(name=name.decode('ascii', 'ignore').lower(),
                   value=value,
                   part=self,
                   message=self.message,
                   index=len(self._headers)))

    def walk(self, shallow=False):
        """
        @param shallow: return only immediate children?
        """
        # this depends on the order the parts are returned by the queries
        if shallow:
            return self._walkShallow()
        return self._walkDeep()

    def _walkDeep(self):
        yield self
        for child in self.store.query(Part, Part.parent == self):
            for grandchild in child.walk():
                yield grandchild

    def _walkShallow(self):
        return self.store.query(Part, Part.parent == self)

    def getSubPart(self, partID):
        return self.store.findUnique(
            Part, attributes.AND(Part.parent == self, Part.partID == partID))

    def getHeader(self, name):
        for hdr in self.getHeaders(name, _limit=1):
            return hdr.value
        raise equotient.NoSuchHeader(name)

    def getHeaders(self, name, _limit=None):
        name = name.lower()
        if self.store is not None:
            if not isinstance(name, unicode):
                name = name.decode("ascii")
            return self.store.query(Header,
                                    attributes.AND(Header.part == self,
                                                   Header.name == name),
                                    sort=Header.index.ascending,
                                    limit=_limit)
        else:
            if not hasattr(self, '_headers'):
                self._headers = []
            return (hdr for hdr in self._headers if hdr.name == name)

    def getAllHeaders(self):
        if self.store is not None:
            return self.store.query(Header,
                                    Header.part == self,
                                    sort=Header.index.ascending)
        else:
            if hasattr(self, '_headers'):
                return iter(self._headers)
            else:
                return iter(())

    def newChild(self):
        if self.store is not None:
            raise NotImplementedError(
                "Don't add children to in-database messages - they aren't mutable [yet?]"
            )
        if not hasattr(self, '_children'):
            self._children = []
        p = Part(partID=self._partCounter(),
                 source=self.source,
                 _partCounter=self._partCounter)
        self._children.append(p)
        return p

    def _addToStore(self, store, message, sourcepath):
        self.source = sourcepath
        self.message = message
        self.store = store

        if hasattr(self, '_headers'):
            for hdr in self._headers:
                hdr.part = self
                hdr.message = self.message
                hdr.store = store

        if hasattr(self, '_children'):
            for child in self._children:
                child.parent = self
                child._addToStore(store, message, sourcepath)

        del self._headers, self._children

    def associateWithMessage(self, message):
        """
        Implement L{IMessageData.associateWithMessage} to add this part and all
        of its subparts and headers to the same store as the given message, and
        set all of their headers.

        This will only be called on the top-level part.

        @param message: a L{exmess.Message}.
        """
        # XXX: we expect our 'source' attribute to be set, since it is set by
        # the delivery code below before the part is handed over to the Message
        # object for processing... this is kind of ugly.

        self._addToStore(message.store, message, self.source)

    # implementation of IMessageIterator

    def getContentType(self, default='text/plain'):
        try:
            value = self.getHeader(u'content-type')
        except equotient.NoSuchHeader:
            return default

        ctype = value.split(';', 1)[0].lower().strip().encode('ascii')
        if ctype.count('/') != 1:
            return default
        return ctype

    def getParam(self,
                 param,
                 default=None,
                 header=u'content-type',
                 un_quote=True):
        try:
            h = self.getHeader(header)
        except equotient.NoSuchHeader:
            return default
        param = param.lower()
        for pair in [x.split('=', 1) for x in h.split(';')[1:]]:
            if pair[0].strip().lower() == param:
                r = len(pair) == 2 and pair[1].strip() or ''
                if un_quote:
                    return mimepart.unquote(r)
                return r
        return default

    def getContentTransferEncoding(self, default=None):
        """
        @returns: string like 'base64', 'quoted-printable' or '7bit'
        """
        try:
            ctran = self.getHeader(u'content-transfer-encoding')
        except equotient.NoSuchHeader:
            return default

        if ctran:
            ct = ctran.lower().strip()
            return ct
        return default

    def getBody(self, decode=False):
        f = self.source.open()
        offt = self.bodyOffset
        leng = self.bodyLength
        f.seek(offt)
        data = f.read(leng)
        if decode:
            ct = self.getContentTransferEncoding()
            if ct == 'quoted-printable':
                return quopri.decodestring(data)
            elif ct == 'base64':
                for extraPadding in ('', '=', '=='):
                    try:
                        return (data + extraPadding).decode('base64')
                    except binascii.Error:
                        pass
                return data
            elif ct == '7bit':
                return data
        return data

    def getUnicodeBody(self, default='utf-8'):
        """Get the payload of this part as a unicode object."""
        charset = self.getParam('charset', default=default)
        payload = self.getBody(decode=True)

        try:
            return unicode(payload, charset, 'replace')
        except LookupError:
            return unicode(payload, default, 'replace')

    def getTypedParts(self, *types):
        for part in self.walk():
            if part.getContentType() in types:
                yield part

    def walkMessage(self, prefer):  # XXX RENAME ME
        """
        Return an iterator of Paragraph, Extract, and Embedded instances for
        this part of the message.
        """
        ctype = self.getContentType()
        if ctype.startswith('multipart'):
            args = (prefer, )
        else:
            args = ()

        methodName = 'iterate_' + ctype.replace('/', '_')
        method = getattr(self, methodName, self.iterateUnhandled)
        return method(*args)

    def getAttachment(self, partID):
        for part in self.walkAttachments():
            if part.identifier == partID:
                return part

    def walkAttachments(self):
        """
        Collect all the subparts of this part regarded as attachments
        (i.e., all non-text parts, or parts whose content disposition
        is"attachment").
        """
        for part in self.walk():
            try:
                disposition = part.getHeader(u'content-disposition')
            except equotient.NoSuchHeader:
                disposition = ''

            ctyp = part.getContentType()
            if (not (ctyp.startswith('text') or ctyp.startswith('multipart'))
                    or disposition.startswith('attachment')):

                fname = part.getParam('filename',
                                      header=u'content-disposition')
                yield mimepart.AttachmentPart(self.message.storeID,
                                              part.partID,
                                              ctyp,
                                              disposition=disposition,
                                              filename=fname,
                                              part=part)

    def iterateUnhandled(self, prefer=None):
        """
        If there is no handler for this part, render it either as multipart/mixed
        (if it is multipart) or as application/octet-stream (if it is not).

        See RFC 2046, Section 4 and Section 5.1.3 for more details.
        """
        if self.getContentType().startswith('multipart'):
            contentType = 'multipart/mixed'
        else:
            contentType = 'application/octet-stream'
        yield mimepart.Part(self.message.storeID,
                            self.partID,
                            contentType,
                            part=self)

    def iterate_text_plain(self):
        content = self.getUnicodeBody()

        if self.getParam('format') == 'flowed':
            pfactory = mimepart.FlowedParagraph.fromRFC2646
        else:
            pfactory = mimepart.FixedParagraph.fromString

        paragraph = pfactory(content)

        yield mimepart.Part(self.message.storeID,
                            self.partID,
                            self.getContentType(),
                            children=[paragraph],
                            part=self)

    def iterate_text_html(self):
        yield mimepart.HTMLPart(self.message.storeID,
                                self.partID,
                                self.getContentType(),
                                part=self)

    def readableParts(self, shallow=False):
        '''return all parts with a content type of text/*'''
        return (part for part in self.walk(shallow=shallow)
                if part.isReadable())

    def readablePart(self, prefer, shallow=False):
        '''return one text/* part, preferably of type prefer.  or None'''
        parts = list(self.readableParts(shallow=shallow))
        if len(parts) == 0:
            return None
        for part in parts:
            if part.getContentType() == prefer:
                return part
        return parts[0]

    def iterate_multipart_alternative(self, prefer):
        # the previous implementation of this method returned the first
        # text/* part at any level below the current part.  that was a
        # problem because it bypassed the logic of any intermediate
        # multiparts.  e.g. it would do the wrong this for this (unrealistic)
        # example part layout:
        #
        # multipart/alternative:
        #     multipart/related:
        #         text/plain
        #         text/plain
        #     multipart/related:
        #         text/html
        #         text/html
        #
        # it would have picked the first text/plain child inside the first
        # multipart/related, without displaying the second one.
        #
        # this is explicit as possible to avoid further confusion:

        # find all immediate children
        parts = list(self.walk(shallow=True))
        # go through each of them
        for part in parts:
            # stop if we find one with a content-type that matches
            # the "prefer" argument
            if part.getContentType() == prefer:
                break
        # we didn't find one
        else:
            # go through them again
            for part in parts:
                # stop if we find a readable/renderable one
                if part.isReadable():
                    break
            # we didn't find one
            else:
                # go through them again
                for part in parts:
                    # stop if we find a multipart
                    if part.isMultipart():
                        break
                # we didn't find one
                else:
                    # there is no renderable or readable component
                    # to this message, at any level
                    return

        # delegate to whichever part we found
        for child in part.walkMessage(prefer):
            yield child

    def iterate_multipart_mixed(self, prefer):
        # the previous implementation of this method had a similar problem
        # to iterate_multipart_alternative, in that it would just return
        # all readable parts at any level below the current part with
        # considering the logic of any multiparts in between.

        # for each immediate child of this part
        for part in self.walk(shallow=True):
            # if the part is readable/renderable or is a multipart
            if part.isReadable() or part.isMultipart():
                # delegate the decision about what components to render
                for child in part.walkMessage(prefer):
                    yield child

    iterate_multipart_related = iterate_multipart_signed = iterate_multipart_mixed

    def isReadable(self):
        return self.getContentType() in ('text/plain', 'text/html')

    def isMultipart(self):
        return self.getContentType().startswith('multipart/')

    def getFilename(self, default='No-Name'):
        return self.getParam('filename',
                             default=default,
                             header=u'content-disposition')

    def relatedAddresses(self):
        """
        Implement L{IMessageData.relatedAddresses} by looking at relevant
        RFC2822 headers for sender and recipient addresses.
        """
        for header in (u'from', u'sender', u'reply-to'):
            try:
                v = self.getHeader(header)
            except equotient.NoSuchHeader:
                continue

            email = mimeutil.EmailAddress(v, mimeEncoded=False)
            yield (exmess.SENDER_RELATION, email)
            break

        for header, relationship in [(u'cc', exmess.COPY_RELATION),
                                     (u'to', exmess.RECIPIENT_RELATION),
                                     (u'bcc', exmess.BLIND_COPY_RELATION),
                                     (u'resent-to', exmess.RESENT_TO_RELATION),
                                     (u'resent-from',
                                      exmess.RESENT_FROM_RELATION)]:
            try:
                v = self.getHeader(header)
            except equotient.NoSuchHeader:
                pass
            else:
                for addressObject in mimeutil.parseEmailAddresses(
                        v, mimeEncoded=False):
                    yield (relationship, addressObject)

    def guessSentTime(self, default=None):
        """
        Try to determine the time this message claims to have been sent by
        analyzing various headers.

        @return: a L{Time} instance, or C{None}, if we don't have a guess.
        """

        try:
            sentHeader = self.getHeader(u'date')
        except equotient.NoSuchHeader:
            sentHeader = None
        else:
            try:
                return Time.fromRFC2822(sentHeader)
            except ValueError:
                pass

        for received in list(self.getHeaders(u'received'))[::-1]:
            lines = received.value.splitlines()
            if lines:
                lastLine = lines[-1]
                parts = lastLine.split('; ')
                if parts:
                    date = parts[-1]
                    try:
                        when = rfc822.parsedate(date)
                        if when is None:
                            continue
                    except ValueError:
                        pass
                    else:
                        return Time.fromStructTime(when)

        return default

    def getAllReplyAddresses(self):
        """
        Figure out the address(es) that a reply to all people involved this
        message should go to

        @return: Mapping of header names to sequences of
        L{xquotient.mimeutil.EmailAddress} instances.  Keys are 'to', 'cc' and
        'bcc'.
        @rtype: C{dict}
        """
        fromAddrs = list(self.store.query(smtpout.FromAddress))
        fromAddrs = set(a.address for a in fromAddrs)

        relToKey = {
            exmess.SENDER_RELATION: 'to',
            exmess.RECIPIENT_RELATION: 'to',
            exmess.COPY_RELATION: 'cc',
            exmess.BLIND_COPY_RELATION: 'bcc'
        }
        addrs = {}

        for (rel, addr) in self.relatedAddresses():
            if rel not in relToKey or addr.email in fromAddrs:
                continue
            addrs.setdefault(relToKey[rel], []).append(addr)
        return addrs

    def getReplyAddresses(self):
        """
        Figure out the address(es) that a reply to this message should be sent
        to.

        @rtype: sequence of L{xquotient.mimeutil.EmailAddress}
        """
        try:
            recipient = self.getHeader(u'reply-to')
        except equotient.NoSuchHeader:
            recipient = dict(
                self.relatedAddresses())[exmess.SENDER_RELATION].email
        return mimeutil.parseEmailAddresses(recipient, mimeEncoded=False)

    def getAlternates(self):
        """
        If any exist, return the alternate versions of this part body.

        @return: a sequence of pairs, the first element holding the MIME type,
        the second a L{Part}
        """
        if (self.parent is not None
                and self.parent.getContentType() == 'multipart/alternative'):
            siblings = self.parent.walk(shallow=True)
            for sibling in siblings:
                if sibling is self:
                    continue
                yield (sibling.getContentType(), sibling)
예제 #15
0
class SubStore(Item):

    schemaVersion = 1
    typeName = 'substore'

    storepath = path()
    substore = inmemory()

    implements(IPowerupIndirector)

    def createNew(cls, store, pathSegments):
        """
        Create a new SubStore, allocating a new file space for it.
        """
        if isinstance(pathSegments, basestring):
            raise ValueError('Received %r instead of a sequence' %
                             (pathSegments, ))
        if store.dbdir is None:
            self = cls(store=store, storepath=None)
        else:
            storepath = store.newDirectory(*pathSegments)
            self = cls(store=store, storepath=storepath)
        self.open()
        self.close()
        return self

    createNew = classmethod(createNew)

    def close(self):
        self.substore.close()
        del self.substore._openSubStore
        del self.substore

    def open(self, debug=None):
        if hasattr(self, 'substore'):
            return self.substore
        else:
            if debug is None:
                debug = self.store.debug
            s = self.substore = self.createStore(debug, self.store.journalMode)
            s._openSubStore = self  # don't fall out of cache as long as the
            # store is alive!
            return s

    def createStore(self, debug, journalMode=None):
        """
        Create the actual Store this Substore represents.
        """
        if self.storepath is None:
            self.store._memorySubstores.append(self)  # don't fall out of cache
            if self.store.filesdir is None:
                filesdir = None
            else:
                filesdir = (self.store.filesdir.child("_substore_files").child(
                    str(self.storeID)).path)
            return Store(parent=self.store,
                         filesdir=filesdir,
                         idInParent=self.storeID,
                         debug=debug,
                         journalMode=journalMode)
        else:
            return Store(self.storepath.path,
                         parent=self.store,
                         idInParent=self.storeID,
                         debug=debug,
                         journalMode=journalMode)

    def __conform__(self, interface):
        """
        I adapt my store object to whatever interface I am adapted to.  This
        allows for avatar adaptation in L{axiom.userbase} to work properly
        without having to know explicitly that all 'avatars' objects are
        SubStore instances, since it is valid to have non-SubStore avatars,
        which are simply adaptable to the cred interfaces they represent.
        """
        ifa = interface(self.open(debug=self.store.debug), None)
        return ifa

    def indirect(self, interface):
        """
        Like __conform__, I adapt my store to whatever interface I am asked to
        produce a powerup for.  This allows for app stores to be installed as
        powerups for their site stores directly, rather than having an
        additional item type for each interface that we might wish to adapt to.
        """
        return interface(self)