use
constant
INTERNALDATE_PARSER
=> DateTime::Format::Strptime->new(
pattern
=>
"%e-%b-%Y %T %z"
);
use
constant
HEADERDATE_PARSER
=> DateTime::Format::Mail->new->loose;
my
%FLAGS
;
$FLAGS
{
lc
$_
} =
$_
for
qw(\Answered \Flagged \Deleted \Seen \Draft)
;
__PACKAGE__->mk_accessors(
qw(sequence mailbox uid _flags mime expunged)
);
sub
new {
my
$class
=
shift
;
my
$self
=
bless
{},
$class
;
$self
->mime( Email::MIME->new(
@_
) )
if
@_
;
$self
->internaldate( DateTime->now(
time_zone
=>
'local'
) );
$self
->_flags( {} );
return
$self
;
}
sub
internaldate {
my
$self
=
shift
;
return
$self
->{internaldate}
unless
@_
;
my
$value
=
shift
;
my
$dt
=
ref
$value
?
$value
:
$self
->INTERNALDATE_PARSER->parse_datetime(
$value
);
return
undef
unless
$dt
;
$self
->{internaldate} =
$dt
->strftime(
"%e-%b-%Y %T %z"
);
$dt
->
truncate
(
to
=>
"day"
);
$dt
->set_time_zone(
"floating"
);
$dt
->set_time_zone(
"UTC"
);
$self
->{epoch_day_utc} =
$dt
->epoch;
return
$self
->{internaldate};
}
sub
epoch_day_utc {
my
$self
=
shift
;
return
$self
->{epoch_day_utc};
}
sub
date {
my
$self
=
shift
;
my
$date
=
$self
->mime_header->header(
"Date"
);
return
unless
$date
;
return
eval
{
$self
->HEADERDATE_PARSER->parse_datetime(
$date
)
};
}
sub
date_day_utc {
my
$self
=
shift
;
my
$date
=
$self
->date;
return
unless
$date
;
$date
->
truncate
(
to
=>
"day"
);
$date
->set_time_zone(
"floating"
);
$date
->set_time_zone(
"UTC"
);
return
$date
;
}
sub
expunge {
my
$self
=
shift
;
$self
->expunged(1);
}
sub
copy_allowed {
return
1;
}
sub
copy {
my
$self
=
shift
;
my
$mailbox
=
shift
;
my
$clone
=
bless
{},
ref
$self
;
$clone
->mime(
$self
->mime );
$clone
->internaldate(
$self
->internaldate );
$clone
->_flags( {} );
$clone
->set_flag(
$_
, 1 )
for
(
'\Recent'
,
$self
->flags );
$mailbox
->add_message(
$clone
);
return
$clone
;
}
sub
session_flags {
return
(
'\Recent'
);
}
sub
_session_flags {
my
$self
=
shift
;
my
$conn
= Net::IMAP::Server->connection;
return
{}
unless
$conn
;
return
$conn
->_session_flags->{
$self
} ||= {};
}
sub
set_flag {
my
$self
=
shift
;
my
(
$flag
,
$silent
) =
@_
;
$flag
=
$FLAGS
{
lc
$flag
} ||
$flag
;
my
$hash
= (
grep
$flag
eq
$_
,
$self
->session_flags) ?
$self
->_session_flags :
$self
->_flags;
my
$old
=
exists
$hash
->{
$flag
};
$hash
->{
$flag
} = 1;
my
$changed
= not
$old
;
if
(
$changed
and not
$silent
) {
for
my
$c
(
Net::IMAP::Server->concurrent_mailbox_connections(
$self
->mailbox
)
)
{
$c
->_unsent_fetch->{
$c
->sequence(
$self
) }{FLAGS}++
unless
$c
->ignore_flags;
}
}
return
$changed
;
}
sub
clear_flag {
my
$self
=
shift
;
my
(
$flag
,
$silent
) =
@_
;
$flag
=
$FLAGS
{
lc
$flag
} ||
$flag
;
my
$hash
= (
grep
$flag
eq
$_
,
$self
->session_flags) ?
$self
->_session_flags :
$self
->_flags;
my
$old
=
exists
$hash
->{
$flag
};
delete
$hash
->{
$flag
};
my
$changed
=
$old
;
if
(
$changed
and not
$silent
) {
for
my
$c
(
Net::IMAP::Server->concurrent_mailbox_connections(
$self
->mailbox
)
)
{
$c
->_unsent_fetch->{
$c
->sequence(
$self
) }{FLAGS}++
unless
$c
->ignore_flags;
}
}
return
$changed
;
}
sub
has_flag {
my
$self
=
shift
;
my
$flag
=
shift
;
$flag
=
$FLAGS
{
lc
$flag
} ||
$flag
;
my
$hash
= (
grep
$flag
eq
$_
,
$self
->session_flags) ?
$self
->_session_flags :
$self
->_flags;
return
exists
$hash
->{
$flag
};
}
sub
flags {
my
$self
=
shift
;
my
%flags
= ( %{
$self
->_flags }, %{
$self
->_session_flags } );
return
sort
keys
%flags
;
}
sub
store {
my
$self
=
shift
;
my
(
$what
,
$flags
) =
@_
;
my
@flags
= @{
$flags
};
if
(
$what
=~ /^-/ ) {
$self
->clear_flag(
$_
)
for
grep
{
$self
->has_flag(
$_
) }
@flags
;
}
elsif
(
$what
=~ /^\+/ ) {
$self
->set_flag(
$_
)
for
grep
{ not
$self
->has_flag(
$_
) }
@flags
;
}
else
{
$self
->set_flag(
$_
)
for
grep
{ not
$self
->has_flag(
$_
) }
@flags
;
$self
->clear_flag(
$_
)
for
grep
{
$a
=
$_
;
not
grep
{
lc
$a
eq
lc
$_
}
@flags
}
$self
->flags;
}
}
sub
mime_header {
my
$self
=
shift
;
return
$self
->mime->header_obj;
}
sub
fetch {
my
$self
=
shift
;
my
$spec
=
shift
;
$spec
= [
qw/FLAGS INTERNALDATE RFC822.SIZE ENVELOPE/
]
if
uc
$spec
eq
"ALL"
;
$spec
= [
qw/FLAGS INTERNALDATE RFC822.SIZE/
]
if
uc
$spec
eq
"FAST"
;
$spec
= [
qw/FLAGS INTERNALDATE RFC822.SIZE ENVELOPE BODY/
]
if
uc
$spec
eq
"FULL"
;
my
@parts
=
ref
$spec
? @{
$spec
} :
$spec
;
if
(
grep
{
$_
=~ /^BODY\[/i }
@parts
and not
$self
->has_flag(
'\Seen'
) ) {
$self
->set_flag(
'\Seen'
, 1);
unshift
@parts
,
"FLAGS"
if
not
grep
{
uc
$_
eq
"FLAGS"
}
@parts
;
}
my
@out
;
for
my
$part
(
@parts
) {
push
@out
, \(
uc
$part
);
if
(
uc
$part
eq
"RFC822"
) {
$part
=
"BODY[]"
;
}
elsif
(
uc
$part
eq
"RFC822.HEADER"
) {
$part
=
"BODY.PEEK[HEADER]"
;
}
elsif
(
uc
$part
eq
"RFC822.TEXT"
) {
$part
=
"BODY[TEXT]"
;
}
if
(
uc
$part
eq
"UID"
) {
push
@out
,
$self
->uid;
}
elsif
(
uc
$part
eq
"INTERNALDATE"
) {
push
@out
,
$self
->internaldate;
}
elsif
(
$part
=~ /^BODY(?:\.PEEK)?\[(.*?)(?:\s+\((.*?)\))?\](?:<(\d+)(?:\.(\d+))>)?$/i
)
{
push
@out
,
$self
->mime_select( [
split
/\./, $1 ],
$3, $4, [
split
' '
, ( $2 ||
""
) ] );
${
$out
[-2] } =~ s/^BODY\.PEEK/BODY/i;
}
elsif
(
uc
$part
eq
"FLAGS"
) {
push
@out
, [
map
{ \
$_
}
$self
->flags ];
}
elsif
(
uc
$part
eq
"RFC822.SIZE"
) {
push
@out
,
length
$self
->mime_select( [],
undef
,
undef
);
}
elsif
(
uc
$part
eq
"BODY"
) {
push
@out
,
$self
->mime_bodystructure( 0 );
}
elsif
(
uc
$part
eq
"BODYSTRUCTURE"
) {
push
@out
,
$self
->mime_bodystructure( 1 );
}
elsif
(
uc
$part
eq
"ENVELOPE"
) {
push
@out
,
$self
->mime_envelope;
}
else
{
pop
@out
;
}
}
return
@out
;
}
sub
mime_select {
my
$self
=
shift
;
my
(
$sections
,
$start
,
$end
,
$extras
) =
@_
;
my
$mime
;
my
@sections
= @{
$sections
|| []};
my
$result
;
$result
=
$self
->mime->as_string
unless
@sections
;
for
(
@sections
) {
if
(
uc
$_
eq
"HEADER"
or
uc
$_
eq
"MIME"
) {
$result
= (
$mime
?
$mime
->header_obj :
$self
->mime_header )
->as_string .
"\r\n"
;
}
elsif
(
uc
$_
eq
"FIELDS"
) {
my
%case
;
my
$mime_header
=
$mime
?
$mime
->header_obj :
$self
->mime_header;
$case
{
uc
$_
} =
$_
for
$mime_header
->header_names;
my
$copy
= Email::Simple::Header->new(
""
);
for
my
$h
( @{
$extras
|| []} ) {
$copy
->header_set(
$case
{
$h
}
||
$h
=>
$mime_header
->header_raw(
$h
) );
}
$result
=
$copy
->as_string ?
$copy
->as_string .
"\r\n"
:
""
;
}
elsif
(
uc
$_
eq
"TEXT"
) {
$mime
||=
$self
->mime;
$result
=
$mime
->body_raw;
}
elsif
(
$_
=~ /^\d+$/i ) {
$mime
||=
$self
->mime;
my
@parts
=
$mime
->parts;
$mime
=
$parts
[
$_
- 1 ];
$result
=
$mime
->body_raw;
}
}
return
$result
unless
defined
$start
;
return
""
if
$start
>
length
$result
;
return
substr
(
$result
,
$start
)
unless
defined
$end
;
return
substr
(
$result
,
$start
,
$end
);
}
sub
mime_bodystructure {
my
$self
=
shift
;
my
(
$long
,
$mime
) =
@_
;
$mime
||=
$self
->mime;
my
$mime_header
=
$mime
->header_obj;
my
$data
= parse_content_type(
$mime
->content_type );
my
$dis_header
=
$mime_header
->header(
"Content-Disposition"
);
my
(
$attrs
,
$disposition
);
if
(
$dis_header
) {
(
$disposition
) = (
$dis_header
=~ /^([^;]+)/ );
$dis_header
=~ s/^
$disposition
(?:;\s*)?//;
$attrs
= Email::MIME::ContentType::_parse_attributes(
$dis_header
);
}
if
(
$data
->{discrete} eq
"multipart"
) {
my
@parts
=
$mime
->parts;
@parts
= ()
if
@parts
== 1 and
$parts
[0] ==
$mime
;
my
$parts
=
join
''
,
map
{
Net::IMAP::Server::Command->data_out(
$self
->mime_bodystructure(
$long
,
$_
) )
}
@parts
;
return
[
$parts
? \
$parts
:
undef
,
$data
->{composite},
(
$long
? ( ( %{
$data
->{attributes} }
? [ %{
$data
->{attributes} } ]
:
undef
),
(
$disposition
? [
$disposition
,
(
$attrs
&& %{
$attrs
} ? [ %{
$attrs
} ] :
undef
),
]
:
undef
),
scalar
$mime_header
->header_raw(
"Content-Language"
),
scalar
$mime_header
->header_raw(
"Content-Location"
),
)
: ()
),
];
}
else
{
my
$lines
;
my
$body
=
$mime
->body_raw;
if
(
lc
$data
->{discrete} eq
"text"
) {
$lines
= 0;
$lines
++
while
$body
=~ /\n/g;
}
return
[
$data
->{discrete},
$data
->{composite},
( %{
$data
->{attributes} }
? [ %{
$data
->{attributes} } ]
:
undef
),
scalar
$mime_header
->header_raw(
"Content-ID"
),
scalar
$mime_header
->header_raw(
"Content-Description"
),
(
scalar
$mime_header
->header_raw(
"Content-Transfer-Encoding"
) or
"7BIT"
),
length
$body
,
(
defined
$lines
? (
$lines
, )
: ()
),
(
$long
? (
scalar
$mime_header
->header_raw(
"Content-MD5"
),
(
$disposition
? [
$disposition
,
(
$attrs
&& %{
$attrs
} ? [ %{
$attrs
} ] :
undef
),
]
:
undef
),
scalar
$mime_header
->header_raw(
"Content-Language"
),
scalar
$mime_header
->header_raw(
"Content-Location"
),
)
: ()
),
];
}
}
sub
address_envelope {
my
$self
=
shift
;
my
$header
=
shift
;
my
$mime_header
=
$self
->mime_header;
return
undef
unless
$mime_header
->header(
$header
);
return
[
map
{
[ {
type
=>
"string"
,
value
=>
$_
->name },
undef
,
{
type
=>
"string"
,
value
=>
$_
->user },
{
type
=>
"string"
,
value
=>
$_
->host }
]
} Email::Address->parse(
$mime_header
->header_raw(
$header
) )
];
}
sub
mime_envelope {
my
$self
=
shift
;
my
$mime_header
=
$self
->mime_header;
return
[
scalar
$mime_header
->header_raw(
"Date"
),
scalar
$mime_header
->header_raw(
"Subject"
),
$self
->address_envelope(
"From"
),
$self
->address_envelope(
$mime_header
->header(
"Sender"
) ?
"Sender"
:
"From"
),
$self
->address_envelope(
$mime_header
->header(
"Reply-To"
) ?
"Reply-To"
:
"From"
),
$self
->address_envelope(
"To"
),
$self
->address_envelope(
"Cc"
),
$self
->address_envelope(
"Bcc"
),
scalar
$mime_header
->header_raw(
"In-Reply-To"
),
scalar
$mime_header
->header_raw(
"Message-ID"
),
];
}
sub
prep_for_destroy {
my
$self
=
shift
;
$self
->mailbox(
undef
);
}
1;