IdCoderHeader.pas 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. {
  2. $Project$
  3. $Workfile$
  4. $Revision$
  5. $DateUTC$
  6. $Id$
  7. This file is part of the Indy (Internet Direct) project, and is offered
  8. under the dual-licensing agreement described on the Indy website.
  9. (http://www.indyproject.org/)
  10. Copyright:
  11. (c) 1993-2005, Chad Z. Hower and the Indy Pit Crew. All rights reserved.
  12. $Log$
  13. Rev 1.13 9/8/2004 8:55:46 PM JPMugaas
  14. Fix for compile problem where a char is being compared with an incompatible
  15. type in some compilers.
  16. Rev 1.12 02/07/2004 21:59:28 CCostelloe
  17. Bug fix
  18. Rev 1.11 17/06/2004 14:19:00 CCostelloe
  19. Bug fix for long subject lines that have characters needing CharSet encoding
  20. Rev 1.10 23/04/2004 20:33:04 CCostelloe
  21. Minor change to support From headers holding multiple addresses
  22. Rev 1.9 2004.02.03 5:44:58 PM czhower
  23. Name changes
  24. Rev 1.8 24/01/2004 19:08:14 CCostelloe
  25. Cleaned up warnings
  26. Rev 1.7 1/22/2004 3:56:38 PM SPerry
  27. fixed set problems
  28. Rev 1.6 2004.01.22 2:34:58 PM czhower
  29. TextIsSame + D8 bug workaround
  30. Rev 1.5 10/16/2003 11:11:02 PM DSiders
  31. Added localization comments.
  32. Rev 1.4 10/8/2003 9:49:36 PM GGrieve
  33. Use IdDelete
  34. Rev 1.3 6/10/2003 5:48:46 PM SGrobety
  35. DotNet updates
  36. Rev 1.2 04/09/2003 20:35:28 CCostelloe
  37. Parameter AUseAddressForNameIfNameMissing (defaulting to False to preserve
  38. existing code) added to EncodeAddressItem
  39. Rev 1.1 2003.06.23 9:46:52 AM czhower
  40. Russian, Ukranian support for headers.
  41. Rev 1.0 11/14/2002 02:14:46 PM JPMugaas
  42. }
  43. unit IdCoderHeader;
  44. //refer http://www.faqs.org/rfcs/rfc2047.html
  45. //TODO: Optimize and restructure code
  46. //TODO: Redo this unit to fit with the new coders and use the exisiting MIME stuff
  47. {
  48. 2002-08-21 JM Berg
  49. - brought in line with the RFC regarding
  50. whitespace between encoded words
  51. - added logic so that lines that already seem encoded are really encoded again
  52. (so that if a user types =?iso8859-1?Q?======?= its really encoded again
  53. and displayed like that on the other side)
  54. 2001-Nov-18 Peter Mee
  55. - Fixed multiple QP decoding in single header.
  56. 11-10-2001 - J. Peter Mugaas
  57. - tiny fix for 8bit header encoding suggested by Andrew P.Rybin
  58. }
  59. interface
  60. {$i IdCompilerDefines.inc}
  61. uses
  62. Classes,
  63. IdComponent,
  64. IdEMailAddress,
  65. IdHeaderCoderBase;
  66. // Procs
  67. function EncodeAddressItem(EmailAddr: TIdEmailAddressItem; const HeaderEncoding: Char;
  68. const MimeCharSet: string; AUseAddressForNameIfNameMissing: Boolean = False): string;
  69. function EncodeHeader(const Header: string; Specials: String; const HeaderEncoding: Char;
  70. const MimeCharSet: string): string;
  71. function EncodeAddress(EmailAddr: TIdEMailAddressList; const HeaderEncoding: Char;
  72. const MimeCharSet: string; AUseAddressForNameIfNameMissing: Boolean = False): string;
  73. function DecodeHeader(const Header: string): string;
  74. procedure DecodeAddress(EMailAddr: TIdEmailAddressItem);
  75. procedure DecodeAddresses(AEMails: String; EMailAddr: TIdEmailAddressList);
  76. implementation
  77. uses
  78. IdGlobal,
  79. IdGlobalProtocols,
  80. IdAllHeaderCoders,
  81. SysUtils;
  82. const
  83. csAddressSpecials: String = '()[]<>:;.,@\"'; {Do not Localize}
  84. base64_tbl: array [0..63] of Char = (
  85. 'A','B','C','D','E','F','G','H', {Do not Localize}
  86. 'I','J','K','L','M','N','O','P', {Do not Localize}
  87. 'Q','R','S','T','U','V','W','X', {Do not Localize}
  88. 'Y','Z','a','b','c','d','e','f', {Do not Localize}
  89. 'g','h','i','j','k','l','m','n', {Do not Localize}
  90. 'o','p','q','r','s','t','u','v', {Do not Localize}
  91. 'w','x','y','z','0','1','2','3', {Do not Localize}
  92. '4','5','6','7','8','9','+','/'); {Do not Localize}
  93. function EncodeAddressItem(EmailAddr: TIdEmailAddressItem; const HeaderEncoding: Char;
  94. const MimeCharSet: string; AUseAddressForNameIfNameMissing: Boolean = False): string;
  95. var
  96. S : string;
  97. I : Integer;
  98. NeedEncode : Boolean;
  99. begin
  100. if AUseAddressForNameIfNameMissing and (EmailAddr.Name = '') then begin
  101. {CC: Use Address as Name...}
  102. EmailAddr.Name := EmailAddr.Address;
  103. end;
  104. if EmailAddr.Name <> '' then {Do not Localize}
  105. begin
  106. NeedEncode := False;
  107. for I := 1 to Length(EmailAddr.Name) do begin
  108. if (EmailAddr.Name[I] < #32) or (EmailAddr.Name[I] >= #127) then
  109. begin
  110. NeedEncode := True;
  111. Break;
  112. end;
  113. end;
  114. if NeedEncode then begin
  115. S := EncodeHeader(EmailAddr.Name, csAddressSpecials, HeaderEncoding, MimeCharSet);
  116. end else begin
  117. { quoted string }
  118. S := '"'; {Do not Localize}
  119. for I := 1 to Length(EmailAddr.Name) do
  120. begin { quote special characters }
  121. if (EmailAddr.Name[I] = '\') or (EmailAddr.Name[I] = '"') then begin
  122. S := S + '\'; {Do not Localize}
  123. end;
  124. S := S + EmailAddr.Name[I];
  125. end;
  126. S := S + '"'; {Do not Localize}
  127. end;
  128. Result := IndyFormat('%s <%s>', [S, EmailAddr.Address]) {Do not Localize}
  129. end
  130. else begin
  131. Result := IndyFormat('%s', [EmailAddr.Address]); {Do not Localize}
  132. end;
  133. end;
  134. function B64(AChar: Char): Byte;
  135. //TODO: Make this use the more efficient MIME Coder
  136. begin
  137. for Result := Low(base64_tbl) to High(base64_tbl) do begin
  138. if AChar = base64_tbl[Result] then begin
  139. Exit;
  140. end;
  141. end;
  142. Result := 0;
  143. end;
  144. function DecodeHeader(const Header: string): string;
  145. var
  146. HeaderCharSet, HeaderEncoding, HeaderData, S: string;
  147. LDecoded: Boolean;
  148. LStartPos, LLength, LEncodingStartPos, LEncodingEndPos, LLastStartPos: Integer;
  149. LLastWordWasEncoded: Boolean;
  150. Buf: TIdBytes;
  151. function ExtractEncoding(const AHeader: string; const AStartPos: Integer;
  152. var VStartPos, VEndPos: Integer; var VCharSet, VEncoding, VData: String): Boolean;
  153. var
  154. LCharSet, LCharSetEnd, LEncoding, LEncodingEnd, LData, LDataEnd: Integer;
  155. begin
  156. Result := False;
  157. //we need a '=? followed by 2 question marks followed by a '?='. {Do not Localize}
  158. //to find the end of the substring, we can't just search for '?=', {Do not Localize}
  159. //example: '=?ISO-8859-1?Q?=E4?=' {Do not Localize}
  160. LCharSet := PosIdx('=?', AHeader, AStartPos); {Do not Localize}
  161. if (LCharSet = 0) or (LCharSet > VEndPos) then begin
  162. Exit;
  163. end;
  164. Inc(LCharSet, 2);
  165. // ignore language, if present
  166. LCharSetEnd := FindFirstOf('*?', AHeader, -1, LCharSet); {Do not Localize}
  167. if (LCharSetEnd = 0) or (LCharSetEnd > VEndPos) then begin
  168. Exit;
  169. end;
  170. if AHeader[LCharSetEnd] = '*' then begin
  171. LEncoding := PosIdx('?', AHeader, LCharSetEnd); {Do not Localize}
  172. if (LEncoding = 0) or (LEncoding > VEndPos) then begin
  173. Exit;
  174. end;
  175. end else begin
  176. LEncoding := LCharSetEnd;
  177. end;
  178. Inc(LEncoding);
  179. LEncodingEnd := PosIdx('?', AHeader, LEncoding); {Do not Localize}
  180. if (LEncodingEnd = 0) or (LEncodingEnd > VEndPos) then begin
  181. Exit;
  182. end;
  183. LData := LEncodingEnd+1;
  184. LDataEnd := PosIdx('?=', AHeader, LData); {Do not Localize}
  185. if (LDataEnd = 0) or (LDataEnd > VEndPos) then begin
  186. Exit;
  187. end;
  188. VStartPos := LCharSet-2;
  189. VEndPos := LDataEnd+1;
  190. VCharSet := Copy(AHeader, LCharSet, LCharSetEnd-LCharSet);
  191. VEncoding := Copy(AHeader, LEncoding, LEncodingEnd-LEncoding);
  192. VData := Copy(AHeader, LData, LDataEnd-LData);
  193. Result := True;
  194. end;
  195. // TODO: use TIdCoderQuotedPrintable and TIdCoderMIME instead
  196. function ExtractEncodedData(const AEncoding, AData: String; var VDecoded: TIdBytes): Boolean;
  197. var
  198. I, J: Integer;
  199. a3: TIdBytes;
  200. a4: array [0..3] of Byte;
  201. begin
  202. Result := False;
  203. SetLength(VDecoded, 0);
  204. case PosInStrArray(AEncoding, ['Q', 'B', '8'], False) of {Do not Localize}
  205. 0: begin // quoted-printable
  206. I := 1;
  207. while I <= Length(AData) do begin
  208. if AData[i] = '_' then begin {Do not Localize}
  209. AppendByte(VDecoded, Ord(' ')); {Do not Localize}
  210. end
  211. else if (AData[i] = '=') and (Length(AData) >= (i+2)) then begin //make sure we can access i+2
  212. AppendByte(VDecoded, IndyStrToInt('$' + Copy(AData, i+1, 2), 32)); {Do not Localize}
  213. Inc(I, 2);
  214. end else
  215. begin
  216. AppendByte(VDecoded, Ord(AData[i]));
  217. end;
  218. Inc(I);
  219. end;
  220. Result := True;
  221. end;
  222. 1: begin // base64
  223. J := Length(AData) div 4;
  224. if J > 0 then
  225. begin
  226. SetLength(a3, 3);
  227. for I := 0 to J-1 do
  228. begin
  229. a4[0] := B64(AData[(I*4)+1]);
  230. a4[1] := B64(AData[(I*4)+2]);
  231. a4[2] := B64(AData[(I*4)+3]);
  232. a4[3] := B64(AData[(I*4)+4]);
  233. a3[0] := Byte((a4[0] shl 2) or (a4[1] shr 4));
  234. a3[1] := Byte((a4[1] shl 4) or (a4[2] shr 2));
  235. a3[2] := Byte((a4[2] shl 6) or (a4[3] shr 0));
  236. if AData[(I*4)+4] = '=' then begin
  237. if AData[(I*4)+3] = '=' then begin
  238. AppendByte(VDecoded, a3[0]);
  239. end else begin
  240. AppendBytes(VDecoded, a3, 0, 2);
  241. end;
  242. Break;
  243. end else begin
  244. AppendBytes(VDecoded, a3, 0, 3);
  245. end;
  246. end;
  247. end;
  248. Result := True;
  249. end;
  250. 2: begin // 8-bit
  251. {$IFDEF STRING_IS_ANSI}
  252. if AData <> '' then begin
  253. VDecoded := RawToBytes(AData[1], Length(AData));
  254. end;
  255. {$ELSE}
  256. VDecoded := IndyTextEncoding_8Bit.GetBytes(AData);
  257. {$ENDIF}
  258. Result := True;
  259. end;
  260. end;
  261. end;
  262. begin
  263. Result := Header;
  264. LStartPos := 1;
  265. LLength := Length(Result);
  266. LLastWordWasEncoded := False;
  267. LLastStartPos := LStartPos;
  268. while LStartPos <= LLength do
  269. begin
  270. // valid encoded words can not contain spaces
  271. // if the user types something *almost* like an encoded word,
  272. // and its sent as-is, we need to find this!!
  273. LStartPos := FindFirstNotOf(LWS+CR+LF, Result, LLength, LStartPos);
  274. if LStartPos = 0 then begin
  275. Break;
  276. end;
  277. LEncodingEndPos := FindFirstOf(LWS+CR+LF, Result, LLength, LStartPos);
  278. if LEncodingEndPos <> 0 then begin
  279. Dec(LEncodingEndPos);
  280. end else begin
  281. LEncodingEndPos := LLength;
  282. end;
  283. if ExtractEncoding(Result, LStartPos, LEncodingStartPos, LEncodingEndPos, HeaderCharSet, HeaderEncoding, HeaderData) then
  284. begin
  285. LDecoded := False;
  286. if ExtractEncodedData(HeaderEncoding, HeaderData, Buf) then begin
  287. LDecoded := DecodeHeaderData(HeaderCharSet, Buf, S);
  288. end;
  289. if LDecoded then
  290. begin
  291. //replace old substring in header with decoded string,
  292. // ignoring whitespace that separates encoded words:
  293. if LLastWordWasEncoded then begin
  294. Result := Copy(Result, 1, LLastStartPos - 1) + S + Copy(Result, LEncodingEndPos + 1, MaxInt);
  295. LStartPos := LLastStartPos + Length(S);
  296. end else begin
  297. Result := Copy(Result, 1, LEncodingStartPos - 1) + S + Copy(Result, LEncodingEndPos + 1, MaxInt);
  298. LStartPos := LEncodingStartPos + Length(S);
  299. end;
  300. end else
  301. begin
  302. // could not decode the data, so preserve it in case the user
  303. // wants to do it manually. Though, they really should use the
  304. // IdHeaderCoderBase.GHeaderDecodingNeeded hook for that instead...
  305. LStartPos := LEncodingEndPos + 1;
  306. end;
  307. LLength := Length(Result);
  308. LLastWordWasEncoded := True;
  309. LLastStartPos := LStartPos;
  310. end else
  311. begin
  312. LStartPos := FindFirstOf(LWS+CR+LF, Result, LLength, LStartPos);
  313. if LStartPos = 0 then begin
  314. Break;
  315. end;
  316. LLastWordWasEncoded := False;
  317. end;
  318. end;
  319. end;
  320. procedure DecodeAddress(EMailAddr : TIdEmailAddressItem);
  321. begin
  322. EMailAddr.Name := UnquotedStr(DecodeHeader(EMailAddr.Name));
  323. end;
  324. procedure DecodeAddresses(AEMails : String; EMailAddr: TIdEmailAddressList);
  325. var
  326. idx : Integer;
  327. begin
  328. EMailAddr.EMailAddresses := AEMails;
  329. for idx := 0 to EMailAddr.Count-1 do begin
  330. DecodeAddress(EMailAddr[idx]);
  331. end;
  332. end;
  333. function EncodeAddress(EmailAddr: TIdEMailAddressList; const HeaderEncoding: Char;
  334. const MimeCharSet: string; AUseAddressForNameIfNameMissing: Boolean = False): string;
  335. var
  336. idx : Integer;
  337. begin
  338. if EmailAddr.Count > 0 then begin
  339. Result := EncodeAddressItem(EMailAddr[0], HeaderEncoding, MimeCharSet, AUseAddressForNameIfNameMissing);
  340. for idx := 1 to EmailAddr.Count-1 do begin
  341. Result := Result + ', ' + {Do not Localize}
  342. EncodeAddressItem(EMailAddr[idx], HeaderEncoding, MimeCharSet, AUseAddressForNameIfNameMissing);
  343. end;
  344. end else begin
  345. Result := ''; {Do not Localize}
  346. end;
  347. end;
  348. { encode a header field if non-ASCII characters are used }
  349. function EncodeHeader(const Header: string; Specials: String; const HeaderEncoding: Char;
  350. const MimeCharSet: string): string;
  351. const
  352. SPACES = [Ord(' '), 9, 13, 10]; {Do not Localize}
  353. var
  354. T: string;
  355. Buf: TIdBytes;
  356. L, P, Q, R: Integer;
  357. B0, B1, B2: Integer;
  358. InEncode: Integer;
  359. NeedEncode: Boolean;
  360. csNoEncode, csNoReqQuote, csSpecials: TIdBytes;
  361. BeginEncode, EndEncode: string;
  362. procedure EncodeWord(AP: Integer);
  363. const
  364. MaxEncLen = 75;
  365. var
  366. LQ: Integer;
  367. EncLen: Integer;
  368. Enc1: string;
  369. begin
  370. T := T + BeginEncode;
  371. if L < AP then AP := L + 1;
  372. LQ := InEncode;
  373. InEncode := -1;
  374. EncLen := Length(BeginEncode) + 2;
  375. case PosInStrArray(HeaderEncoding, ['Q', 'B'], False) of {Do not Localize}
  376. 0: begin { quoted-printable }
  377. while LQ < AP do
  378. begin
  379. if Buf[LQ] = Ord(' ') then begin {Do not Localize}
  380. Enc1 := '_'; {Do not Localize}
  381. end
  382. else if (not ByteIsInSet(Buf, LQ, csNoReqQuote)) or ByteIsInSet(Buf, LQ, csSpecials) then begin
  383. Enc1 := '=' + IntToHex(Buf[LQ], 2); {Do not Localize}
  384. end
  385. else begin
  386. Enc1 := Char(Buf[LQ]);
  387. end;
  388. if (EncLen + Length(Enc1)) > MaxEncLen then begin
  389. //T := T + EndEncode + #13#10#9 + BeginEncode;
  390. //CC: The #13#10#9 above caused the subsequent call to FoldWrapText to
  391. //insert an extra #13#10 which, being a blank line in the headers,
  392. //was interpreted by email clients, etc., as the end of the headers
  393. //and the start of the message body. FoldWrapText seems to look for
  394. //and treat correctly the sequence #13#10 + ' ' however...
  395. T := T + EndEncode + EOL + ' ' + BeginEncode;
  396. EncLen := Length(BeginEncode) + 2;
  397. end;
  398. T := T + Enc1;
  399. Inc(EncLen, Length(Enc1));
  400. Inc(LQ);
  401. end;
  402. end;
  403. 1: begin { base64 }
  404. while LQ < AP do begin
  405. if (EncLen + 4) > MaxEncLen then begin
  406. //T := T + EndEncode + #13#10#9 + BeginEncode;
  407. //CC: The #13#10#9 above caused the subsequent call to FoldWrapText to
  408. //insert an extra #13#10 which, being a blank line in the headers,
  409. //was interpreted by email clients, etc., as the end of the headers
  410. //and the start of the message body. FoldWrapText seems to look for
  411. //and treat correctly the sequence #13#10 + ' ' however...
  412. T := T + EndEncode + EOL + ' ' + BeginEncode;
  413. EncLen := Length(BeginEncode) + 2;
  414. end;
  415. B0 := Buf[LQ];
  416. case AP - LQ of
  417. 1:
  418. begin
  419. T := T + base64_tbl[B0 shr 2] + base64_tbl[B0 and $03 shl 4] + '=='; {Do not Localize}
  420. end;
  421. 2:
  422. begin
  423. B1 := Buf[LQ + 1];
  424. T := T + base64_tbl[B0 shr 2] +
  425. base64_tbl[B0 and $03 shl 4 + B1 shr 4] +
  426. base64_tbl[B1 and $0F shl 2] + '='; {Do not Localize}
  427. end;
  428. else
  429. begin
  430. B1 := Buf[LQ + 1];
  431. B2 := Buf[LQ + 2];
  432. T := T + base64_tbl[B0 shr 2] +
  433. base64_tbl[B0 and $03 shl 4 + B1 shr 4] +
  434. base64_tbl[B1 and $0F shl 2 + B2 shr 6] +
  435. base64_tbl[B2 and $3F];
  436. end;
  437. end;
  438. Inc(EncLen, 4);
  439. Inc(LQ, 3);
  440. end;
  441. end;
  442. end;
  443. T := T + EndEncode;
  444. end;
  445. function CreateEncodeRange(AStart, AEnd: Byte): TIdBytes;
  446. var
  447. I: Integer;
  448. begin
  449. SetLength(Result, AEnd-AStart+1);
  450. for I := 0 to Length(Result)-1 do begin
  451. Result[I] := AStart+I;
  452. end;
  453. end;
  454. begin
  455. if Header = '' then begin
  456. Result := '';
  457. Exit;
  458. end;
  459. // TODO: this function needs to take encoded codeunits into account when
  460. // deciding where to split the encoded data between adjacent encoded-words,
  461. // so that a single encoded character does not get split between encoded-words
  462. // thus corrupting that character...
  463. Buf := EncodeHeaderData(MimeCharSet, Header);
  464. {Suggested by Andrew P.Rybin for easy 8bit support}
  465. if HeaderEncoding = '8' then begin {Do not Localize}
  466. Result := BytesToStringRaw(Buf);
  467. Exit;
  468. end;//if
  469. // RLebeau 1/7/09: using Char() for #128-#255 because in D2009, the compiler
  470. // may change characters >= #128 from their Ansi codepage value to their true
  471. // Unicode codepoint value, depending on the codepage used for the source code.
  472. // For instance, #128 may become #$20AC...
  473. // RLebeau 2/12/09: changed the logic to use "no-encode" sets instead, so
  474. // that words containing codeunits outside the ASCII range are always
  475. // encoded. This is easier to manage when Unicode data is involved.
  476. csNoEncode := CreateEncodeRange(32, 126);
  477. csNoReqQuote := CreateEncodeRange(33, 60);
  478. AppendByte(csNoReqQuote, 62);
  479. AppendBytes(csNoReqQuote, CreateEncodeRange(64, 94));
  480. AppendBytes(csNoReqQuote, CreateEncodeRange(96, 126));
  481. csSpecials := ToBytes(Specials, IndyTextEncoding_8Bit);
  482. BeginEncode := '=?' + MimeCharSet + '?' + HeaderEncoding + '?'; {Do not Localize}
  483. EndEncode := '?='; {Do not Localize}
  484. // JMBERG: We want to encode stuff that the user typed
  485. // as if it already is encoded!!
  486. if DecodeHeader(Header) <> Header then begin
  487. RemoveBytes(csNoEncode, 1, ByteIndex(Ord('='), csNoEncode));
  488. end;
  489. L := Length(Buf);
  490. P := 0;
  491. T := ''; {Do not Localize}
  492. InEncode := -1;
  493. while P < L do
  494. begin
  495. Q := P;
  496. while (P < L) and (Buf[P] in SPACES) do begin
  497. Inc(P);
  498. end;
  499. R := P;
  500. NeedEncode := False;
  501. while (P < L) and (not (Buf[P] in SPACES)) do begin
  502. if (not ByteIsInSet(Buf, P, csNoEncode)) or ByteIsInSet(Buf, P, csSpecials) then begin
  503. NeedEncode := True;
  504. end;
  505. Inc(P);
  506. end;
  507. if NeedEncode then begin
  508. if InEncode = -1 then begin
  509. T := T + BytesToString(Buf, Q, R - Q);
  510. InEncode := R;
  511. end;
  512. end else
  513. begin
  514. if InEncode <> -1 then begin
  515. EncodeWord(Q);
  516. end;
  517. T := T + BytesToString(Buf, Q, P - Q);
  518. end;
  519. end;
  520. if InEncode <> -1 then begin
  521. EncodeWord(P);
  522. end;
  523. Result := T;
  524. end;
  525. end.