diff options
| author | 2025-11-14 00:00:00 +0000 | |
|---|---|---|
| committer | 2025-11-14 00:00:00 +0000 | |
| commit | 9ef659a016d4ffeac931291984a4c71f9527a747 (patch) | |
| tree | 957a76630fe248b638c0a9c84f7acef40a7ee9f5 /loose.go | |
| parent | Initial commit (diff) | |
Read types and sizes without inflating entire object
Diffstat (limited to 'loose.go')
| -rw-r--r-- | loose.go | 111 |
1 files changed, 89 insertions, 22 deletions
@@ -10,6 +10,8 @@ import ( "strconv" ) +const looseHeaderLimit = 4096 + func loosePath(id Hash) string { hex := id.String() return filepath.Join("objects", hex[:2], hex[2:]) @@ -53,30 +55,11 @@ func (repo *Repository) looseReadTyped(id Hash) (ObjType, []byte, error) { header := raw[:nul] body := raw[nul+1:] - space := bytes.IndexByte(header, ' ') - if space < 0 { - return ObjInvalid, nil, ErrInvalidObject - } - tyStr := string(header[:space]) - var ty ObjType - switch tyStr { - case "blob": - ty = ObjBlob - case "tree": - ty = ObjTree - case "commit": - ty = ObjCommit - case "tag": - ty = ObjTag - default: - return ObjInvalid, nil, ErrInvalidObject - } - expect := header[space+1:] - size, err := strconv.Atoi(string(expect)) + ty, declaredSize, err := parseLooseHeader(header) if err != nil { - return ObjInvalid, nil, fmt.Errorf("furgit: loose: size parse: %w", err) + return ObjInvalid, nil, err } - if size != len(body) { + if declaredSize != int64(len(body)) { return ObjInvalid, nil, ErrInvalidObject } if !verifyRawObject(raw, id) { @@ -86,3 +69,87 @@ func (repo *Repository) looseReadTyped(id Hash) (ObjType, []byte, error) { 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 + } +} |
