]> Sergey Matveev's repositories - tofuproxy.git/blob - gzip.go
609d7c502983ff8142b503abcae3d5cdde545628
[tofuproxy.git] / gzip.go
1 /*
2 tofuproxy -- flexible HTTP/HTTPS proxy, TLS terminator, X.509 TOFU
3              manager, WARC/geminispace browser
4 Copyright (C) 2021-2023 Sergey Matveev <stargrave@stargrave.org>
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, version 3 of the License.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package warc
20
21 import (
22         "bufio"
23         "compress/gzip"
24         "io"
25         "os"
26 )
27
28 type CountableReader struct {
29         *bufio.Reader
30         n int64
31 }
32
33 func (r *CountableReader) ReadByte() (b byte, err error) {
34         b, err = r.Reader.ReadByte()
35         if err == nil {
36                 r.n++
37         }
38         return b, err
39 }
40
41 func (r *CountableReader) Read(p []byte) (n int, err error) {
42         n, err = r.Reader.Read(p)
43         r.n += int64(n)
44         return
45 }
46
47 type GZIPReader struct {
48         fd      *os.File
49         r       io.Reader
50         offsets []Offset
51 }
52
53 func (r *GZIPReader) Read(p []byte) (n int, err error) {
54         return r.r.Read(p)
55 }
56
57 func (r *GZIPReader) Close() error {
58         return r.fd.Close()
59 }
60
61 func (r *GZIPReader) Offsets() []Offset {
62         return r.offsets
63 }
64
65 func NewGZIPReader(
66         warcPath string,
67         offsets []Offset,
68         uOffset int64,
69 ) (*GZIPReader, error) {
70         var offZ, offU int64
71         for _, off := range offsets {
72                 if uOffset < offU+off.U {
73                         break
74                 }
75                 offU += off.U
76                 offZ += off.Z
77         }
78         fd, err := os.Open(warcPath)
79         if err != nil {
80                 return nil, err
81         }
82         if _, err = fd.Seek(offZ, io.SeekStart); err != nil {
83                 fd.Close()
84                 return nil, err
85         }
86         cr := &CountableReader{Reader: bufio.NewReader(fd)}
87         z, err := gzip.NewReader(cr)
88         if err != nil {
89                 fd.Close()
90                 return nil, err
91         }
92         r, w := io.Pipe()
93         gr := GZIPReader{r: r}
94         go func() {
95                 z.Multistream(false)
96                 var offset, offsetPrev int64
97                 for {
98                         written, err := io.Copy(w, z)
99                         if err != nil {
100                                 w.CloseWithError(err)
101                                 return
102                         }
103                         offset = cr.n
104                         gr.offsets = append(gr.offsets, Offset{offset - offsetPrev, written})
105                         offsetPrev = offset
106                         err = z.Reset(cr)
107                         if err != nil {
108                                 if err == io.EOF {
109                                         break
110                                 }
111                                 w.CloseWithError(err)
112                                 return
113                         }
114                         z.Multistream(false)
115                 }
116                 w.CloseWithError(io.EOF)
117         }()
118         _, err = io.CopyN(io.Discard, r, uOffset-offU)
119         if err != nil {
120                 fd.Close()
121                 return nil, err
122         }
123         return &gr, nil
124 }