クレートを追加して文字列をエンコードする
[dependencies]
base64 = "0.22" # the snippets below also build with 0.23
0.21 以降、すべての処理は Engine を通して行います。prelude モジュールは Engine トレイトと、すぐに使える 4 つのエンジンをエクスポートしています。
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セーフ 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 KB のテストファイルでは、ストリーム処理の出力は BASE64_STANDARD.encode と等しく、デコードしたバイト列も元のファイルと一致しました。
検証と DecodeError
decode は Result<Vec<u8>, DecodeError> を返します。以下は BASE64_STANDARD での結果で、0.22.1 の Debug 出力と 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. |
バイト値は 10進数で、95 は _、61 は =、10 は改行です。InvalidLastSymbol は 0 でないパディングビットを意味し、このクレートはデフォルトでこれを拒否します。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)や config 定数は 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-unsafeフィーチャーで SIMD エンジンを追加、事前設定済みの定数が増加、独自のパディング記号に対応、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 の本文をデコードする前に空白を取り除いてください。