github.com/vnforks/kid@v5.11.1+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/plugin" 32 "github.com/mattermost/mattermost-server/services/filesstore" 33 "github.com/mattermost/mattermost-server/store" 34 "github.com/mattermost/mattermost-server/utils" 35 ) 36 37 const ( 38 /* 39 EXIF Image Orientations 40 1 2 3 4 5 6 7 8 41 42 888888 888888 88 88 8888888888 88 88 8888888888 43 88 88 88 88 88 88 88 88 88 88 88 88 44 8888 8888 8888 8888 88 8888888888 8888888888 88 45 88 88 88 88 46 88 88 888888 888888 47 */ 48 Upright = 1 49 UprightMirrored = 2 50 UpsideDown = 3 51 UpsideDownMirrored = 4 52 RotatedCWMirrored = 5 53 RotatedCCW = 6 54 RotatedCCWMirrored = 7 55 RotatedCW = 8 56 57 MaxImageSize = 6048 * 4032 // 24 megapixels, roughly 36MB as a raw image 58 ImageThumbnailWidth = 120 59 ImageThumbnailHeight = 100 60 ImageThumbnailRatio = float64(ImageThumbnailHeight) / float64(ImageThumbnailWidth) 61 ImagePreviewWidth = 1920 62 63 UploadFileInitialBufferSize = 2 * 1024 * 1024 // 2Mb 64 65 // Deprecated 66 IMAGE_THUMBNAIL_PIXEL_WIDTH = 120 67 IMAGE_THUMBNAIL_PIXEL_HEIGHT = 100 68 IMAGE_PREVIEW_PIXEL_WIDTH = 1920 69 ) 70 71 func (a *App) FileBackend() (filesstore.FileBackend, *model.AppError) { 72 license := a.License() 73 return filesstore.NewFileBackend(&a.Config().FileSettings, license != nil && *license.Features.Compliance) 74 } 75 76 func (a *App) ReadFile(path string) ([]byte, *model.AppError) { 77 backend, err := a.FileBackend() 78 if err != nil { 79 return nil, err 80 } 81 return backend.ReadFile(path) 82 } 83 84 // Caller must close the first return value 85 func (a *App) FileReader(path string) (io.ReadCloser, *model.AppError) { 86 backend, err := a.FileBackend() 87 if err != nil { 88 return nil, err 89 } 90 return backend.Reader(path) 91 } 92 93 func (a *App) FileExists(path string) (bool, *model.AppError) { 94 backend, err := a.FileBackend() 95 if err != nil { 96 return false, err 97 } 98 return backend.FileExists(path) 99 } 100 101 func (a *App) MoveFile(oldPath, newPath string) *model.AppError { 102 backend, err := a.FileBackend() 103 if err != nil { 104 return err 105 } 106 return backend.MoveFile(oldPath, newPath) 107 } 108 109 func (a *App) WriteFile(fr io.Reader, path string) (int64, *model.AppError) { 110 backend, err := a.FileBackend() 111 if err != nil { 112 return 0, err 113 } 114 115 return backend.WriteFile(fr, path) 116 } 117 118 func (a *App) RemoveFile(path string) *model.AppError { 119 backend, err := a.FileBackend() 120 if err != nil { 121 return err 122 } 123 return backend.RemoveFile(path) 124 } 125 126 func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename string) *model.FileInfo { 127 // Find the path from the Filename of the form /{channelId}/{userId}/{uid}/{nameWithExtension} 128 split := strings.SplitN(filename, "/", 5) 129 if len(split) < 5 { 130 mlog.Error("Unable to decipher filename when migrating post to use FileInfos", mlog.String("post_id", post.Id), mlog.String("filename", filename)) 131 return nil 132 } 133 134 channelId := split[1] 135 userId := split[2] 136 oldId := split[3] 137 name, _ := url.QueryUnescape(split[4]) 138 139 if split[0] != "" || split[1] != post.ChannelId || split[2] != post.UserId || strings.Contains(split[4], "/") { 140 mlog.Warn( 141 "Found an unusual filename when migrating post to use FileInfos", 142 mlog.String("post_id", post.Id), 143 mlog.String("channel_id", post.ChannelId), 144 mlog.String("user_id", post.UserId), 145 mlog.String("filename", filename), 146 ) 147 } 148 149 pathPrefix := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/", teamId, channelId, userId, oldId) 150 path := pathPrefix + name 151 152 // Open the file and populate the fields of the FileInfo 153 data, err := a.ReadFile(path) 154 if err != nil { 155 mlog.Error( 156 fmt.Sprintf("File not found when migrating post to use FileInfos, err=%v", err), 157 mlog.String("post_id", post.Id), 158 mlog.String("filename", filename), 159 mlog.String("path", path), 160 ) 161 return nil 162 } 163 164 info, err := model.GetInfoForBytes(name, data) 165 if err != nil { 166 mlog.Warn( 167 fmt.Sprintf("Unable to fully decode file info when migrating post to use FileInfos, err=%v", err), 168 mlog.String("post_id", post.Id), 169 mlog.String("filename", filename), 170 ) 171 } 172 173 // Generate a new ID because with the old system, you could very rarely get multiple posts referencing the same file 174 info.Id = model.NewId() 175 info.CreatorId = post.UserId 176 info.PostId = post.Id 177 info.CreateAt = post.CreateAt 178 info.UpdateAt = post.UpdateAt 179 info.Path = path 180 181 if info.IsImage() { 182 nameWithoutExtension := name[:strings.LastIndex(name, ".")] 183 info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg" 184 info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg" 185 } 186 187 return info 188 } 189 190 func (a *App) FindTeamIdForFilename(post *model.Post, filename string) string { 191 split := strings.SplitN(filename, "/", 5) 192 id := split[3] 193 name, _ := url.QueryUnescape(split[4]) 194 195 // This post is in a direct channel so we need to figure out what team the files are stored under. 196 result := <-a.Srv.Store.Team().GetTeamsByUserId(post.UserId) 197 if result.Err != nil { 198 mlog.Error(fmt.Sprintf("Unable to get teams when migrating post to use FileInfo, err=%v", result.Err), mlog.String("post_id", post.Id)) 199 return "" 200 } 201 202 teams := result.Data.([]*model.Team) 203 if len(teams) == 1 { 204 // The user has only one team so the post must've been sent from it 205 return teams[0].Id 206 } 207 208 for _, team := range teams { 209 path := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/%s", team.Id, post.ChannelId, post.UserId, id, name) 210 if _, err := a.ReadFile(path); err == nil { 211 // Found the team that this file was posted from 212 return team.Id 213 } 214 } 215 216 return "" 217 } 218 219 var fileMigrationLock sync.Mutex 220 221 // Creates and stores FileInfos for a post created before the FileInfos table existed. 222 func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo { 223 if len(post.Filenames) == 0 { 224 mlog.Warn("Unable to migrate post to use FileInfos with an empty Filenames field", mlog.String("post_id", post.Id)) 225 return []*model.FileInfo{} 226 } 227 228 cchan := a.Srv.Store.Channel().Get(post.ChannelId, true) 229 230 // There's a weird bug that rarely happens where a post ends up with duplicate Filenames so remove those 231 filenames := utils.RemoveDuplicatesFromStringArray(post.Filenames) 232 233 result := <-cchan 234 if result.Err != nil { 235 mlog.Error( 236 fmt.Sprintf("Unable to get channel when migrating post to use FileInfos, err=%v", result.Err), 237 mlog.String("post_id", post.Id), 238 mlog.String("channel_id", post.ChannelId), 239 ) 240 return []*model.FileInfo{} 241 } 242 channel := result.Data.(*model.Channel) 243 244 // Find the team that was used to make this post since its part of the file path that isn't saved in the Filename 245 var teamId string 246 if channel.TeamId == "" { 247 // This post was made in a cross-team DM channel so we need to find where its files were saved 248 teamId = a.FindTeamIdForFilename(post, filenames[0]) 249 } else { 250 teamId = channel.TeamId 251 } 252 253 // Create FileInfo objects for this post 254 infos := make([]*model.FileInfo, 0, len(filenames)) 255 if teamId == "" { 256 mlog.Error( 257 fmt.Sprintf("Unable to find team id for files when migrating post to use FileInfos, filenames=%v", filenames), 258 mlog.String("post_id", post.Id), 259 ) 260 } else { 261 for _, filename := range filenames { 262 info := a.GetInfoForFilename(post, teamId, filename) 263 if info == nil { 264 continue 265 } 266 267 infos = append(infos, info) 268 } 269 } 270 271 // Lock to prevent only one migration thread from trying to update the post at once, preventing duplicate FileInfos from being created 272 fileMigrationLock.Lock() 273 defer fileMigrationLock.Unlock() 274 275 result = <-a.Srv.Store.Post().Get(post.Id) 276 if result.Err != nil { 277 mlog.Error(fmt.Sprintf("Unable to get post when migrating post to use FileInfos, err=%v", result.Err), mlog.String("post_id", post.Id)) 278 return []*model.FileInfo{} 279 } 280 281 if newPost := result.Data.(*model.PostList).Posts[post.Id]; len(newPost.Filenames) != len(post.Filenames) { 282 // Another thread has already created FileInfos for this post, so just return those 283 result := <-a.Srv.Store.FileInfo().GetForPost(post.Id, true, false) 284 if result.Err != nil { 285 mlog.Error(fmt.Sprintf("Unable to get FileInfos for migrated post, err=%v", result.Err), mlog.String("post_id", post.Id)) 286 return []*model.FileInfo{} 287 } 288 289 mlog.Debug("Post already migrated to use FileInfos", mlog.String("post_id", post.Id)) 290 return result.Data.([]*model.FileInfo) 291 } 292 293 mlog.Debug("Migrating post to use FileInfos", mlog.String("post_id", post.Id)) 294 295 savedInfos := make([]*model.FileInfo, 0, len(infos)) 296 fileIds := make([]string, 0, len(filenames)) 297 for _, info := range infos { 298 if result := <-a.Srv.Store.FileInfo().Save(info); result.Err != nil { 299 mlog.Error( 300 fmt.Sprintf("Unable to save file info when migrating post to use FileInfos, err=%v", result.Err), 301 mlog.String("post_id", post.Id), 302 mlog.String("file_info_id", info.Id), 303 mlog.String("file_info_path", info.Path), 304 ) 305 continue 306 } 307 308 savedInfos = append(savedInfos, info) 309 fileIds = append(fileIds, info.Id) 310 } 311 312 // Copy and save the updated post 313 newPost := &model.Post{} 314 *newPost = *post 315 316 newPost.Filenames = []string{} 317 newPost.FileIds = fileIds 318 319 // Update Posts to clear Filenames and set FileIds 320 if result := <-a.Srv.Store.Post().Update(newPost, post); result.Err != nil { 321 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)) 322 return []*model.FileInfo{} 323 } 324 return savedInfos 325 } 326 327 func (a *App) GeneratePublicLink(siteURL string, info *model.FileInfo) string { 328 hash := GeneratePublicLinkHash(info.Id, *a.Config().FileSettings.PublicLinkSalt) 329 return fmt.Sprintf("%s/files/%v/public?h=%s", siteURL, info.Id, hash) 330 } 331 332 func GeneratePublicLinkHash(fileId, salt string) string { 333 hash := sha256.New() 334 hash.Write([]byte(salt)) 335 hash.Write([]byte(fileId)) 336 337 return base64.RawURLEncoding.EncodeToString(hash.Sum(nil)) 338 } 339 340 func (a *App) UploadMultipartFiles(teamId string, channelId string, userId string, fileHeaders []*multipart.FileHeader, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) { 341 files := make([]io.ReadCloser, len(fileHeaders)) 342 filenames := make([]string, len(fileHeaders)) 343 344 for i, fileHeader := range fileHeaders { 345 file, fileErr := fileHeader.Open() 346 if fileErr != nil { 347 return nil, model.NewAppError("UploadFiles", "api.file.upload_file.read_request.app_error", 348 map[string]interface{}{"Filename": fileHeader.Filename}, fileErr.Error(), http.StatusBadRequest) 349 } 350 351 // Will be closed after UploadFiles returns 352 defer file.Close() 353 354 files[i] = file 355 filenames[i] = fileHeader.Filename 356 } 357 358 return a.UploadFiles(teamId, channelId, userId, files, filenames, clientIds, now) 359 } 360 361 // Uploads some files to the given team and channel as the given user. files and filenames should have 362 // the same length. clientIds should either not be provided or have the same length as files and filenames. 363 // The provided files should be closed by the caller so that they are not leaked. 364 func (a *App) UploadFiles(teamId string, channelId string, userId string, files []io.ReadCloser, filenames []string, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) { 365 if len(*a.Config().FileSettings.DriverName) == 0 { 366 return nil, model.NewAppError("UploadFiles", "api.file.upload_file.storage.app_error", nil, "", http.StatusNotImplemented) 367 } 368 369 if len(filenames) != len(files) || (len(clientIds) > 0 && len(clientIds) != len(files)) { 370 return nil, model.NewAppError("UploadFiles", "api.file.upload_file.incorrect_number_of_files.app_error", nil, "", http.StatusBadRequest) 371 } 372 373 resStruct := &model.FileUploadResponse{ 374 FileInfos: []*model.FileInfo{}, 375 ClientIds: []string{}, 376 } 377 378 previewPathList := []string{} 379 thumbnailPathList := []string{} 380 imageDataList := [][]byte{} 381 382 for i, file := range files { 383 buf := bytes.NewBuffer(nil) 384 io.Copy(buf, file) 385 data := buf.Bytes() 386 387 info, data, err := a.DoUploadFileExpectModification(now, teamId, channelId, userId, filenames[i], data) 388 if err != nil { 389 return nil, err 390 } 391 392 if info.PreviewPath != "" || info.ThumbnailPath != "" { 393 previewPathList = append(previewPathList, info.PreviewPath) 394 thumbnailPathList = append(thumbnailPathList, info.ThumbnailPath) 395 imageDataList = append(imageDataList, data) 396 } 397 398 resStruct.FileInfos = append(resStruct.FileInfos, info) 399 400 if len(clientIds) > 0 { 401 resStruct.ClientIds = append(resStruct.ClientIds, clientIds[i]) 402 } 403 } 404 405 a.HandleImages(previewPathList, thumbnailPathList, imageDataList) 406 407 return resStruct, nil 408 } 409 410 // UploadFile uploads a single file in form of a completely constructed byte array for a channel. 411 func (a *App) UploadFile(data []byte, channelId string, filename string) (*model.FileInfo, *model.AppError) { 412 info, _, appError := a.DoUploadFileExpectModification(time.Now(), "noteam", channelId, "nouser", filename, data) 413 414 if appError != nil { 415 return nil, appError 416 } 417 418 if info.PreviewPath != "" || info.ThumbnailPath != "" { 419 previewPathList := []string{info.PreviewPath} 420 thumbnailPathList := []string{info.ThumbnailPath} 421 imageDataList := [][]byte{data} 422 423 a.HandleImages(previewPathList, thumbnailPathList, imageDataList) 424 } 425 426 return info, nil 427 } 428 429 func (a *App) DoUploadFile(now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, *model.AppError) { 430 info, _, err := a.DoUploadFileExpectModification(now, rawTeamId, rawChannelId, rawUserId, rawFilename, data) 431 return info, err 432 } 433 434 func UploadFileSetTeamId(teamId string) func(t *uploadFileTask) { 435 return func(t *uploadFileTask) { 436 t.TeamId = filepath.Base(teamId) 437 } 438 } 439 440 func UploadFileSetUserId(userId string) func(t *uploadFileTask) { 441 return func(t *uploadFileTask) { 442 t.UserId = filepath.Base(userId) 443 } 444 } 445 446 func UploadFileSetTimestamp(timestamp time.Time) func(t *uploadFileTask) { 447 return func(t *uploadFileTask) { 448 t.Timestamp = timestamp 449 } 450 } 451 452 func UploadFileSetContentLength(contentLength int64) func(t *uploadFileTask) { 453 return func(t *uploadFileTask) { 454 t.ContentLength = contentLength 455 } 456 } 457 458 func UploadFileSetClientId(clientId string) func(t *uploadFileTask) { 459 return func(t *uploadFileTask) { 460 t.ClientId = clientId 461 } 462 } 463 464 func UploadFileSetRaw() func(t *uploadFileTask) { 465 return func(t *uploadFileTask) { 466 t.Raw = true 467 } 468 } 469 470 type uploadFileTask struct { 471 // File name. 472 Name string 473 474 ChannelId string 475 TeamId string 476 UserId string 477 478 // Time stamp to use when creating the file. 479 Timestamp time.Time 480 481 // The value of the Content-Length http header, when available. 482 ContentLength int64 483 484 // The file data stream. 485 Input io.Reader 486 487 // An optional, client-assigned Id field. 488 ClientId string 489 490 // If Raw, do not execute special processing for images, just upload 491 // the file. Plugins are still invoked. 492 Raw bool 493 494 //============================================================= 495 // Internal state 496 497 buf *bytes.Buffer 498 limit int64 499 limitedInput io.Reader 500 teeInput io.Reader 501 fileinfo *model.FileInfo 502 maxFileSize int64 503 504 // Cached image data that (may) get initialized in preprocessImage and 505 // is used in postprocessImage 506 decoded image.Image 507 imageType string 508 imageOrientation int 509 510 // Testing: overrideable dependency functions 511 pluginsEnvironment *plugin.Environment 512 writeFile func(io.Reader, string) (int64, *model.AppError) 513 saveToDatabase func(*model.FileInfo) store.StoreChannel 514 } 515 516 func (t *uploadFileTask) init(a *App) { 517 t.buf = &bytes.Buffer{} 518 t.maxFileSize = *a.Config().FileSettings.MaxFileSize 519 t.limit = *a.Config().FileSettings.MaxFileSize 520 521 t.fileinfo = model.NewInfo(filepath.Base(t.Name)) 522 t.fileinfo.Id = model.NewId() 523 t.fileinfo.CreatorId = t.UserId 524 t.fileinfo.CreateAt = t.Timestamp.UnixNano() / int64(time.Millisecond) 525 t.fileinfo.Path = t.pathPrefix() + t.Name 526 527 // Prepare to read ContentLength if it is known, otherwise limit 528 // ourselves to MaxFileSize. Add an extra byte to check and fail if the 529 // client sent too many bytes. 530 if t.ContentLength > 0 { 531 t.limit = t.ContentLength 532 // Over-Grow the buffer to prevent bytes.ReadFrom from doing it 533 // at the very end. 534 t.buf.Grow(int(t.limit + 1 + bytes.MinRead)) 535 } else { 536 // If we don't know the upload size, grow the buffer somewhat 537 // anyway to avoid extra reslicing. 538 t.buf.Grow(UploadFileInitialBufferSize) 539 } 540 t.limitedInput = &io.LimitedReader{ 541 R: t.Input, 542 N: t.limit + 1, 543 } 544 t.teeInput = io.TeeReader(t.limitedInput, t.buf) 545 546 t.pluginsEnvironment = a.GetPluginsEnvironment() 547 t.writeFile = a.WriteFile 548 t.saveToDatabase = a.Srv.Store.FileInfo().Save 549 } 550 551 // UploadFileX uploads a single file as specified in t. It applies the upload 552 // constraints, executes plugins and image processing logic as needed. It 553 // returns a filled-out FileInfo and an optional error. A plugin may reject the 554 // upload, returning a rejection error. In this case FileInfo would have 555 // contained the last "good" FileInfo before the execution of that plugin. 556 func (a *App) UploadFileX(channelId, name string, input io.Reader, 557 opts ...func(*uploadFileTask)) (*model.FileInfo, *model.AppError) { 558 559 t := &uploadFileTask{ 560 ChannelId: filepath.Base(channelId), 561 Name: filepath.Base(name), 562 Input: input, 563 } 564 for _, o := range opts { 565 o(t) 566 } 567 t.init(a) 568 569 if len(*a.Config().FileSettings.DriverName) == 0 { 570 return nil, t.newAppError("api.file.upload_file.storage.app_error", 571 "", http.StatusNotImplemented) 572 } 573 if t.ContentLength > t.maxFileSize { 574 return nil, t.newAppError("api.file.upload_file.too_large_detailed.app_error", 575 "", http.StatusRequestEntityTooLarge, "Length", t.ContentLength, "Limit", t.maxFileSize) 576 } 577 578 var aerr *model.AppError 579 if !t.Raw && t.fileinfo.IsImage() { 580 aerr = t.preprocessImage() 581 if aerr != nil { 582 return t.fileinfo, aerr 583 } 584 } 585 586 aerr = t.readAll() 587 if aerr != nil { 588 return t.fileinfo, aerr 589 } 590 591 aerr = t.runPlugins() 592 if aerr != nil { 593 return t.fileinfo, aerr 594 } 595 596 // Concurrently upload and update DB, and post-process the image. 597 wg := sync.WaitGroup{} 598 599 if !t.Raw && t.fileinfo.IsImage() { 600 wg.Add(1) 601 go func() { 602 t.postprocessImage() 603 wg.Done() 604 }() 605 } 606 607 _, aerr = t.writeFile(t.newReader(), t.fileinfo.Path) 608 if aerr != nil { 609 return nil, aerr 610 } 611 612 if result := <-t.saveToDatabase(t.fileinfo); result.Err != nil { 613 return nil, result.Err 614 } 615 616 wg.Wait() 617 618 return t.fileinfo, nil 619 } 620 621 func (t *uploadFileTask) readAll() *model.AppError { 622 _, err := t.buf.ReadFrom(t.limitedInput) 623 if err != nil { 624 return t.newAppError("api.file.upload_file.read_request.app_error", 625 err.Error(), http.StatusBadRequest) 626 } 627 if int64(t.buf.Len()) > t.limit { 628 return t.newAppError("api.file.upload_file.too_large_detailed.app_error", 629 "", http.StatusRequestEntityTooLarge, "Length", t.buf.Len(), "Limit", t.limit) 630 } 631 t.fileinfo.Size = int64(t.buf.Len()) 632 633 t.limitedInput = nil 634 t.teeInput = nil 635 return nil 636 } 637 638 func (t *uploadFileTask) runPlugins() *model.AppError { 639 if t.pluginsEnvironment == nil { 640 return nil 641 } 642 643 pluginContext := &plugin.Context{} 644 var rejectionError *model.AppError 645 646 t.pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 647 buf := &bytes.Buffer{} 648 replacementInfo, rejectionReason := hooks.FileWillBeUploaded(pluginContext, 649 t.fileinfo, t.newReader(), buf) 650 if rejectionReason != "" { 651 rejectionError = t.newAppError("api.file.upload_file.rejected_by_plugin.app_error", 652 rejectionReason, http.StatusForbidden, "Reason", rejectionReason) 653 return false 654 } 655 if replacementInfo != nil { 656 t.fileinfo = replacementInfo 657 } 658 if buf.Len() != 0 { 659 t.buf = buf 660 t.teeInput = nil 661 t.limitedInput = nil 662 t.fileinfo.Size = int64(buf.Len()) 663 } 664 665 return true 666 }, plugin.FileWillBeUploadedId) 667 668 if rejectionError != nil { 669 return rejectionError 670 } 671 672 return nil 673 } 674 675 func (t *uploadFileTask) preprocessImage() *model.AppError { 676 // If SVG, attempt to extract dimensions and then return 677 if t.fileinfo.MimeType == "image/svg+xml" { 678 svgInfo, err := parseSVG(t.newReader()) 679 if err != nil { 680 mlog.Error("Failed to parse SVG", mlog.Err(err)) 681 } 682 if svgInfo.Width > 0 && svgInfo.Height > 0 { 683 t.fileinfo.Width = svgInfo.Width 684 t.fileinfo.Height = svgInfo.Height 685 } 686 t.fileinfo.HasPreviewImage = false 687 return nil 688 } 689 690 // If we fail to decode, return "as is". 691 config, _, err := image.DecodeConfig(t.newReader()) 692 if err != nil { 693 return nil 694 } 695 696 t.fileinfo.Width = config.Width 697 t.fileinfo.Height = config.Height 698 699 // Check dimensions before loading the whole thing into memory later on. 700 if t.fileinfo.Width*t.fileinfo.Height > MaxImageSize { 701 return t.newAppError("api.file.upload_file.large_image_detailed.app_error", 702 "", http.StatusBadRequest) 703 } 704 t.fileinfo.HasPreviewImage = true 705 nameWithoutExtension := t.Name[:strings.LastIndex(t.Name, ".")] 706 t.fileinfo.PreviewPath = t.pathPrefix() + nameWithoutExtension + "_preview.jpg" 707 t.fileinfo.ThumbnailPath = t.pathPrefix() + nameWithoutExtension + "_thumb.jpg" 708 709 // check the image orientation with goexif; consume the bytes we 710 // already have first, then keep Tee-ing from input. 711 // TODO: try to reuse exif's .Raw buffer rather than Tee-ing 712 if t.imageOrientation, err = getImageOrientation(t.newReader()); err == nil && 713 (t.imageOrientation == RotatedCWMirrored || 714 t.imageOrientation == RotatedCCW || 715 t.imageOrientation == RotatedCCWMirrored || 716 t.imageOrientation == RotatedCW) { 717 t.fileinfo.Width, t.fileinfo.Height = t.fileinfo.Height, t.fileinfo.Width 718 } 719 720 // For animated GIFs disable the preview; since we have to Decode gifs 721 // anyway, cache the decoded image for later. 722 if t.fileinfo.MimeType == "image/gif" { 723 gifConfig, err := gif.DecodeAll(t.newReader()) 724 if err == nil { 725 if len(gifConfig.Image) >= 1 { 726 t.fileinfo.HasPreviewImage = false 727 728 } 729 if len(gifConfig.Image) > 0 { 730 t.decoded = gifConfig.Image[0] 731 t.imageType = "gif" 732 } 733 } 734 } 735 736 return nil 737 } 738 739 func (t *uploadFileTask) postprocessImage() { 740 // don't try to process SVG files 741 if t.fileinfo.MimeType == "image/svg+xml" { 742 return 743 } 744 745 decoded, typ := t.decoded, t.imageType 746 if decoded == nil { 747 var err error 748 decoded, typ, err = image.Decode(t.newReader()) 749 if err != nil { 750 mlog.Error(fmt.Sprintf("Unable to decode image err=%v", err)) 751 return 752 } 753 } 754 755 // Fill in the background of a potentially-transparent png file as 756 // white. 757 if typ == "png" { 758 dst := image.NewRGBA(decoded.Bounds()) 759 draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src) 760 draw.Draw(dst, dst.Bounds(), decoded, decoded.Bounds().Min, draw.Over) 761 decoded = dst 762 } 763 764 decoded = makeImageUpright(decoded, t.imageOrientation) 765 if decoded == nil { 766 return 767 } 768 769 writeJPEG := func(img image.Image, path string) { 770 r, w := io.Pipe() 771 go func() { 772 _, aerr := t.writeFile(r, path) 773 if aerr != nil { 774 mlog.Error(fmt.Sprintf("Unable to upload path=%v err=%v", path, aerr)) 775 return 776 } 777 }() 778 779 err := jpeg.Encode(w, img, &jpeg.Options{Quality: 90}) 780 if err != nil { 781 mlog.Error(fmt.Sprintf("Unable to encode image as jpeg path=%v err=%v", path, err)) 782 w.CloseWithError(err) 783 } else { 784 w.Close() 785 } 786 } 787 788 w := decoded.Bounds().Dx() 789 h := decoded.Bounds().Dy() 790 791 wg := &sync.WaitGroup{} 792 wg.Add(2) 793 go func() { 794 defer wg.Done() 795 thumb := decoded 796 if h > ImageThumbnailHeight || w > ImageThumbnailWidth { 797 if float64(h)/float64(w) < ImageThumbnailRatio { 798 thumb = imaging.Resize(decoded, 0, ImageThumbnailHeight, imaging.Lanczos) 799 } else { 800 thumb = imaging.Resize(decoded, ImageThumbnailWidth, 0, imaging.Lanczos) 801 } 802 } 803 writeJPEG(thumb, t.fileinfo.ThumbnailPath) 804 }() 805 806 go func() { 807 defer wg.Done() 808 preview := decoded 809 if w > ImagePreviewWidth { 810 preview = imaging.Resize(decoded, ImagePreviewWidth, 0, imaging.Lanczos) 811 } 812 writeJPEG(preview, t.fileinfo.PreviewPath) 813 }() 814 wg.Wait() 815 } 816 817 func (t uploadFileTask) newReader() io.Reader { 818 if t.teeInput != nil { 819 return io.MultiReader(bytes.NewReader(t.buf.Bytes()), t.teeInput) 820 } else { 821 return bytes.NewReader(t.buf.Bytes()) 822 } 823 } 824 825 func (t uploadFileTask) pathPrefix() string { 826 return t.Timestamp.Format("20060102") + 827 "/teams/" + t.TeamId + 828 "/channels/" + t.ChannelId + 829 "/users/" + t.UserId + 830 "/" + t.fileinfo.Id + "/" 831 } 832 833 func (t uploadFileTask) newAppError(id string, details interface{}, httpStatus int, extra ...interface{}) *model.AppError { 834 params := map[string]interface{}{ 835 "Name": t.Name, 836 "Filename": t.Name, 837 "ChannelId": t.ChannelId, 838 "TeamId": t.TeamId, 839 "UserId": t.UserId, 840 "ContentLength": t.ContentLength, 841 "ClientId": t.ClientId, 842 } 843 if t.fileinfo != nil { 844 params["Width"] = t.fileinfo.Width 845 params["Height"] = t.fileinfo.Height 846 } 847 for i := 0; i+1 < len(extra); i += 2 { 848 params[fmt.Sprintf("%v", extra[i])] = extra[i+1] 849 } 850 851 return model.NewAppError("uploadFileTask", id, params, fmt.Sprintf("%v", details), httpStatus) 852 } 853 854 func (a *App) DoUploadFileExpectModification(now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, []byte, *model.AppError) { 855 filename := filepath.Base(rawFilename) 856 teamId := filepath.Base(rawTeamId) 857 channelId := filepath.Base(rawChannelId) 858 userId := filepath.Base(rawUserId) 859 860 info, err := model.GetInfoForBytes(filename, data) 861 if err != nil { 862 err.StatusCode = http.StatusBadRequest 863 return nil, data, err 864 } 865 866 if orientation, err := getImageOrientation(bytes.NewReader(data)); err == nil && 867 (orientation == RotatedCWMirrored || 868 orientation == RotatedCCW || 869 orientation == RotatedCCWMirrored || 870 orientation == RotatedCW) { 871 info.Width, info.Height = info.Height, info.Width 872 } 873 874 info.Id = model.NewId() 875 info.CreatorId = userId 876 info.CreateAt = now.UnixNano() / int64(time.Millisecond) 877 878 pathPrefix := now.Format("20060102") + "/teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + info.Id + "/" 879 info.Path = pathPrefix + filename 880 881 if info.IsImage() { 882 // Check dimensions before loading the whole thing into memory later on 883 if info.Width*info.Height > MaxImageSize { 884 err := model.NewAppError("uploadFile", "api.file.upload_file.large_image.app_error", map[string]interface{}{"Filename": filename}, "", http.StatusBadRequest) 885 return nil, data, err 886 } 887 888 nameWithoutExtension := filename[:strings.LastIndex(filename, ".")] 889 info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg" 890 info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg" 891 } 892 893 if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil { 894 var rejectionError *model.AppError 895 pluginContext := a.PluginContext() 896 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 897 var newBytes bytes.Buffer 898 replacementInfo, rejectionReason := hooks.FileWillBeUploaded(pluginContext, info, bytes.NewReader(data), &newBytes) 899 if rejectionReason != "" { 900 rejectionError = model.NewAppError("DoUploadFile", "File rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest) 901 return false 902 } 903 if replacementInfo != nil { 904 info = replacementInfo 905 } 906 if newBytes.Len() != 0 { 907 data = newBytes.Bytes() 908 info.Size = int64(len(data)) 909 } 910 911 return true 912 }, plugin.FileWillBeUploadedId) 913 if rejectionError != nil { 914 return nil, data, rejectionError 915 } 916 } 917 918 if _, err := a.WriteFile(bytes.NewReader(data), info.Path); err != nil { 919 return nil, data, err 920 } 921 922 if result := <-a.Srv.Store.FileInfo().Save(info); result.Err != nil { 923 return nil, data, result.Err 924 } 925 926 return info, data, nil 927 } 928 929 func (a *App) HandleImages(previewPathList []string, thumbnailPathList []string, fileData [][]byte) { 930 wg := new(sync.WaitGroup) 931 932 for i := range fileData { 933 img, width, height := prepareImage(fileData[i]) 934 if img != nil { 935 wg.Add(2) 936 go func(img image.Image, path string, width int, height int) { 937 defer wg.Done() 938 a.generateThumbnailImage(img, path, width, height) 939 }(img, thumbnailPathList[i], width, height) 940 941 go func(img image.Image, path string, width int) { 942 defer wg.Done() 943 a.generatePreviewImage(img, path, width) 944 }(img, previewPathList[i], width) 945 } 946 } 947 wg.Wait() 948 } 949 950 func prepareImage(fileData []byte) (image.Image, int, int) { 951 // Decode image bytes into Image object 952 img, imgType, err := image.Decode(bytes.NewReader(fileData)) 953 if err != nil { 954 mlog.Error(fmt.Sprintf("Unable to decode image err=%v", err)) 955 return nil, 0, 0 956 } 957 958 width := img.Bounds().Dx() 959 height := img.Bounds().Dy() 960 961 // Fill in the background of a potentially-transparent png file as white 962 if imgType == "png" { 963 dst := image.NewRGBA(img.Bounds()) 964 draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src) 965 draw.Draw(dst, dst.Bounds(), img, img.Bounds().Min, draw.Over) 966 img = dst 967 } 968 969 // Flip the image to be upright 970 orientation, _ := getImageOrientation(bytes.NewReader(fileData)) 971 img = makeImageUpright(img, orientation) 972 973 return img, width, height 974 } 975 976 func makeImageUpright(img image.Image, orientation int) image.Image { 977 switch orientation { 978 case UprightMirrored: 979 return imaging.FlipH(img) 980 case UpsideDown: 981 return imaging.Rotate180(img) 982 case UpsideDownMirrored: 983 return imaging.FlipV(img) 984 case RotatedCWMirrored: 985 return imaging.Transpose(img) 986 case RotatedCCW: 987 return imaging.Rotate270(img) 988 case RotatedCCWMirrored: 989 return imaging.Transverse(img) 990 case RotatedCW: 991 return imaging.Rotate90(img) 992 default: 993 return img 994 } 995 } 996 997 func getImageOrientation(input io.Reader) (int, error) { 998 exifData, err := exif.Decode(input) 999 if err != nil { 1000 return Upright, err 1001 } 1002 1003 tag, err := exifData.Get("Orientation") 1004 if err != nil { 1005 return Upright, err 1006 } 1007 1008 orientation, err := tag.Int(0) 1009 if err != nil { 1010 return Upright, err 1011 } 1012 1013 return orientation, nil 1014 } 1015 1016 func (a *App) generateThumbnailImage(img image.Image, thumbnailPath string, width int, height int) { 1017 thumbWidth := float64(IMAGE_THUMBNAIL_PIXEL_WIDTH) 1018 thumbHeight := float64(IMAGE_THUMBNAIL_PIXEL_HEIGHT) 1019 imgWidth := float64(width) 1020 imgHeight := float64(height) 1021 1022 var thumbnail image.Image 1023 if imgHeight < IMAGE_THUMBNAIL_PIXEL_HEIGHT && imgWidth < thumbWidth { 1024 thumbnail = img 1025 } else if imgHeight/imgWidth < thumbHeight/thumbWidth { 1026 thumbnail = imaging.Resize(img, 0, IMAGE_THUMBNAIL_PIXEL_HEIGHT, imaging.Lanczos) 1027 } else { 1028 thumbnail = imaging.Resize(img, IMAGE_THUMBNAIL_PIXEL_WIDTH, 0, imaging.Lanczos) 1029 } 1030 1031 buf := new(bytes.Buffer) 1032 if err := jpeg.Encode(buf, thumbnail, &jpeg.Options{Quality: 90}); err != nil { 1033 mlog.Error(fmt.Sprintf("Unable to encode image as jpeg path=%v err=%v", thumbnailPath, err)) 1034 return 1035 } 1036 1037 if _, err := a.WriteFile(buf, thumbnailPath); err != nil { 1038 mlog.Error(fmt.Sprintf("Unable to upload thumbnail path=%v err=%v", thumbnailPath, err)) 1039 return 1040 } 1041 } 1042 1043 func (a *App) generatePreviewImage(img image.Image, previewPath string, width int) { 1044 var preview image.Image 1045 1046 if width > IMAGE_PREVIEW_PIXEL_WIDTH { 1047 preview = imaging.Resize(img, IMAGE_PREVIEW_PIXEL_WIDTH, 0, imaging.Lanczos) 1048 } else { 1049 preview = img 1050 } 1051 1052 buf := new(bytes.Buffer) 1053 1054 if err := jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90}); err != nil { 1055 mlog.Error(fmt.Sprintf("Unable to encode image as preview jpg err=%v", err), mlog.String("path", previewPath)) 1056 return 1057 } 1058 1059 if _, err := a.WriteFile(buf, previewPath); err != nil { 1060 mlog.Error(fmt.Sprintf("Unable to upload preview err=%v", err), mlog.String("path", previewPath)) 1061 return 1062 } 1063 } 1064 1065 func (a *App) GetFileInfo(fileId string) (*model.FileInfo, *model.AppError) { 1066 result := <-a.Srv.Store.FileInfo().Get(fileId) 1067 if result.Err != nil { 1068 return nil, result.Err 1069 } 1070 return result.Data.(*model.FileInfo), nil 1071 } 1072 1073 func (a *App) GetFile(fileId string) ([]byte, *model.AppError) { 1074 info, err := a.GetFileInfo(fileId) 1075 if err != nil { 1076 return nil, err 1077 } 1078 1079 data, err := a.ReadFile(info.Path) 1080 if err != nil { 1081 return nil, err 1082 } 1083 1084 return data, nil 1085 } 1086 1087 func (a *App) CopyFileInfos(userId string, fileIds []string) ([]string, *model.AppError) { 1088 var newFileIds []string 1089 1090 now := model.GetMillis() 1091 1092 for _, fileId := range fileIds { 1093 result := <-a.Srv.Store.FileInfo().Get(fileId) 1094 1095 if result.Err != nil { 1096 return nil, result.Err 1097 } 1098 1099 fileInfo := result.Data.(*model.FileInfo) 1100 fileInfo.Id = model.NewId() 1101 fileInfo.CreatorId = userId 1102 fileInfo.CreateAt = now 1103 fileInfo.UpdateAt = now 1104 fileInfo.PostId = "" 1105 1106 if result := <-a.Srv.Store.FileInfo().Save(fileInfo); result.Err != nil { 1107 return newFileIds, result.Err 1108 } 1109 1110 newFileIds = append(newFileIds, fileInfo.Id) 1111 } 1112 1113 return newFileIds, nil 1114 }