async def email_set(self,
                        idmap,
                        ifInState=None,
                        create=None,
                        update=None,
                        destroy=None):
        oldState = await self.email_state()
        if ifInState is not None and ifInState != oldState:
            raise errors.stateMismatch()

        created, notCreated = await self.create_emails(idmap, create or {})

        update = {idmap.get(id): path for id, path in (update or {}).items()}
        updated, notUpdated = await self.update_emails(update)

        destroy = [idmap.get(id) for id in (destroy or ())]
        destroyed, notDestroyed = await self.destroy_emails(destroy)

        return {
            'accountId': self.id,
            'oldState': oldState,
            'newState': await self.email_state(),
            'created': created or None,
            'notCreated': notCreated or None,
            'updated': updated or None,
            'notUpdated': notUpdated or None,
            'destroyed': destroyed or None,
            'notDestroyed': notDestroyed or None,
        }
Beispiel #2
0
def api_Mailbox_set(request, accountId=None, ifInState=None, create=None, update=None, destroy=None, onDestroyRemoveEmails=False):
    """
    https://jmap.io/spec-mail.html#mailboxset
    https://jmap.io/spec-core.html#set
    """
    account = request.get_account(accountId)
    account.db.sync_mailboxes()
    if ifInState is not None and ifInState != account.db.highModSeqMailbox:
        raise errors.stateMismatch()
    oldState = account.db.highModSeqMailbox

    # CREATE
    created = {}
    notCreated = {}
    if create:
        for cid, mailbox in create.items():
            try:
                id = account.db.create_mailbox(**mailbox)
                created[cid] = {'id': id}
                request.setid(cid, id)
            except errors.JmapError as e:
                notCreated[cid] = {'type': e.__class__.__name__, 'description': str(e)}

    # UPDATE
    updated = {}
    notUpdated = {}
    if update:
        for id, mailbox in update.items():
            try:
                account.db.update_mailbox(id, **update)
                updated[id] = mailbox
            except errors.JmapError as e:
                notUpdated[id] = {'type': e.__class__.__name__, 'description': str(e)}

    # DESTROY
    destroyed = []
    notDestroyed = {}
    if destroy:
        for id in destroy:
            try:
                account.db.destroy_mailbox(id)
                destroyed.append(id)
            except errors.JmapError as e:
                notDestroyed[id] = {'type': e.__class__.__name__, 'description': str(e)}

    return {
        'accountId': accountId,
        'oldState': oldState,
        'newState': account.db.highModSeqMailbox,
        'created': created,
        'notCreated': notCreated,
        'updated': updated,
        'notUpdated': notUpdated,
        'destroyed': destroyed,
        'notDestroyed': notDestroyed,
    }
    async def email_import(self, ifInState=None, emails=()):
        oldState = await self.thread_state()
        if ifInState and ifInState != oldState:
            raise errors.stateMismatch({'newState': oldState})

        created = {}
        notCreated = {}
        for id, email in emails.items():
            try:
                blobId = email.get('blobId', None)
                if not blobId:
                    raise errors.invalidArguments()
                body = await self.download(blobId)
                mailboxIds = email.get('mailboxIds', None)
                if not mailboxIds:
                    raise errors.invalidArguments('mailboxIds are required')
                elif len(mailboxIds) > 1:
                    raise errors.invalidArguments('Max 1 mailboxIds allowed')
                try:
                    imapname = self.mailboxes[mailboxIds[0]]['imapname']
                except KeyError:
                    raise errors.notFound(
                        f"mailboxId {mailboxIds[0]} not found")
                flags = "(%s)" % (' '.join(
                    keyword2flag(kw) for kw in email['keywords']))
                date = email.get('receivedAt', datetime.now())
                uid, guid = await self._imap_append(body, imapname, flags,
                                                    date)
                created[id] = self.format_email_id(uid)
            except errors.JmapError as e:
                notCreated[id] = e.to_dict()
            except Exception as e:
                notCreated[id] = errors.serverPartialFail(str(e))

        return {
            'accountId': self.id,
            'oldState': oldState,
            'newState': await self.email_state(),
            'created': created,
            'notCreated': notCreated,
        }
    async def mailbox_set(self,
                          idmap,
                          ifInState=None,
                          create=None,
                          update=None,
                          destroy=None,
                          onDestroyRemoveEmails=False):
        """https://jmap.io/spec-mail.html#mailboxset"""
        oldState = await self.mailbox_state()
        if ifInState is not None and ifInState != oldState:
            raise errors.stateMismatch()

        # CREATE
        created = {}
        notCreated = {}
        created_imapnames = {}
        for cid, mailbox in (create or {}).items():
            mbox = ImapMailbox(mailbox)
            mbox.db = self
            try:
                imapname = mbox['imapname']
            except KeyError:
                raise errors.notFound("Parent mailbox not found")
            try:
                ok, lines = await self.imap.create(quoted(imapname))
                if ok != 'OK':
                    if '[ALREADYEXISTS]' in lines[0]:
                        raise errors.invalidArguments(lines[0])
                    else:
                        raise errors.serverFail(lines[0])
                # OBJECTID extension returns MAILBOXID on create
                match = re.search(r'\[MAILBOXID \(([^)]+)\)\]', lines[0])
                if match:
                    id = match.group(1)
                    mbox[id] = id
                    mbox['sep'] = '/'
                    mbox['flags'] = set()
                    self.mailboxes[id] = mbox
                    self.byimapname[imapname] = mbox
                    created[cid] = {'id': id}
                else:
                    # set created[cid] after sync_mailboxes()
                    created_imapnames[cid] = imapname
                if not mailbox.get('isSubscribed', True):
                    ok, lines = await self.imap.unsubscribe(imapname)
                    # TODO: handle failed unsubscribe
            except KeyError:
                notCreated[cid] = errors.invalidArguments().to_dict()
            except errors.JmapError as e:
                notCreated[cid] = e.to_dict()

        # UPDATE
        updated = {}
        notUpdated = {}
        for id, update in (update or {}).items():
            try:
                mailbox = self.mailboxes.get(id, None)
                if not mailbox or mailbox['deleted']:
                    raise errors.notFound(f'Mailbox {id} not found')
                await self.update_mailbox(mailbox, update)
                updated[id] = update
            except errors.JmapError as e:
                notUpdated[id] = e.to_dict()

        # DESTROY
        destroyed = []
        notDestroyed = {}
        for id in destroy or ():
            try:
                mailbox = self.mailboxes.get(id, None)
                if not mailbox or mailbox['deleted']:
                    raise errors.notFound('mailbox not found')
                ok, lines = await self.imap.delete(quoted(mailbox['imapname']))
                if ok != 'OK':
                    raise errors.serverFail(lines[0])
                mailbox['deleted'] = True
                destroyed.append(id)
            except errors.JmapError as e:
                notDestroyed[id] = e.to_dict()

        await self.sync_mailboxes({'id', 'imapname'})
        for cid, imapname in created_imapnames.values():
            mbox = self.byimapname.get(imapname, None)
            if mbox:
                created[cid] = {'id': mbox['id']}
                idmap.set(cid, mbox['id'])
            else:
                notCreated[cid] = errors.serverFail().to_dict()

        return {
            'accountId': self.id,
            'oldState': oldState,
            'newState': await self.mailbox_state(),
            'created': created,
            'notCreated': notCreated,
            'updated': updated,
            'notUpdated': notUpdated,
            'destroyed': destroyed,
            'notDestroyed': notDestroyed,
        }
