std.base64

Base64 nach RFC 4648: Binärdaten als druckbare ASCII-Zeichen. Je drei Eingabebytes werden zu vier Zeichen, das Ergebnis ist rund ein Drittel größer als die Eingabe. Die Unit kann das Standardalphabet und die URL-sichere Variante, dazu Padding-Helfer, HTTP-Basic-Auth und Data-URLs.

Standard Library · std.string · std.alloc

Alle Beispiele dieser Seite sind mit lyxc 1.0.21A übersetzt und ausgeführt; die gezeigten Ausgaben sind echte Programmausgaben und mit Pythons base64 gegengeprüft.

 
Decode ist derzeit nicht abgesichert — für Daten aus fremder Quelle ungeeignet. Zwei Befunde, beide nachgemessen:

* max_output_len wird nicht ausgewertet. Der Dekoder schreibt so viele Bytes, wie die Eingabe hergibt; ein zu kleiner Zielpuffer führt zum Überlauf in benachbarten Speicher (#1406).
* Ungültige Zeichen werden nicht erkannt. Die Umkehrtabelle markiert sie mit -1, gelesen wird vorzeichenlos — die Prüfung v < 0 greift nie, und Decode liefert Datenmüll statt -1 (#1407).

Bis das behoben ist: Eingaben mit IsValid vorprüfen, die Zielpuffergröße aus DecodedLen berechnen und großzügig bemessen.

Alle Puffer gehören dem Aufrufer

Keine Funktion dieser Unit legt einen Ausgabepuffer an — jede schreibt in einen Puffer, den der Aufrufer stellt, und hängt ein Nullbyte an. Die Rückgabe ist die Zahl geschriebener Zeichen ohne dieses Nullbyte.

Für Größe
Encode / EncodeBytes EncodedLen(len) + 1
EncodeUrlSafe EncodedLen(len) + 1 (das Ergebnis ist kürzer, weil kein Padding)
Decode / DecodeUrlSafe DecodedLen(len) + 1
EncodeBasicAuth 6 + EncodedLen(user + pass + 1) + 1
EncodeDataUrl 13 + mime + EncodedLen(len) + 1
 
EncodedLen und DecodedLen rechnen ohne Nullbyte. EncodedLen(6) ergibt 8 — der Puffer muss 9 Byte fassen. DecodedLen liefert die obere Schranke (len / 4 × 3); mit Padding sind es ein oder zwei Byte weniger.

Zwei Funktionen legen intern Puffer fester Größe an: EncodeBasicAuth rechnet mit 512 Byte für benutzer:passwort und 1024 Byte für das Ergebnis, EncodeDataUrl mit 4096 Byte für die kodierten Daten. Längere Eingaben laufen über diese internen Grenzen hinaus.


Kodieren

Signatur Beschreibung
Encode(input: pchar, output: pchar): int64 Kodiert bis zum Nullbyte. Für Binärdaten ungeeignet — ein Nullbyte im Eingang beendet die Kodierung.
EncodeBytes(input: pchar, input_len: int64, output: pchar): int64 Kodiert genau input_len Bytes; der Weg für Binärdaten.
EncodeUrlSafe(input: pchar, output: pchar): int64 Alphabet mit - und _ statt + und /, ohne Padding. Kodiert ebenfalls bis zum Nullbyte.
EncodedLen(input_len: int64): int64 (len + 2) / 3 × 4 — die Länge mit Padding.

import std.base64;
import std.alloc;
import std.string;

fn main(): int64 {
    var buf: int64 := alloc(256);
    var out: pchar := buf as pchar;

    // RFC-4648-Testvektoren
    PrintLn(StrConcat("''      -> ", IntToStr(Encode(""c, out))));
    Encode("f"c, out);      PrintLn(StrConcat("f       -> ", out));
    Encode("fo"c, out);     PrintLn(StrConcat("fo      -> ", out));
    Encode("foo"c, out);    PrintLn(StrConcat("foo     -> ", out));
    Encode("foob"c, out);   PrintLn(StrConcat("foob    -> ", out));
    Encode("fooba"c, out);  PrintLn(StrConcat("fooba   -> ", out));
    Encode("foobar"c, out); PrintLn(StrConcat("foobar  -> ", out));

    // Rueckweg
    var back: int64 := alloc(256);
    var n: int64 := Decode("Zm9vYmFy"c, back as pchar, 256);
    poke8(back + n, 0);
    PrintLn(StrConcat(StrConcat("Zm9vYmFy -> ", back as pchar), StrConcat("  (", StrConcat(IntToStr(n), " Byte)"))));

    // URL-sichere Variante
    EncodeUrlSafe("\xfb\xff\xbf"c, out);
    PrintLn(StrConcat("URL-safe (fb ff bf) -> ", out));
    Encode("\xfb\xff\xbf"c, out);
    PrintLn(StrConcat("Standard (fb ff bf) -> ", out));
    return 0;
}

''      -> 0
f       -> Zg==
fo      -> Zm8=
foo     -> Zm9v
foob    -> Zm9vYg==
fooba   -> Zm9vYmE=
foobar  -> Zm9vYmFy
Zm9vYmFy -> foobar  (6 Byte)
URL-safe (fb ff bf) -> -_-_
Standard (fb ff bf) -> +/+/

Alle sieben Testvektoren aus RFC 4648 §10 stimmen, ebenso die Alphabet-Umschaltung an den Bytes fb ff bf.


Dekodieren

Signatur Beschreibung
Decode(input: pchar, output: pchar, max_output_len: int64): int64 Dekodiert Standard-Base64. Rückgabe: Zahl der Bytes. Das Ergebnis wird nicht nullterminiert und max_output_len nicht geprüft.
DecodeUrlSafe(input: pchar, output: pchar, max_output_len: int64): int64 Dasselbe mit dem URL-sicheren Alphabet.
DecodedLen(input_len: int64): int64 len / 4 × 3 — obere Schranke der Bytezahl.

Der Dekoder arbeitet in Vierergruppen und hält an, sobald weniger als vier Zeichen übrig sind. Eine Eingabe, deren Länge kein Vielfaches von 4 ist, liefert also die Bytes der vollständigen Gruppen und ignoriert den Rest — ohne Fehler. Padding (=) beendet die Verarbeitung sauber.

 
Der Standard-Dekoder verschluckt URL-sichere Eingaben. Decode("-_-_") liefert drei Bytes statt einer Fehlermeldung — andere Bytes als DecodeUrlSafe. Wer nicht weiß, welche Variante ankommt, prüft das Alphabet selbst: enthält die Eingabe - oder _, ist es die URL-sichere Form; + oder / die Standardform.

Prüfen

Signatur Beschreibung
IsValidChar(c: int64): bool Ein Zeichen des Standardalphabets oder =. Kennt - und _ nicht.
IsValid(input: pchar): bool Länge ist Vielfaches von 4, alle Zeichen zulässig. Leerer Text gilt als gültig.
HasValidPadding(input: pchar): bool Liefert immer true — siehe Kasten.

import std.base64;
import std.alloc;
import std.string;

fn J(b: bool): pchar { if (b) { return "true"; } return "false"; }

fn main(): int64 {
    var out: int64 := alloc(256);
    PrintLn(StrConcat("Decode('***='):        ", IntToStr(Decode("***="c, out as pchar, 256))));
    PrintLn(StrConcat("Decode('-_-_'):        ", IntToStr(Decode("-_-_"c, out as pchar, 256))));
    PrintLn(StrConcat("DecodeUrlSafe('-_-_'): ", IntToStr(DecodeUrlSafe("-_-_"c, out as pchar, 256))));
    PrintLn(StrConcat("IsValid('Zm9vYmFy'):   ", J(IsValid("Zm9vYmFy"c))));
    PrintLn(StrConcat("IsValid('Zm9v Ymfy'):  ", J(IsValid("Zm9v Ymfy"c))));
    PrintLn(StrConcat("IsValid('abc'):        ", J(IsValid("abc"c))));
    PrintLn(StrConcat("IsValid('-_-_'):       ", J(IsValid("-_-_"c))));
    PrintLn(StrConcat("HasValidPadding('a=b='): ", J(HasValidPadding("a=b="c))));
    return 0;
}

Decode('***='):        2
Decode('-_-_'):        3
DecodeUrlSafe('-_-_'): 3
IsValid('Zm9vYmFy'):   true
IsValid('Zm9v Ymfy'):  false
IsValid('abc'):        false
IsValid('-_-_'):       false
HasValidPadding('a=b='): true

 
HasValidPadding prüft nichts. Alle Verzweigungen der Funktion enden mit true, das abschließende return false ist unerreichbar. "a=b=" und "====" gelten damit als gültig, obwohl = nur am Ende stehen darf (#1409). IsValid endet mit dieser Prüfung und erbt den Fehler — es prüft faktisch nur Länge und Zeichenvorrat.

Für URL-sicheres Base64 gibt es keine Prüffunktion: IsValid meldet dort false, weil - und _ nicht im Standardalphabet stehen.

Padding-Helfer

Signatur Beschreibung
StripPadding(input: pchar, output: pchar): int64 Entfernt alle =-Zeichen, auch mitten im Text.
AddPadding(input: pchar, output: pchar): int64 Füllt auf ein Vielfaches von 4 auf. -1, wenn die Länge modulo 4 gleich 1 ist — dieser Fall ist in Base64 nicht darstellbar.

Typischer Einsatz: JSON Web Tokens und OAuth-Parameter tragen URL-sicheres Base64 ohne Padding. Vor dem Dekodieren mit einem Standard-Dekoder ergänzt AddPadding die fehlenden Zeichen.

StripPadding('Zm9vYg=='): 6   -> Zm9vYg
AddPadding('Zm9vYg'):     8   -> Zm9vYg==
AddPadding('Zm9vYgX'):    8


HTTP Basic Auth und Data-URLs

Signatur Beschreibung
EncodeBasicAuth(username: pchar, password: pchar, output: pchar): int64 Erzeugt den vollständigen Header-Wert einschließlich Basic -Präfix.
DecodeBasicAuth(auth_header: pchar, username: pchar, password: pchar): int64 Zerlegt ihn wieder; 1 bei Erfolg, 0 bei fehlendem Präfix, ungültiger Kodierung oder fehlendem Doppelpunkt.
EncodeDataUrl(mime_type: pchar, data: pchar, output: pchar): int64 Baut data:&lt;mime&gt;;base64,&lt;daten&gt;. Kodiert bis zum Nullbyte — für Binärdaten ungeeignet.

import std.base64;
import std.alloc;
import std.string;

fn main(): int64 {
    // HTTP Basic Auth
    var hdr: int64 := alloc(512);
    var n: int64 := EncodeBasicAuth("aladdin"c, "opensesame"c, hdr as pchar);
    PrintLn(StrConcat(StrConcat("Header (", IntToStr(n)), StrConcat(" Zeichen): ", hdr as pchar)));

    var user: int64 := alloc(128);
    var pass: int64 := alloc(128);
    var ok: int64 := DecodeBasicAuth(hdr as pchar, user as pchar, pass as pchar);
    PrintLn(StrConcat(StrConcat("zurueck: ok=", IntToStr(ok)),
            StrConcat("  Benutzer=", StrConcat(user as pchar, StrConcat("  Passwort=", pass as pchar)))));

    // Ein Header ohne "Basic "-Praefix wird abgewiesen
    PrintLn(StrConcat("DecodeBasicAuth('Bearer xyz'): ", IntToStr(DecodeBasicAuth("Bearer xyz"c, user as pchar, pass as pchar))));

    // Data-URL
    var url: int64 := alloc(512);
    var m: int64 := EncodeDataUrl("text/plain"c, "Hallo Lyx"c, url as pchar);
    PrintLn(StrConcat("Data-URL: ", url as pchar));
    return 0;
}

Header (30 Zeichen): Basic YWxhZGRpbjpvcGVuc2VzYW1l
zurueck: ok=1  Benutzer=aladdin  Passwort=opensesame
DecodeBasicAuth('Bearer xyz'): 0
Data-URL: data:text/plain;base64,SGFsbG8gTHl4

Beide Werte stimmen mit dem Beispiel aus RFC 7617 überein.

 
DecodeBasicAuth prüft nur das Präfix, nicht die Länge der Zielpuffer. Benutzername und Passwort werden ungeprüft in die übergebenen Puffer geschrieben; der interne Dekodierpuffer fasst 512 Byte. Für Header aus dem Netz gilt derselbe Vorbehalt wie für Decode.

Konstanten

Konstante Wert Bedeutung
ENCODE_STD 0 Standardalphabet (+, /, mit Padding)
ENCODE_URL_SAFE 1 URL-sicher (-, _, ohne Padding)

Beide sind Kennzahlen für eigene Fallunterscheidungen — keine Funktion dieser Unit nimmt sie als Parameter entgegen. Die Variante wählt man über den Funktionsnamen (Encode gegen EncodeUrlSafe).


Fallstricke

  • Ergebnis von Decode selbst terminieren. Die Kodierfunktionen hängen ein Nullbyte an, der Dekoder nicht — er liefert Binärdaten. Für die Textausgabe poke8(puffer + n, 0).
  • Encode hört beim ersten Nullbyte auf. Für Binärdaten immer EncodeBytes mit ausdrücklicher Länge nehmen; dasselbe gilt sinngemäß für EncodeUrlSafe und EncodeDataUrl, die beide keine Längenvariante haben.
  • DecodedLen ist eine obere Schranke, kein exaktes Ergebnis — der Rückgabewert von Decode ist maßgeblich.
  • Kein Zeilenumbruch. MIME (RFC 2045) verlangt Umbrüche alle 76 Zeichen; std.base64 erzeugt eine durchgehende Zeile und überliest beim Dekodieren keine Umbrüche — ein \n in der Eingabe ist ein ungültiges Zeichen und wird derzeit als Wert 255 verrechnet.
  • Ein Speicherleck je Dekodierung: Decode und DecodeUrlSafe legen ihre 128-Byte-Umkehrtabelle bei jedem Aufruf neu an und geben sie nie frei — 1000 Aufrufe kosten 128 000 Byte (#1408).
  • Interne Festgrößen bei EncodeBasicAuth (512 / 1024 Byte) und EncodeDataUrl (4096 Byte) begrenzen die Eingabelänge, ohne das zu melden.

Weiterführend: std.string · std.crypto · std.net.http · Standard Library

Quelle: std/base64.lyx (498 Zeilen) · Autor: Andreas Röne · Copyright: 2024–2025 Andreas Röne

Letzte Aktualisierung: 2026-08-13 — Seite gegen std/base64.lyx neu geschrieben. Der Abschnitt „Externe Funktionen„ mit libc_malloc ist entfallen: Die Bindung wurde mit #1310 entfernt, die Unit nutzt std.alloc. Neu: drei lauffähige Beispiele mit echter Ausgabe (RFC-4648-Testvektoren gegen Python geprüft), Puffergrößen-Tabelle, Fallstricke — und die vier belegten Defekte #1406 (max_output_len ignoriert), #1407 (ungültige Zeichen unerkannt), #1408 (Speicherleck je Aufruf), #1409 (HasValidPadding immer true). Geprüft mit lyxc 1.0.21A.