-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathblob.go
More file actions
119 lines (109 loc) · 3.58 KB
/
Copy pathblob.go
File metadata and controls
119 lines (109 loc) · 3.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package clone
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"math"
"os/exec"
"strings"
"github.com/git-pkgs/magic"
)
// BlobResult contains a bounded blob read and its content classification.
type BlobResult struct {
Content []byte
Detection magic.Result
Truncated bool
}
// InspectBlob reads path from commit in dir through the git binary and
// classifies the returned bytes. It uses prefix detection when maxBytes
// truncates the blob. commit and path are validated with ValidCommit and
// SanitizePath before invoking Git.
func InspectBlob(ctx context.Context, dir, commit, blobPath string, maxBytes int64) (BlobResult, error) {
content, truncated, err := readBlob(ctx, dir, commit, blobPath, maxBytes)
if err != nil {
return BlobResult{}, err
}
var detection magic.Result
if truncated {
detection = magic.DetectPrefix(content)
} else {
detection = magic.Detect(content)
}
return BlobResult{
Content: content,
Detection: detection,
Truncated: truncated,
}, nil
}
// Blob reads path from commit in dir through the git binary. It caps content
// at maxBytes and reports whether the blob is binary or was truncated. commit
// and path are validated with ValidCommit and SanitizePath before invoking Git.
func Blob(ctx context.Context, dir, commit, blobPath string, maxBytes int64) (content []byte, binary, truncated bool, err error) {
content, truncated, err = readBlob(ctx, dir, commit, blobPath, maxBytes)
if err != nil {
return nil, false, false, err
}
if bytes.IndexByte(content, 0) != -1 {
return nil, true, truncated, nil
}
return content, false, truncated, nil
}
func readBlob(ctx context.Context, dir, commit, blobPath string, maxBytes int64) (content []byte, truncated bool, err error) {
if maxBytes < 0 {
return nil, false, fmt.Errorf("maxBytes must be non-negative")
}
if maxBytes == math.MaxInt64 {
return nil, false, fmt.Errorf("maxBytes is too large")
}
if !ValidCommit(commit) {
return nil, false, fmt.Errorf("invalid commit %q", commit)
}
clean, ok := SanitizePath(blobPath)
if !ok {
return nil, false, fmt.Errorf("invalid path %q", blobPath)
}
if err := ctx.Err(); err != nil {
return nil, false, err
}
return readBlobWithGit(ctx, dir, commit, clean, maxBytes)
}
func readBlobWithGit(ctx context.Context, dir, commit, clean string, maxBytes int64) (content []byte, truncated bool, err error) {
// --end-of-options stops a commit or path that somehow slipped past the
// validators from being parsed as a git-show flag. commit is validated to
// hex above, so this is defence in depth rather than the primary guard.
cmd := exec.CommandContext(ctx, "git", "-C", dir, "show", "--end-of-options", commit+":"+clean)
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, false, err
}
var errBuf bytes.Buffer
cmd.Stderr = &errBuf
if err := cmd.Start(); err != nil {
return nil, false, err
}
raw, readErr := io.ReadAll(io.LimitReader(stdout, maxBytes+1))
truncated = int64(len(raw)) > maxBytes
if truncated {
raw = raw[:maxBytes]
// Close the pipe rather than draining it: a hostile repo with a
// multi-GB blob at this path would otherwise keep the caller in
// io.Copy for as long as git can produce bytes. git receives EPIPE
// or SIGPIPE and exits non-zero, which is treated as success below
// since maxBytes was already read.
_ = stdout.Close()
}
waitErr := cmd.Wait()
if waitErr != nil && !truncated {
message := strings.TrimSpace(errBuf.String())
if message == "" {
message = waitErr.Error()
}
return nil, false, errors.New(message)
}
if readErr != nil {
return nil, false, readErr
}
return raw, truncated, nil
}