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 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';
20 # BUG? strict check on capability commented out for now...
21 #my $can = $self->has_capability("COMPRESS")
22 #return undef unless $can and $can eq "DEFLATE";
24 $self->_imap_command("COMPRESS DEFLATE") or return undef;
26 my $zcl = $self->_load_module("Compress-Zlib") or return undef;
28 # give caller control of args if desired
31 -WindowBits => -$zcl->MAX_WBITS(),
32 -Level => $zcl->Z_BEST_SPEED()
34 ) unless ( $self->Compress and ref( $self->Compress ) eq "ARRAY" );
38 ( $do, $rc ) = Compress::Zlib::deflateInit( @{ $self->Compress } );
39 unless ( $rc == $zcl->Z_OK ) {
40 $self->LastError("deflateInit failed (rc=$rc)");
45 Compress::Zlib::inflateInit( -WindowBits => -$zcl->MAX_WBITS() );
46 unless ( $rc == $zcl->Z_OK ) {
47 $self->LastError("inflateInit failed (rc=$rc)");
51 $self->{Prewritemethod} = sub {
52 my ( $self, $string ) = @_;
54 my ( $rc, $out1, $out2 );
55 ( $out1, $rc ) = $do->deflate($string);
56 ( $out2, $rc ) = $do->flush( $zcl->Z_PARTIAL_FLUSH() )
57 unless ( $rc != $zcl->Z_OK );
59 unless ( $rc == $zcl->Z_OK ) {
60 $self->LastError("deflate/flush failed (rc=$rc)");
67 # need to retain some state for Readmoremethod/Readmethod calls
68 my ( $Zbuf, $Ibuf ) = ( "", "" );
70 $self->{Readmoremethod} = sub {
72 return 1 if ( length($Zbuf) || length($Ibuf) );
73 $self->__read_more(@_);
76 $self->{Readmethod} = sub {
77 my ( $self, $fh, $buf, $len, $off ) = @_;
79 # get more data, but empty $Ibuf first if any data is left
80 my ( $lz, $li ) = ( length $Zbuf, length $Ibuf );
82 my $readlen = $self->Buffer || 4096;
83 my $ret = sysread( $fh, $Zbuf, $readlen, length $Zbuf );
85 return $ret if ( !$ret && !$lz ); # $ret is undef or 0
88 # accumulate inflated data in $Ibuf
90 my ( $tbuf, $rc ) = $io->inflate( \$Zbuf );
91 unless ( $rc == $zcl->Z_OK ) {
92 $self->LastError("inflate failed (rc=$rc)");
100 # note: faking EAGAIN here is only safe with level-triggered
101 # I/O readiness notifications (select, poll). Refactoring
102 # callers will be needed in the unlikely case somebody wants
103 # to use edge-triggered notifications (EV_CLEAR, EPOLLET).
108 # pull desired length of data from $Ibuf
109 my $tbuf = substr( $Ibuf, 0, $len );
110 substr( $Ibuf, 0, $len ) = "";
111 substr( $$buf, $off ) = $tbuf;