• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cinode / go / 4801130633

25 Apr 2023 06:54PM UTC coverage: 90.273% (-0.2%) from 90.484%
4801130633

push

github

Bartłomiej Święcki
Fix validation of release docker images

1717 of 1902 relevant lines covered (90.27%)

1.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.37
/pkg/datastore/webinterface.go
1
/*
2
Copyright © 2022 Bartłomiej Święcki (byo)
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package datastore
18

19
import (
20
        "encoding/json"
21
        "errors"
22
        "io"
23
        "mime/multipart"
24
        "net/http"
25

26
        "github.com/cinode/go/pkg/common"
27
)
28

29
var (
30
        errNoData = errors.New("no upload data")
31
)
32

33
// WebInterface provides simple web interface for given Datastore
34
type webInterface struct {
35
        ds DS
36
}
37

38
// WebInterface returns http handler representing web interface to given
39
// Datastore instance
40
func WebInterface(ds DS) http.Handler {
1✔
41
        return &webInterface{
1✔
42
                ds: ds,
1✔
43
        }
1✔
44
}
1✔
45

46
func (i *webInterface) ServeHTTP(w http.ResponseWriter, r *http.Request) {
1✔
47
        switch r.Method {
1✔
48
        case http.MethodGet:
1✔
49
                i.serveGet(w, r)
1✔
50
        case http.MethodPut:
1✔
51
                i.servePut(w, r)
1✔
52
        case http.MethodDelete:
1✔
53
                i.serveDelete(w, r)
1✔
54
        case http.MethodHead:
1✔
55
                i.serveHead(w, r)
1✔
56
        default:
1✔
57
                http.Error(w, "Unsupported method", http.StatusMethodNotAllowed)
1✔
58
        }
59
}
60

61
func (i *webInterface) getName(w http.ResponseWriter, r *http.Request) (common.BlobName, error) {
1✔
62
        // Don't allow url queries and require path to start with '/'
1✔
63
        if r.URL.Path[0] != '/' || r.URL.RawQuery != "" {
2✔
64
                return nil, common.ErrInvalidBlobName
1✔
65
        }
1✔
66

67
        bn, err := common.BlobNameFromString(r.URL.Path[1:])
1✔
68
        if err != nil {
1✔
69
                return nil, err
×
70
        }
×
71

72
        return bn, nil
1✔
73
}
74

75
func (i *webInterface) sendName(name common.BlobName, w http.ResponseWriter, r *http.Request) {
1✔
76
        w.Header().Set("Content-type", "application/json")
1✔
77
        json.NewEncoder(w).Encode(&webNameResponse{
1✔
78
                Name: name.String(),
1✔
79
        })
1✔
80
}
1✔
81

82
func (i *webInterface) sendError(w http.ResponseWriter, httpCode int, code string, message string) {
1✔
83
        w.Header().Set("Content-type", "application/json")
1✔
84
        w.WriteHeader(httpCode)
1✔
85
        json.NewEncoder(w).Encode(&webErrResponse{
1✔
86
                Code:    code,
1✔
87
                Message: message,
1✔
88
        })
1✔
89
}
1✔
90
func (i *webInterface) checkErr(err error, w http.ResponseWriter, r *http.Request) bool {
1✔
91
        if err == nil {
2✔
92
                return true
1✔
93
        }
1✔
94

95
        if errors.Is(err, ErrNotFound) {
2✔
96
                http.NotFound(w, r)
1✔
97
                return false
1✔
98
        }
1✔
99

100
        code := webErrToCode(err)
1✔
101
        if code != "" {
2✔
102
                i.sendError(w, http.StatusBadRequest, code, err.Error())
1✔
103
                return false
1✔
104
        }
1✔
105

106
        http.Error(w, "Internal server error", http.StatusInternalServerError)
1✔
107
        return false
1✔
108
}
109

110
func (i *webInterface) serveGet(w http.ResponseWriter, r *http.Request) {
1✔
111
        name, err := i.getName(w, r)
1✔
112
        if !i.checkErr(err, w, r) {
2✔
113
                return
1✔
114
        }
1✔
115

116
        rc, err := i.ds.Open(r.Context(), name)
1✔
117
        if !i.checkErr(err, w, r) {
2✔
118
                return
1✔
119
        }
1✔
120

121
        defer rc.Close()
1✔
122
        io.Copy(w, rc)
1✔
123
        // TODO: Log error / drop the connection ? It may be too late to send the error to the user
124
        // thus we have to assume that the blob will be validated on the other side
125
}
126

127
type partReader struct {
128
        p *multipart.Part
129
        b io.Closer
130
}
131

132
func (r *partReader) Read(b []byte) (int, error) {
1✔
133
        return r.p.Read(b)
1✔
134
}
1✔
135

136
func (r *partReader) Close() error {
1✔
137
        err1 := r.p.Close()
1✔
138
        err2 := r.b.Close()
1✔
139
        if err1 != nil {
1✔
140
                return err1
×
141
        }
×
142
        return err2
1✔
143
}
144

145
func (i *webInterface) getUploadReader(r *http.Request) (io.ReadCloser, error) {
1✔
146

1✔
147
        mpr, err := r.MultipartReader()
1✔
148
        if err == http.ErrNotMultipart {
2✔
149
                // Not multipart, read raw body data
1✔
150
                return r.Body, nil
1✔
151
        }
1✔
152
        if err != nil {
1✔
153
                return nil, err
×
154
        }
×
155

156
        for {
2✔
157
                // Get next part of the upload
1✔
158
                part, err := mpr.NextPart()
1✔
159
                if err == io.EOF {
2✔
160
                        return nil, errNoData
1✔
161
                }
1✔
162
                if err != nil {
1✔
163
                        return nil, err
×
164
                }
×
165

166
                // Search for first file input
167
                fn := part.FileName()
1✔
168
                if fn != "" {
2✔
169
                        return &partReader{
1✔
170
                                p: part,
1✔
171
                                b: r.Body,
1✔
172
                        }, nil
1✔
173
                }
1✔
174
        }
175
}
176

177
func (i *webInterface) servePut(w http.ResponseWriter, r *http.Request) {
1✔
178
        name, err := i.getName(w, r)
1✔
179
        if !i.checkErr(err, w, r) {
2✔
180
                return
1✔
181
        }
1✔
182

183
        reader, err := i.getUploadReader(r)
1✔
184
        if !i.checkErr(err, w, r) {
2✔
185
                return
1✔
186
        }
1✔
187
        defer reader.Close()
1✔
188

1✔
189
        err = i.ds.Update(r.Context(), name, reader)
1✔
190
        if !i.checkErr(err, w, r) {
2✔
191
                return
1✔
192
        }
1✔
193

194
        i.sendName(name, w, r)
1✔
195
}
196

197
func (i *webInterface) serveDelete(w http.ResponseWriter, r *http.Request) {
1✔
198

1✔
199
        name, err := i.getName(w, r)
1✔
200
        if !i.checkErr(err, w, r) {
2✔
201
                return
1✔
202
        }
1✔
203

204
        err = i.ds.Delete(r.Context(), name)
1✔
205
        if !i.checkErr(err, w, r) {
2✔
206
                return
1✔
207
        }
1✔
208

209
        i.sendName(name, w, r)
1✔
210
}
211

212
func (i *webInterface) serveHead(w http.ResponseWriter, r *http.Request) {
1✔
213
        name, err := i.getName(w, r)
1✔
214
        if !i.checkErr(err, w, r) {
2✔
215
                return
1✔
216
        }
1✔
217

218
        exists, err := i.ds.Exists(r.Context(), name)
1✔
219
        if !i.checkErr(err, w, r) {
2✔
220
                return
1✔
221
        }
1✔
222

223
        if !exists {
2✔
224
                http.NotFound(w, r)
1✔
225
        }
1✔
226
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc