Enviar um arquivo
  1. base64hub
  2. Aprenda Base64
  3. Base64 em e-mail
Guia

Base64 em e-mail: MIME, anexos e headers

Todo anexo de e-mail viaja como texto Base64, quebrado a cada 76 caracteres. Depois que você aprende a ler a estrutura MIME em volta, tirar o arquivo de uma mensagem bruta leva um minuto.

Por que o e-mail precisa de Base64

O correio da internet foi feito para texto ASCII de 7 bits em linhas de comprimento limitado. A RFC 5322 ainda limita a linha a 998 caracteres, e o SMTP só garante a entrega segura de dados de 7 bits, a menos que o servidor de destino anuncie a extensão 8BITMIME. Mesmo com transporte de 8 bits, uma mensagem não pode conter bytes NUL nem carriage returns e line feeds soltos, coisa que todo arquivo binário acaba tendo. Enviar bytes brutos exige a extensão BINARYMIME, e quem envia não pode contar com ela em todo o caminho de servidor em servidor.

O MIME (RFC 2045) resolve isso com o header Content-Transfer-Encoding. Ele diz a quem recebe como o corpo de uma parte foi transformado em texto seguro. Há cinco valores:

ValorSignificado
7bitLinhas ASCII simples, sem codificação
8bitTexto de 8 bits em linhas curtas, exige 8BITMIME
binaryBytes brutos, exige BINARYMIME
quoted-printableTexto quase todo legível, bytes não ASCII escapados como =XX
base64Base64 em linhas de no máximo 76 caracteres

Para anexos, os programas de e-mail quase sempre escolhem base64. O algoritmo em si é explicado em como o Base64 funciona.

Base64 vs quoted-printable

O quoted-printable mantém os caracteres ASCII como estão e escreve todos os outros bytes como = mais dois dígitos hex. Um é em UTF-8 (bytes C3 A9) vira =C3=A9. Linhas com mais de 76 caracteres são divididas com uma quebra suave, um = no fim da linha.

Isso o torna ideal para texto em inglês com um acento aqui e ali, e um desperdício para todo o resto, já que cada byte não ASCII custa 3 caracteres. O Base64 custa 4/3 fixos, seja qual for o conteúdo. Codificamos 20 repetições de uma frase em três idiomas com quopri.encodestring e base64.encodebytes do Python (ambos com finais de linha LF):

Amostra (UTF-8)BytesQuoted-printableBase64
Pangrama em inglês9009261.216
Frase em francês com acentos1.0601.8301.435
Pangrama em russo1.6004.6022.165

Os clientes de e-mail costumam fazer essa escolha por você: quoted-printable ou 7bit para corpos de texto quase todo ASCII, Base64 para anexos e para texto em alfabetos não latinos. Se você precisa ler ou gerar quoted-printable à mão, a ferramenta quoted-printable converte nas duas direções.

Anexos numa mensagem bruta

Uma mensagem com anexo tem um corpo multipart/mixed. Cada parte tem os próprios headers, uma linha em branco e depois o conteúdo. Aqui está uma mensagem completa e mínima, gerada com o pacote email do Python (boundary encurtado para facilitar a leitura):

From: Ana <ana@example.com>
To: bo@example.org
Subject: =?utf-8?b?Q2Fmw6kgbWVudSDigJMgR3LDtsOfZQ==?=
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="b1"

--b1
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: quoted-printable

Hi Bo,

The caf=C3=A9 menu is attached.

--b1
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="menu.pdf"

JVBERi0xLjcKJeLjz9MKMSAwIG9iago8PCAvVHlwZSAvQ2F0YWxvZyA+PgplbmRvYmoKdHJhaWxl
cgo8PCAvUm9vdCAxIDAgUiA+PgolJUVPRgo=

--b1--

O que procurar:

  • Content-Type informa o tipo do arquivo, Content-Disposition o nome dele.
  • Content-Transfer-Encoding: base64 confirma que o corpo é Base64.
  • O Base64 começa depois da primeira linha em branco da parte e termina antes da próxima linha --boundary.
  • As linhas têm exatamente 76 caracteres, menos a última. Cada linha completa guarda 57 bytes do arquivo.
  • O JVBERi0 no início é o Base64 de %PDF-, então isto é um PDF. Outras assinaturas estão listadas em prefixos Base64 comuns.

Imagens embutidas em e-mails HTML usam o mesmo mecanismo numa parte multipart/related. Cada imagem ganha um header Content-ID, e o HTML se refere a ela como <img src="cid:...">. Na transmissão, essas linhas terminam em CRLF, o que faz cada 76 caracteres de Base64 custarem 78 bytes; a conta completa está em aumento de tamanho do Base64. Para gerar saída quebrada em linhas você mesmo, use o codificador Base64 MIME.

Headers codificados: =?UTF-8?B?…?=

Headers como Subject, os nomes de exibição em From e alguns nomes de arquivo precisam ser ASCII. A RFC 2047 codifica o resto do texto como encoded-words:

=?charset?encoding?encoded-text?=

