]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/IMAPdeflate.pm
preliminary imap server implementation
[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 # $_[1] may be a reference or not
54 sub do_read ($$$$) {
55         my ($self, $rbuf, $len, $off) = @_;
56
57         my $zin = $self->{zin} or return; # closed
58         my $doff;
59         my $dbuf = delete($self->{dbuf}) // '';
60         $doff = length($dbuf);
61         my $r = PublicInbox::DS::do_read($self, \$dbuf, $len, $doff) or return;
62
63         # assert(length($$rbuf) == $off) as far as NNTP.pm is concerned
64         # -ConsumeInput is true, so $dbuf is automatically emptied
65         my $err = $zin->inflate($dbuf, $rbuf);
66         if ($err == Z_OK) {
67                 $self->{dbuf} = $dbuf if $dbuf ne '';
68                 $r = length($$rbuf) and return $r;
69                 # nothing ready, yet, get more, later
70                 $self->requeue;
71         } else {
72                 delete $self->{zin};
73                 $self->close;
74         }
75         0;
76 }
77
78 # override PublicInbox::DS::msg_more
79 sub msg_more ($$) {
80         my $self = $_[0];
81
82         # $_[1] may be a reference or not for ->deflate
83         my $err = $zout->deflate($_[1], $zbuf);
84         $err == Z_OK or die "->deflate failed $err";
85         1;
86 }
87
88 sub zflush ($) {
89         my ($self) = @_;
90
91         my $deflated = $zbuf;
92         $zbuf = \(my $next = '');
93
94         my $err = $zout->flush($deflated, Z_FULL_FLUSH);
95         $err == Z_OK or die "->flush failed $err";
96
97         # We can still let the lower socket layer do buffering:
98         PublicInbox::DS::msg_more($self, $$deflated);
99 }
100
101 # compatible with PublicInbox::DS::write, so $_[1] may be a reference or not
102 sub write ($$) {
103         my $self = $_[0];
104         return PublicInbox::DS::write($self, $_[1]) if ref($_[1]) eq 'CODE';
105
106         my $deflated = $zbuf;
107         $zbuf = \(my $next = '');
108
109         # $_[1] may be a reference or not for ->deflate
110         my $err = $zout->deflate($_[1], $deflated);
111         $err == Z_OK or die "->deflate failed $err";
112         $err = $zout->flush($deflated, Z_FULL_FLUSH);
113         $err == Z_OK or die "->flush failed $err";
114
115         # We can still let the socket layer do buffering:
116         PublicInbox::DS::write($self, $deflated);
117 }
118
119 1;