web-dev-qa-db-fra.com

Python - en-tête de courrier électronique décodant UTF-8

existe-t-il un module Python qui aide à décoder les différentes formes d'en-têtes de messagerie codées, principalement Subject, en simples - disons - chaînes UTF-8?

Voici des exemples d'en-têtes de sujet des fichiers courrier que j'ai:

Subject: [ 201105311136 ]=?UTF-8?B?IMKnIDE2NSBBYnM=?=. 1 AO;
Subject: [ 201105161048 ] GewSt:=?UTF-8?B?IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0?=
Subject: [ 201105191633 ]
  =?UTF-8?B?IERyZWltb25hdHNmcmlzdCBmw7xyIFZlcnBmbGVndW5nc21laHJhdWZ3ZW5kdW4=?=
  =?UTF-8?B?Z2VuIGVpbmVzIFNlZW1hbm5z?=

texte - aiguille codée - texte

texte - chaîne codée

texte - chaîne codée - chaîne codée

Encodig pourrait également être quelque chose d'autre comme ISO 8859-15.

Mise à jour 1: j'ai oublié de mentionner, j'ai essayé email.header.decode_header

    for item in message.items():
    if item[0] == 'Subject':
            sub = email.header.decode_header(item[1])
            logging.debug( 'Subject is %s' %  sub )

Cette sorties

DEBUG: root: le sujet est [('[201101251025] ELStAM; =? UTF-8? B? IFZlcmbDvGd1bmcgdm9tIDIx? =. Januar 2011', aucun)]

ce qui n'aide pas vraiment.

Mise à jour 2: Merci à Ingmar Hupp dans les commentaires.

le premier exemple décode en une liste de deux tupels:

print decode_header ("" "[201105161048] GewSt: =? UTF-8? B? IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0? =" "")
[('[201105161048] GewSt:', None), ('Wegfall der Vorl\xc3\xa4ufigkeit', 'utf-8')]

est-ce toujours [(chaîne, encodage), (chaîne, encodage), ...] donc j'ai besoin d'une boucle pour concaténer tous les éléments [0] en une seule chaîne ou comment obtenir le tout en une seule chaîne?

Objet: [201101251025] ELStAM; =? UTF-8? B? IFZlcmbDvGd1bmcgdm9tIDIx? =. Januar 2011

ne décode pas bien:

print decode_header ("" "[201101251025] ELStAM; =? UTF-8? B? IFZlcmbDvGd1bmcgdm9tIDIx? =. Januar 2011" "")

[('[201101251025] ELStAM; =? UTF-8? B? IFZlcmbDvGd1bmcgdm9tIDIx? =. Januar 2011', Aucun)]

35
Hans Moser

Ce type d'encodage est connu sous le nom de MIME encoded-Word et le module email peut le décoder:

from email.header import decode_header
print decode_header("""=?UTF-8?B?IERyZWltb25hdHNmcmlzdCBmw7xyIFZlcnBmbGVndW5nc21laHJhdWZ3ZW5kdW4=?=""")

Cela génère une liste de tuples, contenant la chaîne décodée et l'encodage utilisé. En effet, le format prend en charge différents encodages dans un seul en-tête. Pour les fusionner en une seule chaîne, vous devez les convertir en un codage partagé, puis les concaténer, ce qui peut être accompli en utilisant l'objet unicode de Python:

from email.header import decode_header
dh = decode_header("""[ 201105161048 ] GewSt:=?UTF-8?B?IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0?=""")
default_charset = 'ASCII'
print ''.join([ unicode(t[0], t[1] or default_charset) for t in dh ])

Mise à jour 2:

Le problème avec cette ligne Objet ne décodant pas:

Subject: [ 201101251025 ] ELStAM;=?UTF-8?B?IFZlcmbDvGd1bmcgdm9tIDIx?=. Januar 2011
                                                                     ^

Est en fait la faute de l'expéditeur, qui viole l'exigence de mots codés dans un en-tête étant séparés par un espace blanc, spécifié dans RFC 2047, section 5, paragraphe 1 : un "mot codé" qui apparaît dans un champ d'en-tête défini comme "* texte" DOIT être séparé de tout "mot codé" ou "texte" adjacent par un "espace blanc linéaire".

