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

oxyno-zeta / s3-proxy / 10979626808

22 Sep 2024 08:24AM UTC coverage: 84.23% (-2.0%) from 86.185%
10979626808

push

github

oxyno-zeta
feat(wip): Add HEAD request support

Close #474

138 of 295 new or added lines in 6 files covered. (46.78%)

1 existing line in 1 file now uncovered.

5213 of 6189 relevant lines covered (84.23%)

38.47 hits per line

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

90.88
/pkg/s3-proxy/response-handler/handler.go
1
package responsehandler
2

3
import (
4
        "context"
5
        "io"
6
        "net/http"
7
        "strings"
8

9
        "emperror.dev/errors"
10
        "github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/authx/models"
11
        "github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/config"
12
        "github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/utils/templateutils"
13
)
14

15
type handler struct {
16
        req            *http.Request
17
        res            http.ResponseWriter
18
        cfgManager     config.Manager
19
        targetKey      string
20
        headAnswerMode bool
21
}
22

NEW
23
func (h *handler) EnableHeadAnswerMode() {
×
NEW
24
        h.headAnswerMode = true
×
UNCOV
25
}
×
26

27
func (h *handler) GetRequest() *http.Request {
×
28
        return h.req
×
29
}
×
30

31
func (h *handler) UpdateRequestAndResponse(req *http.Request, res http.ResponseWriter) {
22✔
32
        // Update request
22✔
33
        h.req = req
22✔
34
        // Update response
22✔
35
        h.res = res
22✔
36
}
22✔
37

38
func (h *handler) PreconditionFailed() {
×
39
        h.res.WriteHeader(http.StatusPreconditionFailed)
×
40
}
×
41

42
func (h *handler) NotModified() {
×
43
        h.res.WriteHeader(http.StatusNotModified)
×
44
}
×
45

46
func (h *handler) Put(
47
        loadFileContent func(ctx context.Context, path string) (string, error),
48
        input *PutInput,
49
) {
5✔
50
        // Get configuration
5✔
51
        cfg := h.cfgManager.GetConfig()
5✔
52

5✔
53
        // Variable to save target template configuration item override
5✔
54
        var tplCfgItem *config.TargetTemplateConfigItem
5✔
55

5✔
56
        // Store helpers template configs
5✔
57
        var helpersCfgItems []*config.TargetHelperConfigItem
5✔
58

5✔
59
        // Check if a target has been involve in this request
5✔
60
        if h.targetKey != "" {
10✔
61
                // Get target from key
5✔
62
                targetCfg := cfg.Targets[h.targetKey]
5✔
63
                // Check if have a template override
5✔
64
                if targetCfg != nil &&
5✔
65
                        targetCfg.Templates != nil &&
5✔
66
                        targetCfg.Templates.Put != nil {
6✔
67
                        // Save override
1✔
68
                        tplCfgItem = targetCfg.Templates.Put
1✔
69
                        helpersCfgItems = targetCfg.Templates.Helpers
1✔
70
                }
1✔
71
        }
72

73
        // Create data
74
        data := &putData{
5✔
75
                Request: h.req,
5✔
76
                User:    models.GetAuthenticatedUserFromContext(h.req.Context()),
5✔
77
                PutData: input,
5✔
78
        }
5✔
79

5✔
80
        // Call generic template handler
5✔
81
        h.handleGenericAnswer(
5✔
82
                loadFileContent,
5✔
83
                data,
5✔
84
                tplCfgItem,
5✔
85
                helpersCfgItems,
5✔
86
                cfg.Templates.Put,
5✔
87
                cfg.Templates.Helpers,
5✔
88
        )
5✔
89
}
90

