github.com/keys-pub/mattermost-server@v4.10.10+incompatible/api4/file.go (about)

     1  // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api4
     5  
     6  import (
     7  	"io"
     8  	"net/http"
     9  	"net/url"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/mattermost/mattermost-server/app"
    14  	"github.com/mattermost/mattermost-server/model"
    15  	"github.com/mattermost/mattermost-server/utils"
    16  )
    17  
    18  const (
    19  	FILE_TEAM_ID = "noteam"
    20  
    21  	PREVIEW_IMAGE_TYPE   = "image/jpeg"
    22  	THUMBNAIL_IMAGE_TYPE = "image/jpeg"
    23  )
    24  
    25  var UNSAFE_CONTENT_TYPES = [...]string{
    26  	"application/javascript",
    27  	"application/ecmascript",
    28  	"text/javascript",
    29  	"text/ecmascript",
    30  	"application/x-javascript",
    31  	"text/html",
    32  }
    33  
    34  var MEDIA_CONTENT_TYPES = [...]string{
    35  	"image/jpeg",
    36  	"image/png",
    37  	"image/bmp",
    38  	"image/gif",
    39  	"video/avi",
    40  	"video/mpeg",
    41  	"video/mp4",
    42  	"audio/mpeg",
    43  	"audio/wav",
    44  }
    45  
    46  func (api *API) InitFile() {
    47  	api.BaseRoutes.Files.Handle("", api.ApiSessionRequired(uploadFile)).Methods("POST")
    48  	api.BaseRoutes.File.Handle("", api.ApiSessionRequiredTrustRequester(getFile)).Methods("GET")
    49  	api.BaseRoutes.File.Handle("/thumbnail", api.ApiSessionRequiredTrustRequester(getFileThumbnail)).Methods("GET")
    50  	api.BaseRoutes.File.Handle("/link", api.ApiSessionRequired(getFileLink)).Methods("GET")
    51  	api.BaseRoutes.File.Handle("/preview", api.ApiSessionRequiredTrustRequester(getFilePreview)).Methods("GET")
    52  	api.BaseRoutes.File.Handle("/info", api.ApiSessionRequired(getFileInfo)).Methods("GET")
    53  
    54  	api.BaseRoutes.PublicFile.Handle("", api.ApiHandler(getPublicFile)).Methods("GET")
    55  
    56  }
    57  
    58  func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
    59  	if !*c.App.Config().FileSettings.EnableFileAttachments {
    60  		c.Err = model.NewAppError("uploadFile", "api.file.attachments.disabled.app_error", nil, "", http.StatusNotImplemented)
    61  		return
    62  	}
    63  
    64  	if r.ContentLength > *c.App.Config().FileSettings.MaxFileSize {
    65  		c.Err = model.NewAppError("uploadFile", "api.file.upload_file.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge)
    66  		return
    67  	}
    68  
    69  	var resStruct *model.FileUploadResponse
    70  	var appErr *model.AppError
    71  
    72  	if err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize); err != nil && err != http.ErrNotMultipart {
    73  		http.Error(w, err.Error(), http.StatusInternalServerError)
    74  		return
    75  	} else if err == http.ErrNotMultipart {
    76  		defer r.Body.Close()
    77  
    78  		c.RequireChannelId()
    79  		c.RequireFilename()
    80  
    81  		if c.Err != nil {
    82  			return
    83  		}
    84  
    85  		channelId := c.Params.ChannelId
    86  		filename := c.Params.Filename
    87  
    88  		if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_UPLOAD_FILE) {
    89  			c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
    90  			return
    91  		}
    92  
    93  		resStruct, appErr = c.App.UploadFiles(
    94  			FILE_TEAM_ID,
    95  			channelId,
    96  			c.Session.UserId,
    97  			[]io.ReadCloser{r.Body},
    98  			[]string{filename},
    99  			[]string{},
   100  		)
   101  	} else {
   102  		m := r.MultipartForm
   103  
   104  		props := m.Value
   105  		if len(props["channel_id"]) == 0 {
   106  			c.SetInvalidParam("channel_id")
   107  			return
   108  		}
   109  		channelId := props["channel_id"][0]
   110  		if len(channelId) == 0 {
   111  			c.SetInvalidParam("channel_id")
   112  			return
   113  		}
   114  
   115  		if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_UPLOAD_FILE) {
   116  			c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
   117  			return
   118  		}
   119  
   120  		resStruct, appErr = c.App.UploadMultipartFiles(FILE_TEAM_ID, channelId, c.Session.UserId, m.File["files"], m.Value["client_ids"])
   121  	}
   122  
   123  	if appErr != nil {
   124  		c.Err = appErr
   125  		return
   126  	}
   127  
   128  	w.WriteHeader(http.StatusCreated)
   129  	w.Write([]byte(resStruct.ToJson()))
   130  }
   131  
   132  func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
   133  	c.RequireFileId()
   134  	if c.Err != nil {
   135  		return
   136  	}
   137  
   138  	forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download"))
   139  	if convErr != nil {
   140  		forceDownload = false
   141  	}
   142  
   143  	info, err := c.App.GetFileInfo(c.Params.FileId)
   144  	if err != nil {
   145  		c.Err = err
   146  		return
   147  	}
   148  
   149  	if info.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   150  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   151  		return
   152  	}
   153  
   154  	data, err := c.App.ReadFile(info.Path)
   155  	if err != nil {
   156  		c.Err = err
   157  		c.Err.StatusCode = http.StatusNotFound
   158  		return
   159  	}
   160  
   161  	err = writeFileResponse(info.Name, info.MimeType, data, forceDownload, w, r)
   162  	if err != nil {
   163  		c.Err = err
   164  		return
   165  	}
   166  }
   167  
   168  func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) {
   169  	c.RequireFileId()
   170  	if c.Err != nil {
   171  		return
   172  	}
   173  
   174  	forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download"))
   175  	if convErr != nil {
   176  		forceDownload = false
   177  	}
   178  
   179  	info, err := c.App.GetFileInfo(c.Params.FileId)
   180  	if err != nil {
   181  		c.Err = err
   182  		return
   183  	}
   184  
   185  	if info.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   186  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   187  		return
   188  	}
   189  
   190  	if info.ThumbnailPath == "" {
   191  		c.Err = model.NewAppError("getFileThumbnail", "api.file.get_file_thumbnail.no_thumbnail.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
   192  		return
   193  	}
   194  
   195  	if data, err := c.App.ReadFile(info.ThumbnailPath); err != nil {
   196  		c.Err = err
   197  		c.Err.StatusCode = http.StatusNotFound
   198  	} else if err := writeFileResponse(info.Name, THUMBNAIL_IMAGE_TYPE, data, forceDownload, w, r); err != nil {
   199  		c.Err = err
   200  		return
   201  	}
   202  }
   203  
   204  func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) {
   205  	c.RequireFileId()
   206  	if c.Err != nil {
   207  		return
   208  	}
   209  
   210  	if !c.App.Config().FileSettings.EnablePublicLink {
   211  		c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented)
   212  		return
   213  	}
   214  
   215  	info, err := c.App.GetFileInfo(c.Params.FileId)
   216  	if err != nil {
   217  		c.Err = err
   218  		return
   219  	}
   220  
   221  	if info.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   222  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   223  		return
   224  	}
   225  
   226  	if len(info.PostId) == 0 {
   227  		c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.no_post.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
   228  		return
   229  	}
   230  
   231  	resp := make(map[string]string)
   232  	resp["link"] = c.App.GeneratePublicLink(c.GetSiteURLHeader(), info)
   233  
   234  	w.Write([]byte(model.MapToJson(resp)))
   235  }
   236  
   237  func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) {
   238  	c.RequireFileId()
   239  	if c.Err != nil {
   240  		return
   241  	}
   242  
   243  	forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download"))
   244  	if convErr != nil {
   245  		forceDownload = false
   246  	}
   247  
   248  	info, err := c.App.GetFileInfo(c.Params.FileId)
   249  	if err != nil {
   250  		c.Err = err
   251  		return
   252  	}
   253  
   254  	if info.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   255  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   256  		return
   257  	}
   258  
   259  	if info.PreviewPath == "" {
   260  		c.Err = model.NewAppError("getFilePreview", "api.file.get_file_preview.no_preview.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
   261  		return
   262  	}
   263  
   264  	if data, err := c.App.ReadFile(info.PreviewPath); err != nil {
   265  		c.Err = err
   266  		c.Err.StatusCode = http.StatusNotFound
   267  	} else if err := writeFileResponse(info.Name, PREVIEW_IMAGE_TYPE, data, forceDownload, w, r); err != nil {
   268  		c.Err = err
   269  		return
   270  	}
   271  }
   272  
   273  func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) {
   274  	c.RequireFileId()
   275  	if c.Err != nil {
   276  		return
   277  	}
   278  
   279  	info, err := c.App.GetFileInfo(c.Params.FileId)
   280  	if err != nil {
   281  		c.Err = err
   282  		return
   283  	}
   284  
   285  	if info.CreatorId != c.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   286  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   287  		return
   288  	}
   289  
   290  	w.Header().Set("Cache-Control", "max-age=2592000, public")
   291  	w.Write([]byte(info.ToJson()))
   292  }
   293  
   294  func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) {
   295  	c.RequireFileId()
   296  	if c.Err != nil {
   297  		return
   298  	}
   299  
   300  	if !c.App.Config().FileSettings.EnablePublicLink {
   301  		c.Err = model.NewAppError("getPublicFile", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented)
   302  		return
   303  	}
   304  
   305  	info, err := c.App.GetFileInfo(c.Params.FileId)
   306  	if err != nil {
   307  		c.Err = err
   308  		return
   309  	}
   310  
   311  	hash := r.URL.Query().Get("h")
   312  
   313  	if len(hash) == 0 {
   314  		c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest)
   315  		utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey())
   316  		return
   317  	}
   318  
   319  	if hash != app.GeneratePublicLinkHash(info.Id, *c.App.Config().FileSettings.PublicLinkSalt) {
   320  		c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest)
   321  		utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey())
   322  		return
   323  	}
   324  
   325  	if data, err := c.App.ReadFile(info.Path); err != nil {
   326  		c.Err = err
   327  		c.Err.StatusCode = http.StatusNotFound
   328  	} else if err := writeFileResponse(info.Name, info.MimeType, data, true, w, r); err != nil {
   329  		c.Err = err
   330  		return
   331  	}
   332  }
   333  
   334  func writeFileResponse(filename string, contentType string, bytes []byte, forceDownload bool, w http.ResponseWriter, r *http.Request) *model.AppError {
   335  	w.Header().Set("Cache-Control", "max-age=2592000, private")
   336  	w.Header().Set("Content-Length", strconv.Itoa(len(bytes)))
   337  	w.Header().Set("X-Content-Type-Options", "nosniff")
   338  
   339  	if contentType == "" {
   340  		contentType = "application/octet-stream"
   341  	} else {
   342  		for _, unsafeContentType := range UNSAFE_CONTENT_TYPES {
   343  			if strings.HasPrefix(contentType, unsafeContentType) {
   344  				contentType = "text/plain"
   345  				break
   346  			}
   347  		}
   348  	}
   349  
   350  	w.Header().Set("Content-Type", contentType)
   351  
   352  	var toDownload bool
   353  	if forceDownload {
   354  		toDownload = true
   355  	} else {
   356  		isMediaType := false
   357  
   358  		for _, mediaContentType := range MEDIA_CONTENT_TYPES {
   359  			if strings.HasPrefix(contentType, mediaContentType) {
   360  				isMediaType = true
   361  				break
   362  			}
   363  		}
   364  
   365  		toDownload = !isMediaType
   366  	}
   367  
   368  	filename = url.PathEscape(filename)
   369  
   370  	if toDownload {
   371  		w.Header().Set("Content-Disposition", "attachment;filename=\""+filename+"\"; filename*=UTF-8''"+filename)
   372  	} else {
   373  		w.Header().Set("Content-Disposition", "inline;filename=\""+filename+"\"; filename*=UTF-8''"+filename)
   374  	}
   375  
   376  	// prevent file links from being embedded in iframes
   377  	w.Header().Set("X-Frame-Options", "DENY")
   378  	w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'")
   379  
   380  	w.Write(bytes)
   381  
   382  	return nil
   383  }