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  }