91
func (h *handler) Delete(
92
        loadFileContent func(ctx context.Context, path string) (string, error),
93
        input *DeleteInput,
94
) {
3✔
95
        // Get configuration
3✔
96
        cfg := h.cfgManager.GetConfig()
3✔
97

3✔
98
        // Variable to save target template configuration item override
3✔
99
        var tplCfgItem *config.TargetTemplateConfigItem
3✔
100

3✔
101
        // Store helpers template configs
3✔
102
        var helpersCfgItems []*config.TargetHelperConfigItem
3✔
103

3✔
104
        // Check if a target has been involve in this request
3✔
105
        if h.targetKey != "" {
6✔
106
                // Get target from key
3✔
107
                targetCfg := cfg.Targets[h.targetKey]
3✔
108
                // Check if have a template override
3✔
109
                if targetCfg != nil &&
3✔
110
                        targetCfg.Templates != nil &&
3✔
111
                        targetCfg.Templates.Delete != nil {
4✔
112
                        // Save override
1✔
113
                        tplCfgItem = targetCfg.Templates.Delete
1✔
114
                        helpersCfgItems = targetCfg.Templates.Helpers
1✔
115
                }
1✔
116
        }
117

118
        // Create data
119
        data := &deleteData{
3✔
120
                Request:    h.req,
3✔
121
                User:       models.GetAuthenticatedUserFromContext(h.req.Context()),
3✔
122
                DeleteData: input,
3✔
123
        }
3✔
124

3✔
125
        // Call generic template handler
3✔
126
        h.handleGenericAnswer(
3✔
127
                loadFileContent,
3✔
128
                data,
3✔
129
                tplCfgItem,
3✔
130
                helpersCfgItems,
3✔
131
                cfg.Templates.Delete,
3✔
132
                cfg.Templates.Helpers,
3✔
133
        )
3✔
134
}
135

136
func (h *handler) TargetList() {
7✔
137
        // Get configuration
7✔
138
        cfg := h.cfgManager.GetConfig()
7✔
139

7✔
140
        // Create targets map[string]interface{}
7✔
141
        targets := map[string]interface{}{}
7✔
142
        for key, value := range cfg.Targets {
14✔
143
                targets[key] = value
7✔
144
        }
7✔
145

146
        // Create data structure
147
        data := targetListData{
7✔
148
                Request: h.req,
7✔
149
                User:    models.GetAuthenticatedUserFromContext(h.req.Context()),
7✔
150
                Targets: targets,
7✔
151
        }
7✔
152

7✔
153
        h.handleGenericAnswer(
7✔
154
                nil,
7✔
155
                data,
7✔
156
                nil,
7✔
157
                nil,
7✔
158
                cfg.Templates.TargetList,
7✔
159
                cfg.Templates.Helpers,
7✔
160
        )
7✔
161
}
162

163
func (h *handler) RedirectTo(url string) {
3✔
164
        // Redirect
3✔
165
        http.Redirect(h.res, h.req, url, http.StatusFound)
3✔
166
}
3✔
167

168
func (h *handler) RedirectWithTrailingSlash() {
1✔
169
        //  Get path
1✔
170
        p := h.req.URL.RequestURI()
1✔
171
        // Check if path doesn't start with /
1✔
172
        if !strings.HasPrefix(p, "/") {
1✔
173
                p = "/" + p
×
174
        }
×
175
        // Check if path doesn't end with /
176
        if !strings.HasSuffix(p, "/") {
2✔
177
                p += "/"
1✔
178
        }
1✔
179
        // Redirect
180
        h.RedirectTo(p)
1✔
181
}
182

