Загрузить файл
  1. base64hub
  2. Base64 в коде
  3. Rust
Код

Base64 в Rust

В стандартной библиотеке Rust нет Base64. Стандартом де-факто стал крейт base64, API которого в версии 0.21 перешёл на движки (engines). Фрагменты ниже собраны с 0.23.1 и 0.22.1.

Подключить крейт и закодировать строку

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

Начиная с 0.21 вся работа идёт через Engine. Модуль prelude экспортирует трейт Engine и четыре готовых движка:

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 принимает всё, что реализует AsRef<[u8]>, так что подходят &str, String, Vec<u8> и байтовые литералы. Строки в Rust – это UTF-8, поэтому преобразовывать ничего не нужно. Если декодированные байты не образуют корректный UTF-8, String::from_utf8 вернёт ошибку и гадать не станет.

Имена из prelude соответствуют движкам из base64::engine::general_purpose:

PreludeДвижокАлфавитДополнение при кодированииДополнение при декодировании
BASE64_STANDARDSTANDARD+ /естьобязательно
BASE64_STANDARD_NO_PADSTANDARD_NO_PAD+ /нетдолжно отсутствовать
BASE64_URL_SAFEURL_SAFE- _естьобязательно
BASE64_URL_SAFE_NO_PADURL_SAFE_NO_PAD- _нетдолжно отсутствовать

URL-safe Base64 с дополнением и без

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

Движки без дополнения отвергают строки с ним (BASE64_URL_SAFE_NO_PAD.decode("PDw_Pz8-Pg==") возвращает Err(InvalidPadding)). Если вы принимаете токены от разных источников, соберите движок, которому всё равно:

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

Остальные режимы – RequireCanonical (по умолчанию у движков с дополнением) и RequireNone. Подробнее о вариантах в статьях Base64 и Base64URL и про дополнение.

Закодировать и декодировать файл

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

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

Чтобы дописать результат в существующий буфер без нового выделения памяти, используйте encode_string:

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

Потоковая обработка с EncoderWriter и DecoderReader

Модули write и read оборачивают типы из std::io, так что большие файлы не держатся в памяти целиком:

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

Вызывайте finish() явно. При удалении writer тоже пытается сбросить данные, но сообщить об ошибке он уже не может. На тестовом файле в 300 КБ потоковый вывод совпал с BASE64_STANDARD.encode, а декодированные байты – с исходными.

Проверка и DecodeError

decode возвращает Result<Vec<u8>, DecodeError>. Вот результаты с BASE64_STANDARD: вывод Debug из 0.22.1 и сообщение Display:

ВходВариантСообщение
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.

Значения байтов десятичные: 95 – это _, 61 – =, 10 – перевод строки. InvalidLastSymbol означает ненулевые биты дополнения, которые этот крейт по умолчанию отвергает; ослабить проверку можно через with_decode_allow_trailing_bits(true). В 0.23 вариант InvalidLastSymbol изменился, и его сообщение теперь выглядит так: Invalid last symbol 0x6c ('l') at offset 2, decoded as 0b00100101. Сопоставляйте по имени варианта, а не по тексту.

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

Вставьте строку, которая не декодируется, в валидатор Base64, чтобы увидеть проблемный символ; частые причины перечислены в статье об ошибках Base64.

Подводные камни

  • Старые руководства. encode_config(x, base64::URL_SAFE) и константы конфигурации относятся к версиям до 0.21 и больше не компилируются; base64::encode(x) пока собирается с предупреждением об устаревании. Переписывайте их на URL_SAFE.encode(x) и STANDARD.encode(x).
  • Забытый импорт трейта. Без use base64::Engine; (или prelude) вызов .encode у движка падает с ошибкой «no method named encode found».
  • Пробельные символы. Декодер считает переводы строк и пробелы недопустимыми байтами. Удаляйте их, например так: s.split_ascii_whitespace().collect::<String>().
  • Нет помощника для MIME. Крейт не переносит строки. Если вы формируете тела писем, разбейте вывод на куски по 76 символов сами.

Версии

  • 0.21: появился Engine API; свободные функции encode/decode объявлены устаревшими, функции *_config удалены. Конфигурация задаётся через GeneralPurposeConfig и DecodePaddingMode.
  • 0.22: DecodeError::InvalidLength теперь указывает число допустимых символов; decode_slice работает с буферами точного размера.
  • 0.23: SIMD-движки за включённой по умолчанию фичей simd-unsafe, больше готовых констант, свои символы дополнения, MSRV 1.71. Движки общего назначения, которые используются на этой странице, не изменились.

Вопросы и ответы

Почему base64::encode помечен как устаревший?

Начиная с 0.21 свободные функции base64::encode и base64::decode – устаревшие обёртки (в 0.23 они ещё есть). Используйте движок: use base64::prelude::*;, затем BASE64_STANDARD.encode(data).

Почему крейт base64 отвергает строки без дополнения?

STANDARD требует канонического дополнения. Для строк без дополнения берите URL_SAFE_NO_PAD или STANDARD_NO_PAD либо соберите движок с DecodePaddingMode::Indifferent, который принимает оба варианта.

Пропускает ли крейт base64 переводы строк?

Нет. "aGVs\nbG8K" падает с InvalidByte(4, 10). Перед декодированием тел MIME или PEM удалите пробельные символы.

Читайте также