2017-04-12 08:27:57 -04:00
|
|
|
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
|
2017-02-17 10:31:21 -05:00
|
|
|
// See License.txt for license information.
|
|
|
|
|
|
|
|
|
|
package api4
|
|
|
|
|
|
|
|
|
|
import (
|
2018-02-20 10:41:00 -05:00
|
|
|
"io"
|
2018-07-18 10:07:00 +02:00
|
|
|
"io/ioutil"
|
2017-02-17 10:31:21 -05:00
|
|
|
"net/http"
|
|
|
|
|
"net/url"
|
|
|
|
|
"strconv"
|
2017-07-14 14:42:08 -04:00
|
|
|
"strings"
|
2018-07-28 14:27:55 +08:00
|
|
|
"time"
|
2017-02-17 10:31:21 -05:00
|
|
|
|
2017-09-06 23:05:10 -07:00
|
|
|
"github.com/mattermost/mattermost-server/app"
|
|
|
|
|
"github.com/mattermost/mattermost-server/model"
|
|
|
|
|
"github.com/mattermost/mattermost-server/utils"
|
2017-02-17 10:31:21 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
FILE_TEAM_ID = "noteam"
|
2017-08-04 09:05:16 -04:00
|
|
|
|
|
|
|
|
PREVIEW_IMAGE_TYPE = "image/jpeg"
|
|
|
|
|
THUMBNAIL_IMAGE_TYPE = "image/jpeg"
|
2017-02-17 10:31:21 -05:00
|
|
|
)
|
|
|
|
|
|
2017-07-14 14:42:08 -04:00
|
|
|
var UNSAFE_CONTENT_TYPES = [...]string{
|
|
|
|
|
"application/javascript",
|
|
|
|
|
"application/ecmascript",
|
|
|
|
|
"text/javascript",
|
|
|
|
|
"text/ecmascript",
|
|
|
|
|
"application/x-javascript",
|
|
|
|
|
"text/html",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var MEDIA_CONTENT_TYPES = [...]string{
|
|
|
|
|
"image/jpeg",
|
|
|
|
|
"image/png",
|
|
|
|
|
"image/bmp",
|
|
|
|
|
"image/gif",
|
|
|
|
|
"video/avi",
|
|
|
|
|
"video/mpeg",
|
|
|
|
|
"video/mp4",
|
|
|
|
|
"audio/mpeg",
|
|
|
|
|
"audio/wav",
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-22 12:54:27 -05:00
|
|
|
func (api *API) InitFile() {
|
|
|
|
|
api.BaseRoutes.Files.Handle("", api.ApiSessionRequired(uploadFile)).Methods("POST")
|
|
|
|
|
api.BaseRoutes.File.Handle("", api.ApiSessionRequiredTrustRequester(getFile)).Methods("GET")
|
|
|
|
|
api.BaseRoutes.File.Handle("/thumbnail", api.ApiSessionRequiredTrustRequester(getFileThumbnail)).Methods("GET")
|
|
|
|
|
api.BaseRoutes.File.Handle("/link", api.ApiSessionRequired(getFileLink)).Methods("GET")
|
|
|
|
|
api.BaseRoutes.File.Handle("/preview", api.ApiSessionRequiredTrustRequester(getFilePreview)).Methods("GET")
|
|
|
|
|
api.BaseRoutes.File.Handle("/info", api.ApiSessionRequired(getFileInfo)).Methods("GET")
|
2017-02-17 10:31:21 -05:00
|
|
|
|
2017-09-22 12:54:27 -05:00
|
|
|
api.BaseRoutes.PublicFile.Handle("", api.ApiHandler(getPublicFile)).Methods("GET")
|
2017-03-14 06:13:48 +09:00
|
|
|
|
2017-02-17 10:31:21 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
|
2018-07-18 10:07:00 +02:00
|
|
|
defer io.Copy(ioutil.Discard, r.Body)
|
|
|
|
|
|
2017-10-18 15:36:43 -07:00
|
|
|
if !*c.App.Config().FileSettings.EnableFileAttachments {
|
2017-05-04 15:45:19 -04:00
|
|
|
c.Err = model.NewAppError("uploadFile", "api.file.attachments.disabled.app_error", nil, "", http.StatusNotImplemented)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 15:36:43 -07:00
|
|
|
if r.ContentLength > *c.App.Config().FileSettings.MaxFileSize {
|
2017-05-04 15:45:19 -04:00
|
|
|
c.Err = model.NewAppError("uploadFile", "api.file.upload_file.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge)
|
2017-02-17 10:31:21 -05:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-28 14:27:55 +08:00
|
|
|
now := time.Now()
|
2018-02-20 10:41:00 -05:00
|
|
|
var resStruct *model.FileUploadResponse
|
|
|
|
|
var appErr *model.AppError
|
2017-02-17 10:31:21 -05:00
|
|
|
|
2018-02-20 10:41:00 -05:00
|
|
|
if err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize); err != nil && err != http.ErrNotMultipart {
|
|
|
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
2017-02-17 10:31:21 -05:00
|
|
|
return
|
2018-02-20 10:41:00 -05:00
|
|
|
} else if err == http.ErrNotMultipart {
|
|
|
|
|
defer r.Body.Close()
|
|
|
|
|
|
|
|
|
|
c.RequireChannelId()
|
|
|
|
|
c.RequireFilename()
|
|
|
|
|
|
|
|
|
|
if c.Err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
channelId := c.Params.ChannelId
|
|
|
|
|
filename := c.Params.Filename
|
|
|
|
|
|
|
|
|
|
if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_UPLOAD_FILE) {
|
|
|
|
|
c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resStruct, appErr = c.App.UploadFiles(
|
|
|
|
|
FILE_TEAM_ID,
|
|
|
|
|
channelId,
|
|
|
|
|
c.Session.UserId,
|
|
|
|
|
[]io.ReadCloser{r.Body},
|
|
|
|
|
[]string{filename},
|
|
|
|
|
[]string{},
|
2018-07-28 14:27:55 +08:00
|
|
|
now,
|
2018-02-20 10:41:00 -05:00
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
m := r.MultipartForm
|
|
|
|
|
|
|
|
|
|
props := m.Value
|
|
|
|
|
if len(props["channel_id"]) == 0 {
|
|
|
|
|
c.SetInvalidParam("channel_id")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
channelId := props["channel_id"][0]
|
|
|
|
|
if len(channelId) == 0 {
|
|
|
|
|
c.SetInvalidParam("channel_id")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_UPLOAD_FILE) {
|
|
|
|
|
c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-28 14:27:55 +08:00
|
|
|
resStruct, appErr = c.App.UploadMultipartFiles(
|
|
|
|
|
FILE_TEAM_ID,
|
|
|
|
|
channelId,
|
|
|
|
|
c.Session.UserId,
|
|
|
|
|
m.File["files"],
|
|
|
|
|
m.Value["client_ids"],
|
|
|
|
|
now,
|
|
|
|
|
)
|
2017-02-17 10:31:21 -05:00
|
|
|
}
|
|
|
|
|
|
2018-02-20 10:41:00 -05:00
|
|
|
if appErr != nil {
|
|
|
|
|
c.Err = appErr
|
2017-02-17 10:31:21 -05:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.WriteHeader(http.StatusCreated)
|
|
|
|
|
w.Write([]byte(resStruct.ToJson()))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
c.RequireFileId()
|
|
|
|
|
if c.Err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 14:42:08 -04:00
|
|
|
forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download"))
|
|
|
|
|
if convErr != nil {
|
|
|
|
|
forceDownload = false
|
2017-04-20 17:34:07 +02:00
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
info, err := c.App.GetFileInfo(c.Params.FileId)
|
2017-02-17 10:31:21 -05:00
|
|
|
if err != nil {
|
|
|
|
|
c.Err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
if info.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
|
2017-02-17 10:31:21 -05:00
|
|
|
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-25 03:23:54 +09:00
|
|
|
fileReader, err := c.App.FileReader(info.Path)
|
2017-04-20 17:34:07 +02:00
|
|
|
if err != nil {
|
2017-02-17 10:31:21 -05:00
|
|
|
c.Err = err
|
|
|
|
|
c.Err.StatusCode = http.StatusNotFound
|
2017-04-20 17:34:07 +02:00
|
|
|
return
|
|
|
|
|
}
|
2018-07-26 10:52:24 -07:00
|
|
|
defer fileReader.Close()
|
2017-04-20 17:34:07 +02:00
|
|
|
|
2018-07-25 03:23:54 +09:00
|
|
|
err = writeFileResponse(info.Name, info.MimeType, info.Size, fileReader, forceDownload, w, r)
|
2017-04-20 17:34:07 +02:00
|
|
|
if err != nil {
|
2017-02-17 10:31:21 -05:00
|
|
|
c.Err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-01 10:18:36 +09:00
|
|
|
func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
c.RequireFileId()
|
|
|
|
|
if c.Err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 14:42:08 -04:00
|
|
|
forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download"))
|
|
|
|
|
if convErr != nil {
|
|
|
|
|
forceDownload = false
|
2017-04-20 17:34:07 +02:00
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
info, err := c.App.GetFileInfo(c.Params.FileId)
|
2017-03-01 10:18:36 +09:00
|
|
|
if err != nil {
|
|
|
|
|
c.Err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
if info.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
|
2017-03-01 10:18:36 +09:00
|
|
|
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if info.ThumbnailPath == "" {
|
2017-08-31 15:03:16 +01:00
|
|
|
c.Err = model.NewAppError("getFileThumbnail", "api.file.get_file_thumbnail.no_thumbnail.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
|
2017-03-01 10:18:36 +09:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-26 10:52:24 -07:00
|
|
|
fileReader, err := c.App.FileReader(info.ThumbnailPath)
|
|
|
|
|
if err != nil {
|
2017-03-01 10:18:36 +09:00
|
|
|
c.Err = err
|
|
|
|
|
c.Err.StatusCode = http.StatusNotFound
|
2018-07-26 10:52:24 -07:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
defer fileReader.Close()
|
|
|
|
|
|
|
|
|
|
err = writeFileResponse(info.Name, THUMBNAIL_IMAGE_TYPE, 0, fileReader, forceDownload, w, r)
|
|
|
|
|
if err != nil {
|
2017-03-01 10:18:36 +09:00
|
|
|
c.Err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-12 07:24:44 +09:00
|
|
|
func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
c.RequireFileId()
|
|
|
|
|
if c.Err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 15:36:43 -07:00
|
|
|
if !c.App.Config().FileSettings.EnablePublicLink {
|
2017-08-31 15:03:16 +01:00
|
|
|
c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented)
|
2017-03-12 07:24:44 +09:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
info, err := c.App.GetFileInfo(c.Params.FileId)
|
2017-03-12 07:24:44 +09:00
|
|
|
if err != nil {
|
|
|
|
|
c.Err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
if info.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
|
2017-03-12 07:24:44 +09:00
|
|
|
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(info.PostId) == 0 {
|
2017-08-31 15:03:16 +01:00
|
|
|
c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.no_post.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
|
2017-03-12 07:24:44 +09:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
resp := make(map[string]string)
|
2017-11-09 14:46:20 -06:00
|
|
|
resp["link"] = c.App.GeneratePublicLink(c.GetSiteURLHeader(), info)
|
2017-03-12 07:24:44 +09:00
|
|
|
|
|
|
|
|
w.Write([]byte(model.MapToJson(resp)))
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-14 04:40:32 +09:00
|
|
|
func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
c.RequireFileId()
|
|
|
|
|
if c.Err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 14:42:08 -04:00
|
|
|
forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download"))
|
|
|
|
|
if convErr != nil {
|
|
|
|
|
forceDownload = false
|
2017-04-20 17:34:07 +02:00
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
info, err := c.App.GetFileInfo(c.Params.FileId)
|
2017-03-14 04:40:32 +09:00
|
|
|
if err != nil {
|
|
|
|
|
c.Err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
if info.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
|
2017-03-14 04:40:32 +09:00
|
|
|
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if info.PreviewPath == "" {
|
2017-08-31 15:03:16 +01:00
|
|
|
c.Err = model.NewAppError("getFilePreview", "api.file.get_file_preview.no_preview.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
|
2017-03-14 04:40:32 +09:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-26 10:52:24 -07:00
|
|
|
fileReader, err := c.App.FileReader(info.PreviewPath)
|
|
|
|
|
if err != nil {
|
2017-03-14 04:40:32 +09:00
|
|
|
c.Err = err
|
|
|
|
|
c.Err.StatusCode = http.StatusNotFound
|
2018-08-08 12:10:05 +02:00
|
|
|
return
|
2018-07-26 10:52:24 -07:00
|
|
|
}
|
|
|
|
|
defer fileReader.Close()
|
|
|
|
|
|
|
|
|
|
err = writeFileResponse(info.Name, PREVIEW_IMAGE_TYPE, 0, fileReader, forceDownload, w, r)
|
|
|
|
|
if err != nil {
|
2017-03-14 04:40:32 +09:00
|
|
|
c.Err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-14 05:34:43 +09:00
|
|
|
func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
c.RequireFileId()
|
|
|
|
|
if c.Err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
info, err := c.App.GetFileInfo(c.Params.FileId)
|
2017-03-14 05:34:43 +09:00
|
|
|
if err != nil {
|
|
|
|
|
c.Err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
if info.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
|
2017-03-14 05:34:43 +09:00
|
|
|
c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
w.Header().Set("Cache-Control", "max-age=2592000, public")
|
|
|
|
|
w.Write([]byte(info.ToJson()))
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-14 06:13:48 +09:00
|
|
|
func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
c.RequireFileId()
|
|
|
|
|
if c.Err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 15:36:43 -07:00
|
|
|
if !c.App.Config().FileSettings.EnablePublicLink {
|
2017-08-31 15:03:16 +01:00
|
|
|
c.Err = model.NewAppError("getPublicFile", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented)
|
2017-03-14 06:13:48 +09:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-09-06 17:12:54 -05:00
|
|
|
info, err := c.App.GetFileInfo(c.Params.FileId)
|
2017-03-14 06:13:48 +09:00
|
|
|
if err != nil {
|
|
|
|
|
c.Err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hash := r.URL.Query().Get("h")
|
|
|
|
|
|
|
|
|
|
if len(hash) == 0 {
|
2017-08-31 15:03:16 +01:00
|
|
|
c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest)
|
2018-06-21 14:31:51 -04:00
|
|
|
utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey())
|
2017-03-14 06:13:48 +09:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2017-10-18 15:36:43 -07:00
|
|
|
if hash != app.GeneratePublicLinkHash(info.Id, *c.App.Config().FileSettings.PublicLinkSalt) {
|
2017-08-31 15:03:16 +01:00
|
|
|
c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest)
|
2018-06-21 14:31:51 -04:00
|
|
|
utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey())
|
2017-03-14 06:13:48 +09:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-26 10:52:24 -07:00
|
|
|
fileReader, err := c.App.FileReader(info.Path)
|
|
|
|
|
if err != nil {
|
2017-03-14 06:13:48 +09:00
|
|
|
c.Err = err
|
|
|
|
|
c.Err.StatusCode = http.StatusNotFound
|
2018-07-26 10:52:24 -07:00
|
|
|
}
|
|
|
|
|
defer fileReader.Close()
|
|
|
|
|
|
|
|
|
|
err = writeFileResponse(info.Name, info.MimeType, info.Size, fileReader, true, w, r)
|
|
|
|
|
if err != nil {
|
2017-03-14 06:13:48 +09:00
|
|
|
c.Err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-25 03:23:54 +09:00
|
|
|
func writeFileResponse(filename string, contentType string, contentSize int64, fileReader io.Reader, forceDownload bool, w http.ResponseWriter, r *http.Request) *model.AppError {
|
2017-06-16 12:35:15 -04:00
|
|
|
w.Header().Set("Cache-Control", "max-age=2592000, private")
|
2017-07-14 14:42:08 -04:00
|
|
|
w.Header().Set("X-Content-Type-Options", "nosniff")
|
2017-02-17 10:31:21 -05:00
|
|
|
|
2018-07-25 03:23:54 +09:00
|
|
|
if contentSize > 0 {
|
|
|
|
|
w.Header().Set("Content-Length", strconv.Itoa(int(contentSize)))
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-14 14:42:08 -04:00
|
|
|
if contentType == "" {
|
|
|
|
|
contentType = "application/octet-stream"
|
2017-02-17 10:31:21 -05:00
|
|
|
} else {
|
2017-07-14 14:42:08 -04:00
|
|
|
for _, unsafeContentType := range UNSAFE_CONTENT_TYPES {
|
|
|
|
|
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 MEDIA_CONTENT_TYPES {
|
|
|
|
|
if strings.HasPrefix(contentType, mediaContentType) {
|
|
|
|
|
isMediaType = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toDownload = !isMediaType
|
2017-02-17 10:31:21 -05:00
|
|
|
}
|
|
|
|
|
|
2017-10-19 15:01:45 -04:00
|
|
|
filename = url.PathEscape(filename)
|
|
|
|
|
|
2017-04-20 17:34:07 +02:00
|
|
|
if toDownload {
|
2017-10-19 15:01:45 -04:00
|
|
|
w.Header().Set("Content-Disposition", "attachment;filename=\""+filename+"\"; filename*=UTF-8''"+filename)
|
2017-04-20 17:34:07 +02:00
|
|
|
} else {
|
2017-10-19 15:01:45 -04:00
|
|
|
w.Header().Set("Content-Disposition", "inline;filename=\""+filename+"\"; filename*=UTF-8''"+filename)
|
2017-04-20 17:34:07 +02:00
|
|
|
}
|
2017-02-17 10:31:21 -05:00
|
|
|
|
|
|
|
|
// prevent file links from being embedded in iframes
|
|
|
|
|
w.Header().Set("X-Frame-Options", "DENY")
|
|
|
|
|
w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'")
|
|
|
|
|
|
2018-07-25 03:23:54 +09:00
|
|
|
io.Copy(w, fileReader)
|
2017-02-17 10:31:21 -05:00
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|