{ 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 by Jeff Atwood, // which is based on C code from . 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.