about summary refs log tree commit
path: root/lib/Net/NNTP/Deflate.pm
diff options
context:
space:
mode:
Diffstat (limited to 'lib/Net/NNTP/Deflate.pm')
-rw-r--r--lib/Net/NNTP/Deflate.pm128
1 files changed, 128 insertions, 0 deletions
diff --git a/lib/Net/NNTP/Deflate.pm b/lib/Net/NNTP/Deflate.pm
new file mode 100644
index 0000000..90b5685
--- /dev/null
+++ b/lib/Net/NNTP/Deflate.pm
@@ -0,0 +1,128 @@
+# Net::NNTP::Deflate
+#
+# Copyright (C) 2019 Eric Wong <e@80x24.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself.
+#
+# support NNTP COMPRESS DEFLATE as specified in RFC 8054.  This
+# is an internal class intended for use by Net::NNTP use, only
+#
+# This is done via subclassing and SUPER.  tie can't seem to work
+# since it doesn't seem to stack and layer with IO::Socket::SSL
+# (which also uses tie).  PerlIO won't work, either, since Net::Cmd
+# depends on sysread/syswrite semantics for timeouts.
+package Net::NNTP::Deflate;
+
+use 5.008001;
+
+use strict;
+use warnings;
+
+use Compress::Raw::Zlib;
+use base qw(Net::NNTP);
+my %ZOPT = ( -WindowBits => -15, -AppendOutput => 1 );
+use Errno qw(ECONNRESET);
+
+sub compression { 'DEFLATE' }
+
+sub wrap {
+  my ($class, $self) = @_;
+  my ($zin, $zout, $err);
+
+  ($zin, $err) = Compress::Raw::Zlib::Inflate->new(%ZOPT);
+  $err == Z_OK or die "Inflate->new failed: $err\n";
+  ${*$self}{net_nntp_inflate} = [ $zin, '' ];
+
+  ($zout, $err) = Compress::Raw::Zlib::Deflate->new(%ZOPT);
+  $err == Z_OK or die "Deflate->new failed: $err\n";
+  ${*$self}{net_nntp_deflate} = [ $zout, '', 0 ];
+
+  # We need to capture these methods now, otherwise tied calls via
+  # IO::Socket::SSL::SSL_HANDLE::* will infinitely recurse.
+  # XXX sysread/syswrite are obviously needed for wrapping,
+  # but I don't understand why close and fileno wrappers are needed,
+  # but infinite recursion ensues without them :<
+  for my $m (qw(sysread syswrite close fileno)) {
+    ${*$self}{"net_nntp_$m"} = $self->can($m);
+  }
+
+  $class .= 'SSL' if $self->isa('IO::Socket::SSL');
+  bless $self, $class;
+}
+
+sub fileno {
+  my $self = shift;
+  ${*$self}{net_nntp_fileno}->($self);
+}
+
+sub close {
+  my $self = shift;
+  ${*$self}{net_nntp_close}->($self);
+}
+
+sub syswrite {
+  my ($self, undef, $len, $off) = @_;
+  my $de = ${*$self}{net_nntp_deflate};
+  my $zout = $de->[0];
+
+  $off = 0 unless defined($off);
+  $len = bytes::length($_[1]) unless defined($len);
+
+  my $err = $zout->deflate(substr($_[1], $off, $len), $de->[1]);
+  $err == Z_OK or die "->deflate failed: $err";
+
+  $err = $zout->flush($de->[1], Z_PARTIAL_FLUSH);
+  $err == Z_OK or die "->flush failed: $err";
+
+  my $olen = length($de->[1]);
+  my $w = ${*$self}{net_nntp_syswrite}->($self, $de->[1], $olen, $de->[2]);
+  defined($w) or return;
+
+  if ($olen == $w) { # all done!
+    $de->[1] = '';
+    $de->[2] = 0;
+  }
+  else { # try again, later
+    $de->[2] += $w;
+  }
+
+  $len;
+}
+
+# note: only intended for Net::Cmd use, so returning extra data
+# beyond $len won't matter
+sub sysread {
+  my ($self, undef, $len, $off) = @_;
+
+  if (!defined($off) || $off != length($_[1])) {
+    die __PACKAGE__.'::sysread is only intended for Net::Cmd use';
+  }
+
+  my $in = ${*$self}{net_nntp_inflate};
+
+  while (1) {
+    my $ioff = length($in->[1]);
+    my $r = ${*$self}{net_nntp_sysread}->($self, $in->[1], $len, $ioff);
+    $r or return $r; # $r may be undef or 0
+
+    my $err = $in->[0]->inflate($in->[1], $_[1]); # consumes $in->[1]
+    if ($err == Z_OK) {
+      $r = length($_[1]);
+      return $r if $r;
+      next;
+    }
+
+    return 0 if $err == Z_STREAM_END;
+
+    # some other error from ->inflate, probably not recoverable
+    warn "Error: $err from ".ref($in->[0])."::inflate\n";
+    $! = ECONNRESET;
+    return;
+  }
+}
+
+package Net::NNTP::DeflateSSL;
+use base qw(Net::NNTP::Deflate Net::NNTP::_SSL);
+
+1;