1 # This library is free software; you can redistribute it and/or modify it
2 # under the same terms as Perl itself, either Perl version 5.8.0 or, at
3 # your option, any later version of Perl 5 you may have available.
5 # The license for this file differs from the rest of public-inbox.
7 # Workaround some bugs in upstream Mail::IMAPClient <= 3.42 when
8 # compression is enabled:
9 # - reference cycle: https://rt.cpan.org/Ticket/Display.html?id=132654
10 # - read starvation: https://rt.cpan.org/Ticket/Display.html?id=132720
11 package PublicInbox::IMAPClient;
13 use parent 'Mail::IMAPClient';
14 unless (eval('use Mail::IMAPClient 3.43')) {
22 # BUG? strict check on capability commented out for now...
23 #my $can = $self->has_capability("COMPRESS")
24 #return undef unless $can and $can eq "DEFLATE";
26 $self->_imap_command("COMPRESS DEFLATE") or return undef;
28 my $zcl = $self->_load_module("Compress-Zlib") or return undef;
30 # give caller control of args if desired
33 -WindowBits => -$zcl->MAX_WBITS(),
34 -Level => $zcl->Z_BEST_SPEED()
36 ) unless ( $self->Compress and ref( $self->Compress ) eq "ARRAY" );
40 ( $do, $rc ) = Compress::Zlib::deflateInit( @{ $self->Compress } );
41 unless ( $rc == $zcl->Z_OK ) {
42 $self->LastError("deflateInit failed (rc=$rc)");
47 Compress::Zlib::inflateInit( -WindowBits => -$zcl->MAX_WBITS() );
48 unless ( $rc == $zcl->Z_OK ) {
49 $self->LastError("inflateInit failed (rc=$rc)");
53 $self->{Prewritemethod} = sub {
54 my ( $self, $string ) = @_;
56 my ( $rc, $out1, $out2 );
57 ( $out1, $rc ) = $do->deflate($string);
58 ( $out2, $rc ) = $do->flush( $zcl->Z_PARTIAL_FLUSH() )
59 unless ( $rc != $zcl->Z_OK );
61 unless ( $rc == $zcl->Z_OK ) {
62 $self->LastError("deflate/flush failed (rc=$rc)");
69 # need to retain some state for Readmoremethod/Readmethod calls
70 my ( $Zbuf, $Ibuf ) = ( "", "" );
72 $self->{Readmoremethod} = sub {
74 return 1 if ( length($Zbuf) || length($Ibuf) );
75 $self->__read_more(@_);
78 $self->{Readmethod} = sub {
79 my ( $self, $fh, $buf, $len, $off ) = @_;
81 # get more data, but empty $Ibuf first if any data is left
82 my ( $lz, $li ) = ( length $Zbuf, length $Ibuf );
84 my $readlen = $self->Buffer || 4096;
85 my $ret = sysread( $fh, $Zbuf, $readlen, length $Zbuf );
87 return $ret if ( !$ret && !$lz ); # $ret is undef or 0
90 # accumulate inflated data in $Ibuf
92 my ( $tbuf, $rc ) = $io->inflate( \$Zbuf );
93 unless ( $rc == $zcl->Z_OK ) {
94 $self->LastError("inflate failed (rc=$rc)");
102 # note: faking EAGAIN here is only safe with level-triggered
103 # I/O readiness notifications (select, poll). Refactoring
104 # callers will be needed in the unlikely case somebody wants
105 # to use edge-triggered notifications (EV_CLEAR, EPOLLET).
106 $! = Errno::EAGAIN();
110 # pull desired length of data from $Ibuf
111 my $tbuf = substr( $Ibuf, 0, $len );
112 substr( $Ibuf, 0, $len ) = "";
113 substr( $$buf, $off ) = $tbuf;
120 } # $Mail::IMAPClient::VERSION < 3.43