5e6b4428bb
Windows has no base64 command; certutil is file-only, adds PEM markers and cannot be piped. This is a real base64.exe matching GNU coreutils: same flags (-d, -i, -w), stdin/stdout streaming, exit codes and error messages, so existing muscle memory and scripts carry over. Verified against coreutils 9.7 with a 153-case differential test. The subtle behaviours are reproduced: bytes are emitted incrementally so a stream that goes bad still writes its valid prefix, unpadded tails are accepted only when canonically encoded, concatenated streams decode as one, and -w0 suppresses the trailing newline. Deliberate deviation: '\r' is skipped when decoding alongside '\n', as CRLF base64 files are routine on Windows and GNU rejects them. Zero dependencies, so the build works offline.
73 lines
3.9 KiB
Markdown
73 lines
3.9 KiB
Markdown
# base64 for Windows
|
||
|
||
Windows has no `base64` command. `certutil -encode` is the usual substitute, but it only works on files, wraps output in `-----BEGIN CERTIFICATE-----` markers, and can't be piped. This is a real `base64.exe` that behaves like the one on a Linux box, so the commands and scripts you already know carry over.
|
||
|
||
```powershell
|
||
base64 image.png > image.b64
|
||
base64 -d image.b64 > image.png
|
||
type secrets.json | base64 -w0
|
||
base64 -di messy.txt > payload.bin
|
||
```
|
||
|
||
## Build and install
|
||
|
||
```powershell
|
||
cargo build --release
|
||
```
|
||
|
||
Copy `target\release\base64.exe` somewhere on your PATH — e.g. `%LOCALAPPDATA%\Microsoft\WindowsApps`, or a `C:\tools\bin` folder you've added yourself:
|
||
|
||
```powershell
|
||
mkdir C:\tools\bin
|
||
copy target\release\base64.exe C:\tools\bin\
|
||
[Environment]::SetEnvironmentVariable(
|
||
'Path',
|
||
[Environment]::GetEnvironmentVariable('Path','User') + ';C:\tools\bin',
|
||
'User')
|
||
```
|
||
|
||
Open a new terminal afterwards for the PATH change to take effect. No runtime dependencies — it's a single static binary, and the crate itself has zero dependencies, so the build works offline.
|
||
|
||
## Usage
|
||
|
||
```
|
||
Usage: base64 [OPTION]... [FILE]
|
||
Base64 encode or decode FILE, or standard input, to standard output.
|
||
|
||
With no FILE, or when FILE is -, read standard input.
|
||
|
||
-d, --decode decode data
|
||
-i, --ignore-garbage when decoding, ignore non-alphabet characters
|
||
-w, --wrap=COLS wrap encoded lines after COLS character (default 76).
|
||
Use 0 to disable line wrapping
|
||
--help display this help and exit
|
||
--version output version information and exit
|
||
```
|
||
|
||
Long options accept unambiguous abbreviations (`--dec`), short options cluster (`-di`), `-w` takes its value joined or separate (`-w0`, `-w 0`, `--wrap=0`), and `--` ends option parsing.
|
||
|
||
## Behaviour
|
||
|
||
Verified against GNU coreutils 9.7 with a 153-case differential test covering encoding at every wrap width, decode round-trips, malformed padding, truncated input, garbage handling, all the usage errors and file operands. Output, exit codes and error messages match, including the fiddly parts:
|
||
|
||
- **Partial output on failure.** Bytes are emitted as soon as enough bits are available, so a stream that goes bad halfway still writes the valid prefix before the error — same as coreutils.
|
||
- **Unpadded input is accepted** when the encoding is canonical, i.e. the leftover bits are zero. `aGVsbG8` decodes to `hello`; `aGV` is rejected because `V` carries stray bits.
|
||
- **Concatenated streams** decode as one (`aGVsbG8=aGVsbG8=` → `hellohello`).
|
||
- **`-w0` emits no trailing newline**; wrapped output always ends with one. Empty input produces no output at all.
|
||
- **Exit codes**: `0` success, `1` on any error.
|
||
|
||
### Deliberate differences
|
||
|
||
| | Why |
|
||
|---|---|
|
||
| `\r` is skipped when decoding, as `\n` already is | GNU rejects CRLF input as invalid. On Windows a base64 file will routinely have CRLF line endings, and erroring on those would make the tool useless for its main job. Everything else is still rejected unless you pass `-i`. |
|
||
| `--help` omits GNU's support URLs | This isn't GNU coreutils; pointing at their bug tracker would be wrong. |
|
||
| Error messages quote with `'ASCII'` | GNU uses `'…'` in a UTF-8 locale. Windows consoles are frequently cp437/cp1252, where those bytes render as mojibake. |
|
||
|
||
## Notes
|
||
|
||
- Roughly 2× slower than GNU on large inputs (100 MB encodes in ~0.35s vs ~0.18s). GNU uses hand-tuned routines; this is plain portable Rust and fast enough that the difference is invisible for normal use.
|
||
- Input is streamed, not buffered whole, so encoding a multi-gigabyte file doesn't blow up memory.
|
||
- Piping to a program that exits early (`base64 big.bin | head`) is handled quietly rather than reporting a broken pipe.
|
||
- Writing raw binary to a Windows console fails with a clear message telling you to redirect, because consoles take UTF-16 text and would corrupt the bytes. Redirecting to a file or piping is unaffected.
|