github.com/coincircle/mattermost-server@v4.8.1-0.20180321182714-9d701c704416+incompatible/api/file.go (about)

     1  // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api
     5  
     6  import (
     7  	"net/http"
     8  	"net/url"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/gorilla/mux"
    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  	PREVIEW_IMAGE_TYPE   = "image/jpeg"
    20  	THUMBNAIL_IMAGE_TYPE = "image/jpeg"
    21  )
    22  
    23  var UNSAFE_CONTENT_TYPES = [...]string{
    24  	"application/javascript",
    25  	"application/ecmascript",
    26  	"text/javascript",
    27  	"text/ecmascript",
    28  	"application/x-javascript",
    29  	"text/html",
    30  }
    31  
    32  func (api *API) InitFile() {
    33  	api.BaseRoutes.TeamFiles.Handle("/upload", api.ApiUserRequired(uploadFile)).Methods("POST")
    34  
    35  	api.BaseRoutes.NeedFile.Handle("/get", api.ApiUserRequiredTrustRequester(getFile)).Methods("GET")
    36  	api.BaseRoutes.NeedFile.Handle("/get_thumbnail", api.ApiUserRequiredTrustRequester(getFileThumbnail)).Methods("GET")
    37  	api.BaseRoutes.NeedFile.Handle("/get_preview", api.ApiUserRequiredTrustRequester(getFilePreview)).Methods("GET")
    38  	api.BaseRoutes.NeedFile.Handle("/get_info", api.ApiUserRequired(getFileInfo)).Methods("GET")
    39  	api.BaseRoutes.NeedFile.Handle("/get_public_link", api.ApiUserRequired(getPublicLink)).Methods("GET")
    40  
    41  	api.BaseRoutes.Public.Handle("/files/{file_id:[A-Za-z0-9]+}/get", api.ApiAppHandlerTrustRequesterIndependent(getPublicFile)).Methods("GET")
    42  	api.BaseRoutes.Public.Handle("/files/get/{team_id:[A-Za-z0-9]+}/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:(?:[A-Za-z0-9]+/)?.+(?:\\.[A-Za-z0-9]{3,})?}", api.ApiAppHandlerTrustRequesterIndependent(getPublicFileOld)).Methods("GET")
    43  }
    44  
    45  func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
    46  	if !*c.App.Config().FileSettings.EnableFileAttachments {
    47  		c.Err = model.NewAppError("uploadFile", "api.file.attachments.disabled.app_error", nil, "", http.StatusNotImplemented)
    48  		return
    49  	}
    50  
    51  	if r.ContentLength > *c.App.Config().FileSettings.MaxFileSize {
    52  		c.Err = model.NewAppError("uploadFile", "api.file.upload_file.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge)
    53  		return
    54  	}
    55  
    56  	if err := r.ParseMultipartForm(*c.App.Config().FileSettings.MaxFileSize); err != nil {
    57  		http.Error(w, err.Error(), http.StatusInternalServerError)
    58  		return
    59  	}
    60  
    61  	m := r.MultipartForm
    62  
    63  	props := m.Value
    64  	if len(props["channel_id"]) == 0 {
    65  		c.SetInvalidParam("uploadFile", "channel_id")
    66  		return
    67  	}
    68  	channelId := props["channel_id"][0]
    69  	if len(channelId) == 0 {
    70  		c.SetInvalidParam("uploadFile", "channel_id")
    71  		return
    72  	}
    73  
    74  	if !c.App.SessionHasPermissionToChannel(c.Session, channelId, model.PERMISSION_UPLOAD_FILE) {
    75  		c.SetPermissionError(model.PERMISSION_UPLOAD_FILE)
    76  		return
    77  	}
    78  
    79  	resStruct, err := c.App.UploadMultipartFiles(c.TeamId, channelId, c.Session.UserId, m.File["files"], m.Value["client_ids"])
    80  	if err != nil {
    81  		c.Err = err
    82  		return
    83  	}
    84  
    85  	w.Write([]byte(resStruct.ToJson()))
    86  }
    87  
    88  func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
    89  	info, err := getFileInfoForRequest(c, r, true)
    90  	if err != nil {
    91  		c.Err = err
    92  		return
    93  	}
    94  
    95  	if data, err := c.App.ReadFile(info.Path); err != nil {
    96  		c.Err = err
    97  		c.Err.StatusCode = http.StatusNotFound
    98  	} else if err := writeFileResponse(info.Name, info.MimeType, data, w, r); err != nil {
    99  		c.Err = err
   100  		return
   101  	}
   102  }
   103  
   104  func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) {
   105  	info, err := getFileInfoForRequest(c, r, true)
   106  	if err != nil {
   107  		c.Err = err
   108  		return
   109  	}
   110  
   111  	if info.ThumbnailPath == "" {
   112  		c.Err = model.NewAppError("getFileThumbnail", "api.file.get_file_thumbnail.no_thumbnail.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
   113  		return
   114  	}
   115  
   116  	if data, err := c.App.ReadFile(info.ThumbnailPath); err != nil {
   117  		c.Err = err
   118  		c.Err.StatusCode = http.StatusNotFound
   119  	} else if err := writeFileResponse(info.Name, THUMBNAIL_IMAGE_TYPE, data, w, r); err != nil {
   120  		c.Err = err
   121  		return
   122  	}
   123  }
   124  
   125  func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) {
   126  	info, err := getFileInfoForRequest(c, r, true)
   127  	if err != nil {
   128  		c.Err = err
   129  		return
   130  	}
   131  
   132  	if info.PreviewPath == "" {
   133  		c.Err = model.NewAppError("getFilePreview", "api.file.get_file_preview.no_preview.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
   134  		return
   135  	}
   136  
   137  	if data, err := c.App.ReadFile(info.PreviewPath); err != nil {
   138  		c.Err = err
   139  		c.Err.StatusCode = http.StatusNotFound
   140  	} else if err := writeFileResponse(info.Name, PREVIEW_IMAGE_TYPE, data, w, r); err != nil {
   141  		c.Err = err
   142  		return
   143  	}
   144  }
   145  
   146  func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) {
   147  	info, err := getFileInfoForRequest(c, r, true)
   148  	if err != nil {
   149  		c.Err = err
   150  		return
   151  	}
   152  
   153  	w.Header().Set("Cache-Control", "max-age=2592000, public")
   154  
   155  	w.Write([]byte(info.ToJson()))
   156  }
   157  
   158  func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) {
   159  	if !c.App.Config().FileSettings.EnablePublicLink {
   160  		c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_disabled.app_error", nil, "", http.StatusNotImplemented)
   161  		return
   162  	}
   163  
   164  	info, err := getFileInfoForRequest(c, r, false)
   165  	if err != nil {
   166  		c.Err = err
   167  		return
   168  	}
   169  
   170  	hash := r.URL.Query().Get("h")
   171  
   172  	if len(hash) > 0 {
   173  		correctHash := app.GeneratePublicLinkHash(info.Id, *c.App.Config().FileSettings.PublicLinkSalt)
   174  
   175  		if hash != correctHash {
   176  			c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest)
   177  			utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey())
   178  			return
   179  		}
   180  	} else {
   181  		c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest)
   182  		utils.RenderWebAppError(w, r, c.Err, c.App.AsymmetricSigningKey())
   183  		return
   184  	}
   185  
   186  	if data, err := c.App.ReadFile(info.Path); err != nil {
   187  		c.Err = err
   188  		c.Err.StatusCode = http.StatusNotFound
   189  	} else if err := writeFileResponse(info.Name, info.MimeType, data, w, r); err != nil {
   190  		c.Err = err
   191  		return
   192  	}
   193  }
   194  
   195  func getFileInfoForRequest(c *Context, r *http.Request, requireFileVisible bool) (*model.FileInfo, *model.AppError) {
   196  	if len(*c.App.Config().FileSettings.DriverName) == 0 {
   197  		return nil, model.NewAppError("getFileInfoForRequest", "api.file.get_info_for_request.storage.app_error", nil, "", http.StatusNotImplemented)
   198  	}
   199  
   200  	params := mux.Vars(r)
   201  
   202  	fileId := params["file_id"]
   203  	if len(fileId) != 26 {
   204  		return nil, NewInvalidParamError("getFileInfoForRequest", "file_id")
   205  	}
   206  
   207  	info, err := c.App.GetFileInfo(fileId)
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	// only let users access files visible in a channel, unless they're the one who uploaded the file
   213  	if info.CreatorId != c.Session.UserId {
   214  		if len(info.PostId) == 0 {
   215  			err := model.NewAppError("getFileInfoForRequest", "api.file.get_file_info_for_request.no_post.app_error", nil, "file_id="+fileId, http.StatusBadRequest)
   216  			return nil, err
   217  		}
   218  
   219  		if requireFileVisible {
   220  			if !c.App.SessionHasPermissionToChannelByPost(c.Session, info.PostId, model.PERMISSION_READ_CHANNEL) {
   221  				c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
   222  				return nil, c.Err
   223  			}
   224  		}
   225  	}
   226  
   227  	return info, nil
   228  }
   229  
   230  func getPublicFileOld(c *Context, w http.ResponseWriter, r *http.Request) {
   231  	if len(*c.App.Config().FileSettings.DriverName) == 0 {
   232  		c.Err = model.NewAppError("getPublicFile", "api.file.get_public_file_old.storage.app_error", nil, "", http.StatusNotImplemented)
   233  		return
   234  	} else if !c.App.Config().FileSettings.EnablePublicLink {
   235  		c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_disabled.app_error", nil, "", http.StatusNotImplemented)
   236  		return
   237  	}
   238  
   239  	params := mux.Vars(r)
   240  
   241  	teamId := params["team_id"]
   242  	channelId := params["channel_id"]
   243  	userId := params["user_id"]
   244  	filename := params["filename"]
   245  
   246  	hash := r.URL.Query().Get("h")
   247  
   248  	if len(hash) > 0 {
   249  		correctHash := app.GeneratePublicLinkHash(filename, *c.App.Config().FileSettings.PublicLinkSalt)
   250  
   251  		if hash != correctHash {
   252  			c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest)
   253  			http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect)
   254  			return
   255  		}
   256  	} else {
   257  		c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest)
   258  		http.Redirect(w, r, c.GetSiteURLHeader()+"/error?message="+utils.T(c.Err.Message), http.StatusTemporaryRedirect)
   259  		return
   260  	}
   261  
   262  	path := "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
   263  
   264  	var info *model.FileInfo
   265  	if result := <-c.App.Srv.Store.FileInfo().GetByPath(path); result.Err != nil {
   266  		c.Err = result.Err
   267  		return
   268  	} else {
   269  		info = result.Data.(*model.FileInfo)
   270  	}
   271  
   272  	if len(info.PostId) == 0 {
   273  		c.Err = model.NewAppError("getPublicFileOld", "api.file.get_public_file_old.no_post.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
   274  		return
   275  	}
   276  
   277  	if data, err := c.App.ReadFile(info.Path); err != nil {
   278  		c.Err = err
   279  		c.Err.StatusCode = http.StatusNotFound
   280  	} else if err := writeFileResponse(info.Name, info.MimeType, data, w, r); err != nil {
   281  		c.Err = err
   282  		return
   283  	}
   284  }
   285  
   286  func writeFileResponse(filename string, contentType string, bytes []byte, w http.ResponseWriter, r *http.Request) *model.AppError {
   287  	w.Header().Set("Cache-Control", "max-age=2592000, private")
   288  	w.Header().Set("Content-Length", strconv.Itoa(len(bytes)))
   289  	w.Header().Set("X-Content-Type-Options", "nosniff")
   290  
   291  	if contentType == "" {
   292  		contentType = "application/octet-stream"
   293  	} else {
   294  		for _, unsafeContentType := range UNSAFE_CONTENT_TYPES {
   295  			if strings.HasPrefix(contentType, unsafeContentType) {
   296  				contentType = "text/plain"
   297  				break
   298  			}
   299  		}
   300  	}
   301  
   302  	w.Header().Set("Content-Type", contentType)
   303  
   304  	w.Header().Set("Content-Disposition", "attachment;filename=\""+filename+"\"; filename*=UTF-8''"+url.QueryEscape(filename))
   305  
   306  	// prevent file links from being embedded in iframes
   307  	w.Header().Set("X-Frame-Options", "DENY")
   308  	w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'")
   309  
   310  	w.Write(bytes)
   311  
   312  	return nil
   313  }
   314  
   315  func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
   316  	if !c.App.Config().FileSettings.EnablePublicLink {
   317  		c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented)
   318  		return
   319  	}
   320  
   321  	info, err := getFileInfoForRequest(c, r, true)
   322  	if err != nil {
   323  		c.Err = err
   324  		return
   325  	}
   326  
   327  	if len(info.PostId) == 0 {
   328  		c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.no_post.app_error", nil, "file_id="+info.Id, http.StatusBadRequest)
   329  		return
   330  	}
   331  
   332  	w.Write([]byte(model.StringToJson(c.App.GeneratePublicLinkV3(c.GetSiteURLHeader(), info))))
   333  }