use
5.006;
$VERSION
=
"0.921"
;
our
$skipheaders
= {
map
{
uc
(
$_
) => 1 }
"MIME-Version"
,
"Content-Type"
,
"Content-Transfer-Encoding"
,
"X-Mailer"
,
"X-Msgconvert"
,
"X-MS-Tnef-Correlator"
,
"X-MS-Has-Attach"
};
our
$MAP_SUBITEM_FILE
= {
'1000'
=>
"BODY_PLAIN"
,
'1009'
=>
"BODY_RTF"
,
'1013'
=>
"BODY_HTML"
,
'0037'
=>
"SUBJECT"
,
'0047'
=>
"SUBMISSION_ID"
,
'007D'
=>
"HEAD"
,
'0C1A'
=>
"FROM"
,
'0C1E'
=>
"FROM_ADDR_TYPE"
,
'0C1F'
=>
"FROM_ADDR"
,
'0E04'
=>
"TO"
,
'0E03'
=>
"CC"
,
'1035'
=>
"MESSAGEID"
,
'1039'
=>
"REFERENCES"
,
'1042'
=>
"INREPLYTO"
,
'3007'
=>
'DATE2ND'
,
'0039'
=>
'DATE1ST'
,
'3FDE'
=>
'CODEPAGE'
,
};
our
$MAP_CODEPAGE
= {
20127
=>
'US-ASCII'
,
20866
=>
'KOI8-R'
,
28591
=>
'ISO-8859-1'
,
65001
=>
'UTF-8'
,
};
sub
new {
my
$class
=
shift
;
my
$file
=
shift
or croak
"File name is required parameter"
;
my
$verbose
=
shift
;
my
$self
=
$class
->_empty_new;
$self
->{EMBEDDED} = 0;
my
$msg
= OLE::Storage_Lite->new(
$file
);
my
$pps
=
$msg
->getPpsTree(1);
$pps
or croak
"Parsing $file as OLE file failed"
;
$self
->_set_verbosity(
$verbose
);
$self
->_process_pps(
$pps
);
return
$self
;
}
sub
_empty_new {
my
$class
=
shift
;
return
bless
{
ADDRESSES
=> [],
ATTACHMENTS
=> [],
FROM_ADDR_TYPE
=>
""
,
VERBOSE
=> 0,
EMBEDDED
=> 1
},
$class
;
}
sub
to_email_mime {
my
$self
=
shift
;
my
$bodymime
;
my
$mime
;
my
@parts
;
if
(
$self
->{BODY_PLAIN}) {
push
(
@parts
,
$self
->_create_mime_plain_body()); }
if
(
$self
->{BODY_HTML}) {
push
(
@parts
,
$self
->_create_mime_html_body()); }
if
(
$self
->{BODY_RTF}) {
push
(
@parts
,
$self
->_create_mime_rtf_body()); }
if
((
scalar
@parts
) > 1) {
for
(
@parts
) {
$self
->_clean_part_header(
$_
) };
$bodymime
= Email::MIME->create(
attributes
=> {
content_type
=>
"multipart/alternative"
,
encoding
=>
"8bit"
,
},
parts
=> \
@parts
);
}
elsif
((
@parts
) == 1) {
$bodymime
=
$parts
[0];
}
else
{
$bodymime
=
$self
->_create_mime_plain_body();
}
if
(@{
$self
->{ATTACHMENTS}}>0) {
$self
->_clean_part_header(
$bodymime
);
my
$mult
= Email::MIME->create(
attributes
=> {
content_type
=>
"multipart/mixed"
,
encoding
=>
"8bit"
,
},
parts
=> [
$bodymime
],
);
foreach
my
$att
(@{
$self
->{ATTACHMENTS}}) {
$self
->_SaveAttachment(
$mult
,
$att
);
}
$mime
=
$mult
;
}
else
{
$mime
=
$bodymime
;
}
$self
->_SetHeaderFields(
$mime
);
$self
->_copy_header_data(
$mime
);
return
$mime
;
}
sub
_property_map {
return
$MAP_SUBITEM_FILE
;
}
sub
_process_subdirectory {
my
(
$self
,
$pps
) =
@_
;
$self
->_extract_ole_date(
$pps
);
my
$name
=
$self
->_get_pps_name(
$pps
);
if
(
$name
=~
'__recip_version1 0_ '
) {
$self
->_process_address(
$pps
);
}
elsif
(
$name
=~
'__attach_version1 0_ '
) {
$self
->_process_attachment(
$pps
);
}
else
{
$self
->_warn_about_unknown_directory(
$pps
);
}
return
;
}
sub
_process_address {
my
(
$self
,
$pps
) =
@_
;
my
$addr_info
= Email::Outlook::Message::AddressInfo->new(
$pps
,
$self
->{VERBOSE});
push
@{
$self
->{ADDRESSES}},
$addr_info
;
return
;
}
sub
_process_attachment {
my
(
$self
,
$pps
) =
@_
;
my
$attachment
= Email::Outlook::Message::Attachment->new(
$pps
,
$self
->{VERBOSE});
push
@{
$self
->{ATTACHMENTS}},
$attachment
;
return
;
}
sub
_property_stream_header_length {
my
$self
=
shift
;
return
(
$self
->{EMBEDDED} ? 24 : 32)
}
sub
_extract_ole_date {
my
(
$self
,
$pps
) =
@_
;
unless
(
defined
(
$self
->{OLEDATE})) {
my
$datearr
;
$datearr
=
$pps
->{Time2nd};
$datearr
=
$pps
->{Time1st}
unless
$datearr
and
$datearr
->[0];
$self
->{OLEDATE} =
$self
->_format_date(
$datearr
)
if
$datearr
and
$datearr
->[0];
}
return
;
}
sub
_submission_id_date {
my
$self
=
shift
;
my
$submission_id
=
$self
->{SUBMISSION_ID} or
return
;
$submission_id
=~ m/ l=.*- (\d\d) (\d\d) (\d\d) (\d\d) (\d\d) (\d\d) Z-.* /x
or
return
;
my
$year
= $1;
$year
+= 100
if
$year
< 20;
return
$self
->_format_date([$6,$5,$4,$3,$2-1,
$year
]);
}
sub
_SaveAttachment {
my
(
$self
,
$mime
,
$att
) =
@_
;
my
$m
=
$att
->to_email_mime;
$self
->_clean_part_header(
$m
);
$mime
->parts_add([
$m
]);
return
;
}
sub
_AddHeaderField {
my
(
$self
,
$mime
,
$fieldname
,
$value
) =
@_
;
$mime
->header_set(
$fieldname
,
$value
)
if
$value
;
return
;
}
sub
_Address {
my
(
$self
,
$tag
) =
@_
;
my
$result
=
$self
->{
$tag
} ||
""
;
my
$address
=
$self
->{
$tag
.
"_ADDR"
} ||
""
;
if
(
$address
) {
$result
.=
" "
if
$result
;
$result
.=
"<$address>"
;
}
return
$result
;
}
sub
_expand_address_list {
my
(
$self
,
$names
) =
@_
;
return
""
unless
defined
$names
;
my
@namelist
=
split
/ ; [ ]* /x,
$names
;
my
@result
;
name:
foreach
my
$name
(
@namelist
) {
my
$addresstext
=
$self
->_find_name_in_addresspool(
$name
);
if
(
$addresstext
) {
push
@result
,
$addresstext
;
}
else
{
push
@result
,
$name
;
}
}
return
join
", "
,
@result
;
}
sub
_find_name_in_addresspool {
my
(
$self
,
$name
) =
@_
;
my
$addresspool
=
$self
->{ADDRESSES};
foreach
my
$address
(@{
$addresspool
}) {
if
(
$name
eq
$address
->name) {
return
$address
->display_address;
}
}
return
;
}
sub
_clean_part_header {
my
(
$self
,
$part
) =
@_
;
$part
->header_set(
'Date'
);
unless
(
$part
->content_type =~ m{ ^ multipart / }x) {
$part
->header_set(
'MIME-Version'
)
};
return
;
}
sub
_body_plain_character_set {
my
$self
=
shift
;
my
$body_encoding
=
$self
->{BODY_PLAIN_ENCODING};
$self
->_body_character_set(
$body_encoding
)
}
sub
_body_html_character_set {
my
$self
=
shift
;
my
$body_encoding
=
$self
->{BODY_HTML_ENCODING};
$self
->_body_character_set(
$body_encoding
)
}
sub
_body_character_set {
my
$self
=
shift
;
my
$body_encoding
=
shift
;
my
$codepage
=
$self
->{CODEPAGE};
if
(
defined
$body_encoding
&&
$body_encoding
eq
"001F"
) {
return
"UTF-8"
;
}
elsif
(
defined
$codepage
) {
return
$MAP_CODEPAGE
->{
$codepage
} ||
"CP$codepage"
;
}
else
{
return
'CP1252'
;
}
}
sub
_create_mime_plain_body {
my
$self
=
shift
;
my
$charset
=
$self
->_body_plain_character_set;
my
$body_str
=
$self
->{BODY_PLAIN};
if
(
$charset
ne
"UTF-8"
) {
$body_str
= Encode::decode(
$charset
,
$body_str
);
}
return
Email::MIME->create(
attributes
=> {
content_type
=>
"text/plain"
,
charset
=>
$charset
,
disposition
=>
"inline"
,
encoding
=>
"8bit"
,
},
body_str
=>
$body_str
);
}
sub
_create_mime_html_body {
my
$self
=
shift
;
return
Email::MIME->create(
attributes
=> {
content_type
=>
"text/html"
,
charset
=>
$self
->_body_html_character_set,
disposition
=>
"inline"
,
encoding
=>
"8bit"
,
},
body
=>
$self
->{BODY_HTML}
);
}
my
$MAGIC_COMPRESSED_RTF
= 0x75465a4c;
my
$MAGIC_UNCOMPRESSED_RTF
= 0x414c454d;
my
$BASE_BUFFER
=
"{\\rtf1\\ansi\\mac\\deff0\\deftab720{\\fonttbl;}{\\f0\\fnil \\froman "
.
"\\fswiss \\fmodern \\fscript \\fdecor MS Sans SerifSymbolArial"
.
"Times New RomanCourier{\\colortbl\\red0\\green0\\blue0\n\r\\par "
.
"\\pard\\plain\\f0\\fs20\\b\\i\\u\\tab\\tx"
;
sub
_create_mime_rtf_body {
my
$self
=
shift
;
my
$data
=
$self
->{BODY_RTF};
my
(
$size
,
$rawsize
,
$magic
,
$crc
) =
unpack
"V4"
,
substr
$data
, 0, 16;
my
$buffer
;
if
(
$magic
==
$MAGIC_COMPRESSED_RTF
) {
$buffer
=
$BASE_BUFFER
;
my
$output_length
=
length
(
$buffer
) +
$rawsize
;
my
@flags
;
my
$in
= 16;
while
(
length
(
$buffer
) <
$output_length
) {
if
(
@flags
== 0) {
@flags
=
split
""
,
unpack
"b8"
,
substr
$data
,
$in
++, 1;
}
my
$flag
=
shift
@flags
;
if
(
$flag
eq
"0"
) {
$buffer
.=
substr
$data
,
$in
++, 1;
}
else
{
my
(
$a
,
$b
) =
unpack
"C2"
,
substr
$data
,
$in
, 2;
my
$offset
= (
$a
<< 4) | (
$b
>> 4);
my
$length
= (
$b
& 0xf) + 2;
my
$buflen
=
length
$buffer
;
my
$longoffset
=
$buflen
- (
$buflen
% 4096) +
$offset
;
if
(
$longoffset
>=
$buflen
) {
$longoffset
-= 4096; }
while
(
$length
> 0) {
$buffer
.=
substr
$buffer
,
$longoffset
, 1;
$length
--;
$longoffset
++;
}
$in
+= 2;
}
}
$buffer
=
substr
$buffer
,
length
$BASE_BUFFER
;
}
elsif
(
$magic
==
$MAGIC_UNCOMPRESSED_RTF
) {
$buffer
=
substr
$data
, 16;
}
else
{
carp
"Incorrect magic number in RTF body.\n"
;
}
return
Email::MIME->create(
attributes
=> {
content_type
=>
"application/rtf"
,
disposition
=>
"inline"
,
encoding
=>
"base64"
,
},
body
=>
$buffer
);
}
sub
_copy_header_data {
my
(
$self
,
$mime
) =
@_
;
defined
$self
->{HEAD} or
return
;
my
$parsed
= Email::Simple->new(
$self
->{HEAD} .
"\n"
);
foreach
my
$tag
(
grep
{ !
$skipheaders
->{
uc
$_
}}
$parsed
->header_names) {
$mime
->header_set(
$tag
,
$parsed
->header(
$tag
));
}
return
;
}
sub
_SetHeaderFields {
my
(
$self
,
$mime
) =
@_
;
$self
->_AddHeaderField(
$mime
,
'Subject'
,
$self
->{SUBJECT});
$self
->_AddHeaderField(
$mime
,
'From'
,
$self
->_Address(
"FROM"
));
$self
->_AddHeaderField(
$mime
,
'To'
,
$self
->_expand_address_list(
$self
->{TO}));
$self
->_AddHeaderField(
$mime
,
'Cc'
,
$self
->_expand_address_list(
$self
->{CC}));
$self
->_AddHeaderField(
$mime
,
'Message-Id'
,
$self
->{MESSAGEID});
$self
->_AddHeaderField(
$mime
,
'In-Reply-To'
,
$self
->{INREPLYTO});
$self
->_AddHeaderField(
$mime
,
'References'
,
$self
->{REFERENCES});
$self
->_AddHeaderField(
$mime
,
'Date'
,
$self
->{OLEDATE});
$self
->_AddHeaderField(
$mime
,
'Date'
,
$self
->_submission_id_date());
$self
->_AddHeaderField(
$mime
,
'Date'
,
$self
->{DATE2ND});
$self
->_AddHeaderField(
$mime
,
'Date'
,
$self
->{DATE1ST});
return
;
}
1;