Browse Source

Merged revisions 6982-6990 via svnmerge from
svn+ssh://[email protected]/FPC/svn/fpc/trunk

........
r6982 | michael | 2007-03-24 22:37:50 +0100 (Sat, 24 Mar 2007) | 1 line

* Patch to fix reset
........
r6983 | michael | 2007-03-24 22:44:00 +0100 (Sat, 24 Mar 2007) | 1 line

* Fixed equivalent of bug #8529
........
r6984 | michael | 2007-03-24 23:11:03 +0100 (Sat, 24 Mar 2007) | 1 line

* Removed inttostr, isvalidident so sysutils versions are used.
........
r6985 | michael | 2007-03-24 23:33:06 +0100 (Sat, 24 Mar 2007) | 1 line

* Fixed bug #8303
........
r6986 | michael | 2007-03-24 23:43:02 +0100 (Sat, 24 Mar 2007) | 1 line

* Fixed TimeToStr and DateTimeToStr so they are delphi compatible
........
r6987 | michael | 2007-03-24 23:46:33 +0100 (Sat, 24 Mar 2007) | 1 line

* Patch from Bram Kuijvenhoven to fix stream, and add MIME mode
........
r6988 | michael | 2007-03-24 23:47:20 +0100 (Sat, 24 Mar 2007) | 1 line

* fpcunit test case for base64 unit
........
r6989 | michael | 2007-03-25 00:03:05 +0100 (Sun, 25 Mar 2007) | 1 line

* Applied and checked patch from Martin Schreiber for issue #8245
........
r6990 | michael | 2007-03-25 00:36:35 +0100 (Sun, 25 Mar 2007) | 1 line

* Fixed bug #8187
........

git-svn-id: branches/fixes_2_2@7209 -

joost 18 years ago
parent
commit
90248430b6

+ 1 - 0
.gitattributes

@@ -3858,6 +3858,7 @@ packages/fcl-base/tests/b64dec.pp svneol=native#text/plain
 packages/fcl-base/tests/b64enc.pp svneol=native#text/plain
 packages/fcl-base/tests/b64test.pp svneol=native#text/plain
 packages/fcl-base/tests/b64test2.pp svneol=native#text/plain
+packages/fcl-base/tests/base64decodingtestcase.pas svneol=native#text/plain
 packages/fcl-base/tests/cachetest.pp svneol=native#text/plain
 packages/fcl-base/tests/cfgtest.pp svneol=native#text/plain
 packages/fcl-base/tests/daemon.pp svneol=native#text/plain

+ 226 - 95
packages/fcl-base/src/inc/base64.pp

@@ -12,7 +12,12 @@
 
  **********************************************************************}
 
-// Encoding and decoding streams for base64 data as described in RFC2045
+// Encoding and decoding streams for base64 data as described in
+//   RFC2045 (Mode = bdmMIME) and
+//   RFC3548 (Mode = bdmStrict)
+
+// Addition of TBase64DecodingMode supporting both Strict and MIME mode is
+//   (C) 2007 Hexis BV, by Bram Kuijvenhoven ([email protected])
 
 {$MODE objfpc}
 {$H+}
@@ -21,7 +26,7 @@ unit base64;
 
 interface
 
-uses classes;
+uses classes, sysutils;
 
 type
 
@@ -40,51 +45,88 @@ type
     function Seek(Offset: Longint; Origin: Word): Longint; override;
   end;
 
+  (* The TBase64DecodingStream supports two modes:
+   * - 'strict mode':
+   *    - follows RFC3548
+   *    - rejects any characters outside of base64 alphabet,
+   *    - only accepts up to two '=' characters at the end and
+   *    - requires the input to have a Size being a multiple of 4; otherwise raises an EBase64DecodeException
+   * - 'MIME mode':
+   *    - follows RFC2045
+   *    - ignores any characters outside of base64 alphabet
+   *    - takes any '=' as end of string
+   *    - handles apparently truncated input streams gracefully
+   *)
+  TBase64DecodingMode = (bdmStrict, bdmMIME);
+
+  { TBase64DecodingStream }
 
   TBase64DecodingStream = class(TStream)
+  private
+    FMode: TBase64DecodingMode;
+    procedure SetMode(const AValue: TBase64DecodingMode);
+    function  GetSize: Int64; override;
+    function  GetPosition: Int64; override;
   protected
     InputStream: TStream;
-    CurPos, InputStreamSize: LongInt;
-    Buf: array[0..2] of Byte;
-    BufPos: Integer;    // Offset of byte which is to be read next
-    fEOF: Boolean;
+    CurPos,             // 0-based (decoded) position of this stream (nr. of decoded & Read bytes since last reset)
+    DecodedSize: Int64; // length of decoded stream ((expected) decoded bytes since last Reset until Mode-dependent end of stream)
+    ReadBase64ByteCount: Int64; // number of valid base64 bytes read from input stream since last Reset
+    Buf: array[0..2] of Byte; // last 3 decoded bytes
+    BufPos: Integer;          // offset in Buf of byte which is to be read next; if >2, next block must be read from InputStream & decoded
+    FEOF: Boolean;            // if true, all decoded bytes have been read
   public
     constructor Create(AInputStream: TStream);
+    constructor Create(AInputStream: TStream; AMode: TBase64DecodingMode);
     procedure Reset;
 
     function Read(var Buffer; Count: Longint): Longint; override;
     function Write(const Buffer; Count: Longint): Longint; override;
     function Seek(Offset: Longint; Origin: Word): Longint; override;
+    
     property EOF: Boolean read fEOF;
+    property Mode: TBase64DecodingMode read FMode write SetMode;
+  end;
+  
+  EBase64DecodingException = class(Exception)
   end;
-
-
 
 implementation
 
+uses
+  Math;
+
 const
+  SStrictNonBase64Char    = 'Non-valid Base64 Encoding character in input';
+  SStrictInputTruncated   = 'Input stream was truncated at non-4 byte boundary';
+  SStrictMisplacedPadChar = 'Unexpected padding character ''='' before end of input stream';
 
   EncodingTable: PChar =
     'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
 
