]> Sergey Matveev's repositories - public-inbox.git/blob - lib/PublicInbox/MboxGz.pm
598b103478384fe656724b76d61d488a580b575b
[public-inbox.git] / lib / PublicInbox / MboxGz.pm
1 # Copyright (C) 2015-2020 all contributors <meta@public-inbox.org>
2 # License: AGPL-3.0+ <https://www.gnu.org/licenses/agpl-3.0.txt>
3 package PublicInbox::MboxGz;
4 use strict;
5 use parent 'PublicInbox::GzipFilter';
6 use PublicInbox::Eml;
7 use PublicInbox::Hval qw/to_filename/;
8 use PublicInbox::Mbox;
9 use PublicInbox::GitAsyncCat;
10 *msg_hdr = \&PublicInbox::Mbox::msg_hdr;
11 *msg_body = \&PublicInbox::Mbox::msg_body;
12
13 # this is public-inbox-httpd-specific
14 sub mboxgz_blob_cb { # git->cat_async callback
15         my ($bref, $oid, $type, $size, $self) = @_;
16         my $http = $self->{env}->{'psgix.io'} or return; # client abort
17         my $smsg = delete $self->{smsg} or die 'BUG: no smsg';
18         if (!defined($oid)) {
19                 # it's possible to have TOCTOU if an admin runs
20                 # public-inbox-(edit|purge), just move onto the next message
21                 return $http->next_step(\&async_next);
22         } else {
23                 $smsg->{blob} eq $oid or die "BUG: $smsg->{blob} != $oid";
24         }
25         $self->zmore(msg_hdr($self,
26                                 PublicInbox::Eml->new($bref)->header_obj,
27                                 $smsg->{mid}));
28
29         # PublicInbox::HTTP::{Chunked,Identity}::write
30         $self->{http_out}->write($self->translate(msg_body($$bref)));
31
32         $http->next_step(\&async_next);
33 }
34
35 # this is public-inbox-httpd-specific
36 sub async_step ($) {
37         my ($self) = @_;
38         if (my $smsg = $self->{smsg} = $self->{cb}->($self)) {
39                 git_async_cat($self->{-inbox}->git, $smsg->{blob},
40                                 \&mboxgz_blob_cb, $self);
41         } elsif (my $out = delete $self->{http_out}) {
42                 $out->write($self->zflush);
43                 $out->close;
44         }
45 }
46
47 # called by PublicInbox::DS::write
48 sub async_next {
49         my ($http) = @_; # PublicInbox::HTTP
50         async_step($http->{forward});
51 }
52
53 # called by PublicInbox::HTTP::close, or any other PSGI server
54 sub close { !!delete($_[0]->{http_out}) }
55
56 sub response {
57         my ($class, $self, $cb, $fn) = @_;
58         $self->{base_url} = $self->{-inbox}->base_url($self->{env});
59         $self->{cb} = $cb;
60         $self->{gz} = PublicInbox::GzipFilter::gzip_or_die();
61         bless $self, $class;
62         # http://www.iana.org/assignments/media-types/application/gzip
63         $fn = defined($fn) && $fn ne '' ? to_filename($fn) : 'no-subject';
64         my $h = [ qw(Content-Type application/gzip),
65                 'Content-Disposition', "inline; filename=$fn.mbox.gz" ];
66         if ($self->{env}->{'pi-httpd.async'}) {
67                 sub {
68                         my ($wcb) = @_; # -httpd provided write callback
69                         $self->{http_out} = $wcb->([200, $h]);
70                         $self->{env}->{'psgix.io'}->{forward} = $self;
71                         async_step($self); # start stepping
72                 };
73         } else { # generic PSGI
74                 [ 200, $h, $self ];
75         }
76 }
77
78 # called by Plack::Util::foreach or similar (generic PSGI)
79 sub getline {
80         my ($self) = @_;
81         my $cb = $self->{cb} or return;
82         while (my $smsg = $cb->($self)) {
83                 my $mref = $self->{-inbox}->msg_by_smsg($smsg) or next;
84                 my $h = PublicInbox::Eml->new($mref)->header_obj;
85                 $self->zmore(msg_hdr($self, $h, $smsg->{mid}));
86                 return $self->translate(msg_body($$mref));
87         }
88         # signal that we're done and can return undef next call:
89         delete $self->{cb};
90         $self->zflush;
91 }
92
93 1;