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