-  DecTable: array[Byte] of Byte =
-    (99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,  // 0-15
-     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,  // 16-31
-     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 62, 99, 99, 99, 63,  // 32-47
-     52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 99, 99, 99, 00, 99, 99,  // 48-63
-     99, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14,  // 64-79
-     15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 99, 99, 99, 99, 99,  // 80-95
-     99, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,  // 96-111
-     41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 99, 99, 99, 99, 99,  // 112-127
-     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
-     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
-     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
-     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
-     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
-     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
-     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
-     99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99);
+const
+  NA =  85; // not in base64 alphabet at all; binary: 01010101
+  PC = 255; // padding character                      11111111
 
+  DecTable: array[Byte] of Byte =
+    (NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,  // 0-15
+     NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,  // 16-31
+     NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 62, NA, NA, NA, 63,  // 32-47
+     52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NA, NA, NA, PC, NA, NA,  // 48-63
+     NA, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14,  // 64-79
+     15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NA, NA, NA, NA, NA,  // 80-95
+     NA, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,  // 96-111
+     41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NA, NA, NA, NA, NA,  // 112-127
+     NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
+     NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
+     NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
+     NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
+     NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
+     NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
+     NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
+     NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA);
+
+  Alphabet = ['a'..'z','A'..'Z','0'..'9','+','/','=']; // all 65 chars that are in the base64 encoding alphabet
 
 constructor TBase64EncodingStream.Create(AOutputStream: TStream);
 begin
@@ -164,96 +206,211 @@ begin
     raise EStreamError.Create('Invalid stream operation');
 end;
 
+procedure TBase64DecodingStream.SetMode(const AValue: TBase64DecodingMode);
+begin
+  if FMode = AValue then exit;
+  FMode := AValue;
+  DecodedSize := -1; // forget any calculations on this
+end;
 
+function TBase64DecodingStream.GetSize: Int64;
+var
+  endBytes: array[0..1] of Char;
+  ipos, isize: Int64;
+  scanBuf: array[0..1023] of Char;
+  count: LongInt;
+  i: Integer;
+  c: Char;
+begin
+  // Note: this method only works on Seekable InputStreams (for bdmStrict we also get the Size property)
+  if DecodedSize<>-1 then Exit(DecodedSize);
+  ipos := InputStream.Position; // save position in input stream
+  case Mode of
+    bdmMIME:  begin
+      // read until end of input stream or first occurence of a '='
+      Result := ReadBase64ByteCount; // keep number of valid base64 bytes since last Reset in Result
+      repeat
+        count := InputStream.Read(scanBuf, SizeOf(scanBuf));
+        for i := 0 to count-1 do begin
+          c := scanBuf[i];
+          if c in Alphabet-['='] then // base64 encoding characters except '='
+            Inc(Result)
+          else if c = '=' then // end marker '='
+            Break;
+        end;
+      until count = 0;
+      writeln(Result);
+      // we are now either at the end of the stream, or encountered our first '=', stored in c
+      if c = '=' then begin // '=' found
+        if Result mod 4 <= 1 then // badly placed '=', disregard last block
+          Result := (Result div 4) * 3
+        else // 4 byte block ended with '=' or '=='
+          Result := (Result div 4) * 3 + Result mod 4 - 1;
+      end else // end of stream
+        Result := (Result div 4) * 3; // number of valid 4 byte blocks times 3
+    end;
+    bdmStrict:begin
+      // seek to end of input stream, read last two bytes and determine size
+      //   from InputStream size and the number of leading '=' bytes
+      // NB we don't raise an exception here if the input does not contains an integer multiple of 4 bytes
+      ipos  := InputStream.Position;
+      isize := InputStream.Size;
+      Result := ((ReadBase64ByteCount + (isize - ipos) + 3) div 4) * 3;
+      InputStream.Seek(-2, soFromEnd);
+      InputStream.Read(endBytes, 2);
+      if endBytes[1] = '=' then begin // last byte
+        Dec(Result);
+      if endBytes[0] = '=' then       // second to last byte
+        Dec(Result);
+      end;
+    end;
+  end;
+  InputStream.Position := ipos; // restore position in input stream
+  // store calculated DecodedSize
+  DecodedSize := Result;
+end;
 
+function TBase64DecodingStream.GetPosition: Int64;
+begin
+  Result := CurPos;
+end;
 
 constructor TBase64DecodingStream.Create(AInputStream: TStream);
+begin
+  Create(AInputStream, bdmMIME); // MIME mode is default
+end;
+
+constructor TBase64DecodingStream.Create(AInputStream: TStream; AMode: TBase64DecodingMode);
 begin
   inherited Create;
   InputStream := AInputStream;
+  Mode := AMode;
   Reset;
 end;
 
 procedure TBase64DecodingStream.Reset;
 begin
-  InputStreamSize := -1;
-  BufPos := 3;
-  fEOF := False;
+  ReadBase64ByteCount := 0; // number of bytes Read form InputStream since last call to Reset
+  CurPos := 0; // position in decoded byte sequence since last Reset
+  DecodedSize := -1; // indicates unknown; will be set after first call to GetSize or when reaching end of stream
+  BufPos := 3; // signals we need to read & decode a new block of 4 bytes
+  FEOF := False;
 end;
 
 function TBase64DecodingStream.Read(var Buffer; Count: Longint): Longint;
 var
-  p: PChar;
-  b: Char;
-  ReadBuf: array[0..3] of Byte;
-  ToRead, OrgToRead, HaveRead, ReadOK, i, j: Integer;
+  p: PByte;
+  b: byte;
+  ReadBuf: array[0..3] of Byte; // buffer to store last read 4 input bytes
+  ToRead, OrgToRead, HaveRead, ReadOK, i: Integer;
+  
+  procedure DetectedEnd(ASize:Int64);
+  begin
+    DecodedSize := ASize;
+    // Correct Count if at end of base64 input
+    if CurPos + Count > DecodedSize then
+      Count := DecodedSize - CurPos;
+  end;
+  
 begin
