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