12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241 |
- {==============================================================================|
- | Project : Ararat Synapse | 002.009.002 |
- |==============================================================================|
- | Content: MIME support procedures and functions |
- |==============================================================================|
- | Copyright (c)1999-2021 |
- | |
- | 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-2021. |
- | Portions created by Petr Fejfar are Copyright (c)2011-2012. |
- | All Rights Reserved. |
- |==============================================================================|
- | Contributor(s): |
- |==============================================================================|
- | History: see HISTORY.HTM from distribution package |
- | (Found at URL: http://www.ararat.cz/synapse/) |
- |==============================================================================}
- {:@abstract(MIME part handling)
- Handling with MIME parts.
- Used RFC: RFC-2045
- }
- {$IFDEF FPC}
- {$MODE DELPHI}
- {$ENDIF}
- {$H+}
- {$Q-}
- {$R-}
- {$M+}
- {$IFDEF UNICODE}
- {$WARN IMPLICIT_STRING_CAST OFF}
- {$WARN IMPLICIT_STRING_CAST_LOSS OFF}
- {$ENDIF}
- unit mimepart;
- interface
- uses
- SysUtils, Classes,
- synafpc,
- synachar, synacode, synautil, mimeinln;
- type
- TMimePart = class;
- {:@abstract(Procedural type for @link(TMimepart.Walkpart) hook). This hook is used for
- easy walking through MIME subparts.}
- THookWalkPart = procedure(const Sender: TMimePart) of object;
- {:The four types of MIME parts. (textual, multipart, message or any other
- binary data.)}
- TMimePrimary = (MP_TEXT, MP_MULTIPART, MP_MESSAGE, MP_BINARY);
- {:The various types of possible part encodings.}
- TMimeEncoding = (ME_7BIT, ME_8BIT, ME_QUOTED_PRINTABLE,
- ME_BASE64, ME_UU, ME_XX);
- {:@abstract(Object for working with parts of MIME e-mail.)
- Each TMimePart object can handle any number of nested subparts as new
- TMimepart objects. It can handle any tree hierarchy structure of nested MIME
- subparts itself.
- Basic tasks are:
- Decoding of MIME message:
- - store message into Lines property
- - call DecomposeParts. Now you have decomposed MIME parts in all nested levels!
- - now you can explore all properties and subparts. (You can use WalkPart method)
- - if you need decode part, call DecodePart.
- Encoding of MIME message:
- - if you need multipart message, you must create subpart by AddSubPart.
- - set all properties of all parts.
- - set content of part into DecodedLines stream
- - encode this stream by EncodePart.
- - 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!)
- - encoded MIME message is stored in Lines property.
- }
- TMimePart = class(TObject)
- private
- FPrimary: string;
- FPrimaryCode: TMimePrimary;
- FSecondary: string;
- FEncoding: string;
- FEncodingCode: TMimeEncoding;
- FDefaultCharset: string;
- FCharset: string;
- FCharsetCode: TMimeChar;
- FTargetCharset: TMimeChar;
- FDescription: string;
- FDisposition: string;
- FContentID: string;
- FBoundary: string;
- FFileName: string;
- FLines: TStringList;
- FPartBody: TStringList;
- FHeaders: TStringList;
- FPrePart: TStringList;
- FPostPart: TStringList;
- FDecodedLines: TMemoryStream;
- FSubParts: TList;
- FOnWalkPart: THookWalkPart;
- FMaxLineLength: integer;
- FSubLevel: integer;
- FMaxSubLevel: integer;
- FAttachInside: boolean;
- FConvertCharset: Boolean;
- FForcedHTMLConvert: Boolean;
- FBinaryDecomposer: boolean;
- procedure SetPrimary(Value: string);
- procedure SetEncoding(Value: string);
- procedure SetCharset(Value: string);
- function IsUUcode(Value: string): boolean;
- public
- constructor Create;
- destructor Destroy; override;
- {:Assign content of another object to this object. (Only this part,
- not subparts!)}
- procedure Assign(Value: TMimePart);
- {:Assign content of another object to this object. (With all subparts!)}
- procedure AssignSubParts(Value: TMimePart);
- {:Clear all data values to default values. It also call @link(ClearSubparts).}
- procedure Clear;
- {:Decode Mime part from @link(Lines) to @link(DecodedLines).}
- procedure DecodePart;
- {:Parse header lines from Headers property into another properties.}
- procedure DecodePartHeader;
- {:Encode mime part from @link(DecodedLines) to @link(Lines) and build mime
- headers.}
- procedure EncodePart;
- {:Build header lines in Headers property from another properties.}
- procedure EncodePartHeader;
- {:generate primary and secondary mime type from filename extension in value.
- If type not recognised, it return 'Application/octet-string' type.}
- procedure MimeTypeFromExt(Value: string);
- {:Return number of decomposed subparts. (On this level! Each of this
- subparts can hold any number of their own nested subparts!)}
- function GetSubPartCount: integer;
- {:Get nested subpart object as new TMimePart. For getting maximum possible
- index you can use @link(GetSubPartCount) method.}
- function GetSubPart(index: integer): TMimePart;
- {:delete subpart on given index.}
- procedure DeleteSubPart(index: integer);
- {:Clear and destroy all subpart TMimePart objects.}
- procedure ClearSubParts;
- {:Add and create new subpart.}
- function AddSubPart: TMimePart;
- {:E-mail message in @link(Lines) property is parsed into this object.
- E-mail headers are stored in @link(Headers) property and is parsed into
- another properties automaticly. Not need call @link(DecodePartHeader)!
- Content of message (part) is stored into @link(PartBody) property. This
- part is in undecoded form! If you need decode it, then you must call
- @link(DecodePart) method by your hands. Lot of another properties is filled
- also.
- Decoding of parts you must call separately due performance reasons. (Not
- needed to decode all parts in all reasons.)
- For each MIME subpart is created new TMimepart object (accessible via
- method @link(GetSubPart)).}
- procedure DecomposeParts;
- {pf}
- {: HTTP message is received by @link(THTTPSend) component in two parts:
- headers are stored in @link(THTTPSend.Headers) and a body in memory stream
- @link(THTTPSend.Document).
- On the top of it, HTTP connections are always 8-bit, hence data are
- transferred in native format i.e. no transfer encoding is applied.
- This method operates the similiar way and produces the same
- result as @link(DecomposeParts).
- }
- procedure DecomposePartsBinary(AHeader:TStrings; AStx,AEtx:PChar);
- {/pf}
- {:This part and all subparts is composed into one MIME message stored in
- @link(Lines) property.}
- procedure ComposeParts;
- {:By calling this method is called @link(OnWalkPart) event for each part
- and their subparts. It is very good for calling some code for each part in
- MIME message}
- procedure WalkPart;
- {:Return @true when is possible create next subpart. (@link(maxSublevel)
- is still not reached)}
- function CanSubPart: boolean;
- published
- {:Primary Mime type of part. (i.e. 'application') Writing to this property
- automaticly generate value of @link(PrimaryCode).}
- property Primary: string read FPrimary write SetPrimary;
- {:String representation of used Mime encoding in part. (i.e. 'base64')
- Writing to this property automaticly generate value of @link(EncodingCode).}
- property Encoding: string read FEncoding write SetEncoding;
- {:String representation of used Mime charset in part. (i.e. 'iso-8859-1')
- Writing to this property automaticly generate value of @link(CharsetCode).
- Charset is used only for text parts.}
- property Charset: string read FCharset write SetCharset;
- {:Define default charset for decoding text MIME parts without charset
- specification. Default value is 'ISO-8859-1' by RCF documents.
- But Microsoft Outlook use windows codings as default. This property allows
- properly decode textual parts from some broken versions of Microsoft
- Outlook. (this is bad software!)}
- property DefaultCharset: string read FDefaultCharset write FDefaultCharset;
- {:Decoded primary type. Possible values are: MP_TEXT, MP_MULTIPART,
- MP_MESSAGE and MP_BINARY. If type not recognised, result is MP_BINARY.}
- property PrimaryCode: TMimePrimary read FPrimaryCode Write FPrimaryCode;
- {:Decoded encoding type. Possible values are: ME_7BIT, ME_8BIT,
- ME_QUOTED_PRINTABLE and ME_BASE64. If type not recognised, result is
- ME_7BIT.}
- property EncodingCode: TMimeEncoding read FEncodingCode Write FEncodingCode;
- {:Decoded charset type. Possible values are defined in @link(SynaChar) unit.}
- property CharsetCode: TMimeChar read FCharsetCode Write FCharsetCode;
- {:System charset type. Default value is charset used by default in your
- operating system.}
- property TargetCharset: TMimeChar read FTargetCharset Write FTargetCharset;
- {:If @true, then do internal charset translation of part content between @link(CharsetCode)
- and @link(TargetCharset)}
- property ConvertCharset: Boolean read FConvertCharset Write FConvertCharset;
- {:If @true, then allways do internal charset translation of HTML parts
- by MIME even it have their own charset in META tag. Default is @false.}
- property ForcedHTMLConvert: Boolean read FForcedHTMLConvert Write FForcedHTMLConvert;
- {:Secondary Mime type of part. (i.e. 'mixed')}
- property Secondary: string read FSecondary Write FSecondary;
- {:Description of Mime part.}
- property Description: string read FDescription Write FDescription;
- {:Value of content disposition field. (i.e. 'inline' or 'attachment')}
- property Disposition: string read FDisposition Write FDisposition;
- {:Content ID.}
- property ContentID: string read FContentID Write FContentID;
- {:Boundary delimiter of multipart Mime part. Used only in multipart part.}
- property Boundary: string read FBoundary Write FBoundary;
- {:Filename of file in binary part.}
- property FileName: string read FFileName Write FFileName;
- {:String list with lines contains mime part (It can be a full message).}
- property Lines: TStringList read FLines;
- {:Encoded form of MIME part data.}
- property PartBody: TStringList read FPartBody;
- {:All header lines of MIME part.}
- property Headers: TStringList read FHeaders;
- {:On multipart this contains part of message between first line of message
- and first boundary.}
- property PrePart: TStringList read FPrePart;
- {:On multipart this contains part of message between last boundary and end
- of message.}
- property PostPart: TStringList read FPostPart;
- {:Stream with decoded form of budy part.}
- property DecodedLines: TMemoryStream read FDecodedLines;
- {:Show nested level in subpart tree. Value 0 means root part. 1 means
- subpart from this root. etc.}
- property SubLevel: integer read FSubLevel write FSubLevel;
- {:Specify maximum sublevel value for decomposing.}
- property MaxSubLevel: integer read FMaxSubLevel write FMaxSubLevel;
- {:When is @true, then this part maybe(!) have included some uuencoded binary
- data.}
- property AttachInside: boolean read FAttachInside;
- {:Here you can assign hook procedure for walking through all part and their
- subparts.}
- property OnWalkPart: THookWalkPart read FOnWalkPart write FOnWalkPart;
- {:Here you can specify maximum line length for encoding of MIME part.
- If line is longer, then is splitted by standard of MIME. Correct MIME
- mailers can de-split this line into original length.}
- property MaxLineLength: integer read FMaxLineLength Write FMaxLineLength;
- end;
- const
- MaxMimeType = 25;
- MimeType: array[0..MaxMimeType, 0..2] of string =
- (
- ('AU', 'audio', 'basic'),
- ('AVI', 'video', 'x-msvideo'),
- ('BMP', 'image', 'BMP'),
- ('DOC', 'application', 'MSWord'),
- ('EPS', 'application', 'Postscript'),
- ('GIF', 'image', 'GIF'),
- ('JPEG', 'image', 'JPEG'),
- ('JPG', 'image', 'JPEG'),
- ('MID', 'audio', 'midi'),
- ('MOV', 'video', 'quicktime'),
- ('MPEG', 'video', 'MPEG'),
- ('MPG', 'video', 'MPEG'),
- ('MP2', 'audio', 'mpeg'),
- ('MP3', 'audio', 'mpeg'),
- ('PDF', 'application', 'PDF'),
- ('PNG', 'image', 'PNG'),
- ('PS', 'application', 'Postscript'),
- ('QT', 'video', 'quicktime'),
- ('RA', 'audio', 'x-realaudio'),
- ('RTF', 'application', 'RTF'),
- ('SND', 'audio', 'basic'),
- ('TIF', 'image', 'TIFF'),
- ('TIFF', 'image', 'TIFF'),
- ('WAV', 'audio', 'x-wav'),
- ('WPD', 'application', 'Wordperfect5.1'),
- ('ZIP', 'application', 'ZIP')
- );
- {:Generates a unique boundary string.}
- function GenerateBoundary: string;
- {:Generates a stringlist that does not write a BOM character.}
- Function CreateStringList : TStringList;
- implementation
- {==============================================================================}
- constructor TMIMEPart.Create;
- begin
- inherited Create;
- FOnWalkPart := nil;
- FLines := CreateStringList;
- FPartBody := CreateStringList;
- FHeaders := CreateStringList;
- FPrePart := CreateStringList;
- FPostPart := CreateStringList;
- FDecodedLines := TMemoryStream.Create;
- FSubParts := TList.Create;
- FTargetCharset := GetCurCP;
- //was 'US-ASCII' before, but RFC-ignorant Outlook sometimes using default
- //system charset instead.
- FDefaultCharset := GetIDFromCP(GetCurCP);
- FMaxLineLength := 78;
- FSubLevel := 0;
- FMaxSubLevel := -1;
- FAttachInside := false;
- FConvertCharset := true;
- FForcedHTMLConvert := false;
- end;
- destructor TMIMEPart.Destroy;
- begin
- ClearSubParts;
- FSubParts.Free;
- FDecodedLines.Free;
- FPartBody.Free;
- FLines.Free;
- FHeaders.Free;
- FPrePart.Free;
- FPostPart.Free;
- inherited Destroy;
- end;
- {==============================================================================}
- procedure TMIMEPart.Clear;
- begin
- FPrimary := '';
- FEncoding := '';
- FCharset := '';
- FPrimaryCode := MP_TEXT;
- FEncodingCode := ME_7BIT;
- FCharsetCode := ISO_8859_1;
- FTargetCharset := GetCurCP;
- FSecondary := '';
- FDisposition := '';
- FContentID := '';
- FDescription := '';
- FBoundary := '';
- FFileName := '';
- FAttachInside := False;
- FPartBody.Clear;
- FHeaders.Clear;
- FPrePart.Clear;
- FPostPart.Clear;
- FDecodedLines.Clear;
- FConvertCharset := true;
- FForcedHTMLConvert := false;
- ClearSubParts;
- end;
- {==============================================================================}
- procedure TMIMEPart.Assign(Value: TMimePart);
- begin
- Primary := Value.Primary;
- Encoding := Value.Encoding;
- Charset := Value.Charset;
- DefaultCharset := Value.DefaultCharset;
- PrimaryCode := Value.PrimaryCode;
- EncodingCode := Value.EncodingCode;
- CharsetCode := Value.CharsetCode;
- TargetCharset := Value.TargetCharset;
- Secondary := Value.Secondary;
- Description := Value.Description;
- Disposition := Value.Disposition;
- ContentID := Value.ContentID;
- Boundary := Value.Boundary;
- FileName := Value.FileName;
- Lines.Assign(Value.Lines);
- PartBody.Assign(Value.PartBody);
- Headers.Assign(Value.Headers);
- PrePart.Assign(Value.PrePart);
- PostPart.Assign(Value.PostPart);
- MaxLineLength := Value.MaxLineLength;
- FAttachInside := Value.AttachInside;
- FConvertCharset := Value.ConvertCharset;
- end;
- {==============================================================================}
- procedure TMIMEPart.AssignSubParts(Value: TMimePart);
- var
- n: integer;
- p: TMimePart;
- begin
- Assign(Value);
- for n := 0 to Value.GetSubPartCount - 1 do
- begin
- p := AddSubPart;
- p.AssignSubParts(Value.GetSubPart(n));
- end;
- end;
- {==============================================================================}
- function TMIMEPart.GetSubPartCount: integer;
- begin
- Result := FSubParts.Count;
- end;
- {==============================================================================}
- function TMIMEPart.GetSubPart(index: integer): TMimePart;
- begin
- Result := nil;
- if Index < GetSubPartCount then
- Result := TMimePart(FSubParts[Index]);
- end;
- {==============================================================================}
- procedure TMIMEPart.DeleteSubPart(index: integer);
- begin
- if Index < GetSubPartCount then
- begin
- GetSubPart(Index).Free;
- FSubParts.Delete(Index);
- end;
- end;
- {==============================================================================}
- procedure TMIMEPart.ClearSubParts;
- var
- n: integer;
- begin
- for n := 0 to GetSubPartCount - 1 do
- TMimePart(FSubParts[n]).Free;
- FSubParts.Clear;
- end;
- {==============================================================================}
- function TMIMEPart.AddSubPart: TMimePart;
- begin
- Result := TMimePart.Create;
- Result.DefaultCharset := FDefaultCharset;
- FSubParts.Add(Result);
- Result.SubLevel := FSubLevel + 1;
- Result.MaxSubLevel := FMaxSubLevel;
- end;
- {==============================================================================}
- procedure TMIMEPart.DecomposeParts;
- var
- x: integer;
- s: string;
- Mime: TMimePart;
- procedure SkipEmpty;
- begin
- while FLines.Count > x do
- begin
- s := TrimRight(FLines[x]);
- if s <> '' then
- Break;
- Inc(x);
- end;
- end;
- begin
- FBinaryDecomposer := false;
- x := 0;
- Clear;
- //extract headers
- while FLines.Count > x do
- begin
- s := NormalizeHeader(FLines, x);
- if s = '' then
- Break;
- FHeaders.Add(s);
- end;
- DecodePartHeader;
- //extract prepart
- if FPrimaryCode = MP_MULTIPART then
- begin
- while FLines.Count > x do
- begin
- s := FLines[x];
- Inc(x);
- if TrimRight(s) = '--' + FBoundary then
- Break;
- FPrePart.Add(s);
- if not FAttachInside then
- FAttachInside := IsUUcode(s);
- end;
- end;
- //extract body part
- if FPrimaryCode = MP_MULTIPART then
- begin
- repeat
- if CanSubPart then
- begin
- Mime := AddSubPart;
- while FLines.Count > x do
- begin
- s := FLines[x];
- Inc(x);
- if Pos('--' + FBoundary, s) = 1 then
- Break;
- Mime.Lines.Add(s);
- end;
- Mime.DecomposeParts;
- end
- else
- begin
- s := FLines[x];
- Inc(x);
- FPartBody.Add(s);
- end;
- if x >= FLines.Count then
- break;
- until s = '--' + FBoundary + '--';
- end;
- if (FPrimaryCode = MP_MESSAGE) and CanSubPart then
- begin
- Mime := AddSubPart;
- SkipEmpty;
- while FLines.Count > x do
- begin
- s := TrimRight(FLines[x]);
- Inc(x);
- Mime.Lines.Add(s);
- end;
- Mime.DecomposeParts;
- end
- else
- begin
- while FLines.Count > x do
- begin
- s := FLines[x];
- Inc(x);
- FPartBody.Add(s);
- if not FAttachInside then
- FAttachInside := IsUUcode(s);
- end;
- end;
- //extract postpart
- if FPrimaryCode = MP_MULTIPART then
- begin
- while FLines.Count > x do
- begin
- s := TrimRight(FLines[x]);
- Inc(x);
- FPostPart.Add(s);
- if not FAttachInside then
- FAttachInside := IsUUcode(s);
- end;
- end;
- end;
- procedure TMIMEPart.DecomposePartsBinary(AHeader:TStrings; AStx,AEtx:PChar);
- var
- x: integer;
- s: ANSIString;
- Mime: TMimePart;
- BOP: PChar; // Beginning of Part
- EOP: PChar; // End of Part
- function ___HasUUCode(ALines:TStrings): boolean;
- var
- x: integer;
- begin
- Result := FALSE;
- for x:=0 to ALines.Count-1 do
- if IsUUcode(ALInes[x]) then
- begin
- Result := TRUE;
- exit;
- end;
- end;
- begin
- FBinaryDecomposer := true;
- Clear;
- // Parse passed headers (THTTPSend returns HTTP headers and body separately)
- x := 0;
- while x<AHeader.Count do
- begin
- s := NormalizeHeader(AHeader,x);
- if s = '' then
- Break;
- FHeaders.Add(s);
- end;
- DecodePartHeader;
- // Extract prepart
- if FPrimaryCode=MP_MULTIPART then
- begin
- CopyLinesFromStreamUntilBoundary(AStx,AEtx,FPrePart,FBoundary);
- FAttachInside := FAttachInside or ___HasUUCode(FPrePart);
- end;
- // Extract body part
- if FPrimaryCode=MP_MULTIPART then
- begin
- repeat
- if CanSubPart then
- begin
- Mime := AddSubPart;
- BOP := AStx;
- EOP := SearchForBoundary(AStx,AEtx,FBoundary);
- CopyLinesFromStreamUntilNullLine(BOP,EOP,Mime.Lines);
- Mime.DecomposePartsBinary(Mime.Lines,BOP,EOP);
- end
- else
- begin
- EOP := SearchForBoundary(AStx,AEtx,FBoundary);
- FPartBody.Add(BuildStringFromBuffer(AStx,EOP));
- end;
- //
- BOP := MatchLastBoundary(EOP,AEtx,FBoundary);
- if Assigned(BOP) then
- begin
- AStx := BOP;
- Break;
- end;
- until FALSE;
- end;
- // Extract nested MIME message
- if (FPrimaryCode=MP_MESSAGE) and CanSubPart then
- begin
- Mime := AddSubPart;
- SkipNullLines(AStx,AEtx);
- CopyLinesFromStreamUntilNullLine(AStx,AEtx,Mime.Lines);
- Mime.DecomposePartsBinary(Mime.Lines,AStx,AEtx);
- end
- // Extract body of single part
- else
- begin
- FPartBody.Add(BuildStringFromBuffer(AStx,AEtx));
- FAttachInside := FAttachInside or ___HasUUCode(FPartBody);
- end;
- // Extract postpart
- if FPrimaryCode=MP_MULTIPART then
- begin
- CopyLinesFromStreamUntilBoundary(AStx,AEtx,FPostPart,'');
- FAttachInside := FAttachInside or ___HasUUCode(FPostPart);
- end;
- end;
- {/pf}
- {==============================================================================}
- procedure TMIMEPart.ComposeParts;
- var
- n: integer;
- mime: TMimePart;
- s, t: string;
- d1, d2, d3: integer;
- x: integer;
- begin
- FLines.Clear;
- //add headers
- for n := 0 to FHeaders.Count -1 do
- begin
- s := FHeaders[n];
- repeat
- if Length(s) < FMaxLineLength then
- begin
- t := s;
- s := '';
- end
- else
- begin
- d1 := RPosEx('; ', s, FMaxLineLength);
- d2 := RPosEx(' ', s, FMaxLineLength);
- d3 := RPosEx(', ', s, FMaxLineLength);
- if (d1 <= 1) and (d2 <= 1) and (d3 <= 1) then
- begin
- x := Pos(' ', Copy(s, 2, Length(s) - 1));
- if x < 1 then
- x := Length(s);
- end
- else
- if d1 > 0 then
- x := d1
- else
- if d3 > 0 then
- x := d3
- else
- x := d2 - 1;
- t := Copy(s, 1, x);
- Delete(s, 1, x);
- end;
- Flines.Add(t);
- until s = '';
- end;
- Flines.Add('');
- //add body
- //if multipart
- if FPrimaryCode = MP_MULTIPART then
- begin
- Flines.AddStrings(FPrePart);
- for n := 0 to GetSubPartCount - 1 do
- begin
- Flines.Add('--' + FBoundary);
- mime := GetSubPart(n);
- mime.ComposeParts;
- FLines.AddStrings(mime.Lines);
- end;
- Flines.Add('--' + FBoundary + '--');
- Flines.AddStrings(FPostPart);
- end;
- //if message
- if FPrimaryCode = MP_MESSAGE then
- begin
- if GetSubPartCount > 0 then
- begin
- mime := GetSubPart(0);
- mime.ComposeParts;
- FLines.AddStrings(mime.Lines);
- end;
- end
- else
- //if normal part
- begin
- FLines.AddStrings(FPartBody);
- end;
- end;
- {==============================================================================}
- procedure TMIMEPart.DecodePart;
- var
- n: Integer;
- s, t, t2: string;
- b: Boolean;
- begin
- FDecodedLines.Clear;
- {pf}
- // The part decomposer passes data via TStringList which appends trailing line
- // break inherently. But in a case of native 8-bit data transferred withouth
- // encoding (default e.g. for HTTP protocol), the redundant line terminators
- // has to be removed
- if FBinaryDecomposer and (FPartBody.Count=1) then
- begin
- case FEncodingCode of
- ME_QUOTED_PRINTABLE:
- s := DecodeQuotedPrintable(FPartBody[0]);
- ME_BASE64:
- s := DecodeBase64(FPartBody[0]);
- ME_UU, ME_XX:
- begin
- s := '';
- for n := 0 to FPartBody.Count - 1 do
- if FEncodingCode = ME_UU then
- s := s + DecodeUU(FPartBody[n])
- else
- s := s + DecodeXX(FPartBody[n]);
- end;
- else
- s := FPartBody[0];
- end;
- end
- else
- {/pf}
- case FEncodingCode of
- ME_QUOTED_PRINTABLE:
- s := DecodeQuotedPrintable(FPartBody.Text);
- ME_BASE64:
- s := DecodeBase64(FPartBody.Text);
- ME_UU, ME_XX:
- begin
- s := '';
- for n := 0 to FPartBody.Count - 1 do
- if FEncodingCode = ME_UU then
- s := s + DecodeUU(FPartBody[n])
- else
- s := s + DecodeXX(FPartBody[n]);
- end;
- else
- s := FPartBody.Text;
- end;
- if FConvertCharset and (FPrimaryCode = MP_TEXT) then
- if (not FForcedHTMLConvert) and (uppercase(FSecondary) = 'HTML') then
- begin
- b := false;
- t2 := uppercase(s);
- t := SeparateLeft(t2, '</HEAD>');
- if length(t) <> length(s) then
- begin
- t := SeparateRight(t, '<HEAD>');
- t := ReplaceString(t, '"', '');
- t := ReplaceString(t, ' ', '');
- b := Pos('HTTP-EQUIV=CONTENT-TYPE', t) > 0;
- end;
- //workaround for shitty M$ Outlook 11 which is placing this information
- //outside <head> section
- if not b then
- begin
- t := Copy(t2, 1, 2048);
- t := ReplaceString(t, '"', '');
- t := ReplaceString(t, ' ', '');
- b := Pos('HTTP-EQUIV=CONTENT-TYPE', t) > 0;
- end;
- if not b then
- s := CharsetConversion(s, FCharsetCode, FTargetCharset);
- end
- else
- s := CharsetConversion(s, FCharsetCode, FTargetCharset);
- WriteStrToStream(FDecodedLines, s);
- FDecodedLines.Position := 0;
- end;
- {==============================================================================}
- procedure TMIMEPart.DecodePartHeader;
- var
- n: integer;
- s, su, fn: string;
- st, st2: string;
- begin
- Primary := 'text';
- FSecondary := 'plain';
- FDescription := '';
- Charset := FDefaultCharset;
- FFileName := '';
- //was 7bit before, but this is more compatible with RFC-ignorant outlook
- Encoding := '8BIT';
- FDisposition := '';
- FContentID := '';
- fn := '';
- for n := 0 to FHeaders.Count - 1 do
- if FHeaders[n] <> '' then
- begin
- s := FHeaders[n];
- su := UpperCase(s);
- if Pos('CONTENT-TYPE:', su) = 1 then
- begin
- st := Trim(SeparateRight(su, ':'));
- st2 := Trim(SeparateLeft(st, ';'));
- Primary := Trim(SeparateLeft(st2, '/'));
- FSecondary := Trim(SeparateRight(st2, '/'));
- if (FSecondary = Primary) and (Pos('/', st2) < 1) then
- FSecondary := '';
- case FPrimaryCode of
- MP_TEXT:
- begin
- Charset := UpperCase(GetParameter(s, 'charset'));
- FFileName := GetParameter(s, 'name');
- end;
- MP_MULTIPART:
- FBoundary := GetParameter(s, 'Boundary');
- MP_MESSAGE:
- begin
- end;
- MP_BINARY:
- FFileName := GetParameter(s, 'name');
- end;
- end;
- if Pos('CONTENT-TRANSFER-ENCODING:', su) = 1 then
- Encoding := Trim(SeparateRight(su, ':'));
- if Pos('CONTENT-DESCRIPTION:', su) = 1 then
- FDescription := Trim(SeparateRight(s, ':'));
- if Pos('CONTENT-DISPOSITION:', su) = 1 then
- begin
- FDisposition := SeparateRight(su, ':');
- FDisposition := Trim(SeparateLeft(FDisposition, ';'));
- fn := GetParameter(s, 'FileName');
- end;
- if Pos('CONTENT-ID:', su) = 1 then
- FContentID := Trim(SeparateRight(s, ':'));
- end;
- if fn <> '' then
- FFileName := fn;
- FFileName := InlineDecode(FFileName, FTargetCharset);
- FFileName := ExtractFileName(FFileName);
- end;
- {==============================================================================}
- procedure TMIMEPart.EncodePart;
- var
- l: TStringList;
- {$IFDEF UNICODE}
- s, t: RawByteString;
- {$ELSE}
- s, t: string;
- {$ENDIF}
- n, x: Integer;
- d1, d2: integer;
- begin
- if (FEncodingCode = ME_UU) or (FEncodingCode = ME_XX) then
- Encoding := 'base64';
- l := CreateStringList;
- FPartBody.Clear;
- FDecodedLines.Position := 0;
- try
- case FPrimaryCode of
- MP_MULTIPART, MP_MESSAGE:
- FPartBody.LoadFromStream(FDecodedLines);
- MP_TEXT, MP_BINARY:
- begin
- s := ReadStrFromStream(FDecodedLines, FDecodedLines.Size);
- if FConvertCharset and (FPrimaryCode = MP_TEXT) and (FEncodingCode <> ME_7BIT) then
- s := GetBOM(FCharSetCode) + CharsetConversion(s, FTargetCharset, FCharsetCode);
- if FEncodingCode = ME_BASE64 then
- begin
- x := 1;
- while x <= length(s) do
- begin
- t := copy(s, x, 54);
- x := x + length(t);
- t := EncodeBase64(t);
- FPartBody.Add(t);
- end;
- end
- else
- begin
- if FPrimaryCode = MP_BINARY then
- l.Add(s)
- else
- l.Text := s;
- for n := 0 to l.Count - 1 do
- begin
- s := l[n];
- if FEncodingCode = ME_QUOTED_PRINTABLE then
- begin
- s := EncodeQuotedPrintable(s);
- repeat
- if Length(s) < FMaxLineLength then
- begin
- t := s;
- s := '';
- end
- else
- begin
- d1 := RPosEx('=', s, FMaxLineLength);
- d2 := RPosEx(' ', s, FMaxLineLength);
- if (d1 = 0) and (d2 = 0) then
- x := FMaxLineLength
- else
- if d1 > d2 then
- x := d1 - 1
- else
- x := d2 - 1;
- if x = 0 then
- x := FMaxLineLength;
- t := Copy(s, 1, x);
- Delete(s, 1, x);
- if s <> '' then
- t := t + '=';
- end;
- FPartBody.Add(t);
- until s = '';
- end
- else
- FPartBody.Add(s);
- end;
- if (FPrimaryCode = MP_BINARY)
- and (FEncodingCode = ME_QUOTED_PRINTABLE) then
- FPartBody[FPartBody.Count - 1] := FPartBody[FPartBody.Count - 1] + '=';
- end;
- end;
- end;
- finally
- l.Free;
- end;
- end;
- {==============================================================================}
- procedure TMIMEPart.EncodePartHeader;
- var
- s: string;
- begin
- FHeaders.Clear;
- if FSecondary = '' then
- case FPrimaryCode of
- MP_TEXT:
- FSecondary := 'plain';
- MP_MULTIPART:
- FSecondary := 'mixed';
- MP_MESSAGE:
- FSecondary := 'rfc822';
- MP_BINARY:
- FSecondary := 'octet-stream';
- end;
- if FDescription <> '' then
- FHeaders.Insert(0, 'Content-Description: ' + FDescription);
- if FDisposition <> '' then
- begin
- s := '';
- if FFileName <> '' then
- s := '; FileName=' + QuoteStr(InlineCodeEx(FileName, FTargetCharset), '"');
- FHeaders.Insert(0, 'Content-Disposition: ' + LowerCase(FDisposition) + s);
- end;
- if FContentID <> '' then
- FHeaders.Insert(0, 'Content-ID: <' + FContentID + '>');
- case FEncodingCode of
- ME_7BIT:
- s := '7bit';
- ME_8BIT:
- s := '8bit';
- ME_QUOTED_PRINTABLE:
- s := 'Quoted-printable';
- ME_BASE64:
- s := 'Base64';
- end;
- case FPrimaryCode of
- MP_TEXT,
- MP_BINARY: FHeaders.Insert(0, 'Content-Transfer-Encoding: ' + s);
- end;
- case FPrimaryCode of
- MP_TEXT:
- s := FPrimary + '/' + FSecondary + '; charset=' + GetIDfromCP(FCharsetCode);
- MP_MULTIPART:
- s := FPrimary + '/' + FSecondary + '; boundary="' + FBoundary + '"';
- MP_MESSAGE, MP_BINARY:
- s := FPrimary + '/' + FSecondary;
- end;
- if FFileName <> '' then
- s := s + '; name=' + QuoteStr(InlineCodeEx(FileName, FTargetCharset), '"');
- FHeaders.Insert(0, 'Content-type: ' + s);
- end;
- {==============================================================================}
- procedure TMIMEPart.MimeTypeFromExt(Value: string);
- var
- s: string;
- n: Integer;
- begin
- Primary := '';
- FSecondary := '';
- s := UpperCase(ExtractFileExt(Value));
- if s = '' then
- s := UpperCase(Value);
- s := SeparateRight(s, '.');
- for n := 0 to MaxMimeType do
- if MimeType[n, 0] = s then
- begin
- Primary := MimeType[n, 1];
- FSecondary := MimeType[n, 2];
- Break;
- end;
- if Primary = '' then
- Primary := 'application';
- if FSecondary = '' then
- FSecondary := 'octet-stream';
- end;
- {==============================================================================}
- procedure TMIMEPart.WalkPart;
- var
- n: integer;
- m: TMimepart;
- begin
- if assigned(OnWalkPart) then
- begin
- OnWalkPart(self);
- for n := 0 to GetSubPartCount - 1 do
- begin
- m := GetSubPart(n);
- m.OnWalkPart := OnWalkPart;
- m.WalkPart;
- end;
- end;
- end;
- {==============================================================================}
- procedure TMIMEPart.SetPrimary(Value: string);
- var
- s: string;
- begin
- FPrimary := Value;
- s := UpperCase(Value);
- FPrimaryCode := MP_BINARY;
- if Pos('TEXT', s) = 1 then
- FPrimaryCode := MP_TEXT;
- if Pos('MULTIPART', s) = 1 then
- FPrimaryCode := MP_MULTIPART;
- if Pos('MESSAGE', s) = 1 then
- FPrimaryCode := MP_MESSAGE;
- end;
- procedure TMIMEPart.SetEncoding(Value: string);
- var
- s: string;
- begin
- FEncoding := Value;
- s := UpperCase(Value);
- FEncodingCode := ME_7BIT;
- if Pos('8BIT', s) = 1 then
- FEncodingCode := ME_8BIT;
- if Pos('QUOTED-PRINTABLE', s) = 1 then
- FEncodingCode := ME_QUOTED_PRINTABLE;
- if Pos('BASE64', s) = 1 then
- FEncodingCode := ME_BASE64;
- if Pos('X-UU', s) = 1 then
- FEncodingCode := ME_UU;
- if Pos('X-XX', s) = 1 then
- FEncodingCode := ME_XX;
- end;
- procedure TMIMEPart.SetCharset(Value: string);
- begin
- if value <> '' then
- begin
- FCharset := Value;
- FCharsetCode := GetCPFromID(Value);
- end;
- end;
- function TMIMEPart.CanSubPart: boolean;
- begin
- Result := True;
- if FMaxSubLevel <> -1 then
- Result := FMaxSubLevel > FSubLevel;
- end;
- function TMIMEPart.IsUUcode(Value: string): boolean;
- begin
- Value := UpperCase(Value);
- Result := (pos('BEGIN ', Value) = 1) and (Trim(SeparateRight(Value, ' ')) <> '');
- end;
- {==============================================================================}
- function GenerateBoundary: string;
- var
- x, y: Integer;
- begin
- y := GetTick;
- x := y;
- while TickDelta(y, x) = 0 do
- begin
- Sleep(1);
- x := GetTick;
- end;
- Randomize;
- y := Random(MaxInt);
- Result := IntToHex(x, 8) + '_' + IntToHex(y, 8) + '_Synapse_boundary';
- end;
- function CreateStringList: TStringList;
- begin
- Result := TStringList.Create;
- {$IFDEF UNICODE}
- Result.WriteBOM := False;
- {$ENDIF}
- end;
- end.
|