-  if Count <= 0 then exit(0);
-  if InputStreamSize <> -1 then begin
-    if CurPos + Count > InputStreamSize then
-      Count := InputStreamSize - CurPos;
+  if Count <= 0 then exit(0); // nothing to read, quit
+  if DecodedSize <> -1 then begin // try using calculated size info if possible
+    if CurPos + Count > DecodedSize then
+      Count := DecodedSize - CurPos;
     if Count <= 0 then exit(0);
   end;
 
   Result := 0;
-  p := PChar(@Buffer);
-  while (Count > 0) and not fEOF do begin
+  p := @Buffer;
+  while true do begin
+    // get new 4-byte block if at end of Buf
     if BufPos > 2 then begin
       BufPos := 0;
       // Read the next 4 valid bytes
-      ToRead := 4;
-      ReadOK := 0;
+      ToRead := 4; // number of base64 bytes left to read into ReadBuf
+      ReadOK := 0; // number of base64 bytes already read into ReadBuf
       while ToRead > 0 do begin
         OrgToRead := ToRead;
         HaveRead := InputStream.Read(ReadBuf[ReadOK], ToRead);
         //WriteLn('ToRead = ', ToRead, ', HaveRead = ', HaveRead, ', ReadOK=', ReadOk);
-        if HaveRead > 0 then begin
-          i := ReadOk;
-          while i<HaveRead do begin
-            ReadBuf[i] := DecTable[ReadBuf[i]];
-            if ReadBuf[i] = 99 then
-              for j := i to 3 do
-                ReadBuf[i] := ReadBuf[i + 1]
-            else begin
-              Inc(i);
+        if HaveRead > 0 then begin // if any new bytes; in ReadBuf[ReadOK .. ReadOK + HaveRead-1]
+          for i := ReadOK to ReadOK + HaveRead - 1 do begin
+            b := DecTable[ReadBuf[i]];
+            if b <> NA then begin // valid base64 alphabet character ('=' inclusive)
+              ReadBuf[ReadOK] := b;
               Inc(ReadOK);
               Dec(ToRead);
+            end else if Mode=bdmStrict then begin // non-valid character
+              raise EBase64DecodingException.CreateFmt(SStrictNonBase64Char,[]);
             end;
           end;
         end;
-        if HaveRead <> OrgToRead then begin
-          //WriteLn('Ende? ReadOK=', ReadOK, ', count=', Count);
+        
+        if HaveRead <> OrgToRead then begin // less than 4 base64 bytes could be read; end of input stream
+          //WriteLn('End: ReadOK=', ReadOK, ', count=', Count);
           for i := ReadOK to 3 do
-            ReadBuf[i] := Ord('=');
-          fEOF := True;
-          if ReadOK < 2 then exit;    // Not enough data available in input stream
-          break;
+            ReadBuf[i] := 0; // pad buffer with zeros so decoding of 4-bytes will be correct
+          if (Mode = bdmStrict) and (ReadOK > 0) then
+            raise EBase64DecodingException.CreateFmt(SStrictInputTruncated,[]);
+          Break;
         end;
       end;
 
-      // Check for fill bytes
-      if (Count >= 2) and (ReadBuf[3] = Ord('=')) then begin
-        //WriteLn('Endemarkierung!');
-        fEOF := True;
-        if ReadBuf[2] = Ord('=') then
-          Count := 1
-        else
-          Count := 2;
+      Inc(ReadBase64ByteCount, ReadOK);
+      
+      // Check for pad characters
+      case Mode of
+        bdmStrict:begin
+          if ReadOK = 0 then // end of input stream was reached at 4-byte boundary
+            DetectedEnd(CurPos)
+          else if (ReadBuf[0] = PC) or (ReadBuf[1] = PC) then
+            raise EBase64DecodingException.CreateFmt(SStrictMisplacedPadChar,[])   // =BBB or B=BB
+          else if (ReadBuf[2] = PC) then begin
+            if (ReadBuf[3] <> PC) or (InputStream.Position < InputStream.Size) then
+              raise EBase64DecodingException.CreateFmt(SStrictMisplacedPadChar,[]); // BB=B or BB==, but not at end of input stream
+            DetectedEnd(CurPos + 1)  // only one byte left to read;  BB==, at end of input stream
+          end else if (ReadBuf[3] = PC) then begin
+            if (InputStream.Position < InputStream.Size) then
+              raise EBase64DecodingException.CreateFmt(SStrictMisplacedPadChar,[]); // BBB=, but not at end of input stream
+            DetectedEnd(CurPos + 2); // only two bytes left to read; BBB=, at end of input stream
+          end;
+        end;
+        bdmMIME:begin
+          if ReadOK = 0 then // end of input stream was reached at 4-byte boundary
+            DetectedEnd(CurPos)
+          else if (ReadBuf[0] = PC) or (ReadBuf[1] = PC) then
+            DetectedEnd(CurPos)      // =BBB or B=BB: end here
+          else if (ReadBuf[2] = PC) then begin
+            DetectedEnd(CurPos + 1)  // only one byte left to read;  BB=B or BB==
+          end else if (ReadBuf[3] = PC) then begin
+            DetectedEnd(CurPos + 2); // only two bytes left to read; BBB=
+          end;
+        end;
       end;
-
+      
       // Decode the 4 bytes in the buffer to 3 undecoded bytes
-      Buf[0] := ReadBuf[0] shl 2 or ReadBuf[1] shr 4;
+      Buf[0] :=  ReadBuf[0]         shl 2 or ReadBuf[1] shr 4;
       Buf[1] := (ReadBuf[1] and 15) shl 4 or ReadBuf[2] shr 2;
-      Buf[2] := (ReadBuf[2] and 3) shl 6 or ReadBuf[3];
+      Buf[2] := (ReadBuf[2] and  3) shl 6 or ReadBuf[3];
+    end;
+    
+    if Count <= 0 then begin
+      Break;
     end;
 
