Подключить крейт и закодировать строку
[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_STANDARD | STANDARD | + / | есть | обязательно |
BASE64_STANDARD_NO_PAD | STANDARD_NO_PAD | + / | нет | должно отсутствовать |
BASE64_URL_SAFE | URL_SAFE | - _ | есть | обязательно |
BASE64_URL_SAFE_NO_PAD | URL_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. |
aGk | InvalidPadding | Invalid padding |
aGk=!! | InvalidByte(3, 61) | Invalid symbol 61, offset 3. |
a | InvalidLength(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 namedencodefound». - Пробельные символы. Декодер считает переводы строк и пробелы недопустимыми байтами. Удаляйте их, например так:
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 удалите пробельные символы.