THRIFT-2890 binary data may lose bytes with JSON transport under specific circumstances
Client: Delphi
Patch: Jens Geyer

This closes #319

This patch consists of a ported version of the base64 encoding/decoding used in C#. It handles the above case correctly, decodes data more efficiently in-place, and removes the dependency to Indy (IdCoderMIME).
diff --git a/lib/delphi/src/Thrift.Protocol.JSON.pas b/lib/delphi/src/Thrift.Protocol.JSON.pas
index 6d305d8..9ec3871 100644
--- a/lib/delphi/src/Thrift.Protocol.JSON.pas
+++ b/lib/delphi/src/Thrift.Protocol.JSON.pas
@@ -27,10 +27,10 @@
   Classes,
   SysUtils,
   Math,
-  IdCoderMIME,
   Generics.Collections,
   Thrift.Transport,
-  Thrift.Protocol;
+  Thrift.Protocol,
+  Thrift.Utils;
 
 type
   IJSONProtocol = interface( IProtocol)
@@ -622,24 +622,29 @@
 
 
 procedure TJSONProtocolImpl.WriteJSONBase64( const b : TBytes);
-var str : string;
-    tmp : TBytes;
-    i   : Integer;
+var len, off, cnt : Integer;
+    tmpBuf : TBytes;
 begin
   FContext.Write;
   Transport.Write( QUOTE);
 
-  // First base64-encode b, then write the resulting 8-bit chars
-  // Unfortunately, EncodeBytes() returns a string of 16-bit (wide) chars
-  // And for the sake of efficiency, we want to write everything at once
-  str := TIdEncoderMIME.EncodeBytes(b);
-  ASSERT( SizeOf(str[1]) = SizeOf(Word));
-  SetLength( tmp, Length(str));
-  for i := 1 to Length(str) do begin
-    ASSERT( Hi(Word(str[i])) = 0);   // base64 consists of a well-defined set of 8-bit chars only
-    tmp[i-1] := Lo(Word(str[i]));    // extract the lower byte
+  len := Length(b);
+  off := 0;
+  SetLength( tmpBuf, 4);
+
+  while len >= 3 do begin
+    // Encode 3 bytes at a time
+    Base64Utils.Encode( b, off, 3, tmpBuf, 0);
+    Transport.Write( tmpBuf, 0, 4);
+    Inc( off, 3);
+    Dec( len, 3);
   end;
-  Transport.Write( tmp);  // now write all the data
+
+  // Encode remainder, if any
+  if len > 0 then begin
+    cnt := Base64Utils.Encode( b, off, len, tmpBuf, 0);
+    Transport.Write( tmpBuf, 0, cnt);
+  end;
 
   Transport.Write( QUOTE);
 end;
@@ -960,12 +965,37 @@
 
 function TJSONProtocolImpl.ReadJSONBase64 : TBytes;
 var b : TBytes;
-    str : string;
+    len, off, size, cnt : Integer;
 begin
   b := ReadJSONString(false);
 
-  SetString( str, PAnsiChar(b), Length(b));
-  result := TIdDecoderMIME.DecodeBytes( str);
+  len := Length(b);
+  off := 0;
+  size := 0;
+
+  // reduce len to ignore fill bytes
+  Dec(len);
+  while (len >= 0) and (b[len] = Byte('=')) do Dec(len);
+  Inc(len);
+
+  // read & decode full byte triplets = 4 source bytes
+  while (len >= 4) do begin
+    // Decode 4 bytes at a time
+    Inc( size, Base64Utils.Decode( b, off, 4, b, size)); // decoded in place
+    Inc( off, 4);
+    Dec( len, 4);
+  end;
+
+  // Don't decode if we hit the end or got a single leftover byte (invalid
+  // base64 but legal for skip of regular string type)
+  if len > 1 then begin
+    // Decode remainder
+    Inc( size, Base64Utils.Decode( b, off, len, b, size)); // decoded in place
+  end;
+
+  // resize to final size and return the data
+  SetLength( b, size);
+  result := b;
 end;