{***************************************************************************** The DEC team (see file NOTICE.txt) licenses this file to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. A copy of this licence is found in the root directory of this project in the file LICENCE.txt or alternatively at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. *****************************************************************************} /// /// This unit provides a standardisized way for applying format conversions /// to data /// unit DECFormat; interface {$INCLUDE DECOptions.inc} uses {$IFDEF FPC} SysUtils, Classes, {$ELSE} System.SysUtils, System.Classes, {$ENDIF} DECBaseClass, DECFormatBase, DECUtil; type /// /// wrapper (allows omitting DECFormatBase in user code) /// TDECFormat = DECFormatBase.TDECFormat; /// /// wrapper (allows omitting DECFormatBase in user code) /// TDECFormatClass = DECFormatBase.TDECFormatClass; /// /// wrapper (allows omitting DECFormatBase in user code) /// TFormat_Copy = DECFormatBase.TFormat_Copy; TFormat_HEX = class; TFormat_HEXL = class; TFormat_Base16 = class; TFormat_Base16L = class; TFormat_DECMIME32 = class; TFormat_Base64 = class; TFormat_MIME64 = class; TFormat_Radix64 = class; TFormat_PGP = class; TFormat_UU = class; TFormat_XX = class; TFormat_ESCAPE = class; TFormat_BigEndian16 = class; TFormat_BigEndian32 = class; /// /// Hexadecimal in Uppercase, Base16, see http://tools.ietf.org/html/rfc4648 /// TFormat_HEX = class(TDECFormat) protected class procedure DoEncode(const Source; var Dest: TBytes; Size: Integer); override; class procedure DoDecode(const Source; var Dest: TBytes; Size: Integer); override; class function DoIsValid(const Data; Size: Integer): Boolean; override; public class function CharTableBinary: TBytes; virtual; end; /// /// Hexadecimal in lowercase, Base16, see http://tools.ietf.org/html/rfc4648 /// TFormat_HEXL = class(TFormat_HEX) public class function CharTableBinary: TBytes; override; end; /// /// Same as TFormat_HEX, use TFormat_HEX instead /// TFormat_Base16 = class(TFormat_HEX) end deprecated 'Use TFormat_HEX instead'; /// /// Same as TFormat_HEXL, use TFormat_HEXL instead /// TFormat_Base16L = class(TFormat_HEXL) end deprecated 'Use TFormat_HEXL instead'; /// /// Proprietary variant of MINE32, kept for backwards compatibility with old /// DEC versions /// /// /// This formatting should only be used for supporting legacy projects which /// already use this format. It is being considered to be more or less deprecated. /// TFormat_DECMIME32 = class(TFormat_HEX) protected /// /// Encodes data passed to this method in the proprietary DECMIME32 format. /// /// /// This formatting should only be used for supporting legacy projects which /// already use this format. It is being considered to be more or less deprecated. /// class procedure DoEncode(const Source; var Dest: TBytes; Size: Integer); override; /// /// Decodes data passed to this method in the proprietary DECMIME32 format /// into an array of normal bytes. /// /// /// This formatting should only be used for supporting legacy projects which /// already use this format. It is being considered to be more or less deprecated. /// class procedure DoDecode(const Source; var Dest: TBytes; Size: Integer); override; /// /// Checks if certain data adheres to the rules for this formatting. /// /// /// This formatting should only be used for supporting legacy projects which /// already use this format. It is being considered to be more or less deprecated. /// class function DoIsValid(const Data; Size: Integer): Boolean; override; public class function CharTableBinary: TBytes; override; end; /// /// Same as DECMIME32, which itsself should only be used for legacy projects /// TFormat_MIME32 = class(TFormat_DECMIME32) end deprecated 'Use TFormat_DECMIME32 instead'; /// /// Base64 (without soft wraps), see http://tools.ietf.org/html/rfc4648 /// TFormat_Base64 = class(TFormat_HEX) protected class procedure DoEncode(const Source; var Dest: TBytes; Size: Integer); override; class procedure DoDecode(const Source; var Dest: TBytes; Size: Integer); override; class function DoIsValid(const Data; Size: Integer): Boolean; override; public class function CharTableBinary: TBytes; override; end; /// /// Same as TFormat_Base64, use TFormat_Base64 instead, kept for backwards /// compatibility only /// TFormat_MIME64 = class(TFormat_Base64) end deprecated 'Use TFormat_Base64 instead'; /// /// OpenPGP/PGP Base64 with 24-bit Checksums, see http://tools.ietf.org/html/rfc4880 /// TFormat_Radix64 = class(TFormat_Base64) /// /// Here the section needs to be private so that the variable can be accessed /// for initialization in the initialization section, which is needed since /// all functionality of the class is implemented as class methods /// private /// /// Maximum number of chars for one line of message text /// class var FCharsPerLine : UInt32; protected /// /// Extracts the CRC24 checksum from Radix64 encoded data /// /// /// Data to extract the checksum from /// /// /// Size of the data in byte /// /// /// CRC24 checksum if present, otherwise $FFFFFFFF /// class function DoExtractCRC(const Data; var Size: Integer): UInt32; /// /// If the data given exceeds FCharsPerLine, means the maximum allowed /// line lenth, a CR/LF pair needs to be inserted at that position. /// /// /// Data to insert a CR/LF into if necessary /// /// /// In this byte array the processed data will be returned /// /// /// Maximum length of a line in byte. At this position the CR/LF will be /// inserted if the source passed in exceeds this length. /// class procedure InsertCRLF(const Source: TBytes; var Dest: TBytes; LineLength: Integer); class procedure DoEncode(const Source; var Dest: TBytes; Size: Integer); override; class procedure DoDecode(const Source; var Dest: TBytes; Size: Integer); override; class function DoIsValid(const Data; Size: Integer): Boolean; override; public /// /// Changes the number of chars after which a line break is being added /// /// /// Maximum number of chars for a single line. Values < 1 result in an /// EArgumentOutOfRangeException being raised /// class procedure SetCharsPerLine(const Value: UInt32); /// /// Returns the number of chars after which a line break will be introduced /// /// /// Maximum number of chars per line /// /// /// Cannot be a property, as properties cannot access class vars /// class function GetCharsPerLine: UInt32; end; /// /// Same as TFormat_Radix64, use TFormat_Radix64 instead, kept for backwards /// compatibility only /// TFormat_PGP = class(TFormat_Radix64) end deprecated 'Use TFormat_Radix64 instead'; /// /// Unix UU format /// TFormat_UU = class(TDECFormat) protected class procedure DoEncode(const Source; var Dest: TBytes; Size: Integer); override; class procedure DoDecode(const Source; var Dest: TBytes; Size: Integer); override; class function DoIsValid(const Data; Size: Integer): Boolean; override; public class function CharTableBinary: TBytes; virtual; end; /// /// Unix XX format /// TFormat_XX = class(TFormat_UU) public class function CharTableBinary: TBytes; override; end; /// /// Escaped format /// TFormat_ESCAPE = class(TDECFormat) protected class procedure DoEncode(const Source; var Dest: TBytes; Size: Integer); override; class procedure DoDecode(const Source; var Dest: TBytes; Size: Integer); override; class function DoIsValid(const Data; Size: Integer): Boolean; override; public class function CharTableBinary: TBytes; virtual; end; /// /// Conversion from/to 16 bit big endian /// TFormat_BigEndian16 = class(TDECFormat) private class procedure DoSawp(const Source; var Dest: TBytes; Size: Integer); inline; protected class procedure DoEncode(const Source; var Dest: TBytes; Size: Integer); override; class procedure DoDecode(const Source; var Dest: TBytes; Size: Integer); override; class function DoIsValid(const Data; Size: Integer): Boolean; override; public end; /// /// Conversion from/to 32 bit big endian /// TFormat_BigEndian32 = class(TDECFormat) private class procedure DoSawp(const Source; var Dest: TBytes; Size: Integer); inline; protected class procedure DoEncode(const Source; var Dest: TBytes; Size: Integer); override; class procedure DoDecode(const Source; var Dest: TBytes; Size: Integer); override; class function DoIsValid(const Data; Size: Integer): Boolean; override; public end; /// /// Conversion from/to 64 bit big endian /// TFormat_BigEndian64 = class(TDECFormat) private class procedure DoSawp(const Source; var Dest: TBytes; Size: Integer); inline; protected class procedure DoEncode(const Source; var Dest: TBytes; Size: Integer); override; class procedure DoDecode(const Source; var Dest: TBytes; Size: Integer); override; class function DoIsValid(const Data; Size: Integer): Boolean; override; public end; implementation uses DECCRC; // needed by TFormat_Radix64 resourcestring sInvalidStringFormat = 'Input is not an valid %s format'; class function TFormat_HEX.CharTableBinary: TBytes; begin SetLength(result, 48); // special and skipped chars // '0123456789ABCDEFX$ abcdefhHx()[]{},;:-_/\*+"'''+CHR(9)+CHR(10)+CHR(13); {$IF CompilerVersion >= 28.0} result := [$30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $41, $42, $43, $44, $45, $46, $58, $24, $20, $61, $62, $63, $64, $65, $66, $68, $48, $78, $28, $29, $5B, $5D, $7B, $7D, $2C, $3B, $3A, $2D, $5F, $2F, $5C, $2A, $2B, $22, $27, $09, $0A, $0D]; {$ELSE} // Remove this initialisation variant as soon as XE7+ is the new minimum // supported Delphi version result[ 0]:=$30; result[ 1]:=$31; result[ 2]:=$32; result[ 3]:=$33; result[ 4]:=$34; result[ 5]:=$35; result[ 6]:=$36; result[ 7]:=$37; result[ 8]:=$38; result[ 9]:=$39; result[10]:=$41; result[11]:=$42; result[12]:=$43; result[13]:=$44; result[14]:=$45; result[15]:=$46; result[16]:=$58; result[17]:=$24; result[18]:=$20; result[19]:=$61; result[20]:=$62; result[21]:=$63; result[22]:=$64; result[23]:=$65; result[24]:=$66; result[25]:=$68; result[26]:=$48; result[27]:=$78; result[28]:=$28; result[29]:=$29; result[30]:=$5B; result[31]:=$5D; result[32]:=$7B; result[33]:=$7D; result[34]:=$2C; result[35]:=$3B; result[36]:=$3A; result[37]:=$2D; result[38]:=$5F; result[39]:=$2F; result[40]:=$5C; result[41]:=$2A; result[42]:=$2B; result[43]:=$22; result[44]:=$27; result[45]:=$09; result[46]:=$0A; result[47]:=$0D; {$IFEND} end; class procedure TFormat_HEX.DoEncode(const Source; var Dest: TBytes; Size: Integer); var S : PByte; Table : TBytes; i : Integer; begin if Size <= 0 then Exit; SetLength(Dest, Size * 2); Table := CharTableBinary; S := PByte(@Source); i := 0; while Size > 0 do begin Dest[i] := Table[S^ shr 4]; Dest[i + 1] := Table[S^ and $F]; Inc(S); Dec(Size); Inc(i, 2); end; end; class procedure TFormat_HEX.DoDecode(const Source; var Dest: TBytes; Size: Integer); var S: PByte; D: PByte; T: TBytes; I,P: Integer; HasIdent: Boolean; begin SetLength(Dest, 0); if Size <= 0 then Exit; SetLength(Dest, Size div 2); // + 1); T := CharTableBinary; D := PByte(Dest); S := PByte(@Source); I := 0; HasIdent := False; while Size > 0 do begin P := TableFindBinary(S^, T, 18); if P < 0 then P := TableFindBinary(UpCaseBinary(S^), T, 16); if P < 0 then raise EDECException.CreateResFmt(@sInvalidStringFormat, [self.GetShortClassName]); Inc(S); if P >= 0 then if P > 16 then begin if not HasIdent then begin HasIdent := True; I := 0; D := PByte(Dest); end; end else begin if Odd(I) then begin D^ := D^ or P; Inc(D); end else D^ := P shl 4; Inc(I); end; Dec(Size); end; end; class function TFormat_HEX.DoIsValid(const Data; Size: Integer): Boolean; var T: TBytes; S: PByte; begin if not odd(Size) then begin Result := True; T := CharTableBinary; S := @Data; while Result and (Size > 0) do begin if TableFindBinary(S^, T, length(T)) >= 0 then begin Inc(S); Dec(Size); end else Result := False; end; end else result := false; end; class function TFormat_HEXL.CharTableBinary: TBytes; begin SetLength(result, 48); // special and skipped chars // '0123456789abcdefX$ ABCDEFhHx()[]{},;:-_/\*+"'''+CHR(9)+CHR(10)+CHR(13); {$IF CompilerVersion >= 28.0} result := [$30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $61, $62, $63, $64, $65, $66, $68, $58, $24, $20, $41, $42, $43, $44, $45, $46, $48, $78, $28, $29, $5B, $5D, $7B, $7D, $2C, $3B, $3A, $2D, $5F, $2F, $5C, $2A, $2B, $22, $27, $09, $0A, $0D]; {$ELSE} // Remove this initialisation variant as soon as XE7+ is the new minimum // supported Delphi version result[ 0]:=$30; result[ 1]:=$31; result[ 2]:=$32; result[ 3]:=$33; result[ 4]:=$34; result[ 5]:=$35; result[ 6]:=$36; result[ 7]:=$37; result[ 8]:=$38; result[ 9]:=$39; result[10]:=$61; result[11]:=$62; result[12]:=$63; result[13]:=$64; result[14]:=$65; result[15]:=$66; result[16]:=$68; result[17]:=$58; result[18]:=$24; result[19]:=$20; result[20]:=$41; result[21]:=$42; result[22]:=$43; result[23]:=$44; result[24]:=$45; result[25]:=$46; result[26]:=$48; result[27]:=$78; result[28]:=$28; result[29]:=$29; result[30]:=$5B; result[31]:=$5D; result[32]:=$7B; result[33]:=$7D; result[34]:=$2C; result[35]:=$3B; result[36]:=$3A; result[37]:=$2D; result[38]:=$5F; result[39]:=$2F; result[40]:=$5C; result[41]:=$2A; result[42]:=$2B; result[43]:=$22; result[44]:=$27; result[45]:=$09; result[46]:=$0A; result[47]:=$0D; {$IFEND} end; class function TFormat_DECMIME32.CharTableBinary: TBytes; begin // special and skipped chars // 'abcdefghijklnpqrstuwxyz123456789 =$()[]{},;:-_\*"'''+CHR(9)+CHR(10)+CHR(13); SetLength(result, 53); {$IF CompilerVersion >= 28.0} result := [$61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6E, $70, $71, $72, $73, $74, $75, $77, $78, $79, $7A, $31, $32, $33, $34, $35, $36, $37, $38, $39, $20, $3D, $24, $28, $29, $5B, $5D, $7B, $7D, $2C, $3B, $3A, $2D, $5F, $5C, $2A, $22, $27, $09, $0A, $0D]; {$ELSE} // Remove this initialisation variant as soon as XE7+ is the new minimum // supported Delphi version result[ 0]:=$61; result[ 1]:=$62; result[ 2]:=$63; result[ 3]:=$64; result[ 4]:=$65; result[ 5]:=$66; result[ 6]:=$67; result[ 7]:=$68; result[ 8]:=$69; result[ 9]:=$6A; result[10]:=$6B; result[11]:=$6C; result[12]:=$6E; result[13]:=$70; result[14]:=$71; result[15]:=$72; result[16]:=$73; result[17]:=$74; result[18]:=$75; result[19]:=$77; result[20]:=$78; result[21]:=$79; result[22]:=$7A; result[23]:=$31; result[24]:=$32; result[25]:=$33; result[26]:=$34; result[27]:=$35; result[28]:=$36; result[29]:=$37; result[30]:=$38; result[31]:=$39; result[32]:=$20; result[33]:=$3D; result[34]:=$24; result[35]:=$28; result[36]:=$29; result[37]:=$5B; result[38]:=$5D; result[39]:=$7B; result[40]:=$7D; result[41]:=$2C; result[42]:=$3B; result[43]:=$3A; result[44]:=$2D; result[45]:=$5F; result[46]:=$5C; result[47]:=$2A; result[48]:=$22; result[49]:=$27; result[50]:=$09; result[51]:=$0A; result[52]:=$0D; {$IFEND} end; class procedure TFormat_DECMIME32.DoEncode(const Source; var Dest: TBytes; Size: Integer); var T : TBytes; Src : TBytes; S, D: PByte; i, n: Integer; begin SetLength(Dest, 0); if Size <= 0 then Exit; // The passed in source parameter has to be converted into an array with // added additional 0 value. This is because in the original form a string was // being passed in as source parameter, which automatically contained a #00 at // the end and depending on length of the data passed in, the @S[i shr 3] index // calculation can result in an index which represents the last byte of the // source parameter. That one is accessed as PWord then which results in // reading the first byte behind that source parameter as well! This led to // wrong data errors in the unit tests. SetLength(Src, Size + 1); Move(Source, Src[0], Size); Src[length(Src)-1] := 0; Size := Size * 8; SetLength(Dest, Size div 5 + 5); D := @Dest[0]; T := CharTableBinary; S := @Src[0]; i := 0; n := 0; while i < Size do begin D^ := T[PWord(@S[i shr 3])^ shr (i and $7) and $1F]; Inc(D); Inc(i, 5); Inc(n); end; SetLength(Dest, n); SetLength(Src, 0); end; class function TFormat_DECMIME32.DoIsValid(const Data; Size: Integer): Boolean; var T: TBytes; S: PByte; begin Result := True; T := CharTableBinary; S := @Data; while Result and (Size > 0) do begin if TableFindBinary(S^, T, length(T)) >= 0 then begin Inc(S); Dec(Size); end else Result := False; end; end; class procedure TFormat_DECMIME32.DoDecode(const Source; var Dest: TBytes; Size: Integer); var T: TBytes; S: PByte; D: PByte; i, V: Integer; begin SetLength(Dest, 0); if Size <= 0 then Exit; Size := Size * 5; SetLength(Dest, Size div 8); T := CharTableBinary; S := @Source; D := @Dest[0]; FillChar(D^, Length(Dest), 0); i := 0; while i < Size do begin V := TableFindBinary(S^, T, 32); if V < 0 then V := TableFindBinary(UpCaseBinary(S^), T, 32); if V >= 0 then begin PWord(@D[i shr 3])^ := PWord(@D[i shr 3])^ or (V shl (i and $7)); Inc(i, 5); end else Dec(Size, 5); Inc(S); end; SetLength(Dest, Size div 8); end; class function TFormat_Base64.CharTableBinary: TBytes; begin // 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + // ' $()[]{},;:-_\*"'''+CHR(9)+CHR(10)+CHR(13); // special and skipped chars SetLength(result, 85); {$IF CompilerVersion >= 28.0} result := [$41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F, $50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6D, $6E, $6F, $70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $7A, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $2B, $2F, $3D, $20, $24, $28, $29, $5B, $5D, $7B, $7D, $2C, $3B, $3A, $2D, $5F, $5C, $2A, $22, $27, $09, $0A, $0D]; {$ELSE} // Remove this initialisation variant as soon as XE7+ is the new minimum // supported Delphi version result[0 ]:=$41; result[1 ]:=$42; result[2 ]:=$43; result[3 ]:=$44; result[4 ]:=$45; result[5 ]:=$46; result[6 ]:=$47; result[7 ]:=$48; result[8 ]:=$49; result[9 ]:=$4A; result[10]:=$4B; result[11]:=$4C; result[12]:=$4D; result[13]:=$4E; result[14]:=$4F; result[15]:=$50; result[16]:=$51; result[17]:=$52; result[18]:=$53; result[19]:=$54; result[20]:=$55; result[21]:=$56; result[22]:=$57; result[23]:=$58; result[24]:=$59; result[25]:=$5A; result[26]:=$61; result[27]:=$62; result[28]:=$63; result[29]:=$64; result[30]:=$65; result[31]:=$66; result[32]:=$67; result[33]:=$68; result[34]:=$69; result[35]:=$6A; result[36]:=$6B; result[37]:=$6C; result[38]:=$6D; result[39]:=$6E; result[40]:=$6F; result[41]:=$70; result[42]:=$71; result[43]:=$72; result[44]:=$73; result[45]:=$74; result[46]:=$75; result[47]:=$76; result[48]:=$77; result[49]:=$78; result[50]:=$79; result[51]:=$7A; result[52]:=$30; result[53]:=$31; result[54]:=$32; result[55]:=$33; result[56]:=$34; result[57]:=$35; result[58]:=$36; result[59]:=$37; result[60]:=$38; result[61]:=$39; result[62]:=$2B; result[63]:=$2F; result[64]:=$3D; result[65]:=$20; result[66]:=$24; result[67]:=$28; result[68]:=$29; result[69]:=$5B; result[70]:=$5D; result[71]:=$7B; result[72]:=$7D; result[73]:=$2C; result[74]:=$3B; result[75]:=$3A; result[76]:=$2D; result[77]:=$5F; result[78]:=$5C; result[79]:=$2A; result[80]:=$22; result[81]:=$27; result[82]:=$09; result[83]:=$0A; result[84]:=$0D; {$IFEND} end; class procedure TFormat_Base64.DoEncode(const Source; var Dest: TBytes; Size: Integer); var T: TBytes; S: PByte; D: PByte; B: UInt32; i: Integer; n: Integer; begin SetLength(Dest, 0); if Size <= 0 then Exit; SetLength(Dest, Size * 4 div 3 + 4); T := CharTableBinary; S := @Source; D := @Dest[0]; n := 0; while Size >= 3 do begin Dec(Size, 3); B := Byte(S[0]) shl 16 or Byte(S[1]) shl 8 or Byte(S[2]); D[0] := T[B shr 18 and $3F]; D[1] := T[B shr 12 and $3F]; D[2] := T[B shr 6 and $3F]; D[3] := T[B and $3F]; Inc(D, 4); S := @S[3]; Inc(n, 4); end; while Size > 0 do begin B := 0; for i := 0 to 2 do begin B := B shl 8; if Size > 0 then begin B := B or Byte(S[0]); S := @S[1]; end; Dec(Size); end; for i := 3 downto 0 do begin if Size < 0 then begin D[i] := T[64]; Inc(Size); end else D[i] := T[B and $3F]; B := B shr 6; end; Inc(D, 4); Inc(n, 4); end; // original calculation was substract dest ptr d - start of dest SetLength(Dest, n); end; class procedure TFormat_Base64.DoDecode(const Source; var Dest: TBytes; Size: Integer); var T: TBytes; S: PByte; D, L: PByte; // 1) to make pointer arithmetic work 2) P/TByteArray is limited to 32768 bytes B: UInt32; i, j, n: Integer; begin SetLength(Dest, 0); if Size <= 0 then Exit; SetLength(Dest, Size); T := CharTableBinary; S := @Source; D := @Dest[0]; Move(Source, Dest[0], Size); L := S + Size; j := 0; n := 0; while S < L do begin B := 0; j := 4; while (j > 0) and (S < L) do begin i := TableFindBinary(S^, T, 65); Inc(S); if i >= 0 then begin if i < 64 then begin B := B shl 6 or Byte(i); Dec(j); end else L := S; end; end; if j > 0 then begin if j >= 4 then begin j := 0; Break; end else B := B shl (6 * j); end; i := 2; while i >= 0 do begin D[i] := Byte(B); B := B shr 8; Dec(i); end; Inc(D, 3); Inc(n, 3); end; SetLength(Dest, n-j); end; class function TFormat_Base64.DoIsValid(const Data; Size: Integer): Boolean; var T: TBytes; S: PByte; begin Result := True; T := CharTableBinary; S := @Data; while Result and (Size > 0) do begin // A-Z, a-z, 0-9, + and / and CR/LF if S^ in [$41..$5A, $61..$7A, $2B, $2F..$39, $3D, $0D, $0A] then begin Inc(S); Dec(Size); end else Result := False; end; end; class function TFormat_Radix64.DoExtractCRC(const Data; var Size: Integer): UInt32; var L: PByte; // 1) to make pointer arithmetic work 2) P/TByteArray is limited to 32768 bytes C: Byte; R: TBytes; begin Result := $FFFFFFFF; C := CharTableBinary[64]; // get padding char, per default '=' L := PByte(@Data) + Size; while L <> @Data do begin if L^ = C then Break else Dec(L); // scan reverse for padding char end; if L - PByte(@Data) >= Size - 5 then // remaining chars must be > 4, e.g. '=XQRT' try Inc(L); inherited DoDecode(L^, R, Size - (L - PByte(@Data))); if Length(R) >= 3 then begin Result := 0; Move(R[0], Result, 3); Size := L - PByte(@Data); end; except end; end; class function TFormat_Radix64.DoIsValid(const Data; Size: Integer): Boolean; var crc24 : UInt32; Dest : TBytes; begin // Radix64 is like Base64 but with additional CRC24 checksum result := TFormat_Base64.IsValid(Data, Size); // Check contained checksum as well if result then begin crc24 := DoExtractCRC(Data, Size); // we need to decode, because it removes the CR/LF linebreaks which would // invalidate the checksum inherited DoDecode(Data, Dest, Size); if crc24 <> $FFFFFFFF then begin // recalc CRC and compare SwapBytes(crc24, 3); result := crc24 = CRCCalc(CRC_24, Dest[0], Length(Dest)); end else result := false; end; end; class function TFormat_Radix64.GetCharsPerLine: UInt32; begin result := FCharsPerLine; end; class procedure TFormat_Radix64.InsertCRLF(const Source: TBytes; var Dest: TBytes; LineLength: Integer); var S, D: PByte; i: Integer; begin i := Length(Source); if (LineLength <= 0) or (i <= LineLength) then begin SetLength(Dest, i); Move(Source[0], Dest[0], i); Exit; end; SetLength(Dest, i + i * 2 div LineLength + 2); S := @Source[0]; D := @Dest[0]; repeat Move(S^, D^, LineLength); Inc(S, LineLength); Inc(D, LineLength); D^ := Ord(#13); Inc(D); D^ := Ord(#10); Inc(D); Dec(i, LineLength); until i < LineLength; Move(S^, D^, i); Inc(D, i); SetLength(Dest, PByte(D) - PByte(Dest)); end; class procedure TFormat_Radix64.SetCharsPerLine(const Value: UInt32); begin Assert(Value > 0, 'Invalid number of chars per line: ' + IntToStr(Value)); if (Value > 0) then FCharsPerLine := Value else raise EArgumentOutOfRangeException.Create('Invalid number of chars per line: ' + IntToStr(Value)); end; class procedure TFormat_Radix64.DoEncode(const Source; var Dest: TBytes; Size: Integer); var TempData : TBytes; CRC : UInt32; CRCData : TBytes; Position : Integer; begin SetLength(Dest, 0); if Size <= 0 then Exit; // use Base64 inherited DoEncode(Source, TempData, Size); // split lines InsertCRLF(TempData, Dest, FCharsPerLine); SetLength(TempData, 0); CRC := CRCCalc(CRC_24, Source, Size); // calculate 24-bit Checksum SwapBytes(CRC, 3); // PGP use Big Endian // check and insert LF if needed Position := Length(Dest) - 1; // last char if Dest[Position] <> $0A then begin // insert CR needed, CRC must be in the next line Position := Length(Dest); SetLength(Dest, Position + 2); Dest[Position] := $0D; // append CR Dest[Position+1] := $0A; // append LF end; // encode CRC with Base64 too inherited DoEncode(CRC, CRCData, 3); // if CRC is too long insert CRLF. -1 to compensate the later added = char InsertCRLF(CRCData, TempData, FCharsPerLine - 1); CRCData := TempData; // append encoded CRC Position := Length(Dest); SetLength(Dest, Position + 1 + Length(CRCData)); Dest[Position] := Ord('='); Move(CRCData[0], Dest[Position + 1], Length(CRCData)); end; class procedure TFormat_Radix64.DoDecode(const Source; var Dest: TBytes; Size: Integer); var CRC: UInt32; begin SetLength(Dest, 0); if Size <= 0 then Exit; CRC := DoExtractCRC(Source, Size); inherited DoDecode(Source, Dest, Size); if CRC <> $FFFFFFFF then // check CRC if found begin SwapBytes(CRC, 3); if CRC <> CRCCalc(CRC_24, Dest[0], Length(Dest)) then raise EDECFormatException.CreateResFmt(@sInvalidStringFormat, [self.GetShortClassName]); end; end; class function TFormat_UU.CharTableBinary: TBytes; begin // '`!"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_' + // ' '+CHR(9)+CHR(10)+CHR(13); SetLength(result, 68); {$IF CompilerVersion >= 28.0} result := [$60, $21, $22, $23, $24, $25, $26, $27, $28, $29, $2A, $2B, $2C, $2D, $2E, $2F, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $3A, $3B, $3C, $3D, $3E, $3F, $40, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F, $50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $5B, $5C, $5D, $5E, $5F, $20, $09, $0A, $0D]; {$ELSE} // Remove this initialisation variant as soon as XE7+ is the new minimum // supported Delphi version result[ 0]:=$60; result[ 1]:=$21; result[ 2]:=$22; result[ 3]:=$23; result[ 4]:=$24; result[ 5]:=$25; result[ 6]:=$26; result[ 7]:=$27; result[ 8]:=$28; result[ 9]:=$29; result[10]:=$2A; result[11]:=$2B; result[12]:=$2C; result[13]:=$2D; result[14]:=$2E; result[15]:=$2F; result[16]:=$30; result[17]:=$31; result[18]:=$32; result[19]:=$33; result[20]:=$34; result[21]:=$35; result[22]:=$36; result[23]:=$37; result[24]:=$38; result[25]:=$39; result[26]:=$3A; result[27]:=$3B; result[28]:=$3C; result[29]:=$3D; result[30]:=$3E; result[31]:=$3F; result[32]:=$40; result[33]:=$41; result[34]:=$42; result[35]:=$43; result[36]:=$44; result[37]:=$45; result[38]:=$46; result[39]:=$47; result[40]:=$48; result[41]:=$49; result[42]:=$4A; result[43]:=$4B; result[44]:=$4C; result[45]:=$4D; result[46]:=$4E; result[47]:=$4F; result[48]:=$50; result[49]:=$51; result[50]:=$52; result[51]:=$53; result[52]:=$54; result[53]:=$55; result[54]:=$56; result[55]:=$57; result[56]:=$58; result[57]:=$59; result[58]:=$5A; result[59]:=$5B; result[60]:=$5C; result[61]:=$5D; result[62]:=$5E; result[63]:=$5F; result[64]:=$20; result[65]:=$09; result[66]:=$0A; result[67]:=$0D; {$IFEND} end; class procedure TFormat_UU.DoEncode(const Source; var Dest: TBytes; Size: Integer); var T: TBytes; S: PByte; D: PByte; // 1) to make pointer arithmetic work 2) P/TByteArray is limited to 32768 bytes L, i: Integer; B: Cardinal; begin SetLength(Dest, 0); if Size <= 0 then Exit; SetLength(Dest, Size * 4 div 3 + Size div 45 + 10); T := CharTableBinary; S := @Source; D := @Dest[0]; while Size > 0 do begin L := Size; if L > 45 then L := 45; Dec(Size, L); D^ := T[L]; while L > 0 do begin B := 0; for i := 0 to 2 do begin B := B shl 8; if L > 0 then begin B := B or S^; Inc(S); end; Dec(L); end; for i := 4 downto 1 do begin D[i] := T[B and $3F]; B := B shr 6; end; Inc(D, 4); end; Inc(D); end; SetLength(Dest, PByte(D) - PByte(Dest)); end; class procedure TFormat_UU.DoDecode(const Source; var Dest: TBytes; Size: Integer); var T: TBytes; S: PByte; D, L: PByte; // 1) to make pointer arithmetic work 2) P/TByteArray is limited to 32768 bytes i, E: Integer; B: UInt32; begin SetLength(Dest, 0); if Size <= 0 then Exit; SetLength(Dest, Size); T := CharTableBinary; S := @Source; D := @Dest[0]; L := PByte(S) + Size; repeat Size := TableFindBinary(S^, T, 64); if (Size < 0) or (Size > 45) then raise EDECException.CreateResFmt(@sInvalidStringFormat, [self.GetShortClassName]); Inc(S); while Size > 0 do begin B := 0; i := 4; while (i > 0) and (S <= L) do begin E := TableFindBinary(S^, T, 64); if E >= 0 then begin B := B shl 6 or Byte(E); Dec(i); end; Inc(S); end; i := 2; repeat D[i] := Byte(B); B := B shr 8; Dec(i); until i < 0; if Size > 3 then Inc(D, 3) else Inc(D, Size); Dec(Size, 3); end; until S >= L; SetLength(Dest, PByte(D) - PByte(Dest)); end; class function TFormat_UU.DoIsValid(const Data; Size: Integer): Boolean; var T: TBytes; S: PByte; Len, P, i: Integer; begin Result := False; T := CharTableBinary; Len := Length(T); S := @Data; P := 0; while Size > 0 do begin i := TableFindBinary(S^, T, Len); if i >= 0 then begin Dec(Size); Inc(S); if P = 0 then begin if i > 45 then Exit; P := (i * 4 + 2) div 3; end else if i < 64 then Dec(P); end else Exit; end; if P <> 0 then Exit; Result := True; end; class function TFormat_XX.CharTableBinary: TBytes; begin // '+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + // ' "()[]'''+CHR(9)+CHR(10)+CHR(13); SetLength(result, 74); {$IF CompilerVersion >= 28.0} result := [$2B, $2D, $30, $31, $32, $33, $34, $35, $36, $37, $38, $39, $41, $42, $43, $44, $45, $46, $47, $48, $49, $4A, $4B, $4C, $4D, $4E, $4F, $50, $51, $52, $53, $54, $55, $56, $57, $58, $59, $5A, $61, $62, $63, $64, $65, $66, $67, $68, $69, $6A, $6B, $6C, $6D, $6E, $6F, $70, $71, $72, $73, $74, $75, $76, $77, $78, $79, $7A, $20, $22, $28, $29, $5B, $5D, $27, $09, $0A, $0D]; {$ELSE} // Remove this initialisation variant as soon as XE7+ is the new minimum // supported Delphi version result[ 0]:=$2B; result[ 1]:=$2D; result[ 2]:=$30; result[ 3]:=$31; result[ 4]:=$32; result[ 5]:=$33; result[ 6]:=$34; result[ 7]:=$35; result[ 8]:=$36; result[ 9]:=$37; result[10]:=$38; result[11]:=$39; result[12]:=$41; result[13]:=$42; result[14]:=$43; result[15]:=$44; result[16]:=$45; result[17]:=$46; result[18]:=$47; result[19]:=$48; result[20]:=$49; result[21]:=$4A; result[22]:=$4B; result[23]:=$4C; result[24]:=$4D; result[25]:=$4E; result[26]:=$4F; result[27]:=$50; result[28]:=$51; result[29]:=$52; result[30]:=$53; result[31]:=$54; result[32]:=$55; result[33]:=$56; result[34]:=$57; result[35]:=$58; result[36]:=$59; result[37]:=$5A; result[38]:=$61; result[39]:=$62; result[40]:=$63; result[41]:=$64; result[42]:=$65; result[43]:=$66; result[44]:=$67; result[45]:=$68; result[46]:=$69; result[47]:=$6A; result[48]:=$6B; result[49]:=$6C; result[50]:=$6D; result[51]:=$6E; result[52]:=$6F; result[53]:=$70; result[54]:=$71; result[55]:=$72; result[56]:=$73; result[57]:=$74; result[58]:=$75; result[59]:=$76; result[60]:=$77; result[61]:=$78; result[62]:=$79; result[63]:=$7A; result[64]:=$20; result[65]:=$22; result[66]:=$28; result[67]:=$29; result[68]:=$5B; result[69]:=$5D; result[70]:=$27; result[71]:=$09; result[72]:=$0A; result[73]:=$0D; {$IFEND} end; var // Initlialized in initialization section, cannot be const because of the // TBytes requirement ESCAPE_CodesL: TBytes; //array[0..6] of Byte = ($61, $62, $74, $6E, $76, $66, $72); ESCAPE_CodesU: TBytes; //array[0..6] of Byte = ($41, $42, $54, $4E, $56, $46, $52); class function TFormat_ESCAPE.CharTableBinary: TBytes; begin Result := TFormat_HEX.CharTableBinary; end; class procedure TFormat_ESCAPE.DoEncode(const Source; var Dest: TBytes; Size: Integer); var T: TBytes; S: PByte; D: PByte; // 1) to make pointer arithmetic work 2) P/TByteArray is limited to 32768 bytes i: Integer; begin SetLength(Dest, 0); if Size <= 0 then Exit; SetLength(Dest, Size + 8); T := CharTableBinary; S := @Source; D := @Dest[0]; i := Size; while Size > 0 do begin if i <= 0 then begin i := D - PByte(Dest); SetLength(Dest, i + Size + 8); D := PByte(Dest) + i; i := Size; end; if (S^ < 32) or (S^ > $7F) then begin if (S^ >= 7) and (S^ <= 13) then begin D^ := $5C; // \ char Inc(D); D^ := ESCAPE_CodesL[S^ - 7]; Inc(D); Dec(i, 2); end else begin D^ := $5C; // \ char Inc(D); D^ := $78; // x Inc(D); D^ := T[S^ shr 4]; Inc(D); D^ := T[S^ and $F]; Inc(D); Dec(i, 4); end end else begin // S^is \ char? if S^ = $5C then begin D^ := $5C; // \ char Inc(D); D^ := $5C; // \ char Inc(D); Dec(i, 2); end else // S^ is " char? if S^ = $22 then begin D^ := $5C; // \ char Inc(D); D^ := $22; // " char Inc(D); Dec(i, 2); end else begin D^ := S^; Inc(D); Dec(i); end; end; Dec(Size); Inc(S); end; SetLength(Dest, PByte(D) - PByte(Dest)); end; class function TFormat_ESCAPE.DoIsValid(const Data; Size: Integer): Boolean; var T: TBytes; S: PByte; begin Result := False; T := CharTableBinary; S := @Data; while Size > 0 do begin if (S^ > $7F) or (S^ < 32) then Exit; // start of an escape sequence if S^ = $5C then begin Dec(Size); Inc(S); // \ at the end if Size <= 0 then Exit; // X for hex notation if UpCaseBinary(S^) = $58 then begin Inc(S); Dec(Size); // incomplete hex notation follows? if (Size < 2) or (TableFindBinary(UpCaseBinary(S^), T, 16) < 0) then Exit; Inc(S); Dec(Size); if (TableFindBinary(UpCaseBinary(S^), T, 16) < 0) then Exit; Inc(S); Dec(Size); end else begin // \ with invalid following char? if TableFindBinary(UpCaseBinary(S^), TBytes(ESCAPE_CodesU), 7) < 0 then Exit; Dec(Size); Inc(S); end; end else begin Dec(Size); Inc(S); end; end; Result := True; end; class procedure TFormat_ESCAPE.DoDecode(const Source; var Dest: TBytes; Size: Integer); var T: TBytes; S: PByte; // 1) to make pointer arithmetic work 2) P/TByteArray is limited to 32768 bytes D: PByte; L: PByte; // 1) to make pointer arithmetic work 2) P/TByteArray is limited to 32768 bytes i: Integer; begin if Size <= 0 then Exit; SetLength(Dest, Size); T := CharTableBinary; S := @Source; D := @Dest[0]; L := S + Size; while S < L do begin // S^ is \ char? if S^ = $5C then begin Inc(S); if S > L then Break; // S^ is X char? if UpCaseBinary(S^) = $58 then begin if S + 2 > L then raise EDECFormatException.CreateResFmt(@sInvalidStringFormat, [self.GetShortClassName]); Inc(S); i := TableFindBinary(UpCaseBinary(S^), T, 16); if i < 0 then raise EDECFormatException.CreateResFmt(@sInvalidStringFormat, [self.GetShortClassName]); D^ := i shl 4; Inc(S); i := TableFindBinary(UpCaseBinary(S^), T, 16); if i < 0 then raise EDECFormatException.CreateResFmt(@sInvalidStringFormat, [self.GetShortClassName]); D^ := D^ or i; end else begin i := TableFindBinary(UpCaseBinary(S^), TBytes(ESCAPE_CodesU), 7); if i >= 0 then D^ := i + 7 else D^ := S^; end; end else D^ := S^; Inc(D); Inc(S); end; SetLength(Dest, PByte(D) - PByte(Dest)); end; { TFormat_BigEndian16 } class procedure TFormat_BigEndian16.DoDecode(const Source; var Dest: TBytes; Size: Integer); begin DoSawp(Source, Dest, Size); end; class procedure TFormat_BigEndian16.DoEncode(const Source; var Dest: TBytes; Size: Integer); begin DoSawp(Source, Dest, Size); end; class function TFormat_BigEndian16.DoIsValid(const Data; Size: Integer): Boolean; begin // swapping bytes in 16 bit mode requires even number of bytes result := not Odd(Size); end; class procedure TFormat_BigEndian16.DoSawp(const Source; var Dest: TBytes; Size: Integer); var i : Integer; begin if (Size < 0) or Odd(Size) then Exit; SetLength(Dest, Size); if (Size > 0) then begin Move(Source, Dest[0], Size); i := 0; while (i < length(Dest)) do begin DECUtil.SwapBytes(Dest[i], 2); inc(i, 2); end; end; end; { TFormat_BigEndian32 } class procedure TFormat_BigEndian32.DoDecode(const Source; var Dest: TBytes; Size: Integer); begin DoSawp(Source, Dest, Size); end; class procedure TFormat_BigEndian32.DoEncode(const Source; var Dest: TBytes; Size: Integer); begin DoSawp(Source, Dest, Size); end; class function TFormat_BigEndian32.DoIsValid(const Data; Size: Integer): Boolean; begin result := (Size mod 4) = 0; end; class procedure TFormat_BigEndian32.DoSawp(const Source; var Dest: TBytes; Size: Integer); var i : Integer; SwapRes : UInt32; begin if (Size < 0) or ((Size mod 4) <> 0) then Exit; SetLength(Dest, Size); if (Size > 0) then begin Move(Source, Dest[0], Size); i := 0; while (i < length(Dest)) do begin Move(Dest[i], SwapRes, 4); SwapRes := DECUtil.SwapUInt32(SwapRes); Move(SwapRes, Dest[i], 4); inc(i, 4); end; end; end; { TFormat_BigEndian64 } class procedure TFormat_BigEndian64.DoDecode(const Source; var Dest: TBytes; Size: Integer); begin DoSawp(Source, Dest, Size); end; class procedure TFormat_BigEndian64.DoEncode(const Source; var Dest: TBytes; Size: Integer); begin DoSawp(Source, Dest, Size); end; class function TFormat_BigEndian64.DoIsValid(const Data; Size: Integer): Boolean; begin result := (Size mod 8) = 0; end; class procedure TFormat_BigEndian64.DoSawp(const Source; var Dest: TBytes; Size: Integer); var i : Integer; SwapRes : Int64; begin if (Size < 0) or ((Size mod 8) <> 0) then Exit; SetLength(Dest, Size); if (Size > 0) then begin Move(Source, Dest[0], Size); i := 0; while (i < length(Dest)) do begin Move(Dest[i], SwapRes, 8); SwapRes := DECUtil.SwapInt64(SwapRes); Move(SwapRes, Dest[i], 8); inc(i, 8); end; end; end; initialization SetLength(ESCAPE_CodesL, 7); ESCAPE_CodesL[0] := $61; ESCAPE_CodesL[1] := $62; ESCAPE_CodesL[2] := $74; ESCAPE_CodesL[3] := $6E; ESCAPE_CodesL[4] := $76; ESCAPE_CodesL[5] := $66; ESCAPE_CodesL[6] := $72; SetLength(ESCAPE_CodesU, 7); ESCAPE_CodesU[0] := $41; ESCAPE_CodesU[1] := $42; ESCAPE_CodesU[2] := $54; ESCAPE_CodesU[3] := $4E; ESCAPE_CodesU[4] := $56; ESCAPE_CodesU[5] := $46; ESCAPE_CodesU[6] := $52; {$IFNDEF ManualRegisterClasses} TFormat_HEX.RegisterClass(TDECFormat.ClassList); TFormat_HEXL.RegisterClass(TDECFormat.ClassList); TFormat_DECMIME32.RegisterClass(TDECFormat.ClassList); TFormat_Base64.RegisterClass(TDECFormat.ClassList); TFormat_Radix64.RegisterClass(TDECFormat.ClassList); TFormat_UU.RegisterClass(TDECFormat.ClassList); TFormat_XX.RegisterClass(TDECFormat.ClassList); TFormat_ESCAPE.RegisterClass(TDECFormat.ClassList); TFormat_BigEndian16.RegisterClass(TDECFormat.ClassList); TFormat_BigEndian32.RegisterClass(TDECFormat.ClassList); TFormat_BigEndian64.RegisterClass(TDECFormat.ClassList); {$ENDIF} // Init the number of chars per line as per RFC 4880 to 76 chars TFormat_Radix64.FCharsPerLine := 76; finalization end.