123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563 |
- {
- This file is part of the Free Component Library (FCL)
- Copyright (c) 2008 by the Free Pascal development team
- 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.
- **********************************************************************}
- // Original header
- // I, Danny Milosavljevic, hereby release this code into the public domain.
- unit ascii85;
- {$M+}
- {$MODE OBJFPC}
- // Based on C# code from <http://www.codinghorror.com/blog/archives/000410.html> by Jeff Atwood,
- // which is based on C code from <http://www.stillhq.com/cgi-bin/cvsweb/ascii85/>.
- interface
- uses sysutils, classes;
- type
- TASCII85State = (ascInitial = 0, ascOneEncodedChar = 1, ascTwoEncodedChars = 2, ascThreeEncodedChars = 3, ascFourEncodedChars = 4, ascNoEncodedChar = 5, ascPrefix = 6);
- TASCII85RingBuffer = class
- private
- fBuffer : array[0..1023] of Byte;
- fBufferReadPosition : Cardinal;
- fBufferWritePosition : Cardinal;
- fBufferFillCount : Cardinal;
- protected
- function GetBufferSize() : Cardinal; inline;
- published
- property FillCount : Cardinal read fBufferFillCount;
- property Size : Cardinal read GetBufferSize;
- procedure Write(const aBuffer; aSize : Cardinal); inline;
- function Read(var aBuffer; aSize : Cardinal) : Cardinal; inline;
- end;
- TASCII85DecoderStream = class(TOwnerStream)
- private
- fBExpectBoundary : Boolean;
- fTuple : Cardinal;
- fState : TASCII85State;
- fBEOF : Boolean;
- fBSourceEOF : Boolean;
- fBuffer : TASCII85RingBuffer;
- fPosition : Int64;
- // decoded data:
- fEncodedBuffer : array[0..((1024 * 5 + 3) div 4) - 1] of Byte; // 1280. // could also be put on the stack, doesn't need to persist between calls.
- private
- procedure BufferByte(aValue : Byte); inline;
- procedure BufferTuple(aValue : Cardinal; aDecodedCount : Cardinal); inline; // decoding shrinks from 5 byte to 4 byte.
- published
- constructor Create(aStream : TStream);
- procedure Decode(aInput : Byte); inline;
- procedure Close();
- function ClosedP() : Boolean; inline;
- property BExpectBoundary : Boolean read fBExpectBoundary write fBExpectBoundary;
- protected
- function GetPosition() : Int64; override;
- public
- destructor Destroy(); override;
- function Read(var aBuffer; aCount : longint) : longint; override;
- function Seek(aOffset : longint; aOrigin : word) : longint; override;
- function Seek(const aOffset: Int64; aOrigin: TSeekOrigin): Int64; override; overload;
- end;
- // TODO encoder...
- TASCII85EncoderStream = class(TOwnerStream)
- private
- FPos,
- FTuple : Cardinal;
- FCount,
- FWidth : Integer;
- FBoundary : Boolean;
- protected
- Procedure WriteBoundary;
- Procedure Flush;
- procedure Encode;
- public
- Constructor Create(ADest: TStream; AWidth : Integer = 72; ABoundary : Boolean = False);
- Destructor Destroy; Override;
- function Write(Const aBuffer; aCount : longint) : longint; override;
- Property Width : Integer Read FWidth;
- Property Boundary : Boolean Read FBoundary;
- end;
-
- implementation
- { TASCII85EncoderStream }
- Procedure TASCII85EncoderStream.WriteBoundary;
- Const
- SBoundary = '<~';
- begin
- Source.Write(SBoundary[1],2);
- FPos:=2;
- end;
- Procedure TASCII85EncoderStream.Encode;
- Var
- S : String[7];
- I,J : Integer;
- Buf : Array[0..4] of Byte;
-
- begin
- If (FTuple=0) then
- begin
- // Write 'z'
- S:='z';
- Inc(FPos);
- If (FPos>FWidth) then
- begin
- S:=S+sLineBreak;
- FPos:=0;
- end;
- end
- else
- begin
- For I:=0 to 4 do
- begin
- Buf[i]:=FTuple mod 85;
- FTuple:=FTuple div 85;
- end;
- J:=0;
- S:='';
- For I:=FCount+1 downto 0 do
- begin
- Inc(j);
- S[J]:=Char(Buf[i]+Ord('!'));
- SetLength(S,J);
- Inc(FPos);
- If (FPos>FWidth) then
- begin
- FPos:=0;
- S:=S+sLinebreak;
- J:=Length(S);
- end;
- end;
- end;
- Source.Write(S[1],Length(S));
- FTuple:=0;
- FCount:=-1;
- end;
- Procedure TASCII85EncoderStream.Flush;
- Const
- Boundary1 = '~>'+slinebreak;
- Boundary2 = slinebreak+Boundary1;
-
- Var
- S : String;
- begin
- If FCount>0 then
- Encode;
- If FBoundary then
- begin
- If FPos+2>FWidth then
- S:=Boundary2
- else
- S:=Boundary1;
- Source.Write(S[1],Length(S));
- FBoundary:=False;
- end;
- end;
- Constructor TASCII85EncoderStream.Create(ADest: TStream; AWidth : Integer = 72; ABoundary : Boolean = False);
- begin
- Inherited Create(ADest);
- FWidth:=AWidth;
- FBoundary:=ABoundary;
- If FBoundary then
- WriteBoundary;
- end;
- Destructor TASCII85EncoderStream.Destroy;
- begin
- Flush;
- Inherited;
- end;
- function TASCII85EncoderStream.Write(Const aBuffer; aCount : longint) : longint;
- Var
- P : PByte;
- C : Byte;
-
- begin
- P:=@Abuffer;
- Result:=ACount;
- While ACount>0 do
- begin
- C:=P^;
- Case FCount of
- 0 : FTuple:=FTuple or (C shl 24);
- 1 : FTuple:=FTuple or (C shl 16);
- 2 : FTuple:=FTuple or (C shl 8);
- 3 : begin
- FTuple:=FTuple or C;
- encode;
- end;
- end;
- Inc(FCount);
- Inc(P);
- Dec(ACount);
- end;
- end;
-
- { TRingBuffer }
- function TASCII85RingBuffer.GetBufferSize() : Cardinal; inline;
- begin
- Result := Length(fBuffer);
- end;
- procedure TASCII85RingBuffer.Write(const aBuffer; aSize : Cardinal); inline;
- var
- vBuffer : PByte;
- begin
- vBuffer := @aBuffer;
- // TODO optimize.
- while aSize > 0 do begin
- assert(fBufferFillCount < Length(fBuffer));
- fBuffer[fBufferWritePosition] := vBuffer^;
- Inc(vBuffer);
- Inc(fBufferFillCount);
- Inc(fBufferWritePosition);
- if fBufferWritePosition >= Length(fBuffer) then
- fBufferWritePosition := 0;
- assert(fBufferWritePosition <> fBufferReadPosition);
- Dec(aSize);
- end;
- end;
- function TASCII85RingBuffer.Read(var aBuffer; aSize : Cardinal) : Cardinal; inline;
- var
- vBuffer : PByte;
- vBufferCount : Cardinal;
- vBufferCount1 : Cardinal;
- vBufferCount2 : Cardinal;
- begin
- vBuffer := @aBuffer;
- Result := 0;
- if fBufferFillCount > 0 then begin
- vBufferCount := aSize; // try to take as much as requested by the client...
- if vBufferCount > fBufferFillCount then // ... if possible.
- vBufferCount := fBufferFillCount;
- if fBufferReadPosition < fBufferWritePosition then begin { ------RXXXXXXW-------- }
- vBufferCount1 := fBufferWritePosition - fBufferReadPosition; // max count for the first Move.
- assert(vBufferCount <= vBufferCount1);
- Move(fBuffer[fBufferReadPosition], vBuffer^, vBufferCount);
- Inc(vBuffer, vBufferCount);
- end else begin { XXXW-----RXXXXXXXXXXXX }
- vBufferCount1 := Length(fBuffer) - fBufferReadPosition; // count for the first Move.
- if vBufferCount < vBufferCount1 then
- vBufferCount1 := vBufferCount;
- if vBufferCount1 > 0 then begin
- Move(fBuffer[fBufferReadPosition], vBuffer^, vBufferCount1);
- Inc(vBuffer, vBufferCount1);
- end;
- vBufferCount2 := vBufferCount - vBufferCount1; // remaining count for the second Move.
- if vBufferCount2 > 0 then begin
- Move(fBuffer[0], vBuffer^, vBufferCount2);
- Inc(vBuffer, vBufferCount2);
- end;
- end;
- Inc(fBufferReadPosition, vBufferCount);
- while fBufferReadPosition >= Length(fBuffer) do
- Dec(fBufferReadPosition, Length(fBuffer));
- assert(fBufferFillCount >= vBufferCount);
- Dec(fBufferFillCount, vBufferCount);
- Inc(Result, vBufferCount);
- end;
- end;
- { TDecoder }
- const
- cPow85 : array[0..4] of Cardinal = (85*85*85*85, 85*85*85, 85*85, 85, 1); // uint
- function DecodeNonTrivialByte(aInput : Byte) : Cardinal; inline;
- begin
- if (aInput >= ord('!')) and (aInput <= ord('u')) then
- Result := aInput - ord('!')
- else
- raise EConvertError.Create(Format('could not decode value %d', [aInput]));
- // if chr(aInput) in ['!'..'u'] then
- end;
- constructor TASCII85DecoderStream.Create(aStream : TStream);
- begin
- inherited Create(aStream);
- fBuffer := TASCII85RingBuffer.Create();
- end;
- procedure TASCII85DecoderStream.BufferByte(aValue : Byte); inline;
- begin
- fBuffer.Write(aValue, 1);
- end;
- procedure TASCII85DecoderStream.BufferTuple(aValue : Cardinal; aDecodedCount { DECODED!!!} : Cardinal); inline;
- begin
- if aDecodedCount >= 1 then begin
- BufferByte(aValue shr (24 - (0 * 8)));
- if aDecodedCount >= 2 then begin
- BufferByte((aValue shr (24 - (1 * 8))) and $ff);
- if aDecodedCount >= 3 then begin
- BufferByte((aValue shr (24 - (2 * 8))) and $ff);
- if aDecodedCount >= 4 then begin
- BufferByte((aValue shr (24 - (3 * 8))) and $ff);
- if aDecodedCount >= 5 then begin
- raise EConvertError.Create('not enough decoded data (internal error).');
- end;
- end;
- end;
- end;
- end;
- end;
- procedure TASCII85DecoderStream.Decode(aInput : Byte);
- begin
- if (aInput in [ 10, 13, 9, {0, 8,} 12, 32]) and (fState <> ascPrefix { chicken}) then // skip whitespace.
- Exit;
- case fState of
- ascInitial, ascNoEncodedChar:
- if aInput = ord('z') then begin
- BufferTuple(0, 4);
- end else begin
- if (aInput = ord('<')) and (fState = ascInitial) {and (fBExpectBoundary)} then begin
- fState := ascPrefix;
- end else begin
- fTuple := fTuple + DecodeNonTrivialByte(aInput) * cPow85[0];
- fState := ascOneEncodedChar;
- end;
- end;
- ascOneEncodedChar:
- begin
- fTuple := fTuple + DecodeNonTrivialByte(aInput) * cPow85[1];
- fState := ascTwoEncodedChars;
- end;
- ascTwoEncodedChars:
- begin
- fTuple := fTuple + DecodeNonTrivialByte(aInput) * cPow85[2];
- fState := ascThreeEncodedChars;
- end;
- ascThreeEncodedChars:
- begin
- fTuple := fTuple + DecodeNonTrivialByte(aInput) * cPow85[3];
- fState := ascFourEncodedChars;
- end;
- ascFourEncodedChars:
- begin
- fTuple := fTuple + DecodeNonTrivialByte(aInput) * cPow85[4];
- BufferTuple(fTuple, 4);
- fTuple := 0;
- fState := ascNoEncodedChar;
- end;
- ascPrefix:
- begin
- if aInput = ord('~') then begin
- fBExpectBoundary := True;
- fState := ascNoEncodedChar
- end else begin
- // whoops, actually "~" is outside the allowed range, so we CAN find out whether there was supposed to be a boundary string or not on our own...
- // we reached this place since we saw a '<', thought it was part of '<~', but it wasn't. '<' is an allowed encoded character.
- // catch up on work we should have been doing...
- assert(fTuple = 0);
- fTuple := fTuple + DecodeNonTrivialByte(ord('<')) * cPow85[0];
- //fState := ascOneEncodedChar;
- fTuple := fTuple + DecodeNonTrivialByte(aInput) * cPow85[1];
- fState := ascTwoEncodedChars;
- //raise EConvertError.Create(Format('expected ''<~'', got %d', [aInput]));
- end;
- end
- else
- raise EConvertError.Create('internal error');
- end;
- end;
- destructor TASCII85DecoderStream.Destroy();
- begin
- Self.Close();
- FreeAndNil(fBuffer);
- inherited Destroy;
- end;
- function TASCII85DecoderStream.ClosedP() : Boolean; inline;
- begin
- Result := (fState in [ascInitial, ascNoEncodedChar, ascPrefix]);
- end;
- procedure TASCII85DecoderStream.Close();
- var
- vCount : Cardinal;
- begin
- if fState = ascPrefix then
- raise EConvertError.Create('unexpected end of file while trying to find ''<~'' prefix (after the ''<'' was seen).');
- if not (fState in [ascInitial, ascNoEncodedChar, ascPrefix]) then begin // we have some bytes left over.
- if fState = ascOneEncodedChar then
- raise EConvertError.Create('The last block of ASCII85 data cannot be a single byte.');
- vCount := Cardinal(fState) - 1; // one less!!
- fTuple := fTuple + cPow85[vCount];
- BufferTuple(fTuple, vCount);
- fState := ascInitial;
- end;
- end;
- function TASCII85DecoderStream.Read(var aBuffer; aCount : longint) : longint;
- var
- vAvailableCount : Cardinal;
- vPermittedReadCount : Cardinal;
- vEncodedBufferCount : Cardinal;
- vEncodedBufferIndex : Cardinal;
- vItem : Byte;
- vBuffer : PByte;
- vBufferCount : Cardinal;
- begin
- vBuffer := @aBuffer;
- Result := 0;
- if fBEOF then begin
- Exit;
- end;
- repeat
- // first use up the buffer contents as far as possible.
- if aCount <= 0 then
- Break;
- vBufferCount := fBuffer.Read(vBuffer^, aCount);
- assert(vBufferCount <= aCount);
- Inc(vBuffer, vBufferCount);
- Dec(aCount, vBufferCount);
- Inc(Result, vBufferCount);
- if fBSourceEOF and (vBufferCount = 0) then begin
- fBEOF := True;
- Break;
- end;
- if aCount <= 0 then
- Break;
- // here, aCount contains the REMAINING request and the buffer is either empty or there wasn't that much needed anyway (in the latter case the Exit above finished the function).
- // if then, there's still something needed, fill the buffer only as far as we need to.
- assert(fBuffer.FillCount = 0);
- vAvailableCount := fBuffer.Size - fBuffer.FillCount;
- vPermittedReadCount := vAvailableCount shr 2; // worst-case, decoded data will grow 4x ('z' -> '0000').
- {if aCount < vAvailableCount then begin
- vAvailableCount := aCount;}
- vEncodedBufferCount := 0;
- if not fBSourceEOF then
- vEncodedBufferCount := Source.Read(fEncodedBuffer[0], vPermittedReadCount);
- if (vEncodedBufferCount = 0) then begin // EOF
- fBSourceEOF := True;
- if not ClosedP() then
- Close() // make sure we catch the "virtual characters". This could fill the buffer a little bit.
- {else
- fBEOF := True};
- Continue;
- end else // Buffer the output we couldn't pass on so far.
- for vEncodedBufferIndex := 0 to vEncodedBufferCount - 1 do begin
- vItem := fEncodedBuffer[vEncodedBufferIndex];
- if (vItem = ord('~')) and fBExpectBoundary then begin // holy #@! oops...
- fBSourceEOF := True;
- {if not fBExpectBoundary then -- flag is not yet valid.
- raise EConvertError.Create('unexpected ''~>'' (there was no starting ''<~'', so why would there be a final one?).');
- }
- // note that here, it could be that we ran over the boundary '~>' suffix in the underlying stream and didn't notice. In that case, the 'Decode' call below would break.
- if not ClosedP() then
- Close(); // make sure we catch the "virtual characters". This could fill the buffer a little bit.
- // seek the underlying stream and hope nobody noticed that we completely ignored the boundary :)
- try
- Source.Seek(vEncodedBufferIndex - vEncodedBufferCount + 1, 1); // from current position.
- if Source.ReadByte() <> ord('>') then
- raise EConvertError.Create('the final ''~>'' is malformed.');
- except
- on E : EConvertError do
- raise;
- {$IFNDEF UNSEEKABLE_STREAMS_ARE_EVIL}
- else
- ; // too bad... well, we tried.
- {$ENDIF}
- end;
- Break; // for.
- end;
- Self.Decode(vItem);
- end;
- until (aCount <= 0) or (fBEOF) or (vPermittedReadCount = 0);
- Inc(fPosition, Result);
- end;
- function TASCII85DecoderStream.Seek(const aOffset: Int64; aOrigin: TSeekOrigin): Int64;
- begin
- if (aOrigin = soCurrent) and (aOffset = 0) then begin // get position.
- Result := fPosition;
- Exit;
- end;
- raise EReadError.Create('could not seek...');
- //assert(fState in [ascInitial, ascNoEncodedChar]);
- //Result := inherited Seek(aOffset, aOrigin); // bad idea.
- end;
- function TASCII85DecoderStream.Seek(aOffset : longint; aOrigin : word) : longint;
- begin
- Result := Self.Seek(Int64(aOffset), TSeekOrigin(aOrigin));
- end;
- function TASCII85DecoderStream.GetPosition() : Int64;
- begin
- Result := fPosition;
- end;
- initialization
- assert(Sizeof(Cardinal) >= 4);
- end.
|