{==============================================================================|
| Project : Ararat Synapse                                       | 002.002.003 |
|==============================================================================|
| Content: Coding and decoding support                                         |
|==============================================================================|
| Copyright (c)1999-2013, Lukas Gebauer                                        |
| All rights reserved                                                         |
|                                                                              |
| Redistribution and use in source and binary forms, with or without           |
| modification, are permitted provided that the following conditions are met:  |
|                                                                              |
| Redistributions of source code must retain the above copyright notice, this  |
| list of conditions and the following disclaimer.                             |
|                                                                              |
| Redistributions in binary form must reproduce the above copyright notice,    |
| this list of conditions and the following disclaimer in the documentation    |
| and/or other materials provided with the distribution.                       |
|                                                                              |
| Neither the name of Lukas Gebauer nor the names of its contributors may      |
| be used to endorse or promote products derived from this software without    |
| specific prior written permission.                                           |
|                                                                              |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"  |
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE    |
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE   |
| ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR  |
| ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL       |
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR   |
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER   |
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT           |
| LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY    |
| OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH  |
| DAMAGE.                                                                      |
|==============================================================================|
| The Initial Developer of the Original Code is Lukas Gebauer (Czech Republic).|
| Portions created by Lukas Gebauer are Copyright (c)2000-2013.                |
| All rights reserved                                                         |
|==============================================================================|
| Contributor(s):                                                              |
|==============================================================================|
| History: see HISTORY.HTM from distribution package                           |
|          (Found at URL: http://www.ararat.cz/synapse/)                       |
|==============================================================================}

{:@abstract(Various encoding and decoding support)}
{$IFDEF FPC}
  {$MODE DELPHI}
{$ENDIF}
{$Q-}
{$R-}
{$H+}
{$TYPEDADDRESS OFF}

{$IFDEF CIL}
  {$DEFINE SYNACODE_NATIVE}
{$ENDIF}
{$IFDEF FPC_BIG_ENDIAN}
  {$DEFINE SYNACODE_NATIVE}
{$ENDIF}

{$IFDEF UNICODE}
  {$WARN IMPLICIT_STRING_CAST OFF}
  {$WARN IMPLICIT_STRING_CAST_LOSS OFF}
  {$WARN SUSPICIOUS_TYPECAST OFF}
{$ENDIF}

unit synacode;

interface

uses
  SysUtils;

type
  TSpecials = set of AnsiChar;

const

  SpecialChar: TSpecials =
  ['=', '(', ')', '[', ']', '<', '>', ':', ';', ',', '@', '/', '?', '\',
    '"', '_'];
  NonAsciiChar: TSpecials =
  [#0..#31, #127..#255];
  URLFullSpecialChar: TSpecials =
  [';', '/', '?', ':', '@', '=', '&', '#', '+'];
  URLSpecialChar: TSpecials =
  [#$00..#$20, '<', '>', '"', '%', '{', '}', '|', '\', '^', '[', ']', '`', #$7F..#$FF];
  TableBase64 =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  TableBase64mod =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+,=';
  TableUU =
    '`!"#$%&''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_';
  TableXX =
    '+-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  ReTablebase64 =
    #$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$3E +#$40
    +#$40 +#$40 +#$3F +#$34 +#$35 +#$36 +#$37 +#$38 +#$39 +#$3A +#$3B +#$3C
    +#$3D +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$00 +#$01 +#$02 +#$03
    +#$04 +#$05 +#$06 +#$07 +#$08 +#$09 +#$0A +#$0B +#$0C +#$0D +#$0E +#$0F
    +#$10 +#$11 +#$12 +#$13 +#$14 +#$15 +#$16 +#$17 +#$18 +#$19 +#$40 +#$40
    +#$40 +#$40 +#$40 +#$40 +#$1A +#$1B +#$1C +#$1D +#$1E +#$1F +#$20 +#$21
    +#$22 +#$23 +#$24 +#$25 +#$26 +#$27 +#$28 +#$29 +#$2A +#$2B +#$2C +#$2D
    +#$2E +#$2F +#$30 +#$31 +#$32 +#$33 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40;
  ReTableUU =
    #$01 +#$02 +#$03 +#$04 +#$05 +#$06 +#$07 +#$08 +#$09 +#$0A +#$0B +#$0C
    +#$0D +#$0E +#$0F +#$10 +#$11 +#$12 +#$13 +#$14 +#$15 +#$16 +#$17 +#$18
    +#$19 +#$1A +#$1B +#$1C +#$1D +#$1E +#$1F +#$20 +#$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 +#$00 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40
    +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40
    +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40;
  ReTableXX =
    #$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$00 +#$40
    +#$01 +#$40 +#$40 +#$02 +#$03 +#$04 +#$05 +#$06 +#$07 +#$08 +#$09 +#$0A
    +#$0B +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$40 +#$0C +#$0D +#$0E +#$0F
    +#$10 +#$11 +#$12 +#$13 +#$14 +#$15 +#$16 +#$17 +#$18 +#$19 +#$1A +#$1B
    +#$1C +#$1D +#$1E +#$1F +#$20 +#$21 +#$22 +#$23 +#$24 +#$25 +#$40 +#$40
    +#$40 +#$40 +#$40 +#$40 +#$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 +#$40 +#$40 +#$40 +#$40 +#$40;

{:Decodes triplet encoding with a given character delimiter. It is used for
 decoding quoted-printable or URL encoding.}
function DecodeTriplet(const Value: AnsiString; Delimiter: AnsiChar): AnsiString;

{:Decodes a string from quoted printable form. (also decodes triplet sequences
 like '=7F')}
function DecodeQuotedPrintable(const Value: AnsiString): AnsiString;

{:Decodes a string of URL encoding. (also decodes triplet sequences like '%7F')}
function DecodeURL(const Value: AnsiString): AnsiString;

{:Performs triplet encoding with a given character delimiter. Used for encoding
 quoted-printable or URL encoding.}
function EncodeTriplet(const Value: AnsiString; Delimiter: AnsiChar;
  Specials: TSpecials): AnsiString;

{:Encodes a string to triplet quoted printable form. All @link(NonAsciiChar)
 are encoded.}
function EncodeQuotedPrintable(const Value: AnsiString): AnsiString;

{:Encodes a string to triplet quoted printable form. All @link(NonAsciiChar) and
 @link(SpecialChar) are encoded.}
function EncodeSafeQuotedPrintable(const Value: AnsiString): AnsiString;

{:Encodes a string to URL format. Used for encoding data from a form field in
 HTTP, etc. (Encodes all critical characters including characters used as URL
 delimiters ('/',':', etc.)}
function EncodeURLElement(const Value: AnsiString): AnsiString;

{:Encodes a string to URL format. Used to encode critical characters in all
 URLs.}
function EncodeURL(const Value: AnsiString): AnsiString;

{:Decode 4to3 encoding with given table. If some element is not found in table,
 first item from table is used. This is good for buggy coded items by Microsoft
 Outlook. This software sometimes using wrong table for UUcode, where is used
 ' ' instead '`'.}
function Decode4to3(const Value, Table: AnsiString): AnsiString;

{:Decode 4to3 encoding with given REVERSE table. Using this function with
reverse table is much faster then @link(Decode4to3). This function is used
internally for Base64, UU or XX decoding.}
function Decode4to3Ex(const Value, Table: AnsiString): AnsiString;

{:Encode by system 3to4 (used by Base64, UU coding, etc) by given table.}
function Encode3to4(const Value, Table: AnsiString): AnsiString;

{:Decode string from base64 format.}
function DecodeBase64(const Value: AnsiString): AnsiString;

{:Encodes a string to base64 format.}
function EncodeBase64(const Value: AnsiString): AnsiString;

{:Decode string from modified base64 format. (used in IMAP, for example.)}
function DecodeBase64mod(const Value: AnsiString): AnsiString;

{:Encodes a string to  modified base64 format. (used in IMAP, for example.)}
function EncodeBase64mod(const Value: AnsiString): AnsiString;

{:Decodes a string from UUcode format.}
function DecodeUU(const Value: AnsiString): AnsiString;

{:encode UUcode. it encode only datas, you must also add header and footer for
 proper encode.}
function EncodeUU(const Value: AnsiString): AnsiString;

{:Decodes a string from XXcode format.}
function DecodeXX(const Value: AnsiString): AnsiString;

{:decode line with Yenc code. This code is sometimes used in newsgroups.}
function DecodeYEnc(const Value: AnsiString): AnsiString;

{:Returns a new CRC32 value after adding a new byte of data.}
function UpdateCrc32(Value: Byte; Crc32: Integer): Integer;

{:return CRC32 from a value string.}
function Crc32(const Value: AnsiString): Integer;

{:Returns a new CRC16 value after adding a new byte of data.}
function UpdateCrc16(Value: Byte; Crc16: Word): Word;

{:return CRC16 from a value string.}
function Crc16(const Value: AnsiString): Word;

{:Returns a binary string with a RSA-MD5 hashing of "Value" string.}
function MD5(const Value: AnsiString): AnsiString;

{:Returns a binary string with HMAC-MD5 hash.}
function HMAC_MD5(Text, Key: AnsiString): AnsiString;

{:Returns a binary string with a RSA-MD5 hashing of string what is constructed
 by repeating "value" until length is "Len".}
function MD5LongHash(const Value: AnsiString; Len: integer): AnsiString;

{:Returns a binary string with a SHA-1 hashing of "Value" string.}
function SHA1(const Value: AnsiString): AnsiString;

{:Returns a binary string with HMAC-SHA1 hash.}
function HMAC_SHA1(Text, Key: AnsiString): AnsiString;

{:Returns a binary string with a SHA-1 hashing of string what is constructed
 by repeating "value" until length is "Len".}
function SHA1LongHash(const Value: AnsiString; Len: integer): AnsiString;

{:Returns a binary string with a RSA-MD4 hashing of "Value" string.}
function MD4(const Value: AnsiString): AnsiString;

implementation

const

  Crc32Tab: array[0..255] of Integer = (
    Integer($00000000), Integer($77073096), Integer($EE0E612C), Integer($990951BA),
    Integer($076DC419), Integer($706AF48F), Integer($E963A535), Integer($9E6495A3),
    Integer($0EDB8832), Integer($79DCB8A4), Integer($E0D5E91E), Integer($97D2D988),
    Integer($09B64C2B), Integer($7EB17CBD), Integer($E7B82D07), Integer($90BF1D91),
    Integer($1DB71064), Integer($6AB020F2), Integer($F3B97148), Integer($84BE41DE),
    Integer($1ADAD47D), Integer($6DDDE4EB), Integer($F4D4B551), Integer($83D385C7),
    Integer($136C9856), Integer($646BA8C0), Integer($FD62F97A), Integer($8A65C9EC),
    Integer($14015C4F), Integer($63066CD9), Integer($FA0F3D63), Integer($8D080DF5),
    Integer($3B6E20C8), Integer($4C69105E), Integer($D56041E4), Integer($A2677172),
    Integer($3C03E4D1), Integer($4B04D447), Integer($D20D85FD), Integer($A50AB56B),
    Integer($35B5A8FA), Integer($42B2986C), Integer($DBBBC9D6), Integer($ACBCF940),
    Integer($32D86CE3), Integer($45DF5C75), Integer($DCD60DCF), Integer($ABD13D59),
    Integer($26D930AC), Integer($51DE003A), Integer($C8D75180), Integer($BFD06116),
    Integer($21B4F4B5), Integer($56B3C423), Integer($CFBA9599), Integer($B8BDA50F),
    Integer($2802B89E), Integer($5F058808), Integer($C60CD9B2), Integer($B10BE924),
    Integer($2F6F7C87), Integer($58684C11), Integer($C1611DAB), Integer($B6662D3D),
    Integer($76DC4190), Integer($01DB7106), Integer($98D220BC), Integer($EFD5102A),
    Integer($71B18589), Integer($06B6B51F), Integer($9FBFE4A5), Integer($E8B8D433),
    Integer($7807C9A2), Integer($0F00F934), Integer($9609A88E), Integer($E10E9818),
    Integer($7F6A0DBB), Integer($086D3D2D), Integer($91646C97), Integer($E6635C01),
    Integer($6B6B51F4), Integer($1C6C6162), Integer($856530D8), Integer($F262004E),
    Integer($6C0695ED), Integer($1B01A57B), Integer($8208F4C1), Integer($F50FC457),
    Integer($65B0D9C6), Integer($12B7E950), Integer($8BBEB8EA), Integer($FCB9887C),
    Integer($62DD1DDF), Integer($15DA2D49), Integer($8CD37CF3), Integer($FBD44C65),
    Integer($4DB26158), Integer($3AB551CE), Integer($A3BC0074), Integer($D4BB30E2),
    Integer($4ADFA541), Integer($3DD895D7), Integer($A4D1C46D), Integer($D3D6F4FB),
    Integer($4369E96A), Integer($346ED9FC), Integer($AD678846), Integer($DA60B8D0),
    Integer($44042D73), Integer($33031DE5), Integer($AA0A4C5F), Integer($DD0D7CC9),
    Integer($5005713C), Integer($270241AA), Integer($BE0B1010), Integer($C90C2086),
    Integer($5768B525), Integer($206F85B3), Integer($B966D409), Integer($CE61E49F),
    Integer($5EDEF90E), Integer($29D9C998), Integer($B0D09822), Integer($C7D7A8B4),
    Integer($59B33D17), Integer($2EB40D81), Integer($B7BD5C3B), Integer($C0BA6CAD),
    Integer($EDB88320), Integer($9ABFB3B6), Integer($03B6E20C), Integer($74B1D29A),
    Integer($EAD54739), Integer($9DD277AF), Integer($04DB2615), Integer($73DC1683),
    Integer($E3630B12), Integer($94643B84), Integer($0D6D6A3E), Integer($7A6A5AA8),
    Integer($E40ECF0B), Integer($9309FF9D), Integer($0A00AE27), Integer($7D079EB1),
    Integer($F00F9344), Integer($8708A3D2), Integer($1E01F268), Integer($6906C2FE),
    Integer($F762575D), Integer($806567CB), Integer($196C3671), Integer($6E6B06E7),
    Integer($FED41B76), Integer($89D32BE0), Integer($10DA7A5A), Integer($67DD4ACC),
    Integer($F9B9DF6F), Integer($8EBEEFF9), Integer($17B7BE43), Integer($60B08ED5),
    Integer($D6D6A3E8), Integer($A1D1937E), Integer($38D8C2C4), Integer($4FDFF252),
    Integer($D1BB67F1), Integer($A6BC5767), Integer($3FB506DD), Integer($48B2364B),
    Integer($D80D2BDA), Integer($AF0A1B4C), Integer($36034AF6), Integer($41047A60),
    Integer($DF60EFC3), Integer($A867DF55), Integer($316E8EEF), Integer($4669BE79),
    Integer($CB61B38C), Integer($BC66831A), Integer($256FD2A0), Integer($5268E236),
    Integer($CC0C7795), Integer($BB0B4703), Integer($220216B9), Integer($5505262F),
    Integer($C5BA3BBE), Integer($B2BD0B28), Integer($2BB45A92), Integer($5CB36A04),
    Integer($C2D7FFA7), Integer($B5D0CF31), Integer($2CD99E8B), Integer($5BDEAE1D),
    Integer($9B64C2B0), Integer($EC63F226), Integer($756AA39C), Integer($026D930A),
    Integer($9C0906A9), Integer($EB0E363F), Integer($72076785), Integer($05005713),
    Integer($95BF4A82), Integer($E2B87A14), Integer($7BB12BAE), Integer($0CB61B38),
    Integer($92D28E9B), Integer($E5D5BE0D), Integer($7CDCEFB7), Integer($0BDBDF21),
    Integer($86D3D2D4), Integer($F1D4E242), Integer($68DDB3F8), Integer($1FDA836E),
    Integer($81BE16CD), Integer($F6B9265B), Integer($6FB077E1), Integer($18B74777),
    Integer($88085AE6), Integer($FF0F6A70), Integer($66063BCA), Integer($11010B5C),
    Integer($8F659EFF), Integer($F862AE69), Integer($616BFFD3), Integer($166CCF45),
    Integer($A00AE278), Integer($D70DD2EE), Integer($4E048354), Integer($3903B3C2),
    Integer($A7672661), Integer($D06016F7), Integer($4969474D), Integer($3E6E77DB),
    Integer($AED16A4A), Integer($D9D65ADC), Integer($40DF0B66), Integer($37D83BF0),
    Integer($A9BCAE53), Integer($DEBB9EC5), Integer($47B2CF7F), Integer($30B5FFE9),
    Integer($BDBDF21C), Integer($CABAC28A), Integer($53B39330), Integer($24B4A3A6),
    Integer($BAD03605), Integer($CDD70693), Integer($54DE5729), Integer($23D967BF),
    Integer($B3667A2E), Integer($C4614AB8), Integer($5D681B02), Integer($2A6F2B94),
    Integer($B40BBE37), Integer($C30C8EA1), Integer($5A05DF1B), Integer($2D02EF8D)
    );

  Crc16Tab: array[0..255] of Word = (
    $0000, $1189, $2312, $329B, $4624, $57AD, $6536, $74BF,
    $8C48, $9DC1, $AF5A, $BED3, $CA6C, $DBE5, $E97E, $F8F7,
    $1081, $0108, $3393, $221A, $56A5, $472C, $75B7, $643E,
    $9CC9, $8D40, $BFDB, $AE52, $DAED, $CB64, $F9FF, $E876,
    $2102, $308B, $0210, $1399, $6726, $76AF, $4434, $55BD,
    $AD4A, $BCC3, $8E58, $9FD1, $EB6E, $FAE7, $C87C, $D9F5,
    $3183, $200A, $1291, $0318, $77A7, $662E, $54B5, $453C,
    $BDCB, $AC42, $9ED9, $8F50, $FBEF, $EA66, $D8FD, $C974,
    $4204, $538D, $6116, $709F, $0420, $15A9, $2732, $36BB,
    $CE4C, $DFC5, $ED5E, $FCD7, $8868, $99E1, $AB7A, $BAF3,
    $5285, $430C, $7197, $601E, $14A1, $0528, $37B3, $263A,
    $DECD, $CF44, $FDDF, $EC56, $98E9, $8960, $BBFB, $AA72,
    $6306, $728F, $4014, $519D, $2522, $34AB, $0630, $17B9,
    $EF4E, $FEC7, $CC5C, $DDD5, $A96A, $B8E3, $8A78, $9BF1,
    $7387, $620E, $5095, $411C, $35A3, $242A, $16B1, $0738,
    $FFCF, $EE46, $DCDD, $CD54, $B9EB, $A862, $9AF9, $8B70,
    $8408, $9581, $A71A, $B693, $C22C, $D3A5, $E13E, $F0B7,
    $0840, $19C9, $2B52, $3ADB, $4E64, $5FED, $6D76, $7CFF,
    $9489, $8500, $B79B, $A612, $D2AD, $C324, $F1BF, $E036,
    $18C1, $0948, $3BD3, $2A5A, $5EE5, $4F6C, $7DF7, $6C7E,
    $A50A, $B483, $8618, $9791, $E32E, $F2A7, $C03C, $D1B5,
    $2942, $38CB, $0A50, $1BD9, $6F66, $7EEF, $4C74, $5DFD,
    $B58B, $A402, $9699, $8710, $F3AF, $E226, $D0BD, $C134,
    $39C3, $284A, $1AD1, $0B58, $7FE7, $6E6E, $5CF5, $4D7C,
    $C60C, $D785, $E51E, $F497, $8028, $91A1, $A33A, $B2B3,
    $4A44, $5BCD, $6956, $78DF, $0C60, $1DE9, $2F72, $3EFB,
    $D68D, $C704, $F59F, $E416, $90A9, $8120, $B3BB, $A232,
    $5AC5, $4B4C, $79D7, $685E, $1CE1, $0D68, $3FF3, $2E7A,
    $E70E, $F687, $C41C, $D595, $A12A, $B0A3, $8238, $93B1,
    $6B46, $7ACF, $4854, $59DD, $2D62, $3CEB, $0E70, $1FF9,
    $F78F, $E606, $D49D, $C514, $B1AB, $A022, $92B9, $8330,
    $7BC7, $6A4E, $58D5, $495C, $3DE3, $2C6A, $1EF1, $0F78
    );

procedure ArrByteToLong(var ArByte: Array of byte; var ArLong: Array of Integer);
{$IFDEF SYNACODE_NATIVE}
var
  n: integer;
{$ENDIF}
begin
  if (High(ArByte) + 1) > ((High(ArLong) + 1) * 4) then
    Exit;
  {$IFDEF SYNACODE_NATIVE}
  for n := 0 to ((high(ArByte) + 1) div 4) - 1 do
    ArLong[n] := ArByte[n * 4 + 0]
      + (ArByte[n * 4 + 1] shl 8)
      + (ArByte[n * 4 + 2] shl 16)
      + (ArByte[n * 4 + 3] shl 24);
  {$ELSE}
  Move(ArByte[0], ArLong[0], High(ArByte) + 1);
  {$ENDIF}
end;

procedure ArrLongToByte(var ArLong: Array of Integer; var ArByte: Array of byte);
{$IFDEF SYNACODE_NATIVE}
var
  n: integer;
{$ENDIF}
begin
  if (High(ArByte) + 1) < ((High(ArLong) + 1) * 4) then
    Exit;
  {$IFDEF SYNACODE_NATIVE}
  for n := 0 to high(ArLong) do
  begin
    ArByte[n * 4 + 0] := ArLong[n] and $000000FF;
    ArByte[n * 4 + 1] := (ArLong[n] shr 8) and $000000FF;
    ArByte[n * 4 + 2] := (ArLong[n] shr 16) and $000000FF;
    ArByte[n * 4 + 3] := (ArLong[n] shr 24) and $000000FF;
  end;
  {$ELSE}
  Move(ArLong[0], ArByte[0], High(ArByte) + 1);
  {$ENDIF}
end;

type
  TMDCtx = record
    State: array[0..3] of Integer;
    Count: array[0..1] of Integer;
    BufAnsiChar: array[0..63] of Byte;
    BufLong: array[0..15] of Integer;
  end;
  TSHA1Ctx= record
    Hi, Lo: integer;
    Buffer: array[0..63] of byte;
    Index: integer;
    Hash: array[0..4] of Integer;
    HashByte: array[0..19] of byte;
  end;

  TMDTransform = procedure(var Buf: array of LongInt; const Data: array of LongInt);

{==============================================================================}

function DecodeTriplet(const Value: AnsiString; Delimiter: AnsiChar): AnsiString;
var
  x, l, lv: Integer;
  c: AnsiChar;
  b: Byte;
  bad: Boolean;
begin
  lv := Length(Value);
  SetLength(Result, lv);
  x := 1;
  l := 1;
  while x <= lv do
  begin
    c := Value[x];
    Inc(x);
    if c <> Delimiter then
    begin
      Result[l] := c;
      Inc(l);
    end
    else
      if x < lv then
      begin
        Case Value[x] Of
          #13:
            if (Value[x + 1] = #10) then
              Inc(x, 2)
            else
              Inc(x);
          #10:
            if (Value[x + 1] = #13) then
              Inc(x, 2)
            else
              Inc(x);
        else
          begin
            bad := False;
            Case Value[x] Of
              '0'..'9': b := (Byte(Value[x]) - 48) Shl 4;
              'a'..'f', 'A'..'F': b := ((Byte(Value[x]) And 7) + 9) shl 4;
            else
              begin
                b := 0;
                bad := True;
              end;
            end;
            Case Value[x + 1] Of
              '0'..'9': b := b Or (Byte(Value[x + 1]) - 48);
              'a'..'f', 'A'..'F': b := b Or ((Byte(Value[x + 1]) And 7) + 9);
            else
              bad := True;
            end;
            if bad then
            begin
              Result[l] := c;
              Inc(l);
            end
            else
            begin
              Inc(x, 2);
              Result[l] := AnsiChar(b);
              Inc(l);
            end;
          end;
        end;
      end
      else
        break;
  end;
  Dec(l);
  SetLength(Result, l);
end;

{==============================================================================}

function DecodeQuotedPrintable(const Value: AnsiString): AnsiString;
begin
  Result := DecodeTriplet(Value, '=');
end;

{==============================================================================}

function DecodeURL(const Value: AnsiString): AnsiString;
begin
  Result := DecodeTriplet(Value, '%');
end;

{==============================================================================}

function EncodeTriplet(const Value: AnsiString; Delimiter: AnsiChar;
  Specials: TSpecials): AnsiString;
var
  n, l: Integer;
  s: AnsiString;
  c: AnsiChar;
begin
  SetLength(Result, Length(Value) * 3);
  l := 1;
  for n := 1 to Length(Value) do
  begin
    c := Value[n];
    if c in Specials then
    begin
      Result[l] := Delimiter;
      Inc(l);
      s := IntToHex(Ord(c), 2);
      Result[l] := s[1];
      Inc(l);
      Result[l] := s[2];
      Inc(l);
    end
    else
    begin
      Result[l] := c;
      Inc(l);
    end;
  end;
  Dec(l);
  SetLength(Result, l);
end;

{==============================================================================}

function EncodeQuotedPrintable(const Value: AnsiString): AnsiString;
begin
  Result := EncodeTriplet(Value, '=',  ['='] + NonAsciiChar);
end;

{==============================================================================}

function EncodeSafeQuotedPrintable(const Value: AnsiString): AnsiString;
begin
  Result := EncodeTriplet(Value, '=', SpecialChar + NonAsciiChar);
end;

{==============================================================================}

function EncodeURLElement(const Value: AnsiString): AnsiString;
begin
  Result := EncodeTriplet(Value, '%', URLSpecialChar + URLFullSpecialChar);
end;

{==============================================================================}

function EncodeURL(const Value: AnsiString): AnsiString;
begin
  Result := EncodeTriplet(Value, '%', URLSpecialChar);
end;

{==============================================================================}

function Decode4to3(const Value, Table: AnsiString): AnsiString;
var
  x, y, n, l: Integer;
  d: array[0..3] of Byte;
begin
  SetLength(Result, Length(Value));
  x := 1;
  l := 1;
  while x <= Length(Value) do
  begin
    for n := 0 to 3 do
    begin
      if x > Length(Value) then
        d[n] := 64
      else
      begin
        y := Pos(Value[x], Table);
        if y < 1 then
          y := 1;
        d[n] := y - 1;
      end;
      Inc(x);
    end;
    Result[l] := AnsiChar((D[0] and $3F) shl 2 + (D[1] and $30) shr 4);
    Inc(l);
    if d[2] <> 64 then
    begin
      Result[l] := AnsiChar((D[1] and $0F) shl 4 + (D[2] and $3C) shr 2);
      Inc(l);
      if d[3] <> 64 then
      begin
        Result[l] := AnsiChar((D[2] and $03) shl 6 + (D[3] and $3F));
        Inc(l);
      end;
    end;
  end;
  Dec(l);
  SetLength(Result, l);
end;

{==============================================================================}
function Decode4to3Ex(const Value, Table: AnsiString): AnsiString;
var
  x, y, lv: Integer;
  d: integer;
  dl: integer;
  c: byte;
  p: integer;
begin
  lv := Length(Value);
  SetLength(Result, lv);
  x := 1;
  dl := 4;
  d := 0;
  p := 1;
  while x <= lv do
  begin
    y := Ord(Value[x]);
    if y in [33..127] then
      c := Ord(Table[y - 32])
    else
      c := 64;
    Inc(x);
    if c > 63 then
      continue;
    d := (d shl 6) or c;
    dec(dl);
    if dl <> 0 then
      continue;
    Result[p] := AnsiChar((d shr 16) and $ff);
    inc(p);
    Result[p] := AnsiChar((d shr 8) and $ff);
    inc(p);
    Result[p] := AnsiChar(d and $ff);
    inc(p);
    d := 0;
    dl := 4;
  end;
  case dl of
    1:
      begin
        d := d shr 2;
        Result[p] := AnsiChar((d shr 8) and $ff);
        inc(p);
        Result[p] := AnsiChar(d and $ff);
        inc(p);
      end;
    2:
      begin
        d := d shr 4;
        Result[p] := AnsiChar(d and $ff);
        inc(p);
      end;
  end;
  SetLength(Result, p - 1);
end;

{==============================================================================}

function Encode3to4(const Value, Table: AnsiString): AnsiString;
var
  c: Byte;
  n, l: Integer;
  Count: Integer;
  DOut: array[0..3] of Byte;
begin
  setlength(Result, ((Length(Value) + 2) div 3) * 4);
  l := 1;
  Count := 1;
  while Count <= Length(Value) do
  begin
    c := Ord(Value[Count]);
    Inc(Count);
    DOut[0] := (c and $FC) shr 2;
    DOut[1] := (c and $03) shl 4;
    if Count <= Length(Value) then
    begin
      c := Ord(Value[Count]);
      Inc(Count);
      DOut[1] := DOut[1] + (c and $F0) shr 4;
      DOut[2] := (c and $0F) shl 2;
      if Count <= Length(Value) then
      begin
        c := Ord(Value[Count]);
        Inc(Count);
        DOut[2] := DOut[2] + (c and $C0) shr 6;
        DOut[3] := (c and $3F);
      end
      else
      begin
        DOut[3] := $40;
      end;
    end
    else
    begin
      DOut[2] := $40;
      DOut[3] := $40;
    end;
    for n := 0 to 3 do
    begin
      if (DOut[n] + 1) <= Length(Table) then
      begin
        Result[l] := Table[DOut[n] + 1];
        Inc(l);
      end;
    end;
  end;
  SetLength(Result, l - 1);
end;

{==============================================================================}

function DecodeBase64(const Value: AnsiString): AnsiString;
begin
  Result := Decode4to3Ex(Value, ReTableBase64);
end;

{==============================================================================}

function EncodeBase64(const Value: AnsiString): AnsiString;
begin
  Result := Encode3to4(Value, TableBase64);
end;

{==============================================================================}

function DecodeBase64mod(const Value: AnsiString): AnsiString;
begin
  Result := Decode4to3(Value, TableBase64mod);
end;

{==============================================================================}

function EncodeBase64mod(const Value: AnsiString): AnsiString;
begin
  Result := Encode3to4(Value, TableBase64mod);
end;

{==============================================================================}

function DecodeUU(const Value: AnsiString): AnsiString;
var
  s: AnsiString;
  uut: AnsiString;
  x: Integer;
begin
  Result := '';
  uut := TableUU;
  s := trim(UpperCase(Value));
  if s = '' then Exit;
  if Pos('BEGIN', s) = 1 then
    Exit;
  if Pos('END', s) = 1 then
    Exit;
  if Pos('TABLE', s) = 1 then
    Exit; //ignore Table yet (set custom UUT)
  //begin decoding
  x := Pos(Value[1], uut) - 1;
  case (x mod 3) of
    0: x :=(x div 3)* 4;
    1: x :=((x div 3) * 4) + 2;
    2: x :=((x  div 3) * 4) + 3;
  end;
  //x - lenght UU line
  s := Copy(Value, 2, x);
  if s = '' then
    Exit;
  s := s + StringOfChar(' ', x - length(s));
  Result := Decode4to3(s, uut);
end;

{==============================================================================}

function EncodeUU(const Value: AnsiString): AnsiString;
begin
  Result := '';
  if Length(Value) < Length(TableUU) then
    Result := TableUU[Length(Value) + 1] + Encode3to4(Value, TableUU);
end;

{==============================================================================}

function DecodeXX(const Value: AnsiString): AnsiString;
var
  s: AnsiString;
  x: Integer;
begin
  Result := '';
  s := trim(UpperCase(Value));
  if s = '' then
    Exit;
  if Pos('BEGIN', s) = 1 then
    Exit;
  if Pos('END', s) = 1 then
    Exit;
  //begin decoding
  x := Pos(Value[1], TableXX) - 1;
  case (x mod 3) of
    0: x :=(x div 3)* 4;
    1: x :=((x div 3) * 4) + 2;
    2: x :=((x  div 3) * 4) + 3;
  end;
  //x - lenght XX line
  s := Copy(Value, 2, x);
  if s = '' then
    Exit;
  s := s + StringOfChar(' ', x - length(s));
  Result := Decode4to3(s, TableXX);
end;

{==============================================================================}

function DecodeYEnc(const Value: AnsiString): AnsiString;
var
  C : Byte;
  i: integer;
begin
  Result := '';
  i := 1;
  while i <= Length(Value) do
  begin
    c := Ord(Value[i]);
    Inc(i);
    if c = Ord('=') then
    begin
      c := Ord(Value[i]);
      Inc(i);
      Dec(c, 64);
    end;
    Dec(C, 42);
    Result := Result + AnsiChar(C);
  end;
end;

{==============================================================================}

function UpdateCrc32(Value: Byte; Crc32: Integer): Integer;
begin
  Result := (Crc32 shr 8)
    xor crc32tab[Byte(Value xor (Crc32 and Integer($000000FF)))];
end;

{==============================================================================}

function Crc32(const Value: AnsiString): Integer;
var
  n: Integer;
begin
  Result := Integer($FFFFFFFF);
  for n := 1 to Length(Value) do
    Result := UpdateCrc32(Ord(Value[n]), Result);
  Result := not Result;
end;

{==============================================================================}

function UpdateCrc16(Value: Byte; Crc16: Word): Word;
begin
  Result := ((Crc16 shr 8) and $00FF) xor
    crc16tab[Byte(Crc16 xor (Word(Value)) and $00FF)];
end;

{==============================================================================}

function Crc16(const Value: AnsiString): Word;
var
  n: Integer;
begin
  Result := $FFFF;
  for n := 1 to Length(Value) do
    Result := UpdateCrc16(Ord(Value[n]), Result);
end;

{==============================================================================}

procedure MDInit(var MDContext: TMDCtx);
var
  n: integer;
begin
  MDContext.Count[0] := 0;
  MDContext.Count[1] := 0;
  for n := 0 to high(MDContext.BufAnsiChar) do
    MDContext.BufAnsiChar[n] := 0;
  for n := 0 to high(MDContext.BufLong) do
    MDContext.BufLong[n] := 0;
  MDContext.State[0] := Integer($67452301);
  MDContext.State[1] := Integer($EFCDAB89);
  MDContext.State[2] := Integer($98BADCFE);
  MDContext.State[3] := Integer($10325476);
end;

procedure MD5Transform(var Buf: array of LongInt; const Data: array of LongInt);
var
  A, B, C, D: LongInt;

  procedure Round1(var W: LongInt; X, Y, Z, Data: LongInt; S: Byte);
  begin
    Inc(W, (Z xor (X and (Y xor Z))) + Data);
    W := (W shl S) or (W shr (32 - S));
    Inc(W, X);
  end;

  procedure Round2(var W: LongInt; X, Y, Z, Data: LongInt; S: Byte);
  begin
    Inc(W, (Y xor (Z and (X xor Y))) + Data);
    W := (W shl S) or (W shr (32 - S));
    Inc(W, X);
  end;

  procedure Round3(var W: LongInt; X, Y, Z, Data: LongInt; S: Byte);
  begin
    Inc(W, (X xor Y xor Z) + Data);
    W := (W shl S) or (W shr (32 - S));
    Inc(W, X);
  end;

  procedure Round4(var W: LongInt; X, Y, Z, Data: LongInt; S: Byte);
  begin
    Inc(W, (Y xor (X or not Z)) + Data);
    W := (W shl S) or (W shr (32 - S));
    Inc(W, X);
  end;
begin
  A := Buf[0];
  B := Buf[1];
  C := Buf[2];
  D := Buf[3];

  Round1(A, B, C, D, Data[0] + Longint($D76AA478), 7);
  Round1(D, A, B, C, Data[1] + Longint($E8C7B756), 12);
  Round1(C, D, A, B, Data[2] + Longint($242070DB), 17);
  Round1(B, C, D, A, Data[3] + Longint($C1BDCEEE), 22);
  Round1(A, B, C, D, Data[4] + Longint($F57C0FAF), 7);
  Round1(D, A, B, C, Data[5] + Longint($4787C62A), 12);
  Round1(C, D, A, B, Data[6] + Longint($A8304613), 17);
  Round1(B, C, D, A, Data[7] + Longint($FD469501), 22);
  Round1(A, B, C, D, Data[8] + Longint($698098D8), 7);
  Round1(D, A, B, C, Data[9] + Longint($8B44F7AF), 12);
  Round1(C, D, A, B, Data[10] + Longint($FFFF5BB1), 17);
  Round1(B, C, D, A, Data[11] + Longint($895CD7BE), 22);
  Round1(A, B, C, D, Data[12] + Longint($6B901122), 7);
  Round1(D, A, B, C, Data[13] + Longint($FD987193), 12);
  Round1(C, D, A, B, Data[14] + Longint($A679438E), 17);
  Round1(B, C, D, A, Data[15] + Longint($49B40821), 22);

  Round2(A, B, C, D, Data[1] + Longint($F61E2562), 5);
  Round2(D, A, B, C, Data[6] + Longint($C040B340), 9);
  Round2(C, D, A, B, Data[11] + Longint($265E5A51), 14);
  Round2(B, C, D, A, Data[0] + Longint($E9B6C7AA), 20);
  Round2(A, B, C, D, Data[5] + Longint($D62F105D), 5);
  Round2(D, A, B, C, Data[10] + Longint($02441453), 9);
  Round2(C, D, A, B, Data[15] + Longint($D8A1E681), 14);
  Round2(B, C, D, A, Data[4] + Longint($E7D3FBC8), 20);
  Round2(A, B, C, D, Data[9] + Longint($21E1CDE6), 5);
  Round2(D, A, B, C, Data[14] + Longint($C33707D6), 9);
  Round2(C, D, A, B, Data[3] + Longint($F4D50D87), 14);
  Round2(B, C, D, A, Data[8] + Longint($455A14ED), 20);
  Round2(A, B, C, D, Data[13] + Longint($A9E3E905), 5);
  Round2(D, A, B, C, Data[2] + Longint($FCEFA3F8), 9);
  Round2(C, D, A, B, Data[7] + Longint($676F02D9), 14);
  Round2(B, C, D, A, Data[12] + Longint($8D2A4C8A), 20);

  Round3(A, B, C, D, Data[5] + Longint($FFFA3942), 4);
  Round3(D, A, B, C, Data[8] + Longint($8771F681), 11);
  Round3(C, D, A, B, Data[11] + Longint($6D9D6122), 16);
  Round3(B, C, D, A, Data[14] + Longint($FDE5380C), 23);
  Round3(A, B, C, D, Data[1] + Longint($A4BEEA44), 4);
  Round3(D, A, B, C, Data[4] + Longint($4BDECFA9), 11);
  Round3(C, D, A, B, Data[7] + Longint($F6BB4B60), 16);
  Round3(B, C, D, A, Data[10] + Longint($BEBFBC70), 23);
  Round3(A, B, C, D, Data[13] + Longint($289B7EC6), 4);
  Round3(D, A, B, C, Data[0] + Longint($EAA127FA), 11);
  Round3(C, D, A, B, Data[3] + Longint($D4EF3085), 16);
  Round3(B, C, D, A, Data[6] + Longint($04881D05), 23);
  Round3(A, B, C, D, Data[9] + Longint($D9D4D039), 4);
  Round3(D, A, B, C, Data[12] + Longint($E6DB99E5), 11);
  Round3(C, D, A, B, Data[15] + Longint($1FA27CF8), 16);
  Round3(B, C, D, A, Data[2] + Longint($C4AC5665), 23);

  Round4(A, B, C, D, Data[0] + Longint($F4292244), 6);
  Round4(D, A, B, C, Data[7] + Longint($432AFF97), 10);
  Round4(C, D, A, B, Data[14] + Longint($AB9423A7), 15);
  Round4(B, C, D, A, Data[5] + Longint($FC93A039), 21);
  Round4(A, B, C, D, Data[12] + Longint($655B59C3), 6);
  Round4(D, A, B, C, Data[3] + Longint($8F0CCC92), 10);
  Round4(C, D, A, B, Data[10] + Longint($FFEFF47D), 15);
  Round4(B, C, D, A, Data[1] + Longint($85845DD1), 21);
  Round4(A, B, C, D, Data[8] + Longint($6FA87E4F), 6);
  Round4(D, A, B, C, Data[15] + Longint($FE2CE6E0), 10);
  Round4(C, D, A, B, Data[6] + Longint($A3014314), 15);
  Round4(B, C, D, A, Data[13] + Longint($4E0811A1), 21);
  Round4(A, B, C, D, Data[4] + Longint($F7537E82), 6);
  Round4(D, A, B, C, Data[11] + Longint($BD3AF235), 10);
  Round4(C, D, A, B, Data[2] + Longint($2AD7D2BB), 15);
  Round4(B, C, D, A, Data[9] + Longint($EB86D391), 21);

  Inc(Buf[0], A);
  Inc(Buf[1], B);
  Inc(Buf[2], C);
  Inc(Buf[3], D);
end;

//fixed by James McAdams
procedure MDUpdate(var MDContext: TMDCtx; const Data: AnsiString; transform: TMDTransform);
var
  Index, partLen, InputLen, I: integer;
{$IFDEF SYNACODE_NATIVE}
  n: integer;
{$ENDIF}
begin
  InputLen := Length(Data);
  with MDContext do
  begin
    Index := (Count[0] shr 3) and $3F;
    Inc(Count[0], InputLen shl 3);
    if Count[0] < (InputLen shl 3) then
      Inc(Count[1]);
    Inc(Count[1], InputLen shr 29);
    partLen := 64 - Index;
    if InputLen >= partLen then
    begin
      ArrLongToByte(BufLong, BufAnsiChar);
      {$IFDEF SYNACODE_NATIVE}
      for n := 1 to partLen do
        BufAnsiChar[index - 1 + n] := Ord(Data[n]);
      {$ELSE}
      Move(Data[1], BufAnsiChar[Index], partLen);
      {$ENDIF}
      ArrByteToLong(BufAnsiChar, BufLong);
      Transform(State, Buflong);
      I := partLen;
  		while I + 63 < InputLen do
      begin
        ArrLongToByte(BufLong, BufAnsiChar);
        {$IFDEF SYNACODE_NATIVE}
        for n := 1 to 64 do
          BufAnsiChar[n - 1] := Ord(Data[i + n]);
        {$ELSE}
        Move(Data[I+1], BufAnsiChar, 64);
        {$ENDIF}
        ArrByteToLong(BufAnsiChar, BufLong);
        Transform(State, Buflong);
	  	  inc(I, 64);
		  end;
      Index := 0;
    end
    else
      I := 0;
    ArrLongToByte(BufLong, BufAnsiChar);
    {$IFDEF SYNACODE_NATIVE}
    for n := 1 to InputLen-I do
      BufAnsiChar[Index + n - 1] := Ord(Data[i + n]);
    {$ELSE}
    Move(Data[I+1], BufAnsiChar[Index], InputLen-I);
    {$ENDIF}
    ArrByteToLong(BufAnsiChar, BufLong);
  end
end;

function MDFinal(var MDContext: TMDCtx; transform: TMDTransform): AnsiString;
var
  Cnt: Word;
  P: Byte;
  digest: array[0..15] of Byte;
  i: Integer;
  n: integer;
begin
  for I := 0 to 15 do
    Digest[I] := I + 1;
  with MDContext do
  begin
    Cnt := (Count[0] shr 3) and $3F;
    P := Cnt;
    BufAnsiChar[P] := $80;
    Inc(P);
    Cnt := 64 - 1 - Cnt;
    if Cnt < 8 then
    begin
      for n := 0 to cnt - 1 do
        BufAnsiChar[P + n] := 0;
      ArrByteToLong(BufAnsiChar, BufLong);
//      FillChar(BufAnsiChar[P], Cnt, #0);
      Transform(State, BufLong);
      ArrLongToByte(BufLong, BufAnsiChar);
      for n := 0 to 55 do
        BufAnsiChar[n] := 0;
      ArrByteToLong(BufAnsiChar, BufLong);
//      FillChar(BufAnsiChar, 56, #0);
    end
    else
    begin
      for n := 0 to Cnt - 8 - 1 do
        BufAnsiChar[p + n] := 0;
      ArrByteToLong(BufAnsiChar, BufLong);
//      FillChar(BufAnsiChar[P], Cnt - 8, #0);
    end;
    BufLong[14] := Count[0];
    BufLong[15] := Count[1];
    Transform(State, BufLong);
    ArrLongToByte(State, Digest);
//    Move(State, Digest, 16);
    Result := '';
    for i := 0 to 15 do
      Result := Result + AnsiChar(digest[i]);
  end;
//  FillChar(MD5Context, SizeOf(TMD5Ctx), #0)
end;

{==============================================================================}

function MD5(const Value: AnsiString): AnsiString;
var
  MDContext: TMDCtx;
begin
  MDInit(MDContext);
  MDUpdate(MDContext, Value, @MD5Transform);
  Result := MDFinal(MDContext, @MD5Transform);
end;

{==============================================================================}

function HMAC_MD5(Text, Key: AnsiString): AnsiString;
var
  ipad, opad, s: AnsiString;
  n: Integer;
  MDContext: TMDCtx;
begin
  if Length(Key) > 64 then
    Key := md5(Key);
  ipad := StringOfChar(#$36, 64);
  opad := StringOfChar(#$5C, 64);
  for n := 1 to Length(Key) do
  begin
    ipad[n] := AnsiChar(Byte(ipad[n]) xor Byte(Key[n]));
    opad[n] := AnsiChar(Byte(opad[n]) xor Byte(Key[n]));
  end;
  MDInit(MDContext);
  MDUpdate(MDContext, ipad, @MD5Transform);
  MDUpdate(MDContext, Text, @MD5Transform);
  s := MDFinal(MDContext, @MD5Transform);
  MDInit(MDContext);
  MDUpdate(MDContext, opad, @MD5Transform);
  MDUpdate(MDContext, s, @MD5Transform);
  Result := MDFinal(MDContext, @MD5Transform);
end;

{==============================================================================}

function MD5LongHash(const Value: AnsiString; Len: integer): AnsiString;
var
  cnt, rest: integer;
  l: integer;
  n: integer;
  MDContext: TMDCtx;
begin
  l := length(Value);
  cnt := Len div l;
  rest := Len mod l;
  MDInit(MDContext);
  for n := 1 to cnt do
    MDUpdate(MDContext, Value, @MD5Transform);
  if rest > 0 then
    MDUpdate(MDContext, Copy(Value, 1, rest), @MD5Transform);
  Result := MDFinal(MDContext, @MD5Transform);
end;

{==============================================================================}
// SHA1 is based on sources by Dave Barton (davebarton@bigfoot.com)

procedure SHA1init( var SHA1Context: TSHA1Ctx );
var
  n: integer;
begin
  SHA1Context.Hi := 0;
  SHA1Context.Lo := 0;
  SHA1Context.Index := 0;
  for n := 0 to High(SHA1Context.Buffer) do
    SHA1Context.Buffer[n] := 0;
  for n := 0 to High(SHA1Context.HashByte) do
    SHA1Context.HashByte[n] := 0;
//  FillChar(SHA1Context, SizeOf(TSHA1Ctx), #0);
  SHA1Context.Hash[0] := integer($67452301);
  SHA1Context.Hash[1] := integer($EFCDAB89);
  SHA1Context.Hash[2] := integer($98BADCFE);
  SHA1Context.Hash[3] := integer($10325476);
  SHA1Context.Hash[4] := integer($C3D2E1F0);
end;

//******************************************************************************
function RB(A: integer): integer;
begin
  Result := (A shr 24) or ((A shr 8) and $FF00) or ((A shl 8) and $FF0000) or (A shl 24);
end;

procedure SHA1Compress(var Data: TSHA1Ctx);
var
  A, B, C, D, E, T: integer;
  W: array[0..79] of integer;
  i: integer;
  n: integer;

  function F1(x, y, z: integer): integer;
  begin
    Result := z xor (x and (y xor z));
  end;
  function F2(x, y, z: integer): integer;
  begin
    Result := x xor y xor z;
  end;
  function F3(x, y, z: integer): integer;
  begin
    Result := (x and y) or (z and (x or y));
  end;
  function LRot32(X: integer; c: integer): integer;
  begin
    result := (x shl c) or (x shr (32 - c));
  end;
begin
  ArrByteToLong(Data.Buffer, W);
//  Move(Data.Buffer, W, Sizeof(Data.Buffer));
  for i := 0 to 15 do
    W[i] := RB(W[i]);
  for i := 16 to 79 do
    W[i] := LRot32(W[i-3] xor W[i-8] xor W[i-14] xor W[i-16], 1);
  A := Data.Hash[0];
  B := Data.Hash[1];
  C := Data.Hash[2];
  D := Data.Hash[3];
  E := Data.Hash[4];
  for i := 0 to 19 do
  begin
    T := LRot32(A, 5) + F1(B, C, D) + E + W[i] + integer($5A827999);
    E := D;
    D := C;
    C := LRot32(B, 30);
    B := A;
    A := T;
  end;
  for i := 20 to 39 do
  begin
    T := LRot32(A, 5) + F2(B, C, D) + E + W[i] + integer($6ED9EBA1);
    E := D;
    D := C;
    C := LRot32(B, 30);
    B := A;
    A := T;
  end;
  for i := 40 to 59 do
  begin
    T := LRot32(A, 5) + F3(B, C, D) + E + W[i] + integer($8F1BBCDC);
    E := D;
    D := C;
    C := LRot32(B, 30);
    B := A;
    A := T;
  end;
  for i := 60 to 79 do
  begin
    T := LRot32(A, 5) + F2(B, C, D) + E + W[i] + integer($CA62C1D6);
    E := D;
    D := C;
    C := LRot32(B, 30);
    B := A;
    A := T;
  end;
  Data.Hash[0] := Data.Hash[0] + A;
  Data.Hash[1] := Data.Hash[1] + B;
  Data.Hash[2] := Data.Hash[2] + C;
  Data.Hash[3] := Data.Hash[3] + D;
  Data.Hash[4] := Data.Hash[4] + E;
  for n := 0 to high(w) do
    w[n] := 0;
//  FillChar(W, Sizeof(W), 0);
  for n := 0 to high(Data.Buffer) do
    Data.Buffer[n] := 0;
//  FillChar(Data.Buffer, Sizeof(Data.Buffer), 0);
end;

//******************************************************************************
procedure SHA1Update(var Context: TSHA1Ctx; const Data: AnsiString);
var
  Len: integer;
  n: integer;
  i, k: integer;
begin
  Len := Length(data);
  for k := 0 to 7 do
  begin
    i := Context.Lo;
    Inc(Context.Lo, Len);
    if Context.Lo < i then
      Inc(Context.Hi);
  end;
  for n := 1 to len do
  begin
    Context.Buffer[Context.Index] := byte(Data[n]);
    Inc(Context.Index);
    if Context.Index = 64 then
    begin
      Context.Index := 0;
      SHA1Compress(Context);
    end;
  end;
end;

//******************************************************************************
function SHA1Final(var Context: TSHA1Ctx): AnsiString;
type
  Pinteger = ^integer;
var
  i: integer;
  procedure ItoArr(var Ar: Array of byte; I, value: Integer);
  begin
    Ar[i + 0] := Value and $000000FF;
    Ar[i + 1] := (Value shr 8) and $000000FF;
    Ar[i + 2] := (Value shr 16) and $000000FF;
    Ar[i + 3] := (Value shr 24) and $000000FF;
  end;
begin
  Context.Buffer[Context.Index] := $80;
  if Context.Index >= 56 then
    SHA1Compress(Context);
  ItoArr(Context.Buffer, 56, RB(Context.Hi));
  ItoArr(Context.Buffer, 60, RB(Context.Lo));
//  Pinteger(@Context.Buffer[56])^ := RB(Context.Hi);
//  Pinteger(@Context.Buffer[60])^ := RB(Context.Lo);
  SHA1Compress(Context);
  Context.Hash[0] := RB(Context.Hash[0]);
  Context.Hash[1] := RB(Context.Hash[1]);
  Context.Hash[2] := RB(Context.Hash[2]);
  Context.Hash[3] := RB(Context.Hash[3]);
  Context.Hash[4] := RB(Context.Hash[4]);
  ArrLongToByte(Context.Hash, Context.HashByte);
  Result := '';
  for i := 0 to 19 do
    Result := Result + AnsiChar(Context.HashByte[i]);
end;

function SHA1(const Value: AnsiString): AnsiString;
var
  SHA1Context: TSHA1Ctx;
begin
  SHA1Init(SHA1Context);
  SHA1Update(SHA1Context, Value);
  Result := SHA1Final(SHA1Context);
end;

{==============================================================================}

function HMAC_SHA1(Text, Key: AnsiString): AnsiString;
var
  ipad, opad, s: AnsiString;
  n: Integer;
  SHA1Context: TSHA1Ctx;
begin
  if Length(Key) > 64 then
    Key := SHA1(Key);
  ipad := StringOfChar(#$36, 64);
  opad := StringOfChar(#$5C, 64);
  for n := 1 to Length(Key) do
  begin
    ipad[n] := AnsiChar(Byte(ipad[n]) xor Byte(Key[n]));
    opad[n] := AnsiChar(Byte(opad[n]) xor Byte(Key[n]));
  end;
  SHA1Init(SHA1Context);
  SHA1Update(SHA1Context, ipad);
  SHA1Update(SHA1Context, Text);
  s := SHA1Final(SHA1Context);
  SHA1Init(SHA1Context);
  SHA1Update(SHA1Context, opad);
  SHA1Update(SHA1Context, s);
  Result := SHA1Final(SHA1Context);
end;

{==============================================================================}

function SHA1LongHash(const Value: AnsiString; Len: integer): AnsiString;
var
  cnt, rest: integer;
  l: integer;
  n: integer;
  SHA1Context: TSHA1Ctx;
begin
  l := length(Value);
  cnt := Len div l;
  rest := Len mod l;
  SHA1Init(SHA1Context);
  for n := 1 to cnt do
    SHA1Update(SHA1Context, Value);
  if rest > 0 then
    SHA1Update(SHA1Context, Copy(Value, 1, rest));
  Result := SHA1Final(SHA1Context);
end;

{==============================================================================}

procedure MD4Transform(var Buf: array of LongInt; const Data: array of LongInt);
var
  A, B, C, D: LongInt;
  function LRot32(a, b: longint): longint;
  begin
    Result:= (a shl b) or (a shr (32 - b));
  end;
begin
  A := Buf[0];
  B := Buf[1];
  C := Buf[2];
  D := Buf[3];

  A:= LRot32(A + (D xor (B and (C xor D))) + Data[ 0], 3);
  D:= LRot32(D + (C xor (A and (B xor C))) + Data[ 1], 7);
  C:= LRot32(C + (B xor (D and (A xor B))) + Data[ 2], 11);
  B:= LRot32(B + (A xor (C and (D xor A))) + Data[ 3], 19);
  A:= LRot32(A + (D xor (B and (C xor D))) + Data[ 4], 3);
  D:= LRot32(D + (C xor (A and (B xor C))) + Data[ 5], 7);
  C:= LRot32(C + (B xor (D and (A xor B))) + Data[ 6], 11);
  B:= LRot32(B + (A xor (C and (D xor A))) + Data[ 7], 19);
  A:= LRot32(A + (D xor (B and (C xor D))) + Data[ 8], 3);
  D:= LRot32(D + (C xor (A and (B xor C))) + Data[ 9], 7);
  C:= LRot32(C + (B xor (D and (A xor B))) + Data[10], 11);
  B:= LRot32(B + (A xor (C and (D xor A))) + Data[11], 19);
  A:= LRot32(A + (D xor (B and (C xor D))) + Data[12], 3);
  D:= LRot32(D + (C xor (A and (B xor C))) + Data[13], 7);
  C:= LRot32(C + (B xor (D and (A xor B))) + Data[14], 11);
  B:= LRot32(B + (A xor (C and (D xor A))) + Data[15], 19);

  A:= LRot32(A + ((B and C) or (B and D) or (C and D)) + Data[ 0] + longint($5a827999), 3);
  D:= LRot32(D + ((A and B) or (A and C) or (B and C)) + Data[ 4] + longint($5a827999), 5);
  C:= LRot32(C + ((D and A) or (D and B) or (A and B)) + Data[ 8] + longint($5a827999), 9);
  B:= LRot32(B + ((C and D) or (C and A) or (D and A)) + Data[12] + longint($5a827999), 13);
  A:= LRot32(A + ((B and C) or (B and D) or (C and D)) + Data[ 1] + longint($5a827999), 3);
  D:= LRot32(D + ((A and B) or (A and C) or (B and C)) + Data[ 5] + longint($5a827999), 5);
  C:= LRot32(C + ((D and A) or (D and B) or (A and B)) + Data[ 9] + longint($5a827999), 9);
  B:= LRot32(B + ((C and D) or (C and A) or (D and A)) + Data[13] + longint($5a827999), 13);
  A:= LRot32(A + ((B and C) or (B and D) or (C and D)) + Data[ 2] + longint($5a827999), 3);
  D:= LRot32(D + ((A and B) or (A and C) or (B and C)) + Data[ 6] + longint($5a827999), 5);
  C:= LRot32(C + ((D and A) or (D and B) or (A and B)) + Data[10] + longint($5a827999), 9);
  B:= LRot32(B + ((C and D) or (C and A) or (D and A)) + Data[14] + longint($5a827999), 13);
  A:= LRot32(A + ((B and C) or (B and D) or (C and D)) + Data[ 3] + longint($5a827999), 3);
  D:= LRot32(D + ((A and B) or (A and C) or (B and C)) + Data[ 7] + longint($5a827999), 5);
  C:= LRot32(C + ((D and A) or (D and B) or (A and B)) + Data[11] + longint($5a827999), 9);
  B:= LRot32(B + ((C and D) or (C and A) or (D and A)) + Data[15] + longint($5a827999), 13);

  A:= LRot32(A + (B xor C xor D) + Data[ 0] + longint($6ed9eba1), 3);
  D:= LRot32(D + (A xor B xor C) + Data[ 8] + longint($6ed9eba1), 9);
  C:= LRot32(C + (D xor A xor B) + Data[ 4] + longint($6ed9eba1), 11);
  B:= LRot32(B + (C xor D xor A) + Data[12] + longint($6ed9eba1), 15);
  A:= LRot32(A + (B xor C xor D) + Data[ 2] + longint($6ed9eba1), 3);
  D:= LRot32(D + (A xor B xor C) + Data[10] + longint($6ed9eba1), 9);
  C:= LRot32(C + (D xor A xor B) + Data[ 6] + longint($6ed9eba1), 11);
  B:= LRot32(B + (C xor D xor A) + Data[14] + longint($6ed9eba1), 15);
  A:= LRot32(A + (B xor C xor D) + Data[ 1] + longint($6ed9eba1), 3);
  D:= LRot32(D + (A xor B xor C) + Data[ 9] + longint($6ed9eba1), 9);
  C:= LRot32(C + (D xor A xor B) + Data[ 5] + longint($6ed9eba1), 11);
  B:= LRot32(B + (C xor D xor A) + Data[13] + longint($6ed9eba1), 15);
  A:= LRot32(A + (B xor C xor D) + Data[ 3] + longint($6ed9eba1), 3);
  D:= LRot32(D + (A xor B xor C) + Data[11] + longint($6ed9eba1), 9);
  C:= LRot32(C + (D xor A xor B) + Data[ 7] + longint($6ed9eba1), 11);
  B:= LRot32(B + (C xor D xor A) + Data[15] + longint($6ed9eba1), 15);

  Inc(Buf[0], A);
  Inc(Buf[1], B);
  Inc(Buf[2], C);
  Inc(Buf[3], D);
end;

{==============================================================================}

function MD4(const Value: AnsiString): AnsiString;
var
  MDContext: TMDCtx;
begin
  MDInit(MDContext);
  MDUpdate(MDContext, Value, @MD4Transform);
  Result := MDFinal(MDContext, @MD4Transform);
end;

{==============================================================================}


end.