183
func (h *handler) StreamFile(
184
        loadFileContent func(ctx context.Context, path string) (string, error),
185
        input *StreamInput,
186
) error {
28✔
187
        // Get configuration
28✔
188
        cfg := h.cfgManager.GetConfig()
28✔
189
        // Get target configuration (exists in this case)
28✔
190
        targetCfg := cfg.Targets[h.targetKey]
28✔
191

28✔
192
        // Check if headers templates are defined in the GET configuration
28✔
193
        if targetCfg.Actions != nil &&
28✔
194
                targetCfg.Actions.GET != nil &&
28✔
195
                targetCfg.Actions.GET.Config != nil &&
28✔
196
                targetCfg.Actions.GET.Config.StreamedFileHeaders != nil {
30✔
197
                // Target template helpers
2✔
198
                var tplHelpers []*config.TargetHelperConfigItem
2✔
199
                // Check if target templates are defined
2✔
200
                if targetCfg.Templates != nil {
3✔
201
                        tplHelpers = targetCfg.Templates.Helpers
1✔
202
                }
1✔
203

204
                // Get template content
205
                helpersTpl, err := templateutils.LoadAllHelpersContent(
2✔
206
                        h.req.Context(),
2✔
207
                        loadFileContent,
2✔
208
                        tplHelpers,
2✔
209
                        cfg.Templates.Helpers,
2✔
210
                )
2✔
211
                // Check error
2✔
212
                if err != nil {
2✔
213
                        return err
×
214
                }
×
215

216
                // Create data structure
217
                data := &streamFileHeaderData{
2✔
218
                        Request:    h.req,
2✔
219
                        User:       models.GetAuthenticatedUserFromContext(h.req.Context()),
2✔
220
                        StreamFile: input,
2✔
221
                }
2✔
222
                // Manage headers
2✔
223
                headers, err := h.manageHeaders(helpersTpl, targetCfg.Actions.GET.Config.StreamedFileHeaders, data)
2✔
224
                // Check error
2✔
225
                if err != nil {
2✔
226
                        return err
×
227
                }
×
228

229
                // Loop over them to add them to response
230
                for k, v := range headers {
4✔
231
                        // Add them
2✔
232
                        h.res.Header().Set(k, v)
2✔
233
                }
2✔
234
        }
235

236
        // Set headers from object
237
        setHeadersFromObjectOutput(h.res, input)
28✔
238

28✔
239
        // Check if we aren't in head answer mode
28✔
240
        if !h.headAnswerMode {
56✔
241
                // Copy data stream to output stream
28✔
242
                _, err := io.Copy(h.res, input.Body)
28✔
243

28✔
244
                return errors.WithStack(err)
28✔
245
        }
28✔
246

247
        // Default
NEW
248
        return nil
×
249
}
250

251
func (h *handler) FoldersFilesList(
252
        loadFileContent func(ctx context.Context, path string) (string, error),
253
        entries []*Entry,
254
) {
14✔
255
        // Get config
14✔
256
        cfg := h.cfgManager.GetConfig()
14✔
257

14✔
258
        // Get target configuration
14✔
259
        targetCfg := cfg.Targets[h.targetKey]
14✔
260

14✔
261
        // Helpers list
14✔
262
        var helpersCfgList []*config.TargetHelperConfigItem
14✔
263

14✔
264
        // Target template config item
14✔
265
        var tplConfigItem *config.TargetTemplateConfigItem
14✔
266

14✔
267
        // Get helpers template configs
14✔
268
        if targetCfg != nil && targetCfg.Templates != nil {
17✔
269
                // Save
3✔
270
                helpersCfgList = targetCfg.Templates.Helpers
3✔
271
                tplConfigItem = targetCfg.Templates.FolderList
3✔
272
        }
3✔
273

274
        // Create bucket list data for templating
275
        data := &folderListingData{
14✔
276
                Request:    h.req,
14✔
277
                User:       models.GetAuthenticatedUserFromContext(h.req.Context()),
14✔
278
                Entries:    entries,
14✔
279
                BucketName: targetCfg.Bucket.Name,
14✔
280
                Name:       targetCfg.Name,
14✔
281
        }
14✔
282

14✔
283
        h.handleGenericAnswer(
14✔
284
                loadFileContent,
14✔
285
                data,
14✔
286
                tplConfigItem,
14✔
287
                helpersCfgList,
14✔
288
                cfg.Templates.FolderList,
14✔
289
                cfg.Templates.Helpers,
14✔
290
        )
14✔
291
}
292

