github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+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 "github.com/disintegration/imaging" 26 "github.com/rwcarlsen/goexif/exif" 27 _ "golang.org/x/image/bmp" 28 29 "github.com/mattermost/mattermost-server/mlog" 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 license := a.License() 62 return utils.NewFileBackend(&a.Config().FileSettings, license != nil && *license.Features.Compliance) 63 } 64 65 func (a *App) ReadFile(path string) ([]byte, *model.AppError) { 66 backend, err := a.FileBackend() 67 if err != nil { 68 return nil, err 69 } 70 return backend.ReadFile(path) 71 } 72 73 func (a *App) MoveFile(oldPath, newPath string) *model.AppError { 74 backend, err := a.FileBackend() 75 if err != nil { 76 return err 77 } 78 return backend.MoveFile(oldPath, newPath) 79 } 80 81 func (a *App) WriteFile(fr io.Reader, path string) (int64, *model.AppError) { 82 backend, err := a.FileBackend() 83 if err != nil { 84 return 0, err 85 } 86 87 return backend.WriteFile(fr, path) 88 } 89 90 func (a *App) RemoveFile(path string) *model.AppError { 91 backend, err := a.FileBackend() 92 if err != nil { 93 return err 94 } 95 return backend.RemoveFile(path) 96 } 97 98 func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename string) *model.FileInfo { 99 // Find the path from the Filename of the form /{channelId}/{userId}/{uid}/{nameWithExtension} 100 split := strings.SplitN(filename, "/", 5) 101 if len(split) < 5 { 102 mlog.Error("Unable to decipher filename when migrating post to use FileInfos", mlog.String("post_id", post.Id), mlog.String("filename", filename)) 103 return nil 104 } 105 106 channelId := split[1] 107 userId := split[2] 108 oldId := split[3] 109 name, _ := url.QueryUnescape(split[4]) 110 111 if split[0] != "" || split[1] != post.ChannelId || split[2] != post.UserId || strings.Contains(split[4], "/") { 112 mlog.Warn( 113 "Found an unusual filename when migrating post to use FileInfos", 114 mlog.String("post_id", post.Id), 115 mlog.String("channel_id", post.ChannelId), 116 mlog.String("user_id", post.UserId), 117 mlog.String("filename", filename), 118 ) 119 } 120 121 pathPrefix := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/", teamId, channelId, userId, oldId) 122 path := pathPrefix + name 123 124 // Open the file and populate the fields of the FileInfo 125 var info *model.FileInfo 126 if data, err := a.ReadFile(path); err != nil { 127 mlog.Error( 128 fmt.Sprintf("File not found when migrating post to use FileInfos, err=%v", err), 129 mlog.String("post_id", post.Id), 130 mlog.String("filename", filename), 131 mlog.String("path", path), 132 ) 133 return nil 134 } else { 135 var err *model.AppError 136 info, err = model.GetInfoForBytes(name, data) 137 if err != nil { 138 mlog.Warn( 139 fmt.Sprintf("Unable to fully decode file info when migrating post to use FileInfos, err=%v", err), 140 mlog.String("post_id", post.Id), 141 mlog.String("filename", filename), 142 ) 143 } 144 } 145 146 // Generate a new ID because with the old system, you could very rarely get multiple posts referencing the same file 147 info.Id = model.NewId() 148 info.CreatorId = post.UserId 149 info.PostId = post.Id 150 info.CreateAt = post.CreateAt 151 info.UpdateAt = post.UpdateAt 152 info.Path = path 153 154 if info.IsImage() { 155 nameWithoutExtension := name[:strings.LastIndex(name, ".")] 156 info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg" 157 info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg" 158 } 159 160 return info 161 } 162 163 func (a *App) FindTeamIdForFilename(post *model.Post, filename string) string { 164 split := strings.SplitN(filename, "/", 5) 165 id := split[3] 166 name, _ := url.QueryUnescape(split[4]) 167 168 // This post is in a direct channel so we need to figure out what team the files are stored under. 169 if result := <-a.Srv.Store.Team().GetTeamsByUserId(post.UserId); result.Err != nil { 170 mlog.Error(fmt.Sprintf("Unable to get teams when migrating post to use FileInfo, err=%v", result.Err), mlog.String("post_id", post.Id)) 171 } else if teams := result.Data.([]*model.Team); len(teams) == 1 { 172 // The user has only one team so the post must've been sent from it 173 return teams[0].Id 174 } else { 175 for _, team := range teams { 176 path := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/%s", team.Id, post.ChannelId, post.UserId, id, name) 177 if _, err := a.ReadFile(path); err == nil { 178 // Found the team that this file was posted from 179 return team.Id 180 } 181 } 182 } 183 184 return "" 185 } 186 187 var fileMigrationLock sync.Mutex 188 189 // Creates and stores FileInfos for a post created before the FileInfos table existed. 190 func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo { 191 if len(post.Filenames) == 0 { 192 mlog.Warn("Unable to migrate post to use FileInfos with an empty Filenames field", mlog.String("post_id", post.Id)) 193 return []*model.FileInfo{} 194 } 195 196 cchan := a.Srv.Store.Channel().Get(post.ChannelId, true) 197 198 // There's a weird bug that rarely happens where a post ends up with duplicate Filenames so remove those 199 filenames := utils.RemoveDuplicatesFromStringArray(post.Filenames) 200 201 var channel *model.Channel 202 if result := <-cchan; result.Err != nil { 203 mlog.Error( 204 fmt.Sprintf("Unable to get channel when migrating post to use FileInfos, err=%v", result.Err), 205 mlog.String("post_id", post.Id), 206 mlog.String("channel_id", post.ChannelId), 207 ) 208 return []*model.FileInfo{} 209 } else { 210 channel = result.Data.(*model.Channel) 211 } 212 213 // Find the team that was used to make this post since its part of the file path that isn't saved in the Filename 214 var teamId string 215 if channel.TeamId == "" { 216 // This post was made in a cross-team DM channel so we need to find where its files were saved 217 teamId = a.FindTeamIdForFilename(post, filenames[0]) 218 } else { 219 teamId = channel.TeamId 220 } 221 222 // Create FileInfo objects for this post 223 infos := make([]*model.FileInfo, 0, len(filenames)) 224 if teamId == "" { 225 mlog.Error( 226 fmt.Sprintf("Unable to find team id for files when migrating post to use FileInfos, filenames=%v", filenames), 227 mlog.String("post_id", post.Id), 228 ) 229 } else { 230 for _, filename := range filenames { 231 info := a.GetInfoForFilename(post, teamId, filename) 232 if info == nil { 233 continue 234 } 235 236 infos = append(infos, info) 237 } 238 } 239 240 // Lock to prevent only one migration thread from trying to update the post at once, preventing duplicate FileInfos from being created 241 fileMigrationLock.Lock() 242 defer fileMigrationLock.Unlock() 243 244 if result := <-a.Srv.Store.Post().Get(post.Id); result.Err != nil { 245 mlog.Error(fmt.Sprintf("Unable to get post when migrating post to use FileInfos, err=%v", result.Err), mlog.String("post_id", post.Id)) 246 return []*model.FileInfo{} 247 } else if newPost := result.Data.(*model.PostList).Posts[post.Id]; len(newPost.Filenames) != len(post.Filenames) { 248 // Another thread has already created FileInfos for this post, so just return those 249 if result := <-a.Srv.Store.FileInfo().GetForPost(post.Id, true, false); result.Err != nil { 250 mlog.Error(fmt.Sprintf("Unable to get FileInfos for migrated post, err=%v", result.Err), mlog.String("post_id", post.Id)) 251 return []*model.FileInfo{} 252 } else { 253 mlog.Debug("Post already migrated to use FileInfos", mlog.String("post_id", post.Id)) 254 return result.Data.([]*model.FileInfo) 255 } 256 } 257 258 mlog.Debug("Migrating post to use FileInfos", mlog.String("post_id", post.Id)) 259 260 savedInfos := make([]*model.FileInfo, 0, len(infos)) 261 fileIds := make([]string, 0, len(filenames)) 262 for _, info := range infos { 263 if result := <-a.Srv.Store.FileInfo().Save(info); result.Err != nil { 264 mlog.Error( 265 fmt.Sprintf("Unable to save file info when migrating post to use FileInfos, err=%v", result.Err), 266 mlog.String("post_id", post.Id), 267 mlog.String("file_info_id", info.Id), 268 mlog.String("file_info_path", info.Path), 269 ) 270 continue 271 } 272 273 savedInfos = append(savedInfos, info) 274 fileIds = append(fileIds, info.Id) 275 } 276 277 // Copy and save the updated post 278 newPost := &model.Post{} 279 *newPost = *post 280 281 newPost.Filenames = []string{} 282 newPost.FileIds = fileIds 283 284 // Update Posts to clear Filenames and set FileIds 285 if result := <-a.Srv.Store.Post().Update(newPost, post); result.Err != nil { 286 mlog.Error(fmt.Sprintf("Unable to save migrated post when migrating to use FileInfos, new_file_ids=%v, old_filenames=%v, err=%v", newPost.FileIds, post.Filenames, result.Err), mlog.String("post_id", post.Id)) 287 return []*model.FileInfo{} 288 } else { 289 return savedInfos 290 } 291 } 292 293 func (a *App) GeneratePublicLink(siteURL string, info *model.FileInfo) string { 294 hash := GeneratePublicLinkHash(info.Id, *a.Config().FileSettings.PublicLinkSalt) 295 return fmt.Sprintf("%s/files/%v/public?h=%s", siteURL, info.Id, hash) 296 } 297 298 func (a *App) GeneratePublicLinkV3(siteURL string, info *model.FileInfo) string { 299 hash := GeneratePublicLinkHash(info.Id, *a.Config().FileSettings.PublicLinkSalt) 300 return fmt.Sprintf("%s%s/public/files/%v/get?h=%s", siteURL, model.API_URL_SUFFIX_V3, info.Id, hash) 301 } 302 303 func GeneratePublicLinkHash(fileId, salt string) string { 304 hash := sha256.New() 305 hash.Write([]byte(salt)) 306 hash.Write([]byte(fileId)) 307 308 return base64.RawURLEncoding.EncodeToString(hash.Sum(nil)) 309 } 310 311 func (a *App) UploadMultipartFiles(teamId string, channelId string, userId string, fileHeaders []*multipart.FileHeader, clientIds []string) (*model.FileUploadResponse, *model.AppError) { 312 files := make([]io.ReadCloser, len(fileHeaders)) 313 filenames := make([]string, len(fileHeaders)) 314 315 for i, fileHeader := range fileHeaders { 316 file, fileErr := fileHeader.Open() 317 if fileErr != nil { 318 return nil, model.NewAppError("UploadFiles", "api.file.upload_file.bad_parse.app_error", nil, fileErr.Error(), http.StatusBadRequest) 319 } 320 321 // Will be closed after UploadFiles returns 322 defer file.Close() 323 324 files[i] = file 325 filenames[i] = fileHeader.Filename 326 } 327 328 return a.UploadFiles(teamId, channelId, userId, files, filenames, clientIds) 329 } 330 331 // Uploads some files to the given team and channel as the given user. files and filenames should have 332 // the same length. clientIds should either not be provided or have the same length as files and filenames. 333 // The provided files should be closed by the caller so that they are not leaked. 334 func (a *App) UploadFiles(teamId string, channelId string, userId string, files []io.ReadCloser, filenames []string, clientIds []string) (*model.FileUploadResponse, *model.AppError) { 335 if len(*a.Config().FileSettings.DriverName) == 0 { 336 return nil, model.NewAppError("uploadFile", "api.file.upload_file.storage.app_error", nil, "", http.StatusNotImplemented) 337 } 338 339 if len(filenames) != len(files) || (len(clientIds) > 0 && len(clientIds) != len(files)) { 340 return nil, model.NewAppError("UploadFiles", "api.file.upload_file.incorrect_number_of_files.app_error", nil, "", http.StatusBadRequest) 341 } 342 343 resStruct := &model.FileUploadResponse{ 344 FileInfos: []*model.FileInfo{}, 345 ClientIds: []string{}, 346 } 347 348 previewPathList := []string{} 349 thumbnailPathList := []string{} 350 imageDataList := [][]byte{} 351 352 for i, file := range files { 353 buf := bytes.NewBuffer(nil) 354 io.Copy(buf, file) 355 data := buf.Bytes() 356 357 info, err := a.DoUploadFile(time.Now(), teamId, channelId, userId, filenames[i], data) 358 if err != nil { 359 return nil, err 360 } 361 362 if info.PreviewPath != "" || info.ThumbnailPath != "" { 363 previewPathList = append(previewPathList, info.PreviewPath) 364 thumbnailPathList = append(thumbnailPathList, info.ThumbnailPath) 365 imageDataList = append(imageDataList, data) 366 } 367 368 resStruct.FileInfos = append(resStruct.FileInfos, info) 369 370 if len(clientIds) > 0 { 371 resStruct.ClientIds = append(resStruct.ClientIds, clientIds[i]) 372 } 373 } 374 375 a.HandleImages(previewPathList, thumbnailPathList, imageDataList) 376 377 return resStruct, nil 378 } 379 380 func (a *App) DoUploadFile(now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, *model.AppError) { 381 filename := filepath.Base(rawFilename) 382 teamId := filepath.Base(rawTeamId) 383 channelId := filepath.Base(rawChannelId) 384 userId := filepath.Base(rawUserId) 385 386 info, err := model.GetInfoForBytes(filename, data) 387 if err != nil { 388 err.StatusCode = http.StatusBadRequest 389 return nil, err 390 } 391 392 if orientation, err := getImageOrientation(bytes.NewReader(data)); err == nil && 393 (orientation == RotatedCWMirrored || 394 orientation == RotatedCCW || 395 orientation == RotatedCCWMirrored || 396 orientation == RotatedCW) { 397 info.Width, info.Height = info.Height, info.Width 398 } 399 400 info.Id = model.NewId() 401 info.CreatorId = userId 402 403 pathPrefix := now.Format("20060102") + "/teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + info.Id + "/" 404 info.Path = pathPrefix + filename 405 406 if info.IsImage() { 407 // Check dimensions before loading the whole thing into memory later on 408 if info.Width*info.Height > MaxImageSize { 409 err := model.NewAppError("uploadFile", "api.file.upload_file.large_image.app_error", map[string]interface{}{"Filename": filename}, "", http.StatusBadRequest) 410 return nil, err 411 } 412 413 nameWithoutExtension := filename[:strings.LastIndex(filename, ".")] 414 info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg" 415 info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg" 416 } 417 418 if _, err := a.WriteFile(bytes.NewReader(data), info.Path); err != nil { 419 return nil, err 420 } 421 422 if result := <-a.Srv.Store.FileInfo().Save(info); result.Err != nil { 423 return nil, result.Err 424 } 425 426 return info, nil 427 } 428 429 func (a *App) HandleImages(previewPathList []string, thumbnailPathList []string, fileData [][]byte) { 430 wg := new(sync.WaitGroup) 431 432 for i := range fileData { 433 img, width, height := prepareImage(fileData[i]) 434 if img != nil { 435 wg.Add(2) 436 go func(img *image.Image, path string, width int, height int) { 437 defer wg.Done() 438 a.generateThumbnailImage(*img, path, width, height) 439 }(img, thumbnailPathList[i], width, height) 440 441 go func(img *image.Image, path string, width int) { 442 defer wg.Done() 443 a.generatePreviewImage(*img, path, width) 444 }(img, previewPathList[i], width) 445 } 446 } 447 wg.Wait() 448 } 449 450 func prepareImage(fileData []byte) (*image.Image, int, int) { 451 // Decode image bytes into Image object 452 img, imgType, err := image.Decode(bytes.NewReader(fileData)) 453 if err != nil { 454 mlog.Error(fmt.Sprintf("Unable to decode image err=%v", err)) 455 return nil, 0, 0 456 } 457 458 width := img.Bounds().Dx() 459 height := img.Bounds().Dy() 460 461 // Fill in the background of a potentially-transparent png file as white 462 if imgType == "png" { 463 dst := image.NewRGBA(img.Bounds()) 464 draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src) 465 draw.Draw(dst, dst.Bounds(), img, img.Bounds().Min, draw.Over) 466 img = dst 467 } 468 469 // Flip the image to be upright 470 orientation, _ := getImageOrientation(bytes.NewReader(fileData)) 471 img = makeImageUpright(img, orientation) 472 473 return &img, width, height 474 } 475 476 func makeImageUpright(img image.Image, orientation int) image.Image { 477 switch orientation { 478 case UprightMirrored: 479 return imaging.FlipH(img) 480 case UpsideDown: 481 return imaging.Rotate180(img) 482 case UpsideDownMirrored: 483 return imaging.FlipV(img) 484 case RotatedCWMirrored: 485 return imaging.Transpose(img) 486 case RotatedCCW: 487 return imaging.Rotate270(img) 488 case RotatedCCWMirrored: 489 return imaging.Transverse(img) 490 case RotatedCW: 491 return imaging.Rotate90(img) 492 default: 493 return img 494 } 495 } 496 497 func getImageOrientation(input io.Reader) (int, error) { 498 if exifData, err := exif.Decode(input); err != nil { 499 return Upright, err 500 } else { 501 if tag, err := exifData.Get("Orientation"); err != nil { 502 return Upright, err 503 } else { 504 orientation, err := tag.Int(0) 505 if err != nil { 506 return Upright, err 507 } else { 508 return orientation, nil 509 } 510 } 511 } 512 } 513 514 func (a *App) generateThumbnailImage(img image.Image, thumbnailPath string, width int, height int) { 515 thumbWidth := float64(IMAGE_THUMBNAIL_PIXEL_WIDTH) 516 thumbHeight := float64(IMAGE_THUMBNAIL_PIXEL_HEIGHT) 517 imgWidth := float64(width) 518 imgHeight := float64(height) 519 520 var thumbnail image.Image 521 if imgHeight < IMAGE_THUMBNAIL_PIXEL_HEIGHT && imgWidth < thumbWidth { 522 thumbnail = img 523 } else if imgHeight/imgWidth < thumbHeight/thumbWidth { 524 thumbnail = imaging.Resize(img, 0, IMAGE_THUMBNAIL_PIXEL_HEIGHT, imaging.Lanczos) 525 } else { 526 thumbnail = imaging.Resize(img, IMAGE_THUMBNAIL_PIXEL_WIDTH, 0, imaging.Lanczos) 527 } 528 529 buf := new(bytes.Buffer) 530 if err := jpeg.Encode(buf, thumbnail, &jpeg.Options{Quality: 90}); err != nil { 531 mlog.Error(fmt.Sprintf("Unable to encode image as jpeg path=%v err=%v", thumbnailPath, err)) 532 return 533 } 534 535 if _, err := a.WriteFile(buf, thumbnailPath); err != nil { 536 mlog.Error(fmt.Sprintf("Unable to upload thumbnail path=%v err=%v", thumbnailPath, err)) 537 return 538 } 539 } 540 541 func (a *App) generatePreviewImage(img image.Image, previewPath string, width int) { 542 var preview image.Image 543 544 if width > IMAGE_PREVIEW_PIXEL_WIDTH { 545 preview = imaging.Resize(img, IMAGE_PREVIEW_PIXEL_WIDTH, 0, imaging.Lanczos) 546 } else { 547 preview = img 548 } 549 550 buf := new(bytes.Buffer) 551 552 if err := jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90}); err != nil { 553 mlog.Error(fmt.Sprintf("Unable to encode image as preview jpg err=%v", err), mlog.String("path", previewPath)) 554 return 555 } 556 557 if _, err := a.WriteFile(buf, previewPath); err != nil { 558 mlog.Error(fmt.Sprintf("Unable to upload preview err=%v", err), mlog.String("path", previewPath)) 559 return 560 } 561 } 562 563 func (a *App) GetFileInfo(fileId string) (*model.FileInfo, *model.AppError) { 564 if result := <-a.Srv.Store.FileInfo().Get(fileId); result.Err != nil { 565 return nil, result.Err 566 } else { 567 return result.Data.(*model.FileInfo), nil 568 } 569 }