mimepart.pas 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241
  1. {==============================================================================|
  2. | Project : Ararat Synapse | 002.009.002 |
  3. |==============================================================================|
  4. | Content: MIME support procedures and functions |
  5. |==============================================================================|
  6. | Copyright (c)1999-2021 |
  7. | |
  8. | Redistribution and use in source and binary forms, with or without |
  9. | modification, are permitted provided that the following conditions are met: |
  10. | |
  11. | Redistributions of source code must retain the above copyright notice, this |
  12. | list of conditions and the following disclaimer. |
  13. | |
  14. | Redistributions in binary form must reproduce the above copyright notice, |
  15. | this list of conditions and the following disclaimer in the documentation |
  16. | and/or other materials provided with the distribution. |
  17. | |
  18. | Neither the name of Lukas Gebauer nor the names of its contributors may |
  19. | be used to endorse or promote products derived from this software without |
  20. | specific prior written permission. |
  21. | |
  22. | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
  23. | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
  24. | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
  25. | ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR |
  26. | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
  27. | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
  28. | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
  29. | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
  30. | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
  31. | OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
  32. | DAMAGE. |
  33. |==============================================================================|
  34. | The Initial Developer of the Original Code is Lukas Gebauer (Czech Republic).|
  35. | Portions created by Lukas Gebauer are Copyright (c)2000-2021. |
  36. | Portions created by Petr Fejfar are Copyright (c)2011-2012. |
  37. | All Rights Reserved. |
  38. |==============================================================================|
  39. | Contributor(s): |
  40. |==============================================================================|
  41. | History: see HISTORY.HTM from distribution package |
  42. | (Found at URL: http://www.ararat.cz/synapse/) |
  43. |==============================================================================}
  44. {:@abstract(MIME part handling)
  45. Handling with MIME parts.
  46. Used RFC: RFC-2045
  47. }
  48. {$IFDEF FPC}
  49. {$MODE DELPHI}
  50. {$ENDIF}
  51. {$H+}
  52. {$Q-}
  53. {$R-}
  54. {$M+}
  55. {$IFDEF UNICODE}
  56. {$WARN IMPLICIT_STRING_CAST OFF}
  57. {$WARN IMPLICIT_STRING_CAST_LOSS OFF}
  58. {$ENDIF}
  59. unit mimepart;
  60. interface
  61. uses
  62. SysUtils, Classes,
  63. synafpc,
  64. synachar, synacode, synautil, mimeinln;
  65. type
  66. TMimePart = class;
  67. {:@abstract(Procedural type for @link(TMimepart.Walkpart) hook). This hook is used for
  68. easy walking through MIME subparts.}
  69. THookWalkPart = procedure(const Sender: TMimePart) of object;
  70. {:The four types of MIME parts. (textual, multipart, message or any other
  71. binary data.)}
  72. TMimePrimary = (MP_TEXT, MP_MULTIPART, MP_MESSAGE, MP_BINARY);
  73. {:The various types of possible part encodings.}
  74. TMimeEncoding = (ME_7BIT, ME_8BIT, ME_QUOTED_PRINTABLE,
  75. ME_BASE64, ME_UU, ME_XX);
  76. {:@abstract(Object for working with parts of MIME e-mail.)
  77. Each TMimePart object can handle any number of nested subparts as new
  78. TMimepart objects. It can handle any tree hierarchy structure of nested MIME
  79. subparts itself.
  80. Basic tasks are:
  81. Decoding of MIME message:
  82. - store message into Lines property
  83. - call DecomposeParts. Now you have decomposed MIME parts in all nested levels!
  84. - now you can explore all properties and subparts. (You can use WalkPart method)
  85. - if you need decode part, call DecodePart.
  86. Encoding of MIME message:
  87. - if you need multipart message, you must create subpart by AddSubPart.
  88. - set all properties of all parts.
  89. - set content of part into DecodedLines stream
  90. - encode this stream by EncodePart.
  91. - compose full message by ComposeParts. (it build full MIME message from all subparts. Do not call this method for each subpart! It is needed on root part!)
  92. - encoded MIME message is stored in Lines property.
  93. }
  94. TMimePart = class(TObject)
  95. private
  96. FPrimary: string;
  97. FPrimaryCode: TMimePrimary;
  98. FSecondary: string;
  99. FEncoding: string;
  100. FEncodingCode: TMimeEncoding;
  101. FDefaultCharset: string;
  102. FCharset: string;
  103. FCharsetCode: TMimeChar;
  104. FTargetCharset: TMimeChar;
  105. FDescription: string;
  106. FDisposition: string;
  107. FContentID: string;
  108. FBoundary: string;
  109. FFileName: string;
  110. FLines: TStringList;
  111. FPartBody: TStringList;
  112. FHeaders: TStringList;
  113. FPrePart: TStringList;
  114. FPostPart: TStringList;
  115. FDecodedLines: TMemoryStream;
  116. FSubParts: TList;
  117. FOnWalkPart: THookWalkPart;
  118. FMaxLineLength: integer;
  119. FSubLevel: integer;
  120. FMaxSubLevel: integer;
  121. FAttachInside: boolean;
  122. FConvertCharset: Boolean;
  123. FForcedHTMLConvert: Boolean;
  124. FBinaryDecomposer: boolean;
  125. procedure SetPrimary(Value: string);
  126. procedure SetEncoding(Value: string);
  127. procedure SetCharset(Value: string);
  128. function IsUUcode(Value: string): boolean;
  129. public
  130. constructor Create;
  131. destructor Destroy; override;
  132. {:Assign content of another object to this object. (Only this part,
  133. not subparts!)}
  134. procedure Assign(Value: TMimePart);
  135. {:Assign content of another object to this object. (With all subparts!)}
  136. procedure AssignSubParts(Value: TMimePart);
  137. {:Clear all data values to default values. It also call @link(ClearSubparts).}
  138. procedure Clear;
  139. {:Decode Mime part from @link(Lines) to @link(DecodedLines).}
  140. procedure DecodePart;
  141. {:Parse header lines from Headers property into another properties.}
  142. procedure DecodePartHeader;
  143. {:Encode mime part from @link(DecodedLines) to @link(Lines) and build mime
  144. headers.}
  145. procedure EncodePart;
  146. {:Build header lines in Headers property from another properties.}
  147. procedure EncodePartHeader;
  148. {:generate primary and secondary mime type from filename extension in value.
  149. If type not recognised, it return 'Application/octet-string' type.}
  150. procedure MimeTypeFromExt(Value: string);
  151. {:Return number of decomposed subparts. (On this level! Each of this
  152. subparts can hold any number of their own nested subparts!)}
  153. function GetSubPartCount: integer;
  154. {:Get nested subpart object as new TMimePart. For getting maximum possible
  155. index you can use @link(GetSubPartCount) method.}
  156. function GetSubPart(index: integer): TMimePart;
  157. {:delete subpart on given index.}
  158. procedure DeleteSubPart(index: integer);
  159. {:Clear and destroy all subpart TMimePart objects.}
  160. procedure ClearSubParts;
  161. {:Add and create new subpart.}
  162. function AddSubPart: TMimePart;
  163. {:E-mail message in @link(Lines) property is parsed into this object.
  164. E-mail headers are stored in @link(Headers) property and is parsed into
  165. another properties automaticly. Not need call @link(DecodePartHeader)!
  166. Content of message (part) is stored into @link(PartBody) property. This
  167. part is in undecoded form! If you need decode it, then you must call
  168. @link(DecodePart) method by your hands. Lot of another properties is filled
  169. also.
  170. Decoding of parts you must call separately due performance reasons. (Not
  171. needed to decode all parts in all reasons.)
  172. For each MIME subpart is created new TMimepart object (accessible via
  173. method @link(GetSubPart)).}
  174. procedure DecomposeParts;
  175. {pf}
  176. {: HTTP message is received by @link(THTTPSend) component in two parts:
  177. headers are stored in @link(THTTPSend.Headers) and a body in memory stream
  178. @link(THTTPSend.Document).
  179. On the top of it, HTTP connections are always 8-bit, hence data are
  180. transferred in native format i.e. no transfer encoding is applied.
  181. This method operates the similiar way and produces the same
  182. result as @link(DecomposeParts).
  183. }
  184. procedure DecomposePartsBinary(AHeader:TStrings; AStx,AEtx:PChar);
  185. {/pf}
  186. {:This part and all subparts is composed into one MIME message stored in
  187. @link(Lines) property.}
  188. procedure ComposeParts;
  189. {:By calling this method is called @link(OnWalkPart) event for each part
  190. and their subparts. It is very good for calling some code for each part in
  191. MIME message}
  192. procedure WalkPart;
  193. {:Return @true when is possible create next subpart. (@link(maxSublevel)
  194. is still not reached)}
  195. function CanSubPart: boolean;
  196. published
  197. {:Primary Mime type of part. (i.e. 'application') Writing to this property
  198. automaticly generate value of @link(PrimaryCode).}
  199. property Primary: string read FPrimary write SetPrimary;
  200. {:String representation of used Mime encoding in part. (i.e. 'base64')
  201. Writing to this property automaticly generate value of @link(EncodingCode).}
  202. property Encoding: string read FEncoding write SetEncoding;
  203. {:String representation of used Mime charset in part. (i.e. 'iso-8859-1')
  204. Writing to this property automaticly generate value of @link(CharsetCode).
  205. Charset is used only for text parts.}
  206. property Charset: string read FCharset write SetCharset;
  207. {:Define default charset for decoding text MIME parts without charset
  208. specification. Default value is 'ISO-8859-1' by RCF documents.
  209. But Microsoft Outlook use windows codings as default. This property allows
  210. properly decode textual parts from some broken versions of Microsoft
  211. Outlook. (this is bad software!)}
  212. property DefaultCharset: string read FDefaultCharset write FDefaultCharset;
  213. {:Decoded primary type. Possible values are: MP_TEXT, MP_MULTIPART,
  214. MP_MESSAGE and MP_BINARY. If type not recognised, result is MP_BINARY.}
  215. property PrimaryCode: TMimePrimary read FPrimaryCode Write FPrimaryCode;
  216. {:Decoded encoding type. Possible values are: ME_7BIT, ME_8BIT,
  217. ME_QUOTED_PRINTABLE and ME_BASE64. If type not recognised, result is
  218. ME_7BIT.}
  219. property EncodingCode: TMimeEncoding read FEncodingCode Write FEncodingCode;
  220. {:Decoded charset type. Possible values are defined in @link(SynaChar) unit.}
  221. property CharsetCode: TMimeChar read FCharsetCode Write FCharsetCode;
  222. {:System charset type. Default value is charset used by default in your
  223. operating system.}
  224. property TargetCharset: TMimeChar read FTargetCharset Write FTargetCharset;
  225. {:If @true, then do internal charset translation of part content between @link(CharsetCode)
  226. and @link(TargetCharset)}
  227. property ConvertCharset: Boolean read FConvertCharset Write FConvertCharset;
  228. {:If @true, then allways do internal charset translation of HTML parts
  229. by MIME even it have their own charset in META tag. Default is @false.}
  230. property ForcedHTMLConvert: Boolean read FForcedHTMLConvert Write FForcedHTMLConvert;
  231. {:Secondary Mime type of part. (i.e. 'mixed')}
  232. property Secondary: string read FSecondary Write FSecondary;
  233. {:Description of Mime part.}
  234. property Description: string read FDescription Write FDescription;
  235. {:Value of content disposition field. (i.e. 'inline' or 'attachment')}
  236. property Disposition: string read FDisposition Write FDisposition;
  237. {:Content ID.}
  238. property ContentID: string read FContentID Write FContentID;
  239. {:Boundary delimiter of multipart Mime part. Used only in multipart part.}
  240. property Boundary: string read FBoundary Write FBoundary;
  241. {:Filename of file in binary part.}
  242. property FileName: string read FFileName Write FFileName;
  243. {:String list with lines contains mime part (It can be a full message).}
  244. property Lines: TStringList read FLines;
  245. {:Encoded form of MIME part data.}
  246. property PartBody: TStringList read FPartBody;
  247. {:All header lines of MIME part.}
  248. property Headers: TStringList read FHeaders;
  249. {:On multipart this contains part of message between first line of message
  250. and first boundary.}
  251. property PrePart: TStringList read FPrePart;
  252. {:On multipart this contains part of message between last boundary and end
  253. of message.}
  254. property PostPart: TStringList read FPostPart;
  255. {:Stream with decoded form of budy part.}
  256. property DecodedLines: TMemoryStream read FDecodedLines;
  257. {:Show nested level in subpart tree. Value 0 means root part. 1 means
  258. subpart from this root. etc.}
  259. property SubLevel: integer read FSubLevel write FSubLevel;
  260. {:Specify maximum sublevel value for decomposing.}
  261. property MaxSubLevel: integer read FMaxSubLevel write FMaxSubLevel;
  262. {:When is @true, then this part maybe(!) have included some uuencoded binary
  263. data.}
  264. property AttachInside: boolean read FAttachInside;
  265. {:Here you can assign hook procedure for walking through all part and their
  266. subparts.}
  267. property OnWalkPart: THookWalkPart read FOnWalkPart write FOnWalkPart;
  268. {:Here you can specify maximum line length for encoding of MIME part.
  269. If line is longer, then is splitted by standard of MIME. Correct MIME
  270. mailers can de-split this line into original length.}
  271. property MaxLineLength: integer read FMaxLineLength Write FMaxLineLength;
  272. end;
  273. const
  274. MaxMimeType = 25;
  275. MimeType: array[0..MaxMimeType, 0..2] of string =
  276. (
  277. ('AU', 'audio', 'basic'),
  278. ('AVI', 'video', 'x-msvideo'),
  279. ('BMP', 'image', 'BMP'),
  280. ('DOC', 'application', 'MSWord'),
  281. ('EPS', 'application', 'Postscript'),
  282. ('GIF', 'image', 'GIF'),
  283. ('JPEG', 'image', 'JPEG'),
  284. ('JPG', 'image', 'JPEG'),
  285. ('MID', 'audio', 'midi'),
  286. ('MOV', 'video', 'quicktime'),
  287. ('MPEG', 'video', 'MPEG'),
  288. ('MPG', 'video', 'MPEG'),
  289. ('MP2', 'audio', 'mpeg'),
  290. ('MP3', 'audio', 'mpeg'),
  291. ('PDF', 'application', 'PDF'),
  292. ('PNG', 'image', 'PNG'),
  293. ('PS', 'application', 'Postscript'),
  294. ('QT', 'video', 'quicktime'),
  295. ('RA', 'audio', 'x-realaudio'),
  296. ('RTF', 'application', 'RTF'),
  297. ('SND', 'audio', 'basic'),
  298. ('TIF', 'image', 'TIFF'),
  299. ('TIFF', 'image', 'TIFF'),
  300. ('WAV', 'audio', 'x-wav'),
  301. ('WPD', 'application', 'Wordperfect5.1'),
  302. ('ZIP', 'application', 'ZIP')
  303. );
  304. {:Generates a unique boundary string.}
  305. function GenerateBoundary: string;
  306. {:Generates a stringlist that does not write a BOM character.}
  307. Function CreateStringList : TStringList;
  308. implementation
  309. {==============================================================================}
  310. constructor TMIMEPart.Create;
  311. begin
  312. inherited Create;
  313. FOnWalkPart := nil;
  314. FLines := CreateStringList;
  315. FPartBody := CreateStringList;
  316. FHeaders := CreateStringList;
  317. FPrePart := CreateStringList;
  318. FPostPart := CreateStringList;
  319. FDecodedLines := TMemoryStream.Create;
  320. FSubParts := TList.Create;
  321. FTargetCharset := GetCurCP;
  322. //was 'US-ASCII' before, but RFC-ignorant Outlook sometimes using default
  323. //system charset instead.
  324. FDefaultCharset := GetIDFromCP(GetCurCP);
  325. FMaxLineLength := 78;
  326. FSubLevel := 0;
  327. FMaxSubLevel := -1;
  328. FAttachInside := false;
  329. FConvertCharset := true;
  330. FForcedHTMLConvert := false;
  331. end;
  332. destructor TMIMEPart.Destroy;
  333. begin
  334. ClearSubParts;
  335. FSubParts.Free;
  336. FDecodedLines.Free;
  337. FPartBody.Free;
  338. FLines.Free;
  339. FHeaders.Free;
  340. FPrePart.Free;
  341. FPostPart.Free;
  342. inherited Destroy;
  343. end;
  344. {==============================================================================}
  345. procedure TMIMEPart.Clear;
  346. begin
  347. FPrimary := '';
  348. FEncoding := '';
  349. FCharset := '';
  350. FPrimaryCode := MP_TEXT;
  351. FEncodingCode := ME_7BIT;
  352. FCharsetCode := ISO_8859_1;
  353. FTargetCharset := GetCurCP;
  354. FSecondary := '';
  355. FDisposition := '';
  356. FContentID := '';
  357. FDescription := '';
  358. FBoundary := '';
  359. FFileName := '';
  360. FAttachInside := False;
  361. FPartBody.Clear;
  362. FHeaders.Clear;
  363. FPrePart.Clear;
  364. FPostPart.Clear;
  365. FDecodedLines.Clear;
  366. FConvertCharset := true;
  367. FForcedHTMLConvert := false;
  368. ClearSubParts;
  369. end;
  370. {==============================================================================}
  371. procedure TMIMEPart.Assign(Value: TMimePart);
  372. begin
  373. Primary := Value.Primary;
  374. Encoding := Value.Encoding;
  375. Charset := Value.Charset;
  376. DefaultCharset := Value.DefaultCharset;
  377. PrimaryCode := Value.PrimaryCode;
  378. EncodingCode := Value.EncodingCode;
  379. CharsetCode := Value.CharsetCode;
  380. TargetCharset := Value.TargetCharset;
  381. Secondary := Value.Secondary;
  382. Description := Value.Description;
  383. Disposition := Value.Disposition;
  384. ContentID := Value.ContentID;
  385. Boundary := Value.Boundary;
  386. FileName := Value.FileName;
  387. Lines.Assign(Value.Lines);
  388. PartBody.Assign(Value.PartBody);
  389. Headers.Assign(Value.Headers);
  390. PrePart.Assign(Value.PrePart);
  391. PostPart.Assign(Value.PostPart);
  392. MaxLineLength := Value.MaxLineLength;
  393. FAttachInside := Value.AttachInside;
  394. FConvertCharset := Value.ConvertCharset;
  395. end;
  396. {==============================================================================}
  397. procedure TMIMEPart.AssignSubParts(Value: TMimePart);
  398. var
  399. n: integer;
  400. p: TMimePart;
  401. begin
  402. Assign(Value);
  403. for n := 0 to Value.GetSubPartCount - 1 do
  404. begin
  405. p := AddSubPart;
  406. p.AssignSubParts(Value.GetSubPart(n));
  407. end;
  408. end;
  409. {==============================================================================}
  410. function TMIMEPart.GetSubPartCount: integer;
  411. begin
  412. Result := FSubParts.Count;
  413. end;
  414. {==============================================================================}
  415. function TMIMEPart.GetSubPart(index: integer): TMimePart;
  416. begin
  417. Result := nil;
  418. if Index < GetSubPartCount then
  419. Result := TMimePart(FSubParts[Index]);
  420. end;
  421. {==============================================================================}
  422. procedure TMIMEPart.DeleteSubPart(index: integer);
  423. begin
  424. if Index < GetSubPartCount then
  425. begin
  426. GetSubPart(Index).Free;
  427. FSubParts.Delete(Index);
  428. end;
  429. end;
  430. {==============================================================================}
  431. procedure TMIMEPart.ClearSubParts;
  432. var
  433. n: integer;
  434. begin
  435. for n := 0 to GetSubPartCount - 1 do
  436. TMimePart(FSubParts[n]).Free;
  437. FSubParts.Clear;
  438. end;
  439. {==============================================================================}
  440. function TMIMEPart.AddSubPart: TMimePart;
  441. begin
  442. Result := TMimePart.Create;
  443. Result.DefaultCharset := FDefaultCharset;
  444. FSubParts.Add(Result);
  445. Result.SubLevel := FSubLevel + 1;
  446. Result.MaxSubLevel := FMaxSubLevel;
  447. end;
  448. {==============================================================================}
  449. procedure TMIMEPart.DecomposeParts;
  450. var
  451. x: integer;
  452. s: string;
  453. Mime: TMimePart;
  454. procedure SkipEmpty;
  455. begin
  456. while FLines.Count > x do
  457. begin
  458. s := TrimRight(FLines[x]);
  459. if s <> '' then
  460. Break;
  461. Inc(x);
  462. end;
  463. end;
  464. begin
  465. FBinaryDecomposer := false;
  466. x := 0;
  467. Clear;
  468. //extract headers
  469. while FLines.Count > x do
  470. begin
  471. s := NormalizeHeader(FLines, x);
  472. if s = '' then
  473. Break;
  474. FHeaders.Add(s);
  475. end;
  476. DecodePartHeader;
  477. //extract prepart
  478. if FPrimaryCode = MP_MULTIPART then
  479. begin
  480. while FLines.Count > x do
  481. begin
  482. s := FLines[x];
  483. Inc(x);
  484. if TrimRight(s) = '--' + FBoundary then
  485. Break;
  486. FPrePart.Add(s);
  487. if not FAttachInside then
  488. FAttachInside := IsUUcode(s);
  489. end;
  490. end;
  491. //extract body part
  492. if FPrimaryCode = MP_MULTIPART then
  493. begin
  494. repeat
  495. if CanSubPart then
  496. begin
  497. Mime := AddSubPart;
  498. while FLines.Count > x do
  499. begin
  500. s := FLines[x];
  501. Inc(x);
  502. if Pos('--' + FBoundary, s) = 1 then
  503. Break;
  504. Mime.Lines.Add(s);
  505. end;
  506. Mime.DecomposeParts;
  507. end
  508. else
  509. begin
  510. s := FLines[x];
  511. Inc(x);
  512. FPartBody.Add(s);
  513. end;
  514. if x >= FLines.Count then
  515. break;
  516. until s = '--' + FBoundary + '--';
  517. end;
  518. if (FPrimaryCode = MP_MESSAGE) and CanSubPart then
  519. begin
  520. Mime := AddSubPart;
  521. SkipEmpty;
  522. while FLines.Count > x do
  523. begin
  524. s := TrimRight(FLines[x]);
  525. Inc(x);
  526. Mime.Lines.Add(s);
  527. end;
  528. Mime.DecomposeParts;
  529. end
  530. else
  531. begin
  532. while FLines.Count > x do
  533. begin
  534. s := FLines[x];
  535. Inc(x);
  536. FPartBody.Add(s);
  537. if not FAttachInside then
  538. FAttachInside := IsUUcode(s);
  539. end;
  540. end;
  541. //extract postpart
  542. if FPrimaryCode = MP_MULTIPART then
  543. begin
  544. while FLines.Count > x do
  545. begin
  546. s := TrimRight(FLines[x]);
  547. Inc(x);
  548. FPostPart.Add(s);
  549. if not FAttachInside then
  550. FAttachInside := IsUUcode(s);
  551. end;
  552. end;
  553. end;
  554. procedure TMIMEPart.DecomposePartsBinary(AHeader:TStrings; AStx,AEtx:PChar);
  555. var
  556. x: integer;
  557. s: ANSIString;
  558. Mime: TMimePart;
  559. BOP: PChar; // Beginning of Part
  560. EOP: PChar; // End of Part
  561. function ___HasUUCode(ALines:TStrings): boolean;
  562. var
  563. x: integer;
  564. begin
  565. Result := FALSE;
  566. for x:=0 to ALines.Count-1 do
  567. if IsUUcode(ALInes[x]) then
  568. begin
  569. Result := TRUE;
  570. exit;
  571. end;
  572. end;
  573. begin
  574. FBinaryDecomposer := true;
  575. Clear;
  576. // Parse passed headers (THTTPSend returns HTTP headers and body separately)
  577. x := 0;
  578. while x<AHeader.Count do
  579. begin
  580. s := NormalizeHeader(AHeader,x);
  581. if s = '' then
  582. Break;
  583. FHeaders.Add(s);
  584. end;
  585. DecodePartHeader;
  586. // Extract prepart
  587. if FPrimaryCode=MP_MULTIPART then
  588. begin
  589. CopyLinesFromStreamUntilBoundary(AStx,AEtx,FPrePart,FBoundary);
  590. FAttachInside := FAttachInside or ___HasUUCode(FPrePart);
  591. end;
  592. // Extract body part
  593. if FPrimaryCode=MP_MULTIPART then
  594. begin
  595. repeat
  596. if CanSubPart then
  597. begin
  598. Mime := AddSubPart;
  599. BOP := AStx;
  600. EOP := SearchForBoundary(AStx,AEtx,FBoundary);
  601. CopyLinesFromStreamUntilNullLine(BOP,EOP,Mime.Lines);
  602. Mime.DecomposePartsBinary(Mime.Lines,BOP,EOP);
  603. end
  604. else
  605. begin
  606. EOP := SearchForBoundary(AStx,AEtx,FBoundary);
  607. FPartBody.Add(BuildStringFromBuffer(AStx,EOP));
  608. end;
  609. //
  610. BOP := MatchLastBoundary(EOP,AEtx,FBoundary);
  611. if Assigned(BOP) then
  612. begin
  613. AStx := BOP;
  614. Break;
  615. end;
  616. until FALSE;
  617. end;
  618. // Extract nested MIME message
  619. if (FPrimaryCode=MP_MESSAGE) and CanSubPart then
  620. begin
  621. Mime := AddSubPart;
  622. SkipNullLines(AStx,AEtx);
  623. CopyLinesFromStreamUntilNullLine(AStx,AEtx,Mime.Lines);
  624. Mime.DecomposePartsBinary(Mime.Lines,AStx,AEtx);
  625. end
  626. // Extract body of single part
  627. else
  628. begin
  629. FPartBody.Add(BuildStringFromBuffer(AStx,AEtx));
  630. FAttachInside := FAttachInside or ___HasUUCode(FPartBody);
  631. end;
  632. // Extract postpart
  633. if FPrimaryCode=MP_MULTIPART then
  634. begin
  635. CopyLinesFromStreamUntilBoundary(AStx,AEtx,FPostPart,'');
  636. FAttachInside := FAttachInside or ___HasUUCode(FPostPart);
  637. end;
  638. end;
  639. {/pf}
  640. {==============================================================================}
  641. procedure TMIMEPart.ComposeParts;
  642. var
  643. n: integer;
  644. mime: TMimePart;
  645. s, t: string;
  646. d1, d2, d3: integer;
  647. x: integer;
  648. begin
  649. FLines.Clear;
  650. //add headers
  651. for n := 0 to FHeaders.Count -1 do
  652. begin
  653. s := FHeaders[n];
  654. repeat
  655. if Length(s) < FMaxLineLength then
  656. begin
  657. t := s;
  658. s := '';
  659. end
  660. else
  661. begin
  662. d1 := RPosEx('; ', s, FMaxLineLength);
  663. d2 := RPosEx(' ', s, FMaxLineLength);
  664. d3 := RPosEx(', ', s, FMaxLineLength);
  665. if (d1 <= 1) and (d2 <= 1) and (d3 <= 1) then
  666. begin
  667. x := Pos(' ', Copy(s, 2, Length(s) - 1));
  668. if x < 1 then
  669. x := Length(s);
  670. end
  671. else
  672. if d1 > 0 then
  673. x := d1
  674. else
  675. if d3 > 0 then
  676. x := d3
  677. else
  678. x := d2 - 1;
  679. t := Copy(s, 1, x);
  680. Delete(s, 1, x);
  681. end;
  682. Flines.Add(t);
  683. until s = '';
  684. end;
  685. Flines.Add('');
  686. //add body
  687. //if multipart
  688. if FPrimaryCode = MP_MULTIPART then
  689. begin
  690. Flines.AddStrings(FPrePart);
  691. for n := 0 to GetSubPartCount - 1 do
  692. begin
  693. Flines.Add('--' + FBoundary);
  694. mime := GetSubPart(n);
  695. mime.ComposeParts;
  696. FLines.AddStrings(mime.Lines);
  697. end;
  698. Flines.Add('--' + FBoundary + '--');
  699. Flines.AddStrings(FPostPart);
  700. end;
  701. //if message
  702. if FPrimaryCode = MP_MESSAGE then
  703. begin
  704. if GetSubPartCount > 0 then
  705. begin
  706. mime := GetSubPart(0);
  707. mime.ComposeParts;
  708. FLines.AddStrings(mime.Lines);
  709. end;
  710. end
  711. else
  712. //if normal part
  713. begin
  714. FLines.AddStrings(FPartBody);
  715. end;
  716. end;
  717. {==============================================================================}
  718. procedure TMIMEPart.DecodePart;
  719. var
  720. n: Integer;
  721. s, t, t2: string;
  722. b: Boolean;
  723. begin
  724. FDecodedLines.Clear;
  725. {pf}
  726. // The part decomposer passes data via TStringList which appends trailing line
  727. // break inherently. But in a case of native 8-bit data transferred withouth
  728. // encoding (default e.g. for HTTP protocol), the redundant line terminators
  729. // has to be removed
  730. if FBinaryDecomposer and (FPartBody.Count=1) then
  731. begin
  732. case FEncodingCode of
  733. ME_QUOTED_PRINTABLE:
  734. s := DecodeQuotedPrintable(FPartBody[0]);
  735. ME_BASE64:
  736. s := DecodeBase64(FPartBody[0]);
  737. ME_UU, ME_XX:
  738. begin
  739. s := '';
  740. for n := 0 to FPartBody.Count - 1 do
  741. if FEncodingCode = ME_UU then
  742. s := s + DecodeUU(FPartBody[n])
  743. else
  744. s := s + DecodeXX(FPartBody[n]);
  745. end;
  746. else
  747. s := FPartBody[0];
  748. end;
  749. end
  750. else
  751. {/pf}
  752. case FEncodingCode of
  753. ME_QUOTED_PRINTABLE:
  754. s := DecodeQuotedPrintable(FPartBody.Text);
  755. ME_BASE64:
  756. s := DecodeBase64(FPartBody.Text);
  757. ME_UU, ME_XX:
  758. begin
  759. s := '';
  760. for n := 0 to FPartBody.Count - 1 do
  761. if FEncodingCode = ME_UU then
  762. s := s + DecodeUU(FPartBody[n])
  763. else
  764. s := s + DecodeXX(FPartBody[n]);
  765. end;
  766. else
  767. s := FPartBody.Text;
  768. end;
  769. if FConvertCharset and (FPrimaryCode = MP_TEXT) then
  770. if (not FForcedHTMLConvert) and (uppercase(FSecondary) = 'HTML') then
  771. begin
  772. b := false;
  773. t2 := uppercase(s);
  774. t := SeparateLeft(t2, '</HEAD>');
  775. if length(t) <> length(s) then
  776. begin
  777. t := SeparateRight(t, '<HEAD>');
  778. t := ReplaceString(t, '"', '');
  779. t := ReplaceString(t, ' ', '');
  780. b := Pos('HTTP-EQUIV=CONTENT-TYPE', t) > 0;
  781. end;
  782. //workaround for shitty M$ Outlook 11 which is placing this information
  783. //outside <head> section
  784. if not b then
  785. begin
  786. t := Copy(t2, 1, 2048);
  787. t := ReplaceString(t, '"', '');
  788. t := ReplaceString(t, ' ', '');
  789. b := Pos('HTTP-EQUIV=CONTENT-TYPE', t) > 0;
  790. end;
  791. if not b then
  792. s := CharsetConversion(s, FCharsetCode, FTargetCharset);
  793. end
  794. else
  795. s := CharsetConversion(s, FCharsetCode, FTargetCharset);
  796. WriteStrToStream(FDecodedLines, s);
  797. FDecodedLines.Position := 0;
  798. end;
  799. {==============================================================================}
  800. procedure TMIMEPart.DecodePartHeader;
  801. var
  802. n: integer;
  803. s, su, fn: string;
  804. st, st2: string;
  805. begin
  806. Primary := 'text';
  807. FSecondary := 'plain';
  808. FDescription := '';
  809. Charset := FDefaultCharset;
  810. FFileName := '';
  811. //was 7bit before, but this is more compatible with RFC-ignorant outlook
  812. Encoding := '8BIT';
  813. FDisposition := '';
  814. FContentID := '';
  815. fn := '';
  816. for n := 0 to FHeaders.Count - 1 do
  817. if FHeaders[n] <> '' then
  818. begin
  819. s := FHeaders[n];
  820. su := UpperCase(s);
  821. if Pos('CONTENT-TYPE:', su) = 1 then
  822. begin
  823. st := Trim(SeparateRight(su, ':'));
  824. st2 := Trim(SeparateLeft(st, ';'));
  825. Primary := Trim(SeparateLeft(st2, '/'));
  826. FSecondary := Trim(SeparateRight(st2, '/'));
  827. if (FSecondary = Primary) and (Pos('/', st2) < 1) then
  828. FSecondary := '';
  829. case FPrimaryCode of
  830. MP_TEXT:
  831. begin
  832. Charset := UpperCase(GetParameter(s, 'charset'));
  833. FFileName := GetParameter(s, 'name');
  834. end;
  835. MP_MULTIPART:
  836. FBoundary := GetParameter(s, 'Boundary');
  837. MP_MESSAGE:
  838. begin
  839. end;
  840. MP_BINARY:
  841. FFileName := GetParameter(s, 'name');
  842. end;
  843. end;
  844. if Pos('CONTENT-TRANSFER-ENCODING:', su) = 1 then
  845. Encoding := Trim(SeparateRight(su, ':'));
  846. if Pos('CONTENT-DESCRIPTION:', su) = 1 then
  847. FDescription := Trim(SeparateRight(s, ':'));
  848. if Pos('CONTENT-DISPOSITION:', su) = 1 then
  849. begin
  850. FDisposition := SeparateRight(su, ':');
  851. FDisposition := Trim(SeparateLeft(FDisposition, ';'));
  852. fn := GetParameter(s, 'FileName');
  853. end;
  854. if Pos('CONTENT-ID:', su) = 1 then
  855. FContentID := Trim(SeparateRight(s, ':'));
  856. end;
  857. if fn <> '' then
  858. FFileName := fn;
  859. FFileName := InlineDecode(FFileName, FTargetCharset);
  860. FFileName := ExtractFileName(FFileName);
  861. end;
  862. {==============================================================================}
  863. procedure TMIMEPart.EncodePart;
  864. var
  865. l: TStringList;
  866. {$IFDEF UNICODE}
  867. s, t: RawByteString;
  868. {$ELSE}
  869. s, t: string;
  870. {$ENDIF}
  871. n, x: Integer;
  872. d1, d2: integer;
  873. begin
  874. if (FEncodingCode = ME_UU) or (FEncodingCode = ME_XX) then
  875. Encoding := 'base64';
  876. l := CreateStringList;
  877. FPartBody.Clear;
  878. FDecodedLines.Position := 0;
  879. try
  880. case FPrimaryCode of
  881. MP_MULTIPART, MP_MESSAGE:
  882. FPartBody.LoadFromStream(FDecodedLines);
  883. MP_TEXT, MP_BINARY:
  884. begin
  885. s := ReadStrFromStream(FDecodedLines, FDecodedLines.Size);
  886. if FConvertCharset and (FPrimaryCode = MP_TEXT) and (FEncodingCode <> ME_7BIT) then
  887. s := GetBOM(FCharSetCode) + CharsetConversion(s, FTargetCharset, FCharsetCode);
  888. if FEncodingCode = ME_BASE64 then
  889. begin
  890. x := 1;
  891. while x <= length(s) do
  892. begin
  893. t := copy(s, x, 54);
  894. x := x + length(t);
  895. t := EncodeBase64(t);
  896. FPartBody.Add(t);
  897. end;
  898. end
  899. else
  900. begin
  901. if FPrimaryCode = MP_BINARY then
  902. l.Add(s)
  903. else
  904. l.Text := s;
  905. for n := 0 to l.Count - 1 do
  906. begin
  907. s := l[n];
  908. if FEncodingCode = ME_QUOTED_PRINTABLE then
  909. begin
  910. s := EncodeQuotedPrintable(s);
  911. repeat
  912. if Length(s) < FMaxLineLength then
  913. begin
  914. t := s;
  915. s := '';
  916. end
  917. else
  918. begin
  919. d1 := RPosEx('=', s, FMaxLineLength);
  920. d2 := RPosEx(' ', s, FMaxLineLength);
  921. if (d1 = 0) and (d2 = 0) then
  922. x := FMaxLineLength
  923. else
  924. if d1 > d2 then
  925. x := d1 - 1
  926. else
  927. x := d2 - 1;
  928. if x = 0 then
  929. x := FMaxLineLength;
  930. t := Copy(s, 1, x);
  931. Delete(s, 1, x);
  932. if s <> '' then
  933. t := t + '=';
  934. end;
  935. FPartBody.Add(t);
  936. until s = '';
  937. end
  938. else
  939. FPartBody.Add(s);
  940. end;
  941. if (FPrimaryCode = MP_BINARY)
  942. and (FEncodingCode = ME_QUOTED_PRINTABLE) then
  943. FPartBody[FPartBody.Count - 1] := FPartBody[FPartBody.Count - 1] + '=';
  944. end;
  945. end;
  946. end;
  947. finally
  948. l.Free;
  949. end;
  950. end;
  951. {==============================================================================}
  952. procedure TMIMEPart.EncodePartHeader;
  953. var
  954. s: string;
  955. begin
  956. FHeaders.Clear;
  957. if FSecondary = '' then
  958. case FPrimaryCode of
  959. MP_TEXT:
  960. FSecondary := 'plain';
  961. MP_MULTIPART:
  962. FSecondary := 'mixed';
  963. MP_MESSAGE:
  964. FSecondary := 'rfc822';
  965. MP_BINARY:
  966. FSecondary := 'octet-stream';
  967. end;
  968. if FDescription <> '' then
  969. FHeaders.Insert(0, 'Content-Description: ' + FDescription);
  970. if FDisposition <> '' then
  971. begin
  972. s := '';
  973. if FFileName <> '' then
  974. s := '; FileName=' + QuoteStr(InlineCodeEx(FileName, FTargetCharset), '"');
  975. FHeaders.Insert(0, 'Content-Disposition: ' + LowerCase(FDisposition) + s);
  976. end;
  977. if FContentID <> '' then
  978. FHeaders.Insert(0, 'Content-ID: <' + FContentID + '>');
  979. case FEncodingCode of
  980. ME_7BIT:
  981. s := '7bit';
  982. ME_8BIT:
  983. s := '8bit';
  984. ME_QUOTED_PRINTABLE:
  985. s := 'Quoted-printable';
  986. ME_BASE64:
  987. s := 'Base64';
  988. end;
  989. case FPrimaryCode of
  990. MP_TEXT,
  991. MP_BINARY: FHeaders.Insert(0, 'Content-Transfer-Encoding: ' + s);
  992. end;
  993. case FPrimaryCode of
  994. MP_TEXT:
  995. s := FPrimary + '/' + FSecondary + '; charset=' + GetIDfromCP(FCharsetCode);
  996. MP_MULTIPART:
  997. s := FPrimary + '/' + FSecondary + '; boundary="' + FBoundary + '"';
  998. MP_MESSAGE, MP_BINARY:
  999. s := FPrimary + '/' + FSecondary;
  1000. end;
  1001. if FFileName <> '' then
  1002. s := s + '; name=' + QuoteStr(InlineCodeEx(FileName, FTargetCharset), '"');
  1003. FHeaders.Insert(0, 'Content-type: ' + s);
  1004. end;
  1005. {==============================================================================}
  1006. procedure TMIMEPart.MimeTypeFromExt(Value: string);
  1007. var
  1008. s: string;
  1009. n: Integer;
  1010. begin
  1011. Primary := '';
  1012. FSecondary := '';
  1013. s := UpperCase(ExtractFileExt(Value));
  1014. if s = '' then
  1015. s := UpperCase(Value);
  1016. s := SeparateRight(s, '.');
  1017. for n := 0 to MaxMimeType do
  1018. if MimeType[n, 0] = s then
  1019. begin
  1020. Primary := MimeType[n, 1];
  1021. FSecondary := MimeType[n, 2];
  1022. Break;
  1023. end;
  1024. if Primary = '' then
  1025. Primary := 'application';
  1026. if FSecondary = '' then
  1027. FSecondary := 'octet-stream';
  1028. end;
  1029. {==============================================================================}
  1030. procedure TMIMEPart.WalkPart;
  1031. var
  1032. n: integer;
  1033. m: TMimepart;
  1034. begin
  1035. if assigned(OnWalkPart) then
  1036. begin
  1037. OnWalkPart(self);
  1038. for n := 0 to GetSubPartCount - 1 do
  1039. begin
  1040. m := GetSubPart(n);
  1041. m.OnWalkPart := OnWalkPart;
  1042. m.WalkPart;
  1043. end;
  1044. end;
  1045. end;
  1046. {==============================================================================}
  1047. procedure TMIMEPart.SetPrimary(Value: string);
  1048. var
  1049. s: string;
  1050. begin
  1051. FPrimary := Value;
  1052. s := UpperCase(Value);
  1053. FPrimaryCode := MP_BINARY;
  1054. if Pos('TEXT', s) = 1 then
  1055. FPrimaryCode := MP_TEXT;
  1056. if Pos('MULTIPART', s) = 1 then
  1057. FPrimaryCode := MP_MULTIPART;
  1058. if Pos('MESSAGE', s) = 1 then
  1059. FPrimaryCode := MP_MESSAGE;
  1060. end;
  1061. procedure TMIMEPart.SetEncoding(Value: string);
  1062. var
  1063. s: string;
  1064. begin
  1065. FEncoding := Value;
  1066. s := UpperCase(Value);
  1067. FEncodingCode := ME_7BIT;
  1068. if Pos('8BIT', s) = 1 then
  1069. FEncodingCode := ME_8BIT;
  1070. if Pos('QUOTED-PRINTABLE', s) = 1 then
  1071. FEncodingCode := ME_QUOTED_PRINTABLE;
  1072. if Pos('BASE64', s) = 1 then
  1073. FEncodingCode := ME_BASE64;
  1074. if Pos('X-UU', s) = 1 then
  1075. FEncodingCode := ME_UU;
  1076. if Pos('X-XX', s) = 1 then
  1077. FEncodingCode := ME_XX;
  1078. end;
  1079. procedure TMIMEPart.SetCharset(Value: string);
  1080. begin
  1081. if value <> '' then
  1082. begin
  1083. FCharset := Value;
  1084. FCharsetCode := GetCPFromID(Value);
  1085. end;
  1086. end;
  1087. function TMIMEPart.CanSubPart: boolean;
  1088. begin
  1089. Result := True;
  1090. if FMaxSubLevel <> -1 then
  1091. Result := FMaxSubLevel > FSubLevel;
  1092. end;
  1093. function TMIMEPart.IsUUcode(Value: string): boolean;
  1094. begin
  1095. Value := UpperCase(Value);
  1096. Result := (pos('BEGIN ', Value) = 1) and (Trim(SeparateRight(Value, ' ')) <> '');
  1097. end;
  1098. {==============================================================================}
  1099. function GenerateBoundary: string;
  1100. var
  1101. x, y: Integer;
  1102. begin
  1103. y := GetTick;
  1104. x := y;
  1105. while TickDelta(y, x) = 0 do
  1106. begin
  1107. Sleep(1);
  1108. x := GetTick;
  1109. end;
  1110. Randomize;
  1111. y := Random(MaxInt);
  1112. Result := IntToHex(x, 8) + '_' + IntToHex(y, 8) + '_Synapse_boundary';
  1113. end;
  1114. function CreateStringList: TStringList;
  1115. begin
  1116. Result := TStringList.Create;
  1117. {$IFDEF UNICODE}
  1118. Result.WriteBOM := False;
  1119. {$ENDIF}
  1120. end;
  1121. end.