Decodifica Base64 in Dart: una guida completa
Arriva in una risposta API, dentro un URL, o incollato in un ticket di supporto: una lunga sequenza di lettere e cifre con qua e là un +, /, - o _, e forse una coppia di segni = appesi alla fine. Qualcuno lo chiama Base64, e a te serve quello che c'è dentro. Questa guida è la ricetta Dart per riaverlo. Brevissimo orientamento, perché la home page spiega il formato nel dettaglio: Base64 riscrive ogni tre byte in ingresso come quattro caratteri tratti da un alfabeto di 64 caratteri, e appiccica uno o due pad = alla fine quando l'ultimo frammento è corto. La decodifica è la direzione in cui questo scambio si rimpicciolisce: entrano quattro caratteri, ne escono tre byte, quindi il risultato occupa sempre circa un quarto in meno di spazio rispetto all'input.
La buona notizia: non c'è nulla da installare. Base64 è a bordo nella libreria dart:convert dal Dart 1.13 del 2015, e l'API è stabile dal Dart 2.0 del 2018. Un solo import ti dà un decodificatore veloce e rigoroso, che legge sia l'alfabeto standard sia quello URL-safe.
Un confine onesto: questa è la parte decodificatore della storia. Imparerai cosa il decodificatore accetta e cosa rifiuta, come funziona il padding, come riottenere testo dai byte senza mojibake, e dove ti aspetta Base64 nei JWT, nei data URI, nei file, negli stream, nelle email, nelle configurazioni e nella riga di comando. L'altra direzione, impacchettare i byte in una stringa, ha la sua guida, collegata in fondo a questa.
Le quattro porte di una sola macchina rigorosa
Ecco l'intera superficie pubblica che userai, tutta in dart:convert:
| Voce | Che cos'è | Quando usarlo |
|---|---|---|
base64Decode(source) |
Funzione di livello superiore, decodifica in una Uint8List |
La decodifica di tutti i giorni, quasi sempre questa |
base64.decode(source) |
Il metodo di decodifica del codec, comportamento identico | Se vuoi il codec per fuse o per le trasformazioni degli stream |
base64Url.decode(source) |
Il metodo di decodifica del codec URL-safe | L'input è documentato come URL-safe (la macchina è la stessa) |
base64Url.normalize(source) |
Valida e ripara una stringa, la restituisce con il padding | L'input può mancare di padding, mescolare alfabeti o usare percent-escape |
Due cose da notare. Prima, tutte e quattro le strade portano allo stesso decodificatore: una macchina a stati rigorosa con una sola tabella di ricerca. Secondo, l'ultima riga non è affatto un decodificatore. È un punto di riparazione, e si farà valere al primo JWT senza padding o al primo valore di configurazione pulito a metà che compare.
La tua prima decodifica
Nove volte su dieci, la decodifica si risolve in cinque righe. Ecco l'esempio minimo che mostra la forma intera del lavoro:
import 'dart:convert';
void main() {
final bytes = base64Decode('TWFu');
final text = utf8.decode(bytes);
print(text); // Man
}
Tre frasi su quello che è appena successo. Prima, il punto d'ingresso restituisce byte, non testo: base64Decode restituisce una Uint8List, e non è un caso, perché il payload può essere una frase, un JPEG o un hash, e nessuno dei tre va trattato allo stesso modo prima di sapere cos'hai in mano. Secondo, il salto dai byte al testo è un passaggio separato ed esplicito, con una codifica esplicita, ed è proprio lì che "café" diventa mojibake se non ci fai caso. Terzo, la stringa vuota è un valore di prima classe: base64Decode('') ti dà una lista di lunghezza zero, senza eccezioni e senza fronzoli.
Cosa accetta e cosa rifiuta il decodificatore
Il decodificatore di Dart è rigoroso per design. L'RFC 4648 dice che le implementazioni dovrebbero rifiutare gli input con caratteri fuori dall'alfabeto, e Dart segue quella lettura alla lettera: niente spazi saltati, niente a capo ignorati, niente seconde chance. Quando l'input è sbagliato, ottieni una FormatException che mostra l'input e punta al carattere esatto. Ecco il comportamento sui classici fastidiosi:
| Input | Cosa c'è che non va | Errore esatto |
|---|---|---|
'SGVs bG8s' |
un spazio si è infilato dentro | FormatException: Invalid character (at character 5) |
'SGVs\nbG8s' |
un a capo si è infilato dentro | FormatException: Invalid character (at character 5) |
'SGVs$bG8s' |
il segno di dollaro non è nell'alfabeto | FormatException: Invalid character (at character 5) |
'Zm8' |
nessun padding | FormatException: Invalid length, must be multiple of four (at character 4) |
'Zm8==' |
due pad dove ne va uno | FormatException: Invalid padding character (at character 5) |
'Zm=8' |
padding in mezzo ai dati | FormatException: Invalid encoding before padding (at character 3) |
'Zm8=xx' |
spazzatura dopo i pad | FormatException: Invalid padding character (at character 5) |
'Zé' |
un carattere non ASCII | FormatException: Invalid character (at character 2) |
La posizione nel messaggio è un conteggio di caratteri che parte da uno, e l'input viene stampato proprio sotto il caret, così bisettare un payload corrotto è rapido. Nella rigidità si nasconde una sorpresa piacevole: il decodificatore accetta entrambi gli alfabeti. Un - o un _ in mezzo a una stringa standard va bene, e un + o un / in una stringa URL-safe va bene altrettanto. La scelta dell'alfabeto conta solo quando sei tu a produrre il testo, non quando lo stai leggendo.
Padding: la partita non negoziabile
Ecco la regola che sorprende di più: il decodificatore di Dart richiede il padding corretto. L'input deve essere lungo un multiplo esatto di quattro caratteri, e i segni = finali devono essere presenti nella quantità giusta. Non c'è una modalità permissiva, nessun flag per allentare la regola e nessuna impostazione da cambiare. I motivi sono solidi: la decodifica senza padding è ambigua nei casi limite, e l'RFC avverte che una decodifica troppo permissiva può aprire un canale coperto, quindi la lettura rigorosa è quella sicura. In pratica significa questo:
| Input | Risultato |
|---|---|
'' |
Uint8List vuota, nessun errore |
'QQ==' |
1 byte: A |
'QUI=' |
2 byte: AB |
'QUJD' |
3 byte: ABC |
'Zm8' |
FormatException: lunghezza non valida |
'Zm8==' |
FormatException: carattere di padding non valido |
Quando l'input viene da un sistema che toglie il padding, e i JWT ne sono pieni di valori senza padding, il passaggio di riparazione è una sola chiamata a normalize. Convalida la stringa, converte i caratteri URL-safe nell'alfabeto standard e aggiunge i pad mancanti:
import 'dart:convert';
void main() {
final stripped = '-__--Q';
final repaired = base64Url.normalize(stripped);
print(repaired); // +//++Q==
final bytes = base64Decode(repaired);
print('decoded ${bytes.length} bytes'); // decoded 4 bytes
}
La sorpresa del carattere %
Questa è un'originalità di Dart. Quando il Base64 compare in un data URI, alcuni strumenti fanno il percent-encoding del padding, scrivendo %3D invece di =, perché un = nudo nella sintassi URL può significare "separatore di parametri". Nella maggior parte dei linguaggi dovresti prima rimuovere le escape. Il decodificatore di Dart no: la sua tabella di ricerca tratta %3D come una notazione nativa del carattere di padding, così puoi passargli il payload così com'è:
import 'dart:convert';
void main() {
final fromDataUri = 'SGVsbG8%3D';
final bytes = base64Decode(fromDataUri);
print(utf8.decode(bytes)); // Hello
}
L'escape viene accettata esattamente dove il padding è legittimo, cioè nella posizione finale. Metti %3D dove un = verrebbe rifiutato e verrà rifiutato allo stesso modo, e %25 fallisce invece al controllo del padding: % è il carattere nativo di escape del padding in Dart, quindi il decodificatore lo legge come un = escapato e rifiuta il 2 con Invalid padding character. In pratica questo significa che un payload ;base64, copiato direttamente dagli strumenti di sviluppo del browser si decodifica senza nessuna pre-elaborazione: un trucco piccolo ma davvero comodo.
Base64 URL-safe
L'RFC 4648 definisce un secondo alfabeto per una sola ragione: quello standard ha tre caratteri, +, / e =, che sono in conflitto con la sintassi URL. L'alfabeto URL-safe, chiamato base64url nell'RFC, sostituisce + con - e / con _, e spesso toglie anche il padding. È l'alfabeto dei JWT, degli ID di oggetto, dei link condivisi e di tutto ciò che vive dentro un URL o un nome file.
Dal lato decodifica, Dart ti dà una risposta sola: entrambi gli alfabeti sono letti dalla stessa macchina. base64Decode e base64Url.decode sono due nomi per lo stesso decodificatore, quindi l'unico lavoro vero è il padding, perché chi produce in URL-safe molto spesso non lo applica. Ed è esattamente a questo che serve normalize:
import 'dart:convert';
void main() {
final bytes = [0xfb, 0xff, 0xfe, 0xf9];
final urlSafe = base64UrlEncode(bytes);
print(urlSafe); // -__--Q==
final repaired = base64Url.normalize(urlSafe.replaceAll('=', ''));
print(repaired); // +//++Q==
print(base64Decode(repaired).length); // 4
}
Due traboccoli da portarti a casa. Non scrivere a mano una sostituzione da - a + prima di decodificare: è inutile, e normalize fa già la conversione dell'alfabeto quando serve. E non dare per scontato che una stringa URL-safe arrivi senza padding: alcuni produttori tengono i pad, e il decodificatore accetta entrambe le varianti, purché il padding sia corretto.
Dai byte al testo: la decisione del charset
La decodifica Base64 ti consegna dei byte. Se quei byte sono testo, devi scegliere la codifica che li trasforma di nuovo in una String, e quella scelta tocca a te, da fare in modo esplicito. L'ipotesi di default nei sistemi moderni è UTF-8, e utf8.decode è il cavallo da lavoro:
import 'dart:convert';
void main() {
final payload = base64Encode(utf8.encode('Héllo Wörld'));
final bytes = base64Decode(payload);
print(utf8.decode(bytes)); // Héllo Wörld
final legacy = base64Encode(latin1.encode('Héllo'));
print(latin1.decode(base64Decode(legacy))); // Héllo
}
Quando i byte non sono UTF-8 valido, utf8.decode lancia una FormatException, ed è il comportamento giusto, molto meglio del mojibake silenzioso. Se sai che i dati sono testo legacy a byte singolo, usa la codifica corrispondente:
| Codifica | Quando usarla | Decodifica con |
|---|---|---|
utf8 |
Testo moderno, JSON, qualsiasi cosa sul web | utf8.decode(bytes) |
latin1 |
Dati legacy occidentali a byte singolo | latin1.decode(bytes) |
ascii |
Testo 7 bit semplice | ascii.decode(bytes) |
Una trappola merita un suo avviso a parte: String.fromCharCodes non è un charset. Legge i byte come unità di codice UTF-16, quindi gli dai i byte UTF-8 di Héllo e stampa Héllo senza battere ciglio. Se vedi quel modello di mojibake nel tuo output, la correzione è quasi sempre utf8.decode.
JWT: leggere il token
Un JSON Web Token è fatto di tre parti base64url unite da punti: header, payload, firma. Il Base64 è usato qui per compattezza e sicurezza URL, non per segretezza. Chiunque abbia il token può leggere header e payload, ed è così per design. La firma è ciò che verifichi, con il segreto condiviso o la chiave pubblica dell'emittente. Decodificare le parti leggibili in Dart richiede poche righe:
import 'dart:convert';
Map<String, dynamic> readJwtPayload(String token) {
final parts = token.split('.');
if (parts.length != 3) {
throw FormatException('Not a compact JWT');
}
final padded = base64Url.normalize(parts[1]);
final bytes = base64Decode(padded);
return jsonDecode(utf8.decode(bytes)) as Map<String, dynamic>;
}
void main() {
const token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9'
'.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkRhcnQgRGV2IiwiaWF0IjoxNTE2MjM5MDIyfQ'
'.c2lnbmF0dXJl';
print(readJwtPayload(token)['name']); // Dart Dev
}
Nota la danza del padding: i JWT sono costruiti senza padding, quindi una parte fallirà una base64Decode diretta ogni volta che la sua lunghezza non è un multiplo di quattro. (Nell'esempio sopra l'header è capitato lungo 36 caratteri e si decodifica direttamente; il payload ne ha 74 e non ce la fa.) La chiamata a normalize rende la riparazione uniforme a prescindere dalla lunghezza. Due avvisi in più. Decodificare non è verificare: controllare la firma e il claim exp è un passo separato e obbligatorio, di solito con il pacchetto crypto per gli algoritmi HMAC. E diffida dei token che dichiarano alg: none; un parser che li accetta è una vulnerabilità, non una funzionalità.
Data URI: file in costume da URL
Un data URI, definito dall'RFC 2397, è un URL il cui payload è i dati stessi: data:image/png;base64, seguito dai byte codificati. Esistono perché i canali solo testo - attributi HTML, regole CSS, documenti JSON - possano portare dati binari senza un file a parte. Il Base64 è il formato di payload preferito, perché l'alternativa, il percent-encoding, è molto più lunga per i dati binari.
E il Dart sa analizzarli nativamente: il supporto ai data URI c'è in dart:core dal 2016, quindi nessuna libreria URI serve:
import 'dart:convert';
void main() {
final uri = Uri.parse('data:image/png;base64,iVBORw0KGgo=');
final data = uri.data!;
print(data.mimeType); // image/png
print(data.isBase64); // true
print('decoded ${data.contentAsBytes().length} bytes');
final textUri = Uri.parse('data:text/plain;base64,SGVsbG8sIERhcnQh');
print(textUri.data!.contentAsString()); // Hello, Dart!
}
L'oggetto UriData ti dà il tipo MIME, il flag isBase64, il testo del payload grezzo e il contenuto decodificato come stringa o come byte. Due traboccoli: il tipo MIME dichiarato può mentire, quindi in codice sensibile alla sicurezza controlla i byte magici reali; e i data URI sono per asset piccoli, perché l'intero payload viaggia dentro il documento che lo riferisce.
File: Base64 su disco
I file Base64 compaiono nei formati di export, nei bundle di provisioning e in qualsiasi trasferimento solo testo che debba portare dati binari. La ricetta è: leggi il testo, appiattiscilo, decodifica, scrivi i byte:
import 'dart:convert';
import 'dart:io';
Future<void> main() async {
final encoded = await File('image.b64').readAsString();
final flat = encoded.replaceAll(RegExp(r'\s+'), '');
final bytes = base64Decode(flat);
await File('image.png').writeAsBytes(bytes);
print('wrote ${bytes.length} bytes');
}
Quel replaceAll fa un lavoro vero. I file di testo sono pieni di a capo, spesso l'avvolgimento MIME a 76 caratteri, e il decodificatore rigoroso li rifiuta, quindi prima appiattisci. L'espressione regolare rimuove ogni carattere di spazio, ed è esattamente ciò che vuoi per un file base64 puro. Se il file può contenere altre annotazioni, come un header PEM, toglile esplicitamente prima di decodificare, e lascia che gli errori del decodificatore catturino ciò che è davvero corrotto.
HTTP e API
Il Base64 in HTTP indossa due costumi. Il primo: le risposte API, un campo JSON che porta dati binari come stringa. Il secondo: l'header Authorization: Basic, dove le credenziali sono codificate in base64 con l'alfabeto standard e il padding:
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> main() async {
final response = await http.get(
Uri.parse('https://httpbin.org/get?attachment=TWFuIGlzIGhlcmU%3D&name=man.txt'),
);
final payload = jsonDecode(response.body) as Map<String, dynamic>;
final args = payload['args'] as Map<String, dynamic>;
final bytes = base64Decode(args['attachment'] as String);
print('got ${bytes.length} bytes');
final credentials = utf8.decode(base64Decode('b2N0b2NhdDpzZWNyZXQ='));
print(credentials.split(':').first); // octocat
}
Il pacchetto http è il client standard, a un dart pub add http di distanza. Per l'autenticazione Basic decodi la parte dopo il prefisso Basic . Due traboccoli: alcune API mandano valori URL-safe o senza padding dove la documentazione dice base64, quindi se la decodifica diretta lancia un errore, fai prima passare il valore da base64Url.normalize; e ricorda che l'autenticazione Basic è offuscamento, non protezione, ed è per questo che ha senso solo su connessioni TLS.
Email e MIME: il problema degli a capo
L'email è il cliente più vecchio del base64. Il MIME avvolge le righe base64 a 76 caratteri - 76 più CRLF stanno comodi su un display a 80 colonne - e l'RFC 2045 dice ai decodificatori di ignorare gli a capo. Il decodificatore di Dart no, e lo fa apposta: li rifiuta. La correzione è appiattire prima di decodificare:
import 'dart:convert';
List<int> decodeMimeBody(String wrapped) {
final flat = wrapped.replaceAll(RegExp(r'\s+'), '');
return base64Decode(flat);
}
void main() {
const wrapped =
'SGVsbG8gZnJvbSBhbiBlbWFpbCBhdHRhY2htZW50LCB3cmFwcGVkIGF0IDc2IGNoYXJhY3RlcnMg'
'\r\n'
'dGhlIHdheSBNSU1FIHdhbnRzIGl0IHRvIGJlLCB3aXRoIENSTEYgYmV0d2VlbiB0aGUgbGluZXMu';
print(utf8.decode(decodeMimeBody(wrapped)));
}
La regola è semplice: togli gli spazi, nient'altro. Non togliere altri caratteri nella speranza di essere utile; il decodificatore è il validatore, e vuoi che si lamenti della corruzione vera. Se elabori email su larga scala, il passaggio di appiattimento costa poco, un giro di espressione regolare, e mantiene onesto il resto della pipeline.
Configurazione e variabili d'ambiente
I token e le credenziali che vivono in configurazioni basate su testo a volte sono codificati in base64 per stare su una riga sola e sembrare token. La cornice onesta: il base64 è offuscamento, non cifratura, quindi questo schema è per ordine, mai per segretezza. Lo schema in sé è banale:
import 'dart:convert';
import 'package:dotenv/dotenv.dart';
Future<void> main() async {
final env = DotEnv()..load();
final encoded = env['API_TOKEN_B64'];
if (encoded == null) {
return;
}
final token = utf8.decode(base64Decode(encoded));
print('loaded a ${token.length}-char token');
}
Con il pacchetto dotenv, il valore sta in un file .env come API_TOKEN_B64=c2stbGl2ZS1hYmMxMjM= e torna come testo normale dopo la decodifica. La stessa forma funziona con String.fromEnvironment per i valori dart-define a tempo di compilazione, con un avvertimento: i valori dart-define sono incrostati nel binario compilato, quindi ciò che è segreto va nella configurazione a runtime o in un secret manager, non lì.
Stream: frammento per frammento
Quando il testo codificato arriva a pezzi - uno stream di rete, un file grande letto a blocchi - il decodificatore se la cava. La sua macchina a stati porta il gruppo parziale oltre i confini dei frammenti, quindi i frammenti non devono allinearsi sui confini a quattro caratteri:
import 'dart:convert';
Future<void> main() async {
final incoming = Stream.fromIterable(['TWF', 'uaGVsbG8=']);
final text = await incoming
.transform(base64.decoder)
.map(utf8.decode)
.join();
print(text); // Manhello
}
La chiamata transform usa il decodificatore come trasformatore di stream; il primo frammento, tre caratteri, parcheggia i suoi bit nello stato del decodificatore, e il secondo frammento completa il gruppo. Gli errori emergono come errori di stream con gli stessi dettagli di FormatException, e uno stream vuoto semplicemente non produce output. Se preferisci i sink, base64.decoder.startChunkedConversion ti dà un StringConversionSink collegato alla stessa macchina a stati.
Dati grandi: la matematica e la memoria
La decodifica rimpicciolisce: quattro caratteri diventano tre byte, quindi l'output è sempre un po' sotto i tre quarti della lunghezza dell'input. Questo significa che la dimensione dell'output è conoscibile prima di decodificare, il che rende la memoria prevedibile. Una funzione ausiliaria la calcola a partire dalla stringa da sola:
import 'dart:convert';
int decodedLength(String encoded) {
var padding = 0;
for (var i = encoded.length - 1; i >= 0 && padding < 2; i--) {
if (encoded.codeUnitAt(i) == 0x3d) {
padding++;
} else {
break;
}
}
return (encoded.length ~/ 4) * 3 - padding;
}
void main() {
print(decodedLength('QQ==')); // 1
print(decodedLength('QUI=')); // 2
print(decodedLength('QUJD')); // 3
}
Il decodificatore integrato è veloce: un solo passaggio su una tabella di ricerca, senza allocazioni di stringa per carattere, quindi le stringhe da diversi megabyte sono una routine. Dove il base64 ti costa davvero è sul lato input: il testo codificato è circa il 33 percento più grande dei dati, ed è una stringa, che sulla VM vive come unità di codice UTF-16, grosso modo il doppio della lunghezza in byte dei caratteri codificati. Per i payload che possono crescere grandi, fai la decodifica in streaming invece di unire tutto in un'unica grande stringa.
Dalla riga di comando
La VM di Dart fa un CLI pulito del decodificatore. Questo piccolo strumento legge un argomento file o lo standard input, appiattisce gli spazi e scrive byte grezzi sullo standard output:
import 'dart:convert';
import 'dart:io';
Future<void> main(List<String> args) async {
String encoded;
if (args.isNotEmpty) {
encoded = await File(args[0]).readAsString();
} else {
encoded = await stdin
.transform(utf8.decoder)
.join();
}
final flat = encoded.replaceAll(RegExp(r'\s+'), '');
stdout.add(base64Decode(flat));
await stdout.flush();
}
Salvalo come bin/decode.dart ed eseguilo con dart run bin/decode.dart image.b64 > image.png, oppure usa un pipe: cat token.b64 | dart run bin/decode.dart. La chiamata stdout.add prende la Uint8List direttamente, senza stringa intermedia, ed è esattamente così che i dati binari dovrebbero muoversi in una pipeline.
I traboccoli che mordono gli sviluppatori Dart
- Il muro del padding. L'input in stile JWT e da strumenti URL arriva spesso senza i segni
=, e il decodificatore lo rifiuta conInvalid length, must be multiple of four. Fai prima passare l'input non fidato dabase64Url.normalize. - La trappola degli spazi. File di testo, email e copia-incolla introducono tutti a capo, e il decodificatore non li salta mai. Appiattisci con
replaceAll(RegExp(r'\s+'), '')prima di decodificare. - Fiducia nell'alfabeto. Poiché entrambi gli alfabeti si decodificano ovunque, non costruire logica su quale decodificatore ha prodotto una stringa. La stringa è il contratto, non le impostazioni del produttore.
- String.fromCharCodes non è un charset. Legge unità di codice UTF-16, quindi trasforma il testo UTF-8 in mojibake. Usa
utf8.decodeo una codifica esplicita. - Due tipi di errore diversi. I problemi di decodifica sono
FormatException; il codificatore lanciaArgumentErrorper valori fuori dall'intervallo da 0 a 255. Catturali separatamente se stai costruendo un confine di fiducia. - Il risultato ha lunghezza fissa.
Uint8Listnon può crescere, quindibytes.add(1)lancia unUnsupportedError. Copia conList<int>.from(bytes)quando ti serve una lista estendibile. - Non rimuovere le escape di %3D a mano. Il decodificatore legge il padding percent-escapato in modo nativo; un
replaceAll('%3D', '=')prematuro lega il tuo codice a un dettaglio che l'SDK già gestisce. - Decodificare il payload di un JWT non è verificarlo. Leggere i claim e fidarsene è un bug di sicurezza in attesa di un utente determinato.
La checklist corta
- Di default usa
base64Decode; rivolgiti anormalizesolo al confine dove l'input non è fidato. - Sii esplicito sul charset con
utf8.decode(bytes), anche quando dai per scontato l'UTF-8. - Tieni i byte come byte finché non sai cos' sono; la
Uint8Listviaggia pulita dentroFile.writeAsBytese compagni. - Ai confini di fiducia, cattura
FormatExceptione logga la posizione dell'input che il messaggio ti dà. - Fai in streaming tutto ciò che potrebbe superare qualche megabyte.
- Tratta il base64 come un formato, non come una protezione: non nasconde nulla a chi sa che è base64.
Una breve storia del Base64 in Dart
Il decodificatore che hai appena incontrato c'è da prima del Dart 3, della null safety e dell'era Flutter. La versione corta:
- 18 novembre 2015, Dart 1.13: il Base64 arriva in
dart:convertcome costanteBASE64più le classiBase64Codec,Base64EncodereBase64Decoder. Prima di questo rilascio, l'SDK non aveva alcun base64. - 28 gennaio 2016, Dart 1.14:
Base64Decoder.convertguadagna i parametri di intervallostarteend, e lo stesso rilascio aggiunge il supporto ai data URI indart:core, il percorso diUri.parsesu cui si regge questo articolo. - 26 aprile 2016, Dart 1.16: entra in scena l'alfabeto URL-safe come
BASE64URLe il costruttoreBase64Codec.urlSafe. - 7 agosto 2018, Dart 2.0: le costanti vengono rinominate in minuscolo
base64ebase64Url, arrivanobase64Decodedi livello superiore e soci, la decodifica restituisce unaUint8Listinvece di unaList<int>estendibile, eBase64Codec.normalizeentra in famiglia, trasformando validazione e riparazione in un passo a chiamata singola. - 2021, Dart 2.12: arriva la null safety, e tutta la storia di
dart:convert, base64 incluso, diventa null-safe. - Oggi, Dart 3.13: le classi sono marcate
final, e il comportamento che hai incontrato qui sopra è la stessa macchina rigorosa, a due alfabeti, attenta ai percent, che gira dal 2015.
La rigidità non è un caso dell'implementazione. È il decodificatore che segue l'istruzione dell'RFC 4648 secondo cui le implementazioni dovrebbero rifiutare i caratteri fuori dall'alfabeto, lasciando la permissività in stile MIME alle applicazioni che ne hanno bisogno: in Dart significa un passo di appiattimento prima della decodifica.
Fatti divertenti
- Il decodificatore legge
%3Dcome padding nativo. Dagli il payload grezzo di un data URI, escape comprese, e lo decodifica. Pochissimi runtime di linguaggi lo farebbero senza un passo di pre-elaborazione. base64.decoderebase64Url.decodersono letteralmente lo stesso oggetto: entrambi sono l'istanza normalizzataconst Base64Decoder(). Il "decodificatore URL-safe" è il decodificatore standard in un altro costume.- L'intero decodificatore sta in una tabella di ricerca con 128 voci, un
Int8Listcondiviso tra interprete e codice compilato AOT, con+e-che puntano entrambi alla casella 62 dell'alfabeto e/e_che puntano entrambi alla 63. - Il base64 di Dart e il suo supporto ai data URI sono arrivati a due rilasci di distanza, nel 1.13 e nel 1.14, ed erano chiaramente pianificati come coppia: uno per leggere il formato, uno per leggerlo dritto da un URL.
- La stringa vuota si decodifica in una
Uint8Listvuota, senza errori, e la stringa vuota si codifica nella stringa vuota: il base64 tratta l'assenza di dati come un messaggio perfettamente valido. - Nel 2018, quando il Dart 2.0 ha rinominato le sue costanti,
BASE64è diventatobase64nell'ambito di un passaggio generalizzato dell'SDK a nomi di costante minuscoli, la stessa ondata che ti ha datoascii,jsoneutf8.
Ora hai il decodificatore completo: cosa accetta, cosa rifiuta, come riparare un input danneggiato, e dove lo incontri nei JWT, nei data URI, nei file, negli stream, nelle email e nel terminale. L'altra direzione dello scambio, prendere i byte e produrre uno dei due alfabeti, con le decisioni sul padding e i calcoli sulle dimensioni, è trattata nel dettaglio nella guida alla codifica Base64, collegata in fondo a questa pagina.
Ultimo aggiornamento: 2026-09-08
Articolo correlato: Codifica Base64 in Dart: una guida completa