require
5.008_001;
our
$VERSION
=
'3.43'
;
use
Fcntl
qw(F_GETFL F_SETFL O_NONBLOCK)
;
use
Errno
qw(EAGAIN EBADF ECONNRESET EPIPE)
;
use
constant
APPEND_BUFFER_SIZE
=> 1024 * 1024;
Unconnected
=> 0,
Connected
=> 1,
Authenticated
=> 2,
Selected
=> 3,
};
INDEX
=> 0,
TYPE
=> 1,
DATA
=> 2,
};
my
%SEARCH_KEYS
=
map
{ (
$_
=> 1 ) }
qw(
ALL ANSWERED BCC BEFORE BODY CC DELETED DRAFT FLAGGED
FROM HEADER KEYWORD LARGER NEW NOT OLD ON OR RECENT
SEEN SENTBEFORE SENTON SENTSINCE SINCE SMALLER SUBJECT
TEXT TO UID UNANSWERED UNDELETED UNDRAFT UNFLAGGED
UNKEYWORD UNSEEN)
;
my
%Load_Module
= (
"Compress-Zlib"
=>
"Compress::Zlib"
,
"INET"
=>
"IO::Socket::INET"
,
"IP"
=>
"IO::Socket::IP"
,
"SSL"
=>
"IO::Socket::SSL"
,
"UNIX"
=>
"IO::Socket::UNIX"
,
"BodyStructure"
=>
"Mail::IMAPClient::BodyStructure"
,
"Envelope"
=>
"Mail::IMAPClient::BodyStructure::Envelope"
,
"Thread"
=>
"Mail::IMAPClient::Thread"
,
);
sub
_load_module {
my
$self
=
shift
;
my
$modkey
=
shift
;
my
$module
=
$Load_Module
{
$modkey
} ||
$modkey
;
my
$err
=
do
{
local
($@);
eval
"require $module"
;
$@;
};
if
(
$err
) {
$self
->LastError(
"Unable to load '$module': $err"
);
return
undef
;
}
return
$module
;
}
sub
_debug {
my
$self
=
shift
;
return
unless
$self
->Debug;
my
$text
=
join
''
,
@_
;
$text
=~ s/
$CRLF
/\n /og;
$text
=~ s/\s*$/\n/;
my
$fh
=
$self
->{Debug_fh} || \
*STDERR
;
print
$fh
$text
;
}
BEGIN {
foreach
my
$datum
(
qw(Authcallback Authmechanism Authuser Buffer Count Compress
Debug Debug_fh Domain Folder Ignoresizeerrors Keepalive
Maxappendstringlength Maxcommandlength Maxtemperrors
Password Peek Port Prewritemethod Proxy Ranges Readmethod
Readmoremethod Reconnectretry Server Showcredentials
Socketargs Ssl Starttls Supportedflags Timeout Uid User)
)
{
no
strict
'refs'
;
*$datum
=
sub
{
@_
> 1 ? (
$_
[0]->{
$datum
} =
$_
[1] ) :
$_
[0]->{
$datum
};
};
}
}
sub
LastError {
my
$self
=
shift
;
@_
or
return
$self
->{LastError};
my
$err
=
shift
;
if
(
defined
$err
) {
$err
=~ s/
$CRLF
$//og;
local
($!);
$self
->_debug( Carp::longmess(
"ERROR: $err"
) );
if
(
$err
=~ /NO not connected/ ) {
my
$lerr
=
$self
->{LastError} ||
""
;
my
$emsg
=
"Trying command when NOT connected!"
;
$emsg
.=
" LastError was: $lerr"
if
$lerr
;
Carp::cluck(
$emsg
);
}
}
$@ =
$self
->{LastError} =
$err
;
}
sub
Fast_io(;$) {
my
(
$self
,
$use
) =
@_
;
defined
$use
or
return
$self
->{Fast_io};
my
$socket
=
$self
->{Socket}
or
return
undef
;
local
( $@, $! );
unless
(
$use
) {
eval
{
fcntl
(
$socket
, F_SETFL,
delete
$self
->{_fcntl} ) }
if
exists
$self
->{_fcntl};
$self
->{Fast_io} = 0;
return
undef
;
}
my
$fcntl
=
eval
{
fcntl
(
$socket
, F_GETFL, 0 ) };
if
($@) {
$self
->{Fast_io} = 0;
$self
->_debug(
"not using Fast_IO; not available on this platform"
)
unless
$self
->{_fastio_warning_}++;
return
undef
;
}
$self
->{Fast_io} = 1;
my
$newflags
=
$self
->{_fcntl} =
$fcntl
;
$newflags
|= O_NONBLOCK;
fcntl
(
$socket
, F_SETFL,
$newflags
);
}
sub
EnableServerResponseInLiteral {
undef
}
sub
Wrap {
shift
->Clear(
@_
) }
my
@dow
=
qw(Sun Mon Tue Wed Thu Fri Sat)
;
my
@mnt
=
qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)
;
sub
Rfc822_date {
my
$class
=
shift
;
my
$date
=
$class
=~ /^\d+$/ ?
$class
:
shift
;
my
@date
=
gmtime
(
$date
);
sprintf
(
"%s, %02d %s %04d %02d:%02d:%02d -%04d"
,
$dow
[
$date
[6] ],
$date
[3],
$mnt
[
$date
[4] ],
$date
[5] + 1900,
$date
[2],
$date
[1],
$date
[0],
$date
[8]
);
}
sub
Rfc2060_date {
$_
[0] =~ /^\d+$/ ? Rfc3501_date(
@_
) :
shift
->Rfc3501_date(
@_
);
}
sub
Rfc3501_date {
my
$class
=
shift
;
my
$stamp
=
$class
=~ /^\d+$/ ?
$class
:
shift
;
my
@date
=
gmtime
(
$stamp
);
sprintf
(
"%02d-%s-%04d"
,
$date
[3],
$mnt
[
$date
[4] ],
$date
[5] + 1900 );
}
sub
Rfc2060_datetime($;$) {
$_
[0] =~ /^\d+$/ ? Rfc3501_datetime(
@_
) :
shift
->Rfc3501_datetime(
@_
);
}
sub
Rfc3501_datetime($;$) {
my
$class
=
shift
;
my
$stamp
=
$class
=~ /^\d+$/ ?
$class
:
shift
;
my
$zone
=
shift
||
'+0000'
;
my
@date
=
gmtime
(
$stamp
);
sprintf
(
"%02d-%s-%04d %02d:%02d:%02d %s"
,
$date
[3],
$mnt
[
$date
[4] ],
$date
[5] + 1900,
$date
[2],
$date
[1],
$date
[0],
$zone
);
}
sub
Strip_cr {
my
$class
=
shift
;
if
( !
ref
$_
[0] &&
@_
== 1 ) {
(
my
$string
=
$_
[0] ) =~ s/
$CRLF
/\n/og;
return
$string
;
}
return
wantarray
?
map
{ s/
$CRLF
/\n/og;
$_
} (
ref
$_
[0] ? @{
$_
[0] } :
@_
)
: [
map
{ s/
$CRLF
/\n/og;
$_
} (
ref
$_
[0] ? @{
$_
[0] } :
@_
) ];
}
sub
Clear {
my
(
$self
,
$clear
) =
@_
;
defined
$clear
or
return
$self
->{Clear};
my
$oldclear
=
$self
->{Clear};
$self
->{Clear} =
$clear
;
my
@keys
=
reverse
$self
->_trans_index;
for
(
my
$i
=
$clear
;
$i
<
@keys
;
$i
++ ) {
delete
$self
->{History}{
$keys
[
$i
] };
}
return
$oldclear
;
}
sub
Transaction {
shift
->Count }
sub
_remove_doubles(@) {
my
%seen
;
grep
{ !
$seen
{
$_
->{name} }++ }
@_
;
}
sub
new {
my
$class
=
shift
;
my
$self
= {
LastError
=>
""
,
Uid
=> 1,
Count
=> 0,
Clear
=> 2,
Keepalive
=> 0,
Maxappendstringlength
=> 1024**2,
Maxcommandlength
=> 1000,
Maxtemperrors
=>
undef
,
State
=> Unconnected,
Authmechanism
=>
'LOGIN'
,
Timeout
=> 600,
History
=> {},
};
while
(
@_
) {
my
$k
=
ucfirst
lc
shift
;
my
$v
=
shift
;
$self
->{
$k
} =
$v
if
defined
$v
;
}
bless
$self
,
ref
(
$class
) ||
$class
;
unless
(
exists
$self
->{Fast_io} ||
$self
->{Socket} ||
$self
->{Rawsocket} )
{
$self
->{Fast_io} = 1;
}
if
(
my
$sup
=
$self
->{Supportedflags} ) {
my
%sup
=
map
{ m/^\\?(\S+)/ ?
lc
$1 : () }
@$sup
;
$self
->{Supportedflags} = \
%sup
;
}
$self
->{Debug_fh} ||= \
*STDERR
;
CORE::
select
( (
select
(
$self
->{Debug_fh} ), $|++ )[0] );
if
(
$self
->Debug ) {
$self
->_debug(
"Started at "
.
localtime
() );
$self
->_debug(
"Using Mail::IMAPClient version $VERSION on perl $]"
);
}
$self
->Socket(
$self
->{Socket} )
if
$self
->{Socket};
if
(
$self
->{Rawsocket} ) {
my
$sock
=
delete
$self
->{Rawsocket};
$self
->RawSocket(
$sock
)
unless
$self
->{Socket};
}
if
( !
$self
->{Socket} &&
$self
->{Server} ) {
$self
->
connect
or
return
undef
;
}
return
$self
;
}
sub
connect
(@) {
my
$self
=
shift
;
%$self
= (
%$self
,
@_
)
if
@_
;
my
@sockargs
=
$self
->Timeout ? (
Timeout
=>
$self
->Timeout ) : ();
push
(
@sockargs
,
$self
->Debug ? (
Debug
=>
$self
->Debug ) : () );
if
(
$self
->Socketargs and
ref
$self
->Socketargs eq
"ARRAY"
) {
push
(
@sockargs
, @{
$self
->Socketargs } );
}
my
$server
=
$self
->Server ||
" "
;
my
$port
=
$self
->Port ||
$self
->Port(
$self
->Ssl ?
"993"
:
"143"
);
my
(
$ioclass
,
$sock
);
if
( File::Spec->file_name_is_absolute(
$server
) ) {
$ioclass
=
$self
->_load_module(
"UNIX"
);
unshift
(
@sockargs
,
Peer
=>
$server
);
}
else
{
unshift
(
@sockargs
,
PeerAddr
=>
$server
,
PeerPort
=>
$port
,
Proto
=>
"tcp"
,
);
if
(
$self
->Ssl ) {
$ioclass
=
$self
->_load_module(
"SSL"
);
push
(
@sockargs
, @{
$self
->Ssl } )
if
ref
$self
->Ssl eq
"ARRAY"
;
}
else
{
$ioclass
=
$self
->_load_module(
"IP"
);
$ioclass
=
$self
->_load_module(
"INET"
)
unless
$ioclass
;
}
}
if
(
$ioclass
) {
$self
->_debug(
"Connecting with $ioclass @sockargs"
);
$sock
=
$ioclass
->new(
@sockargs
);
}
if
(
$sock
) {
$self
->_debug(
"Connected to $server"
. ( $! ?
" errno($!)"
:
""
) );
return
$self
->Socket(
$sock
);
}
else
{
my
$lasterr
=
$self
->LastError;
if
( !
$lasterr
and
$self
->Ssl and
$ioclass
) {
$lasterr
=
$ioclass
->errstr;
}
$lasterr
||=
""
;
$self
->LastError(
"Unable to connect to $server: $lasterr"
);
return
undef
;
}
}
sub
RawSocket(;$) {
my
(
$self
,
$sock
) =
@_
;
defined
$sock
or
return
$self
->{Socket};
$self
->{Socket} =
$sock
;
$self
->{_select} = IO::Select->new(
$sock
);
delete
$self
->{_fcntl};
$self
->Fast_io(
$self
->Fast_io );
return
$sock
;
}
sub
Socket($) {
my
(
$self
,
$sock
) =
@_
;
defined
$sock
or
return
$self
->{Socket};
$self
->RawSocket(
$sock
);
$self
->State(Connected);
setsockopt
(
$sock
, SOL_SOCKET, SO_KEEPALIVE, 1 )
if
$self
->Keepalive;
my
$code
=
$self
->_get_response(
'*'
,
'PREAUTH'
) or
return
undef
;
if
(
$code
eq
'BYE'
||
$code
eq
'NO'
) {
$self
->State(Unconnected);
return
undef
;
}
elsif
(
$code
eq
'PREAUTH'
) {
$self
->State(Authenticated);
return
$self
;
}
if
(
$self
->Starttls ) {
$self
->starttls or
return
undef
;
}
if
(
defined
$self
->User &&
defined
$self
->Password ) {
$self
->login or
return
undef
;
}
return
$self
->{Socket};
}
sub
starttls {
my
(
$self
) =
@_
;
$self
->_imap_command(
"STARTTLS"
) or
return
undef
;
delete
$self
->{CAPABILITY};
my
$ioclass
=
$self
->_load_module(
"SSL"
) or
return
undef
;
my
$sock
=
$self
->RawSocket;
my
$blocking
=
$sock
->blocking;
$sock
->blocking(1);
my
@sslargs
=
(
$self
->Starttls and
ref
(
$self
->Starttls ) eq
"ARRAY"
)
? ( @{
$self
->Starttls } )
: (
Timeout
=> 30 );
unless
(
$ioclass
->start_SSL(
$sock
,
@sslargs
) ) {
$self
->LastError(
"Unable to start TLS: "
.
$ioclass
->errstr );
return
undef
;
}
$sock
->blocking(
$blocking
);
return
$self
;
}
sub
compress {
my
(
$self
) =
@_
;
$self
->_imap_command(
"COMPRESS DEFLATE"
) or
return
undef
;
my
$zcl
=
$self
->_load_module(
"Compress-Zlib"
) or
return
undef
;
$self
->Compress(
[
-WindowBits
=> -
$zcl
->MAX_WBITS(),
-Level
=>
$zcl
->Z_BEST_SPEED()
]
)
unless
(
$self
->Compress and
ref
(
$self
->Compress ) eq
"ARRAY"
);
my
(
$rc
,
$do
,
$io
);
(
$do
,
$rc
) = Compress::Zlib::deflateInit( @{
$self
->Compress } );
unless
(
$rc
==
$zcl
->Z_OK ) {
$self
->LastError(
"deflateInit failed (rc=$rc)"
);
return
undef
;
}
(
$io
,
$rc
) =
Compress::Zlib::inflateInit(
-WindowBits
=> -
$zcl
->MAX_WBITS() );
unless
(
$rc
==
$zcl
->Z_OK ) {
$self
->LastError(
"inflateInit failed (rc=$rc)"
);
return
undef
;
}
$self
->{Prewritemethod} =
sub
{
my
(
$self
,
$string
) =
@_
;
my
(
$rc
,
$out1
,
$out2
);
(
$out1
,
$rc
) =
$do
->deflate(
$string
);
(
$out2
,
$rc
) =
$do
->flush(
$zcl
->Z_PARTIAL_FLUSH() )
unless
(
$rc
!=
$zcl
->Z_OK );
unless
(
$rc
==
$zcl
->Z_OK ) {
$self
->LastError(
"deflate/flush failed (rc=$rc)"
);
return
undef
;
}
return
$out1
.
$out2
;
};
my
(
$Zbuf
,
$Ibuf
) = (
""
,
""
);
$self
->{Readmoremethod} =
sub
{
my
$self
=
shift
;
return
1
if
(
length
(
$Zbuf
) ||
length
(
$Ibuf
) );
$self
->__read_more(
@_
);
};
$self
->{Readmethod} =
sub
{
my
(
$self
,
$fh
,
$buf
,
$len
,
$off
) =
@_
;
my
(
$lz
,
$li
) = (
length
$Zbuf
,
length
$Ibuf
);
if
(
$lz
|| !
$li
) {
my
$ret
=
sysread
(
$fh
,
$Zbuf
,
$len
|| 4096,
length
$Zbuf
);
$lz
=
length
$Zbuf
;
return
$ret
if
( !
$ret
&& !
$lz
);
}
if
(
$lz
) {
my
(
$tbuf
,
$rc
) =
$io
->inflate( \
$Zbuf
);
unless
(
$rc
==
$zcl
->Z_OK ) {
$self
->LastError(
"inflate failed (rc=$rc)"
);
return
undef
;
}
$Ibuf
.=
$tbuf
;
$li
=
length
$Ibuf
;
}
if
( !
$li
) {
$! = EAGAIN;
return
undef
;
}
my
$tbuf
=
substr
(
$Ibuf
, 0,
$len
);
substr
(
$Ibuf
, 0,
$len
) =
""
;
substr
(
$$buf
,
$off
) =
$tbuf
;
return
length
$tbuf
;
};
return
$self
;
}
sub
login {
my
$self
=
shift
;
my
$auth
=
$self
->Authmechanism;
if
(
$auth
&&
$auth
ne
'LOGIN'
) {
$self
->authenticate(
$auth
,
$self
->Authcallback )
or
return
undef
;
}
else
{
my
$user
=
$self
->User;
my
$passwd
=
$self
->Password;
return
undef
unless
(
defined
(
$passwd
) and
defined
(
$user
) );
$user
=
$self
->Quote(
$user
);
if
(
$user
=~ /^{/ ) {
my
$nopasswd
= (
$passwd
eq
""
) ? 1 : 0;
$passwd
=
$self
->Quote(
$passwd
, 1 );
$passwd
.=
$CRLF
if
(
$nopasswd
);
}
else
{
$passwd
=
$self
->Quote(
$passwd
);
}
$self
->_imap_command(
"LOGIN $user $passwd"
)
or
return
undef
;
}
$self
->State(Authenticated);
if
(
$self
->Compress ) {
$self
->compress or
return
undef
;
}
return
$self
;
}
sub
noop {
my
(
$self
) =
@_
;
$self
->_imap_command(
"NOOP"
) ?
$self
->Results :
undef
;
}
sub
proxyauth {
my
(
$self
,
$user
) =
@_
;
$user
=
$self
->Quote(
$user
);
$self
->_imap_command(
"PROXYAUTH $user"
) ?
$self
->Results :
undef
;
}
sub
separator {
my
(
$self
,
$target
) =
@_
;
unless
(
defined
$target
) {
my
$ns
=
$self
->namespace or
return
undef
;
if
(
$ns
) {
my
$sep
=
$ns
->[0][0][1];
return
$sep
if
$sep
;
}
$target
=
''
;
}
return
$self
->{separators}{
$target
}
if
exists
$self
->{separators}{
$target
};
my
$list
=
$self
->list(
undef
,
$target
) or
return
undef
;
foreach
my
$line
(
@$list
) {
my
$rec
=
$self
->_list_or_lsub_response_parse(
$line
);
next
unless
defined
$rec
->{name};
$self
->{separators}{
$rec
->{name} } =
$rec
->{delim};
}
return
$self
->{separators}{
$target
};
}
sub
sort
{
my
(
$self
,
$crit
,
@a
) =
@_
;
$crit
=~ /^\(.*\)$/
or
$crit
=
"($crit)"
;
my
@hits
;
if
(
$self
->_imap_uid_command(
SORT
=>
$crit
,
@a
) ) {
my
@results
=
$self
->History;
foreach
(
@results
) {
chomp
;
s/
$CR
$//;
s/^\*\s+SORT\s+// or
next
;
push
@hits
,
grep
/\d/,
split
;
}
}
return
wantarray
?
@hits
: \
@hits
;
}
sub
_list_or_lsub {
my
(
$self
,
$cmd
,
$reference
,
$target
) =
@_
;
defined
$reference
or
$reference
=
''
;
defined
$target
or
$target
=
'*'
;
length
$target
or
$target
=
'""'
;
$target
eq
'*'
||
$target
eq
'""'
or
$target
=
$self
->Quote(
$target
);
$self
->_imap_command(
qq($cmd "$reference" $target)
)
or
return
undef
;
return
wantarray
?
$self
->Escaped_history :
$self
->Escaped_results;
}
sub
list {
shift
->_list_or_lsub(
"LIST"
,
@_
) }
sub
lsub {
shift
->_list_or_lsub(
"LSUB"
,
@_
) }
sub
xlist {
my
(
$self
) =
@_
;
return
undef
unless
$self
->has_capability(
"XLIST"
);
shift
->_list_or_lsub(
"XLIST"
,
@_
);
}
sub
_folders_or_subscribed {
my
(
$self
,
$method
,
$what
) =
@_
;
my
@folders
;
do
{
{
my
@list
;
if
(
$what
) {
my
$sep
=
$self
->separator(
$what
) ||
$self
->separator(
undef
);
last
unless
defined
$sep
;
my
$whatsub
=
$what
=~ m/\Q${sep}\E$/ ?
"$what*"
:
"$what$sep*"
;
my
$tref
=
$self
->
$method
(
undef
,
$whatsub
) or
last
;
shift
@$tref
;
push
@list
,
@$tref
;
my
$cansel
=
$self
->selectable(
$what
);
last
unless
defined
$cansel
;
if
(
$cansel
) {
$tref
=
$self
->
$method
(
undef
,
$what
) or
last
;
shift
@$tref
;
push
@list
,
@$tref
;
}
}
else
{
my
$tref
=
$self
->
$method
(
undef
,
undef
) or
last
;
shift
@$tref
;
push
@list
,
@$tref
;
}
foreach
my
$resp
(
@list
) {
my
$rec
=
$self
->_list_or_lsub_response_parse(
$resp
);
next
unless
defined
$rec
->{name};
next
if
first {
lc
(
$_
) eq
'\noselect'
} @{
$rec
->{attrs} };
push
@folders
,
$rec
;
}
}
};
my
@clean
= _remove_doubles
@folders
;
return
wantarray
?
@clean
: \
@clean
;
}
sub
folders {
my
(
$self
,
$what
) =
@_
;
my
@folders
=
map
(
$_
->{name},
$self
->_folders_or_subscribed(
"list"
,
$what
) );
return
wantarray
?
@folders
: \
@folders
;
}
sub
folders_hash {
my
(
$self
,
$what
) =
@_
;
my
@folders_hash
=
$self
->_folders_or_subscribed(
"list"
,
$what
);
return
wantarray
?
@folders_hash
: \
@folders_hash
;
}
sub
xlist_folders {
my
(
$self
) =
@_
;
my
$xlist
=
$self
->xlist;
return
undef
unless
defined
$xlist
;
my
%xlist
;
my
$xlist_re
=
qr/\A\\(Inbox|AllMail|Trash|Drafts|Sent|Spam|Starred)\Z/
;
for
my
$resp
(
@$xlist
) {
my
$rec
=
$self
->_list_or_lsub_response_parse(
$resp
);
next
unless
defined
$rec
->{name};
for
my
$attr
( @{
$rec
->{attrs} } ) {
$xlist
{$1} =
$rec
->{name}
if
(
$attr
=~
$xlist_re
);
}
}
return
wantarray
?
%xlist
: \
%xlist
;
}
sub
subscribed {
my
(
$self
,
$what
) =
@_
;
my
@folders
=
map
(
$_
->{name},
$self
->_folders_or_subscribed(
"lsub"
,
$what
) );
return
wantarray
?
@folders
: \
@folders
;
}
sub
deleteacl {
my
(
$self
,
$target
,
$user
) =
@_
;
$target
=
$self
->Quote(
$target
);
$user
=
$self
->Quote(
$user
);
$self
->_imap_command(
qq(DELETEACL $target $user)
)
or
return
undef
;
return
wantarray
?
$self
->History :
$self
->Results;
}
sub
setacl {
my
(
$self
,
$target
,
$user
,
$acl
) =
@_
;
$target
||=
$self
->Folder;
$target
=
$self
->Quote(
$target
);
$user
||=
$self
->User;
$user
=
$self
->Quote(
$user
);
$acl
=
$self
->Quote(
$acl
);
$self
->_imap_command(
qq(SETACL $target $user $acl)
)
or
return
undef
;
return
wantarray
?
$self
->History :
$self
->Results;
}
sub
getacl {
my
(
$self
,
$target
) =
@_
;
defined
$target
or
$target
=
$self
->Folder;
my
$mtarget
=
$self
->Quote(
$target
);
$self
->_imap_command(
qq(GETACL $mtarget)
)
or
return
undef
;
my
@history
=
$self
->History;
my
$hash
;
for
(
my
$x
= 0 ;
$x
<
@history
;
$x
++ ) {
next
if
$history
[
$x
] !~ /^\* ACL/;
my
$perm
=
$history
[
$x
] =~ /^\* ACL $/
?
$history
[ ++
$x
] .
$history
[ ++
$x
]
:
$history
[
$x
];
$perm
=~ s/\s?
$CRLF
$//o;
until
(
$perm
=~ /\Q
$target
\E"?$/ || !
$perm
) {
$perm
=~ s/\s([^\s]+)\s?$// or
last
;
my
$p
= $1;
$perm
=~ s/\s([^\s]+)\s?$// or
last
;
my
$u
= $1;
$hash
->{
$u
} =
$p
;
$self
->_debug(
"Permissions: $u => $p"
);
}
}
return
$hash
;
}
sub
listrights {
my
(
$self
,
$target
,
$user
) =
@_
;
$target
||=
$self
->Folder;
$target
=
$self
->Quote(
$target
);
$user
||=
$self
->User;
$user
=
$self
->Quote(
$user
);
$self
->_imap_command(
qq(LISTRIGHTS $target $user)
)
or
return
undef
;
my
$resp
= first { /^\* LISTRIGHTS/ }
$self
->History;
my
@rights
=
split
/\s/,
$resp
;
my
$rights
=
join
''
,
@rights
[ 4 ..
$#rights
];
$rights
=~ s/"//g;
return
wantarray
?
split
( //,
$rights
) :
$rights
;
}
sub
select
{
my
(
$self
,
$target
) =
@_
;
defined
$target
or
return
undef
;
my
$qqtarget
=
$self
->Quote(
$target
);
my
$old
=
$self
->Folder;
$self
->_imap_command(
"SELECT $qqtarget"
)
or
return
undef
;
$self
->State(Selected);
$self
->Folder(
$target
);
return
$old
||
$self
;
}
sub
message_string {
my
(
$self
,
$msg
) =
@_
;
return
undef
unless
defined
$self
->imap4rev1;
my
$peek
=
$self
->Peek ?
'.PEEK'
:
''
;
my
$cmd
=
$self
->imap4rev1 ?
"BODY$peek\[]"
:
"RFC822$peek"
;
my
$string
;
$self
->message_to_file( \
$string
,
$msg
);
unless
(
$self
->Ignoresizeerrors ) {
my
$expected_size
=
$self
->size(
$msg
);
return
undef
unless
defined
$expected_size
;
if
(
length
(
$string
) !=
$expected_size
) {
$self
->LastError(
"message_string() "
.
"expected $expected_size bytes but received "
.
length
(
$string
)
.
" you may need the IgnoreSizeErrors option"
);
return
undef
;
}
}
return
$string
;
}
sub
bodypart_string {
my
(
$self
,
$msg
,
$partno
,
$bytes
,
$offset
) =
@_
;
unless
(
$self
->imap4rev1 ) {
$self
->LastError(
"Unable to get body part; server "
.
$self
->Server
.
" does not support IMAP4REV1"
)
unless
$self
->LastError;
return
undef
;
}
$offset
||= 0;
my
$cmd
=
"BODY"
. (
$self
->Peek ?
'.PEEK'
:
''
)
.
"[$partno]"
. (
$bytes
?
"<$offset.$bytes>"
:
''
);
$self
->fetch(
$msg
,
$cmd
)
or
return
undef
;
$self
->_transaction_literals;
}
sub
message_to_file {
my
(
$self
,
$file
,
@msgs
) =
@_
;
my
$fh
;
if
(
ref
$file
and
ref
$file
ne
"SCALAR"
) {
$fh
=
$file
;
}
else
{
$$file
=
""
if
(
ref
$file
eq
"SCALAR"
and !
defined
$$file
);
local
($!);
open
(
$fh
,
">>"
,
$file
);
unless
(
defined
(
$fh
) ) {
$self
->LastError(
"Unable to open file '$file': $!"
);
return
undef
;
}
}
binmode
(
$fh
);
unless
(
@msgs
) {
$self
->LastError(
"message_to_file: NO messages specified!"
);
return
undef
;
}
my
$peek
=
$self
->Peek ?
'.PEEK'
:
''
;
$peek
=
sprintf
(
$self
->imap4rev1 ?
"BODY%s\[]"
:
"RFC822%s"
,
$peek
);
my
@args
= (
join
(
","
,
@msgs
),
$peek
);
return
$self
->_imap_uid_command( {
outref
=>
$fh
},
"FETCH"
=>
@args
)
?
$self
:
undef
;
}
sub
message_uid {
my
(
$self
,
$msg
) =
@_
;
my
$ref
=
$self
->fetch(
$msg
,
"UID"
) or
return
undef
;
foreach
(
@$ref
) {
return
$1
if
m/\(UID\s+(\d+)\s*\)
$CR
?$/o;
}
return
undef
;
}
sub
migrate {
my
(
$self
,
$peer
,
$msgs
,
$folder
) =
@_
;
unless
(
$peer
and
$peer
->IsConnected ) {
$self
->LastError( (
$peer
?
"Invalid"
:
"Unconnected"
)
.
" target "
.
ref
(
$self
)
.
" object in migrate()"
. (
$peer
? (
": "
.
$peer
->LastError ) :
""
) );
return
undef
;
}
if
(
$self
eq
$peer
) {
$self
->LastError(
"dest must not be the same object as self"
);
return
undef
;
}
$folder
=
$self
->Folder
unless
(
defined
$folder
);
unless
(
$folder
) {
$self
->LastError(
"No folder selected on source mailbox."
);
return
undef
;
}
unless
(
$peer
->
exists
(
$folder
) or
$peer
->create(
$folder
) ) {
$self
->LastError(
"Create folder '$folder' on target host failed: "
.
$peer
->LastError );
return
undef
;
}
if
( !
defined
$msgs
or
uc
(
$msgs
) eq
"ALL"
) {
$msgs
=
$self
->search(
"ALL"
) or
return
undef
;
}
my
@headers
=
qw(RFC822.SIZE INTERNALDATE FLAGS)
;
my
$range
=
$self
->Range(
$msgs
);
$self
->_debug(
"Messages to migrate from '$folder': $range"
);
foreach
my
$mid
(
$range
->unfold ) {
my
$minfo
=
$self
->fetch_hash(
$mid
,
@headers
)
or
return
undef
;
my
(
$size
,
$date
) = @{
$minfo
->{
$mid
} }{
@headers
};
return
undef
unless
(
defined
$size
and
defined
$date
);
$self
->_debug(
"Copy message $mid (sz=$size,dt=$date) from '$folder'"
);
my
@flags
=
grep
!/\\Recent/i,
$self
->flags(
$mid
);
my
$flags
=
join
' '
,
$peer
->supported_flags(
@flags
);
my
$msg
;
$self
->message_to_file( \
$msg
,
$mid
)
or
return
undef
;
my
$newid
=
$peer
->append_file(
$folder
, \
$msg
,
undef
,
$flags
,
$date
);
unless
(
defined
$newid
) {
$self
->LastError(
"Append to '$folder' on target failed: "
.
$peer
->LastError );
return
undef
;
}
$self
->_debug(
"Copied UID $mid in '$folder' to target UID $newid"
);
}
return
$self
;
}
sub
_optimal_sleep($$$) {
my
(
$self
,
$maxwrite
,
$waittime
,
$last5writes
) =
@_
;
push
@$last5writes
,
$waittime
;
shift
@$last5writes
if
@$last5writes
> 5;
my
$bufferavail
= ( sum
@$last5writes
) /
@$last5writes
;
if
(
$bufferavail
< .4 *
$maxwrite
) {
$waittime
*= 1.3;
}
elsif
(
$bufferavail
> .9 *
$maxwrite
) {
$waittime
*= .5;
}
CORE::
select
(
undef
,
undef
,
undef
,
$waittime
);
$waittime
;
}
sub
body_string {
my
(
$self
,
$msg
) =
@_
;
my
$ref
=
$self
->fetch(
$msg
,
"BODY"
. (
$self
->Peek ?
".PEEK"
:
""
) .
"[TEXT]"
)
or
return
undef
;
my
$string
=
join
''
,
map
{
$_
->[DATA] }
grep
{
$self
->_is_literal(
$_
) }
@$ref
;
return
$string
if
$string
;
my
$head
;
while
(
$head
=
shift
@$ref
) {
$self
->_debug(
"body_string: head = '$head'"
);
last
if
$head
=~
/(?:.
*FETCH
.*\(.
*BODY
\[TEXT\])|(?:^\d+ BAD )|(?:^\d NO )/i;
}
unless
(
@$ref
) {
$self
->LastError(
"Unable to parse server response from "
.
$self
->LastIMAPCommand );
return
undef
;
}
my
$popped
;
$popped
=
pop
@$ref
until
(
$popped
&&
$popped
=~ /^\)
$CRLF
$/o )
|| !
grep
/^\)
$CRLF
$/o,
@$ref
;
if
(
$head
=~ /BODY\[TEXT\]\s*$/i ) {
$string
.=
shift
@$ref
while
@$ref
;
$self
->_debug(
"String is now $string"
)
if
$self
->Debug;
}
$string
;
}
sub
examine {
my
(
$self
,
$target
) =
@_
;
defined
$target
or
return
undef
;
$self
->_imap_command(
'EXAMINE '
.
$self
->Quote(
$target
) )
or
return
undef
;
my
$old
=
$self
->Folder;
$self
->Folder(
$target
);
$self
->State(Selected);
$old
||
$self
;
}
sub
idle {
my
$self
=
shift
;
my
$good
=
'+'
;
my
$count
=
$self
->Count + 1;
$self
->_imap_command(
"IDLE"
,
$good
) ?
$count
:
undef
;
}
sub
idle_data {
my
$self
=
shift
;
my
$timeout
=
scalar
(
@_
) ?
shift
: 0;
my
$socket
=
$self
->Socket;
my
$trans_c1
=
$self
->_next_index;
my
(
$rc
,
$ret
);
do
{
$ret
=
$self
->_read_more( {
error_on_timeout
=> 0 },
$socket
,
$timeout
);
$rc
=
$ret
if
( !
defined
(
$rc
) or
$ret
< 0 );
if
(
$ret
> 0 ) {
$self
->_get_response(
'*'
,
qr/(?!BAD|BYE|NO)(?:\d+\s+\w+|\S+)/
)
or
return
undef
;
$timeout
= 0;
}
}
while
$ret
> 0 and
$self
->IsConnected;
return
undef
if
$rc
< 0;
my
$trans_c2
=
$self
->_next_index;
my
@res
;
if
(
$trans_c1
<
$trans_c2
) {
@res
=
$self
->Results;
@res
=
@res
[
$trans_c1
.. (
$trans_c2
- 1 ) ];
}
return
wantarray
?
@res
: \
@res
;
}
sub
done {
my
$self
=
shift
;
my
$count
=
shift
||
$self
->Count;
$self
->_imap_command(
{
addtag
=> 0,
tag
=>
qr/(?:$count|DONE)/
,
doretry
=> 0 },
"DONE"
)
or
return
undef
;
return
$self
->Results;
}
sub
tag_and_run {
my
$self
=
shift
;
$self
->_imap_command(
@_
) or
return
undef
;
return
$self
->Results;
}
sub
reconnect {
my
$self
=
shift
;
if
(
$self
->IsAuthenticated ) {
$self
->_debug(
"reconnect called but already authenticated"
);
return
1;
}
if
(
$self
->{_doing_reconnect} ) {
$self
->_debug(
"recursive call to reconnect, returning 0\n"
);
$self
->LastError(
"unexpected reconnect recursion"
)
unless
$self
->LastError;
return
0;
}
my
$einfo
=
$self
->LastError ||
""
;
$self
->_debug(
"reconnecting to "
,
$self
->Server,
", last error: $einfo"
);
$self
->{_doing_reconnect} = 1;
my
$ret
;
if
(
$self
->
connect
) {
$ret
= 1;
if
(
defined
$self
->Folder ) {
$ret
=
defined
(
$self
->
select
(
$self
->Folder ) ) ? 1 :
undef
;
}
}
delete
$self
->{_doing_reconnect};
return
$ret
? 1 :
$ret
;
}
sub
_imap_command {
my
$self
=
shift
;
my
$opt
=
ref
(
$_
[0] ) eq
"HASH"
?
$_
[0] : {};
my
$tries
= 0;
my
$retry
=
$self
->Reconnectretry || 0;
my
(
$rc
,
@err
);
while
(
$tries
++ <=
$retry
) {
if
(
$tries
== 1 or
$self
->IsConnected ) {
$rc
=
$self
->_imap_command_do(
@_
);
push
(
@err
,
$self
->LastError )
if
$self
->LastError;
}
if
( !
defined
(
$rc
) and
$retry
and
$self
->IsUnconnected ) {
last
unless
(
$! == EPIPE
or $! == ECONNRESET
or
$self
->LastError =~ /(?:error\(.*?\)|timeout) waiting\b/
or
$self
->LastError =~ /(?:
socket
closed|\* BYE)\b/
);
my
$ret
=
$self
->reconnect;
if
(
$ret
) {
$self
->_debug(
"reconnect success($ret) on try #$tries/$retry"
);
last
if
exists
$opt
->{doretry} and !
$opt
->{doretry};
}
elsif
(
defined
$ret
and
$ret
== 0 ) {
return
undef
;
}
else
{
$self
->_debug(
"reconnect failure on try #$tries/$retry"
);
push
(
@err
,
$self
->LastError )
if
$self
->LastError;
}
}
else
{
last
;
}
}
unless
(
$rc
) {
my
(
%seen
,
@keep
,
@info
);
foreach
my
$str
(
@err
) {
my
(
$sz
,
$len
) = ( 96,
length
(
$str
) );
$str
=~ s/
$CR
?
$LF
$/\\n/omg;
if
( !
$self
->Debug and
$len
>
$sz
* 2 ) {
my
$beg
=
substr
(
$str
, 0,
$sz
);
my
$end
=
substr
(
$str
, -
$sz
,
$sz
);
$str
=
$beg
.
"..."
.
$end
;
}
next
if
$seen
{
$str
}++;
push
(
@keep
,
$str
);
}
foreach
my
$msg
(
@keep
) {
push
(
@info
,
$msg
. (
$seen
{
$msg
} > 1 ?
" ($seen{$msg}x)"
:
""
) );
}
$self
->LastError(
join
(
"; "
,
@info
) );
}
return
$rc
;
}
sub
_imap_command_do {
my
$self
=
shift
;
my
$opt
=
ref
(
$_
[0] ) eq
"HASH"
?
shift
: {};
my
$string
=
shift
or
return
undef
;
my
$good
=
shift
;
my
@gropt
= (
$opt
->{outref} ? {
outref
=>
$opt
->{outref} } : () );
$opt
->{addcrlf} = 1
unless
exists
$opt
->{addcrlf};
$opt
->{addtag} = 1
unless
exists
$opt
->{addtag};
if
(
$self
->LastError ) {
$self
->LastError(
undef
);
}
my
$clear
=
$self
->Clear;
$self
->Clear(
$clear
)
if
$self
->Count >=
$clear
&&
$clear
> 0;
my
$count
=
$self
->Count(
$self
->Count + 1 );
my
$tag
=
$opt
->{tag} ||
$count
;
$string
=
"$tag $string"
if
$opt
->{addtag};
my
$logstr
= (
$string
=~ /^(
$tag
\s+APPEND\s+.*?)
$CR
?
$LF
/ ) ? $1 :
$string
;
$self
->_record(
$count
, [ 0,
"INPUT"
,
$logstr
] );
unless
(
$self
->_send_line(
$string
,
$opt
->{addcrlf} ? 0 : 1 ) ) {
$self
->LastError(
"Error sending '$logstr': "
.
$self
->LastError );
return
undef
;
}
my
$code
=
$self
->_get_response(
@gropt
,
$tag
,
$good
) or
return
undef
;
if
(
$code
eq
'OK'
) {
return
$self
;
}
elsif
(
$good
and
$code
eq
$good
) {
return
$self
;
}
else
{
return
undef
;
}
}
sub
_response_code_sub {
my
(
$self
,
$tag
,
$good
) =
@_
;
my
$qtag
=
ref
(
$tag
) ?
$tag
:
defined
(
$tag
) ?
quotemeta
(
$tag
) :
undef
;
my
$qgood
=
ref
(
$good
) ?
$good
:
defined
(
$good
) ?
quotemeta
(
$good
) :
undef
;
my
$getcodesub
=
sub
{
if
(
defined
$qgood
) {
if
(
$good
eq
'+'
and
$_
[0] =~ /^
$qgood
/ ) {
return
(
$good
);
}
if
(
defined
$qtag
and
$_
[0] =~ /^
$qtag
\s+(
$qgood
)/i ) {
return
(
ref
(
$qgood
) ? $1 :
uc
($1) );
}
}
if
(
defined
$qtag
) {
if
(
$tag
eq
'+'
and
$_
[0] =~ /^
$qtag
/ ) {
return
(
$tag
);
}
if
(
$_
[0] =~ /^
$qtag
\s+(OK|BAD|NO)\b/i ) {
my
$code
=
uc
($1);
$self
->LastError(
$_
[0] )
unless
(
$code
eq
'OK'
);
return
(
$code
);
}
}
if
(
$_
[0] =~ /^\*\s+(BYE)\b/i ) {
return
(
uc
($1),
$_
[0] );
}
return
(
undef
);
};
return
$getcodesub
;
}
sub
_get_response {
my
$self
=
shift
;
my
$opt
=
ref
(
$_
[0] ) eq
"HASH"
?
shift
: {};
my
$tag
=
shift
;
my
$good
=
shift
;
my
$outref
=
$opt
->{outref};
my
@readopt
=
defined
(
$outref
) ? (
$outref
) : ();
my
$getcode
=
$self
->_response_code_sub(
$tag
,
$good
);
my
(
$count
,
$out
,
$code
,
$byemsg
) = (
$self
->Count, [],
undef
,
undef
);
until
(
defined
$code
) {
my
$output
=
$self
->_read_line(
@readopt
) or
return
undef
;
$out
=
$output
;
foreach
my
$o
(
@$output
) {
$self
->_record(
$count
,
$o
);
$self
->_is_output(
$o
) or
next
;
my
(
$tcode
,
$tbyemsg
) =
$getcode
->(
$o
->[DATA] );
$code
=
$tcode
if
(
defined
$tcode
);
$byemsg
=
$tbyemsg
if
(
defined
$tbyemsg
);
}
}
if
(
defined
$code
) {
$code
=~ s/
$CR
?
$LF
?$//o;
$code
=
uc
(
$code
)
unless
(
$good
and
$code
eq
$good
);
if
(
$code
eq
'BYE'
) {
$self
->State(Unconnected);
if
(
$byemsg
) {
$self
->LastError(
$byemsg
)
unless
(
$good
and
$code
eq
$good
);
}
}
}
elsif
( !
$self
->LastError ) {
my
$info
=
"unexpected response: "
.
join
(
" "
,
@$out
);
$self
->LastError(
$info
);
}
return
$code
;
}
sub
_imap_uid_command {
my
$self
=
shift
;
my
@opt
=
ref
(
$_
[0] ) eq
"HASH"
? (
shift
) : ();
my
$cmd
=
shift
;
my
$args
=
@_
?
join
(
" "
,
''
,
@_
) :
''
;
my
$uid
=
$self
->Uid ?
'UID '
:
''
;
$self
->_imap_command(
@opt
,
"$uid$cmd$args"
);
}
sub
run {
my
$self
=
shift
;
my
$string
=
shift
or
return
undef
;
my
$tag
=
$string
=~ /^(\S+) / ? $1 :
undef
;
unless
(
$tag
) {
$self
->LastError(
"No tag found in string passed to run(): $string"
);
return
undef
;
}
$self
->_imap_command( {
addtag
=> 0,
addcrlf
=> 0,
tag
=>
$tag
},
$string
)
or
return
undef
;
$self
->{History}{
$tag
} =
$self
->{History}{
$self
->Count }
unless
$tag
eq
$self
->Count;
return
$self
->Results;
}
sub
_record {
my
(
$self
,
$count
,
$array
) =
@_
;
if
(
$array
->[DATA] =~ /^\d+ LOGIN/i && !
$self
->Showcredentials ) {
$array
->[DATA] =~ s/LOGIN.*/LOGIN XXXXXXXX XXXXXXXX/i;
}
push
@{
$self
->{History}{
$count
} },
$array
;
}
sub
_redact_line {
my
(
$self
,
$string
) =
@_
;
$self
->Showcredentials and
return
undef
;
my
(
$tag
,
$cmd
) = (
$self
->Count,
undef
);
my
$retext
=
"[Redact: Count=$tag Showcredentials=OFF]"
;
my
$show
=
$retext
;
if
(
$string
=~ s/^(
$tag
\s+(\S+)\s+)// ) {
(
$show
,
$cmd
) = ( $1, $2 );
if
(
$cmd
=~ /login/i ) {
if
(
$string
=~ /^{/ ) {
$show
.=
$string
;
}
elsif
(
$string
=~ s/^((?:
"(?>(?:(?>[^"
\\]+)|\\.)*)"|\S+)\s*)// ) {
$show
.= $1;
$show
.= (
$string
=~ /^{/ ) ?
$string
:
$retext
;
}
}
elsif
(
$cmd
=~ /^auth/i ) {
$show
.=
$string
;
}
else
{
return
undef
;
}
}
return
$show
;
}
sub
_send_line {
my
(
$self
,
$string
,
$suppress
) =
@_
;
$string
=~ s/
$CR
?
$LF
?$/
$CRLF
/o
unless
$suppress
;
if
(
$string
=~ s/^([^
$LF
\{]*\{\d+\}
$CRLF
)(?=.)//o ) {
my
$first
= $1;
if
(
$self
->Debug ) {
my
$dat
=
(
$self
->IsConnected and !
$self
->IsAuthenticated )
?
$self
->_redact_line(
$string
)
:
undef
;
$self
->_debug(
"Sending literal: $first\tthen: "
,
$dat
||
$string
);
}
$self
->_send_line(
$first
) or
return
undef
;
my
$code
=
$self
->_get_response(
$self
->Count,
'+'
) or
return
undef
;
return
undef
unless
$code
eq
'+'
;
}
if
(
my
$prew
=
$self
->Prewritemethod ) {
$string
=
$prew
->(
$self
,
$string
);
}
if
(
$self
->Debug ) {
my
$dat
=
(
$self
->IsConnected and !
$self
->IsAuthenticated )
?
$self
->_redact_line(
$string
)
:
undef
;
$self
->_debug(
"Sending: "
,
$dat
||
$string
);
}
unless
(
$self
->IsConnected ) {
$self
->LastError(
"NO not connected"
);
return
undef
;
}
$self
->_send_bytes( \
$string
);
}
sub
_send_bytes($) {
my
(
$self
,
$byteref
) =
@_
;
my
(
$total
,
$temperrs
,
$maxwrite
) = ( 0, 0, 0 );
my
$waittime
= .02;
my
@previous_writes
;
my
$maxagain
=
$self
->Maxtemperrors;
undef
$maxagain
if
$maxagain
and
lc
(
$maxagain
) eq
'unlimited'
;
local
$SIG
{PIPE} =
'IGNORE'
;
my
$socket
=
$self
->Socket;
while
(
$total
<
length
$$byteref
) {
my
$written
=
syswrite
(
$socket
,
$$byteref
,
length
(
$$byteref
) -
$total
,
$total
);
if
(
defined
$written
) {
$temperrs
= 0;
$total
+=
$written
;
next
;
}
if
( $! == EAGAIN ) {
if
(
defined
$maxagain
&&
$temperrs
++ >
$maxagain
) {
$self
->LastError(
"Persistent error '$!'"
);
return
undef
;
}
$waittime
=
$self
->_optimal_sleep(
$maxwrite
,
$waittime
, \
@previous_writes
);
next
;
}
my
$emsg
= $! ?
"$!"
:
"no error caught"
;
$self
->State(Unconnected)
if
( $! == EPIPE or $! == ECONNRESET or $! == EBADF );
$self
->LastError(
"Write failed '$emsg'"
);
return
undef
;
}
$self
->_debug(
"Sent $total bytes"
);
return
$total
;
}
sub
_read_line {
my
(
$self
,
$literal_callback
) =
@_
;
my
$socket
=
$self
->Socket;
unless
(
$self
->IsConnected &&
$socket
) {
$self
->LastError(
"NO not connected"
);
return
undef
;
}
my
$iBuffer
=
""
;
my
$oBuffer
= [];
my
$index
=
$self
->_next_index;
my
$timeout
=
$self
->Timeout;
my
$readlen
=
$self
->Buffer || 4096;
my
$transno
=
$self
->Transaction;
my
$literal_cbtype
=
""
;
if
(
$literal_callback
) {
if
( UNIVERSAL::isa(
$literal_callback
,
"GLOB"
) ) {
$literal_cbtype
=
"GLOB"
;
}
elsif
( UNIVERSAL::isa(
$literal_callback
,
"CODE"
) ) {
$literal_cbtype
=
"CODE"
;
}
else
{
$self
->LastError(
"'$literal_callback' is an "
.
"invalid callback; must be a filehandle or CODE"
);
return
undef
;
}
}
my
$temperrs
= 0;
my
$maxagain
=
$self
->Maxtemperrors;
undef
$maxagain
if
$maxagain
and
lc
(
$maxagain
) eq
'unlimited'
;
until
(
@$oBuffer
&&
$oBuffer
->[-1][TYPE] eq
'OUTPUT'
&&
$oBuffer
->[-1][DATA] =~
/
$CR
?
$LF
$/o
&& !
length
$iBuffer
)
{
if
(
$timeout
) {
my
$rc
=
$self
->_read_more(
$socket
,
$timeout
);
return
undef
unless
(
$rc
> 0 );
}
my
$emsg
;
my
$ret
=
$self
->_sysread(
$socket
, \
$iBuffer
,
$readlen
,
length
$iBuffer
);
if
(
$timeout
) {
if
(
defined
$ret
) {
$temperrs
= 0;
}
else
{
$emsg
=
"error while reading data from server: $!"
;
if
( $! == ECONNRESET ) {
$self
->State(Unconnected);
}
elsif
( $! == EAGAIN ) {
if
(
defined
$maxagain
&&
$temperrs
++ >=
$maxagain
) {
$emsg
.=
" ($temperrs)"
;
}
else
{
next
;
}
}
}
}
if
(
defined
$ret
&&
$ret
== 0 ) {
$emsg
=
"socket closed while reading data from server"
;
$self
->State(Unconnected);
}
if
(
$emsg
) {
$self
->LastError(
$emsg
);
$self
->_record(
$transno
,
[
$self
->_next_index(
$transno
),
"ERROR"
,
"$transno * NO $emsg"
]
);
return
undef
;
}
while
(
$iBuffer
=~ s/^(.*?
$CR
?
$LF
)//o )
{
my
$current_line
= $1;
if
(
$current_line
!~ s/\{(\d+)\}
$CR
?
$LF
$//o ) {
push
@$oBuffer
, [
$index
++,
'OUTPUT'
,
$current_line
];
next
;
}
push
@$oBuffer
, [
$index
++,
'OUTPUT'
,
$current_line
];
my
$expected_size
= $1;
$self
->_debug(
"LITERAL: received literal in line "
.
"$current_line of length $expected_size; attempting to "
.
"retrieve from the "
.
length
(
$iBuffer
)
.
" bytes in: $iBuffer<END_OF_iBuffer>"
);
my
$litstring
;
if
(
length
$iBuffer
>=
$expected_size
) {
$litstring
=
substr
$iBuffer
, 0,
$expected_size
,
''
;
}
else
{
$litstring
=
$iBuffer
;
$iBuffer
=
''
;
my
$litreadb
=
length
(
$litstring
);
my
$temperrs
= 0;
my
$maxagain
=
$self
->Maxtemperrors;
undef
$maxagain
if
$maxagain
and
lc
(
$maxagain
) eq
'unlimited'
;
while
(
$expected_size
>
$litreadb
) {
if
(
$timeout
) {
my
$rc
=
$self
->_read_more(
$socket
,
$timeout
);
return
undef
unless
(
$rc
> 0 );
}
else
{
CORE::
select
(
undef
,
undef
,
undef
, 0.025 );
}
my
$ret
=
$self
->_sysread(
$socket
, \
$litstring
,
$expected_size
-
$litreadb
,
length
(
$litstring
) );
if
(
$timeout
) {
if
(
defined
$ret
) {
$temperrs
= 0;
}
else
{
$emsg
=
"error while reading data from server: $!"
;
if
( $! == ECONNRESET ) {
$self
->State(Unconnected);
}
elsif
( $! == EAGAIN ) {
if
(
defined
$maxagain
&&
$temperrs
++ >=
$maxagain
)
{
$emsg
.=
" ($temperrs)"
;
}
else
{
undef
$emsg
;
next
;
}
}
}
}
if
(
defined
$ret
and
$ret
== 0 ) {
$emsg
=
"socket closed while reading data from server"
;
$self
->State(Unconnected);
}
elsif
(
defined
$ret
and
$ret
> 0 ) {
$litreadb
+=
$ret
;
if
(
$literal_cbtype
eq
"GLOB"
) {
print
$literal_callback
$litstring
;
$litstring
=
""
unless
(
$emsg
);
}
}
$self
->_debug(
"Received ret="
. (
defined
(
$ret
) ?
$ret
:
"<undef>"
)
.
" $litreadb of $expected_size"
);
if
(
$emsg
) {
$self
->LastError(
$emsg
);
$self
->_record(
$transno
,
[
$self
->_next_index(
$transno
),
"ERROR"
,
"$transno * NO $emsg"
]
);
$litstring
=
""
unless
defined
$litstring
;
$self
->_debug(
"ERROR while processing LITERAL, "
.
" buffer=\n"
.
$litstring
.
"<END>\n"
);
return
undef
;
}
}
}
if
(
defined
$litstring
) {
if
(
$literal_cbtype
eq
"GLOB"
) {
print
$literal_callback
$litstring
;
}
elsif
(
$literal_cbtype
eq
"CODE"
) {
$literal_callback
->(
$litstring
);
}
}
push
@$oBuffer
, [
$index
++,
'LITERAL'
,
$litstring
]
if
(
$literal_cbtype
ne
"GLOB"
);
}
}
$self
->_debug(
"Read: "
.
join
""
,
map
{
"\t"
.
$_
->[DATA] }
@$oBuffer
)
if
(
$self
->Debug );
@$oBuffer
?
$oBuffer
:
undef
;
}
sub
_sysread {
my
(
$self
,
$fh
,
$buf
,
$len
,
$off
) =
@_
;
my
$rm
=
$self
->Readmethod;
$rm
?
$rm
->(
@_
) :
sysread
(
$fh
,
$$buf
,
$len
,
$off
);
}
sub
_read_more {
my
$self
=
shift
;
my
$rm
=
$self
->Readmoremethod;
$rm
?
$rm
->(
$self
,
@_
) :
$self
->__read_more(
@_
);
}
sub
__read_more {
my
$self
=
shift
;
my
$opt
=
ref
(
$_
[0] ) eq
"HASH"
?
shift
: {};
my
(
$socket
,
$timeout
) =
@_
;
return
1
if
$socket
->isa(
"IO::Socket::SSL"
) &&
$socket
->pending;
my
$rvec
=
''
;
vec
(
$rvec
,
fileno
(
$socket
), 1 ) = 1;
my
$rc
= CORE::
select
(
$rvec
,
undef
,
$rvec
,
$timeout
);
return
$rc
if
$rc
> 0;
my
$err_on_timeout
=
exists
$opt
->{error_on_timeout} ?
$opt
->{error_on_timeout} : 1;
return
$rc
if
!
$rc
and !
$err_on_timeout
;
my
$transno
=
$self
->Transaction;
my
$msg
=
(
$rc
?
"error($rc)"
:
"timeout"
)
.
" waiting ${timeout}s for data from server"
. ( $! ?
": $!"
:
""
);
$self
->LastError(
$msg
);
$self
->_record(
$transno
,
[
$self
->_next_index(
$transno
),
"ERROR"
,
"$transno * NO $msg"
] );
$self
->_disconnect;
return
$rc
;
}
sub
_trans_index() {
sort
{
$a
<=>
$b
}
keys
%{
$_
[0]->{History} };
}
sub
_transaction(;$) {
@{
$_
[0]->{History}{
$_
[1] ||
$_
[0]->Transaction } || [] };
}
sub
_trans_data(;$) {
map
{
$_
->[DATA] }
$_
[0]->_transaction(
$_
[1] );
}
sub
_escaped_trans_data(;$) {
my
(
$self
,
$trans
) =
@_
;
my
@a
;
my
$prevwasliteral
= 0;
foreach
my
$line
(
$self
->_transaction(
$trans
) ) {
next
unless
defined
$line
;
my
$data
=
$line
->[DATA];
if
(
$self
->_is_literal(
$line
) ) {
$data
=
$self
->Escape(
$data
);
$a
[-1] .=
qq("$data")
;
$prevwasliteral
= 1;
}
else
{
if
(
$prevwasliteral
) {
$a
[-1] .=
$data
;
}
else
{
push
(
@a
,
$data
);
}
$prevwasliteral
= 0;
}
}
return
wantarray
?
@a
: \
@a
;
}
sub
Report {
my
$self
=
shift
;
map
{
$self
->_trans_data(
$_
) }
$self
->_trans_index;
}
sub
LastIMAPCommand(;$) {
my
(
$self
,
$trans
) =
@_
;
my
$msg
= (
$self
->_transaction(
$trans
) )[0];
$msg
?
$msg
->[DATA] :
undef
;
}
sub
History(;$) {
my
(
$self
,
$trans
) =
@_
;
my
(
$cmd
,
@a
) =
$self
->_trans_data(
$trans
);
return
wantarray
?
@a
: \
@a
;
}
sub
Results(;$) {
my
(
$self
,
$trans
) =
@_
;
my
@a
=
$self
->_trans_data(
$trans
);
return
wantarray
?
@a
: \
@a
;
}
sub
_transaction_literals() {
my
$self
=
shift
;
join
''
,
map
{
$_
->[DATA] }
grep
{
$self
->_is_literal(
$_
) }
$self
->_transaction;
}
sub
Escaped_history {
my
(
$self
,
$trans
) =
@_
;
my
(
$cmd
,
@a
) =
$self
->_escaped_trans_data(
$trans
);
return
wantarray
?
@a
: \
@a
;
}
sub
Escaped_results {
my
(
$self
,
$trans
) =
@_
;
my
@a
=
$self
->_escaped_trans_data(
$trans
);
return
wantarray
?
@a
: \
@a
;
}
sub
Escape {
my
$data
=
$_
[1];
$data
=~ s/([\\\"])/\\$1/og;
return
$data
;
}
sub
Unescape {
my
$data
=
$_
[1];
$data
=~ s/\\([\\\"])/$1/og;
return
$data
;
}
sub
logout {
my
$self
=
shift
;
my
$rc
=
$self
->_imap_command(
"LOGOUT"
,
"BYE"
);
$self
->_disconnect;
return
$rc
;
}
sub
_disconnect {
my
$self
=
shift
;
delete
$self
->{CAPABILITY};
delete
$self
->{_IMAP4REV1};
$self
->State(Unconnected);
if
(
my
$sock
=
delete
$self
->{Socket} ) {
local
($@);
eval
{
$sock
->
close
};
}
return
$self
;
}
sub
_list_or_lsub_response_parse {
my
(
$self
,
$resp
) =
@_
;
return
undef
unless
defined
$resp
;
my
%info
;
$resp
=~ s/\015?\012$//;
if
(
$resp
=~ / ^\* \s+ (?:LIST|XLIST|LSUB) \s+
\( ([^\)]*) \) \s+
(?: \
" ([^"
]*) \
" | NIL ) \s # "
delimiter" or NIL
(?:\s*\
" (.*) \" | (.*) ) # "
name" or name
/ix
)
{
@info
{
qw(attrs delim name)
} =
( [
split
( / /, $1 ) ], $2,
defined
($3) ?
$self
->Unescape($3) : $4 );
}
return
wantarray
?
%info
: \
%info
;
}
sub
exists
{
my
(
$self
,
$folder
) =
@_
;
$self
->status(
$folder
) ?
$self
:
undef
;
}
sub
get_bodystructure {
my
(
$self
,
$msg
) =
@_
;
my
$class
=
$self
->_load_module(
"BodyStructure"
) or
return
undef
;
my
$out
=
$self
->fetch(
$msg
,
"BODYSTRUCTURE"
) or
return
undef
;
my
$bs
=
""
;
my
$output
= first { /BODYSTRUCTURE\s+\(/i }
@$out
;
unless
(
$output
=~ /
$CRLF
$/o ) {
$output
=
''
;
$self
->_debug(
"get_bodystructure: reassembling original response"
);
my
$started
= 0;
foreach
my
$o
(
$self
->_transaction ) {
next
unless
$self
->_is_output_or_literal(
$o
);
$started
++
if
$o
->[DATA] =~ /BODYSTRUCTURE \(/i;
$started
or
next
;
if
(
length
(
$output
) &&
$self
->_is_literal(
$o
) ) {
my
$data
=
$o
->[DATA];
$data
=~ s/
"/\\"
/g;
$data
=~ s/\(/\\\(/g;
$data
=~ s/\)/\\\)/g;
$output
.=
qq("$data")
;
}
else
{
$output
.=
$o
->[DATA];
}
}
$self
->_debug(
"get_bodystructure: reassembled output=$output<END>"
);
}
{
local
($@);
$bs
=
eval
{
$class
->new(
$output
) };
}
$self
->_debug(
"get_bodystructure: msg $msg returns: "
. (
$bs
||
"UNDEF"
) );
$bs
;
}
sub
get_envelope {
my
(
$self
,
$msg
) =
@_
;
my
$class
=
$self
->_load_module(
"BodyStructure"
) or
return
undef
;
$class
.=
"::Envelope"
;
my
$out
=
$self
->fetch(
$msg
,
'ENVELOPE'
) or
return
undef
;
my
$bs
=
""
;
my
$output
= first { /ENVELOPE \(/i }
@$out
;
unless
(
$output
=~ /
$CRLF
$/o ) {
$output
=
''
;
$self
->_debug(
"get_envelope: reassembling original response"
);
my
$started
= 0;
foreach
my
$o
(
$self
->_transaction ) {
next
unless
$self
->_is_output_or_literal(
$o
);
$started
++
if
$o
->[DATA] =~ /ENVELOPE \(/i;
$started
or
next
;
if
(
length
(
$output
) &&
$self
->_is_literal(
$o
) ) {
my
$data
=
$o
->[DATA];
$data
=~ s/
"/\\"
/g;
$data
=~ s/\(/\\\(/g;
$data
=~ s/\)/\\\)/g;
$output
.=
qq("$data")
;
}
else
{
$output
.=
$o
->[DATA];
}
}
$self
->_debug(
"get_envelope: reassembled output=$output<END>"
);
}
{
local
($@);
$bs
=
eval
{
$class
->new(
$output
) };
}
$self
->_debug(
"get_envelope: msg $msg returns: "
. (
$bs
||
"UNDEF"
) );
$bs
;
}
sub
fetch {
my
$self
=
shift
;
my
$opt
=
ref
(
$_
[0] ) eq
"HASH"
?
shift
: {};
my
$what
=
shift
||
"ALL"
;
my
$take
=
$what
;
if
(
$what
eq
'ALL'
) {
my
$msgs
=
$self
->messages or
return
undef
;
$take
=
$self
->Range(
$msgs
);
}
elsif
(
ref
$what
||
$what
=~ /^[,:\d]+\w*$/ ) {
$take
=
$self
->Range(
$what
);
}
my
(
@data
,
$cmd
);
my
(
$seq_set
,
@fetch_att
) =
$self
->_split_sequence(
$take
,
"FETCH"
,
@_
);
for
(
my
$x
= 0 ;
$x
<=
$#$seq_set
;
$x
++ ) {
my
$seq
=
$seq_set
->[
$x
];
$self
->_imap_uid_command(
FETCH
=>
$seq
,
@fetch_att
,
@_
)
or
return
undef
;
my
$res
=
$opt
->{escaped} ?
$self
->Escaped_results :
$self
->Results;
$cmd
=
shift
(
@$res
);
pop
(
@$res
)
if
(
$x
!= $
push
(
@data
,
@$res
);
}
if
(
$cmd
and !
wantarray
) {
$cmd
=~ s/^(\d+\s+.*?FETCH\s+)\S+(\s*)/$1
$take
$2/;
unshift
(
@data
,
$cmd
);
}
return
wantarray
?
@data
: \
@data
;
}
sub
_split_sequence {
my
(
$self
,
$take
,
@args
) =
@_
;
my
(
$seq
,
@att
) =
split
( / /,
$take
, 2 );
my
@seqs
;
my
$maxl
=
$self
->Maxcommandlength;
if
(
$maxl
) {
push
@args
,
$self
->Transaction,
$self
->Uid ?
"UID"
: (),
"\015\012"
;
my
$clen
=
length
join
(
" "
,
@att
,
@args
);
my
$diff
=
$maxl
-
$clen
;
my
$most
=
$diff
> 64 ?
$diff
: 64;
@seqs
= (
$seq
=~ m/(.{1,
$most
})(?:,|$)/g )
if
defined
$seq
;
$self
->_debug(
"split_sequence: length($maxl-$clen) parts: "
,
$#seqs
+ 1 )
if
(
$#seqs
!= 0 );
}
else
{
push
(
@seqs
,
$seq
)
if
defined
$seq
;
}
return
\
@seqs
,
@att
;
}
sub
fetch_hash {
my
$self
=
shift
;
my
$uids
=
ref
$_
[-1] ?
pop
@_
: {};
my
@words
=
@_
;
my
$msgs
=
'ALL'
;
if
(
defined
$words
[0] ) {
if
(
ref
$words
[0] ) {
$msgs
=
shift
@words
;
}
else
{
if
(
$words
[0] eq
'ALL'
) {
$msgs
=
shift
@words
;
}
elsif
(
$words
[0] =~ s/^([*,:\d]+)\s*// ) {
$msgs
= $1;
shift
@words
if
$words
[0] eq
""
;
}
}
}
my
$what
= (
@words
> 1 or
$words
[0] =~ /\s/ ) ?
"(@words)"
:
"@words"
;
my
$output
=
$self
->fetch(
$msgs
,
$what
)
or
return
undef
;
my
$asked_for_uid
=
$what
=~ /[\s(]UID[)\s]/i;
while
(
my
$l
=
shift
@$output
) {
next
if
$l
!~ m/^\*\s(\d+)\sFETCH\s\(/g;
my
(
$mid
,
$entry
) = ( $1, {} );
my
(
$key
,
$value
);
ATTR:
while
(
$l
and
$l
!~ m/\G\s*\)\s*$/gc ) {
if
(
$l
=~ m/\G\s*([^\s\[]+(?:\[[^\]]*\])?(?:<[^>]*>)?)\s*/gc ) {
$key
=
uc
($1);
if
(
$key
=~ /^BODY\[HEADER\.FIELDS \(
"[^"
]+".*?\)\]$/ ) {
$key
=~ s/"//g;
}
}
elsif
( !
defined
$key
) {
$self
->LastError(
"Invalid item name in FETCH response: $l"
);
return
undef
;
}
if
(
$l
=~ m/\G\s*$/gc ) {
$value
=
shift
@$output
;
$entry
->{
$key
} =
$value
;
$l
=
shift
@$output
;
next
ATTR;
}
elsif
(
$l
=~ m/\G(?:
"((?>(?:(?>[^"
\\]+)|\\.)*))"|([^()\s]+))\s*/gc )
{
$value
=
defined
$1 ? $1 : $2;
$entry
->{
$key
} =
$value
;
next
ATTR;
}
elsif
(
$l
=~ m/\G\(/gc ) {
my
$depth
= 1;
$value
=
""
;
while
(
$l
=~
m/\G(
"((?>(?:(?>[^"
\\]+)|\\.)*))
"\s*|[()]|[^()"
]+)/gc )
{
my
$stuff
= $1;
if
(
$stuff
eq
"("
) {
$depth
++;
$value
.=
"("
;
}
elsif
(
$stuff
eq
")"
) {
$depth
--;
if
(
$depth
== 0 ) {
$entry
->{
$key
} =
$value
;
next
ATTR;
}
$value
.=
")"
;
}
else
{
$value
.=
$stuff
;
}
if
(
$l
=~ m/\G\s*$/gc and
scalar
(
@$output
) ) {
my
$elit
=
$self
->Escape(
shift
@$output
);
$l
=
shift
@$output
;
$value
.= (
length
(
$value
) ?
" "
:
""
) .
qq{"$elit"}
;
}
}
$l
=~ m/\G\s*/gc;
}
else
{
$self
->LastError(
"Invalid item value in FETCH response: $l"
);
return
undef
;
}
}
if
(
$self
->Uid ) {
if
(
$entry
->{UID} ) {
$uids
->{
$entry
->{UID} } =
$entry
;
delete
$entry
->{UID}
unless
$asked_for_uid
;
}
else
{
$self
->_debug(
"ignoring unsolicited response: $l"
);
}
}
else
{
$uids
->{
$mid
} =
$entry
;
}
}
return
wantarray
?
%$uids
:
$uids
;
}
sub
store {
my
(
$self
,
@a
) =
@_
;
$self
->_imap_uid_command(
STORE
=>
@a
)
or
return
undef
;
return
wantarray
?
$self
->History :
$self
->Results;
}
sub
_imap_folder_command($$@) {
my
(
$self
,
$command
) = (
shift
,
shift
);
my
$folder
=
$self
->Quote(
shift
);
$self
->_imap_command(
join
' '
,
$command
,
$folder
,
@_
)
or
return
undef
;
return
wantarray
?
$self
->History :
$self
->Results;
}
sub
subscribe($) {
shift
->_imap_folder_command(
SUBSCRIBE
=>
@_
) }
sub
unsubscribe($) {
shift
->_imap_folder_command(
UNSUBSCRIBE
=>
@_
) }
sub
create($) {
shift
->_imap_folder_command(
CREATE
=>
@_
) }
sub
delete
($) {
my
$self
=
shift
;
$self
->_imap_folder_command(
DELETE
=>
@_
) or
return
undef
;
$self
->Folder(
undef
);
return
wantarray
?
$self
->History :
$self
->Results;
}
sub
myrights($) {
$_
[0]->_imap_folder_command(
MYRIGHTS
=>
$_
[1] ) }
sub
close
{
my
$self
=
shift
;
$self
->_imap_command(
'CLOSE'
)
or
return
undef
;
return
wantarray
?
$self
->History :
$self
->Results;
}
sub
expunge {
my
(
$self
,
$folder
) =
@_
;
return
undef
unless
(
defined
$folder
or
defined
$self
->Folder );
my
$old
=
defined
$self
->Folder ?
$self
->Folder :
''
;
if
( !
defined
(
$folder
) ||
$folder
eq
$old
) {
$self
->_imap_command(
'EXPUNGE'
)
or
return
undef
;
}
else
{
$self
->
select
(
$folder
) or
return
undef
;
my
$succ
=
$self
->_imap_command(
'EXPUNGE'
);
return
undef
unless
(
$self
->
select
(
$old
) and
$succ
);
}
return
wantarray
?
$self
->History :
$self
->Results;
}
sub
uidexpunge {
my
(
$self
,
$msgspec
) = (
shift
,
shift
);
return
undef
unless
$self
->has_capability(
"UIDPLUS"
);
unless
(
$self
->Uid ) {
$self
->LastError(
"Uid must be enabled for uidexpunge"
);
return
undef
;
}
my
$msg
=
UNIVERSAL::isa(
$msgspec
,
'Mail::IMAPClient::MessageSet'
)
?
$msgspec
:
$self
->Range(
$msgspec
);
$msg
->cat(
@_
)
if
@_
;
my
(
@data
,
$cmd
);
my
(
$seq_set
) =
$self
->_split_sequence(
$msg
,
"UID EXPUNGE"
);
for
(
my
$x
= 0 ;
$x
<=
$#$seq_set
;
$x
++ ) {
my
$seq
=
$seq_set
->[
$x
];
$self
->_imap_uid_command(
"EXPUNGE"
=>
$seq
)
or
return
undef
;
my
$res
=
$self
->Results;
$cmd
=
shift
(
@$res
);
pop
(
@$res
)
if
(
$x
!= $
push
(
@data
,
@$res
);
}
if
(
$cmd
and !
wantarray
) {
$cmd
=~ s/^(\d+\s+.*?EXPUNGE\s+)\S+(\s*)/$1
$msg
$2/;
unshift
(
@data
,
$cmd
);
}
return
wantarray
?
@data
: \
@data
;
}
sub
rename
{
my
(
$self
,
$from
,
$to
) =
@_
;
$from
=
$self
->Quote(
$from
);
$to
=
$self
->Quote(
$to
);
$self
->_imap_command(
qq(RENAME $from $to)
) ?
$self
:
undef
;
}
sub
status {
my
(
$self
,
$folder
) = (
shift
,
shift
);
defined
$folder
or
return
undef
;
my
$which
=
@_
?
join
(
" "
,
@_
) :
'MESSAGES'
;
my
$box
=
$self
->Quote(
$folder
);
$self
->_imap_command(
"STATUS $box ($which)"
)
or
return
undef
;
return
wantarray
?
$self
->History :
$self
->Results;
}
sub
flags {
my
(
$self
,
$msgspec
) = (
shift
,
shift
);
my
$msg
=
UNIVERSAL::isa(
$msgspec
,
'Mail::IMAPClient::MessageSet'
)
?
$msgspec
:
$self
->Range(
$msgspec
);
$msg
->cat(
@_
)
if
@_
;
my
$ref
=
$self
->fetch(
$msg
,
"FLAGS"
) or
return
undef
;
my
$u_f
=
$self
->Uid;
my
$flagset
= {};
foreach
my
$line
(
@$ref
) {
$self
->_debug(
"flags: line = '$line'"
);
if
(
$line
=~ /\* \s+ (\d+) \s+ FETCH \s+
\(
(?:\s* UID \s+ (\d+) \s* )?
FLAGS \s* \( (.*?) \) \s*
(?:\s* UID \s+ (\d+) \s* )?
\)
/x
)
{
my
$mailid
=
$u_f
? ( $2 || $4 ) : $1;
$flagset
->{
$mailid
} = [
split
" "
, $3 ];
}
}
return
$flagset
if
ref
$msgspec
;
my
$flagsref
=
$flagset
->{
$msgspec
};
return
wantarray
? @{
$flagsref
|| [] } :
$flagsref
;
}
sub
supported_flags(@) {
my
$self
=
shift
;
my
$sup
=
$self
->Supportedflags
or
return
@_
;
return
map
{
$sup
->(
$_
) }
@_
if
ref
$sup
eq
'CODE'
;
grep
{
$sup
->{ /^\\(\S+)/ ?
lc
$1 : () } }
@_
;
}
sub
parse_headers {
my
(
$self
,
$msgspec
,
@fields
) =
@_
;
my
$fields
=
join
' '
,
@fields
;
my
$msg
=
ref
$msgspec
eq
'ARRAY'
?
$self
->Range(
$msgspec
) :
$msgspec
;
my
$peek
= !
defined
$self
->Peek ||
$self
->Peek ?
'.PEEK'
:
''
;
my
$string
=
"$msg BODY$peek"
. (
$fields
eq
'ALL'
?
'[HEADER]'
:
"[HEADER.FIELDS ($fields)]"
);
my
$raw
=
$self
->fetch(
$string
) or
return
undef
;
my
$cmd
=
shift
@$raw
;
my
%headers
;
my
$h
;
my
$field
;
my
%fieldmap
=
map
{ (
lc
(
$_
) =>
$_
) }
@fields
;
my
$msgid
;
foreach
my
$header
(
map
{
split
/
$CR
?
$LF
/o }
@$raw
) {
if
(
$header
=~ s/^\* \s+ (\d+) \s+ FETCH \s+
\( (.*?) BODY\[HEADER (?:\.FIELDS)? .*? \]\s*//ix
)
{
(
$msgid
,
my
$msgattrs
) = ( $1, $2 );
$h
= {};
if
(
$self
->Uid ) {
$msgid
=
$msgattrs
=~ m/\b UID \s+ (\d+)/x ? $1 :
undef
;
}
$headers
{
$msgid
} =
$h
if
$msgid
;
}
$header
=~ /\S/ or
next
;
if
(
$header
=~ /^\)/ ) {
undef
$h
;
next
;
}
elsif
( !
$msgid
&&
$header
=~ /^\s
*UID
\s+(\d+).*\)$/ ) {
$headers
{$1} =
$h
;
undef
$h
;
next
;
}
unless
(
defined
$h
) {
$self
->_debug(
"found data between fetch headers: $header"
);
next
;
}
if
(
$header
and
$header
=~ s/^(\S+?)\:\s*// ) {
$field
=
$fieldmap
{
lc
$1 } || $1;
push
@{
$h
->{
$field
} },
$header
;
}
elsif
(
$field
and
ref
$h
->{
$field
} eq
'ARRAY'
) {
$h
->{
$field
}[-1] .=
$header
;
}
else
{
$self
->_debug(
"non-header data between fetch headers: $header"
)
if
(
$header
!~ /^(?:\s*\"\"\)|\{\d+\})
$CR
?
$LF
$/o );
}
}
ref
$msgspec
eq
'ARRAY'
? \
%headers
:
$headers
{
$msgspec
};
}
sub
subject {
$_
[0]->get_header(
$_
[1],
"Subject"
) }
sub
date {
$_
[0]->get_header(
$_
[1],
"Date"
) }
sub
rfc822_header {
shift
->get_header(
@_
) }
sub
get_header {
my
(
$self
,
$msg
,
$field
) =
@_
;
my
$headers
=
$self
->parse_headers(
$msg
,
$field
);
$headers
?
$headers
->{
$field
}[0] :
undef
;
}
sub
recent_count {
my
(
$self
,
$folder
) = (
shift
,
shift
);
$self
->status(
$folder
,
'RECENT'
)
or
return
undef
;
my
$r
=
first { s/\*\s+STATUS\s+.*\(RECENT\s+(\d+)\s*\)/$1/ }
$self
->History;
chomp
$r
;
$r
;
}
sub
message_count {
my
$self
=
shift
;
my
$folder
=
shift
||
$self
->Folder;
$self
->status(
$folder
,
'MESSAGES'
)
or
return
undef
;
foreach
my
$result
(
$self
->Results ) {
return
$1
if
$result
=~ /\(MESSAGES\s+(\d+)\s*\)/i;
}
undef
;
}
sub
recent() {
shift
->search(
'recent'
) }
sub
seen() {
shift
->search(
'seen'
) }
sub
unseen() {
shift
->search(
'unseen'
) }
sub
messages() {
shift
->search(
'ALL'
) }
sub
sentbefore($$) {
shift
->_search_date(
sentbefore
=>
@_
) }
sub
sentsince($$) {
shift
->_search_date(
sentsince
=>
@_
) }
sub
senton($$) {
shift
->_search_date(
senton
=>
@_
) }
sub
since($$) {
shift
->_search_date(
since
=>
@_
) }
sub
before
($$) {
shift
->_search_date(
before
=>
@_
) }
sub
on($$) {
shift
->_search_date(
on
=>
@_
) }
sub
_search_date($$$) {
my
(
$self
,
$how
,
$time
) =
@_
;
my
$imapdate
;
if
(
$time
=~ /\d\d-\D\D\D-\d\d\d\d/ ) {
$imapdate
=
$time
;
}
elsif
(
$time
=~ /^\d+$/ ) {
my
@ltime
=
localtime
$time
;
$imapdate
=
sprintf
(
"%2.2d-%s-%4.4d"
,
$ltime
[3],
$mnt
[
$ltime
[4] ],
$ltime
[5] + 1900 );
}
else
{
$self
->LastError(
"Invalid date format supplied for '$how': $time"
);
return
undef
;
}
$self
->_imap_uid_command(
SEARCH
=>
$how
,
$imapdate
)
or
return
undef
;
my
@hits
;
foreach
(
$self
->History ) {
chomp
;
s/
$CR
?
$LF
$//o;
s/^\*\s+SEARCH\s+//i or
next
;
push
@hits
,
grep
/\d/,
split
;
}
$self
->_debug(
"Hits are: @hits"
);
return
wantarray
?
@hits
: \
@hits
;
}
sub
or {
my
(
$self
,
@what
) =
@_
;
if
(
@what
< 2 ) {
$self
->LastError(
"Invalid number of arguments passed to or()"
);
return
undef
;
}
my
$or
=
"OR "
.
$self
->Quote(
shift
@what
) .
" "
.
$self
->Quote(
shift
@what
);
$or
=
"OR $or "
.
$self
->Quote(
$_
)
for
@what
;
$self
->_imap_uid_command(
SEARCH
=>
$or
)
or
return
undef
;
my
@hits
;
foreach
(
$self
->History ) {
chomp
;
s/
$CR
?
$LF
$//o;
s/^\*\s+SEARCH\s+//i or
next
;
push
@hits
,
grep
/\d/,
split
;
}
$self
->_debug(
"Hits are now: @hits"
);
return
wantarray
?
@hits
: \
@hits
;
}
sub
disconnect {
shift
->logout }
sub
_quote_search {
my
(
$self
,
@args
) =
@_
;
my
@ret
;
foreach
my
$v
(
@args
) {
if
(
ref
(
$v
) eq
"SCALAR"
) {
push
(
@ret
,
$$v
);
}
elsif
(
exists
$SEARCH_KEYS
{
uc
(
$v
) } ) {
push
(
@ret
,
$v
);
}
elsif
(
@args
== 1 ) {
push
(
@ret
,
$v
);
}
else
{
push
(
@ret
,
$self
->Quote(
$v
) );
}
}
return
@ret
;
}
sub
search {
my
(
$self
,
@args
) =
@_
;
@args
=
$self
->_quote_search(
@args
);
$self
->_imap_uid_command(
SEARCH
=>
@args
)
or
return
undef
;
my
@hits
;
foreach
(
$self
->History ) {
chomp
;
s/
$CR
?
$LF
$//o;
s/^\*\s+SEARCH\s+(?=.*?\d)// or
next
;
push
@hits
,
grep
/^\d+$/,
split
;
}
@hits
or
$self
->_debug(
"Search successful but found no matching messages"
);
return
wantarray
?
@hits
: !
@hits
? \
@hits
:
$self
->Ranges ?
$self
->Range( \
@hits
)
: \
@hits
;
}
my
$thread_parser
;
sub
thread {
my
$self
=
shift
;
return
undef
unless
defined
$self
->has_capability(
"THREAD=REFERENCES"
);
my
$algorythm
=
shift
|| (
$self
->has_capability(
"THREAD=REFERENCES"
)
?
'REFERENCES'
:
'ORDEREDSUBJECT'
);
my
$charset
=
shift
||
'UTF-8'
;
my
@a
=
@_
?
@_
:
'ALL'
;
$a
[-1] =
$self
->Quote(
$a
[-1], 1 )
if
@a
> 1 && !
exists
$SEARCH_KEYS
{
uc
$a
[-1] };
$self
->_imap_uid_command(
THREAD
=>
$algorythm
,
$charset
,
@a
)
or
return
undef
;
unless
(
$thread_parser
) {
return
if
(
defined
(
$thread_parser
) and
$thread_parser
== 0 );
my
$class
=
$self
->_load_module(
"Thread"
);
unless
(
$class
) {
$thread_parser
= 0;
return
undef
;
}
$thread_parser
=
$class
->new;
}
my
$thread
;
foreach
(
$self
->History ) {
/^\*\s+THREAD\s+/ or
next
;
s/
$CR
?
$LF
|
$LF
+/ /og;
$thread
=
$thread_parser
->start(
$_
);
}
unless
(
$thread
) {
$self
->LastError(
"Thread search completed successfully but found no matching messages"
);
return
undef
;
}
$thread
;
}
sub
delete_message {
my
$self
=
shift
;
my
@msgs
=
map
{
ref
$_
eq
'ARRAY'
?
@$_
:
split
/\,/ }
@_
;
$self
->store(
join
(
','
,
@msgs
),
'+FLAGS.SILENT'
,
'(\Deleted)'
)
?
scalar
@msgs
:
undef
;
}
sub
restore_message {
my
$self
=
shift
;
my
$msgs
=
join
','
,
map
{
ref
$_
eq
'ARRAY'
?
@$_
:
split
/\,/ }
@_
;
$self
->store(
$msgs
,
'-FLAGS'
,
'(\Deleted)'
) or
return
undef
;
scalar
grep
/^\*\s\d+\sFETCH\s\(.
*FLAGS
.*(?!\\Deleted)/,
$self
->Results;
}
sub
uidvalidity {
my
(
$self
,
$folder
) =
@_
;
$self
->status(
$folder
,
"UIDVALIDITY"
) or
return
undef
;
my
$line
= first { /UIDVALIDITY/i }
$self
->History;
defined
$line
&&
$line
=~ /\(UIDVALIDITY\s+([^\)]+)/ ? $1 :
undef
;
}
sub
uidnext {
my
(
$self
,
$folder
) =
@_
;
$self
->status(
$folder
,
"UIDNEXT"
) or
return
undef
;
my
$line
= first { /UIDNEXT/i }
$self
->History;
defined
$line
&&
$line
=~ /\(UIDNEXT\s+([^\)]+)/ ? $1 :
undef
;
}
sub
capability {
my
$self
=
shift
;
if
(
$self
->{CAPABILITY} ) {
my
@caps
=
keys
%{
$self
->{CAPABILITY} };
return
wantarray
?
@caps
: \
@caps
;
}
$self
->_imap_command(
'CAPABILITY'
)
or
return
undef
;
my
@caps
=
map
{
split
}
grep
/^\*\s+CAPABILITY\s+/,
$self
->History;
splice
(
@caps
, 0, 2 );
for
(
my
$i
= 0 ;
$i
<
@caps
;
$i
++ ) {
$self
->{CAPABILITY}->{
uc
$caps
[
$i
] } ||= [];
my
(
$capa
,
$cval
) =
split
( /=/,
$caps
[
$i
], 2 );
if
(
defined
$cval
) {
$capa
=
uc
$capa
;
push
(
@caps
,
$capa
)
unless
exists
$self
->{CAPABILITY}->{
$capa
};
push
( @{
$self
->{CAPABILITY}->{
$capa
} },
$cval
);
}
}
return
wantarray
?
@caps
: \
@caps
;
}
sub
has_capability {
my
(
$self
,
$which
) =
@_
;
$self
->capability or
return
undef
;
my
$aref
= [];
if
(
defined
$which
) {
$which
=
uc
$which
;
if
(
exists
$self
->{CAPABILITY}{
$which
} ) {
if
( @{
$self
->{CAPABILITY}{
$which
} } ) {
$aref
=
$self
->{CAPABILITY}{
$which
};
}
else
{
$aref
= [
$which
];
}
}
}
return
@$aref
if
wantarray
;
return
scalar
@$aref
?
$aref
:
""
;
}
sub
imap4rev1 {
my
$self
=
shift
;
return
$self
->{_IMAP4REV1}
if
exists
$self
->{_IMAP4REV1};
$self
->{_IMAP4REV1} =
$self
->has_capability(
'IMAP4REV1'
);
}
sub
namespace {
my
$self
=
shift
;
unless
(
$self
->has_capability(
"NAMESPACE"
) ) {
$self
->LastError(
"NO NAMESPACE not supported by "
.
$self
->Server )
unless
$self
->LastError;
return
undef
;
}
my
$got
=
$self
->_imap_command(
"NAMESPACE"
) or
return
undef
;
my
@namespaces
=
map
{ /^\* NAMESPACE (.*)/ ? $1 : () }
$got
->Results;
my
$namespace
=
shift
@namespaces
;
$namespace
=~ s/
$CR
?
$LF
$//o;
my
(
$personal
,
$shared
,
$public
) =
$namespace
=~ m
(NIL|\((?:\([^\)]+\)\s*)+\))\s
(NIL|\((?:\([^\)]+\)\s*)+\))\s
(NIL|\((?:\([^\)]+\)\s*)+\))
my
@ns
;
$self
->_debug(
"NAMESPACE: pers=$personal, shared=$shared, pub=$public"
);
foreach
(
$personal
,
$shared
,
$public
) {
uc
$_
ne
'NIL'
or
next
;
s/^\((.*)\)$/$1/;
my
@pieces
= m
$self
->_debug(
"NAMESPACE pieces: @pieces"
);
push
@ns
, [
map
{ [m
}
return
wantarray
?
@ns
: \
@ns
;
}
sub
internaldate {
my
(
$self
,
$msg
) =
@_
;
$self
->_imap_uid_command(
FETCH
=>
$msg
,
'INTERNALDATE'
)
or
return
undef
;
my
$hist
=
join
''
,
$self
->History;
return
$hist
=~ /\bINTERNALDATE
"([^"
]*)"/i ? $1 :
undef
;
}
sub
is_parent {
my
(
$self
,
$folder
) =
@_
;
my
$list
=
$self
->list(
undef
,
$folder
) or
return
undef
;
my
$attrs
;
foreach
my
$resp
(
@$list
) {
my
$rec
=
$self
->_list_or_lsub_response_parse(
$resp
);
next
unless
defined
$rec
->{attrs};
$self
->_debug(
"unexpected attrs data: @$list\n"
)
if
$attrs
;
$attrs
=
$rec
->{attrs};
}
if
(
$attrs
) {
return
undef
if
first {
lc
(
$_
) eq
'\noinferiors'
}
@$attrs
;
return
1
if
first {
lc
(
$_
) eq
'\haschildren'
}
@$attrs
;
return
0
if
first {
lc
(
$_
) eq
'\hasnochildren'
}
@$attrs
;
}
else
{
$self
->_debug(
join
(
"\n\t"
,
"no attrs for '$folder' in:"
,
@$list
) );
}
my
$sep
=
$self
->separator(
$folder
) ||
$self
->separator(
undef
);
return
undef
unless
defined
$sep
;
my
$lead
=
$folder
.
$sep
;
my
$len
=
length
$lead
;
scalar
grep
{
$lead
eq
substr
(
$_
, 0,
$len
) }
$self
->folders;
}
sub
selectable {
my
(
$self
,
$f
) =
@_
;
my
$info
=
$self
->list(
""
,
$f
) or
return
undef
;
return
not(
grep
/[\s(]\\Noselect[)\s]/i,
@$info
);
}
sub
append {
my
$self
=
shift
;
my
$folder
=
shift
;
$self
->append_string(
$folder
, (
@_
> 1 ?
join
(
$CRLF
,
@_
) :
$_
[0] ) );
}
sub
_clean_flags {
my
(
$self
,
$flags
) =
@_
;
$flags
=~ s/^\s+//;
$flags
=~ s/\s+$//;
$flags
=
"($flags)"
if
$flags
!~ /^\(.*\)$/;
return
$flags
;
}
sub
_clean_date {
my
(
$self
,
$date
) =
@_
;
$date
=~ s/^\s+//
if
$date
!~ /^\s\d/;
$date
=~ s/\s+$//;
$date
=
qq("$date")
if
$date
!~ /^"/;
return
$date
;
}
sub
_append_command {
my
(
$self
,
$folder
,
$flags
,
$date
,
$length
) =
@_
;
return
join
(
" "
,
"APPEND $folder"
,
(
$flags
?
$flags
: () ),
(
$date
?
$date
: () ),
"{"
.
$length
.
"}"
,
);
}
sub
append_string($$$;$$) {
my
(
$self
,
$folder
,
$flags
,
$date
) =
@_
[ 0, 1, 3, 4 ];
my
$maxl
=
$self
->Maxappendstringlength;
if
(
$_
[2] and
$maxl
and
length
(
$_
[2] ) >
$maxl
) {
$self
->_debug(
"append_string: using in memory file"
);
return
$self
->append_file(
$folder
, \(
$_
[2] ),
undef
,
$flags
,
$date
);
}
my
$text
=
defined
(
$_
[2] ) ?
$_
[2] :
''
;
$folder
=
$self
->Quote(
$folder
);
$flags
=
$self
->_clean_flags(
$flags
)
if
(
defined
$flags
);
$date
=
$self
->_clean_date(
$date
)
if
(
defined
$date
);
$text
=~ s/\r?\n/
$CRLF
/og;
my
$cmd
=
$self
->_append_command(
$folder
,
$flags
,
$date
,
length
(
$text
) );
$cmd
.=
$CRLF
.
$text
.
$CRLF
;
$self
->_imap_command( {
addcrlf
=> 0 },
$cmd
) or
return
undef
;
my
$data
=
join
''
,
$self
->Results;
my
$ret
=
$data
=~ m
return
$ret
;
}
sub
append_file {
my
(
$self
,
$folder
,
$file
,
$control
,
$flags
,
$date
) =
@_
;
my
@err
;
push
(
@err
,
"folder not specified"
)
unless
(
defined
(
$folder
) and
$folder
ne
""
);
my
$fh
;
if
( !
defined
(
$file
) ) {
push
(
@err
,
"file not specified"
);
}
elsif
(
ref
(
$file
) and
ref
(
$file
) ne
"SCALAR"
) {
$fh
=
$file
;
}
elsif
( !
ref
(
$file
) and !-f
$file
) {
push
(
@err
,
"file '$file' not found"
);
}
else
{
local
($!);
open
(
$fh
,
"<"
,
$file
)
or
push
(
@err
,
"Unable to open file '$file': $!"
);
}
if
(
@err
) {
$self
->LastError(
join
(
", "
,
@err
) );
return
undef
;
}
binmode
(
$fh
);
$folder
=
$self
->Quote(
$folder
)
if
(
defined
$folder
);
$flags
=
$self
->_clean_flags(
$flags
)
if
(
defined
$flags
);
if
(
$date
) {
$date
=
$self
->Rfc3501_datetime( (
stat
(
$fh
) )[9] )
if
(
$date
eq
"1"
);
$date
=
$self
->_clean_date(
$date
);
}
my
$length
= 0;
{
local
$/ =
"\n"
;
while
(
my
$line
= <
$fh
> ) {
$line
=~ s/\r?\n$/
$CRLF
/;
$length
+=
length
(
$line
);
}
seek
(
$fh
, 0, 0 );
}
my
$cmd
=
$self
->_append_command(
$folder
,
$flags
,
$date
,
$length
);
my
$rc
=
$self
->_imap_command(
$cmd
,
'+'
);
unless
(
$rc
) {
$self
->LastError(
"Error sending '$cmd': "
.
$self
->LastError );
return
undef
;
}
my
(
$buffer
,
$buflen
) = (
""
, 0 );
until
( !
$buflen
and
eof
(
$fh
) ) {
if
(
$buflen
< APPEND_BUFFER_SIZE ) {
FILLBUFF:
while
(
my
$line
= <
$fh
> ) {
$line
=~ s/\r?\n$/
$CRLF
/;
$buffer
.=
$line
;
$buflen
=
length
(
$buffer
);
last
FILLBUFF
if
(
$buflen
>= APPEND_BUFFER_SIZE );
}
}
last
unless
$buflen
;
my
$savebuff
=
(
$buflen
> APPEND_BUFFER_SIZE )
?
substr
(
$buffer
, APPEND_BUFFER_SIZE )
:
undef
;
$buffer
=
substr
(
$buffer
, 0, APPEND_BUFFER_SIZE );
my
$bytes_written
=
$self
->_send_bytes( \
$buffer
);
unless
(
$bytes_written
) {
$self
->LastError(
"Error appending message: "
.
$self
->LastError );
return
undef
;
}
$buffer
=
defined
(
$savebuff
) ?
$savebuff
:
""
;
$buflen
=
length
(
$buffer
);
}
unless
(
$self
->_send_bytes( \
$CRLF
) ) {
$self
->LastError(
"Error appending CRLF: "
.
$self
->LastError );
return
undef
;
}
my
$code
=
$self
->_get_response(
$self
->Count ) or
return
undef
;
if
(
$code
eq
'OK'
) {
my
$data
=
join
''
,
$self
->Results;
my
$ret
=
$data
=~ m
return
$ret
;
}
else
{
return
undef
;
}
}
sub
authenticate {
my
(
$self
,
$scheme
,
$response
) =
@_
;
$scheme
||=
$self
->Authmechanism;
$response
||=
$self
->Authcallback;
my
$clear
=
$self
->Clear;
$self
->Clear(
$clear
)
if
$self
->Count >=
$clear
&&
$clear
> 0;
if
( !
$scheme
) {
$self
->LastError(
"Authmechanism not set"
);
return
undef
;
}
elsif
(
$scheme
eq
'LOGIN'
) {
$self
->LastError(
"Authmechanism LOGIN is invalid, use login()"
);
return
undef
;
}
my
$string
=
"AUTHENTICATE $scheme"
;
$self
->_imap_command(
$string
,
'+'
) or
return
undef
;
my
$count
=
$self
->Count;
my
$code
;
foreach
my
$line
(
$self
->Results ) {
if
(
$line
=~ /^\+\s*(.*?)\s*$/ ) {
$code
= $1;
last
;
}
}
if
(
$scheme
eq
'CRAM-MD5'
) {
$response
||=
sub
{
my
(
$code
,
$client
) =
@_
;
my
$hmac
=
Digest::HMAC_MD5::hmac_md5_hex( decode_base64(
$code
),
$client
->Password );
encode_base64(
$client
->User .
" "
.
$hmac
,
''
);
};
}
elsif
(
$scheme
eq
'DIGEST-MD5'
) {
$response
||=
sub
{
my
(
$code
,
$client
) =
@_
;
my
$authname
=
defined
$client
->Authuser ?
$client
->Authuser :
$client
->User;
my
$sasl
= Authen::SASL->new(
mechanism
=>
'DIGEST-MD5'
,
callback
=> {
user
=>
$client
->User,
pass
=>
$client
->Password,
authname
=>
$authname
}
);
my
$conn
=
$sasl
->client_new(
'imap'
,
'localhost'
,
''
);
my
$answer
=
$conn
->client_step( decode_base64
$code
);
encode_base64(
$answer
,
''
)
if
defined
$answer
;
};
}
elsif
(
$scheme
eq
'PLAIN'
) {
$response
||=
sub
{
my
(
$code
,
$client
) =
@_
;
encode_base64(
join
(
chr
(0),
defined
$client
->Proxy
? (
$client
->User,
$client
->Proxy )
: (
""
,
$client
->User ),
defined
$client
->Password ?
$client
->Password :
""
,
),
''
);
};
}
elsif
(
$scheme
eq
'NTLM'
) {
$response
||=
sub
{
my
(
$code
,
$client
) =
@_
;
Authen::NTLM::ntlm_user(
$client
->User );
Authen::NTLM::ntlm_password(
$client
->Password );
Authen::NTLM::ntlm_domain(
$client
->Domain )
if
$client
->Domain;
Authen::NTLM::ntlm(
$code
);
};
}
my
$resp
=
$response
->(
$code
,
$self
);
unless
(
defined
(
$resp
) ) {
$self
->LastError(
"Error getting $scheme data: "
.
$self
->LastError );
return
undef
;
}
unless
(
$self
->_send_line(
$resp
) ) {
$self
->LastError(
"Error sending $scheme data: "
.
$self
->LastError );
return
undef
;
}
undef
$code
;
until
(
$code
) {
my
$output
=
$self
->_read_line or
return
undef
;
foreach
my
$o
(
@$output
) {
$self
->_record(
$count
,
$o
);
$code
=
$o
->[DATA] =~ /^\+\s+(.*?)\s*$/ ? $1 :
undef
;
if
(
$code
) {
unless
(
$self
->_send_line(
$response
->(
$code
,
$self
) ) ) {
$self
->LastError(
"Error sending $scheme data: "
.
$self
->LastError );
return
undef
;
}
undef
$code
;
}
if
(
$o
->[DATA] =~ /^
$count
\s+(OK|NO|BAD)\b/i ) {
$code
=
uc
($1);
$self
->LastError(
$o
->[DATA] )
unless
(
$code
eq
'OK'
);
}
elsif
(
$o
->[DATA] =~ /^\*\s+BYE/ ) {
$self
->State(Unconnected);
$self
->LastError(
$o
->[DATA] );
return
undef
;
}
}
}
return
undef
unless
$code
eq
'OK'
;
Authen::NTLM::ntlm_reset()
if
$scheme
eq
'NTLM'
;
$self
->State(Authenticated);
return
$self
;
}
sub
copy {
my
(
$self
,
$target
,
@msgs
) =
@_
;
my
$msgs
=
$self
->Ranges
?
$self
->Range(
@msgs
)
:
join
','
,
map
{
ref
$_
?
@$_
:
$_
}
@msgs
;
$self
->_imap_uid_command(
COPY
=>
$msgs
,
$self
->Quote(
$target
) )
or
return
undef
;
my
@results
=
$self
->History;
my
@uids
;
foreach
(
@results
) {
chomp
;
s/
$CR
?
$LF
$//o;
s/^.*\[COPYUID\s+\d+\s+[\d:,]+\s+([\d:,]+)\].*/$1/ or
next
;
push
@uids
, /(\d+):(\d+)/ ? ( $1 ... $2 ) : (
split
/\,/ );
}
return
@uids
?
join
(
","
,
@uids
) :
$self
;
}
sub
move {
my
(
$self
,
$target
,
@msgs
) =
@_
;
$self
->
exists
(
$target
)
or
$self
->create(
$target
) &&
$self
->subscribe(
$target
);
my
$uids
=
$self
->copy(
$target
,
map
{
ref
$_
eq
'ARRAY'
?
@$_
:
$_
}
@msgs
)
or
return
undef
;
unless
(
$self
->delete_message(
@msgs
) ) {
local
($!);
carp
$self
->LastError;
}
return
$uids
;
}
sub
set_flag {
my
(
$self
,
$flag
,
@msgs
) =
@_
;
@msgs
= @{
$msgs
[0] }
if
ref
$msgs
[0] eq
'ARRAY'
;
$flag
=
"\\$flag"
if
$flag
=~ /^(?:Answered|Flagged|Deleted|Seen|Draft)$/i;
my
$which
=
$self
->Ranges ?
$self
->Range(
@msgs
) :
join
(
','
,
@msgs
);
return
$self
->store(
$which
,
'+FLAGS.SILENT'
,
"($flag)"
);
}
sub
see {
my
(
$self
,
@msgs
) =
@_
;
@msgs
= @{
$msgs
[0] }
if
ref
$msgs
[0] eq
'ARRAY'
;
return
$self
->set_flag(
'\\Seen'
,
@msgs
);
}
sub
mark {
my
(
$self
,
@msgs
) =
@_
;
@msgs
= @{
$msgs
[0] }
if
ref
$msgs
[0] eq
'ARRAY'
;
return
$self
->set_flag(
'\\Flagged'
,
@msgs
);
}
sub
unmark {
my
(
$self
,
@msgs
) =
@_
;
@msgs
= @{
$msgs
[0] }
if
ref
$msgs
[0] eq
'ARRAY'
;
return
$self
->unset_flag(
'\\Flagged'
,
@msgs
);
}
sub
unset_flag {
my
(
$self
,
$flag
,
@msgs
) =
@_
;
@msgs
= @{
$msgs
[0] }
if
ref
$msgs
[0] eq
'ARRAY'
;
$flag
=
"\\$flag"
if
$flag
=~ /^(?:Answered|Flagged|Deleted|Seen|Draft)$/i;
return
$self
->store(
join
(
","
,
@msgs
),
"-FLAGS.SILENT ($flag)"
);
}
sub
deny_seeing {
my
(
$self
,
@msgs
) =
@_
;
@msgs
= @{
$msgs
[0] }
if
ref
$msgs
[0] eq
'ARRAY'
;
return
$self
->unset_flag(
'\\Seen'
,
@msgs
);
}
sub
size {
my
(
$self
,
$msg
) =
@_
;
my
$data
=
$self
->fetch(
$msg
,
"(RFC822.SIZE)"
) or
return
undef
;
my
$cmd
=
shift
@$data
;
my
$err
;
foreach
my
$line
(
@$data
) {
return
$1
if
(
$line
=~ /RFC822\.SIZE\s+(\d+)/ );
$err
=
$line
if
(
$line
=~ /\* NO\b/ );
}
if
(
$err
) {
my
$info
=
"$err was returned for $cmd"
;
$info
=~ s/
$CR
?
$LF
//og;
$self
->LastError(
$info
);
}
elsif
( !
$self
->LastError ) {
my
$info
=
"no RFC822.SIZE found in: "
.
join
(
" "
,
@$data
);
$self
->LastError(
$info
);
}
return
undef
;
}
sub
getquotaroot {
my
(
$self
,
$what
) =
@_
;
my
$who
=
defined
$what
?
$self
->Quote(
$what
) :
"INBOX"
;
return
$self
->_imap_command(
"GETQUOTAROOT $who"
) ?
$self
->Results :
undef
;
}
sub
getquota {
my
(
$self
,
$what
) =
@_
;
my
$who
=
defined
$what
?
$self
->Quote(
$what
) :
"user/"
.
$self
->User;
return
$self
->_imap_command(
"GETQUOTA $who"
) ?
$self
->Results :
undef
;
}
sub
setquota(@) {
my
(
$self
,
$what
) = (
shift
,
shift
);
my
$who
=
defined
$what
?
$self
->Quote(
$what
) :
"user/"
.
$self
->User;
my
@limits
;
while
(
@_
) {
my
(
$k
,
$v
) = (
$self
->Quote(
uc
(
shift
@_
) ),
shift
@_
);
push
(
@limits
,
"($k $v)"
);
}
my
$limits
=
join
(
' '
,
@limits
);
$self
->_imap_command(
"SETQUOTA $who $limits"
) ?
$self
->Results :
undef
;
}
sub
quota {
my
(
$self
,
$what
) = (
shift
,
shift
||
"INBOX"
);
my
$tref
=
$self
->getquota(
$what
) or
return
undef
;
shift
@$tref
;
return
(
map
{ /.
*STORAGE
\s+\d+\s+(\d+).*\n$/ ? $1 : () }
@$tref
)[0];
}
sub
quota_usage {
my
(
$self
,
$what
) = (
shift
,
shift
||
"INBOX"
);
my
$tref
=
$self
->getquota(
$what
) or
return
undef
;
shift
@$tref
;
return
(
map
{ /.
*STORAGE
\s+(\d+)\s+\d+.*\n$/ ? $1 : () }
@$tref
)[0];
}
sub
Quote($;$) {
my
(
$self
,
$name
,
$force
) =
@_
;
if
(
$force
or
$name
=~ /["\\[:^ascii:][:cntrl:]]/s ) {
return
"{"
.
length
(
$name
) .
"}"
.
$CRLF
.
$name
;
}
elsif
(
$name
=~ /[(){}\s%*\[\]]/s or
$name
eq
""
) {
return
qq("$name")
;
}
else
{
return
$name
;
}
}
sub
Massage($;$) {
my
(
$self
,
$name
,
$notFolder
) =
@_
;
$name
=~ s/^\"(.*)\"$/$1/s
unless
$notFolder
;
return
$self
->Quote(
$name
);
}
sub
unseen_count {
my
(
$self
,
$folder
) = (
shift
,
shift
);
$folder
||=
$self
->Folder;
$self
->status(
$folder
,
'UNSEEN'
) or
return
undef
;
my
$r
=
first { s/\*\s+STATUS\s+.*\(UNSEEN\s+(\d+)\s*\)/$1/ }
$self
->History;
$r
=~ s/\D//g;
return
$r
;
}
sub
State($) {
my
(
$self
,
$state
) =
@_
;
if
(
defined
$state
) {
$self
->{State} =
$state
;
delete
$self
->{CAPABILITY}
if
(
$state
== Authenticated );
}
return
defined
(
$self
->{State} ) ?
$self
->{State} : Unconnected;
}
sub
Status {
shift
->State }
sub
IsUnconnected {
shift
->State == Unconnected }
sub
IsConnected {
shift
->State >= Connected }
sub
IsAuthenticated {
shift
->State >= Authenticated }
sub
IsSelected {
shift
->State == Selected }
sub
_data {
ref
$_
[1] &&
defined
$_
[1]->[TYPE] ?
$_
[1]->[DATA] :
undef
}
sub
_index {
ref
$_
[1] &&
defined
$_
[1]->[TYPE] ?
$_
[1]->[INDEX] :
undef
}
sub
_type {
ref
$_
[1] &&
$_
[1]->[TYPE] }
sub
_is_literal {
ref
$_
[1] &&
$_
[1]->[TYPE] &&
$_
[1]->[TYPE] eq
'LITERAL'
}
sub
_is_output_or_literal {
ref
$_
[1]
&&
defined
$_
[1]->[TYPE]
&& (
$_
[1]->[TYPE] eq
"OUTPUT"
||
$_
[1]->[TYPE] eq
"LITERAL"
);
}
sub
_is_output {
ref
$_
[1] &&
$_
[1]->[TYPE] &&
$_
[1]->[TYPE] eq
"OUTPUT"
}
sub
_is_input {
ref
$_
[1] &&
$_
[1]->[TYPE] &&
$_
[1]->[TYPE] eq
"INPUT"
}
sub
_next_index {
my
$r
=
$_
[0]->_transaction(
$_
[1] );
$r
}
sub
Range {
my
(
$self
,
$targ
) = (
shift
,
shift
);
UNIVERSAL::isa(
$targ
,
'Mail::IMAPClient::MessageSet'
)
?
$targ
->cat(
@_
)
: Mail::IMAPClient::MessageSet->new(
$targ
,
@_
);
}
1;