====== 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.
→ [[lyx_-_programmiersprache:units|Standard Library]] · [[lyx_-_programmiersprache:units:string|std.string]] · [[lyx_-_programmiersprache:units:alloc|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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1406|#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'' ([[https://github.com/SEOLizer/LyX-Compiler/issues/1407|#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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1409|#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:<mime>;base64,<daten>''. 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 ([[https://github.com/SEOLizer/LyX-Compiler/issues/1408|#1408]]).
* **Interne Festgrößen** bei ''EncodeBasicAuth'' (512 / 1024 Byte) und ''EncodeDataUrl'' (4096 Byte) begrenzen die Eingabelänge, ohne das zu melden.
----
**Weiterführend:** [[lyx_-_programmiersprache:units:string|std.string]] · [[lyx_-_programmiersprache:units:crypto|std.crypto]] · [[lyx_-_programmiersprache:units:net:http|std.net.http]] · [[lyx_-_programmiersprache:units|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''.