added ?download file serve query param support to force file download

This commit is contained in:
Gani Georgiev 2023-07-20 15:04:26 +03:00
parent 7e0a4e61b4
commit 50d7df45eb
3 changed files with 42 additions and 3 deletions

View File

@ -87,6 +87,8 @@
- **!** renamed `models.RequestData` to `models.RequestInfo` and soft-deprecated `apis.RequestData(c)` to `apis.RequestInfo(c)` to avoid the stuttering with the `Data` field. - **!** renamed `models.RequestData` to `models.RequestInfo` and soft-deprecated `apis.RequestData(c)` to `apis.RequestInfo(c)` to avoid the stuttering with the `Data` field.
_The old `apis.RequestData()` method still works to minimize the breaking changes but it is recommended to replace it with `apis.RequestInfo(c)`._ _The old `apis.RequestData()` method still works to minimize the breaking changes but it is recommended to replace it with `apis.RequestInfo(c)`._
- Added `?download` file query parameter option to instruct the browser to always download a file and not show a preview.
## v0.16.10 ## v0.16.10

View File

@ -321,7 +321,14 @@ var manualExtensionContentTypes = map[string]string{
".css": "text/css", // (see https://github.com/gabriel-vasile/mimetype/pull/113) ".css": "text/css", // (see https://github.com/gabriel-vasile/mimetype/pull/113)
} }
// forceAttachmentParam is the name of the request query parameter to
// force "Content-Disposition: attachment" header.
const forceAttachmentParam = "download"
// Serve serves the file at fileKey location to an HTTP response. // Serve serves the file at fileKey location to an HTTP response.
//
// If the `download` query parameter is used the file will be always served for
// download no matter of its type (aka. with "Content-Disposition: attachment").
func (s *System) Serve(res http.ResponseWriter, req *http.Request, fileKey string, name string) error { func (s *System) Serve(res http.ResponseWriter, req *http.Request, fileKey string, name string) error {
br, readErr := s.bucket.NewReader(s.ctx, fileKey, nil) br, readErr := s.bucket.NewReader(s.ctx, fileKey, nil)
if readErr != nil { if readErr != nil {
@ -329,9 +336,11 @@ func (s *System) Serve(res http.ResponseWriter, req *http.Request, fileKey strin
} }
defer br.Close() defer br.Close()
forceAttachment := req.URL.Query().Has(forceAttachmentParam)
disposition := "attachment" disposition := "attachment"
realContentType := br.ContentType() realContentType := br.ContentType()
if list.ExistInSlice(realContentType, inlineServeContentTypes) { if !forceAttachment && list.ExistInSlice(realContentType, inlineServeContentTypes) {
disposition = "inline" disposition = "inline"
} }

View File

@ -257,7 +257,8 @@ func TestFileSystemServe(t *testing.T) {
scenarios := []struct { scenarios := []struct {
path string path string
name string name string
customHeaders map[string]string query map[string]string
headers map[string]string
expectError bool expectError bool
expectHeaders map[string]string expectHeaders map[string]string
}{ }{
@ -266,6 +267,7 @@ func TestFileSystemServe(t *testing.T) {
"missing.txt", "missing.txt",
"test_name.txt", "test_name.txt",
nil, nil,
nil,
true, true,
nil, nil,
}, },
@ -274,6 +276,7 @@ func TestFileSystemServe(t *testing.T) {
"test/sub1.txt", "test/sub1.txt",
"test_name.txt", "test_name.txt",
nil, nil,
nil,
false, false,
map[string]string{ map[string]string{
"Content-Disposition": "attachment; filename=test_name.txt", "Content-Disposition": "attachment; filename=test_name.txt",
@ -288,6 +291,7 @@ func TestFileSystemServe(t *testing.T) {
"image.png", "image.png",
"test_name.png", "test_name.png",
nil, nil,
nil,
false, false,
map[string]string{ map[string]string{
"Content-Disposition": "inline; filename=test_name.png", "Content-Disposition": "inline; filename=test_name.png",
@ -297,11 +301,27 @@ func TestFileSystemServe(t *testing.T) {
"Cache-Control": cacheControl, "Cache-Control": cacheControl,
}, },
}, },
{
// png with forced attachment
"image.png",
"test_name_download.png",
map[string]string{"download": "12"},
nil,
false,
map[string]string{
"Content-Disposition": "attachment; filename=test_name_download.png",
"Content-Type": "image/png",
"Content-Length": "73",
"Content-Security-Policy": csp,
"Cache-Control": cacheControl,
},
},
{ {
// svg exception // svg exception
"image.svg", "image.svg",
"test_name.svg", "test_name.svg",
nil, nil,
nil,
false, false,
map[string]string{ map[string]string{
"Content-Disposition": "attachment; filename=test_name.svg", "Content-Disposition": "attachment; filename=test_name.svg",
@ -316,6 +336,7 @@ func TestFileSystemServe(t *testing.T) {
"style.css", "style.css",
"test_name.css", "test_name.css",
nil, nil,
nil,
false, false,
map[string]string{ map[string]string{
"Content-Disposition": "attachment; filename=test_name.css", "Content-Disposition": "attachment; filename=test_name.css",
@ -329,6 +350,7 @@ func TestFileSystemServe(t *testing.T) {
// custom header // custom header
"test/sub2.txt", "test/sub2.txt",
"test_name.txt", "test_name.txt",
nil,
map[string]string{ map[string]string{
"Content-Disposition": "1", "Content-Disposition": "1",
"Content-Type": "2", "Content-Type": "2",
@ -353,7 +375,13 @@ func TestFileSystemServe(t *testing.T) {
res := httptest.NewRecorder() res := httptest.NewRecorder()
req := httptest.NewRequest("GET", "/", nil) req := httptest.NewRequest("GET", "/", nil)
for k, v := range s.customHeaders { query := req.URL.Query()
for k, v := range s.query {
query.Set(k, v)
}
req.URL.RawQuery = query.Encode()
for k, v := range s.headers {
res.Header().Set(k, v) res.Header().Set(k, v)
} }