O encoding é B para Base64 ou Q para uma variante de quoted-printable em que _ representa um espaço. As duas letras não diferenciam maiúsculas de minúsculas, e é por isso que o Python escreveu =?utf-8?b? acima. Alguns exemplos reais, todos conferidos com o email.header.decode_header do Python:

CodificadoDecodificado
=?UTF-8?B?0J/RgNC40LLQtdGC?=Привет
=?utf-8?q?Caf=C3=A9_menu?=Café menu
=?ISO-8859-1?Q?Caf=E9_menu?=Café menu
=?utf-8?b?Q2Fmw6kgbWVudSDigJMgR3LDtsOfZQ==?=Café menu – Größe

Uma encoded-word pode ter no máximo 75 caracteres, então assuntos longos são divididos em várias, uma por linha dobrada do header. A RFC 2047 diz que o espaço em branco entre encoded-words vizinhas é ignorado, e cada palavra precisa conter caracteres inteiros, então um caractere UTF-8 de vários bytes nunca é dividido entre duas palavras. Um assunto em russo de 44 caracteres saiu do Python assim:

Subject: =?utf-8?b?0KHRh9GR0YIg0LfQsCDQvtC60YLRj9Cx0YDRjCDQuCDQsNC60YIg0YE=?=
 =?utf-8?b?0LLQtdGA0LrQuCDQtNC70Y8g0LHRg9GF0LPQsNC70YLQtdGA0LjQuA==?=

Cada palavra é uma string Base64 completa, com o próprio preenchimento. Decodificadas separadamente, a primeira dá Счёт за октябрь и акт с e a segunda верки для бухгалтерии; um decodificador as junta no assunto original.

Os nomes de arquivo de anexos têm um segundo padrão, a RFC 2231, que se parece com filename*=UTF-8''%D0%BE%D1%82%D1%87%D1%91%D1%82.pdf. Muitos programas de e-mail também colocam encoded-words da RFC 2047 dentro de filename="...", então um parser deve lidar com as duas formas.

Como extrair um anexo de um arquivo .eml

No navegador. Abra o .eml no decodificador de EML para ler os corpos da mensagem e salvar os anexos. Ele roda localmente na página, o que importa quando a mensagem contém notas fiscais ou dados pessoais.

Com Python. A biblioteca padrão faz o parsing do MIME e decodifica tanto os corpos em Base64 quanto os headers da RFC 2047:

import sys
from email import policy
from email.parser import BytesParser

with open(sys.argv[1], "rb") as f:
    msg = BytesParser(policy=policy.default).parse(f)

print("Subject:", msg["Subject"])        # encoded-words decoded
for part in msg.iter_attachments():
    name = part.get_filename()
    data = part.get_content()             # Base64 already decoded to bytes
    with open(name, "wb") as out:
        out.write(data)
    print("saved", name, len(data), "bytes")

Executado com a mensagem de exemplo acima, ele imprimiu Subject: Café menu – Größe e saved menu.pdf 83 bytes. Em código de verdade, sanitize o name antes de gravar, já que ele vem de quem enviou.

À mão. Copie as linhas Base64 da parte para um arquivo e decodifique-as. Arquivos .eml brutos usam finais de linha CRLF, e o base64 -d do GNU para com invalid input no primeiro carriage return. Remova-os antes ou use -i para pular caracteres fora do alfabeto:

tr -d '\r' < block.txt | base64 -d > menu.pdf

openssl base64 -d -in block.txt -out menu.pdf aceitou a versão com CRLF sem alterações nos nossos testes. Se você só tem o texto Base64, cole-o em Base64 para arquivo, que detecta o tipo pelos primeiros bytes, ou direto em Base64 para PDF, no caso de PDFs.

Abrir um arquivo .eml

Veja os headers, os corpos e os anexos de um e-mail bruto, decodificados localmente no seu navegador.

Decodificador de e-mail

Perguntas frequentes

Como abrir um anexo em Base64 a partir do código-fonte de um e-mail?

Copie as linhas Base64 entre a linha em branco da parte e o boundary seguinte e decodifique-as como arquivo. O decodificador de EML extrai os anexos de um arquivo .eml para você.

O que significa =?UTF-8?B? no assunto de um e-mail?

É o início de uma encoded-word da RFC 2047: o charset é UTF-8 e o B indica que o texto entre o próximo ? e o ?= está em Base64. Um Q no lugar do B indica uma variante de quoted-printable.

Por que as linhas Base64 do e-mail têm 76 caracteres?

A RFC 2045 limita as linhas Base64 no MIME a 76 caracteres. 76 caracteres guardam exatamente 57 bytes, e o limite mantém as linhas bem abaixo do máximo de 998 caracteres que o e-mail permite.

Quoted-printable ou Base64: qual é melhor para texto de e-mail?

Quoted-printable para texto quase todo em ASCII, já que continua legível e quase não cresce. Base64 para dados binários e para texto em alfabetos não latinos, em que o quoted-printable pode deixá-lo quase três vezes maior.

Continue lendo