ref: 0587025b7d48bae29b0843c2c4ab691b99f82752
dir: /loose.go/
package furgit
import (
"bytes"
"compress/zlib"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
)
const looseHeaderLimit = 4096
func loosePath(id Hash) string {
hex := id.String()
return filepath.Join("objects", hex[:2], hex[2:])
}
func (repo *Repository) looseRead(id Hash) (Object, error) {
ty, body, err := repo.looseReadTyped(id)
if err != nil {
return nil, err
}
return parseObjectBody(ty, id, body)
}
func (repo *Repository) looseReadTyped(id Hash) (ObjType, []byte, error) {
path := repo.repoPath(loosePath(id))
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return ObjInvalid, nil, ErrNotFound
}
return ObjInvalid, nil, err
}
defer func() { _ = f.Close() }()
zr, err := zlib.NewReader(f)
if err != nil {
return ObjInvalid, nil, err
}
defer func() { _ = zr.Close() }()
raw, err := io.ReadAll(zr)
if err != nil {
return ObjInvalid, nil, err
}
nul := bytes.IndexByte(raw, 0)
if nul < 0 {
return ObjInvalid, nil, ErrInvalidObject
}
header := raw[:nul]
body := raw[nul+1:]
ty, declaredSize, err := parseLooseHeader(header)
if err != nil {
return ObjInvalid, nil, err
}
if declaredSize != int64(len(body)) {
return ObjInvalid, nil, ErrInvalidObject
}
if !verifyRawObject(raw, id) {
return ObjInvalid, nil, ErrInvalidObject
}
out := append([]byte(nil), body...)
return ty, out, nil
}
func (repo *Repository) looseTypeSize(id Hash) (ObjType, int64, error) {
path := repo.repoPath(loosePath(id))
// #nosec G304
f, err := os.Open(path)
if err != nil {
if os.IsNotExist(err) {
return ObjInvalid, 0, ErrNotFound
}
return ObjInvalid, 0, err
}
defer func() { _ = f.Close() }()
zr, err := zlib.NewReader(f)
if err != nil {
return ObjInvalid, 0, err
}
defer func() { _ = zr.Close() }()
header := make([]byte, 0, 64)
chunk := make([]byte, 128)
for {
n, readErr := zr.Read(chunk)
if n > 0 {
data := chunk[:n]
if nul := bytes.IndexByte(data, 0); nul >= 0 {
header = append(header, data[:nul]...)
if len(header) > looseHeaderLimit {
return ObjInvalid, 0, ErrInvalidObject
}
break
}
header = append(header, data...)
if len(header) > looseHeaderLimit {
return ObjInvalid, 0, ErrInvalidObject
}
}
if readErr != nil {
if readErr == io.EOF {
return ObjInvalid, 0, ErrInvalidObject
}
return ObjInvalid, 0, readErr
}
}
return parseLooseHeader(header)
}
func parseLooseHeader(header []byte) (ObjType, int64, error) {
space := bytes.IndexByte(header, ' ')
if space < 0 {
return ObjInvalid, 0, ErrInvalidObject
}
ty, err := objTypeFromName(string(header[:space]))
if err != nil {
return ObjInvalid, 0, err
}
expect := header[space+1:]
if len(expect) == 0 {
return ObjInvalid, 0, ErrInvalidObject
}
size, err := strconv.ParseInt(string(expect), 10, 64)
if err != nil {
return ObjInvalid, 0, fmt.Errorf("furgit: loose: size parse: %w", err)
}
if size < 0 {
return ObjInvalid, 0, ErrInvalidObject
}
return ty, size, nil
}
func objTypeFromName(name string) (ObjType, error) {
switch name {
case objNameBlob:
return ObjBlob, nil
case objNameTree:
return ObjTree, nil
case objNameCommit:
return ObjCommit, nil
case objNameTag:
return ObjTag, nil
default:
return ObjInvalid, ErrInvalidObject
}
}