]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/IMAPdeflate.pm
imap: fix pipelining with async git
[public-inbox.git] / lib / PublicInbox / IMAPdeflate.pm
1 # Copyright (C) 2020 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3 # TODO: reduce duplication from PublicInbox::NNTPdeflate
4
5 # RFC 4978
6 package PublicInbox::IMAPdeflate;
7 use strict;
8 use warnings;
9 use 5.010_001;
10 use base qw(PublicInbox::IMAP);
11 use Compress::Raw::Zlib;
12 use Hash::Util qw(unlock_hash); # dependency of fields for perl 5.10+, anyways
13
14 my %IN_OPT = (
15         -Bufsize => 1024,
16         -WindowBits => -15, # RFC 1951
17         -AppendOutput => 1,
18 );
19
20 # global deflate context and buffer
21 my $zbuf = \(my $buf = '');
22 my $zout;
23 {
24         my $err;
25         ($zout, $err) = Compress::Raw::Zlib::Deflate->new(
26                 # nnrpd (INN) and Compress::Raw::Zlib favor MemLevel=9,
27                 # the zlib C library and git use MemLevel=8 as the default
28                 # -MemLevel => 9,
29                 -Bufsize => 65536, # same as nnrpd
30                 -WindowBits => -15, # RFC 1951
31                 -AppendOutput => 1,
32         );
33         $err == Z_OK or die "Failed to initialize zlib deflate stream: $err";
34 }
35
36 sub enable {
37         my ($class, $self, $tag) = @_;
38         my ($in, $err) = Compress::Raw::Zlib::Inflate->new(%IN_OPT);
39         if ($err != Z_OK) {
40                 $self->err("Inflate->new failed: $err");
41                 $self->write(\"$tag BAD failed to activate compression\r\n");
42                 return;
43         }
44         unlock_hash(%$self);
45         $self->write(\"$tag OK DEFLATE active\r\n");
46         bless $self, $class;
47         $self->{zin} = $in;
48 }
49
50 # overrides PublicInbox::NNTP::compressed
51 sub compressed { 1 }
52
53 sub do_read ($$$$) {
54         my ($self, $rbuf, $len, $off) = @_;
55
56         my $zin = $self->{zin} or return; # closed
57         my $doff;
58         my $dbuf = delete($self->{dbuf}) // '';
59         $doff = length($dbuf);
60         my $r = PublicInbox::DS::do_read($self, \$dbuf, $len, $doff) or return;
61
62         # Workaround inflate bug appending to OOK scalars:
63         # <https://rt.cpan.org/Ticket/Display.html?id=132734>
64         # We only have $off if the client is pipelining, and pipelining
65         # is where our substr() OOK optimization in event_step makes sense.
66         if ($off) {
67                 my $copy = $$rbuf;
68                 undef $$rbuf;
69                 $$rbuf = $copy;
70         }
71
72         # assert(length($$rbuf) == $off) as far as NNTP.pm is concerned
73         # -ConsumeInput is true, so $dbuf is automatically emptied
74         my $err = $zin->inflate($dbuf, $rbuf);
75         if ($err == Z_OK) {
76                 $self->{dbuf} = $dbuf if $dbuf ne '';
77                 $r = length($$rbuf) and return $r;
78                 # nothing ready, yet, get more, later
79                 $self->requeue;
80         } else {
81                 delete $self->{zin};
82                 $self->close;
83         }
84         0;
85 }
86
87 # override PublicInbox::DS::msg_more
88 sub msg_more ($$) {
89         my $self = $_[0];
90
91         # $_[1] may be a reference or not for ->deflate
92         my $err = $zout->deflate($_[1], $zbuf);
93         $err == Z_OK or die "->deflate failed $err";
94         1;
95 }
96
97 sub zflush ($) {
98         my ($self) = @_;
99
100         my $deflated = $zbuf;
101         $zbuf = \(my $next = '');
102
103         my $err = $zout->flush($deflated, Z_FULL_FLUSH);
104         $err == Z_OK or die "->flush failed $err";
105
106         # We can still let the lower socket layer do buffering:
107         PublicInbox::DS::msg_more($self, $$deflated);
108 }
109
110 # compatible with PublicInbox::DS::write, so $_[1] may be a reference or not
111 sub write ($$) {
112         my $self = $_[0];
113         return PublicInbox::DS::write($self, $_[1]) if ref($_[1]) eq 'CODE';
114
115         my $deflated = $zbuf;
116         $zbuf = \(my $next = '');
117
118         # $_[1] may be a reference or not for ->deflate
119         my $err = $zout->deflate($_[1], $deflated);
120         $err == Z_OK or die "->deflate failed $err";
121         $err = $zout->flush($deflated, Z_FULL_FLUSH);
122         $err == Z_OK or die "->flush failed $err";
123
124         # We can still let the socket layer do buffering:
125         PublicInbox::DS::write($self, $deflated);
126 }
127
128 1;