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