imap envelope parsen

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
svenXY
User
Beiträge: 27
Registriert: Dienstag 16. Juni 2009, 10:36
Kontaktdaten:

Hallo allerseits.

meine eigentliche Herausforderung ist es, Mails aus einer IMAP-Mailbox auszulesen und in eine mbox-Datei zu schreiben.

Ich kann auch problemlos die Mail abholen, in einen String umwandeln und in eine Datei schreiben, aber...

mbox verlangt ja noch nach der "From_"- Zeile, die ich mir eigentlich aus dem Envelope bauen wollte.

Ich kann aber nirgends ein modul/Methode/... finden, welceh den Envelope, der hier als String zurückgegeben wird , parst und irgendwie nutzbar macht.

Code: Alles auswählen

import imaplib

M = imaplib.IMAP4('server')
M.login('user','pwd')
M.select('user.user.subfolder')
typ, data = M.search(None, 'ALL')
for num in data[0].split():
    typ, message_parts = M.fetch(num, '(ENVELOPE)')
    print message_parts[0]
M.close()
M.logout()
gibt jeweils den envelope zurück, aber als string:
1 (ENVELOPE ("Fri, 30 Jan 2009 14:54:29 +0100" "subject" (("Sender Name" NIL "user" "tld.de")) ((("Sender Name" NIL "user" "tld.de")) ((("Sender Name" NIL "user" "tld.de")) ((NIL NIL "empfaenger" "tld2.de")) NIL NIL NIL "<RFRYVkZPVihTMCRRW0U6MjIxNjA5ODQ@digassist>"))
Kann man das irgendwie (ohne regexes ;-) ) parsen?

mit Regexes sähe das in etwa so aus:

Code: Alles auswählen

for num in data[0].split():
    typ, message_parts = M.fetch(num, '(RFC822 ENVELOPE)')

    envelope = email.message_from_string(message_parts[0][0])
    msg = email.message_from_string( message_parts[0][1])

    # blöde regex, um die From_ Zeile bauen zu können
    pattern=re.compile('ENVELOPE \("([^"]+)".+?\(\((".*?")\)\)')
    m = pattern.search(envelope.__str__())
    # baue Sender für From_ Zeile
    senderfelder = m.group(2).split('"')
    sender = '@'.join([senderfelder[3], senderfelder[5]])
    # generiere die ASCII-Time für die From_ Zeile
    timestr = time.strftime('%a %b %d %H:%M:%S %Y', email.utils.parsedate(m.group(1)))

    # setze From_ Zeile
    msg.set_unixfrom(" ".join(["From", sender, timestr]))

    # Mail auf stdout ausgeben
    print msg.as_string(True)
update: oder gibt es einen anderen - sicher tollen - Weg, um eine From_ Zeile bauen zu lassen?

Danke,
Sven
Zuletzt geändert von svenXY am Dienstag 8. September 2009, 13:24, insgesamt 1-mal geändert.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Mit den Klammern und dem NIL sieht das eher wie Lisp, denn wie Python aus ;) Bist du sicher, dass in `message_parts[0]` überhaupt ein String drin steckt? Für mich sieht das eher wie eine spezielle Struktur aus. In der Dokumentation sieht man, wie auf `data[0][1]` zugegriffen wird. Und es heißt "Returned data are tuples of message part envelope and data."

Stefan
svenXY
User
Beiträge: 27
Registriert: Dienstag 16. Juni 2009, 10:36
Kontaktdaten:

Hi, ja, es ist wohl ein String:

Code: Alles auswählen


server = 'imap1.domain.de'

login = 'cyrus'
password = '-----'

import imaplib, email, re, time, sys ,os

M = imaplib.IMAP4(server)
M.login(login, password)

## test mailbox
folder = 'user.user1'

## Rechte holen und Folder öffnen
M.setacl(folder, login, 'lrswipkxtecda')
M.select(folder)

# suchen
typ, data = M.search(None, '(ALL)')

for num in data[0].split():
    typ, message = M.fetch(num, '(ENVELOPE)')
    print type(message)
    print type(message[0])
    print message
    print message[0]
    break
