From 825b281f3610524aa8da8e1f3e0caf25f0b2c2b4 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Thu, 9 Feb 2023 08:39:42 +0530 Subject: [PATCH] Moving file response functionality to shared folder (#22226) * Moving file response functionality to shared folder * Linter fixes * Added the attachment serve to shared folder * Removed the unwanted logs * import fixes * Linter fixes * License added * Moved UnsafeContentTypes and MediaContentTypes from api4 --------- Co-authored-by: Mattermost Build --- api4/cloud.go | 3 +- api4/file.go | 90 +++------------------------------------- api4/hosted_customer.go | 3 +- api4/job.go | 3 +- api4/system.go | 3 +- shared/web/files.go | 92 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 89 deletions(-) create mode 100644 shared/web/files.go diff --git a/api4/cloud.go b/api4/cloud.go index 4a9d0f8983..b55e5d36d3 100644 --- a/api4/cloud.go +++ b/api4/cloud.go @@ -14,6 +14,7 @@ import ( "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/mlog" + "github.com/mattermost/mattermost-server/v6/shared/web" ) func (api *API) InitCloud() { @@ -647,7 +648,7 @@ func getSubscriptionInvoicePDF(c *Context, w http.ResponseWriter, r *http.Reques return } - writeFileResponse( + web.WriteFileResponse( filename, "application/pdf", int64(binary.Size(pdfData)), diff --git a/api4/file.go b/api4/file.go index f81da72bf8..528d7ba442 100644 --- a/api4/file.go +++ b/api4/file.go @@ -11,15 +11,14 @@ import ( "mime" "mime/multipart" "net/http" - "net/url" "strconv" - "strings" "time" "github.com/mattermost/mattermost-server/v6/app" "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/mlog" + "github.com/mattermost/mattermost-server/v6/shared/web" "github.com/mattermost/mattermost-server/v6/utils" ) @@ -30,28 +29,6 @@ const ( ThumbnailImageType = "image/jpeg" ) -var UnsafeContentTypes = [...]string{ - "application/javascript", - "application/ecmascript", - "text/javascript", - "text/ecmascript", - "application/x-javascript", - "text/html", -} - -var MediaContentTypes = [...]string{ - "image/jpeg", - "image/png", - "image/bmp", - "image/gif", - "image/tiff", - "video/avi", - "video/mpeg", - "video/mp4", - "audio/mpeg", - "audio/wav", -} - const maxMultipartFormDataBytes = 10 * 1024 // 10Kb func (api *API) InitFile() { @@ -502,7 +479,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) { auditRec.Success() - writeFileResponse(info.Name, info.MimeType, info.Size, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) + web.WriteFileResponse(info.Name, info.MimeType, info.Size, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) } func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) { @@ -537,7 +514,7 @@ func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) { } defer fileReader.Close() - writeFileResponse(info.Name, ThumbnailImageType, 0, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) + web.WriteFileResponse(info.Name, ThumbnailImageType, 0, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) } func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) { @@ -614,7 +591,7 @@ func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) { } defer fileReader.Close() - writeFileResponse(info.Name, PreviewImageType, 0, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) + web.WriteFileResponse(info.Name, PreviewImageType, 0, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) } func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) { @@ -681,64 +658,7 @@ func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) { } defer fileReader.Close() - writeFileResponse(info.Name, info.MimeType, info.Size, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, false, w, r) -} - -func writeFileResponse(filename string, contentType string, contentSize int64, lastModification time.Time, webserverMode string, fileReader io.ReadSeeker, forceDownload bool, w http.ResponseWriter, r *http.Request) { - w.Header().Set("Cache-Control", "private, no-cache") - w.Header().Set("X-Content-Type-Options", "nosniff") - - if contentSize > 0 { - contentSizeStr := strconv.Itoa(int(contentSize)) - if webserverMode == "gzip" { - w.Header().Set("X-Uncompressed-Content-Length", contentSizeStr) - } else { - w.Header().Set("Content-Length", contentSizeStr) - } - } - - if contentType == "" { - contentType = "application/octet-stream" - } else { - for _, unsafeContentType := range UnsafeContentTypes { - if strings.HasPrefix(contentType, unsafeContentType) { - contentType = "text/plain" - break - } - } - } - - w.Header().Set("Content-Type", contentType) - - var toDownload bool - if forceDownload { - toDownload = true - } else { - isMediaType := false - - for _, mediaContentType := range MediaContentTypes { - if strings.HasPrefix(contentType, mediaContentType) { - isMediaType = true - break - } - } - - toDownload = !isMediaType - } - - filename = url.PathEscape(filename) - - if toDownload { - w.Header().Set("Content-Disposition", "attachment;filename=\""+filename+"\"; filename*=UTF-8''"+filename) - } else { - w.Header().Set("Content-Disposition", "inline;filename=\""+filename+"\"; filename*=UTF-8''"+filename) - } - - // prevent file links from being embedded in iframes - w.Header().Set("X-Frame-Options", "DENY") - w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'") - - http.ServeContent(w, r, filename, lastModification, fileReader) + web.WriteFileResponse(info.Name, info.MimeType, info.Size, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, false, w, r) } func searchFilesInTeam(c *Context, w http.ResponseWriter, r *http.Request) { diff --git a/api4/hosted_customer.go b/api4/hosted_customer.go index 6c82f5e3b9..711232a249 100644 --- a/api4/hosted_customer.go +++ b/api4/hosted_customer.go @@ -15,6 +15,7 @@ import ( "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/mlog" + "github.com/mattermost/mattermost-server/v6/shared/web" "github.com/mattermost/mattermost-server/v6/utils" ) @@ -275,7 +276,7 @@ func selfHostedInvoicePDF(c *Context, w http.ResponseWriter, r *http.Request) { return } - writeFileResponse( + web.WriteFileResponse( filename, "application/pdf", int64(binary.Size(pdfData)), diff --git a/api4/job.go b/api4/job.go index 58ee1350e2..a32259cc63 100644 --- a/api4/job.go +++ b/api4/job.go @@ -13,6 +13,7 @@ import ( "github.com/mattermost/mattermost-server/v6/audit" "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/shared/mlog" + "github.com/mattermost/mattermost-server/v6/shared/web" ) func (api *API) InitJob() { @@ -100,7 +101,7 @@ func downloadJob(c *Context, w http.ResponseWriter, r *http.Request) { // We are able to pass 0 for content size due to the fact that Golang's serveContent (https://golang.org/src/net/http/fs.go) // already sets that for us - writeFileResponse(fileName, FileMime, 0, time.Unix(0, job.LastActivityAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, true, w, r) + web.WriteFileResponse(fileName, FileMime, 0, time.Unix(0, job.LastActivityAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, true, w, r) } func createJob(c *Context, w http.ResponseWriter, r *http.Request) { diff --git a/api4/system.go b/api4/system.go index 9008bd4606..fbba869222 100644 --- a/api4/system.go +++ b/api4/system.go @@ -22,6 +22,7 @@ import ( "github.com/mattermost/mattermost-server/v6/services/cache" "github.com/mattermost/mattermost-server/v6/services/upgrader" "github.com/mattermost/mattermost-server/v6/shared/mlog" + "github.com/mattermost/mattermost-server/v6/shared/web" ) const ( @@ -122,7 +123,7 @@ func generateSupportPacket(c *Context, w http.ResponseWriter, r *http.Request) { // Send the zip file back to client // We are able to pass 0 for content size due to the fact that Golang's serveContent (https://golang.org/src/net/http/fs.go) // already sets that for us - writeFileResponse(outputZipFilename, FileMime, 0, now, *c.App.Config().ServiceSettings.WebserverMode, fileBytesReader, true, w, r) + web.WriteFileResponse(outputZipFilename, FileMime, 0, now, *c.App.Config().ServiceSettings.WebserverMode, fileBytesReader, true, w, r) } func getSystemPing(c *Context, w http.ResponseWriter, r *http.Request) { diff --git a/shared/web/files.go b/shared/web/files.go new file mode 100644 index 0000000000..46a2e2c587 --- /dev/null +++ b/shared/web/files.go @@ -0,0 +1,92 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package web + +import ( + "io" + "net/http" + "net/url" + "strconv" + "strings" + "time" +) + +var UnsafeContentTypes = [...]string{ + "application/javascript", + "application/ecmascript", + "text/javascript", + "text/ecmascript", + "application/x-javascript", + "text/html", +} + +var MediaContentTypes = [...]string{ + "image/jpeg", + "image/png", + "image/bmp", + "image/gif", + "image/tiff", + "video/avi", + "video/mpeg", + "video/mp4", + "audio/mpeg", + "audio/wav", +} + +func WriteFileResponse(filename string, contentType string, contentSize int64, lastModification time.Time, webserverMode string, fileReader io.ReadSeeker, forceDownload bool, w http.ResponseWriter, r *http.Request) { + w.Header().Set("Cache-Control", "private, no-cache") + w.Header().Set("X-Content-Type-Options", "nosniff") + + if contentSize > 0 { + contentSizeStr := strconv.Itoa(int(contentSize)) + if webserverMode == "gzip" { + w.Header().Set("X-Uncompressed-Content-Length", contentSizeStr) + } else { + w.Header().Set("Content-Length", contentSizeStr) + } + } + + if contentType == "" { + contentType = "application/octet-stream" + } else { + for _, unsafeContentType := range UnsafeContentTypes { + if strings.HasPrefix(contentType, unsafeContentType) { + contentType = "text/plain" + break + } + } + } + + w.Header().Set("Content-Type", contentType) + + var toDownload bool + if forceDownload { + toDownload = true + } else { + isMediaType := false + + for _, mediaContentType := range MediaContentTypes { + if strings.HasPrefix(contentType, mediaContentType) { + isMediaType = true + break + } + } + + toDownload = !isMediaType + } + + filename = url.PathEscape(filename) + + if toDownload { + w.Header().Set("Content-Disposition", "attachment;filename=\""+filename+"\"; filename*=UTF-8''"+filename) + } else { + w.Header().Set("Content-Disposition", "inline;filename=\""+filename+"\"; filename*=UTF-8''"+filename) + } + + // prevent file links from being embedded in iframes + w.Header().Set("X-Frame-Options", "DENY") + w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'") + + http.ServeContent(w, r, filename, lastModification, fileReader) +}