--- a/Base64Coder.st Sun Sep 30 15:55:17 2018 +0200
+++ b/Base64Coder.st Sun Sep 30 15:55:25 2018 +0200
@@ -1,3 +1,5 @@
+"{ Encoding: utf8 }"
+
"
COPYRIGHT (c) 2002 by eXept Software AG
All Rights Reserved
@@ -55,7 +57,8 @@
Stefan Vogel
[see also:]
-
+ RFC https://tools.ietf.org/html/rfc4648
+
[instance variables:]
[class variables:]
@@ -118,6 +121,24 @@
Transcript showCR:(coder contents).
[exEnd]
+ [exBegin]
+ |bytes encoded decoded|
+
+ bytes := #[0 0 0] copy.
+ 0 to:255 do:[:b1 |
+ Transcript showCR:b1.
+ bytes at:1 put:b1.
+ 0 to:255 do:[:b2 |
+ bytes at:2 put:b2.
+ 0 to:255 do:[:b3 |
+ bytes at:3 put:b3.
+ encoded := Base64Coder encode:bytes.
+ decoded := Base64Coder decode:encoded.
+ self assert:(decoded = bytes).
+ ]
+ ]
+ ].
+ [exEnd]
"
! !
@@ -129,15 +150,149 @@
Base64Mapping isNil ifTrue:[
"65 characters representing the 6-bit values from 0-63 and one pad character"
Base64Mapping := 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='.
- Base64ReverseMapping := ByteArray new:128 withAll:255.
- Base64Mapping keysAndValuesDo:[:idx :char|
- Base64ReverseMapping at:char codePoint put:idx-1.
- ].
+ Base64ReverseMapping := self reverseMappingFor:Base64Mapping.
].
"
+ Base64Mapping := nil.
self initializeMappings
"
+
+ "Modified (comment): / 30-09-2018 / 15:39:44 / Claus Gittinger"
+!
+
+mapping
+ ^ Base64Mapping
+
+ "Created: / 30-09-2018 / 15:30:33 / Claus Gittinger"
+!
+
+reverseMapping
+ ^ Base64ReverseMapping
+
+ "Created: / 30-09-2018 / 15:30:40 / Claus Gittinger"
+! !
+
+!Base64Coder class methodsFor:'decoding'!
+
+decode:aStringOrStream
+ "because base64 decoding is used heavily in some protocols,
+ a specially tuned version is provided here for the common case of decoding a string"
+
+ ^ super decode:aStringOrStream.
+
+ "Created: / 30-09-2018 / 14:14:51 / Claus Gittinger"
+!
+
+fastDecodeString:aString
+ "because base64 decoding is used heavily in some protocols,
+ a specially tuned version is provided here for the common case of decoding a string"
+
+ ^ self fastDecodeString:aString asString:false
+
+ "
+ (Base64Coder encode:'queen%27s%20gambit') asString => 'cXVlZW4lMjdzJTIwZ2FtYml0'
+
+ (Base64Coder fastDecodeString:'cXVlZW4lMjdzJTIwZ2FtYml0') asString => 'queen%27s%20gambit'
+ "
+
+ "Created: / 30-09-2018 / 14:36:58 / Claus Gittinger"
+!
+
+fastDecodeString:aString asString:asStringBoolean
+ "because base64 decoding is used heavily in some protocols,
+ a specially tuned version is provided here for the common case of decoding a string"
+
+ |decoding|
+
+%{
+ char quickBuffer[512];
+ char *buffer = quickBuffer;
+ int bufferSize = sizeof(quickBuffer);
+ int outLen = 0;
+ int charBuffer = 0;
+ int _bits = 0;
+ int numChars = __stringSize(aString);
+ char *in = __stringVal(aString);
+ int i;
+
+ for (i=0; i<numChars; i++) {
+ char ch = in[i];
+ int bits = -1;
+
+ if (((unsigned)(ch - 'A')) <= 25) {
+ bits = ((unsigned)(ch - 'A')); goto ok;
+ } else if (((unsigned)(ch - 'a')) <= 25) {
+ bits = ((unsigned)(ch - 'a')) + 26; goto ok;
+ } else if (((unsigned)(ch - '0')) <= 9) {
+ bits = ((unsigned)(ch - '0')) + 34; goto ok;
+ } else if (ch == '+') {
+ bits = 0x3E; goto ok;
+ } else if (ch == '/') {
+ bits = 0x3F; goto ok;
+ }
+ if (bits >= 0) {
+ ok:
+ charBuffer = (charBuffer << 6) | bits;
+ _bits += 6;
+ if (_bits == 24) {
+ if ((outLen + 3) > bufferSize) {
+ if (buffer == quickBuffer) {
+ buffer = (char *)malloc(bufferSize*2);
+ memcpy(buffer, quickBuffer, bufferSize);
+ } else {
+ buffer = (char *)realloc(buffer, bufferSize*2);
+ }
+ bufferSize = bufferSize * 2;
+ }
+ buffer[outLen] = (charBuffer >> 16) & 0xFF;
+ buffer[outLen+1] = (charBuffer >> 8) & 0xFF;
+ buffer[outLen+2] = (charBuffer) & 0xFF;
+ charBuffer = 0;
+ outLen += 3;
+ _bits = 0;
+ }
+ } else {
+ if (ch == '=') {
+ // end mark
+ if (_bits == 12) {
+ // data has been padded to 12, skip 4 bits
+ charBuffer >>= 4;
+ _bits -= 4;
+ } else if (_bits == 18) {
+ // data has been padded to 18, skip 2 bits
+ charBuffer >>= 2;
+ _bits -= 2;
+ }
+ } else {
+ // ignore
+ }
+ }
+ }
+
+ if (_bits != 0) {
+ }
+
+ if (asStringBoolean == true) {
+ decoding = __MKSTRING_L(buffer, outLen);
+ } else {
+ decoding = __MKBYTEARRAY(buffer, outLen);
+ }
+ if (buffer != quickBuffer) {
+ free(buffer);
+ }
+%}.
+ ^ decoding.
+
+ "
+ (Base64Coder encode:'queen%27s%20gambit') asString => 'cXVlZW4lMjdzJTIwZ2FtYml0'
+
+ (Base64Coder decode:'cXVlZW4lMjdzJTIwZ2FtYml0') asString => 'queen%27s%20gambit'
+ (Base64Coder fastDecodeString:'cXVlZW4lMjdzJTIwZ2FtYml0') asString => 'queen%27s%20gambit'
+ (Base64Coder fastDecodeString:'cXVlZW4lMjdzJTIwZ2FtYml0' asString:true) => 'queen%27s%20gambit'
+ "
+
+ "Created: / 30-09-2018 / 14:35:05 / Claus Gittinger"
! !
!Base64Coder methodsFor:'encoding'!
@@ -167,15 +322,16 @@
b1 := (bufferedBytes bitShift:-18) bitAnd:16r3F.
buffer := bits := 0.
- stream nextPut:(Base64Mapping at:b1+1);
- nextPut:(Base64Mapping at:b2+1);
- nextPut:(Base64Mapping at:b3+1);
- nextPut:(Base64Mapping at:b4+1).
+ stream nextPut:(mapping at:b1+1);
+ nextPut:(mapping at:b2+1);
+ nextPut:(mapping at:b3+1);
+ nextPut:(mapping at:b4+1).
charCount := charCount + 4.
].
"Modified: / 26-08-2017 / 12:35:17 / cg"
+ "Modified: / 30-09-2018 / 15:15:14 / Claus Gittinger"
! !
!Base64Coder methodsFor:'misc'!
@@ -214,22 +370,28 @@
charCount := 0.
].
- stream nextPut:(Base64Mapping at:b1+1);
- nextPut:(Base64Mapping at:b2+1);
- nextPut:(Base64Mapping at:b3+1);
- nextPut:(Base64Mapping at:b4+1).
+ stream nextPut:(mapping at:b1+1);
+ nextPut:(mapping at:b2+1);
+ nextPut:(mapping at:b3+1);
+ nextPut:(mapping at:b4+1).
charCount := charCount + 4.
+
+ "Modified: / 30-09-2018 / 15:15:52 / Claus Gittinger"
! !
!Base64Coder methodsFor:'private'!
fillBuffer
- "fill buffer with next 4 characters each representing 6 bits"
+ "fill buffer with next 4 characters each representing 6 bits.
+ Used when decoding."
- |b shift tempBuffer "{Class: SmallInteger}"|
+ |b
+ tempBuffer "{Class: SmallInteger}"
+ _bits "{Class: SmallInteger}" |
tempBuffer := 0.
- bits := 0.
+ _bits := 0.
+
[
"read next valid Base64 character, skip invalid characters"
b := 255.
@@ -238,34 +400,35 @@
b isNil ifTrue:[ "end of stream"
b := 64. "simulate end-mark"
] ifFalse:[
- b := Base64ReverseMapping at:b codePoint ifAbsent:255.
+ b := reverseMapping at:b codePoint ifAbsent:255.
]
].
b == 64 ifTrue:[
"got $=, end of Base64 string has been reached"
atEnd := true.
- bits == 12 ifTrue:[
+ _bits == 12 ifTrue:[
"data has been padded to 12, skip 4 bits"
- shift := -4.
- ] ifFalse:[bits == 18 ifTrue:[
+ tempBuffer := tempBuffer bitShift:-4.
+ _bits := _bits - 4.
+ ] ifFalse:[_bits == 18 ifTrue:[
"data has been padded to 18, skip 2 bits"
- shift := -2.
- ] ifFalse:[
- shift := 0.
+ tempBuffer := tempBuffer bitShift:-2.
+ _bits := _bits - 2.
]].
- tempBuffer := tempBuffer bitShift:shift.
- bits := bits + shift.
] ifFalse:[
"got valid Base64 character, append to buffer"
tempBuffer := (tempBuffer bitShift:6) bitOr:b.
- bits := bits + 6.
+ _bits := _bits + 6.
].
- (bits == 24 or:[atEnd]) ifTrue:[
+ (_bits == 24 or:[atEnd]) ifTrue:[
+ bits := _bits.
buffer := tempBuffer.
^ self.
].
] loop.
+
+ "Modified: / 30-09-2018 / 15:16:19 / Claus Gittinger"
! !
!Base64Coder class methodsFor:'documentation'!