Base64Coder.st
changeset 4743 6bdaef8ec48a
parent 4520 d2ea4e714e1f
child 4753 5b849ae3a1a1
--- 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'!