View Issue Details

IDProjectCategoryView StatusLast Update
0037035FPCFCLpublic2020-05-07 12:08
ReporterAlex Assigned ToMichael Van Canneyt  
PrioritynormalSeverityminorReproducibilityalways
Status resolvedResolutionno change required 
PlatformLinux x86_64OSUbuntu  
Product Version3.0.4 
Summary0037035: Base64 incorrect encoding (file base64.pp)
DescriptionIf one or two bytes remains in buffer, they will not be encoded.
Therefore, all strings with (length mod 3 in [1,2]) are encoded incorrectly
The bug is here:
base64.pp
function TBase64EncodingStream.Write(const Buffer; Count: Longint): Longint;
...
if ReadNow > Count then break; // Not enough data available
...
Test program attached.
Steps To ReproduceRun test program
TagsNo tags attached.
Fixed in Revision
FPCOldBugId
FPCTarget-
Attached Files

Activities

Alex

2020-05-07 12:04

reporter  

test.lpr (1,780 bytes)   
{
If one or two bytes remains in buffer, they will not be encoded.
Therefore, all strings with (length mod 3 in [1,2]) are encoded incorrectly
The bug is here:
base64.pp
function TBase64EncodingStream.Write(const Buffer; Count: Longint): Longint;
...
if ReadNow > Count then break;    // Not enough data available
...
Test program attached.
}

program test;
uses
  Classes, SysUtils, Base64;

function Encode(ASource : TStream) : String;
var
  Encoder: TBase64EncodingStream;
  StringStream: TSTringStream;
begin
  StringStream := TSTringStream.Create('');
  try
    Encoder := TBase64EncodingStream.Create(StringStream);
    try
      ASource.Position := 0;
      Encoder.CopyFrom(ASource, ASource.Size);
      Result := StringStream.DataString;
    finally
      Encoder.Free;
    end;
  finally
    StringStream.Free;
  end;
end;

function IsBug(const a, b, ASource : String) : String;
begin
  if a <> b then
    Result := Format('BUG! Length mod 3 = %d', [Length(ASource) mod 3])
  else
    Result := '';
end;

procedure Test(const ASource, AReference : String);
var
  m : TMemoryStream;
begin
  if Length(ASource) = 0 then Exit;
  WriteLn;
  WriteLn(Format('Source="%s"', [ASource]));
  m := TMemoryStream.Create;
  try
    m.Write(ASource[1], Length(ASource));
    WriteLn(Format('Base64="%s" (reference="%s") %s', [Encode(m), AReference, IsBug(Encode(m), AReference, ASource)]));
  finally
    m.Free;
  end;
end;

begin
  Test('1', 'MQ==');
  Test('12', 'MTI=');
  Test('123', 'MTIz');
  Test('1234', 'MTIzNA==');
  Test('12345', 'MTIzNDU=');
  Test('123456', 'MTIzNDU2');
  Test('1234567', 'MTIzNDU2Nw==');
  Test('12345678', 'MTIzNDU2Nzg=');
  Test('123456789', 'MTIzNDU2Nzg5');
  Test('1234567890', 'MTIzNDU2Nzg5MA==');
  Test('12345678901', 'MTIzNDU2Nzg5MDE=');
end.

test.lpr (1,780 bytes)   

Michael Van Canneyt

2020-05-07 12:08

administrator   ~0122653

You must flush the data before using the result. Change your encode function to:

function Encode(ASource : TStream) : String;
var
  Encoder: TBase64EncodingStream;
  StringStream: TSTringStream;
begin
  StringStream := TSTringStream.Create('');
  try
    Encoder := TBase64EncodingStream.Create(StringStream);
    try
      ASource.Position := 0;
      Encoder.CopyFrom(ASource, ASource.Size);
      Encoder.Flush; // <-- add this
      Result := StringStream.DataString;
    finally
      Encoder.Free;
    end;
  finally
    StringStream.Free;
  end;
end;

Issue History

Date Modified Username Field Change
2020-05-07 12:04 Alex New Issue
2020-05-07 12:04 Alex File Added: test.lpr
2020-05-07 12:08 Michael Van Canneyt Assigned To => Michael Van Canneyt
2020-05-07 12:08 Michael Van Canneyt Status new => resolved
2020-05-07 12:08 Michael Van Canneyt Resolution open => no change required
2020-05-07 12:08 Michael Van Canneyt FPCTarget => -
2020-05-07 12:08 Michael Van Canneyt Note Added: 0122653