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

oxyno-zeta / s3-proxy / 20213714844

14 Dec 2025 08:29PM UTC coverage: 86.02% (-0.07%) from 86.089%
20213714844

push

github

oxyno-zeta
chore(deps): update gomock monorepo to v0.6.0

5427 of 6309 relevant lines covered (86.02%)

46.55 hits per line

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

91.19
/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

11
        authxmodels "github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/authx/models"
12
        "github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/config"
13
        "github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/response-handler/models"
14
        "github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/response-handler/models/converter"
15
        "github.com/oxyno-zeta/s3-proxy/pkg/s3-proxy/utils/templateutils"
16
)
17

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

26
func (h *handler) EnableHeadAnswerMode() {
×
27
        h.headAnswerMode = true
×
28
}
×
29

30
func (h *handler) GetRequest() *http.Request {
×
31
        return h.req
×
32
}
×
33

34
func (h *handler) UpdateRequestAndResponse(req *http.Request, res http.ResponseWriter) {
25✔
35
        // Update request
25✔
36
        h.req = req
25✔
37
        // Update response
25✔
38
        h.res = res
25✔
39
}
25✔
40

41
func (h *handler) PreconditionFailed() {
×
42
        h.res.WriteHeader(http.StatusPreconditionFailed)
×
43
}
×
44

45
func (h *handler) NotModified() {
×
46
        h.res.WriteHeader(http.StatusNotModified)
×
47
}
×
48

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

5✔
56
        // Variable to save target template configuration item override
5✔
57
        var tplCfgItem *config.TargetTemplateConfigItem
5✔
58

5✔
59
        // Store helpers template configs
5✔
60
        var helpersCfgItems []*config.TargetHelperConfigItem
5✔
61

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

76
        // Create data
77
        data := &models.PutData{
5✔
78
                Request: converter.ConvertAndSanitizeHTTPRequest(h.req),
5✔
79
                User:    authxmodels.GetAuthenticatedUserFromContext(h.req.Context()),
5✔
80
                PutData: input,
5✔
81
        }
5✔
82

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

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

3✔
101
        // Variable to save target template configuration item override
3✔
102
        var tplCfgItem *config.TargetTemplateConfigItem
3✔
103

3✔
104
        // Store helpers template configs
3✔
105
        var helpersCfgItems []*config.TargetHelperConfigItem
3✔
106

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

121
        // Create data
122
        data := &models.DeleteData{
3✔
123
                Request:    converter.ConvertAndSanitizeHTTPRequest(h.req),
3✔
124
                User:       authxmodels.GetAuthenticatedUserFromContext(h.req.Context()),
3✔
125
                DeleteData: input,
3✔
126
        }
3✔
127

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

139
func (h *handler) TargetList() {
7✔
140
        // Get configuration
7✔
141
        cfg := h.cfgManager.GetConfig()
7✔
142

7✔
143
        // Create targets map[string]interface{}
7✔
144
        targets := map[string]any{}
7✔
145
        for key, value := range cfg.Targets {
14✔
146
                targets[key] = value
7✔
147
        }
7✔
148

149
        // Create data structure
150
        data := &models.TargetListData{
7✔
151
                Request: converter.ConvertAndSanitizeHTTPRequest(h.req),
7✔
152
                User:    authxmodels.GetAuthenticatedUserFromContext(h.req.Context()),
7✔
153
                Targets: targets,
7✔
154
        }
7✔
155

7✔
156
        h.handleGenericAnswer(
7✔
157
                nil,
7✔
158
                data,
7✔
159
                nil,
7✔
160
                nil,
7✔
161
                cfg.Templates.TargetList,
7✔
162
                cfg.Templates.Helpers,
7✔
163
        )
7✔
164
}
165

166
func (h *handler) RedirectTo(url string) {
3✔
167
        // Redirect
3✔
168
        http.Redirect(h.res, h.req, url, http.StatusFound)
3✔
169
}
3✔
170

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

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

41✔
195
        // Check if headers templates are defined in the GET configuration
41✔
196
        if targetCfg.Actions != nil &&
41✔
197
                targetCfg.Actions.GET != nil &&
41✔
198
                targetCfg.Actions.GET.Config != nil &&
