Dart में Base64 डिकोडिंग: एक सम्पूर्ण गाइड
यह API रिस्पॉन्स में आता है, URL के अंदर छिपा आता है, या किसी सपोर्ट टिकट में पेस्ट होता है: अक्षरों और अंकों का लंबा सिलसिला, बीच-बीच में कभी-कभी +, /, - या _, और शायद आख़िर में लटके एक-दो = चिह्न। किसी ने इसे Base64 कह दिया है, और आपको चाहिए जो इसमें बंद है। यह गाइड उसे वापस पाने की Dart रेसिपी है। एक तेज़ ओरिएंटेशन, क्योंकि होम पेज पर फ़ॉर्मैट का गहरा दौर होता है: Base64 हर तीन इनपुट बाइट्स को 64-चिह्नों की वर्णमाला से चार चरों में रीवाइट करता है, और आख़िरी चंक छोटा हो तो अंत में एक-दो = पैड जोड़ देता है। डिकोडिंग इसी सौदे की वह दिशा है जिसमें चीज़ सिकुड़ती है: चार चर अंदर जाते हैं, तीन बाइट्स बाहर आते हैं, इसलिए आउटपुट हमेशा इनपुट से लगभग एक-चौथाई कम जगह घेरता है।
अच्छी ख़बर: इंस्टॉल करने को कुछ नहीं है। Base64 साल 2015 के Dart 1.13 से dart:convert लाइब्रेरी के साथ-साथ आ रहा है, और API साल 2018 के Dart 2.0 से स्टेबल है। एक इम्पोर्ट और हाथ में आ जाता है एक तेज़, सख़्त डिकोडर, जो स्टैंडर्ड वर्णमाला और URL-सेफ़ वर्णमाला दोनों पढ़ लेता है।
एक ईमानदारी वाली सीमा: यह कहानी का डिकोडर वाला हिस्सा है। आपको पता चलेगा कि डिकोडर क्या मान लेता है और क्या नकार देता है, पैडिंग कैसे काम करती है, बाइट्स को मोजिबेक के बिना वापस टेक्स्ट कैसे बनाना है, और Base64 से JWTs, data URIs, फ़ाइलों, स्ट्रीम्स, ईमेल, कॉन्फ़िग और कमांड लाइन पर कैसे मिलना है। दूसरी दिशा - बाइट्स को एक स्ट्रिंग में पैक करना - उसका अपना गाइड है, जो इस गाइड के अंत में लिंक है।
एक सख़्त मशीन के चार दरवाज़े
यह रहा पूरा पब्लिक सरफ़ेस जो आप इस्तेमाल करेंगे, सब कुछ dart:convert में है:
| एंट्री | यह क्या है | इसे कब चुनें |
|---|---|---|
base64Decode(source) |
टॉप-लेवल फंक्शन, Uint8List में डिकोड करता है |
रोज़मर्रा की डिकोडिंग, लगभग हमेशा यही |
base64.decode(source) |
कोडेक का डिकोड मेटॉड, बिल्कुल वही व्यवहार | fuse या स्ट्रीम ट्रांसफ़ॉर्मेशन के लिए कोडेक चाहिए हो |
base64Url.decode(source) |
URL-सेफ़ कोडेक का डिकोड मेटॉड | इनपुट URL-सेफ़ के तौर पर डॉक्यूमेंट किया गया है (मशीन वही है) |
base64Url.normalize(source) |
स्ट्रिंग को वैलिडेट और ठीक करता है, पैड करके लौटाता है | इनपुट में पैडिंग गायब हो सकती है, वर्णमालाएँ आपस में मिली-जुली हो सकती हैं, या पर्सेंट-एस्केप्स हो सकते हैं |
दो बातें नज़र आनी चाहिए। पहली: चारों रास्ते एक ही डिकोडर पर ख़त्म होते हैं: एक सख़्त स्टेट मशीन, एक लुकअप टेबल। दूसरी: आख़िरी पंक्ति कोई डिकोडर ही नहीं है। वह एक रिपेयर स्टेशन है, और पहली बार ही अपना काम साबित कर देगी जब कोई पैडिंग-हटा JWT या आधी-सफ़ाई किया हुआ कॉन्फ़िग वैल्यू सामने आए।
आपका पहला डिकोड
डिकोडिंग की ज़िंदगी का नब्बे फ़ीसद पाँच लाइनों में समा जाता है। यह सबसे छोटा उदाहरण है जो काम का पूरा रूप दिखाता है:
import 'dart:convert';
void main() {
final bytes = base64Decode('TWFu');
final text = utf8.decode(bytes);
print(text); // Man
}
अब जो हुआ, उस पर तीन वाक्य। पहला: एंट्री पॉइंट वापस बाइट्स देता है, टेक्स्ट नहीं: base64Decode एक Uint8List लौटाता है, और यह जानबूझकर तय किया गया है, क्योंकि पेलोड एक वाक्य हो सकता है, एक JPEG हो सकता है, या एक हैश - और यह जानने से पहले कि हाथ में क्या है, इनमें से किसी को भी एक जैसा नहीं समझना चाहिए। दूसरा: बाइट्स से टेक्स्ट तक का कूद एक अलग, एक्सप्लिसिट स्टेप है, एक्सप्लिसिट एनकोडिंग के साथ, और वही वह जगह है जहाँ थोड़ी सी लापरवाही में "café" मोजिबेक बन जाता है। तीसरा: खाली स्ट्रिंग एक पहली श्रेणी की वैल्यू है: base64Decode('') बिना किसी एक्सेप्शन और बिना किसी शोर-शराबे के ज़ीरो-लंबाई की लिस्ट देता है।
डिकोडर क्या मान लेता है और क्या नकार देता है
Dart का डिकोडर डिज़ाइन से सख़्त है। RFC 4648 कहता है कि इम्प्लीमेंटेशन को ऐसी इनपुट रिजेक्ट करनी चाहिए जिसमें वर्णमाला के बाहर के चर हों, और Dart उसी पाठ का पालन अक्षर-दर-अक्षर करता है: स्पेस नहीं छोड़ता, लाइन ब्रेक नहीं नज़रअंदाज़ करता, दूसरा मौक़ा नहीं देता। जब इनपुट ग़लत है, तो आपको एक FormatException मिलता है जो इनपुट दिखाता है और बिल्कुल सही चर की तरफ़ इशारा करता है। क्लासिक फ़सादगर्दों पर व्यवहार इस प्रकार है:
| इनपुट | क्या ग़लती है | सटीक एरर |
|---|---|---|
'SGVs bG8s' |
बीच में स्पेस घुस आया | FormatException: Invalid character (at character 5) |
'SGVs\nbG8s' |
बीच में लाइन ब्रेक घुस आया | FormatException: Invalid character (at character 5) |
'SGVs$bG8s' |
डॉलर साइन वर्णमाला में नहीं है | FormatException: Invalid character (at character 5) |
'Zm8' |
पैडिंग बिल्कुल नहीं | FormatException: Invalid length, must be multiple of four (at character 4) |
'Zm8==' |
एक पैड की जगह दो पैड | FormatException: Invalid padding character (at character 5) |
'Zm=8' |
डेटा के बीच में पैडिंग | FormatException: Invalid encoding before padding (at character 3) |
'Zm8=xx' |
पैड के बाद कचरा | FormatException: Invalid padding character (at character 5) |
'Zé' |
नॉन-ASCII चर | FormatException: Invalid character (at character 2) |
मैसेज में दी गई पोज़िशन एक 1-आधारित चर-गणना है, और केरेट के ठीक नीचे इनपुट छपा होता है, इसलिए ख़राब पेलोड को आधा-आधा करके खोजना तेज़ है। इस सख़्ती में एक अच्छी ख़बर छिपी है: डिकोडर दोनों वर्णमालाएँ मान लेता है। स्टैंडर्ड स्ट्रिंग के बीच - या _ हो तो बस, और URL-सेफ़ स्ट्रिंग में + या / हो तो भी बस। वर्णमाला का चुनाव तभी मायने रखता है जब आप वही हों जो टेक्स्ट बना रहे हैं, तब नहीं जब आप उसे पढ़ रहे हों।
पैडिंग: वह नियम जिस पर समझौता नहीं होता
यह वह नियम है जो ज़्यादातर लोगों को हैरान करता है: Dart डिकोडर सही पैडिंग को ज़रूरी मानता है। इनपुट की लंबाई चार चरों का गुणज होनी चाहिए, और आख़िर में मौजूद = चिह्न बिल्कुल सही मात्रा में होने चाहिए। कोई ढीला मोड नहीं है, इसे और ढीला करने का कोई फ्लैग नहीं, और बदलने का कोई सेटिंग नहीं। वज़हें ठोस हैं: बिना पैडिंग की डिकोडिंग कोर्नर केस में दोहरा-मतलब वाली बन जाती है, और RFC की चेतावनी है कि ढीली डिकोडिंग एक छिपा हुआ चैनल खोल सकती है, इसलिए सख़्त पढ़ना ही सुरक्षित वाला है। इसका अमल में मतलब:
| इनपुट | रिज़ल्ट |
|---|---|
'' |
खाली Uint8List, कोई एरर नहीं |
'QQ==' |
1 बाइट: A |
'QUI=' |
2 बाइट्स: AB |
'QUJD' |
3 बाइट्स: ABC |
'Zm8' |
FormatException: ग़लत लंबाई |
'Zm8==' |
FormatException: ग़लत पैडिंग चर |
जब इनपुट ऐसे सिस्टम से आता है जो पैडिंग हटा देते हैं - और JWTs में तो पैडिंग-रहित वैल्यूएँ ही भरी हुई हैं - तो रिपेयर स्टेप normalize पर एक कॉल है। यह स्ट्रिंग को वैलिडेट करता है, URL-सेफ़ चरों को स्टैंडर्ड वर्णमाला में बदलता है, और ग़ायब पैड जोड़ देता है:
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
}
पर्सेंट साइन की सरप्राइज़
यह तो Dart की अपनी ओरिजिनल चीज़ है। जब Base64 किसी data URI में दिखता है, तो कुछ टूल्स पैडिंग को पर्सेंट-एन्कोड कर देते हैं, %3D लिखते हैं = के बजाय, क्योंकि खाली = URL सिंटैक्स में "पैरामीटर सैपरेटर" भी मतलब रखता है। ज़्यादातर भाषाएँ आपको पहले यूनएस्केप करने पर मजबूर करतीं। Dart का डिकोडर नहीं: उसकी लुकअप टेबल %3D को पैडिंग चर की जन्मजात स्पेलिंग ही समझती है, इसलिए आप सीधा रॉ पेलोड भी थमा सकते हैं:
import 'dart:convert';
void main() {
final fromDataUri = 'SGVsbG8%3D';
final bytes = base64Decode(fromDataUri);
print(utf8.decode(bytes)); // Hello
}
एस्केप बस वहीँ माना जाता है जहाँ पैडिंग क़ानूनी है - यानी आख़िरी पोज़िशन पर। %3D जहाँ रखें जहाँ = रिजेक्ट होता, वहाँ वह भी वैसे ही रिजेक्ट हो जाता है, और %25 की बारी में पैडिंग चैक ही फेल होती है - % Dart का जन्मजात पैडिंग-एस्केप चर है, इसलिए डिकोडर उसे एस्केप किया हुआ = पढ़ता है और 2 को Invalid padding character के साथ रिजेक्ट कर देता है। अमल में इसका मतलब: ब्राउज़र के डेवलपर टूल्स से सीधा कॉपी किया हुआ ;base64, पेलोड बिना किसी पूर्व-प्रोसेसिंग के डिकोड हो जाता है - छोटी ट्रिक, पर सच में काम आने वाला।
URL-सेफ़ Base64
RFC 4648 एक ही वज़ह से दूसरी वर्णमाला तय करता है: स्टैंडर्ड वर्णमाला में तीन चर हैं, +, / और =, जो URL सिंटैक्स से टकराते हैं। URL-सेफ़ वर्णमाला, जो RFC में base64url कहलाती है, + की जगह - और / की जगह _ लगाती है, और अक्सर पैडिंग भी गिरा देती है। यह JWTs, object IDs, शेयर लिंक और हर उस चीज़ की वर्णमाला है जो URL के अंदर या फ़ाइल-नेम के अंदर रहती है।
डिकोडिंग की तरफ़ Dart आपको एक ही जवाब देता है: दोनों वर्णमालाएँ एक ही मशीन पढ़ती है। base64Decode और base64Url.decode एक ही डिकोडर के दो नाम हैं, इसलिए असली काम सिर्फ़ पैडिंग का है, क्योंकि URL-सेफ़ प्रोड्यूसर अक्सर पैडिंग के बिना ही डेटा भेजते हैं। 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
}
आपको दो गड्ढे साथ लिए जाते हैं। डिकोडिंग से पहले अपने हाथ से - को + में बदलने वाला काम मत करना; यह ज़रूरी नहीं है, और ज़रूरत पड़ने पर normalize वर्णमाला बदलना पहले से कर देता है। और यह भी मत मान बैठना कि URL-सेफ़ स्ट्रिंग हमेशा बिना पैडिंग आती है: कुछ प्रोड्यूसर पैड बनाए रखते हैं, और सही पैडिंग हो तो डिकोडर दोनों को मान लेता है।
बाइट्स से टेक्स्ट तक: कैरेक्टर सेट का फैसला
Base64 डिकोडिंग आपको बाइट्स देता है। अगर वो बाइट्स टेक्स्ट हैं, तो आपको वह एनकोडिंग चुननी होगी जो उन्हें वापस String में बदलें, और वह चुनाव आपको खुद एक्सप्लिसिट रूप से करना होता है। मॉडर्न सिस्टम में डिफ़ॉल्ट मान्यता UTF-8 है, और utf8.decode ही वह मज़दूर है जो पूरा भार उठाता है:
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
}
जब बाइट्स वैलिड UTF-8 नहीं होते, तो utf8.decode एक FormatException फेंकता है, जो सही व्यवहार है - चुपचाप मोजिबेक से कहीं बेहतर। अगर आपको पता है कि डेटा लेगासी सिंगल-बाइट टेक्स्ट है, तो मेल खाती एनकोडिंग इस्तेमाल करें:
| एनकोडिंग | इसे किसके लिए | इससे डिकोड करें |
|---|---|---|
utf8 |
मॉडर्न टेक्स्ट, JSON, वेब पर सब कुछ | utf8.decode(bytes) |
latin1 |
लेगासी पश्चिमी सिंगल-बाइट डेटा | latin1.decode(bytes) |
ascii |
सादा 7-बिट टेक्स्ट | ascii.decode(bytes) |
एक गड्ढे को अपनी अलग चेतावनी का हक़ है: String.fromCharCodes कोई कैरेक्टर सेट नहीं है। यह बाइट्स को UTF-16 कोड यूनिट्स के तौर पर पढ़ता है, इसलिए Héllo के UTF-8 बाइट्स डालो तो वह सीधा मुँह खड़े करके Héllo छाप देगा। अगर अपने आउटपुट में वह मोजिबेक पैटर्न नज़र आए, तो इलाज लगभग हमेशा utf8.decode ही है।
JWTs: टोकन पढ़ना
JSON Web Token तीन base64url हिस्सों से बनता है जो डॉट्स से जुड़े होते हैं: हेडर, पेलोड, सिग्नेचर। यहाँ Base64 का इस्तेमाल कॉम्पैक्टनेस और URL-सुरक्षा के लिए होता है, गुप्तता के लिए नहीं। जिस के पास भी टोकन है वह हेडर और पेलोड पढ़ सकता है, और यही डिज़ाइन है। सिग्नेचर वही चीज़ है जिसे आप वरिफ़ाई करते हैं - साझा सीक्रेट से या आइश्यूरर की public key से। Dart में पढ़े जाने वाले हिस्सों को डिकोड करने में कुछ लाइनें लगती हैं:
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
}
पैडिंग के नर्तन पर नज़र रखें: JWTs बिना पैडिंग के बने होते हैं, इसलिए जिस हिस्से की लंबाई चार का गुणज न हो, वह सीधे base64Decode पर फेल हो जाएगा। (ऊपर के उदाहरण में हेडर बस-बस 36 चरों की लंबी है और सीधे डिकोड हो जाती है; पेलोड 74 चरों की है और नहीं होती।) normalize कॉल लंबाई चाहे जो हो, रिपेयर को एक जैसे बना देता है। दो और चेतावनियाँ। डिकोडिंग, वेरिफिकेशन नहीं है: सिग्नेचर और exp क्लेम जाँचना एक अलग, ज़रूरी स्टेप है, HMAC एल्गोरिदम के लिए आमतौर पर crypto पैकेज से। और ऐसे टोकन से शक बनाए रखें जो alg: none दावा करते हैं; पार्सर जो उन्हें मान लेता है, वह सुरक्षा-दुर्बलता है, फीचर नहीं।
Data URIs: URL के कपड़े पहनी हुई फ़ाइलें
Data URI, जिसे RFC 2397 में तय किया गया है, एक URL है जिसका पेलोड खुद डेटा है: data:image/png;base64, और उसके बाद एन्कोड किए गए बाइट्स। इनका अस्तित्व इसलिए है ताकि सिर्फ़-टेक्स्ट चैनल्स - HTML एट्रिब्यूट, CSS नियम, JSON डॉक्यूमेंट्स - अलग फ़ाइल के बिना बायनेरी ले जा सकें। Base64 पेलोड का चुना हुआ फ़ॉर्मेट है, क्योंकि विकल्प, परसेंट-एन्कोडिंग, बायनेरी डेटा के लिए कहीं लंबा पड़ता है।
और Dart इन्हें खुद ही पार्स कर सकता है: data URI सपॉर्ट 2016 से dart:core में है, इसलिए कोई URI लाइब्रेरी ज़रूरी नहीं:
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!
}
UriData ऑब्जेक्ट आपको MIME टाइप, isBase64 फ्लैग, राव पेलोड टेक्स्ट, और डिकोड किया हुआ कंटेंट देता है - स्ट्रिंग के तौर पर या बाइट्स के तौर पर। दो गड्ढे: घोषित MIME टाइप झूठ बोल सकता है, इसलिए सिक्योरिटी-सेंसिटिव कोड में असली मैजिक बाइट्स जाँचें; और data URIs छोटे एसेट्स के लिए हैं, क्योंकि पूरा पेलोड उसी डॉक्यूमेंट के अंदर सवार रहता है जो उसे रिफ़रेंस करता है।
फ़ाइलें: डिस्क पर Base64
Base64 फ़ाइलें एक्सपोर्ट फ़ॉर्मेट्स, प्रोविज़निंग बंडल, और हर सिर्फ़-टेक्स्ट ट्रांसफर में दिखती हैं जिसको बायनेरी ले जानी होती है। रेसिपी है: टेक्स्ट पढ़ो, उसे सपाट (सपाट) करो, डिकोड करो, बाइट्स लिखो:
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');
}
वह replaceAll असली काम कर रहा है। टेक्स्ट फ़ाइलें लाइन ब्रेक्स से भरी होती हैं, अक्सर 76-चर वाले MIME रैपिंग की शक्ल में, और सख़्त डिकोडर उन्हें रिजेक्ट कर देता है, इसलिए पहले सपाट करो। रेगुलर एक्सप्रेशन हर व्हाइटस्पेस चर हटा देता है, जो शुद्ध base64 फ़ाइल के लिए बिल्कुल सही है। अगर फ़ाइल में कोई और एनोटेशन हो सकता है, जैसे PEM हेडर, तो डिकोड करने से पहले उन्हें एक्सप्लिसिट रूप से हटाओ, और असली ख़राबी को पकड़ने का काम डिकोडर के एरर पर छोड़ दो।
HTTP और APIs
HTTP में Base64 दो कपड़े पहनता है। पहला, API रिस्पॉन्स: एक JSON फ़ील्ड जो बायनेरी को स्ट्रिंग के तौर पर ले जाती है। दूसरा, Authorization: Basic हेडर, जहाँ क्रेडेंशियल्स को स्टैंडर्ड वर्णमाला और पैडिंग के साथ base64 एन्कोड किया जाता है:
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
}
http पैकेज स्टैंडर्ड क्लाइंट है, बस एक dart pub add http दूर। Basic auth के लिए आप Basic प्रेफ़िक्स के बाद वाले हिस्से को डिकोड करते हैं। दो गड्ढे: कुछ APIs वहाँ URL-safe या बिना-पैडिंग वैल्यूएँ भेजती हैं जहाँ डॉक्यूमेंटेशन में base64 लिखा होता है, इसलिए अगर सीधी डिकोडिंग एरर दे, तो पहले वैल्यू को base64Url.normalize से गुज़ारो; और याद रखो कि Basic auth छुपाई है, सुरक्षा नहीं - इसी लिए यह सिर्फ़ TLS कनेक्शन्स पर ही जगह रखता है।
ईमेल और MIME: लाइन-ब्रेक की समस्या
ईमेल सबसे पुराना base64 कस्टमर है। MIME base64 लाइन्स को 76 चरों पर रैप करता है - 76 प्लस CRLF एक 80-कॉलम डिस्प्ले पर आराम से बैठ जाता है - और RFC 2045 डिकोडरों को लाइन ब्रेक्स नज़रअंदाज़ करने को कहता है। Dart का डिकोडर ऐसा नहीं करता, जानबूझकर: वह उन्हें रिजेक्ट कर देता है। इलाज है डिकोड करने से पहले सपाट करना:
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)));
}
नियम सादा है: व्हाइटस्पेस हटाओ, कुछ और नहीं। मदद करने की उम्मीद में कोई और चर मत हटाओ; डिकोडर ही वैलिडेटर है, और आप चाहते हो कि वह असली ख़राबी पर शिकायत करे। अगर आप बड़े पैमाने पर ईमेल प्रोसेस कर रहे हैं, तो सपाट स्टेप सस्ता है - एक रेगुलर एक्सप्रेशन पास - और यह बाकी पूरे पिपलाइन को ईमानदार रखता है।
कॉन्फ़िग्यूरेशन और एनवायरन्मेंट वेरिएबल्स
जो टोकन्स और क्रेडेंशियल्स टेक्स्ट-आधारित कॉन्फ़िग में रहते हैं, उन्हें कभी-कभी base64 एन्कोड किया जाता है ताकि वे एक लाइन पर रहें और टोकन्स जैसे दिखें। ईमानदारी से कहें तो: base64 छुपाई है, एन्क्रिप्शन नहीं, इसलिए यह पैटर्न साफ-सुथरापन के लिए है, गुप्तता के लिए कभी नहीं। पैटर्न खुद बेहद आसान है:
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');
}
dotenv पैकेज के साथ, वैल्यू .env फ़ाइल में API_TOKEN_B64=c2stbGl2ZS1hYmMxMjM= की शक्ल में बैठती है, और डिकोड के बाद सादे टेक्स्ट के रूप में वापस आ जाती है। वही रूप String.fromEnvironment के साथ कंपाइल-टाइम dart-define वैल्यूज़ के लिए भी काम करता है, एक चेतावनी के साथ: dart-define वैल्यूज़ कंपाइल्ड बायनेरी में जम जाती हैं, इसलिए कोई भी सीक्रेट चीज़ रनटाइम कॉन्फ़िग या सीक्रेट मैनेजर में ही रहनी चाहिए, वहाँ नहीं।
स्ट्रीम्स: चंक-दर-चंक
जब एन्कोड टेक्स्ट टुकड़ों में आता है - कोई नेटवर्क स्ट्रीम, बड़ी फ़ाइल जो ब्लॉक्स में पढ़ी जा रही हो - तो डिकोडर उगलता नहीं। उसकी स्टेट मशीन अधूरे ग्रुप को चंक बाउंडरीज़ के पार ले जाती है, इसलिए चंक्स को चार-चर की सीमाओं पर सजाने की ज़रूरत नहीं होती:
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
}
transform कॉल डिकोडर का इस्तेमाल स्ट्रीम ट्रांसफ़ॉर्मर के तौर पर करता है; पहला चंक, तीन चरों वाला, अपने बिट्स को डिकोडर की स्टेट में पार्क कर देता है, और दूसरा चंक ग्रुप को पूरा कर देता है। एरर वही FormatException विवरणों के साथ स्ट्रीम एरर्स के रूप में सामने आते हैं, और खाली स्ट्रीम बस कोई आउटपुट नहीं देता। अगर आप सिंक्स ज़्यादा पसंद करते हैं, तो base64.decoder.startChunkedConversion आपको एक StringConversionSink देता है जो उसी स्टेट मशीन से जुड़ा होता है।
बड़ा डेटा: गणित और मेमोरी
डिकोडिंग सिकुड़ती है: चार चर तीन बाइट्स बन जाते हैं, इसलिए आउटपुट हमेशा इनपुट लंबाई का तीन-चौथाई से बस थोड़ा कम होता है। इसका मतलब है कि आउटपुट का आकार डिकोड करने से पहले ही पता हो सकता है, जिससे मेमोरी की ज़रूरत पहले से आँकी जा सकती है। एक छोटा हेल्पर इसे बस स्ट्रिंग से ही निकाल लेता है:
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
}
बिल्ट-इन डिकोडर तेज़ है: लुकअप टेबल पर एक ही पास, हर चर पर स्ट्रिंग एलोकेशन के बिना, इसलिए कई-मेगाबाइट की स्ट्रिंग्स रूटीन हैं। base64 जहाँ असल में कीमत लेता है वह इनपुट की तरफ़ है: एन्कोड टेक्स्ट डेटा से करीब 33 फ़ीसद बड़ा होता है, और वह स्ट्रिंग है, जो VM पर UTF-16 कोड यूनिट्स के रूप में रहती है - एन्कोड चरों की बाइट लंबाई का लगभग दोगुना। ऐसे पेलोड्स के लिए जो बड़े हो सकते हैं, बड़ी एक स्ट्रिंग जोड़ने के बजाय डिकोड को स्ट्रीम करो।
कमांड लाइन से
Dart का VM डिकोडर से एक साफ़-सुथरा CLI बना देता है। यह छोटा टूल फ़ाइल आर्गुमेंट या स्टैंडर्ड इनपुट पढ़ता है, व्हाइटस्पेस सपाट करता है, और राव बाइट्स को स्टैंडर्ड आउटपुट पर लिख देता है:
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();
}
इसे bin/decode.dart के तौर पर सेव करो और चलाओ dart run bin/decode.dart image.b64 > image.png, या पाइप करो: cat token.b64 | dart run bin/decode.dart। stdout.add कॉल Uint8List को सीधे लेता है, बीच की कोई स्ट्रिंग नहीं, और यही बायनेरी को पिपलाइन में आगे बढ़ाने का सही ढंग है।
ऐसे गड्ढे जो Dart डेवलपर्स को काटते हैं
- पैडिंग की दीवार। JWT-स्टाइल और URL-टूल इनपुट अक्सर
=चिह्न के बिना आता है, और डिकोडर उसेInvalid length, must be multiple of fourके साथ नकार देता है। अविश्वसनीय इनपुट को पहलेbase64Url.normalizeसे गुज़ारो। - व्हाइटस्पेस ट्रैप। टेक्स्ट फ़ाइलें, ईमेल और कोपी-पेस्ट - सब लाइन ब्रेक्स लाते हैं, और डिकोडर कभी भी उन्हें छोड़ नहीं करता। डिकोड करने से पहले
replaceAll(RegExp(r'\s+'), '')से सपाट करो। - वर्णमाला की भरोसेमंदी। क्योंकि दोनों वर्णमालाएँ हर जगह डिकोड हो जाती हैं, इस बात पर लॉजिक मत बनाओ कि स्ट्रिंग किसने बनाई। स्ट्रिंग ही कॉन्ट्रैक्ट है, प्रोड्यूसर की सेटिंग्स नहीं।
- String.fromCharCodes कोई कैरेक्टर सेट नहीं है। यह UTF-16 कोड यूनिट्स पढ़ता है, इसलिए यह UTF-8 टेक्स्ट को मोजिबेक बना देता है।
utf8.decodeया कोई एक्सप्लिसिट एनकोडिंग इस्तेमाल करो। - दो अलग एरर टाइप्स। डिकोड की समस्याएँ
FormatExceptionहैं; एन्कोडर 0 से 255 के रेंज के बाहर की वैल्यूज़ के लिएArgumentErrorफेंकता है। अगर आप किसी बाउंडरी बना रहे हो, तो उन्हें अलग-अलग कैच करो। - रिज़ल्ट फिक्स्ड-लेंथ होता है।
Uint8Listबड़ा नहीं हो सकता, इसलिएbytes.add(1)एकUnsupportedErrorफेंकता है। बढ़ने-योग्य लिस्ट चाहिए हो तोList<int>.from(bytes)से कॉपी करो। - %3D को हाथ से यूनएस्केप मत करो। डिकोडर पर्सेंट-एस्केप पैडिंग को खुद-ब-खुद पढ़ता है; जल्दीबाज़ी में
replaceAll('%3D', '=')आपका कोड उस डिटेल से बाँध देता है जिसे SDK पहले से संभाल रहा है। - JWT पेलोड को डिकोड करना, उसे वरिफ़ाई करना नहीं है। क्लेम्स पढ़कर उन्हें भरोसा देना एक सिक्योरिटी बग है जो एक ठोस निश्चय के यूज़र का इंतज़ार कर रहा है।
बेस्ट प्रैक्टिस: छोटी-सी लिस्ट
- डिफ़ॉल्ट में
base64Decodeरखो;normalizeसिर्फ़ उस बाउंडरी पर पकड़ो जहाँ इनपुट अविश्वसनीय हो। utf8.decode(bytes)के साथ कैरेक्टर सेट का ज़िक्र एक्सप्लिसिट रखो, भले ही आप UTF-8 की धारणा पर चल रहे हों।- बाइट्स को तब तक बाइट्स ही रहने दो जब तक आपको पता न चले कि वे क्या हैं;
Uint8ListकोFile.writeAsBytesऔर उसके साथियों के पास साफ़-साफ़ पहुँचता है। - भरोसे की बाउंडरीज पर
FormatExceptionको कैच करो, और मैसेज जो इनपुट पोज़िशन देता है, उसे लॉग करो। - जो भी कुछ मेगाबाइट के पार जा सकता है, उसे स्ट्रीम करो।
- base64 को एक फ़ॉर्मेट के तौर पर ही समझो, सुरक्षा के तौर पर नहीं: जो यह जानता है कि यह base64 है, उससे यह कुछ भी छुपा नहीं पाता।
Dart में Base64 का छोटा-सा इतिहास
जो डिकोडर आपने अभी मिला है, उसकी उम्र Dart 3, null सेफ़्टी और Flutter का दौर से भी ज़्यादा है। छोटा संस्करण:
- 18 नवंबर 2015, Dart 1.13: Base64
dart:convertमें आता है -BASE64कॉन्स्टेंट के साथ-साथBase64Codec,Base64EncoderऔरBase64Decoderक्लासेज़। इस रिलीज़ से पहले SDK में base64 बिल्कुल नहीं था। - 28 जनवरी 2016, Dart 1.14:
Base64Decoder.convertकोstartऔरendरेंज पैरामीटर मिलते हैं, और वही रिलीज़dart:coreमें data URI सपॉर्ट जोड़ती है -Uri.parseवाला रास्ता, जिस पर यह लेख टिका है। - 26 अप्रैल 2016, Dart 1.16: URL-सेफ़ वर्णमाला
BASE64URLऔरBase64Codec.urlSafeकॉन्स्ट्रक्टर के रूप में शामिल हो जाती है। - 7 अगस्त 2018, Dart 2.0: कॉन्स्टेंट्स के नाम छोटे अक्षरों में बदलकर
base64औरbase64Urlहो जाते हैं, टॉप-लेवलbase64Decodeऔर उसके साथी आ जाते हैं, डिकोडिंग अबUint8Listलौटाती है, बढ़ने-योग्यList<int>के बजाय, औरBase64Codec.normalizeपरिवार में शामिल हो जाती है - वैलिडेशन और रिपेयर एक कॉल वाले स्टेप बन जाते हैं। - 2021, Dart 2.12: null safety आ जाती है, और पूरी
dart:convertकहानी, base64 समेत, null-सेफ़ बन जाती है। - आज, Dart 3.13: क्लासेज़ को
finalचिह्नित किया गया है, और ऊपर जो व्यवहार आपने देखा, वही वह सख़्त, दोनों-वर्णमालाओं वाली, पर्सेंट-समझदारी वाली मशीन है जो 2015 से चल रही है।
यह सख़्तगी इम्प्लीमेंटेशन की कोई ग़लती नहीं है। यह डिकोडर का RFC 4648 के उस निर्देश का पालन है कि इम्प्लीमेंटेशन को वर्णमाला-बाहरी चरों को रिजेक्ट करना चाहिए, और MIME-स्टाइल की ढील उन एप्लिकेशन पर छोड़ी गई है जिन्हें वह चाहिए - Dart में इसका मतलब डिकोड से पहले एक सपाट स्टेप है।
मज़ेदार तथ्य
- डिकोडर
%3Dको जन्मजात पैडिंग के तौर पर पढ़ता है। data URI का राव पेलोड, एस्केप समेत, थमा दो - वह डिकोड कर लेता है। बेहद कम भाषा रनटाइम ऐसा बिना किसी पूर्व-प्रोसेसिंग स्टेप के कर पाते हैं। base64.decoderऔरbase64Url.decoderबिल्कुल एक ही ऑब्जेक्ट हैं: दोनों कैनॉनिज़्डconst Base64Decoder()इंस्टेंस हैं। "URL-सेफ़ डिकोडर" बस स्टैंडर्ड डिकोडर है जो दूसरे कपड़ों में है।- पूरा डिकोडर एक 128-एंट्री लुकअप टेबल में समा जाता है - एक
Int8List, जो इंटरप्रेटर और AOT-कंपाइल्ड कोड दोनों में शेयर की जाती है, जहाँ+और-दोनों वर्णमाला के स्लॉट 62 पर इशारा करते हैं और/और_दोनों 63 पर। - Dart का base64 और उसकी data URI सपॉर्ट दो रिलीज़ के फ़ासले पर आईं, 1.13 और 1.14 में, और साफ़ है कि दोनों को जोड़े के तौर पर प्लान किया गया था: एक फ़ॉर्मेट पढ़ने के लिए, एक उसे सीधे URL से पढ़ने के लिए।
- खाली स्ट्रिंग खाली
Uint8Listमें डिकोड होती है, बिना एरर, और खाली स्ट्रिंग एन्कोड होने पर खाली स्ट्रिंग ही बनती है: base64 डेटा के न होने को भी एक बिल्कुल वैध मैसेज मानता है। - 2018 में, जब Dart 2.0 ने अपने कॉन्स्टेंट्स के नाम बदले,
BASE64base64बन गया - यह SDK-भर के लोअरकेस कॉन्स्टेंट नाम की ओर बढ़ने का हिस्सा था, वही लहर जिसने आपकोascii,jsonऔरutf8दिए।
अब आपके पास पूरा डिकोडर है: यह क्या मानता है, क्या नकारता है, ख़राब इनपुट का रिपेयर कैसे करना है, और JWTs, data URIs, फ़ाइलों, स्ट्रीम्स, ईमेल और शेल में इससे कैसे मिलना है। सौदे की दूसरी दिशा - बाइट्स लेकर दोनों वर्णमालाओं में से एक बनाना, पैडिंग के फैसलों और साइज़ के गणित के साथ - उसका पूरा विवरण Base64 एन्कोडिंग गाइड में है, जो इस पेज के अंत में लिंक है।
अंतिम अपडेट: 2026-09-08
संबंधित लेख: Dart में Base64 एन्कोडिंग: एक सम्पूर्ण गाइड