IMAPClient y el aplanamiento de la BODYSTRUCTURE
Para aplanar la BODYSTRUCTURE hay que empezar a leer el RFC3501.
Los desarrolladores de aplicaciones quieren utilizar soluciones probadas para crear una aplicación. Muchas veces esto funciona, pero con el paquete IMAPClient faltan varias cosas.
La idea de IMAP es obtener sólo lo que usted solicita. Supongamos que tienes un correo electrónico con muchos archivos adjuntos pero quieres ver o descargar sólo uno de ellos. Para poder hacerlo necesitas el 'body_number' de este adjunto y luego FETCH esta parte.
En internet se ve gente que descarga el mensaje completo, pero esa no es la forma de hacerlo. Aquí presento una solución para obtener el body_numbers de todas las partes de un mensaje de correo electrónico.
Aplanando el BODYSTRUCTURE
Yo mismo he tenido problemas con esto y para ponerme en marcha he utilizado un código que encontré en Internet, pero su utilidad era limitada. Es hora de empezar a leer el RFC3501, ver los enlaces de abajo. Un mensaje de correo electrónico IMAP no sólo consiste en archivos adjuntos como images y PDF , sino que los archivos adjuntos también pueden ser mensajes en sí mismos, lo que significa que nuestro código debe utilizar la recursión.
Lo que quiero es una lista de body_numbers que se puede utilizar para FETCH la parte (s) que queremos. Podemos llamar a esta operación 'aplanar el BODYSTRUCTURE' porque pasamos de las recursiones a una lista. Durante este proceso generamos el body_numbers.
MULTIPART/ALTERNATIVE, MULTIPART/MIXED, MULTIPART/RELATED, ...
Los mensajes de correo electrónico se construyen la mayoría de las veces con elementos que están relacionados entre sí. Existen diferentes tipos de relaciones, por ejemplo
- ALTERNATIVE: las partes en alternativa tienen el mismo contenido, por lo que el cliente de correo puede elegir cuál mostrar
- RELATED: las partes deben presentarse juntas, no de forma alternativa. Por lo tanto, se combinan. Por ejemplo, la imagen en línea
- MIXED: las partes contienen información diferente y no deben mostrarse juntas.
El documento 'IMAP BODYSTRUCTURE: formatted examples' ofrece una buena introducción, ver los enlaces más abajo.
Este es un ejemplo de una relación ALTERNATIVE . El BODYSTRUCTURE devuelto por IMAPClient es:
(
[
(b'text', b'plain', (b'charset', b'iso-8859-1'), None, None, b'quoted-printable', 426, 15, None, None, None, None),
(b'text', b'html', (b'charset', b'iso-8859-1'), None, None, b'quoted-printable', 1085, 36, None, None, None, None)
],
b'alternative',
(b'boundary', b'_000_CWXP265MB4244C3FA1F3563A988AAE2CABBDF9CWXP265MB4244GBRP_'),
None,
(b'en-US',),
None
)
IMAPClient nos da una lista de elementos ALTERNATIVE . Podemos elegir entre mostrar el texto plano o la parte HTML . Los body_parts aplanados son, el primer número es el body_number:
1 - ALTERNATIVE : TEXT, text/plain, iso-8859-1
2 - ALTERNATIVE : TEXT, text/html, iso-8859-1
Aquí está el mismo ejemplo con un accesorio PDF añadido:
(
[
(
[
(b'text', b'plain', (b'charset', b'UTF-8'), None, None, b'8bit', 1268, 25, None, (b'inline', None), None, None),
(b'text', b'html', (b'charset', b'UTF-8'), None, None, b'8bit', 10887, 115, None, (b'inline', None), None, None)
],
b'alternative',
(b'boundary', b'alt-60e413d9174229.65052373'),
None,
None,
None
),
(b'application', b'pdf', (b'name', b'summary.pdf'), None, None, b'base64', 187354, None,
(b'attachment', (b'filename', b'summary.pdf')), None, None)
],
b'mixed',
(b'boundary', b'multipart-60e413d9174190.84932644'),
None,
None,
None
)
Los body_parts aplanados son, el primer número es el body_number:
1.1 - ALTERNATIVE : TEXT, text/plain, utf-8
1.2 - ALTERNATIVE : TEXT, text/html, utf-8
2 - MIXED : NON_MULTIPART-ATTACHMENT, application/pdf, summary.pdf
¿De dónde vienen los body_numbers ?
No obtenemos el body_numbers del servidor IMAP . En cambio, obtenemos el BODYSTRUCTURE del servidor IMAP y debemos generar el body_numbers a partir de él. En los ejemplos anteriores esto no es difícil. Pero se complica con los mensajes adjuntos.
La estructura del BODYSTRUCTURE
Para entender el BODYSTRUCTURE he copiado algo de texto del RFC3501:
Los campos básicos de un body part no multiparte están en el siguiente orden:
- body type
Una cadena que da el nombre del tipo de medio de contenido tal como se define en [MIME-IMB]. -
body subtype
Una cadena que da el nombre del subtipo de contenido tal como se define en [MIME-IMB]. -
body parameter parenthesized list
Una lista entre paréntesis de pares atributo/valor [por ejemplo, ("foo" "bar" "baz" "rag") donde "bar" es el valor de "foo" y "rag" es el valor de "baz"] tal como se define en [MIME-IMB]. -
body id
Una cadena que proporciona el identificador de contenido tal como se define en [MIME-IMB]. -
body description
Cadena que indica el contenido de tal como se define en [MIME-IMB]. -
body encoding
Cadena que indica la codificación de transferencia del contenido, tal como se define en [MIME-IMB]. -
body size
Un número que da el tamaño del cuerpo en octetos. Tenga en cuenta que este tamaño es el tamaño en su codificación de transferencia y no el tamaño resultante después de cualquier decodificación.
Un body type de tipo MESSAGE y subtipo RFC822 contiene, inmediatamente después de los campos básicos, la estructura del sobre, la estructura del cuerpo y el tamaño en líneas de texto del mensaje encapsulado.
Un body type de tipo TEXT contiene, inmediatamente después de los campos básicos, el tamaño del cuerpo en líneas de texto. Tenga en cuenta que este tamaño es el tamaño en su codificación de transferencia de contenido y no el tamaño resultante después de cualquier decodificación.
Los datos de extensión siguen a los campos básicos y a los campos específicos del tipo mencionados anteriormente. Los datos de extensión nunca se devuelven con la obtención de BODY, pero pueden devolverse con una obtención de BODYSTRUCTURE . Los datos de extensión, si están presentes, DEBEN estar en el orden definido.
Tenga en cuenta que el body type del tipo MESSAGE y el subtipo RFC822 introduce la recursión. Cuando nos encontramos con esto, procesamos este mensaje como un nuevo mensaje, extraemos los body_number, etc. Y este mensaje, de nuevo, puede contener uno o varios mensajes más.
Según el RFC3501, cada mensaje contiene una estructura de sobre, y una estructura de cuerpo. La estructura del cuerpo del mensaje es la nueva estructura del cuerpo que debemos procesar.
Ejemplo de mensajes anidados
He aquí un ejemplo de un mensaje que contiene otro mensaje que contiene otro mensaje con un adjunto. El BODYSTRUCTURE devuelto por IMAPClient es:
(
[
(b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 35, 7, None, None, None, None),
(b'message', b'rfc822', (b'name', b'Re: My message.eml'), None, None, b'7bit', 10034116,
(
b'Sun, 19 Sep 2021 10:04:43 +0200',
b'Re: My message',
((b'Bob Smith', None, b'bobsmith', b'example.com'),),
((b'Bob Smith', None, b'bobsmith', b'example.com'),),
((b'Bob Smith', None, b'bobsmith', b'example.com'),),
((b'richardroe@example.org', None, b'richardroe', b'example.org'),),
None,
None,
None,
b'<8b678e28-d03a-2bdd-2930-12470235ef9a@example.com>'
),
(
(b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 46, 7, None, None, None, None),
(b'message', b'rfc822', (b'name', b'Fw: Some email two.eml'), None, None, b'7bit', 10029135,
(
b'Sat, 18 Sep 2021 18:35:47 +0200',
b'Fw: Some email two',
((b'John Doe', None, b'johndoe', b'example.org'),),
((b'John Doe', None, b'johndoe', b'example.org'),),
((b'John Doe', None, b'johndoe', b'example.org'),),
((None, None, b'richardroe', b'example.org'),),
None,
None,
None,
b'<a7dfbf41-1a26-4316-b8b5-1753fc17cd54-1631982946791@3c-example.org>'),
(
(b'text', b'html', (b'charset', b'UTF-8'), None, None, b'7bit', 5053, 89, None, None, None, None),
(b'application', b'pdf', (b'name', b'YZ345.pdf'), None, None, b'base64', 187354, None,
(b'attachment', (b'filename', b'YZ345.pdf')), None, None),
b'mixed',
(b'boundary', b'abmob-49888c7b-3a09-4d10-b119-df9366de9f4c'),
None,
None,
None
),
128656,
None,
(b'attachment', (b'filename', b'Fw: Some email two.eml')),
None,
None
),
b'mixed',
(b'boundary', b'------------3F95A42110AABF9E24EC86EB'),
None,
(b'en-US',),
None
),
128759,
None,
(b'attachment', (b'filename', b'Re: My message.eml')), None, None
)
],
b'mixed',
(b'boundary', b'------------7323DBBF0E22BDA4B95E42D1'),
None,
(b'en-US',),
None
)
Los body_parts aplanados son, el primer número es el body_number:
1 - MIXED : TEXT, text/plain, utf-8
2 - MIXED : MESSAGE_RFC822, message/rfc822, Re: My message.eml
2.1 - MIXED : TEXT, text/plain, utf-8
2.2 - MIXED : MESSAGE_RFC822, message/rfc822, Fw: Some email two.eml
2.2.1 - MIXED : TEXT, text/html, utf-8
2.2.2 - MIXED : NON_MULTIPART-ATTACHMENT, application/pdf, YZ345.pdf
Fijación del BODYSTRUCTURE de los mensajes anidados
Al mirar el ejemplo de los mensajes anidados de arriba, notarás que los mensajes anidados no contienen listas, como el mensaje de nivel superior. Considero que esto es un error, pero podría ser una decisión de diseño.
Sea como sea, IMAPClient contiene una clase que podemos utilizar para convertir el mensaje anidado BODYSTRUCTURE en un mensaje de nivel superior BODYSTRUCTURE.
body_data = BodyData()
body_structure = body_data.create(nested_message_body_structure_part)
Ahora el mensaje anidado BODYSTRUCTURE contiene listas y puede ser procesado de la misma manera que el nivel superior BODYSTRUCTURE.
La clase BodyStructurePart y los tipos de partes
Aplanar significa crear una lista de objetos BodyStructurePart. Una clase BodyStructurePart contiene toda la información para realizar el procesamiento posterior. Debe contener al menos lo siguiente:
- body_number: el body_number
- body_type: ALTERNATIVE, MIXED, etc.
- body_part: la parte real del BODYSTRUCTURE
Además he añadido atributos:
- part_type
- part_subtype
- content_type
A part_type puede ser, véase también RFC3501 arriba:
- MESSAGE_RFC822
- TEXT
- NON_MULTIPART
Un part_subtype, utilizado con part_type = NON_MULTIPART, puede ser:
- ATTACHMENT
- INLINE
- OTHER
El código del analizador BODYSTRUCTURE
Y finalmente aquí está el código del parser. Contiene tres clases:
- IMAPBodyStructurePartUtils
- IMAPBodyStructurePart
- IMAPBodyStructureParser
La clase IMAPBodyStructureParser tiene un método parse que se llama con el BODYSTRUCTURE devuelto por IMAPClient. Este método devuelve una lista de objetos IMAPBodyStructurePart que pueden ser utilizados en nuestro código.
He puesto el código y los ejemplos en un solo archivo por si quieres probarlo:
import sys
from imapclient import IMAPClient
from imapclient.response_types import BodyData
class IMAPBodyStructurePartUtils:
@classmethod
def __decode(cls, s):
try:
s = s.decode()
except Exception as e:
pass
return s
@classmethod
def get_part_type_and_part_subtype(cls, body_part):
part_type = None
part_subtype = None
try:
if body_part[0] == b'message' and body_part[1] == b'rfc822':
part_type = 'MESSAGE_RFC822'
elif body_part[0] == b'text':
part_type = 'TEXT'
else:
part_type = 'NON_MULTIPART'
except:
pass
if part_type == 'NON_MULTIPART':
try:
if body_part[8][0] == b'attachment':
part_subtype = 'ATTACHMENT'
elif body_part[8][0] == b'inline':
part_subtype = 'INLINE'
else:
part_subtype = 'OTHER'
except Exception as e:
pass
return part_type, part_subtype
@classmethod
def get_content_type(cls, body_part):
try:
ctype = cls.__decode(body_part[0])
csubtype = cls.__decode(body_part[1])
return ctype.lower() + '/' + csubtype.lower()
except Exception as e:
pass
return None
@classmethod
def __get_charset_or_name(cls, charset_or_name, body_part):
try:
for a in range(0, len(body_part[2]), 2):
key = body_part[2][a].lower()
val = body_part[2][a + 1]
if key == charset_or_name:
if charset_or_name == b'charset':
return cls.__decode(val).lower()
return cls.__decode(val)
except Exception as e:
pass
return None
@classmethod
def get_charset(cls, body_part):
return cls.__get_charset_or_name(b'charset', body_part)
@classmethod
def get_name(cls, body_part):
return cls.__get_charset_or_name(b'name', body_part)
@classmethod
def get_filename(cls, body_part):
try:
sub_body_part = body_part[8][1]
for i in range(0, len(sub_body_part), 2):
key = sub_body_part[i]
val = sub_body_part[i + 1]
if key == b'filename':
return cls.__decode(val)
except:
pass
return None
class IMAPBodyStructurePart:
def __init__(
self,
body_number=None,
body_type=None,
body_part=None,
):
self.body_number = body_number
self.body_type = body_type
self.body_part = body_part
self.part_type, self.part_subtype = IMAPBodyStructurePartUtils.get_part_type_and_part_subtype(self.body_part)
self.content_type = IMAPBodyStructurePartUtils.get_content_type(self.body_part)
self.name = IMAPBodyStructurePartUtils.get_name(self.body_part)
self.charset = IMAPBodyStructurePartUtils.get_charset(self.body_part)
self.filename = IMAPBodyStructurePartUtils.get_filename(self.body_part)
def __str__(self):
if self.body_type is None:
self.body_type = ''
if self.part_type == 'MESSAGE_RFC822':
return '{:8} - {:12}: {}, {}, {}'.format(self.body_number, self.body_type, self.part_type, self.content_type, self.name)
elif self.part_type == 'TEXT':
return '{:8} - {:12}: {}, {}, {}'.format(self.body_number, self.body_type, self.part_type, self.content_type, self.charset)
return '{:8} - {:12}: {}-{}, {}, {}'.format(self.body_number, self.body_type, self.part_type, self.part_subtype, self.content_type, self.filename)
class IMAPBodyStructureParser:
def __init__(
self,
dbg=False,
):
pass
@classmethod
def __is_multipart(cls, part):
return isinstance(part[0], list)
@classmethod
def __get_body_type(cls, part):
# body_type (ALTERNATIVE, MIXED, ...) is first item after list
body_type = None
if len(part) > 1:
body_type = part[1]
if body_type is not None:
try:
body_type = body_type.decode()
except Exception as e:
pass
if body_type is not None:
body_type = body_type.upper()
return body_type
@classmethod
def __add_body_part(cls, body_parts, body_number, body_type, part):
body_parts.append(IMAPBodyStructurePart(
body_number=body_number,
body_type=body_type,
body_part=part,
))
@classmethod
def parse(cls, part, body_number='', body_type=None):
return cls.__recursive_parse(body_parts=[], part=part, body_number=body_number, body_type=body_type)
@classmethod
def __recursive_parse(cls, body_parts, part, body_number='', body_type=None):
if part is None:
return None
part_type, part_sub_type = IMAPBodyStructurePartUtils.get_part_type_and_part_subtype(part)
if part_type == 'MESSAGE_RFC822':
cls.__add_body_part(body_parts, body_number, body_type, part)
# convert message body_structure at part[8] using BodyData
body_data = BodyData()
part = body_data.create(part[8])
if cls.__is_multipart(part):
body_type = cls.__get_body_type(part)
for i, p in enumerate(part[0], 1):
if body_number == '':
next_body_number = str(i)
else:
next_body_number = body_number + '.' + str(i)
cls.__recursive_parse(body_parts, p, body_number=next_body_number, body_type=body_type)
else:
cls.__add_body_part(body_parts, body_number, body_type, part)
elif cls.__is_multipart(part):
body_type = cls.__get_body_type(part)
for i, p in enumerate(part[0], 1):
if body_number == '':
next_body_number = str(i)
else:
next_body_number = body_number + '.' + str(i)
cls.__recursive_parse(body_parts, p, body_number=next_body_number, body_type=body_type)
else:
if body_number == '':
body_number = '1'
cls.__add_body_part(body_parts, body_number, body_type, part)
return body_parts
# BODYSTRUCTURE examples
body_structures = [
{
'name': 'single text/plain part',
'body_structure':
(b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 1148, 59, None, None, None, None),
},
{
'name': 'text/plain and text/html',
'body_structure':
(
[
(b'text', b'plain', (b'charset', b'iso-8859-1'), None, None, b'quoted-printable', 426, 15, None, None, None, None),
(b'text', b'html', (b'charset', b'iso-8859-1'), None, None, b'quoted-printable', 1085, 36, None, None, None, None)
],
b'alternative',
(b'boundary', b'_000_CWXP265MB4244C3FA1F3563A988AAE2CABBDF9CWXP265MB4244GBRP_'),
None,
(b'en-US',),
None
),
},
{
'name': 'text/plain and pdf attachment',
'body_structure':
(
[
(b'text', b'plain', (b'charset', b'ISO-8859-15'), None, None, b'quoted-printable', 394, 13, None, (b'inline', None), None, None),
(b'application', b'pdf', (b'name', b'manual.pdf'), None, None, b'base64', 175098, None, (b'attachment', (b'filename', b'manual.pdf')), None, None)
],
b'mixed',
(b'boundary', b'_----------=_1631938636414264302'),
None,
None,
None
),
},
{
'name': 'text/plain and text/html and pdf attachment',
'body_structure':
(
[
(
[
(b'text', b'plain', (b'charset', b'UTF-8'), None, None, b'8bit', 1268, 25, None, (b'inline', None), None, None),
(b'text', b'html', (b'charset', b'UTF-8'), None, None, b'8bit', 10887, 115, None, (b'inline', None), None, None)
],
b'alternative',
(b'boundary', b'alt-60e413d9174229.65052373'),
None,
None,
None
),
(b'application', b'pdf', (b'name', b'summary.pdf'), None, None, b'base64', 187354, None,
(b'attachment', (b'filename', b'summary.pdf')), None, None)
],
b'mixed',
(b'boundary', b'multipart-60e413d9174190.84932644'),
None,
None,
None
),
},
{
'name': 'text/plain, text/html and inline image',
'body_structure':
(
[
(
[
(b'text', b'plain', (b'charset', b'UTF-8'), None, None, b'quoted-printable', 3909, 138, None, None, None, None),
(b'text', b'html', (b'charset', b'UTF-8'), None, None, b'quoted-printable', 21375, 397, None, None, None, None)
],
b'alternative',
(b'boundary', b'000000000000239fe505c86b594b'),
None,
None,
None
),
(b'image', b'png', (b'name', b'image.png'), b'<17afcc25c9bcb971f161>', None, b'base64', 1491868, None,
(b'inline', (b'filename', b'image.png')), None, None)
],
b'related',
(b'boundary',
b'000000000000239fe605c86b594c'),
None,
None,
None
),
},
{
'name': 'text/plain, text/html and inline images and pdf attachment',
'body_structure':
(
[
(
[
(
[
(b'text', b'plain', (b'charset', b'UTF-8'), None, None, b'quoted-printable', 4393, 90, None, None, None, None),
(b'text', b'html', (b'charset', b'UTF-8'), None, None, b'quoted-printable', 12720, 264, None, None, None, None)
],
b'alternative',
(b'boundary', b'0000000000007dda0f05c7b3e2d2'),
None,
None,
None
),
(b'image', b'png', (b'name', b'image.png'), b'<17acdbf96cccb971f161>', None, b'base64', 120514, None,
(b'inline', (b'filename', b'image.png')), None, None),
(b'image', b'png', (b'name', b'image.png'), b'<17acdbf96cccb971f162>', None, b'base64', 78208, None,
(b'inline', (b'filename', b'image.png')), None, None)
],
b'related',
(b'boundary', b'0000000000007dda1005c7b3e2d3'),
None,
None,
None
),
(b'application', b'pdf', (b'name', b'Love letter.pdf'), b'<17acdbf96cc7f74e7e76>', None, b'base64', 591456, None,
(b'attachment', (b'filename', b'Love letter.pdf')), None, None)
],
b'mixed',
(b'boundary', b'0000000000007dda1105c7b3e2d4'),
None,
None,
None
),
},
{
'name': 'text/plain with message/rfc822 attachment',
'body_structure':
(
[
(b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 35, 7, None, None, None, None),
(b'message', b'rfc822', (b'name', b'Please respond.eml'), None, None, b'7bit', 10034116,
(
b'Sun, 19 Sep 2021 10:04:43 +0200',
b'Please respond',
((b'Peter Mooring', None, b'petermooring', b'gmail.com'),),
((b'Peter Mooring', None, b'petermooring', b'gmail.com'),),
((b'Peter Mooring', None, b'petermooring', b'gmail.com'),),
((b'peterpm@xs4all.nl', None, b'peterpm', b'xs4all.nl'),),
None,
None,
None,
b'<8b678e28-d03a-2bdd-2930-12470235ef9a@gmail.com>'),
(
(b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 46, 7, None, None, None, None),
(b'text', b'html', (b'charset', b'UTF-8'), None, None, b'quoted-printable', 21375, 397, None, None, None, None),
b'alternative',
(b'boundary', b'------------3F95A42110AABF9E24EC86EB'),
None,
(b'en-US',),
None
),
128759,
None,
(b'attachment', (b'filename', b'Please respond.eml')), None, None
)
],
b'mixed',
(b'boundary', b'------------7323DBBF0E22BDA4B95E42D1'),
None,
(b'en-US',),
None
),
},
{
'name': 'text/plain with message/rfc822 attachment including another message/rfc822',
'body_structure':
(
[
(b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 35, 7, None, None, None, None),
(b'message', b'rfc822', (b'name', b'Re: My message.eml'), None, None, b'7bit', 10034116,
(
b'Sun, 19 Sep 2021 10:04:43 +0200',
b'Re: My message',
((b'Bob Smith', None, b'bobsmith', b'example.com'),),
((b'Bob Smith', None, b'bobsmith', b'example.com'),),
((b'Bob Smith', None, b'bobsmith', b'example.com'),),
((b'richardroe@example.org', None, b'richardroe', b'example.org'),),
None,
None,
None,
b'<8b678e28-d03a-2bdd-2930-12470235ef9a@example.com>'
),
(
(b'text', b'plain', (b'charset', b'utf-8'), None, None, b'7bit', 46, 7, None, None, None, None),
(b'message', b'rfc822', (b'name', b'Fw: Some email two.eml'), None, None, b'7bit', 10029135,
(
b'Sat, 18 Sep 2021 18:35:47 +0200',
b'Fw: Some email two',
((b'John Doe', None, b'johndoe', b'example.org'),),
((b'John Doe', None, b'johndoe', b'example.org'),),
((b'John Doe', None, b'johndoe', b'example.org'),),
((None, None, b'richardroe', b'example.org'),),
None,
None,
None,
b'<a7dfbf41-1a26-4316-b8b5-1753fc17cd54-1631982946791@3c-example.org>'),
(
(b'text', b'html', (b'charset', b'UTF-8'), None, None, b'7bit', 5053, 89, None, None, None, None),
(b'application', b'pdf', (b'name', b'YZ345.pdf'), None, None, b'base64', 187354, None,
(b'attachment', (b'filename', b'YZ345.pdf')), None, None),
b'mixed',
(b'boundary', b'abmob-49888c7b-3a09-4d10-b119-df9366de9f4c'),
None,
None,
None
),
128656,
None,
(b'attachment', (b'filename', b'Fw: Some email two.eml')),
None,
None
),
b'mixed',
(b'boundary', b'------------3F95A42110AABF9E24EC86EB'),
None,
(b'en-US',),
None
),
128759,
None,
(b'attachment', (b'filename', b'Fw: Some email two.eml')), None, None
)
],
b'mixed',
(b'boundary', b'------------7323DBBF0E22BDA4B95E42D1'),
None,
(b'en-US',),
None
),
},
]
# show examples
for b in body_structures:
print('\nBody structure: {}\n{}'.format(b['name'], '-'*60))
for body_structure_part in IMAPBodyStructureParser.parse(b['body_structure']):
print('{}'.format(body_structure_part))
Unas palabras sobre IMAP y la privacidad
IMAP fue diseñado para dejar tus mensajes en un servidor IMAP , al que se puede acceder desde múltiples dispositivos. Con IMAP usted solicita inicialmente sólo los datos mínimos. Si se desea más, sólo se descargan las partes seleccionadas. Las solicitudes de búsqueda también se envían al servidor IMAP . Personalmente no me gusta IMAP porque deja muchos más datos en el servidor IMAP y la visualización de archivos adjuntos específicos y las solicitudes de búsqueda también se pueden utilizar para tomar huellas digitales.
Resumen
Aplanar el IMAP BODYSTRUCTURE me llevó tiempo porque no había recetas de Python en Internet. Después de leer el RFC3501 parecía no ser tan difícil ... pero ... Debido a que decodificamos el BODYSTRUCTURE nosotros mismos es fácil cometer errores. ¿Y podemos manejar todos los tipos de BODYSTRUCTUREs (mal formados)?
En Internet puedes encontrar información sobre decodificadores que a veces fallan, por ejemplo, de RoundCube. Se trata de un fallo en el que se recupera todo el mensaje.
Enlaces / créditos
IMAP BODYSTRUCTURE: formatted examples
http://sgerwk.altervista.org/imapbodystructure.html
IMAPClient
https://imapclient.readthedocs.io/en/2.2.0/index.html
INTERNET MESSAGE ACCESS PROTOCOL - VERSION 4rev1
https://datatracker.ietf.org/doc/html/rfc3501
Recientes
- Cómo ocultar las claves primarias de la base de datos UUID de su aplicación web
- Don't Repeat Yourself (DRY) con Jinja2
- SQLAlchemy, PostgreSQL, número máximo de filas por user
- Mostrar los valores en filtros dinámicos SQLAlchemy
- Transferencia de datos segura con cifrado de Public Key y pyNaCl
- rqlite: una alternativa de alta disponibilidad y dist distribuida SQLite
Más vistos
- Usando Python's pyOpenSSL para verificar los certificados SSL descargados de un host
- Usando UUIDs en lugar de Integer Autoincrement Primary Keys con SQLAlchemy y MariaDb
- Conectarse a un servicio en un host Docker desde un contenedor Docker
- Usando PyInstaller y Cython para crear un ejecutable de Python
- SQLAlchemy: Uso de Cascade Deletes para eliminar objetos relacionados
- Flask RESTful API validación de parámetros de solicitud con esquemas Marshmallow