Enviar um arquivo
  1. base64hub
  2. Base64 no código
  3. Rust
Código

Base64 em Rust

A biblioteca padrão do Rust não tem Base64. A escolha de fato é a crate base64, cuja API passou a usar engines na 0.21. Os trechos abaixo foram compilados com a 0.23.1 e a 0.22.1.

Adicionar a crate e codificar uma string

[dependencies]
base64 = "0.22"   # the snippets below also build with 0.23

Desde a 0.21, todo o trabalho passa por uma Engine. O módulo prelude exporta o trait Engine e quatro engines prontas:

use base64::prelude::*;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let enc = BASE64_STANDARD.encode("héllo wörld");
    println!("{enc}");                              // aMOpbGxvIHfDtnJsZA==

    let bytes = BASE64_STANDARD.decode(&enc)?;
    println!("{}", String::from_utf8(bytes)?);      // héllo wörld
    Ok(())
}

O encode aceita qualquer coisa que seja AsRef<[u8]>, então &str, String, Vec<u8> e literais de bytes funcionam. As strings do Rust são UTF-8, então não há etapa de conversão. O String::from_utf8 retorna um erro em vez de adivinhar quando os bytes decodificados não são UTF-8 válido.

Os nomes do prelude correspondem a base64::engine::general_purpose:

PreludeEngineAlfabetoPreenchimento ao codificarPreenchimento ao decodificar
BASE64_STANDARDSTANDARD+ /simobrigatório
BASE64_STANDARD_NO_PADSTANDARD_NO_PAD+ /nãodeve estar ausente
BASE64_URL_SAFEURL_SAFE- _simobrigatório
BASE64_URL_SAFE_NO_PADURL_SAFE_NO_PAD- _nãodeve estar ausente

Base64 URL-safe com e sem preenchimento

let d = b"<<???>>";
BASE64_URL_SAFE.encode(d);          // "PDw_Pz8-Pg=="
BASE64_URL_SAFE_NO_PAD.encode(d);   // "PDw_Pz8-Pg"

let back = BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg")?;   // b"<<???>>"