41✔
199
                targetCfg.Actions.GET.Config.StreamedFileHeaders != nil {
45✔
200
                // Target template helpers
4✔
201
                var tplHelpers []*config.TargetHelperConfigItem
4✔
202
                // Check if target templates are defined
4✔
203
                if targetCfg.Templates != nil {
6✔
204
                        tplHelpers = targetCfg.Templates.Helpers
2✔
205
                }
2✔
206

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

219
                // Create data structure
220
                data := &models.StreamFileHeaderData{
4✔
221
                        Request:    converter.ConvertAndSanitizeHTTPRequest(h.req),
4✔
222
                        User:       authxmodels.GetAuthenticatedUserFromContext(h.req.Context()),
4✔
223
                        StreamFile: input,
4✔
224
                }
4✔
225
                // Manage headers
4✔
226
                headers, err := h.manageHeaders(helpersTpl, targetCfg.Actions.GET.Config.StreamedFileHeaders, data)
4✔
227
                // Check error
4✔
228
                if err != nil {
4✔
229
                        return err
×
230
                }
×
231

232
                // Loop over them to add them to response
233
                for k, v := range headers {
8✔
234
                        // Add them
4✔
235
                        h.res.Header().Set(k, v)
4✔
236
                }
4✔
237
        }
238

239
        // Set headers from object
240
        setHeadersFromObjectOutput(h.res, input)
41✔
241

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

28✔
247
                return errors.WithStack(err)
28✔
248
        }
28✔
249

250
        // Default
251
        return nil
13✔
252
}
253

254
func (h *handler) FoldersFilesList(
255
        loadFileContent func(ctx context.Context, path string) (string, error),
256
        entries []*models.Entry,
257
) {
22✔
258
        // Get config
22✔
259
        cfg := h.cfgManager.GetConfig()
22✔
260

22✔
261
        // Get target configuration
22✔
262
        targetCfg := cfg.Targets[h.targetKey]
22✔
263

22✔
264
        // Helpers list
22✔
265
        var helpersCfgList []*config.TargetHelperConfigItem
22✔
266

22✔
267
        // Target template config item
22✔
268
        var tplConfigItem *config.TargetTemplateConfigItem
22✔
269

22✔
270
        // Get helpers template configs
22✔
271
        if targetCfg != nil && targetCfg.Templates != nil {
28✔
272
                // Save
6✔
273
                helpersCfgList = targetCfg.Templates.Helpers
6✔
274
                tplConfigItem = targetCfg.Templates.FolderList
6✔
275
        }
6✔
276

277
        // Create bucket list data for templating
278
        data := &models.FolderListingData{
22✔
279
                Request:    converter.ConvertAndSanitizeHTTPRequest(h.req),
22✔
280
                User:       authxmodels.GetAuthenticatedUserFromContext(h.req.Context()),
22✔
281
                Entries:    entries,
22✔
282
                BucketName: targetCfg.Bucket.Name,
22✔
283
                Name:       targetCfg.Name,
22✔
284
        }
22✔
285

22✔
286
        h.handleGenericAnswer(
22✔
287
                loadFileContent,
22✔
288
                data,
22✔
289
                tplConfigItem,
22✔
290
                helpersCfgList,
22✔
291
                cfg.Templates.FolderList,
22✔
292
                cfg.Templates.Helpers,
22✔
293
        )
22✔
294
}
295

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

3✔
319
                return
3✔
320
        }
3✔
321

322
        // Save in template
323
        tplContent := helpersContent
104✔
324

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

348
        // Check if error exists
349
        if err != nil {
107✔
350
                // Return an internal server error
3✔
351
                h.InternalServerError(
3✔
352
                        loadFileContent,
3✔
353
                        err,
3✔
354
                )
3✔
355

3✔
356
                return
3✔
357
        }
3✔
358

359
        // Store headers
360
        var headers map[string]string
101✔
361

101✔
362
        // Check if target config item exists
101✔
363
        if tplCfgItem != nil {
116✔
364
                // Manage headers
15✔
365
                headers, err = h.manageHeaders(
15✔
366
                        helpersContent,
15✔
367
                        tplCfgItem.Headers,
15✔
368
                        data,
15✔
369
                )
15✔
370
        } else {
101✔
371
                // Manage headers
86✔
372
                headers, err = h.manageHeaders(
86✔
373
                        helpersContent,
86✔
374
                        baseTpl.Headers,
86✔
375
                        data,
86✔
376
                )
86✔
377
        }
86✔
378

379
        // Check if error exists
380
        if err != nil {
103✔
381
                // Return an internal server error
2✔
382
                h.InternalServerError(
2✔
383
                        loadFileContent,
2✔
384
                        err,
2✔
385
                )
2✔
386

2✔
387
                return
2✔
388
        }
2✔
389

390
        // Execute main template
391
        bodyBuf, err := templateutils.ExecuteTemplate(tplContent, data)
99✔
392
        // Check error
99✔
393
        if err != nil {
100✔
394
                h.InternalServerError(loadFileContent, err)
1✔
395

1✔
396
                return
1✔
397
        }
1✔
398

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

×
405
                return
×
406
        }
×
407

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

© 2026 Coveralls, Inc