Charger un fichier
  1. base64hub
  2. Base64 dans le code
  3. Rust
Code

Base64 en Rust

La bibliothèque standard de Rust n’a pas de Base64. Le choix de fait est la crate base64, dont l’API est passée aux engines en 0.21. Les extraits ci-dessous ont été compilés avec les versions 0.23.1 et 0.22.1.

Ajouter la crate et encoder une chaîne

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

Depuis la 0.21, tout passe par un Engine. Le module prelude exporte le trait Engine et quatre engines prêts à l’emploi :

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(())
}

encode accepte tout ce qui implémente AsRef<[u8]> : &str, String, Vec<u8> et les littéraux d’octets fonctionnent tous. Les chaînes Rust sont en UTF-8, aucune étape de conversion n’est donc nécessaire. Si les octets décodés ne sont pas de l’UTF-8 valide, String::from_utf8 renvoie une erreur au lieu de deviner.

Correspondance entre les noms du prelude et base64::engine::general_purpose :

PreludeEngineAlphabetRemplissage à l’encodageRemplissage au décodage
BASE64_STANDARDSTANDARD+ /ouiexigé
BASE64_STANDARD_NO_PADSTANDARD_NO_PAD+ /nondoit être absent
BASE64_URL_SAFEURL_SAFE- _ouiexigé
BASE64_URL_SAFE_NO_PADURL_SAFE_NO_PAD- _nondoit être absent

Base64 URL-safe avec et sans remplissage

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"<<???>>"

Les engines sans remplissage rejettent une entrée qui en a (BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg==") renvoie Err(InvalidPadding)). Si vous acceptez des jetons de plusieurs producteurs, construisez un engine qui ne s’en soucie pas :

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

Les autres modes sont RequireCanonical (le défaut des engines avec remplissage) et RequireNone. Pour le contexte sur les variantes : Base64 ou Base64URL et le remplissage expliqué.

Encoder et décoder un fichier

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

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

Pour ajouter le résultat à un tampon existant sans nouvelle allocation, utilisez encode_string :

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

Flux avec EncoderWriter et DecoderReader

Les modules write et read enveloppent les types de std::io : les gros fichiers ne restent jamais en mémoire.

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)?;

Appelez finish() explicitement. La destruction du writer tente aussi de vider le tampon, mais ne peut pas signaler d’erreur. Sur un fichier de test de 300 Ko, la sortie en flux était égale à BASE64_STANDARD.encode, et les octets décodés correspondaient à l’original.

Validation et DecodeError

decode renvoie Result<Vec<u8>, DecodeError>. Voici les résultats avec BASE64_STANDARD, sous forme de sortie Debug de la 0.22.1 et du message Display :

EntréeVarianteMessage
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.

Les valeurs d’octets sont en décimal : 95 correspond à _, 61 à =, 10 à un saut de ligne. InvalidLastSymbol signale des bits de remplissage non nuls, que cette crate rejette par défaut ; with_decode_allow_trailing_bits(true) assouplit cette règle. En 0.23, la variante InvalidLastSymbol a changé et son message est désormais Invalid last symbol 0x6c ('l') at offset 2, decoded as 0b00100101. Faites le filtrage sur le nom de la variante, pas sur le texte.

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}"),
}

Collez une chaîne qui échoue dans le validateur Base64 pour voir le caractère fautif ; le guide des erreurs Base64 passe les causes en revue.

Pièges

  • Les vieux tutoriels. encode_config(x, base64::URL_SAFE) et les constantes de configuration datent d’avant la 0.21 et ne compilent plus ; base64::encode(x) compile encore, avec un avertissement d’obsolescence. Traduisez-les en URL_SAFE.encode(x) et STANDARD.encode(x).
  • Oublier l’import du trait. Sans use base64::Engine; (ou le prelude), appeler .encode sur un engine échoue avec « no method named encode found ».
  • Les espaces. Le décodeur traite les sauts de ligne et les espaces comme des octets invalides. Supprimez-les, par exemple avec s.split_ascii_whitespace().collect::<String>().
  • Pas d’aide pour MIME. La crate ne coupe pas les lignes. Découpez vous-même la sortie en morceaux de 76 caractères si vous produisez des corps d’e-mail.

Notes de version

  • 0.21 : introduction de l’API Engine ; fonctions libres encode/decode rendues obsolètes, fonctions *_config supprimées. Les configurations sont GeneralPurposeConfig plus DecodePaddingMode.
  • 0.22 : DecodeError::InvalidLength désigne désormais le nombre de symboles valides ; decode_slice fonctionne avec des tampons de taille exacte.
  • 0.23 : engines SIMD derrière la fonctionnalité par défaut simd-unsafe, davantage de constantes préconfigurées, symboles de remplissage personnalisés, MSRV 1.71. Les engines généralistes utilisés sur cette page n’ont pas changé.

FAQ

Pourquoi base64::encode est-il marqué comme obsolète ?

Depuis la 0.21, les fonctions libres base64::encode et base64::decode sont des enveloppes obsolètes (toujours présentes en 0.23). Utilisez plutôt un engine : use base64::prelude::*; puis BASE64_STANDARD.encode(data).

Pourquoi la crate base64 rejette-t-elle une entrée sans remplissage ?

STANDARD exige un remplissage canonique. Utilisez URL_SAFE_NO_PAD ou STANDARD_NO_PAD pour une entrée sans remplissage, ou construisez un engine avec DecodePaddingMode::Indifferent pour accepter les deux.

La crate base64 ignore-t-elle les sauts de ligne ?

Non. "aGVs\nbG8K" échoue avec InvalidByte(4, 10). Supprimez les espaces avant de décoder des corps MIME ou PEM.

Continuer la lecture