-    p[0] := Chr(Buf[BufPos]);
+    // write one byte to Count
+    p^ := Buf[BufPos];
     Inc(p);
     Inc(BufPos);
     Inc(CurPos);
     Dec(Count);
     Inc(Result);
   end;
+  
+  // check for EOF
+  if (DecodedSize <> -1) and (CurPos >= DecodedSize) then begin
+    FEOF := true;
+  end;
 end;
 
 function TBase64DecodingStream.Write(const Buffer; Count: Longint): Longint;
@@ -262,35 +419,9 @@ begin
 end;
 
 function TBase64DecodingStream.Seek(Offset: Longint; Origin: Word): Longint;
-var
-  ipos: LongInt;
-  endbytes: array[0..1] of Char;
 begin
-  {This will work only if the input stream supports seeking / Size. If not, the
-   input stream will raise an exception; we don't handle them here but pass them
-   to the caller.}
-  if InputStreamSize = -1 then begin
-    ipos := InputStream.Position;
-    InputStreamSize := ((InputStream.Size - ipos + 3) div 4) * 3;
-    InputStream.Seek(-2, soFromEnd);
-    InputStream.Read(endbytes, 2);
-    InputStream.Position := ipos;
-    if endbytes[1] = '=' then begin
-      Dec(InputStreamSize);
-    if endbytes[0] = '=' then
-      Dec(InputStreamSize);
-    end;
-  end;
-
-  // This stream only supports the Seek modes needed for determining its size
-  if (Origin = soFromCurrent) and (Offset = 0) then
-    Result := CurPos
-  else if (Origin = soFromEnd) and (Offset = 0) then
-    Result := InputStreamSize
-  else if (Origin = soFromBeginning) and (Offset = CurPos) then
-    Result := CurPos
-  else
-    raise EStreamError.Create('Invalid stream operation');
+  // TODO: implement Seeking in TBase64DecodingStream
+  raise EStreamError.Create('Invalid stream operation');
 end;
 
 

+ 331 - 0
packages/fcl-base/tests/base64decodingtestcase.pas

