039fa1c3b5
git-svn-id: https://svn.code.sf.net/p/synalist/code/trunk@222 7c85be65-684b-0410-a082-b2ed4fbef004
1242 lines
38 KiB
ObjectPascal
1242 lines
38 KiB
ObjectPascal
{==============================================================================|
|
|
| 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:PANSIChar);
|
|
{/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:PANSIChar);
|
|
var
|
|
x: integer;
|
|
s: ANSIString;
|
|
Mime: TMimePart;
|
|
BOP: PANSIChar; // Beginning of Part
|
|
EOP: PANSIChar; // 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.
|