package rardecode import ( "bytes" "errors" "io" "io/fs" ) const ( maxSfxSize = 0x100000 // maximum number of bytes to read when searching for RAR signature sigPrefix = "Rar!\x1A\x07" sigPrefixLen = len(sigPrefix) maxEmptyReads = 100 minBufSize = 32 defaultBufSize = 4096 ) var ( ErrNoSig = errors.New("rardecode: RAR signature not found") ErrNegativeRead = errors.New("rardecode: negative read from Reader") ) type bufVolumeReader struct { r io.Reader sr io.Seeker buf []byte i int n int off int64 err error ver int } func (br *bufVolumeReader) readErr() error { err := br.err br.err = nil return err } func (br *bufVolumeReader) fill() error { if br.err != nil { return br.readErr() } br.i = 0 for i := 0; i < maxEmptyReads; i++ { br.n, br.err = br.r.Read(br.buf) if br.n > 0 { return nil } if br.n < 0 { return ErrNegativeRead } if br.err != nil { return br.readErr() } } return io.ErrNoProgress } func (br *bufVolumeReader) canSeek() bool { return br.sr != nil } func (br *bufVolumeReader) seek(offset int64) error { if br.sr == nil { return fs.ErrInvalid } start := br.off - int64(br.i) end := start + int64(br.n) if offset >= start && offset <= end { diff := offset - br.off br.off += diff br.i += int(diff) return nil } _, err := br.sr.Seek(offset, io.SeekStart) if err != nil { return err } br.i = 0 br.n = 0 br.off = offset return nil } func (br *bufVolumeReader) Read(p []byte) (int, error) { if br.i == br.n { err := br.fill() if err != nil { return 0, err } } n := copy(p, br.buf[br.i:br.n]) br.i += n br.off += int64(n) return n, nil } func (br *bufVolumeReader) ReadByte() (byte, error) { if br.i == br.n { err := br.fill() if err != nil { return 0, err } } c := br.buf[br.i] br.i++ br.off++ return c, nil } func (br *bufVolumeReader) Discard(n int64) error { buffered := int64(br.n - br.i) if buffered >= n { br.i += int(n) br.off += n return nil } // empty buffer n -= buffered br.i = 0 br.n = 0 br.off += buffered // try seek if sr, ok := br.r.(io.Seeker); ok { _, err := sr.Seek(n, io.SeekCurrent) if err != nil { return err } br.off += n return nil } // copy to discard writer written, err := io.CopyN(io.Discard, br.r, n) br.off += written return err } func (br *bufVolumeReader) writeToN(w io.Writer, n int64) (int64, error) { if n == 0 { return 0, nil } var err error todo := n startOffset := br.off for todo != 0 && err == nil { if br.i == br.n { err = br.fill() if err != nil { break } } buf := br.buf[br.i:br.n] if todo > 0 && todo < int64(len(buf)) { buf = buf[:todo] } var l int l, err = w.Write(buf) br.i += l br.off += int64(l) if todo > 0 { todo -= int64(l) } } if todo < 0 && err == io.EOF { err = nil } return br.off - startOffset, nil } // findSig searches for the RAR signature and version at the beginning of a file. // It searches no more than maxSfxSize bytes from the file start. func (br *bufVolumeReader) findSig() (int, error) { for br.off <= maxSfxSize { if br.i == br.n { err := br.fill() if err != nil { return 0, err } } n := bytes.IndexByte(br.buf[br.i:br.n], sigPrefix[0]) if n < 0 { br.off += int64(n) continue } br.i += n br.off += int64(n) // ensure enough bytes available in buffer buffered := br.n - br.i if buffered < sigPrefixLen+2 { br.n = copy(br.buf, br.buf[br.i:br.n]) br.i = 0 l, err := io.ReadAtLeast(br.r, br.buf[br.n:], sigPrefixLen+2-buffered) br.n += l if err != nil { if errors.Is(err, io.ErrUnexpectedEOF) { err = ErrNoSig } return 0, err } } if !bytes.HasPrefix(br.buf[br.i:br.n], []byte(sigPrefix)) { br.i++ br.off++ continue } br.i += sigPrefixLen br.off += int64(sigPrefixLen) ver := int(br.buf[br.i]) if ver == 0 { br.i++ br.off++ } else if br.buf[br.i+1] == 0 { br.i += 2 br.off += 2 } else { continue } return ver, nil } return 0, ErrNoSig } func (br *bufVolumeReader) Reset(r io.Reader) error { br.r = r br.sr, _ = r.(io.Seeker) br.i = 0 br.n = 0 br.off = 0 ver, err := br.findSig() if err != nil { if errors.Is(err, io.EOF) { return ErrNoSig } return err } br.ver = ver return nil } func newBufVolumeReader(r io.Reader, size int) (*bufVolumeReader, error) { if size == 0 { size = defaultBufSize } else { size = max(minBufSize, size) } br := &bufVolumeReader{ buf: make([]byte, size), } err := br.Reset(r) if err != nil { return nil, err } return br, nil }