Beispiel #5
0
    async def emailsubmission_set(self, idmap, ifInState=None,
                                  create=None, update=None, destroy=None,
                                  onSuccessUpdateEmail=None,
                                  onSuccessDestroyEmail=None):
        oldState = await self.emailsubmission_state()
        if ifInState and ifInState != oldState:
            raise errors.stateMismatch({"newState": oldState})

        # CREATE
        created = {}
        notCreated = {}
        if create:
            emailIds = [e['emailId'] for e in create.values()]
            await self.fill_emails(['blobId'], emailIds)
        else:
            create = {}
        for cid, submission in create.items():
            identity = self.identities.get(submission['identityId'], None)
            if identity is None:
                raise errors.notFound(f"Identity {submission['identityId']} not found")
            email = self.emails.get(submission['emailId'], None)
            if not email:
                raise errors.notFound(f"EmailId {submission['emailId']} not found")
            envelope = submission.get('envelope', None)
            if envelope:
                sender = envelope['mailFrom']['email']
                recipients = [to['email'] for to in envelope['rcptTo']]
            else:
                # TODO: If multiple addresses are present in one of these header fields,
                #       or there is more than one Sender/From header field, the server
                #       SHOULD reject the EmailSubmission as invalid; otherwise,
                #       it MUST take the first address in the last Sender/From header field.
                sender = (email['sender'] or email['from'])[0]['email']
                recipients = set(to['email'] for to in email['to'] or ())
                recipients.update(to['email'] for to in email['cc'] or ())
                recipients.update(to['email'] for to in email['bcc'] or ())

            body = await self.download(email['blobId'])

            await aiosmtplib.send(
                body,
                sender=sender,
                recipients=recipients,
                hostname=self.smtp_host,
                port=self.smtp_port,
                # username=self.smtp_user,
                # password=self.smtp_pass,
            )
            id = 'fOobAr'
            idmap.set(cid, id)
            created[cid] = {'id': id}

        updated = []
        destroyed = []
        notDestroyed = []

        result = {
            "accountId": self.id,
            "oldState": oldState,
            "newState": await self.emailsubmission_state(),
            "created": created,
            "notCreated": notCreated,
            "destroyed": destroyed,
            "notDestroyed": notDestroyed,
        }

        if onSuccessUpdateEmail or onSuccessDestroyEmail:
            successfull = set(created.keys())
            successfull.update(updated, destroyed)

            updateEmail = {}
            for id in successfull:
                patch = onSuccessUpdateEmail.get(f"#{id}", None)
                if patch:
                    updateEmail[create[id]['emailId']] = patch
            destroyEmail = [id for id in successfull if f"#{id}" in onSuccessDestroyEmail]

            if updateEmail or destroyEmail:
                update_result = await self.email_set(
                    idmap,
                    update=updateEmail,
                    destroy=destroyEmail,
                )
                update_result['method_name'] = 'Email/set'
                return result, update_result
        return result