123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- {
- This file is part of the Free Component Library (FCL)
- Copyright (c) 1999-2000 by Michael Van Canneyt and Florian Klaempfl
- base64 encoder & decoder (c) 1999 Sebastian Guenther
- See the file COPYING.FPC, included in this distribution,
- for details about the copyright.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- **********************************************************************}
- // 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+}
- unit base64;
- interface
- uses classes, sysutils;
- type
- TBase64EncodingStream = class(TOwnerStream)
- protected
- TotalBytesProcessed, BytesWritten: LongWord;
- Buf: array[0..2] of Byte;
- BufSize: Integer; // # of bytes used in Buf
- public
- destructor Destroy; override;
- Function Flush : Boolean;
- function Write(const Buffer; Count: Longint): Longint; override;
- 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 EBase64DecodingException
- * - '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(TOwnerStream)
- private
- FMode: TBase64DecodingMode;
- procedure SetMode(const AValue: TBase64DecodingMode);
- function GetSize: Int64; override;
- function GetPosition: Int64; override;
- protected
- 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 Source & decoded
- FEOF: Boolean; // if true, all decoded bytes have been read
- public
- constructor Create(ASource: TStream);
- constructor Create(ASource: TStream; AMode: TBase64DecodingMode);
- procedure Reset;
- function Read(var 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;
- function EncodeStringBase64(const s:string):String;
- function DecodeStringBase64(const s:string;strict:boolean=false):String;
- 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+/';
- 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
- function TBase64EncodingStream.Flush : Boolean;
- var
- WriteBuf: array[0..3] of Char;
- begin
- // Fill output to multiple of 4
- case (TotalBytesProcessed mod 3) of
- 1: begin
- WriteBuf[0] := EncodingTable[Buf[0] shr 2];
- WriteBuf[1] := EncodingTable[(Buf[0] and 3) shl 4];
- WriteBuf[2] := '=';
- WriteBuf[3] := '=';
- Source.Write(WriteBuf, 4);
- Result:=True;
- Inc(TotalBytesProcessed,2);
- end;
- 2: begin
- WriteBuf[0] := EncodingTable[Buf[0] shr 2];
- WriteBuf[1] := EncodingTable[(Buf[0] and 3) shl 4 or (Buf[1] shr 4)];
- WriteBuf[2] := EncodingTable[(Buf[1] and 15) shl 2];
- WriteBuf[3] := '=';
- Source.Write(WriteBuf, 4);
- Result:=True;
- Inc(TotalBytesProcessed,1);
- end;
- else
- Result:=False;
- end;
- end;
- destructor TBase64EncodingStream.Destroy;
- begin
- Flush;
- inherited Destroy;
- end;
- function TBase64EncodingStream.Write(const Buffer; Count: Longint): Longint;
- var
- ReadNow: LongInt;
- p: Pointer;
- WriteBuf: array[0..3] of Char;
- begin
- Inc(TotalBytesProcessed, Count);
- Result := Count;
- p := @Buffer;
- while count > 0 do begin
- // Fetch data into the Buffer
- ReadNow := 3 - BufSize;
- if ReadNow > Count then break; // Not enough data available
- Move(p^, Buf[BufSize], ReadNow);
- Inc(p, ReadNow);
- Dec(Count, ReadNow);
- // Encode the 3 bytes in Buf
- WriteBuf[0] := EncodingTable[Buf[0] shr 2];
- WriteBuf[1] := EncodingTable[(Buf[0] and 3) shl 4 or (Buf[1] shr 4)];
- WriteBuf[2] := EncodingTable[(Buf[1] and 15) shl 2 or (Buf[2] shr 6)];
- WriteBuf[3] := EncodingTable[Buf[2] and 63];
- Source.Write(WriteBuf, 4);
- Inc(BytesWritten, 4);
- BufSize := 0;
- end;
- Move(p^, Buf[BufSize], count);
- Inc(BufSize, count);
- end;
- function TBase64EncodingStream.Seek(Offset: Longint; Origin: Word): Longint;
- begin
- Result := BytesWritten;
- if BufSize > 0 then
- Inc(Result, 4);
- // This stream only supports the Seek modes needed for determining its size
- if not ((((Origin = soFromCurrent) or (Origin = soFromEnd)) and (Offset = 0))
- or ((Origin = soFromBeginning) and (Offset = Result))) then
- 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 Sources (for bdmStrict we also get the Size property)
- if DecodedSize<>-1 then Exit(DecodedSize);
- ipos := Source.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 := Source.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 Source 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 := Source.Position;
- isize := Source.Size;
- Result := ((ReadBase64ByteCount + (isize - ipos) + 3) div 4) * 3;
- Source.Seek(-2, soFromEnd);
- Source.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;
- Source.Position := ipos; // restore position in input stream
- // store calculated DecodedSize
- DecodedSize := Result;
- end;
- function TBase64DecodingStream.GetPosition: Int64;
- begin
- Result := CurPos;
- end;
- constructor TBase64DecodingStream.Create(ASource: TStream);
- begin
- Create(ASource, bdmMIME); // MIME mode is default
- end;
- constructor TBase64DecodingStream.Create(ASource: TStream; AMode: TBase64DecodingMode);
- begin
- inherited Create(ASource);
- Mode := AMode;
- Reset;
- end;
- procedure TBase64DecodingStream.Reset;
- begin
- ReadBase64ByteCount := 0; // number of bytes Read form Source 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: 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); // 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 := @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; // 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 := Source.Read(ReadBuf[ReadOK], ToRead);
- //WriteLn('ToRead = ', ToRead, ', HaveRead = ', HaveRead, ', ReadOK=', ReadOk);
- 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 // 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] := 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;
- 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 (Source.Position < Source.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 (Source.Position < Source.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[1] := (ReadBuf[1] and 15) shl 4 or ReadBuf[2] shr 2;
- Buf[2] := (ReadBuf[2] and 3) shl 6 or ReadBuf[3];
- end;
-
- if Count <= 0 then begin
- Break;
- end;
- // 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.Seek(Offset: Longint; Origin: Word): Longint;
- begin
- // TODO: implement Seeking in TBase64DecodingStream
- raise EStreamError.Create('Invalid stream operation');
- end;
- function DecodeStringBase64(const s:string;strict:boolean=false):String;
- var
- Instream,
- Outstream : TStringStream;
- Decoder : TBase64DecodingStream;
- begin
- Instream:=TStringStream.Create(s);
- try
- Outstream:=TStringStream.Create('');
- try
- if strict then
- Decoder:=TBase64DecodingStream.Create(Instream,bdmStrict)
- else
- Decoder:=TBase64DecodingStream.Create(Instream,bdmMIME);
- try
- Outstream.CopyFrom(Decoder,Decoder.Size);
- Result:=Outstream.DataString;
- finally
- Decoder.Free;
- end;
- finally
- Outstream.Free;
- end;
- finally
- Instream.Free;
- end;
- end;
- function EncodeStringBase64(const s:string):String;
- var
- Outstream : TStringStream;
- Encoder : TBase64EncodingStream;
- begin
- Outstream:=TStringStream.Create('');
- try
- Encoder:=TBase64EncodingStream.create(outstream);
- try
- Encoder.Write(s[1],Length(s));
- finally
- Encoder.Free;
- end;
- Result:=Outstream.DataString;
- finally
- Outstream.free;
- end;
- end;
- end.
|