As engines sem preenchimento rejeitam entrada com preenchimento (BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg==") retorna Err(InvalidPadding)). Se você aceita tokens de vários geradores, crie uma engine indiferente a isso:

use base64::alphabet;
use base64::engine::general_purpose::{GeneralPurpose, GeneralPurposeConfig};
use base64::engine::DecodePaddingMode;

const URL_LENIENT: GeneralPurpose = GeneralPurpose::new(
    &alphabet::URL_SAFE,
    GeneralPurposeConfig::new()
        .with_encode_padding(false)
        .with_decode_padding_mode(DecodePaddingMode::Indifferent),
);

URL_LENIENT.decode("PDw_Pz8-Pg").is_ok();     // true
URL_LENIENT.decode("PDw_Pz8-Pg==").is_ok();   // true

Os outros modos são RequireCanonical (o padrão das engines com preenchimento) e RequireNone. Contexto sobre as variantes: Base64 vs Base64URL e preenchimento explicado.

Codificar e decodificar um arquivo

let data = std::fs::read("hello.txt")?;
println!("{}", BASE64_STANDARD.encode(&data));          // aGVsbG8K

std::fs::write("copy.txt", BASE64_STANDARD.decode("aGVsbG8K")?)?;

Para anexar a um buffer existente sem nova alocação, use encode_string:

let mut s = String::from("Basic ");
BASE64_STANDARD.encode_string(b"user:pass", &mut s);
println!("{s}");                                         // Basic dXNlcjpwYXNz

Streaming com EncoderWriter e DecoderReader

Os módulos write e read envolvem tipos de std::io, então arquivos grandes nunca ficam inteiros na memória:

use std::fs::File;
use std::io::{self, Read};

let mut input = File::open("big.bin")?;
let mut w = base64::write::EncoderWriter::new(File::create("big.b64")?, &BASE64_STANDARD);
io::copy(&mut input, &mut w)?;
w.finish()?;            // writes the final block and padding

let mut r = base64::read::DecoderReader::new(File::open("big.b64")?, &BASE64_STANDARD);
let mut out = Vec::new();
r.read_to_end(&mut out)?;

Chame o finish() explicitamente. Descartar o writer também tenta fazer o flush, mas não consegue informar erros. Num arquivo de teste de 300 KB, a saída em streaming foi igual à do BASE64_STANDARD.encode, e os bytes decodificados bateram com o original.

Validação e DecodeError

O decode retorna Result<Vec<u8>, DecodeError>. Estes são os resultados com BASE64_STANDARD, mostrados como saída Debug da 0.22.1 e a mensagem Display:

EntradaVarianteMensagem
PDw_Pz8-Pg==InvalidByte(3, 95)Invalid symbol 95, offset 3.
aGkInvalidPaddingInvalid padding
aGk=!!InvalidByte(3, 61)Invalid symbol 61, offset 3.
aInvalidLength(1)Invalid input length: 1
aGl=InvalidLastSymbol(2, 108)Invalid last symbol 108, offset 2.
"aGVs\nbG8K"InvalidByte(4, 10)Invalid symbol 10, offset 4.

Os valores de byte estão em decimal: 95 é _, 61 é =, 10 é uma quebra de linha. InvalidLastSymbol indica bits de preenchimento diferentes de zero, que esta crate rejeita por padrão; with_decode_allow_trailing_bits(true) relaxa isso. Na 0.23, a variante InvalidLastSymbol mudou, e a mensagem agora diz Invalid last symbol 0x6c ('l') at offset 2, decoded as 0b00100101. Faça o match pelo nome da variante, não pelo texto.

use base64::DecodeError;

match BASE64_STANDARD.decode(input) {
    Ok(bytes) => { /* use bytes */ }
    Err(DecodeError::InvalidByte(offset, byte)) => eprintln!("bad byte {byte} at {offset}"),
    Err(e) => eprintln!("invalid Base64: {e}"),
}

Cole uma string que falha no validador de Base64 para ver o caractere problemático; o guia de erros de Base64 percorre as causas.

Armadilhas

  • Tutoriais antigos. encode_config(x, base64::URL_SAFE) e as constantes de config são anteriores à 0.21 e não compilam mais; base64::encode(x) ainda compila, com um aviso de obsolescência. Traduza para URL_SAFE.encode(x) e STANDARD.encode(x).
  • Esquecer de importar o trait. Sem use base64::Engine; (ou o prelude), chamar .encode numa engine falha com “no method named encode found”.
  • Espaços em branco. O decodificador trata quebras de linha e espaços como bytes inválidos. Remova-os, por exemplo com s.split_ascii_whitespace().collect::<String>().
  • Sem helper de MIME. A crate não quebra linhas. Divida a saída em blocos de 76 caracteres você mesmo se gerar corpos de e-mail.

Notas de versão

  • 0.21: introdução da API de Engine; as funções livres encode/decode ficaram obsoletas e as funções *_config foram removidas. As configs passaram a ser GeneralPurposeConfig mais DecodePaddingMode.
  • 0.22: DecodeError::InvalidLength agora se refere ao número de símbolos válidos; o decode_slice funciona com buffers de tamanho exato.
  • 0.23: engines SIMD por trás da feature padrão simd-unsafe, mais constantes pré-configuradas, símbolos de preenchimento personalizados, MSRV 1.71. As engines de uso geral usadas nesta página não mudaram.

Perguntas frequentes

Por que o base64::encode está marcado como obsoleto?

Desde a 0.21, as funções livres base64::encode e base64::decode são wrappers obsoletos (ainda presentes na 0.23). Use uma engine: use base64::prelude::*; e depois BASE64_STANDARD.encode(data).

Por que a crate base64 rejeita entrada sem preenchimento?

A STANDARD exige preenchimento canônico. Use URL_SAFE_NO_PAD ou STANDARD_NO_PAD para entrada sem preenchimento, ou crie uma engine com DecodePaddingMode::Indifferent para aceitar as duas formas.

A crate base64 pula quebras de linha?

Não. "aGVs\nbG8K" falha com InvalidByte(4, 10). Remova os espaços em branco antes de decodificar corpos MIME ou PEM.

Continue lendo