github.com/kongr45gpen/mattermost-server@v5.11.1+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  	"bytes"
     8  	"crypto/subtle"
     9  	"io"
    10  	"io/ioutil"
    11  	"mime"
    12  	"mime/multipart"
    13  	"net/http"
    14  	"net/url"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/mattermost/mattermost-server/app"
    20  	"github.com/mattermost/mattermost-server/model"
    21  	"github.com/mattermost/mattermost-server/utils"
    22  )
    23  
    24  const (
    25  	FILE_TEAM_ID = "noteam"
    26  
    27  	PREVIEW_IMAGE_TYPE   = "image/jpeg"
    28  	THUMBNAIL_IMAGE_TYPE = "image/jpeg"
    29  )
    30  
    31  var UNSAFE_CONTENT_TYPES = [...]string{
    32  	"application/javascript",
    33  	"application/ecmascript",
    34  	"text/javascript",
    35  	"text/ecmascript",
    36  	"application/x-javascript",
    37  	"text/html",
    38  }
    39  
    40  var MEDIA_CONTENT_TYPES = [...]string{
    41  	"image/jpeg",
    42  	"image/png",
    43  	"image/bmp",
    44  	"image/gif",
    45  	"image/tiff",
    46  	"video/avi",
    47  	"video/mpeg",
    48  	"video/mp4",
    49  	"audio/mpeg",
    50  	"audio/wav",
    51  }
    52  
    53  const maxUploadDrainBytes = (10 * 1024 * 1024) // 10Mb
    54  const maxMultipartFormDataBytes = 10 * 1024    // 10Kb
    55  
    56  func (api *API) InitFile() {
    57  	api.BaseRoutes.Files.Handle("", api.ApiSessionRequired(uploadFileStream)).Methods("POST")
    58  	api.BaseRoutes.File.Handle("", api.ApiSessionRequiredTrustRequester(getFile)).Methods("GET")
    59  	api.BaseRoutes.File.Handle("/thumbnail", api.ApiSessionRequiredTrustRequester(getFileThumbnail)).Methods("GET")
    60  	api.BaseRoutes.File.Handle("/link", api.ApiSessionRequired(getFileLink)).Methods("GET")
    61  	api.BaseRoutes.File.Handle("/preview", api.ApiSessionRequiredTrustRequester(getFilePreview)).Methods("GET")
    62  	api.BaseRoutes.File.Handle("/info", api.ApiSessionRequired(getFileInfo)).Methods("GET")
    63  
    64  	api.BaseRoutes.PublicFile.Handle("", api.ApiHandler(getPublicFile)).Methods("GET")
    65  
    66  }
    67  
    68  func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
    69  	defer io.Copy(ioutil.Discard, r.Body)
    70  
    71  	if !*c.App.Config().FileSettings.EnableFileAttachments {
    72  		c.Err = model.NewAppError("uploadFile", "api.file.attachments.disabled.app_error", nil, "", http.StatusNotImplemented)
    73  		return
    74  	}
    75  
    76  	if r.ContentLength > *c.App.Config().FileSettings.MaxFileSize {
    77  		c.Err = model.NewAppError("uploadFile", "api.file.upload_file.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge)
    78  		return
    79  	}
    80  
    81  	now := time.Now()
    82  	var resStruct *model.FileUploadResponse
    83  	var appErr *model.AppError
    84  
    85  	if err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize); err != nil && err != http.ErrNotMultipart {
    86  		http.Error(w, err.Error(), http.StatusInternalServerError)
    87  		return
    88  	} else if err == http.ErrNotMultipart {
    89  		defer r.Body.Close()
    90  
    91  		c.RequireChannelId()
    92  		c.RequireFilename()
    93  
    94  		if c.Err != nil {
    95  			return
    96  		}
    97  
    98  		channelId := c.Params.ChannelId
    99  		filename := c.Params.Filename
   100  
   101  		if !c.App.SessionHasPermissionToChannel(c.App.Session, channelId, model.PERMISSION_UPLOAD_FILE) {
   102  			c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
   103  			return
   104  		}
   105  
   106  		resStruct, appErr = c.App.UploadFiles(
   107  			FILE_TEAM_ID,
   108  			channelId,
   109  			c.App.Session.UserId,
   110  			[]io.ReadCloser{r.Body},
   111  			[]string{filename},
   112  			[]string{},
   113  			now,
   114  		)
   115  	} else {
   116  		m := r.MultipartForm
   117  
   118  		props := m.Value
   119  		if len(props["channel_id"]) == 0 {
   120  			c.SetInvalidParam("channel_id")
   121  			return
   122  		}
   123  		channelId := props["channel_id"][0]
   124  		c.Params.ChannelId = channelId
   125  		c.RequireChannelId()
   126  		if c.Err != nil {
   127  			return
   128  		}
   129  
   130  		if !c.App.SessionHasPermissionToChannel(c.App.Session, channelId, model.PERMISSION_UPLOAD_FILE) {
   131  			c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
   132  			return
   133  		}
   134  
   135  		resStruct, appErr = c.App.UploadMultipartFiles(
   136  			FILE_TEAM_ID,
   137  			channelId,
   138  			c.App.Session.UserId,
   139  			m.File["files"],
   140  			m.Value["client_ids"],
   141  			now,
   142  		)
   143  	}
   144  
   145  	if appErr != nil {
   146  		c.Err = appErr
   147  		return
   148  	}
   149  
   150  	w.WriteHeader(http.StatusCreated)
   151  	w.Write([]byte(resStruct.ToJson()))
   152  }
   153  
   154  func parseMultipartRequestHeader(req *http.Request) (boundary string, err error) {
   155  	v := req.Header.Get("Content-Type")
   156  	if v == "" {
   157  		return "", http.ErrNotMultipart
   158  	}
   159  	d, params, err := mime.ParseMediaType(v)
   160  	if err != nil || d != "multipart/form-data" {
   161  		return "", http.ErrNotMultipart
   162  	}
   163  	boundary, ok := params["boundary"]
   164  	if !ok {
   165  		return "", http.ErrMissingBoundary
   166  	}
   167  
   168  	return boundary, nil
   169  }
   170  
   171  func multipartReader(req *http.Request, stream io.Reader) (*multipart.Reader, error) {
   172  	boundary, err := parseMultipartRequestHeader(req)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  
   177  	if stream != nil {
   178  		return multipart.NewReader(stream, boundary), nil
   179  	} else {
   180  		return multipart.NewReader(req.Body, boundary), nil
   181  	}
   182  }
   183  
   184  func uploadFileStream(c *Context, w http.ResponseWriter, r *http.Request) {
   185  	// Drain any remaining bytes in the request body, up to a limit
   186  	defer io.CopyN(ioutil.Discard, r.Body, maxUploadDrainBytes)
   187  
   188  	if !*c.App.Config().FileSettings.EnableFileAttachments {
   189  		c.Err = model.NewAppError("uploadFileStream",
   190  			"api.file.attachments.disabled.app_error",
   191  			nil, "", http.StatusNotImplemented)
   192  		return
   193  	}
   194  
   195  	// Parse the post as a regular form (in practice, use the URL values
   196  	// since we never expect a real application/x-www-form-urlencoded
   197  	// form).
   198  	if r.Form == nil {
   199  		err := r.ParseForm()
   200  		if err != nil {
   201  			c.Err = model.NewAppError("uploadFileStream",
   202  				"api.file.upload_file.read_request.app_error",
   203  				nil, err.Error(), http.StatusBadRequest)
   204  			return
   205  		}
   206  	}
   207  
   208  	timestamp := time.Now()
   209  	var fileUploadResponse *model.FileUploadResponse
   210  
   211  	_, err := parseMultipartRequestHeader(r)
   212  	switch err {
   213  	case nil:
   214  		fileUploadResponse = uploadFileMultipart(c, r, nil, timestamp)
   215  
   216  	case http.ErrNotMultipart:
   217  		fileUploadResponse = uploadFileSimple(c, r, timestamp)
   218  
   219  	default:
   220  		c.Err = model.NewAppError("uploadFileStream",
   221  			"api.file.upload_file.read_request.app_error",
   222  			nil, err.Error(), http.StatusBadRequest)
   223  	}
   224  	if c.Err != nil {
   225  		return
   226  	}
   227  
   228  	// Write the response values to the output upon return
   229  	w.WriteHeader(http.StatusCreated)
   230  	w.Write([]byte(fileUploadResponse.ToJson()))
   231  }
   232  
   233  // uploadFileSimple uploads a file from a simple POST with the file in the request body
   234  func uploadFileSimple(c *Context, r *http.Request, timestamp time.Time) *model.FileUploadResponse {
   235  	// Simple POST with the file in the body and all metadata in the args.
   236  	c.RequireChannelId()
   237  	c.RequireFilename()
   238  	if c.Err != nil {
   239  		return nil
   240  	}
   241  
   242  	if !c.App.SessionHasPermissionToChannel(c.App.Session, c.Params.ChannelId, model.PERMISSION_UPLOAD_FILE) {
   243  		c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
   244  		return nil
   245  	}
   246  
   247  	clientId := r.Form.Get("client_id")
   248  	info, appErr := c.App.UploadFileX(c.Params.ChannelId, c.Params.Filename, r.Body,
   249  		app.UploadFileSetTeamId(FILE_TEAM_ID),
   250  		app.UploadFileSetUserId(c.App.Session.UserId),
   251  		app.UploadFileSetTimestamp(timestamp),
   252  		app.UploadFileSetContentLength(r.ContentLength),
   253  		app.UploadFileSetClientId(clientId))
   254  	if appErr != nil {
   255  		c.Err = appErr
   256  		return nil
   257  	}
   258  
   259  	fileUploadResponse := &model.FileUploadResponse{
   260  		FileInfos: []*model.FileInfo{info},
   261  	}
   262  	if clientId != "" {
   263  		fileUploadResponse.ClientIds = []string{clientId}
   264  	}
   265  	return fileUploadResponse
   266  }
   267  
   268  // uploadFileMultipart parses and uploads file(s) from a mime/multipart
   269  // request.  It pre-buffers up to the first part which is either the (a)
   270  // `channel_id` value, or (b) a file. Then in case of (a) it re-processes the
   271  // entire message recursively calling itself in stream mode. In case of (b) it
   272  // calls to uploadFileMultipartLegacy for legacy support
   273  func uploadFileMultipart(c *Context, r *http.Request, asStream io.Reader, timestamp time.Time) *model.FileUploadResponse {
   274  
   275  	expectClientIds := true
   276  	var clientIds []string
   277  	resp := model.FileUploadResponse{
   278  		FileInfos: []*model.FileInfo{},
   279  		ClientIds: []string{},
   280  	}
   281  
   282  	var buf *bytes.Buffer
   283  	var mr *multipart.Reader
   284  	var err error
   285  	if asStream == nil {
   286  		// We need to buffer until we get the channel_id, or the first file.
   287  		buf = &bytes.Buffer{}
   288  		mr, err = multipartReader(r, io.TeeReader(r.Body, buf))
   289  	} else {
   290  		mr, err = multipartReader(r, asStream)
   291  	}
   292  	if err != nil {
   293  		c.Err = model.NewAppError("uploadFileMultipart",
   294  			"api.file.upload_file.read_request.app_error",
   295  			nil, err.Error(), http.StatusBadRequest)
   296  		return nil
   297  	}
   298  
   299  	nFiles := 0
   300  NEXT_PART:
   301  	for {
   302  		part, err := mr.NextPart()
   303  		if err == io.EOF {
   304  			break
   305  		}
   306  		if err != nil {
   307  			c.Err = model.NewAppError("uploadFileMultipart",
   308  				"api.file.upload_file.read_request.app_error",
   309  				nil, err.Error(), http.StatusBadRequest)
   310  			return nil
   311  		}
   312  
   313  		// Parse any form fields in the multipart.
   314  		formname := part.FormName()
   315  		if formname == "" {
   316  			continue
   317  		}
   318  		filename := part.FileName()
   319  		if filename == "" {
   320  			var b bytes.Buffer
   321  			_, err = io.CopyN(&b, part, maxMultipartFormDataBytes)
   322  			if err != nil && err != io.EOF {
   323  				c.Err = model.NewAppError("uploadFileMultipart",
   324  					"api.file.upload_file.read_form_value.app_error",
   325  					map[string]interface{}{"Formname": formname},
   326  					err.Error(), http.StatusBadRequest)
   327  				return nil
   328  			}
   329  			v := b.String()
   330  
   331  			switch formname {
   332  			case "channel_id":
   333  				if c.Params.ChannelId != "" && c.Params.ChannelId != v {
   334  					c.Err = model.NewAppError("uploadFileMultipart",
   335  						"api.file.upload_file.multiple_channel_ids.app_error",
   336  						nil, "", http.StatusBadRequest)
   337  					return nil
   338  				}
   339  				if v != "" {
   340  					c.Params.ChannelId = v
   341  				}
   342  
   343  				// Got channel_id, re-process the entire post
   344  				// in the streaming mode.
   345  				if asStream == nil {
   346  					return uploadFileMultipart(c, r, io.MultiReader(buf, r.Body), timestamp)
   347  				}
   348  
   349  			case "client_ids":
   350  				if !expectClientIds {
   351  					c.SetInvalidParam("client_ids")
   352  					return nil
   353  				}
   354  				clientIds = append(clientIds, v)
   355  
   356  			default:
   357  				c.SetInvalidParam(formname)
   358  				return nil
   359  			}
   360  
   361  			continue NEXT_PART
   362  		}
   363  
   364  		// A file part.
   365  
   366  		if c.Params.ChannelId == "" && asStream == nil {
   367  			// Got file before channel_id, fall back to legacy buffered mode
   368  			mr, err = multipartReader(r, io.MultiReader(buf, r.Body))
   369  			if err != nil {
   370  				c.Err = model.NewAppError("uploadFileMultipart",
   371  					"api.file.upload_file.read_request.app_error",
   372  					nil, err.Error(), http.StatusBadRequest)
   373  				return nil
   374  			}
   375  
   376  			return uploadFileMultipartLegacy(c, mr, timestamp)
   377  		}
   378  
   379  		c.RequireChannelId()
   380  		if c.Err != nil {
   381  			return nil
   382  		}
   383  		if !c.App.SessionHasPermissionToChannel(c.App.Session, c.Params.ChannelId, model.PERMISSION_UPLOAD_FILE) {
   384  			c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
   385  			return nil
   386  		}
   387  
   388  		// If there's no clientIds when the first file comes, expect
   389  		// none later.
   390  		if nFiles == 0 && len(clientIds) == 0 {
   391  			expectClientIds = false
   392  		}
   393  
   394  		// Must have a exactly one client ID for each file.
   395  		clientId := ""
   396  		if expectClientIds {
   397  			if nFiles >= len(clientIds) {
   398  				c.SetInvalidParam("client_ids")
   399  				return nil
   400  			}
   401  
   402  			clientId = clientIds[nFiles]
   403  		}
   404  
   405  		info, appErr := c.App.UploadFileX(c.Params.ChannelId, filename, part,
   406  			app.UploadFileSetTeamId(FILE_TEAM_ID),
   407  			app.UploadFileSetUserId(c.App.Session.UserId),
   408  			app.UploadFileSetTimestamp(timestamp),
   409  			app.UploadFileSetContentLength(-1),
   410  			app.UploadFileSetClientId(clientId))
   411  		if appErr != nil {
   412  			c.Err = appErr
   413  			return nil
   414  		}
   415  
   416  		// add to the response
   417  		resp.FileInfos = append(resp.FileInfos, info)
   418  		if expectClientIds {
   419  			resp.ClientIds = append(resp.ClientIds, clientId)
   420  		}
   421  
   422  		nFiles++
   423  	}
   424  
   425  	// Verify that the number of ClientIds matched the number of files.
   426  	if expectClientIds && len(clientIds) != nFiles {
   427  		c.Err = model.NewAppError("uploadFileMultipart",
   428  			"api.file.upload_file.incorrect_number_of_client_ids.app_error",
   429  			map[string]interface{}{"NumClientIds": len(clientIds), "NumFiles": nFiles},
   430  			"", http.StatusBadRequest)
   431  		return nil
   432  	}
   433  
   434  	return &resp
   435  }
   436  
   437  // uploadFileMultipartLegacy reads, buffers, and then uploads the message,
   438  // borrowing from http.ParseMultipartForm.  If successful it returns a
   439  // *model.FileUploadResponse filled in with the individual model.FileInfo's.
   440  func uploadFileMultipartLegacy(c *Context, mr *multipart.Reader,
   441  	timestamp time.Time) *model.FileUploadResponse {
   442  
   443  	// Parse the entire form.
   444  	form, err := mr.ReadForm(*c.App.Config().FileSettings.MaxFileSize)
   445  	if err != nil {
   446  		c.Err = model.NewAppError("uploadFileMultipartLegacy",
   447  			"api.file.upload_file.read_request.app_error",
   448  			nil, err.Error(), http.StatusInternalServerError)
   449  		return nil
   450  	}
   451  
   452  	// get and validate the channel Id, permission to upload there.
   453  	if len(form.Value["channel_id"]) == 0 {
   454  		c.SetInvalidParam("channel_id")
   455  		return nil
   456  	}
   457  	channelId := form.Value["channel_id"][0]
   458  	c.Params.ChannelId = channelId
   459  	c.RequireChannelId()
   460  	if c.Err != nil {
   461  		return nil
   462  	}
   463  	if !c.App.SessionHasPermissionToChannel(c.App.Session, channelId, model.PERMISSION_UPLOAD_FILE) {
   464  		c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
   465  		return nil
   466  	}
   467  
   468  	// Check that we have either no client IDs, or one per file.
   469  	clientIds := form.Value["client_ids"]
   470  	fileHeaders := form.File["files"]
   471  	if len(clientIds) != 0 && len(clientIds) != len(fileHeaders) {
   472  		c.Err = model.NewAppError("uploadFilesMultipartBuffered",
   473  			"api.file.upload_file.incorrect_number_of_client_ids.app_error",
   474  			map[string]interface{}{"NumClientIds": len(clientIds), "NumFiles": len(fileHeaders)},
   475  			"", http.StatusBadRequest)
   476  		return nil
   477  	}
   478  
   479  	resp := model.FileUploadResponse{
   480  		FileInfos: []*model.FileInfo{},
   481  		ClientIds: []string{},
   482  	}
   483  
   484  	for i, fileHeader := range fileHeaders {
   485  		f, err := fileHeader.Open()
   486  		if err != nil {
   487  			c.Err = model.NewAppError("uploadFileMultipartLegacy",
   488  				"api.file.upload_file.read_request.app_error",
   489  				nil, err.Error(), http.StatusBadRequest)
   490  			return nil
   491  		}
   492  
   493  		clientId := ""
   494  		if len(clientIds) > 0 {
   495  			clientId = clientIds[i]
   496  		}
   497  
   498  		info, appErr := c.App.UploadFileX(c.Params.ChannelId, fileHeader.Filename, f,
   499  			app.UploadFileSetTeamId(FILE_TEAM_ID),
   500  			app.UploadFileSetUserId(c.App.Session.UserId),
   501  			app.UploadFileSetTimestamp(timestamp),
   502  			app.UploadFileSetContentLength(-1),
   503  			app.UploadFileSetClientId(clientId))
   504  		f.Close()
   505  		if appErr != nil {
   506  			c.Err = appErr
   507  			return nil
   508  		}
   509  
   510  		resp.FileInfos = append(resp.FileInfos, info)
   511  		if clientId != "" {
   512  			resp.ClientIds = append(resp.ClientIds, clientId)
   513  		}
   514  	}
   515  
   516  	return &resp
   517  }
   518  
   519  func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
   520  	c.RequireFileId()
   521  	if c.Err != nil {
   522  		return
   523  	}
   524  
   525  	forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download"))
   526  	if convErr != nil {
   527  		forceDownload = false
   528  	}
   529  
   530  	info, err := c.App.GetFileInfo(c.Params.FileId)
   531  	if err != nil {
   532  		c.Err = err
   533  		return
   534  	}
   535  
   536  	if info.CreatorId != c.App.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.App.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   537  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   538  		return
   539  	}
   540  
   541  	fileReader, err := c.App.FileReader(info.Path)
   542  	if err != nil {
   543  		c.Err = err
   544  		c.Err.StatusCode = http.StatusNotFound
   545  		return
   546  	}
   547  	defer fileReader.Close()
   548  
   549  	err = writeFileResponse(info.Name, info.MimeType, info.Size, fileReader, forceDownload, w, r)
   550  	if err != nil {
   551  		c.Err = err
   552  		return
   553  	}
   554  }
   555  
   556  func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) {
   557  	c.RequireFileId()
   558  	if c.Err != nil {
   559  		return
   560  	}
   561  
   562  	forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download"))
   563  	if convErr != nil {
   564  		forceDownload = false
   565  	}
   566  
   567  	info, err := c.App.GetFileInfo(c.Params.FileId)
   568  	if err != nil {
   569  		c.Err = err
   570  		return
   571  	}
   572  
   573  	if info.CreatorId != c.App.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.App.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   574  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   575  		return
   576  	}
   577  
   578  	if info.ThumbnailPath == "" {
   579  		c.Err = model.NewAppError("getFileThumbnail", "api.file.get_file_thumbnail.no_thumbnail.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
   580  		return
   581  	}
   582  
   583  	fileReader, err := c.App.FileReader(info.ThumbnailPath)
   584  	if err != nil {
   585  		c.Err = err
   586  		c.Err.StatusCode = http.StatusNotFound
   587  		return
   588  	}
   589  	defer fileReader.Close()
   590  
   591  	err = writeFileResponse(info.Name, THUMBNAIL_IMAGE_TYPE, 0, fileReader, forceDownload, w, r)
   592  	if err != nil {
   593  		c.Err = err
   594  		return
   595  	}
   596  }
   597  
   598  func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) {
   599  	c.RequireFileId()
   600  	if c.Err != nil {
   601  		return
   602  	}
   603  
   604  	if !*c.App.Config().FileSettings.EnablePublicLink {
   605  		c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented)
   606  		return
   607  	}
   608  
   609  	info, err := c.App.GetFileInfo(c.Params.FileId)
   610  	if err != nil {
   611  		c.Err = err
   612  		return
   613  	}
   614  
   615  	if info.CreatorId != c.App.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.App.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   616  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   617  		return
   618  	}
   619  
   620  	if len(info.PostId) == 0 {
   621  		c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.no_post.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
   622  		return
   623  	}
   624  
   625  	resp := make(map[string]string)
   626  	resp["link"] = c.App.GeneratePublicLink(c.GetSiteURLHeader(), info)
   627  
   628  	w.Write([]byte(model.MapToJson(resp)))
   629  }
   630  
   631  func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) {
   632  	c.RequireFileId()
   633  	if c.Err != nil {
   634  		return
   635  	}
   636  
   637  	forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download"))
   638  	if convErr != nil {
   639  		forceDownload = false
   640  	}
   641  
   642  	info, err := c.App.GetFileInfo(c.Params.FileId)
   643  	if err != nil {
   644  		c.Err = err
   645  		return
   646  	}
   647  
   648  	if info.CreatorId != c.App.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.App.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   649  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   650  		return
   651  	}
   652  
   653  	if info.PreviewPath == "" {
   654  		c.Err = model.NewAppError("getFilePreview", "api.file.get_file_preview.no_preview.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
   655  		return
   656  	}
   657  
   658  	fileReader, err := c.App.FileReader(info.PreviewPath)
   659  	if err != nil {
   660  		c.Err = err
   661  		c.Err.StatusCode = http.StatusNotFound
   662  		return
   663  	}
   664  	defer fileReader.Close()
   665  
   666  	err = writeFileResponse(info.Name, PREVIEW_IMAGE_TYPE, 0, fileReader, forceDownload, w, r)
   667  	if err != nil {
   668  		c.Err = err
   669  		return
   670  	}
   671  }
   672  
   673  func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) {
   674  	c.RequireFileId()
   675  	if c.Err != nil {
   676  		return
   677  	}
   678  
   679  	info, err := c.App.GetFileInfo(c.Params.FileId)
   680  	if err != nil {
   681  		c.Err = err
   682  		return
   683  	}
   684  
   685  	if info.CreatorId != c.App.Session.UserId && !c.App.SessionHasPermissionToChannelByPost(c.App.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   686  		c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   687  		return
   688  	}
   689  
   690  	w.Header().Set("Cache-Control", "max-age=2592000, public")
   691  	w.Write([]byte(info.ToJson()))
   692  }
   693  
   694  func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) {
   695  	c.RequireFileId()
   696  	if c.Err != nil {
   697  		return
   698  	}
   699  
   700  	if !*c.App.Config().FileSettings.EnablePublicLink {
   701  		c.Err = model.NewAppError("getPublicFile", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented)
   702  		return
   703  	}
   704  
   705  	info, err := c.App.GetFileInfo(c.Params.FileId)
   706  	if err != nil {
   707  		c.Err = err
   708  		return
   709  	}
   710  
   711  	hash := r.URL.Query().Get("h")
   712  
   713  	if len(hash) == 0 {
   714  		c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest)
   715  		utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey())
   716  		return
   717  	}
   718  
   719  	if subtle.ConstantTimeCompare([]byte(hash), []byte(app.GeneratePublicLinkHash(info.Id, *c.App.Config().FileSettings.PublicLinkSalt))) != 1 {
   720  		c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest)
   721  		utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey())
   722  		return
   723  	}
   724  
   725  	fileReader, err := c.App.FileReader(info.Path)
   726  	if err != nil {
   727  		c.Err = err
   728  		c.Err.StatusCode = http.StatusNotFound
   729  	}
   730  	defer fileReader.Close()
   731  
   732  	err = writeFileResponse(info.Name, info.MimeType, info.Size, fileReader, false, w, r)
   733  	if err != nil {
   734  		c.Err = err
   735  		return
   736  	}
   737  }
   738  
   739  func writeFileResponse(filename string, contentType string, contentSize int64, fileReader io.Reader, forceDownload bool, w http.ResponseWriter, r *http.Request) *model.AppError {
   740  	w.Header().Set("Cache-Control", "max-age=2592000, private")
   741  	w.Header().Set("X-Content-Type-Options", "nosniff")
   742  
   743  	if contentSize > 0 {
   744  		w.Header().Set("Content-Length", strconv.Itoa(int(contentSize)))
   745  	}
   746  
   747  	if contentType == "" {
   748  		contentType = "application/octet-stream"
   749  	} else {
   750  		for _, unsafeContentType := range UNSAFE_CONTENT_TYPES {
   751  			if strings.HasPrefix(contentType, unsafeContentType) {
   752  				contentType = "text/plain"
   753  				break
   754  			}
   755  		}
   756  	}
   757  
   758  	w.Header().Set("Content-Type", contentType)
   759  
   760  	var toDownload bool
   761  	if forceDownload {
   762  		toDownload = true
   763  	} else {
   764  		isMediaType := false
   765  
   766  		for _, mediaContentType := range MEDIA_CONTENT_TYPES {
   767  			if strings.HasPrefix(contentType, mediaContentType) {
   768  				isMediaType = true
   769  				break
   770  			}
   771  		}
   772  
   773  		toDownload = !isMediaType
   774  	}
   775  
   776  	filename = url.PathEscape(filename)
   777  
   778  	if toDownload {
   779  		w.Header().Set("Content-Disposition", "attachment;filename=\""+filename+"\"; filename*=UTF-8''"+filename)
   780  	} else {
   781  		w.Header().Set("Content-Disposition", "inline;filename=\""+filename+"\"; filename*=UTF-8''"+filename)
   782  	}
   783  
   784  	// prevent file links from being embedded in iframes
   785  	w.Header().Set("X-Frame-Options", "DENY")
   786  	w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'")
   787  
   788  	io.Copy(w, fileReader)
   789  
   790  	return nil
   791  }