--- /dev/null
+/*
+tofuproxy -- flexible HTTP/WARC proxy with TLS certificates management
+Copyright (C) 2021 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package warc
+
+import (
+ "io"
+ "strings"
+)
+
+type Record struct {
+ WARCPath string
+ Offset int64
+ Hdr Header
+ HdrLen int
+ Size int64
+
+ Continuations []*Record
+}
+
+func (rec *Record) URI() string {
+ return strings.Trim(rec.Hdr.Get("WARC-Target-URI"), "<>")
+}
+
+func (rec *Record) TotalSize() int64 {
+ s := rec.Size
+ for _, r := range rec.Continuations {
+ s += r.Size
+ }
+ return s
+}
+
+type SelfRecordReader struct {
+ r *io.LimitedReader
+ rsc io.ReadSeekCloser
+}
+
+func (srr *SelfRecordReader) Read(p []byte) (n int, err error) {
+ n, err = srr.r.Read(p)
+ if err != nil {
+ srr.Close()
+ }
+ return
+}
+
+func (srr *SelfRecordReader) Close() error {
+ return srr.rsc.Close()
+}
+
+func (rec *Record) selfReader(noHdr bool) (*SelfRecordReader, error) {
+ rsc, err := Open(rec.WARCPath)
+ if err != nil {
+ return nil, err
+ }
+ offset := rec.Offset
+ if noHdr {
+ offset += int64(rec.HdrLen)
+ }
+ if _, err = rsc.Seek(offset, io.SeekStart); err != nil {
+ rsc.Close()
+ return nil, err
+ }
+ return &SelfRecordReader{r: &io.LimitedReader{R: rsc, N: rec.Size}, rsc: rsc}, nil
+}
+
+type RecordReader struct {
+ r io.Reader
+ srrs []*SelfRecordReader
+}
+
+func (rec *Record) Reader(noHdr bool) (*RecordReader, error) {
+ srrs := make([]*SelfRecordReader, 0, 1+len(rec.Continuations))
+ rs := make([]io.Reader, 0, 1+len(rec.Continuations))
+ for i, r := range append([]*Record{rec}, rec.Continuations...) {
+ if i > 0 {
+ noHdr = true
+ }
+ srr, err := r.selfReader(noHdr)
+ if err != nil {
+ for _, srr := range srrs {
+ srr.Close()
+ }
+ return nil, err
+ }
+ srrs = append(srrs, srr)
+ rs = append(rs, srr)
+ }
+ return &RecordReader{r: io.MultiReader(rs...), srrs: srrs}, nil
+}
+
+func (rr *RecordReader) Read(p []byte) (n int, err error) {
+ n, err = rr.r.Read(p)
+ if err != nil {
+ rr.Close()
+ }
+ return
+}
+
+func (rr *RecordReader) Close() error {
+ for _, srr := range rr.srrs {
+ srr.Close()
+ }
+ return nil
+}