base64.pp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 1999-2000 by Michael Van Canneyt and Florian Klaempfl
  4. base64 encoder & decoder (c) 1999 Sebastian Guenther
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. // Encoding and decoding streams for base64 data as described in
  12. // RFC2045 (Mode = bdmMIME) and
  13. // RFC3548 (Mode = bdmStrict)
  14. // Addition of TBase64DecodingMode supporting both Strict and MIME mode is
  15. // (C) 2007 Hexis BV, by Bram Kuijvenhoven ([email protected])
  16. {$MODE objfpc}
  17. {$H+}
  18. unit base64;
  19. interface
  20. uses classes, sysutils;
  21. type
  22. TBase64EncodingStream = class(TOwnerStream)
  23. protected
  24. TotalBytesProcessed, BytesWritten: LongWord;
  25. Buf: array[0..2] of Byte;
  26. BufSize: Integer; // # of bytes used in Buf
  27. public
  28. destructor Destroy; override;
  29. Function Flush : Boolean;
  30. function Write(const Buffer; Count: Longint): Longint; override;
  31. function Seek(Offset: Longint; Origin: Word): Longint; override;
  32. end;
  33. (* The TBase64DecodingStream supports two modes:
  34. * - 'strict mode':
  35. * - follows RFC3548
  36. * - rejects any characters outside of base64 alphabet,
  37. * - only accepts up to two '=' characters at the end and
  38. * - requires the input to have a Size being a multiple of 4; otherwise raises an EBase64DecodingException
  39. * - 'MIME mode':
  40. * - follows RFC2045
  41. * - ignores any characters outside of base64 alphabet
  42. * - takes any '=' as end of string
  43. * - handles apparently truncated input streams gracefully
  44. *)
  45. TBase64DecodingMode = (bdmStrict, bdmMIME);
  46. { TBase64DecodingStream }
  47. TBase64DecodingStream = class(TOwnerStream)
  48. private
  49. FMode: TBase64DecodingMode;
  50. procedure SetMode(const AValue: TBase64DecodingMode);
  51. function GetSize: Int64; override;
  52. function GetPosition: Int64; override;
  53. protected
  54. CurPos, // 0-based (decoded) position of this stream (nr. of decoded & Read bytes since last reset)
  55. DecodedSize: Int64; // length of decoded stream ((expected) decoded bytes since last Reset until Mode-dependent end of stream)
  56. ReadBase64ByteCount: Int64; // number of valid base64 bytes read from input stream since last Reset
  57. Buf: array[0..2] of Byte; // last 3 decoded bytes
  58. BufPos: Integer; // offset in Buf of byte which is to be read next; if >2, next block must be read from Source & decoded
  59. FEOF: Boolean; // if true, all decoded bytes have been read
  60. public
  61. constructor Create(ASource: TStream);
  62. constructor Create(ASource: TStream; AMode: TBase64DecodingMode);
  63. procedure Reset;
  64. function Read(var Buffer; Count: Longint): Longint; override;
  65. function Seek(Offset: Longint; Origin: Word): Longint; override;
  66. property EOF: Boolean read fEOF;
  67. property Mode: TBase64DecodingMode read FMode write SetMode;
  68. end;
  69. EBase64DecodingException = class(Exception)
  70. end;
  71. function EncodeStringBase64(const s:string):String;
  72. function DecodeStringBase64(const s:string;strict:boolean=false):String;
  73. implementation
  74. uses
  75. Math;
  76. const
  77. SStrictNonBase64Char = 'Non-valid Base64 Encoding character in input';
  78. SStrictInputTruncated = 'Input stream was truncated at non-4 byte boundary';
  79. SStrictMisplacedPadChar = 'Unexpected padding character ''='' before end of input stream';
  80. EncodingTable: PChar =
  81. 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
  82. const
  83. NA = 85; // not in base64 alphabet at all; binary: 01010101
  84. PC = 255; // padding character 11111111
  85. DecTable: array[Byte] of Byte =
  86. (NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, // 0-15
  87. NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, // 16-31
  88. NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 62, NA, NA, NA, 63, // 32-47
  89. 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, NA, NA, NA, PC, NA, NA, // 48-63
  90. NA, 00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, // 64-79
  91. 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, NA, NA, NA, NA, NA, // 80-95
  92. NA, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, // 96-111
  93. 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, NA, NA, NA, NA, NA, // 112-127
  94. NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
  95. NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
  96. NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
  97. NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
  98. NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
  99. NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
  100. NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,
  101. NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA);
  102. Alphabet = ['a'..'z','A'..'Z','0'..'9','+','/','=']; // all 65 chars that are in the base64 encoding alphabet
  103. function TBase64EncodingStream.Flush : Boolean;
  104. var
  105. WriteBuf: array[0..3] of Char;
  106. begin
  107. // Fill output to multiple of 4
  108. case (TotalBytesProcessed mod 3) of
  109. 1: begin
  110. WriteBuf[0] := EncodingTable[Buf[0] shr 2];
  111. WriteBuf[1] := EncodingTable[(Buf[0] and 3) shl 4];
  112. WriteBuf[2] := '=';
  113. WriteBuf[3] := '=';
  114. Source.Write(WriteBuf, 4);
  115. Result:=True;
  116. Inc(TotalBytesProcessed,2);
  117. end;
  118. 2: begin
  119. WriteBuf[0] := EncodingTable[Buf[0] shr 2];
  120. WriteBuf[1] := EncodingTable[(Buf[0] and 3) shl 4 or (Buf[1] shr 4)];
  121. WriteBuf[2] := EncodingTable[(Buf[1] and 15) shl 2];
  122. WriteBuf[3] := '=';
  123. Source.Write(WriteBuf, 4);
  124. Result:=True;
  125. Inc(TotalBytesProcessed,1);
  126. end;
  127. else
  128. Result:=False;
  129. end;
  130. end;
  131. destructor TBase64EncodingStream.Destroy;
  132. begin
  133. Flush;
  134. inherited Destroy;
  135. end;
  136. function TBase64EncodingStream.Write(const Buffer; Count: Longint): Longint;
  137. var
  138. ReadNow: LongInt;
  139. p: Pointer;
  140. WriteBuf: array[0..3] of Char;
  141. begin
  142. Inc(TotalBytesProcessed, Count);
  143. Result := Count;
  144. p := @Buffer;
  145. while count > 0 do begin
  146. // Fetch data into the Buffer
  147. ReadNow := 3 - BufSize;
  148. if ReadNow > Count then break; // Not enough data available
  149. Move(p^, Buf[BufSize], ReadNow);
  150. Inc(p, ReadNow);
  151. Dec(Count, ReadNow);
  152. // Encode the 3 bytes in Buf
  153. WriteBuf[0] := EncodingTable[Buf[0] shr 2];
  154. WriteBuf[1] := EncodingTable[(Buf[0] and 3) shl 4 or (Buf[1] shr 4)];
  155. WriteBuf[2] := EncodingTable[(Buf[1] and 15) shl 2 or (Buf[2] shr 6)];
  156. WriteBuf[3] := EncodingTable[Buf[2] and 63];
  157. Source.Write(WriteBuf, 4);
  158. Inc(BytesWritten, 4);
  159. BufSize := 0;
  160. end;
  161. Move(p^, Buf[BufSize], count);
  162. Inc(BufSize, count);
  163. end;
  164. function TBase64EncodingStream.Seek(Offset: Longint; Origin: Word): Longint;
  165. begin
  166. Result := BytesWritten;
  167. if BufSize > 0 then
  168. Inc(Result, 4);
  169. // This stream only supports the Seek modes needed for determining its size
  170. if not ((((Origin = soFromCurrent) or (Origin = soFromEnd)) and (Offset = 0))
  171. or ((Origin = soFromBeginning) and (Offset = Result))) then
  172. raise EStreamError.Create('Invalid stream operation');
  173. end;
  174. procedure TBase64DecodingStream.SetMode(const AValue: TBase64DecodingMode);
  175. begin
  176. if FMode = AValue then exit;
  177. FMode := AValue;
  178. DecodedSize := -1; // forget any calculations on this
  179. end;
  180. function TBase64DecodingStream.GetSize: Int64;
  181. var
  182. endBytes: array[0..1] of Char;
  183. ipos, isize: Int64;
  184. scanBuf: array[0..1023] of Char;
  185. count: LongInt;
  186. i: Integer;
  187. c: Char;
  188. begin
  189. // Note: this method only works on Seekable Sources (for bdmStrict we also get the Size property)
  190. if DecodedSize<>-1 then Exit(DecodedSize);
  191. ipos := Source.Position; // save position in input stream
  192. case Mode of
  193. bdmMIME: begin
  194. // read until end of input stream or first occurence of a '='
  195. Result := ReadBase64ByteCount; // keep number of valid base64 bytes since last Reset in Result
  196. repeat
  197. count := Source.Read(scanBuf, SizeOf(scanBuf));
  198. for i := 0 to count-1 do begin
  199. c := scanBuf[i];
  200. if c in Alphabet-['='] then // base64 encoding characters except '='
  201. Inc(Result)
  202. else if c = '=' then // end marker '='
  203. Break;
  204. end;
  205. until count = 0;
  206. // writeln(Result);
  207. // we are now either at the end of the stream, or encountered our first '=', stored in c
  208. if c = '=' then begin // '=' found
  209. if Result mod 4 <= 1 then // badly placed '=', disregard last block
  210. Result := (Result div 4) * 3
  211. else // 4 byte block ended with '=' or '=='
  212. Result := (Result div 4) * 3 + Result mod 4 - 1;
  213. end else // end of stream
  214. Result := (Result div 4) * 3; // number of valid 4 byte blocks times 3
  215. end;
  216. bdmStrict:begin
  217. // seek to end of input stream, read last two bytes and determine size
  218. // from Source size and the number of leading '=' bytes
  219. // NB we don't raise an exception here if the input does not contains an integer multiple of 4 bytes
  220. ipos := Source.Position;
  221. isize := Source.Size;
  222. Result := ((ReadBase64ByteCount + (isize - ipos) + 3) div 4) * 3;
  223. Source.Seek(-2, soFromEnd);
  224. Source.Read(endBytes, 2);
  225. if endBytes[1] = '=' then begin // last byte
  226. Dec(Result);
  227. if endBytes[0] = '=' then // second to last byte
  228. Dec(Result);
  229. end;
  230. end;
  231. end;
  232. Source.Position := ipos; // restore position in input stream
  233. // store calculated DecodedSize
  234. DecodedSize := Result;
  235. end;
  236. function TBase64DecodingStream.GetPosition: Int64;
  237. begin
  238. Result := CurPos;
  239. end;
  240. constructor TBase64DecodingStream.Create(ASource: TStream);
  241. begin
  242. Create(ASource, bdmMIME); // MIME mode is default
  243. end;
  244. constructor TBase64DecodingStream.Create(ASource: TStream; AMode: TBase64DecodingMode);
  245. begin
  246. inherited Create(ASource);
  247. Mode := AMode;
  248. Reset;
  249. end;
  250. procedure TBase64DecodingStream.Reset;
  251. begin
  252. ReadBase64ByteCount := 0; // number of bytes Read form Source since last call to Reset
  253. CurPos := 0; // position in decoded byte sequence since last Reset
  254. DecodedSize := -1; // indicates unknown; will be set after first call to GetSize or when reaching end of stream
  255. BufPos := 3; // signals we need to read & decode a new block of 4 bytes
  256. FEOF := False;
  257. end;
  258. function TBase64DecodingStream.Read(var Buffer; Count: Longint): Longint;
  259. var
  260. p: PByte;
  261. b: byte;
  262. ReadBuf: array[0..3] of Byte; // buffer to store last read 4 input bytes
  263. ToRead, OrgToRead, HaveRead, ReadOK, i: Integer;
  264. procedure DetectedEnd(ASize:Int64);
  265. begin
  266. DecodedSize := ASize;
  267. // Correct Count if at end of base64 input
  268. if CurPos + Count > DecodedSize then
  269. Count := DecodedSize - CurPos;
  270. end;
  271. begin
  272. if Count <= 0 then exit(0); // nothing to read, quit
  273. if DecodedSize <> -1 then begin // try using calculated size info if possible
  274. if CurPos + Count > DecodedSize then
  275. Count := DecodedSize - CurPos;
  276. if Count <= 0 then exit(0);
  277. end;
  278. Result := 0;
  279. p := @Buffer;
  280. while true do begin
  281. // get new 4-byte block if at end of Buf
  282. if BufPos > 2 then begin
  283. BufPos := 0;
  284. // Read the next 4 valid bytes
  285. ToRead := 4; // number of base64 bytes left to read into ReadBuf
  286. ReadOK := 0; // number of base64 bytes already read into ReadBuf
  287. while ToRead > 0 do begin
  288. OrgToRead := ToRead;
  289. HaveRead := Source.Read(ReadBuf[ReadOK], ToRead);
  290. //WriteLn('ToRead = ', ToRead, ', HaveRead = ', HaveRead, ', ReadOK=', ReadOk);
  291. if HaveRead > 0 then begin // if any new bytes; in ReadBuf[ReadOK .. ReadOK + HaveRead-1]
  292. for i := ReadOK to ReadOK + HaveRead - 1 do begin
  293. b := DecTable[ReadBuf[i]];
  294. if b <> NA then begin // valid base64 alphabet character ('=' inclusive)
  295. ReadBuf[ReadOK] := b;
  296. Inc(ReadOK);
  297. Dec(ToRead);
  298. end else if Mode=bdmStrict then begin // non-valid character
  299. raise EBase64DecodingException.CreateFmt(SStrictNonBase64Char,[]);
  300. end;
  301. end;
  302. end;
  303. if HaveRead <> OrgToRead then begin // less than 4 base64 bytes could be read; end of input stream
  304. //WriteLn('End: ReadOK=', ReadOK, ', count=', Count);
  305. for i := ReadOK to 3 do
  306. ReadBuf[i] := 0; // pad buffer with zeros so decoding of 4-bytes will be correct
  307. if (Mode = bdmStrict) and (ReadOK > 0) then
  308. raise EBase64DecodingException.CreateFmt(SStrictInputTruncated,[]);
  309. Break;
  310. end;
  311. end;
  312. Inc(ReadBase64ByteCount, ReadOK);
  313. // Check for pad characters
  314. case Mode of
  315. bdmStrict:begin
  316. if ReadOK = 0 then // end of input stream was reached at 4-byte boundary
  317. DetectedEnd(CurPos)
  318. else if (ReadBuf[0] = PC) or (ReadBuf[1] = PC) then
  319. raise EBase64DecodingException.CreateFmt(SStrictMisplacedPadChar,[]) // =BBB or B=BB
  320. else if (ReadBuf[2] = PC) then begin
  321. if (ReadBuf[3] <> PC) or (Source.Position < Source.Size) then
  322. raise EBase64DecodingException.CreateFmt(SStrictMisplacedPadChar,[]); // BB=B or BB==, but not at end of input stream
  323. DetectedEnd(CurPos + 1) // only one byte left to read; BB==, at end of input stream
  324. end else if (ReadBuf[3] = PC) then begin
  325. if (Source.Position < Source.Size) then
  326. raise EBase64DecodingException.CreateFmt(SStrictMisplacedPadChar,[]); // BBB=, but not at end of input stream
  327. DetectedEnd(CurPos + 2); // only two bytes left to read; BBB=, at end of input stream
  328. end;
  329. end;
  330. bdmMIME:begin
  331. if ReadOK = 0 then // end of input stream was reached at 4-byte boundary
  332. DetectedEnd(CurPos)
  333. else if (ReadBuf[0] = PC) or (ReadBuf[1] = PC) then
  334. DetectedEnd(CurPos) // =BBB or B=BB: end here
  335. else if (ReadBuf[2] = PC) then begin
  336. DetectedEnd(CurPos + 1) // only one byte left to read; BB=B or BB==
  337. end else if (ReadBuf[3] = PC) then begin
  338. DetectedEnd(CurPos + 2); // only two bytes left to read; BBB=
  339. end;
  340. end;
  341. end;
  342. // Decode the 4 bytes in the buffer to 3 undecoded bytes
  343. Buf[0] := ReadBuf[0] shl 2 or ReadBuf[1] shr 4;
  344. Buf[1] := (ReadBuf[1] and 15) shl 4 or ReadBuf[2] shr 2;
  345. Buf[2] := (ReadBuf[2] and 3) shl 6 or ReadBuf[3];
  346. end;
  347. if Count <= 0 then begin
  348. Break;
  349. end;
  350. // write one byte to Count
  351. p^ := Buf[BufPos];
  352. Inc(p);
  353. Inc(BufPos);
  354. Inc(CurPos);
  355. Dec(Count);
  356. Inc(Result);
  357. end;
  358. // check for EOF
  359. if (DecodedSize <> -1) and (CurPos >= DecodedSize) then begin
  360. FEOF := true;
  361. end;
  362. end;
  363. function TBase64DecodingStream.Seek(Offset: Longint; Origin: Word): Longint;
  364. begin
  365. // TODO: implement Seeking in TBase64DecodingStream
  366. raise EStreamError.Create('Invalid stream operation');
  367. end;
  368. function DecodeStringBase64(const s:string;strict:boolean=false):String;
  369. var
  370. Instream,
  371. Outstream : TStringStream;
  372. Decoder : TBase64DecodingStream;
  373. begin
  374. Instream:=TStringStream.Create(s);
  375. try
  376. Outstream:=TStringStream.Create('');
  377. try
  378. if strict then
  379. Decoder:=TBase64DecodingStream.Create(Instream,bdmStrict)
  380. else
  381. Decoder:=TBase64DecodingStream.Create(Instream,bdmMIME);
  382. try
  383. Outstream.CopyFrom(Decoder,Decoder.Size);
  384. Result:=Outstream.DataString;
  385. finally
  386. Decoder.Free;
  387. end;
  388. finally
  389. Outstream.Free;
  390. end;
  391. finally
  392. Instream.Free;
  393. end;
  394. end;
  395. function EncodeStringBase64(const s:string):String;
  396. var
  397. Outstream : TStringStream;
  398. Encoder : TBase64EncodingStream;
  399. begin
  400. Outstream:=TStringStream.Create('');
  401. try
  402. Encoder:=TBase64EncodingStream.create(outstream);
  403. try
  404. Encoder.Write(s[1],Length(s));
  405. finally
  406. Encoder.Free;
  407. end;
  408. Result:=Outstream.DataString;
  409. finally
  410. Outstream.free;
  411. end;
  412. end;
  413. end.