Skip to content
go-libfossil Artifact [3a849800a2]
Login

Artifact [3a849800a2]

Artifact 3a849800a21d3d888cf9b8c32b338a0d1ba6f3ef6f66215efef1b5dcb01612de:


package sync

import (
	"bytes"
	"compress/zlib"
	"encoding/binary"
	"fmt"
	"io"

	"github.com/danmestas/go-libfossil/db"
	"github.com/danmestas/go-libfossil/internal/content"
	libfossil "github.com/danmestas/go-libfossil/internal/fsltype"
)

type requiredLocalFrame struct {
	rid  libfossil.FslID
	uuid string
	size int64
}

// Local bases are untrusted too. Account for compressed buffers, inflated
// programs and delta outputs before allocating any of them, not after Expand.
func (f *requiredFetch) expandLocal(q db.Querier, rid libfossil.FslID) ([]byte, error) {
	if q == nil || rid <= 0 {
		panic("sync.requiredFetch.expandLocal: nil querier or nonpositive rid")
	}
	var path []requiredLocalFrame
	seen := make(map[libfossil.FslID]bool)
	for depth := 0; rid > 0; depth++ {
		if depth >= maxRequiredDeltaDepth || seen[rid] {
			return nil, fmt.Errorf("sparse required content: cyclic or excessive local delta depth")
		}
		seen[rid] = true
		frame := requiredLocalFrame{rid: rid}
		var source libfossil.FslID
		err := q.QueryRow(`SELECT b.uuid,b.size,COALESCE(d.srcid,0)
			FROM blob b LEFT JOIN delta d ON d.rid=b.rid WHERE b.rid=?`, rid).
			Scan(&frame.uuid, &frame.size, &source)
		if err != nil {
			return nil, err
		}
		if err := f.checkName(frame.uuid); err != nil {
			return nil, err
		}
		if content.IsPrivate(q, int64(rid)) && !f.opts.Private {
			return nil, fmt.Errorf("sparse required content: private local delta basis requires permission")
		}
		if frame.size < 0 || frame.size > int64(maxRequiredBytes-f.bytes) {
			return nil, fmt.Errorf("sparse required content: local size exceeds staging budget")
		}
		path = append(path, frame)
		rid = source
	}
	var full []byte
	for i := len(path) - 1; i >= 0; i-- {
		frame := path[i]
		payload, err := f.localPayload(q, frame)
		if err != nil {
			return nil, err
		}
		if i == len(path)-1 {
			full = payload
		} else {
			full, err = f.applyDelta(frame.uuid, payload, full)
			if err != nil {
				return nil, err
			}
		}
		if int64(len(full)) != frame.size || !hashMatches(frame.uuid, full) {
			return nil, fmt.Errorf("sparse required content: invalid local size or hash for %s", frame.uuid)
		}
	}
	return full, nil
}

func (f *requiredFetch) localPayload(q db.Querier, frame requiredLocalFrame) ([]byte, error) {
	if err := f.ctx.Err(); err != nil {
		return nil, err
	}
	var packedSize int64
	var prefix []byte
	if err := q.QueryRow("SELECT length(content),substr(content,1,4) FROM blob WHERE rid=?",
		frame.rid).Scan(&packedSize, &prefix); err != nil {
		return nil, err
	}
	if len(prefix) != 4 || packedSize < 5 {
		return nil, fmt.Errorf("sparse required content: invalid local compression header")
	}
	plainSize := int64(binary.BigEndian.Uint32(prefix))
	remaining := int64(maxRequiredBytes - f.bytes)
	if packedSize > remaining || plainSize >= remaining-packedSize {
		return nil, fmt.Errorf("sparse required content: local inflation exceeds staging budget")
	}
	if err := f.reserve(int(packedSize + plainSize + 1)); err != nil {
		return nil, err
	}
	var packed []byte
	err := q.QueryRow(`SELECT content FROM blob WHERE rid=? AND size=?
		AND length(content)=? AND substr(content,1,4)=?`,
		frame.rid, frame.size, packedSize, prefix).Scan(&packed)
	if err != nil {
		return nil, err
	}
	return requiredInflate(packed, int(plainSize))
}

func requiredInflate(packed []byte, size int) ([]byte, error) {
	reader, err := zlib.NewReader(bytes.NewReader(packed[4:]))
	if err != nil {
		return nil, err
	}
	// Read one extra byte to detect a forged low prefix without expanding the
	// unbounded remainder. Reaching EOF also verifies the zlib checksum.
	plain := make([]byte, size+1)
	n, readErr := io.ReadFull(reader, plain)
	closeErr := reader.Close()
	if readErr != io.EOF && readErr != io.ErrUnexpectedEOF {
		if readErr == nil {
			return nil, fmt.Errorf("sparse required content: local compression length mismatch")
		}
		return nil, readErr
	}
	if closeErr != nil {
		return nil, closeErr
	}
	if n != size {
		return nil, fmt.Errorf("sparse required content: local compression length mismatch")
	}
	return plain[:size], nil
}