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