Si nécessaire, vous pouvez contourner ce problème en prétraitant ces en-têtes corrompus avec une expression régulière qui insère un espace après la partie Word encodé (sauf si elle est à la fin), comme ceci:

import re
header_value = re.sub(r"(=\?.*\?=)(?!$)", r"\1 ", header_value)
49
Ingmar Hupp

Je testais juste avec des en-têtes codés dans Python 3.3, et j'ai trouvé que c'était un moyen très pratique de les gérer:

>>> from email.header import Header, decode_header, make_header

>>> subject = '[ 201105161048 ] GewSt:=?UTF-8?B?IFdlZ2ZhbGwgZGVyIFZvcmzDpHVmaWdrZWl0?='
>>> h = make_header(decode_header(subject))
>>> str(h)
'[ 201105161048 ] GewSt:  Wegfall der Vorläufigkeit'

Comme vous pouvez le voir, il ajoute automatiquement des espaces autour des mots encodés.

Il conserve en interne les parties d'en-tête codées et ASCII séparées comme vous pouvez le voir quand il recode les parties non ASCII:

>>> h.encode()
'[ 201105161048 ] GewSt: =?utf-8?q?_Wegfall_der_Vorl=C3=A4ufigkeit?='

Si vous souhaitez que l'en-tête entier soit recodé, vous pouvez convertir l'en-tête en chaîne, puis le recomposer en en-tête:

>>> h2 = Header(str(h))
>>> str(h2)
'[ 201105161048 ] GewSt:  Wegfall der Vorläufigkeit'
>>> h2.encode()
'=?utf-8?q?=5B_201105161048_=5D_GewSt=3A__Wegfall_der_Vorl=C3=A4ufigkeit?='
41
Sander Steffann
def decode_header(value):
    return ' '.join((item[0].decode(item[1] or 'utf-8').encode('utf-8') for item in email.header.decode_header(value)))
7
Vitaly Greck

Que diriez-vous de décoder les en-têtes de la manière suivante:

import poplib, email

from email.header import decode_header, make_header

...

        subject, encoding = decode_header(message.get('subject'))[0]

        if encoding==None:
            print "\n%s (%s)\n"%(subject, encoding)
        else:
            print "\n%s (%s)\n"%(subject.decode(encoding), encoding)

cela devient l'objet de l'e-mail et le décode avec l'encodage spécifié (ou pas de décodage si l'encodage est défini sur Aucun).

A travaillé pour moi pour les encodages définis comme "Aucun", "utf-8", "koi8-r", "cp1251", "windows-1251"

3
Eugene

J'ai eu un problème similaire, mais mon cas était un peu différent:

  • Python 3.5 (La question date de 2011, mais reste très élevée sur google)
  • Lire le message directement à partir du fichier sous forme de chaîne d'octets

Maintenant, la fonctionnalité intéressante du python 3 email.parser est que tous les en-têtes sont automatiquement décodés en Unicode-Strings. Cependant, cela provoque un peu de "malheur" quand il s'agit de mauvais en-têtes. le problème:

Subject: Re: =?ISO-2022-JP?B?GyRCIVYlMyUiMnE1RCFXGyhC?=
 (1/9(=?ISO-2022-JP?B?GyRCNmIbKEI=?=) 6:00pm-7:00pm) 
 =?ISO-2022-JP?B?GyRCJE4kKkNOJGkkOxsoQg==?=

Cela a abouti à msg['subject']:

Re: 「コア会議」 (1/9(=?ISO-2022-JP?B?GyRCNmIbKEI=?=) 6:00pm-7:00pm)  のお知らせ

Eh bien, le problème est le non-respect de la RFC 2047 (il devrait y avoir un espace blanc en ligne après le mot codé MIME) comme déjà décrit dans le réponse d'Ingmar Hupp . Ma réponse est donc inspirée de la sienne.

Solution 1: Correction de la chaîne d'octets avant d'analyser réellement l'e-mail. Cela semblait être la meilleure solution, mais j'avais du mal à implémenter une substitution Regex sur les chaînes d'octets. J'ai donc opté pour la solution 2:

Solution 2: Correction de la valeur d'en-tête déjà analysée et partiellement décodée:

