-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
backend_gcs.go
128 lines (114 loc) · 3.37 KB
/
backend_gcs.go
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
120
121
122
123
124
125
126
127
128
// Copyright 2022 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
package asset
import (
"fmt"
"io"
"net/url"
"regexp"
"strings"
"github.com/google/syzkaller/pkg/debugtracer"
"github.com/google/syzkaller/pkg/gcs"
)
type cloudStorageBackend struct {
client *gcs.Client
bucket string
tracer debugtracer.DebugTracer
}
func makeCloudStorageBackend(bucket string, tracer debugtracer.DebugTracer) (*cloudStorageBackend, error) {
tracer.Log("created gcs backend for bucket '%s'", bucket)
client, err := gcs.NewClient()
if err != nil {
return nil, fmt.Errorf("the call to NewClient failed: %w", err)
}
return &cloudStorageBackend{
client: client,
bucket: bucket,
tracer: tracer,
}, nil
}
// Actual write errors might be hidden, so we wrap the writer here
// to ensure that they get logged.
type writeErrorLogger struct {
writeCloser io.WriteCloser
tracer debugtracer.DebugTracer
}
func (wel *writeErrorLogger) Write(p []byte) (n int, err error) {
n, err = wel.writeCloser.Write(p)
if err != nil {
wel.tracer.Log("cloud storage write error: %s", err)
}
return
}
func (wel *writeErrorLogger) Close() error {
err := wel.writeCloser.Close()
if err != nil {
wel.tracer.Log("cloud storage writer close error: %s", err)
}
return err
}
func (csb *cloudStorageBackend) upload(req *uploadRequest) (*uploadResponse, error) {
path := fmt.Sprintf("%s/%s", csb.bucket, req.savePath)
// Best-effort check only. In the worst case we'll just overwite the file.
// The alternative would be to add an If-precondition, but it'd require
// complicated error-during-write handling.
exists, err := csb.client.FileExists(path)
if err != nil {
return nil, err
}
if exists {
return nil, &FileExistsError{req.savePath}
}
w, err := csb.client.FileWriterExt(path, req.contentType, req.contentEncoding)
csb.tracer.Log("gcs upload: obtained a writer for %s, error %s", path, err)
if err != nil {
return nil, err
}
return &uploadResponse{
writer: &writeErrorLogger{
writeCloser: w,
tracer: csb.tracer,
},
path: req.savePath,
}, nil
}
func (csb *cloudStorageBackend) downloadURL(path string, publicURL bool) (string, error) {
return csb.client.GetDownloadURL(fmt.Sprintf("%s/%s", csb.bucket, path), publicURL), nil
}
var allowedDomainsRe = regexp.MustCompile(`^storage\.googleapis\.com|storage\.cloud\.google\.com$`)
func (csb *cloudStorageBackend) getPath(downloadURL string) (string, error) {
u, err := url.Parse(downloadURL)
if err != nil {
return "", fmt.Errorf("failed to parse the URL: %w", err)
}
if !allowedDomainsRe.MatchString(u.Host) {
return "", fmt.Errorf("not allowed host: %s", u.Host)
}
prefix := "/" + csb.bucket + "/"
if !strings.HasPrefix(u.Path, prefix) {
return "", ErrUnknownBucket
}
return u.Path[len(prefix):], nil
}
func (csb *cloudStorageBackend) list() ([]storedObject, error) {
list, err := csb.client.ListObjects(csb.bucket)
if err != nil {
return nil, err
}
ret := []storedObject{}
for _, obj := range list {
ret = append(ret, storedObject{
path: obj.Path,
createdAt: obj.CreatedAt,
})
}
return ret, nil
}
func (csb *cloudStorageBackend) remove(path string) error {
path = fmt.Sprintf("%s/%s", csb.bucket, path)
err := csb.client.DeleteFile(path)
if err == gcs.ErrFileNotFound {
return ErrAssetDoesNotExist
}
return err
}