]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/IMAPClient.pm
imap+nntp: share COMPRESS implementation
[public-inbox.git] / lib / PublicInbox / IMAPClient.pm
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.
4 #
5 # The license for this file differs from the rest of public-inbox.
6 #
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;
12 use strict;
13 use parent 'Mail::IMAPClient';
14 unless (eval('use Mail::IMAPClient 3.43')) {
15 require Errno;
16 no warnings 'once';
17
18 # RFC4978 COMPRESS
19 *compress = sub {
20     my ($self) = @_;
21
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";
25
26     $self->_imap_command("COMPRESS DEFLATE") or return undef;
27
28     my $zcl = $self->_load_module("Compress-Zlib") or return undef;
29
30     # give caller control of args if desired
31     $self->Compress(
32         [
33             -WindowBits => -$zcl->MAX_WBITS(),
34             -Level      => $zcl->Z_BEST_SPEED()
35         ]
36     ) unless ( $self->Compress and ref( $self->Compress ) eq "ARRAY" );
37
38     my ( $rc, $do, $io );
39
40     ( $do, $rc ) = Compress::Zlib::deflateInit( @{ $self->Compress } );
41     unless ( $rc == $zcl->Z_OK ) {
42         $self->LastError("deflateInit failed (rc=$rc)");
43         return undef;
44     }
45
46     ( $io, $rc ) =
47       Compress::Zlib::inflateInit( -WindowBits => -$zcl->MAX_WBITS() );
48     unless ( $rc == $zcl->Z_OK ) {
49         $self->LastError("inflateInit failed (rc=$rc)");
50         return undef;
51     }
52
53     $self->{Prewritemethod} = sub {
54         my ( $self, $string ) = @_;
55
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 );
60
61         unless ( $rc == $zcl->Z_OK ) {
62             $self->LastError("deflate/flush failed (rc=$rc)");
63             return undef;
64         }
65
66         return $out1 . $out2;
67     };
68
69     # need to retain some state for Readmoremethod/Readmethod calls
70     my ( $Zbuf, $Ibuf ) = ( "", "" );
71
72     $self->{Readmoremethod} = sub {
73         my $self = shift;
74         return 1 if ( length($Zbuf) || length($Ibuf) );
75         $self->__read_more(@_);
76     };
77
78     $self->{Readmethod} = sub {
79         my ( $self, $fh, $buf, $len, $off ) = @_;
80
81         # get more data, but empty $Ibuf first if any data is left
82         my ( $lz, $li ) = ( length $Zbuf, length $Ibuf );
83         if ( $lz || !$li ) {
84             my $readlen = $self->Buffer || 4096;
85             my $ret = sysread( $fh, $Zbuf, $readlen, length $Zbuf );
86             $lz = length $Zbuf;
87             return $ret if ( !$ret && !$lz );    # $ret is undef or 0
88         }
89
90         # accumulate inflated data in $Ibuf
91         if ($lz) {
92             my ( $tbuf, $rc ) = $io->inflate( \$Zbuf );
93             unless ( $rc == $zcl->Z_OK ) {
94                 $self->LastError("inflate failed (rc=$rc)");
95                 return undef;
96             }
97             $Ibuf .= $tbuf;
98             $li = length $Ibuf;
99         }
100
101         if ( !$li ) {
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();
107             return undef;
108         }
109
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;
114
115         return length $tbuf;
116     };
117
118     return $self;
119 };
120 } # $Mail::IMAPClient::VERSION < 3.43
121
122 1;