with open(file, 'rb') as fp:  # read as byte-string
    msg = email.message_from_binary_file(fp, policy=policy.default)
    subject_fixed = fix_wrong_encoded_words_header(msg['subject'])


def fix_wrong_encoded_words_header(header_value):
    fixed_header_value = re.sub(r"(=\?.*\?=)(?=\S)", r"\1 ", header_value)

    if fixed_header_value == header_value:  # nothing needed to fix
        return header_value
    else:
        dh = decode_header(fixed_header_value) 
        default_charset = 'unicode-escape'
        correct_header_value = ''.join([str(t[0], t[1] or default_charset) for t in dh])
        return correct_header_value

Explication des parties importantes:

J'ai modifié l'expression régulière d'Ingmar Hupp pour ne remplacer que les mauvais mots codés MIME: (=\?.*\?=)(?=\S)Démo Debuggex . Parce que faire pour tous ralentirait fortement l'analyse syntaxique (analyse d'environ 150'000 mails).

Après avoir appliqué le decode_header fonction à fixed_header, nous avons les parties suivantes dans dh:

dh == [(b'Re: \\u300c\\u30b3\\u30a2\\u4f1a\\u8b70\\u300d (1/9(', None), 
       (b'\x1b$B6b\x1b(B', 'iso-2022-jp'), 
       (b' ) 6:00pm-7:00pm)  \\u306e\\u304a\\u77e5\\u3089\\u305b', None)]

Pour re-décoder les séquences échappées unicode, nous définissons default_charset = 'unicode-escape' lors de la création de la nouvelle valeur d'en-tête.

Le correct_header_value est maintenant:

Re: 「コア会議」 (1/9(金 ) 6:00pm-7:00pm)  のお知らせ'

J'espère que cela fera gagner du temps à quelqu'un.

Addition: Le réponse de Sander Steffann ne m'a pas vraiment aidé, car je n'ai pas pu extraire la valeur brute du champ d'en-tête de la classe de message.

2
Luke

Ce script fonctionne bien pour moi .. J'utilise ce script pour décoder tous les sujets des e-mails

pat2=re.compile(r'(([^=]*)=\?([^\?]*)\?([BbQq])\?([^\?]*)\?=([^=]*))',re.IGNORECASE)

def decodev2(a):
    data=pat2.findall(a)
    line=[]
    if data:
            for g in data:
                    (raw,extra1,encoding,method,string,extra)=g
                    extra1=extra1.replace('\r','').replace('\n','').strip()
                    if len(extra1)>0:
                            line.append(extra1)
                    if method.lower()=='q':
                            string=quopri.decodestring(string)
                            string=string.replace("_"," ").strip()
                    if method.lower()=='b':
                            string=base64.b64decode(string)
                    line.append(string.decode(encoding,errors='ignore'))
                    extra=extra.replace('\r','').replace('\n','').strip()
                    if len(extra)>0:
                            line.append(extra)
            return "".join(line)
    else:
            return a

échantillons:

=?iso-8859-1?q?una-al-dia_=2806/04/2017=29_Google_soluciona_102_vulnerabi?=
 =?iso-8859-1?q?lidades_en_Android?=

=?UTF-8?Q?Al=C3=A9grate?= : =?UTF-8?Q?=20La=20compra=20de=20tu=20vehi?= =?UTF-8?Q?culo=20en=20tan=20s=C3=B3lo=2024h?= =?UTF-8?Q?=2E=2E=2E=20=C2=A1Valoraci=C3=B3n=20=26?= =?UTF-8?Q?ago=20=C2=A0inmediato=21?=
1
Jescolabcn
from email.header import decode_header
mail = email.message_from_bytes(data[0][1])

subject_list = decode_header(mail['Subject'])

sub_list = []
for subject in subject_list:
    if subject[1]:
        subject = (subject[0].decode(subject[1]))
    Elif type(subject[0]) == bytes:
        subject = subject[0].decode('utf-8')
    else:
        subject = subject[0]
    sub_list.append(subject)

subject = ''.join(sub_list)
print('Subject:' + subject)
0
Vitalij Chepelev

Python possède une bibliothèque de courrier électronique. http://docs.python.org/library/email.header.html

Jetez un œil à email.header.decode_header ()

0
acron