@@ -0,0 +1,331 @@
+(******************************************************************************
+ *                                                                            *
+ *  (c) 2007 Hexis BV                                                         *
+ *                                                                            *
+ *  File:        Base64DecodingTestCase.pas                                   *
+ *  Author:      Bram Kuijvenhoven ([email protected])                   *
+ *  Description: FPCUnit tests for Base64.TBase64DecodingStream               *
+ *                                                                            *
+ ******************************************************************************)
+
+unit Base64DecodingTestCase;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+  Classes, SysUtils, fpcunit, testutils, testregistry, base64;
+
+type
+
+  { TBase64DecodingStreamTestCase }
+
+  TBase64DecodingStreamTestCase = class(TTestCase)
+  private
+    Base64Stream: TBase64DecodingStream;
+    SourceStream: TStream;
+  protected
+    procedure SetUp; override;
+    procedure TearDown; override;
+    // utility
+    procedure WriteToSourceStream(s:string);
+    function NoiseCharString:string; // all non-base64 alphabet characters
+    function RandomNoise(l:integer):string; // l random non-base64 alphabet characters
+    // test templates
+    procedure TestGetSize(mode:TBase64DecodingMode; s:string; expectedSize:Int64);
+    procedure TestDecode (mode:TBase64DecodingMode; s, expected:string);
+    procedure TestReset  (mode:TBase64DecodingMode; s:string; start:array of Int64; expected:array of string);
+  published
+    procedure TestGetSizeStrict1;
+    procedure TestGetSizeStrict2;
+    procedure TestGetSizeStrict3;
+    procedure TestGetSizeStrict4;
+    procedure TestGetSizeStrict5;
+    procedure TestGetSizeMIME01;
+    procedure TestGetSizeMIME02;
+    procedure TestGetSizeMIME03;
+    procedure TestGetSizeMIME04;
+    procedure TestGetSizeMIME05;
+    procedure TestGetSizeMIME06;
+    procedure TestGetSizeMIME07;
+    procedure TestGetSizeMIME08;
+    procedure TestGetSizeMIME09;
+    procedure TestGetSizeMIME10;
+    procedure TestGetSizeMIME11;
+    procedure TestGetSizeMIME12;
+    procedure TestGetSizeMIME13;
+    procedure TestGetSizeMIME14;
+    procedure TestGetSizeMIME15;
+    procedure TestGetSizeMIME16;
+    procedure TestGetSizeMIME17;
+    procedure TestGetSizeMIME18;
+    procedure TestGetSizeMIME19;
+    procedure TestGetSizeMIME20;
+    procedure TestGetSizeMIME21;
+    procedure TestGetSizeMIME22;
+    procedure TestDecodeStrict01;
+    procedure TestDecodeStrict02;
+    procedure TestDecodeStrict03;
+    procedure TestDecodeStrict04;
+    procedure TestDecodeStrict05;
+    procedure TestDecodeStrict06;
+    procedure TestDecodeStrict07;
+    procedure TestDecodeStrict08;
+    procedure TestDecodeStrict09;
+    procedure TestDecodeStrict10;
+    procedure TestDecodeMIME1;
+    procedure TestDecodeMIME2;
+    procedure TestDecodeMIME3;
+    procedure TestDecodeMIME4;
+    procedure TestDecodeMIME5;
+    procedure TestDecodeMIME6;
+    procedure TestDecodeMIME7;
+    procedure TestDecodeMIME8;
+    procedure TestDecodeMIME9;
+    procedure TestResetStrict1;
+    procedure TestResetStrict2;
+    procedure TestResetStrict3;
+    procedure TestResetMIME1;
+    procedure TestResetMIME2;
+    procedure TestResetMIME3;
+  end;
+  
+implementation
+
+uses
+  Math;
+
+// utility routines
+type
+  TChars = set of char;
+  
+function SetToString(chars:TChars):string;
+var
+  pos: Integer;
+  c: Char;
+begin
+  pos:=0;
+  SetLength(Result,256);
+  for c:=#0 to #255 do
+    if c in chars then begin
+      Inc(pos);
+      Result[pos]:=c;
+    end;
+  SetLength(Result,pos);
+end;
+
+function Shuffle(s:string):string;
+var
+  i: Integer;
+  randomPos: Integer;
+  c: Char;
+begin
+  UniqueString(s);
+  for i:=1 to Length(s) do begin
+    randomPos:=Random(Length(s))+1;
+    c:=s[randomPos];
+    s[randomPos]:=s[i];
+    s[i]:=c;
+  end;
+  Result:=s;
+end;
+
+// order preserving shuffle
+function Merge(s,t:string):string;
+var
+  si: Integer;
+  ti: Integer;
+  sLeft: Integer;
+  tLeft: Integer;
+  chooseS: Boolean;
+  ri: Integer;
+  count: LongInt;
+  i: Integer;
+begin
+  si := 1;
+  ti := 1;
+  ri := 1;
+  sLeft := Length(s);
+  tLeft := Length(t);
+  SetLength(Result, sLeft + tLeft);
+  while (sLeft>0) or (tLeft>0) do begin
+    chooseS := Random(sLeft + tLeft) < sLeft;
+    if chooseS then begin
+      count := Min(Random(7)+1, sLeft);
+      for i := 0 to count - 1 do
+        Result[ri + i] := s[si + i];
+      Inc(ri, count);
+      Inc(si, count);
+      Dec(sLeft, count);
+    end else begin
+      count := Min(Random(7)+1, tLeft);
+      for i := 0 to count - 1 do
+        Result[ri + i] := t[ti + i];
+      Inc(ri, count);
+      Inc(ti, count);
+      Dec(tLeft, count);
+    end;
+  end;
+end;
+
+procedure TBase64DecodingStreamTestCase.SetUp;
+begin
+  SourceStream := TMemoryStream.Create;
+  Base64Stream := TBase64DecodingStream.Create(SourceStream);
+end;
+
+procedure TBase64DecodingStreamTestCase.TearDown;
+begin
+  FreeAndNil(Base64Stream);
+  FreeAndNil(SourceStream);
+end;
+
+procedure TBase64DecodingStreamTestCase.WriteToSourceStream(s: string);
+begin
+  SourceStream.Write(s[1],Length(s));
+  SourceStream.Position:=0;
+end;
+
+function TBase64DecodingStreamTestCase.NoiseCharString: string;
+begin
+  Result:=SetToString([#0..#255]-['a'..'z','A'..'Z','0'..'9','+','/','=']);
+end;
+
+function TBase64DecodingStreamTestCase.RandomNoise(l: integer): string;
+var
+  i: Integer;
+  noiseChars: String;
+begin
+  noiseChars:=NoiseCharString; // our pool
+  SetLength(Result,l);
+  for i:=1 to l do
+    Result[i]:=noiseChars[Random(Length(noiseChars))+1];
+end;
+
+procedure TBase64DecodingStreamTestCase.TestGetSize(mode:TBase64DecodingMode; s: string; expectedSize: Int64);
+var
+  Size: Int64;
+begin
+  Base64Stream.Mode := mode;
+  WriteToSourceStream(s);
+  Size := Base64Stream.Size;
+  AssertEquals('Correct size calculated by Size getter', Size, expectedSize);
+end;
+
+procedure TBase64DecodingStreamTestCase.TestDecode(mode: TBase64DecodingMode; s, expected: string);
+var
+  Buf: array[0..63] of Char;
+  si: Integer;
+  i: Integer;
+  count: LongInt;
+begin
+  writeln(Length(s),'->',Length(expected));
+  Base64Stream.Mode := mode;
+  WriteToSourceStream(s);
+  si := 1;  // index into expected
+  repeat
+    count := Base64Stream.Read(Buf, Random(SizeOf(Buf))+1);
+    AssertTrue('Not too many bytes decoded', si + count - 1 <= Length(expected));
+    for i := 0 to count-1 do
+      AssertEquals('Correctly decoded byte', expected[si + i], Buf[i]);
+    Inc(si, count);
+    AssertEquals('Returned size is correct', Length(expected), Base64Stream.Size);
+  until Base64Stream.EOF;
+  AssertEquals('Correct decoded length', Length(expected), si-1);
+end;
+
+procedure TBase64DecodingStreamTestCase.TestReset(mode: TBase64DecodingMode; s: string; start: array of Int64; expected: array of string);
+var
+  Buf: array[0..63] of Char;
+  len: Integer;
+  i: Integer;
+  count: LongInt;
+  startI: Integer;
+begin
+  Base64Stream.Mode := mode;
+  WriteToSourceStream(s);
+  AssertEquals('start and expected arrays have same length',Length(start),Length(expected));
+  for startI := 0 to High(start) do begin
+    SourceStream.Position := start[startI];
+    Base64Stream.Reset;
+    // test decoding
+    len := 0;
+    repeat
+      count := Base64Stream.Read(Buf, Random(SizeOf(Buf))+1);
+      AssertTrue('Not too many bytes decoded', len + count <= Length(expected[startI]));
+      for i := 0 to count-1 do
+        AssertEquals(Format('Correctly decoded byte at %d',[len+i]), expected[startI][len + i + 1], Buf[i]);
+      Inc(len, count);
+      AssertEquals('Returned size is correct', Length(expected[startI]), Base64Stream.Size);
+    until Base64Stream.EOF;
+    AssertEquals('Correct decoded length', Length(expected[startI]), len);
+  end;
+end;
+
+procedure TBase64DecodingStreamTestCase.TestGetSizeStrict1; begin TestGetSize(bdmStrict,'',0); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeStrict2; begin TestGetSize(bdmStrict,'aAzZ09+/',6); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeStrict3; begin TestGetSize(bdmStrict,'aAzZ09+=',5); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeStrict4; begin TestGetSize(bdmStrict,'aAzZ09==',4); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeStrict5; begin TestGetSize(bdmStrict,'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/',48); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME01; begin TestGetSize(bdmMIME,'',0); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME02; begin TestGetSize(bdmMIME,NoiseCharString,0); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME03; begin TestGetSize(bdmMIME,Shuffle(NoiseCharString),0); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME04; begin TestGetSize(bdmMIME,Shuffle(RandomNoise(12))+'=',0); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME05; begin TestGetSize(bdmMIME,Shuffle(NoiseCharString)+'==',0); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME06; begin TestGetSize(bdmMIME,Shuffle(RandomNoise(13))+'===',0); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME07; begin TestGetSize(bdmMIME,Shuffle(NoiseCharString)+'====',0); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME08; begin TestGetSize(bdmMIME,Shuffle(RandomNoise(14))+'a=',0); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME09; begin TestGetSize(bdmMIME,Shuffle(NoiseCharString)+'ab==',1); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME10; begin TestGetSize(bdmMIME,Shuffle(RandomNoise(15))+'abc=',2); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME11; begin TestGetSize(bdmMIME,Shuffle(NoiseCharString)+'abcd',3); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME12; begin TestGetSize(bdmMIME,Shuffle(RandomNoise(16)+'abcd'),3); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME13; begin TestGetSize(bdmMIME,Shuffle(NoiseCharString+'abcd')+'=',3); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME14; begin TestGetSize(bdmMIME,Shuffle(RandomNoise(17)+'abcd')+'01=',4); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME15; begin TestGetSize(bdmMIME,Shuffle(NoiseCharString+'abcd')+'01==',4); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME16; begin TestGetSize(bdmMIME,Shuffle(RandomNoise(18)+'abcd')+'012==',5); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME17; begin TestGetSize(bdmMIME,Shuffle(NoiseCharString+'abcd')+'0123=',6); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME18; begin TestGetSize(bdmMIME,Shuffle(RandomNoise(19)+'abcd')+'012345=',7); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME19; begin TestGetSize(bdmMIME,Shuffle(NoiseCharString+'abcd')+'=01234=',3); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME20; begin TestGetSize(bdmMIME,Shuffle(RandomNoise(20)+'abcd')+'0=1234=',3); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME21; begin TestGetSize(bdmMIME,Shuffle(NoiseCharString+'abcd')+'01=234=',4); end;
+procedure TBase64DecodingStreamTestCase.TestGetSizeMIME22; begin TestGetSize(bdmMIME,Shuffle(RandomNoise(99)+'abcd')+'012=34=',5); end;
+
+procedure TBase64DecodingStreamTestCase.TestDecodeStrict01; begin TestDecode(bdmStrict,'',''); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeStrict02; begin TestDecode(bdmStrict,'AA==',#0); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeStrict03; begin TestDecode(bdmStrict,'AAA=',#0#0); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeStrict04; begin TestDecode(bdmStrict,'AAAA',#0#0#0); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeStrict05; begin TestDecode(bdmStrict,'AAAAAA==',#0#0#0#0); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeStrict06; begin TestDecode(bdmStrict,'AAAAAAA=',#0#0#0#0#0); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeStrict07; begin TestDecode(bdmStrict,'AAAAAAAA',#0#0#0#0#0#0); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeStrict08; begin TestDecode(bdmStrict,'AAAAAAAA',#0#0#0#0#0#0); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeStrict09; begin TestDecode(bdmStrict, 'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2ljaW5nIGVsaXQsIHNlZCBkbyBlaXVzbW9kIHRlbXBvciBpbmNpZGlkdW50IHV0IGxhYm9yZSBldCBkb2xvcmUgbWFnbmEgYWxpcXVhLiBVdCBlbmltIGFkIG1pbmltIHZlbmlhbSwgcXVpcyBub3N0cnVkIGV4ZXJjaXRhdGlvbiB1bGxhbWNvIGxhYm9yaXM'+'gbmlzaSB1dCBhbGlxdWlwIGV4IGVhIGNvbW1vZG8gY29uc2VxdWF0LiBEdWlzIGF1dGUgaXJ1cmUgZG9sb3IgaW4gcmVwcmVoZW5kZXJpdCBpbiB2b2x1cHRhdGUgdmVsaXQgZXNzZSBjaWxsdW0gZG9sb3JlIGV1IGZ1Z2lhdCBudWxsYSBwYXJpYXR1ci4gRXhjZXB0ZXVyIHNpbnQgb2NjYWVjYXQgY3VwaWRhdGF0IG5vbiBwcm9pZGVudC'+'wgc3VudCBpbiBjdWxwYSBxdWkgb2ZmaWNpYSBkZXNlcnVudCBtb2xsaXQgYW5pbSBpZCBlc3QgbGFib3J1bS4K',
+                                                                                        'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor '+'in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'#10); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeStrict10; begin TestDecode(bdmStrict, 'QmFzZTY0IERlY29kaW5nIFN1Y2NlZWRlZCE=','Base64 Decoding Succeeded!'); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeMIME1; begin TestDecode(bdmMIME,Merge('QmFzZTY0IERlY29kaW5nIFN1Y2NlZWRlZCE=',RandomNoise(12  )),'Base64 Decoding Succeeded!'); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeMIME2; begin TestDecode(bdmMIME,Merge('QmFzZTY0IERlY29kaW5nIFN1Y2NlZWRlZCE=',RandomNoise(1200)),'Base64 Decoding Succeeded!'); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeMIME3; begin TestDecode(bdmMIME,      'QmFzZTY0'                   ,'Base64'); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeMIME4; begin TestDecode(bdmMIME,      'QmFzZTY0X='                 ,'Base64'); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeMIME5; begin TestDecode(bdmMIME,Merge('QmFzZTY0',RandomNoise(1200)),'Base64'); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeMIME6; begin TestDecode(bdmMIME,      'TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2ljaW5nIGVsaXQsIHNlZCBkbyBlaXVzbW9kIHRlbXBvciBpbmNpZGlkdW50IHV0IGxhYm9yZSBldCBkb2xvcmUgbWFnbmEgYWxpcXVhLiBVdCBlbmltIGFkIG1pbmltIHZlbmlhbSwgcXVpcyBub3N0cnVkIGV4ZXJjaXRhdGlvbiB1bGxhbWNvIGxhYm9yaXM'+'gbmlzaSB1dCBhbGlxdWlwIGV4IGVhIGNvbW1vZG8gY29uc2VxdWF0LiBEdWlzIGF1dGUgaXJ1cmUgZG9sb3IgaW4gcmVwcmVoZW5kZXJpdCBpbiB2b2x1cHRhdGUgdmVsaXQgZXNzZSBjaWxsdW0gZG9sb3JlIGV1IGZ1Z2lhdCBudWxsYSBwYXJpYXR1ci4gRXhjZXB0ZXVyIHNpbnQgb2NjYWVjYXQgY3VwaWRhdGF0IG5vbiBwcm9pZGVudC'+'wgc3VudCBpbiBjdWxwYSBxdWkgb2ZmaWNpYSBkZXNlcnVudCBtb2xsaXQgYW5pbSBpZCBlc3QgbGFib3J1bS4K',
+                                                                                        'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor '+'in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'#10); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeMIME7; begin TestDecode(bdmMIME,Merge('TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2ljaW5nIGVsaXQsIHNlZCBkbyBlaXVzbW9kIHRlbXBvciBpbmNpZGlkdW50IHV0IGxhYm9yZSBldCBkb2xvcmUgbWFnbmEgYWxpcXVhLiBVdCBlbmltIGFkIG1pbmltIHZlbmlhbSwgcXVpcyBub3N0cnVkIGV4ZXJjaXRhdGlvbiB1bGxhbWNvIGxhYm9yaXM'+'gbmlzaSB1dCBhbGlxdWlwIGV4IGVhIGNvbW1vZG8gY29uc2VxdWF0LiBEdWlzIGF1dGUgaXJ1cmUgZG9sb3IgaW4gcmVwcmVoZW5kZXJpdCBpbiB2b2x1cHRhdGUgdmVsaXQgZXNzZSBjaWxsdW0gZG9sb3JlIGV1IGZ1Z2lhdCBudWxsYSBwYXJpYXR1ci4gRXhjZXB0ZXVyIHNpbnQgb2NjYWVjYXQgY3VwaWRhdGF0IG5vbiBwcm9pZGVudC'+'wgc3VudCBpbiBjdWxwYSBxdWkgb2ZmaWNpYSBkZXNlcnVudCBtb2xsaXQgYW5pbSBpZCBlc3QgbGFib3J1bS4K',RandomNoise(1200)),
+                                                                                        'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor '+'in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'#10); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeMIME8; begin TestDecode(bdmMIME,Merge('TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2ljaW5nIGVsaXQsIHNlZCBkbyBlaXVzbW9kIHRlbXBvciBpbmNpZGlkdW50IHV0IGxhYm9yZSBldCBkb2xvcmUgbWFnbmEgYWxpcXVhLiBVdCBlbmltIGFkIG1pbmltIHZlbmlhbSwgcXVpcyBub3N0cnVkIGV4ZXJjaXRhdGlvbiB1bGxhbWNvIGxhYm9yaXM'+'gbmlzaSB1dCBhbGlxdWlwIGV4IGVhIGNvbW1vZG8gY29uc2VxdWF0LiBEdWlzIGF1dGUgaXJ1cmUgZG9sb3IgaW4gcmVwcmVoZW5kZXJpdCBpbiB2b2x1cHRhdGUgdmVsaXQgZXNzZSBjaWxsdW0gZG9sb3JlIGV1IGZ1Z2lhdCBudWxsYSBwYXJpYXR1ci4gRXhjZXB0ZXVyIHNpbnQgb2NjYWVjYXQgY3VwaWRhdGF0IG5vbiBwcm9pZGVudC'+'wgc3VudCBpbiBjdWxwYSBxdWkgb2ZmaWNpYSBkZXNlcnVudCBtb2xsaXQgYW5pbSBpZCBlc3QgbGFib3J1bS4K=',RandomNoise(1200)),
+                                                                                        'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor '+'in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'#10); end;
+procedure TBase64DecodingStreamTestCase.TestDecodeMIME9; begin TestDecode(bdmMIME,Merge('TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2ljaW5nIGVsaXQsIHNlZCBkbyBlaXVzbW9kIHRlbXBvciBpbmNpZGlkdW50IHV0IGxhYm9yZSBldCBkb2xvcmUgbWFnbmEgYWxpcXVhLiBVdCBlbmltIGFkIG1pbmltIHZlbmlhbSwgcXVpcyBub3N0cnVkIGV4ZXJjaXRhdGlvbiB1bGxhbWNvIGxhYm9yaXM'+'gbmlzaSB1dCBhbGlxdWlwIGV4IGVhIGNvbW1vZG8gY29uc2VxdWF0LiBEdWlzIGF1dGUgaXJ1cmUgZG9sb3IgaW4gcmVwcmVoZW5kZXJpdCBpbiB2b2x1cHRhdGUgdmVsaXQgZXNzZSBjaWxsdW0gZG9sb3JlIGV1IGZ1Z2lhdCBudWxsYSBwYXJpYXR1ci4gRXhjZXB0ZXVyIHNpbnQgb2NjYWVjYXQgY3VwaWRhdGF0IG5vbiBwcm9pZGVudC'+'wgc3VudCBpbiBjdWxwYSBxdWkgb2ZmaWNpYSBkZXNlcnVudCBtb2xsaXQgYW5pbSBpZCBlc3QgbGFib3J1bS4KX=',RandomNoise(1200)),
+                                                                                        'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor '+'in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'#10); end;
+
+procedure TBase64DecodingStreamTestCase.TestResetStrict1; begin TestReset(bdmStrict,'',[0],['']); end;
+procedure TBase64DecodingStreamTestCase.TestResetStrict2; begin TestReset(bdmStrict,'AA==',[0,4],[#0,'']); end;
+procedure TBase64DecodingStreamTestCase.TestResetStrict3; begin TestReset(bdmStrict,'AAAAAAA=',[0,4,8],[#0#0#0#0#0,#0#0,'']); end;
+procedure TBase64DecodingStreamTestCase.TestResetMIME1; begin TestReset(bdmMIME,'QmFzZTY0IERlY29kaW5nIFN1Y2NlZWRlZCE=',[0],['Base64 Decoding Succeeded!']); end;
+procedure TBase64DecodingStreamTestCase.TestResetMIME2; begin TestReset(bdmMIME,'QmFzZTY0IERlY29kaW5nIFN1Y2NlZWRlZCE=',[0,4,8],['Base64 Decoding Succeeded!','e64 Decoding Succeeded!',' Decoding Succeeded!']); end;
+procedure TBase64DecodingStreamTestCase.TestResetMIME3; begin TestReset(bdmMIME,'Qm!@#$%^&*()F!@#$%^&*()z!@#$%^&*()ZTY0IERlY29kaW5nIFN1Y2NlZWRlZCE=',[0,24,34,38,62,66],['Base64 Decoding Succeeded!','e64 Decoding Succeeded!','e64 Decoding Succeeded!',' Decoding Succeeded!','d!','']); end;
+
+initialization
+
+  RegisterTest(TBase64DecodingStreamTestCase);
+  
+end.
+

+ 18 - 12
packages/fcl-passrc/src/pparser.pp

@@ -1552,20 +1552,27 @@ end;
 
 procedure TPasParser.ParseProperty(Element:TPasElement);
 
-  function GetAccessorName: String;
+  procedure MaybeReadFullyQualifiedIdentifier(Var r : String);
+  
   begin
-    ExpectIdentifier;
-    Result := CurTokenString;
-
-    while True do begin
+    while True do 
+      begin
       NextToken;
-      if CurToken = tkDot then begin
+      if CurToken = tkDot then 
+        begin
         ExpectIdentifier;
-        Result := Result + '.' + CurTokenString;
-      end else
+        R:=R + '.' + CurTokenString;
+        end 
+      else
         break;
-    end;
-	
+      end;
+  end;
+
+  function GetAccessorName: String;
+  begin
+    ExpectIdentifier;
+    Result := CurTokenString;
+    MaybeReadFullyQualifiedIdentifier(Result);
     if CurToken = tkSquaredBraceOpen then begin
       Result := Result + '[';
       NextToken;
@@ -1576,9 +1583,8 @@ procedure TPasParser.ParseProperty(Element:TPasElement);
       Result := Result + ']';
     end else 
       UngetToken;
-  
+    MaybeReadFullyQualifiedIdentifier(Result);  
 //    writeln(Result);
-
   end;
 
 begin

+ 1 - 1
packages/fcl-xml/src/xmlcfg.pp

@@ -300,7 +300,7 @@ procedure TXMLConfig.Loaded;
 begin
   inherited Loaded;
   if Length(Filename) > 0 then
-    SetFilename(Filename);              // Load the XML config file
+    SetFilename(Filename, true);              // Load the XML config file
 end;
 
 function TXMLConfig.FindNode(const APath: String;

+ 1 - 1
packages/fcl-xml/src/xmlconf.pp

@@ -270,7 +270,7 @@ procedure TXMLConfig.Loaded;
 begin
   inherited Loaded;
   if Length(Filename) > 0 then
-    SetFilename(Filename);              // Load the XML config file
+    DoSetFilename(Filename,True);              // Load the XML config file
 end;
 
 // TODO: copied from dom.pp, make public there and delete here

+ 0 - 13
rtl/objpas/classes/util.inc

@@ -11,19 +11,6 @@
 
  **********************************************************************}
 
-Function IntToStr (I : Longint) : String;
-
-begin
-  Str(I,Result);
-end;
-
-function IsValidIdent(const Ident: string): Boolean;
-
-begin
-  Result:=True;
-end;
-
-
 procedure BinToHex(BinValue, HexValue: PChar; BinBufSize: Integer);
 Const
   HexDigits='0123456789ABCDEF';

+ 14 - 5
rtl/objpas/sysutils/dati.inc

@@ -300,14 +300,14 @@ begin
   result := FormatDateTime('ddddd', Date);
 end ;
 
-{  TimeToStr returns a string representation of Time using ShortTimeFormat   }
+{  TimeToStr returns a string representation of Time using LongTimeFormat   }
 
 function TimeToStr(Time: TDateTime): string;
 begin
-  result := FormatDateTime('t', Time);
+  result := FormatDateTime('tt', Time);
 end ;
 
-{   DateTimeToStr returns a string representation of DateTime using ShortDateTimeFormat   }
+{   DateTimeToStr returns a string representation of DateTime using LongDateTimeFormat   }
 
 function DateTimeToStr(DateTime: TDateTime): string;
 begin
@@ -481,7 +481,16 @@ while (i < 5) and (TimeValues[i] <> -1) do begin
    end ;
 If (i<5) and (TimeValues[I]=-1) then
   TimeValues[I]:=0;
-if PM and (TimeValues[0] <> 12) then Inc(TimeValues[0], 12);
+if PM then
+  begin
+  if (TimeValues[0] <> 12) then 
+    Inc(TimeValues[0], 12);
+  end
+else
+  begin
+  if (TimeValues[0]=12) then
+    TimeValues[0]:=0;
+  end;   
 result := EncodeTime(TimeValues[0], TimeValues[1], TimeValues[2], TimeValues[3]);
 end ;
 
@@ -695,7 +704,7 @@ var
                        if (Hour<>0) or (Minute<>0) or (Second<>0) then
                         begin
                           StoreString(' ');
-                          StoreFormat(TimeReformat(ShortTimeFormat));
+                          StoreFormat(TimeReformat(LongTimeFormat));
                         end;
                      end;
                 end;