github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/api4/file.go (about)

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