github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+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 }