====== 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''.