M.deleteacl(folder, login)

M.close()
M.logout()
ergibt hier (anonymisiert):


<type 'list'>
<type 'str'>
['1 (ENVELOPE ("Fri, 30 Jan 2009 14:54:29 +0100" "Betreff der Mail" (("Miriam Nachname" NIL "maria.sperl" "senderdom.de")) (("Miriam Nachname" NIL "maria.sperl" "senderdom.de")) (("Miriam Nachname" NIL "maria.sperl" "senderdom.de")) ((NIL NIL "recipient" "emfaengerdom.de")) NIL NIL NIL "<RFRYVkZPVihTMCRRW0U6MjIxNjA5ODQ@digassist>"))']
1 (ENVELOPE ("Fri, 30 Jan 2009 14:54:29 +0100" "Betreff der Mail" (("Miriam Nachname" NIL "maria.sperl" "senderdom.de")) (("Miriam Nachname" NIL "maria.sperl" "senderdom.de")) (("Miriam Nachname" NIL "maria.sperl" "senderdom.de")) ((NIL NIL "recipient" "emfaengerdom.de")) NIL NIL NIL "<RFRYVkZPVihTMCRRW0U6MjIxNjA5ODQ@digassi
[/code]

Das Ergebnis ist also eine Liste mit hier nur einem Element, welches ein Strin ist und diese komische Struktur hat - nun nochmal - hat jemand das schonmal geparst?

Danke,
Sven

PS: Ich habe mittlerweile eine andere Lösung für mein Problem gefunden und hole mir die Daten nicht mehr aus dem Envelope, sondern aus den Headern des Imap-payload. Auf die kann man wesentlich einfacher zugreifen. Aber ich bin trotzdem an der "Lösung" meines beschriebenen Problems interessiert ;-)
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Ich sagte ja schon, dass sieht wie Lisp-S-Expressions aus. Die lassen sich leicht gemäß dieser Grammatik parsen:

Code: Alles auswählen

expr = atom | "(" {expr} ")"
atom = symbol | string | "NIL" 
Siehe http://www.python-forum.de/post-83870.html#83870 für einen Reader. Den würde ich aber inzwischen auch kürzer schreiben können - das war eines meiner ersten Python-Progrämmchen.

Stefan
svenXY
User
Beiträge: 27
Registriert: Dienstag 16. Juni 2009, 10:36
Kontaktdaten:

Hi sma,

Danke für Deine Erklärung. Ich bin kein studierter Informatiker, weswegen ein zu schreibender Parser hier für mich wohl zu aufwendig wird.

Ich finde es schon seltsam, dass das ansonsten recht brauchbare imaplib-Modul hier den Benutzer mit solchen Datenstrukturen alleine lässt... Aber da ich das, was ich benötige ja mittlerweile aus den Mailheadern ziehe (s.o.) ist das auch nicht weiter problematisch

Gruss, Sven
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

svenXY hat geschrieben:Ich bin kein studierter Informatiker, weswegen ein zu schreibender Parser hier für mich wohl zu aufwendig wird.
Das ist nicht so schwer. Der "Parser", also der Teil, der geklammerte Ausdrücke lesen kann, ist in Zeile 74 bis 95 in meinem Beispielcode - das sind gerade mal 20 Zeilen. Lässt man dotted-pairs weg, reicht dies:

Code: Alles auswählen

import re

def cons(a, b): return [a, b]
  
def read(s):
    next = iter(re.findall(r'\s*([()]|"[^"]*"|[^ ()]+|)', s)).next
    def _read(t):
        if t == "": return None
        if t == "(": return _readl(next())
        if t == ")": raise SyntaxError, "unexpected )"
        return t
    def _readl(t):
        if t == "": raise SyntaxError, "missing )"
        if t == ")": return None
        return cons(_read(t), _readl(next()))
    return _read(next())

print read('(env foo ("bar"))')
Stefan
svenXY
User
Beiträge: 27
Registriert: Dienstag 16. Juni 2009, 10:36
Kontaktdaten:

ich werd's probieren, vielen Dank!
henne.gwath
User
Beiträge: 3
Registriert: Dienstag 14. Februar 2012, 13:06

Dieser Thread ist schon alt. Ich weiß. Trotzdem ist es der einzige, der bei der Suche im großen weiten Web einige Informationen gibt.

Wegen eines kaputten IMAP-Servers muss ich einige Headerzeilen aus dem ENVELOPE wiederherstellen. Den envelope via imap bekommen ist kein Problem, das Parsen bereitet mir allerdings Probleme. Der obige Code geht in die richtige Richtung, scheint die Zeile allerdings nicht korrekt zu parsen.

Ich bekomme etwas wie

Code: Alles auswählen

['1 (ENVELOPE ("Sun, 29 Jan 2012 10:40:05 +0100" "Betreff Zeile" (("Name 1" NIL "benutzer" "domain.com")) (("Name 2" NIL "benutzer" "domain.com")) (("Name 3" NIL "benutzer" "domain.com")) (("Name 4" NIL "benutzer" "domain.com")) (("To Name 1" NIL "benutzer" "domain.com")("To Name 2" NIL "benutzer" "domain.com")("To Name 3" NIL "benutzer" "domain.com")) NIL NIL "<cryptisches34987@domain.com>"))']
Durch den Ausdruck
next = iter(re.findall(r'\s*([()]|"[^"]*"|[^ ()]+|)', s)).next
wird zusätzlich eine neue Liste angefangen, wenn ein " gefunden wird scheint mir. Der Hund liegt im mittleren Ausdruck "[^"]*" begraben, aber ich weiß nicht was ich daran ändern muss, damit es funktioniert. Ich benutze leider Python2

Viele Grüße,
Henning
lunar

@henne.gwath: Ich rate Dir dazu, E-Mails mit TurboMail zu verarbeiten, statt mit den Mitteln der Standardbibliothek selbst. Die E-Mail-Module der Standardbibliothek sind ... nun ja, eher merkwürdig.

Im Allgemeinen ist es bei derartig alten Diskussionen übrigens sinnvoller, eine neue Diskussion zu eröffnen, und dort auf die alte zu verweisen.
henne.gwath
User
Beiträge: 3
Registriert: Dienstag 14. Februar 2012, 13:06

gut, machen wir das mal...
lunar

@henne.gwath: Was jetzt? TurboMail verwenden, oder eine neue Diskussion eröffnen? Letzteres ist nicht unbedingt nötig, meine Bemerkung war nur allgemeiner Natur. Diese Diskussionen können wir jetzt auch hier fortsetzen, Du musst jetzt nicht mehr eigens eine neue eröffnen.
henne.gwath
User
Beiträge: 3
Registriert: Dienstag 14. Februar 2012, 13:06

Also vielleicht nochmal im Ganzen:

Ich habe hier einen kaputten IMAP-Server den ich gern auf eine neue Version portieren möchte. Die Postfächer sollen mit Offlineimap kopiert werden (das war das einzige Programm, dass dazu in der Lage war). Bei gesendeten Mails fehlen dabei leider die Zeilen Date und Subject -- doof!

Ich habe herausgefunden, dass der Server diese Daten nicht im Header, sondern NUR im ENVELOPE speichert. Diese Daten können einfach via imaplib geholt werden.

Code: Alles auswählen

import imaplib

server = 'SERVER'
login = 'LOGIN'
password = 'PASSWORT'
dir = 'INBOX'

M = imaplib.IMAP4(server)
M.login(login, password)
M.select(dir)

typ, data = M.fetch('1', '(ENVELOPE)')
print(data)

M.close()
M.logout()
Offlineimap ist in Python geschrieben, daher würde ich gern eine Funktion in den Code einfügen (ich weiß noch nicht an welcher Stelle, siehe dazu auch die OfflineImap-Mailingliste), die den Envelope holt und daraus die fehlenden Header-Zeilen erstellt.

Ich weiß nicht ob man TurboMail an dieser Stelle einsetzen kann, wenn ja wäre ich über Rat dankbar.

Henning
Antworten