github.com/psyb0t/mattermost-server@v4.6.1-0.20180125161845-5503a1351abf+incompatible/app/file.go (about) 1 // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package app 5 6 import ( 7 "bytes" 8 "crypto/sha256" 9 "encoding/base64" 10 "fmt" 11 "image" 12 "image/color" 13 "image/draw" 14 _ "image/gif" 15 "image/jpeg" 16 "io" 17 "mime/multipart" 18 "net/http" 19 "net/url" 20 "path/filepath" 21 "strings" 22 "sync" 23 "time" 24 25 l4g "github.com/alecthomas/log4go" 26 "github.com/disintegration/imaging" 27 "github.com/rwcarlsen/goexif/exif" 28 _ "golang.org/x/image/bmp" 29 30 "github.com/mattermost/mattermost-server/model" 31 "github.com/mattermost/mattermost-server/utils" 32 ) 33 34 const ( 35 /* 36 EXIF Image Orientations 37 1 2 3 4 5 6 7 8 38 39 888888 888888 88 88 8888888888 88 88 8888888888 40 88 88 88 88 88 88 88 88 88 88 88 88 41 8888 8888 8888 8888 88 8888888888 8888888888 88 42 88 88 88 88 43 88 88 888888 888888 44 */ 45 Upright = 1 46 UprightMirrored = 2 47 UpsideDown = 3 48 UpsideDownMirrored = 4 49 RotatedCWMirrored = 5 50 RotatedCCW = 6 51 RotatedCCWMirrored = 7 52 RotatedCW = 8 53 54 MaxImageSize = 6048 * 4032 // 24 megapixels, roughly 36MB as a raw image 55 IMAGE_THUMBNAIL_PIXEL_WIDTH = 120 56 IMAGE_THUMBNAIL_PIXEL_HEIGHT = 100 57 IMAGE_PREVIEW_PIXEL_WIDTH = 1024 58 ) 59 60 func (a *App) FileBackend() (utils.FileBackend, *model.AppError) { 61 return utils.NewFileBackend(&a.Config().FileSettings) 62 } 63 64 func (a *App) ReadFile(path string) ([]byte, *model.AppError) { 65 backend, err := a.FileBackend() 66 if err != nil { 67 return nil, err 68 } 69 return backend.ReadFile(path) 70 } 71 72 func (a *App) MoveFile(oldPath, newPath string) *model.AppError { 73 backend, err := a.FileBackend() 74 if err != nil { 75 return err 76 } 77 return backend.MoveFile(oldPath, newPath) 78 } 79 80 func (a *App) WriteFile(f []byte, path string) *model.AppError { 81 backend, err := a.FileBackend() 82 if err != nil { 83 return err 84 } 85 return backend.WriteFile(f, path) 86 } 87 88 func (a *App) RemoveFile(path string) *model.AppError { 89 backend, err := a.FileBackend() 90 if err != nil { 91 return err 92 } 93 return backend.RemoveFile(path) 94 } 95 96 func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename string) *model.FileInfo { 97 // Find the path from the Filename of the form /{channelId}/{userId}/{uid}/{nameWithExtension} 98 split := strings.SplitN(filename, "/", 5) 99 if len(split) < 5 { 100 l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.unexpected_filename.error"), post.Id, filename) 101 return nil 102 } 103 104 channelId := split[1] 105 userId := split[2] 106 oldId := split[3] 107 name, _ := url.QueryUnescape(split[4]) 108 109 if split[0] != "" || split[1] != post.ChannelId || split[2] != post.UserId || strings.Contains(split[4], "/") { 110 l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.mismatched_filename.warn"), post.Id, post.ChannelId, post.UserId, filename) 111 } 112 113 pathPrefix := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/", teamId, channelId, userId, oldId) 114 path := pathPrefix + name 115 116 // Open the file and populate the fields of the FileInfo 117 var info *model.FileInfo 118 if data, err := a.ReadFile(path); err != nil { 119 l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.file_not_found.error"), post.Id, filename, path, err) 120 return nil 121 } else { 122 var err *model.AppError 123 info, err = model.GetInfoForBytes(name, data) 124 if err != nil { 125 l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.info.app_error"), post.Id, filename, err) 126 } 127 } 128 129 // Generate a new ID because with the old system, you could very rarely get multiple posts referencing the same file 130 info.Id = model.NewId() 131 info.CreatorId = post.UserId 132 info.PostId = post.Id 133 info.CreateAt = post.CreateAt 134 info.UpdateAt = post.UpdateAt 135 info.Path = path 136 137 if info.IsImage() { 138 nameWithoutExtension := name[:strings.LastIndex(name, ".")] 139 info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg" 140 info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg" 141 } 142 143 return info 144 } 145 146 func (a *App) FindTeamIdForFilename(post *model.Post, filename string) string { 147 split := strings.SplitN(filename, "/", 5) 148 id := split[3] 149 name, _ := url.QueryUnescape(split[4]) 150 151 // This post is in a direct channel so we need to figure out what team the files are stored under. 152 if result := <-a.Srv.Store.Team().GetTeamsByUserId(post.UserId); result.Err != nil { 153 l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.teams.app_error"), post.Id, result.Err) 154 } else if teams := result.Data.([]*model.Team); len(teams) == 1 { 155 // The user has only one team so the post must've been sent from it 156 return teams[0].Id 157 } else { 158 for _, team := range teams { 159 path := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/%s", team.Id, post.ChannelId, post.UserId, id, name) 160 if _, err := a.ReadFile(path); err == nil { 161 // Found the team that this file was posted from 162 return team.Id 163 } 164 } 165 } 166 167 return "" 168 } 169 170 var fileMigrationLock sync.Mutex 171 172 // Creates and stores FileInfos for a post created before the FileInfos table existed. 173 func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo { 174 if len(post.Filenames) == 0 { 175 l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.no_filenames.warn"), post.Id) 176 return []*model.FileInfo{} 177 } 178 179 cchan := a.Srv.Store.Channel().Get(post.ChannelId, true) 180 181 // There's a weird bug that rarely happens where a post ends up with duplicate Filenames so remove those 182 filenames := utils.RemoveDuplicatesFromStringArray(post.Filenames) 183 184 var channel *model.Channel 185 if result := <-cchan; result.Err != nil { 186 l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.channel.app_error"), post.Id, post.ChannelId, result.Err) 187 return []*model.FileInfo{} 188 } else { 189 channel = result.Data.(*model.Channel) 190 } 191 192 // Find the team that was used to make this post since its part of the file path that isn't saved in the Filename 193 var teamId string 194 if channel.TeamId == "" { 195 // This post was made in a cross-team DM channel so we need to find where its files were saved 196 teamId = a.FindTeamIdForFilename(post, filenames[0]) 197 } else { 198 teamId = channel.TeamId 199 } 200 201 // Create FileInfo objects for this post 202 infos := make([]*model.FileInfo, 0, len(filenames)) 203 if teamId == "" { 204 l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.team_id.error"), post.Id, filenames) 205 } else { 206 for _, filename := range filenames { 207 info := a.GetInfoForFilename(post, teamId, filename) 208 if info == nil { 209 continue 210 } 211 212 infos = append(infos, info) 213 } 214 } 215 216 // Lock to prevent only one migration thread from trying to update the post at once, preventing duplicate FileInfos from being created 217 fileMigrationLock.Lock() 218 defer fileMigrationLock.Unlock() 219 220 if result := <-a.Srv.Store.Post().Get(post.Id); result.Err != nil { 221 l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.get_post_again.app_error"), post.Id, result.Err) 222 return []*model.FileInfo{} 223 } else if newPost := result.Data.(*model.PostList).Posts[post.Id]; len(newPost.Filenames) != len(post.Filenames) { 224 // Another thread has already created FileInfos for this post, so just return those 225 if result := <-a.Srv.Store.FileInfo().GetForPost(post.Id, true, false); result.Err != nil { 226 l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.get_post_file_infos_again.app_error"), post.Id, result.Err) 227 return []*model.FileInfo{} 228 } else { 229 l4g.Debug(utils.T("api.file.migrate_filenames_to_file_infos.not_migrating_post.debug"), post.Id) 230 return result.Data.([]*model.FileInfo) 231 } 232 } 233 234 l4g.Debug(utils.T("api.file.migrate_filenames_to_file_infos.migrating_post.debug"), post.Id) 235 236 savedInfos := make([]*model.FileInfo, 0, len(infos)) 237 fileIds := make([]string, 0, len(filenames)) 238 for _, info := range infos { 239 if result := <-a.Srv.Store.FileInfo().Save(info); result.Err != nil { 240 l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.save_file_info.app_error"), post.Id, info.Id, info.Path, result.Err) 241 continue 242 } 243 244 savedInfos = append(savedInfos, info) 245 fileIds = append(fileIds, info.Id) 246 } 247 248 // Copy and save the updated post 249 newPost := &model.Post{} 250 *newPost = *post 251 252 newPost.Filenames = []string{} 253 newPost.FileIds = fileIds 254 255 // Update Posts to clear Filenames and set FileIds 256 if result := <-a.Srv.Store.Post().Update(newPost, post); result.Err != nil { 257 l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.save_post.app_error"), post.Id, newPost.FileIds, post.Filenames, result.Err) 258 return []*model.FileInfo{} 259 } else { 260 return savedInfos 261 } 262 } 263 264 func (a *App) GeneratePublicLink(siteURL string, info *model.FileInfo) string { 265 hash := GeneratePublicLinkHash(info.Id, *a.Config().FileSettings.PublicLinkSalt) 266 return fmt.Sprintf("%s/files/%v/public?h=%s", siteURL, info.Id, hash) 267 } 268 269 func (a *App) GeneratePublicLinkV3(siteURL string, info *model.FileInfo) string { 270 hash := GeneratePublicLinkHash(info.Id, *a.Config().FileSettings.PublicLinkSalt) 271 return fmt.Sprintf("%s%s/public/files/%v/get?h=%s", siteURL, model.API_URL_SUFFIX_V3, info.Id, hash) 272 } 273 274 func GeneratePublicLinkHash(fileId, salt string) string { 275 hash := sha256.New() 276 hash.Write([]byte(salt)) 277 hash.Write([]byte(fileId)) 278 279 return base64.RawURLEncoding.EncodeToString(hash.Sum(nil)) 280 } 281 282 func (a *App) UploadFiles(teamId string, channelId string, userId string, fileHeaders []*multipart.FileHeader, clientIds []string) (*model.FileUploadResponse, *model.AppError) { 283 if len(*a.Config().FileSettings.DriverName) == 0 { 284 return nil, model.NewAppError("uploadFile", "api.file.upload_file.storage.app_error", nil, "", http.StatusNotImplemented) 285 } 286 287 resStruct := &model.FileUploadResponse{ 288 FileInfos: []*model.FileInfo{}, 289 ClientIds: []string{}, 290 } 291 292 previewPathList := []string{} 293 thumbnailPathList := []string{} 294 imageDataList := [][]byte{} 295 296 for i, fileHeader := range fileHeaders { 297 file, fileErr := fileHeader.Open() 298 if fileErr != nil { 299 return nil, model.NewAppError("UploadFiles", "api.file.upload_file.bad_parse.app_error", nil, fileErr.Error(), http.StatusBadRequest) 300 } 301 defer file.Close() 302 303 buf := bytes.NewBuffer(nil) 304 io.Copy(buf, file) 305 data := buf.Bytes() 306 307 info, err := a.DoUploadFile(time.Now(), teamId, channelId, userId, fileHeader.Filename, data) 308 if err != nil { 309 return nil, err 310 } 311 312 if info.PreviewPath != "" || info.ThumbnailPath != "" { 313 previewPathList = append(previewPathList, info.PreviewPath) 314 thumbnailPathList = append(thumbnailPathList, info.ThumbnailPath) 315 imageDataList = append(imageDataList, data) 316 } 317 318 resStruct.FileInfos = append(resStruct.FileInfos, info) 319 320 if len(clientIds) > 0 { 321 resStruct.ClientIds = append(resStruct.ClientIds, clientIds[i]) 322 } 323 } 324 325 a.HandleImages(previewPathList, thumbnailPathList, imageDataList) 326 327 return resStruct, nil 328 } 329 330 func (a *App) DoUploadFile(now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, *model.AppError) { 331 filename := filepath.Base(rawFilename) 332 teamId := filepath.Base(rawTeamId) 333 channelId := filepath.Base(rawChannelId) 334 userId := filepath.Base(rawUserId) 335 336 info, err := model.GetInfoForBytes(filename, data) 337 if err != nil { 338 err.StatusCode = http.StatusBadRequest 339 return nil, err 340 } 341 342 info.Id = model.NewId() 343 info.CreatorId = userId 344 345 pathPrefix := now.Format("20060102") + "/teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + info.Id + "/" 346 info.Path = pathPrefix + filename 347 348 if info.IsImage() { 349 // Check dimensions before loading the whole thing into memory later on 350 if info.Width*info.Height > MaxImageSize { 351 err := model.NewAppError("uploadFile", "api.file.upload_file.large_image.app_error", map[string]interface{}{"Filename": filename}, "", http.StatusBadRequest) 352 return nil, err 353 } 354 355 nameWithoutExtension := filename[:strings.LastIndex(filename, ".")] 356 info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg" 357 info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg" 358 } 359 360 if err := a.WriteFile(data, info.Path); err != nil { 361 return nil, err 362 } 363 364 if result := <-a.Srv.Store.FileInfo().Save(info); result.Err != nil { 365 return nil, result.Err 366 } 367 368 return info, nil 369 } 370 371 func (a *App) HandleImages(previewPathList []string, thumbnailPathList []string, fileData [][]byte) { 372 wg := new(sync.WaitGroup) 373 374 for i := range fileData { 375 img, width, height := prepareImage(fileData[i]) 376 if img != nil { 377 wg.Add(2) 378 go func(img *image.Image, path string, width int, height int) { 379 defer wg.Done() 380 a.generateThumbnailImage(*img, path, width, height) 381 }(img, thumbnailPathList[i], width, height) 382 383 go func(img *image.Image, path string, width int) { 384 defer wg.Done() 385 a.generatePreviewImage(*img, path, width) 386 }(img, previewPathList[i], width) 387 } 388 } 389 wg.Wait() 390 } 391 392 func prepareImage(fileData []byte) (*image.Image, int, int) { 393 // Decode image bytes into Image object 394 img, imgType, err := image.Decode(bytes.NewReader(fileData)) 395 if err != nil { 396 l4g.Error(utils.T("api.file.handle_images_forget.decode.error"), err) 397 return nil, 0, 0 398 } 399 400 width := img.Bounds().Dx() 401 height := img.Bounds().Dy() 402 403 // Fill in the background of a potentially-transparent png file as white 404 if imgType == "png" { 405 dst := image.NewRGBA(img.Bounds()) 406 draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src) 407 draw.Draw(dst, dst.Bounds(), img, img.Bounds().Min, draw.Over) 408 img = dst 409 } 410 411 // Flip the image to be upright 412 orientation, _ := getImageOrientation(bytes.NewReader(fileData)) 413 img = makeImageUpright(img, orientation) 414 415 return &img, width, height 416 } 417 418 func makeImageUpright(img image.Image, orientation int) image.Image { 419 switch orientation { 420 case UprightMirrored: 421 return imaging.FlipH(img) 422 case UpsideDown: 423 return imaging.Rotate180(img) 424 case UpsideDownMirrored: 425 return imaging.FlipV(img) 426 case RotatedCWMirrored: 427 return imaging.Transpose(img) 428 case RotatedCCW: 429 return imaging.Rotate270(img) 430 case RotatedCCWMirrored: 431 return imaging.Transverse(img) 432 case RotatedCW: 433 return imaging.Rotate90(img) 434 default: 435 return img 436 } 437 } 438 439 func getImageOrientation(input io.Reader) (int, error) { 440 if exifData, err := exif.Decode(input); err != nil { 441 return Upright, err 442 } else { 443 if tag, err := exifData.Get("Orientation"); err != nil { 444 return Upright, err 445 } else { 446 orientation, err := tag.Int(0) 447 if err != nil { 448 return Upright, err 449 } else { 450 return orientation, nil 451 } 452 } 453 } 454 } 455 456 func (a *App) generateThumbnailImage(img image.Image, thumbnailPath string, width int, height int) { 457 thumbWidth := float64(IMAGE_THUMBNAIL_PIXEL_WIDTH) 458 thumbHeight := float64(IMAGE_THUMBNAIL_PIXEL_HEIGHT) 459 imgWidth := float64(width) 460 imgHeight := float64(height) 461 462 var thumbnail image.Image 463 if imgHeight < IMAGE_THUMBNAIL_PIXEL_HEIGHT && imgWidth < thumbWidth { 464 thumbnail = img 465 } else if imgHeight/imgWidth < thumbHeight/thumbWidth { 466 thumbnail = imaging.Resize(img, 0, IMAGE_THUMBNAIL_PIXEL_HEIGHT, imaging.Lanczos) 467 } else { 468 thumbnail = imaging.Resize(img, IMAGE_THUMBNAIL_PIXEL_WIDTH, 0, imaging.Lanczos) 469 } 470 471 buf := new(bytes.Buffer) 472 if err := jpeg.Encode(buf, thumbnail, &jpeg.Options{Quality: 90}); err != nil { 473 l4g.Error(utils.T("api.file.handle_images_forget.encode_jpeg.error"), thumbnailPath, err) 474 return 475 } 476 477 if err := a.WriteFile(buf.Bytes(), thumbnailPath); err != nil { 478 l4g.Error(utils.T("api.file.handle_images_forget.upload_thumb.error"), thumbnailPath, err) 479 return 480 } 481 } 482 483 func (a *App) generatePreviewImage(img image.Image, previewPath string, width int) { 484 var preview image.Image 485 486 if width > IMAGE_PREVIEW_PIXEL_WIDTH { 487 preview = imaging.Resize(img, IMAGE_PREVIEW_PIXEL_WIDTH, 0, imaging.Lanczos) 488 } else { 489 preview = img 490 } 491 492 buf := new(bytes.Buffer) 493 494 if err := jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90}); err != nil { 495 l4g.Error(utils.T("api.file.handle_images_forget.encode_preview.error"), previewPath, err) 496 return 497 } 498 499 if err := a.WriteFile(buf.Bytes(), previewPath); err != nil { 500 l4g.Error(utils.T("api.file.handle_images_forget.upload_preview.error"), previewPath, err) 501 return 502 } 503 } 504 505 func (a *App) GetFileInfo(fileId string) (*model.FileInfo, *model.AppError) { 506 if result := <-a.Srv.Store.FileInfo().Get(fileId); result.Err != nil { 507 return nil, result.Err 508 } else { 509 return result.Data.(*model.FileInfo), nil 510 } 511 }