293
func (h *handler) handleGenericAnswer(
294
        loadFileContent func(ctx context.Context, path string) (string, error),
295
        data interface{},
296
        tplCfgItem *config.TargetTemplateConfigItem,
297
        helpersTplCfgItems []*config.TargetHelperConfigItem,
298
        baseTpl *config.TemplateConfigItem,
299
        helpersTplFilePathList []string,
300
) {
80✔
301
        // Get helpers template content
80✔
302
        helpersContent, err := templateutils.LoadAllHelpersContent(
80✔
303
                h.req.Context(),
80✔
304
                loadFileContent,
80✔
305
                helpersTplCfgItems,
80✔
306
                helpersTplFilePathList,
80✔
307
        )
80✔
308
        // Check if error exists
80✔
309
        if err != nil {
83✔
310
                // Return an internal server error
3✔
311
                h.InternalServerError(
3✔
312
                        loadFileContent,
3✔
313
                        err,
3✔
314
                )
3✔
315

3✔
316
                return
3✔
317
        }
3✔
318

319
        // Save in template
320
        tplContent := helpersContent
77✔
321

77✔
322
        // Check if a target template configuration exists
77✔
323
        // Note: Done like this and not with list to avoid creating list of 1 element
77✔
324
        // and to avoid loops etc to save potential memory and cpu
77✔
325
        if tplCfgItem != nil {
91✔
326
                // Load template content
14✔
327
                tpl, err2 := templateutils.LoadTemplateContent(
14✔
328
                        h.req.Context(),
14✔
329
                        loadFileContent,
14✔
330
                        tplCfgItem,
14✔
331
                )
14✔
332
                // Concat
14✔
333
                tplContent = tplContent + "\n" + tpl
14✔
334
                // Save error
14✔
335
                err = err2
14✔
336
        } else {
77✔
337
                // Get template from general configuration
63✔
338
                tpl, err2 := templateutils.LoadLocalFileContent(baseTpl.Path)
63✔
339
                // Concat
63✔
340
                tplContent = tplContent + "\n" + tpl
63✔
341
                // Save error
63✔
342
                err = err2
63✔
343
        }
63✔
344

345
        // Check if error exists
346
        if err != nil {
80✔
347
                // Return an internal server error
3✔
348
                h.InternalServerError(
3✔
349
                        loadFileContent,
3✔
350
                        err,
3✔
351
                )
3✔
352

3✔
353
                return
3✔
354
        }
3✔
355

356
        // Store headers
357
        var headers map[string]string
74✔
358

74✔
359
        // Check if target config item exists
74✔
360
        if tplCfgItem != nil {
86✔
361
                // Manage headers
12✔
362
                headers, err = h.manageHeaders(
12✔
363
                        helpersContent,
12✔
364
                        tplCfgItem.Headers,
12✔
365
                        data,
12✔
366
                )
12✔
367
        } else {
74✔
368
                // Manage headers
62✔
369
                headers, err = h.manageHeaders(
62✔
370
                        helpersContent,
62✔
371
                        baseTpl.Headers,
62✔
372
                        data,
62✔
373
                )
62✔
374
        }
62✔
375

376
        // Check if error exists
377
        if err != nil {
76✔
378
                // Return an internal server error
2✔
379
                h.InternalServerError(
2✔
380
                        loadFileContent,
2✔
381
                        err,
2✔
382
                )
2✔
383

2✔
384
                return
2✔
385
        }
2✔
386

387
        // Execute main template
388
        bodyBuf, err := templateutils.ExecuteTemplate(tplContent, data)
72✔
389
        // Check error
72✔
390
        if err != nil {
73✔
391
                h.InternalServerError(loadFileContent, err)
1✔
392

1✔
393
                return
1✔
394
        }
1✔
395

396
        // Manage status code
397
        statusCode, err := h.manageStatus(helpersContent, tplCfgItem, baseTpl.Status, data)
71✔
398
        // Check error
71✔
399
        if err != nil {
71✔
400
                h.InternalServerError(loadFileContent, err)
×
401

×
402
                return
×
403
        }
×
404

405
        // Send
406
        err = h.send(bodyBuf, headers, statusCode)
71✔
407
        // Check error
71✔
408
        if err != nil {
71✔
409
                // Return an internal server error
×
410
                h.InternalServerError(
×
411
                        loadFileContent,
×
412
                        err,
×
413
                )
×
414
        }
×
415
}
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