github.com/haalcala/mattermost-server-change-repo@v0.0.0-20210713015153-16753fbeee5f/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  	"archive/zip"
     8  	"bytes"
     9  	"crypto/sha256"
    10  	"encoding/base64"
    11  	"errors"
    12  	"fmt"
    13  	"image"
    14  	"image/color"
    15  	"image/draw"
    16  	"image/gif"
    17  	"image/jpeg"
    18  	"io"
    19  	"mime/multipart"
    20  	"net/http"
    21  	"net/url"
    22  	"os"
    23  	"path"
    24  	"path/filepath"
    25  	"regexp"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  
    30  	"github.com/disintegration/imaging"
    31  	_ "github.com/oov/psd"
    32  	"github.com/rwcarlsen/goexif/exif"
    33  	_ "golang.org/x/image/bmp"
    34  	_ "golang.org/x/image/tiff"
    35  
    36  	"github.com/mattermost/mattermost-server/v5/mlog"
    37  	"github.com/mattermost/mattermost-server/v5/model"
    38  	"github.com/mattermost/mattermost-server/v5/plugin"
    39  	"github.com/mattermost/mattermost-server/v5/services/filesstore"
    40  	"github.com/mattermost/mattermost-server/v5/store"
    41  	"github.com/mattermost/mattermost-server/v5/utils"
    42  )
    43  
    44  const (
    45  	/*
    46  	  EXIF Image Orientations
    47  	  1        2       3      4         5            6           7          8
    48  
    49  	  888888  888888      88  88      8888888888  88                  88  8888888888
    50  	  88          88      88  88      88  88      88  88          88  88      88  88
    51  	  8888      8888    8888  8888    88          8888888888  8888888888          88
    52  	  88          88      88  88
    53  	  88          88  888888  888888
    54  	*/
    55  	Upright            = 1
    56  	UprightMirrored    = 2
    57  	UpsideDown         = 3
    58  	UpsideDownMirrored = 4
    59  	RotatedCWMirrored  = 5
    60  	RotatedCCW         = 6
    61  	RotatedCCWMirrored = 7
    62  	RotatedCW          = 8
    63  
    64  	MaxImageSize         = int64(6048 * 4032) // 24 megapixels, roughly 36MB as a raw image
    65  	ImageThumbnailWidth  = 120
    66  	ImageThumbnailHeight = 100
    67  	ImageThumbnailRatio  = float64(ImageThumbnailHeight) / float64(ImageThumbnailWidth)
    68  	ImagePreviewWidth    = 1920
    69  
    70  	maxUploadInitialBufferSize = 1024 * 1024 // 1Mb
    71  
    72  	// Deprecated
    73  	ImageThumbnailPixelWidth  = 120
    74  	ImageThumbnailPixelHeight = 100
    75  	ImagePreviewPixelWidth    = 1920
    76  )
    77  
    78  func (a *App) FileBackend() (filesstore.FileBackend, *model.AppError) {
    79  	return a.Srv().FileBackend()
    80  }
    81  
    82  func (a *App) CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError {
    83  	fileBackendSettings := settings.ToFileBackendSettings(false)
    84  	err := fileBackendSettings.CheckMandatoryS3Fields()
    85  	if err != nil {
    86  		return model.NewAppError("CheckMandatoryS3Fields", "api.admin.test_s3.missing_s3_bucket", nil, err.Error(), http.StatusBadRequest)
    87  	}
    88  	return nil
    89  }
    90  
    91  func (a *App) TestFilesStoreConnection() *model.AppError {
    92  	backend, err := a.FileBackend()
    93  	if err != nil {
    94  		return err
    95  	}
    96  	nErr := backend.TestConnection()
    97  	if nErr != nil {
    98  		return model.NewAppError("TestConnection", "api.file.test_connection.app_error", nil, nErr.Error(), http.StatusInternalServerError)
    99  	}
   100  	return nil
   101  }
   102  
   103  func (a *App) TestFilesStoreConnectionWithConfig(cfg *model.FileSettings) *model.AppError {
   104  	license := a.Srv().License()
   105  	backend, err := filesstore.NewFileBackend(cfg.ToFileBackendSettings(license != nil && *license.Features.Compliance))
   106  	if err != nil {
   107  		return model.NewAppError("FileBackend", "api.file.no_driver.app_error", nil, err.Error(), http.StatusInternalServerError)
   108  	}
   109  	nErr := backend.TestConnection()
   110  	if nErr != nil {
   111  		return model.NewAppError("TestConnection", "api.file.test_connection.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   112  	}
   113  	return nil
   114  }
   115  
   116  func (a *App) ReadFile(path string) ([]byte, *model.AppError) {
   117  	backend, err := a.FileBackend()
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  	result, nErr := backend.ReadFile(path)
   122  	if nErr != nil {
   123  		return nil, model.NewAppError("ReadFile", "api.file.read_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   124  	}
   125  	return result, nil
   126  }
   127  
   128  // Caller must close the first return value
   129  func (a *App) FileReader(path string) (filesstore.ReadCloseSeeker, *model.AppError) {
   130  	backend, err := a.FileBackend()
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	result, nErr := backend.Reader(path)
   135  	if nErr != nil {
   136  		return nil, model.NewAppError("FileReader", "api.file.file_reader.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   137  	}
   138  	return result, nil
   139  }
   140  
   141  func (a *App) FileExists(path string) (bool, *model.AppError) {
   142  	backend, err := a.FileBackend()
   143  	if err != nil {
   144  		return false, err
   145  	}
   146  	result, nErr := backend.FileExists(path)
   147  	if nErr != nil {
   148  		return false, model.NewAppError("FileExists", "api.file.file_exists.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   149  	}
   150  	return result, nil
   151  }
   152  
   153  func (a *App) FileSize(path string) (int64, *model.AppError) {
   154  	backend, err := a.FileBackend()
   155  	if err != nil {
   156  		return 0, err
   157  	}
   158  	size, nErr := backend.FileSize(path)
   159  	if nErr != nil {
   160  		return 0, model.NewAppError("FileSize", "api.file.file_size.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   161  	}
   162  	return size, nil
   163  }
   164  
   165  func (a *App) FileModTime(path string) (time.Time, *model.AppError) {
   166  	backend, err := a.FileBackend()
   167  	if err != nil {
   168  		return time.Time{}, err
   169  	}
   170  	modTime, nErr := backend.FileModTime(path)
   171  	if nErr != nil {
   172  		return time.Time{}, model.NewAppError("FileModTime", "api.file.file_mod_time.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   173  	}
   174  
   175  	return modTime, nil
   176  }
   177  
   178  func (a *App) MoveFile(oldPath, newPath string) *model.AppError {
   179  	backend, err := a.FileBackend()
   180  	if err != nil {
   181  		return err
   182  	}
   183  	nErr := backend.MoveFile(oldPath, newPath)
   184  	if nErr != nil {
   185  		return model.NewAppError("MoveFile", "api.file.move_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   186  	}
   187  	return nil
   188  }
   189  
   190  func (a *App) WriteFile(fr io.Reader, path string) (int64, *model.AppError) {
   191  	backend, err := a.FileBackend()
   192  	if err != nil {
   193  		return 0, err
   194  	}
   195  
   196  	result, nErr := backend.WriteFile(fr, path)
   197  	if nErr != nil {
   198  		return result, model.NewAppError("WriteFile", "api.file.write_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   199  	}
   200  	return result, nil
   201  }
   202  
   203  func (a *App) AppendFile(fr io.Reader, path string) (int64, *model.AppError) {
   204  	backend, err := a.FileBackend()
   205  	if err != nil {
   206  		return 0, err
   207  	}
   208  
   209  	result, nErr := backend.AppendFile(fr, path)
   210  	if nErr != nil {
   211  		return result, model.NewAppError("AppendFile", "api.file.append_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   212  	}
   213  	return result, nil
   214  }
   215  
   216  func (a *App) RemoveFile(path string) *model.AppError {
   217  	backend, err := a.FileBackend()
   218  	if err != nil {
   219  		return err
   220  	}
   221  	nErr := backend.RemoveFile(path)
   222  	if nErr != nil {
   223  		return model.NewAppError("RemoveFile", "api.file.remove_file.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   224  	}
   225  	return nil
   226  }
   227  
   228  func (a *App) ListDirectory(path string) ([]string, *model.AppError) {
   229  	backend, err := a.FileBackend()
   230  	if err != nil {
   231  		return nil, err
   232  	}
   233  	paths, nErr := backend.ListDirectory(path)
   234  	if nErr != nil {
   235  		return nil, model.NewAppError("ListDirectory", "api.file.list_directory.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   236  	}
   237  
   238  	return paths, nil
   239  }
   240  
   241  func (a *App) RemoveDirectory(path string) *model.AppError {
   242  	backend, err := a.FileBackend()
   243  	if err != nil {
   244  		return err
   245  	}
   246  	nErr := backend.RemoveDirectory(path)
   247  	if nErr != nil {
   248  		return model.NewAppError("RemoveDirectory", "api.file.remove_directory.app_error", nil, nErr.Error(), http.StatusInternalServerError)
   249  	}
   250  
   251  	return nil
   252  }
   253  
   254  func (a *App) getInfoForFilename(post *model.Post, teamID, channelId, userID, oldId, filename string) *model.FileInfo {
   255  	name, _ := url.QueryUnescape(filename)
   256  	pathPrefix := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/", teamID, channelId, userID, oldId)
   257  	path := pathPrefix + name
   258  
   259  	// Open the file and populate the fields of the FileInfo
   260  	data, err := a.ReadFile(path)
   261  	if err != nil {
   262  		mlog.Error(
   263  			"File not found when migrating post to use FileInfos",
   264  			mlog.String("post_id", post.Id),
   265  			mlog.String("filename", filename),
   266  			mlog.String("path", path),
   267  			mlog.Err(err),
   268  		)
   269  		return nil
   270  	}
   271  
   272  	info, err := model.GetInfoForBytes(name, bytes.NewReader(data), len(data))
   273  	if err != nil {
   274  		mlog.Warn(
   275  			"Unable to fully decode file info when migrating post to use FileInfos",
   276  			mlog.String("post_id", post.Id),
   277  			mlog.String("filename", filename),
   278  			mlog.Err(err),
   279  		)
   280  	}
   281  
   282  	// Generate a new ID because with the old system, you could very rarely get multiple posts referencing the same file
   283  	info.Id = model.NewId()
   284  	info.CreatorId = post.UserId
   285  	info.PostId = post.Id
   286  	info.CreateAt = post.CreateAt
   287  	info.UpdateAt = post.UpdateAt
   288  	info.Path = path
   289  
   290  	if info.IsImage() {
   291  		nameWithoutExtension := name[:strings.LastIndex(name, ".")]
   292  		info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg"
   293  		info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg"
   294  	}
   295  
   296  	return info
   297  }
   298  
   299  func (a *App) findTeamIdForFilename(post *model.Post, id, filename string) string {
   300  	name, _ := url.QueryUnescape(filename)
   301  
   302  	// This post is in a direct channel so we need to figure out what team the files are stored under.
   303  	teams, err := a.Srv().Store.Team().GetTeamsByUserId(post.UserId)
   304  	if err != nil {
   305  		mlog.Error("Unable to get teams when migrating post to use FileInfo", mlog.Err(err), mlog.String("post_id", post.Id))
   306  		return ""
   307  	}
   308  
   309  	if len(teams) == 1 {
   310  		// The user has only one team so the post must've been sent from it
   311  		return teams[0].Id
   312  	}
   313  
   314  	for _, team := range teams {
   315  		path := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/%s", team.Id, post.ChannelId, post.UserId, id, name)
   316  		if ok, err := a.FileExists(path); ok && err == nil {
   317  			// Found the team that this file was posted from
   318  			return team.Id
   319  		}
   320  	}
   321  
   322  	return ""
   323  }
   324  
   325  var fileMigrationLock sync.Mutex
   326  var oldFilenameMatchExp *regexp.Regexp = regexp.MustCompile(`^\/([a-z\d]{26})\/([a-z\d]{26})\/([a-z\d]{26})\/([^\/]+)$`)
   327  
   328  // Parse the path from the Filename of the form /{channelId}/{userID}/{uid}/{nameWithExtension}
   329  func parseOldFilenames(filenames []string, channelId, userID string) [][]string {
   330  	parsed := [][]string{}
   331  	for _, filename := range filenames {
   332  		matches := oldFilenameMatchExp.FindStringSubmatch(filename)
   333  		if len(matches) != 5 {
   334  			mlog.Error("Failed to parse old Filename", mlog.String("filename", filename))
   335  			continue
   336  		}
   337  		if matches[1] != channelId {
   338  			mlog.Error("ChannelId in Filename does not match", mlog.String("channel_id", channelId), mlog.String("matched", matches[1]))
   339  		} else if matches[2] != userID {
   340  			mlog.Error("UserId in Filename does not match", mlog.String("user_id", userID), mlog.String("matched", matches[2]))
   341  		} else {
   342  			parsed = append(parsed, matches[1:])
   343  		}
   344  	}
   345  	return parsed
   346  }
   347  
   348  // Creates and stores FileInfos for a post created before the FileInfos table existed.
   349  func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
   350  	if len(post.Filenames) == 0 {
   351  		mlog.Warn("Unable to migrate post to use FileInfos with an empty Filenames field", mlog.String("post_id", post.Id))
   352  		return []*model.FileInfo{}
   353  	}
   354  
   355  	channel, errCh := a.Srv().Store.Channel().Get(post.ChannelId, true)
   356  	// There's a weird bug that rarely happens where a post ends up with duplicate Filenames so remove those
   357  	filenames := utils.RemoveDuplicatesFromStringArray(post.Filenames)
   358  	if errCh != nil {
   359  		mlog.Error(
   360  			"Unable to get channel when migrating post to use FileInfos",
   361  			mlog.String("post_id", post.Id),
   362  			mlog.String("channel_id", post.ChannelId),
   363  			mlog.Err(errCh),
   364  		)
   365  		return []*model.FileInfo{}
   366  	}
   367  
   368  	// Parse and validate filenames before further processing
   369  	parsedFilenames := parseOldFilenames(filenames, post.ChannelId, post.UserId)
   370  
   371  	if len(parsedFilenames) == 0 {
   372  		mlog.Error("Unable to parse filenames")
   373  		return []*model.FileInfo{}
   374  	}
   375  
   376  	// Find the team that was used to make this post since its part of the file path that isn't saved in the Filename
   377  	var teamID string
   378  	if channel.TeamId == "" {
   379  		// This post was made in a cross-team DM channel, so we need to find where its files were saved
   380  		teamID = a.findTeamIdForFilename(post, parsedFilenames[0][2], parsedFilenames[0][3])
   381  	} else {
   382  		teamID = channel.TeamId
   383  	}
   384  
   385  	// Create FileInfo objects for this post
   386  	infos := make([]*model.FileInfo, 0, len(filenames))
   387  	if teamID == "" {
   388  		mlog.Error(
   389  			"Unable to find team id for files when migrating post to use FileInfos",
   390  			mlog.String("filenames", strings.Join(filenames, ",")),
   391  			mlog.String("post_id", post.Id),
   392  		)
   393  	} else {
   394  		for _, parsed := range parsedFilenames {
   395  			info := a.getInfoForFilename(post, teamID, parsed[0], parsed[1], parsed[2], parsed[3])
   396  			if info == nil {
   397  				continue
   398  			}
   399  
   400  			infos = append(infos, info)
   401  		}
   402  	}
   403  
   404  	// Lock to prevent only one migration thread from trying to update the post at once, preventing duplicate FileInfos from being created
   405  	fileMigrationLock.Lock()
   406  	defer fileMigrationLock.Unlock()
   407  
   408  	result, nErr := a.Srv().Store.Post().Get(post.Id, false, false, false)
   409  	if nErr != nil {
   410  		mlog.Error("Unable to get post when migrating post to use FileInfos", mlog.Err(nErr), mlog.String("post_id", post.Id))
   411  		return []*model.FileInfo{}
   412  	}
   413  
   414  	if newPost := result.Posts[post.Id]; len(newPost.Filenames) != len(post.Filenames) {
   415  		// Another thread has already created FileInfos for this post, so just return those
   416  		var fileInfos []*model.FileInfo
   417  		fileInfos, nErr = a.Srv().Store.FileInfo().GetForPost(post.Id, true, false, false)
   418  		if nErr != nil {
   419  			mlog.Error("Unable to get FileInfos for migrated post", mlog.Err(nErr), mlog.String("post_id", post.Id))
   420  			return []*model.FileInfo{}
   421  		}
   422  
   423  		mlog.Debug("Post already migrated to use FileInfos", mlog.String("post_id", post.Id))
   424  		return fileInfos
   425  	}
   426  
   427  	mlog.Debug("Migrating post to use FileInfos", mlog.String("post_id", post.Id))
   428  
   429  	savedInfos := make([]*model.FileInfo, 0, len(infos))
   430  	fileIds := make([]string, 0, len(filenames))
   431  	for _, info := range infos {
   432  		if _, nErr = a.Srv().Store.FileInfo().Save(info); nErr != nil {
   433  			mlog.Error(
   434  				"Unable to save file info when migrating post to use FileInfos",
   435  				mlog.String("post_id", post.Id),
   436  				mlog.String("file_info_id", info.Id),
   437  				mlog.String("file_info_path", info.Path),
   438  				mlog.Err(nErr),
   439  			)
   440  			continue
   441  		}
   442  
   443  		savedInfos = append(savedInfos, info)
   444  		fileIds = append(fileIds, info.Id)
   445  	}
   446  
   447  	// Copy and save the updated post
   448  	newPost := post.Clone()
   449  
   450  	newPost.Filenames = []string{}
   451  	newPost.FileIds = fileIds
   452  
   453  	// Update Posts to clear Filenames and set FileIds
   454  	if _, nErr = a.Srv().Store.Post().Update(newPost, post); nErr != nil {
   455  		mlog.Error(
   456  			"Unable to save migrated post when migrating to use FileInfos",
   457  			mlog.String("new_file_ids", strings.Join(newPost.FileIds, ",")),
   458  			mlog.String("old_filenames", strings.Join(post.Filenames, ",")),
   459  			mlog.String("post_id", post.Id),
   460  			mlog.Err(nErr),
   461  		)
   462  		return []*model.FileInfo{}
   463  	}
   464  	return savedInfos
   465  }
   466  
   467  func (a *App) GeneratePublicLink(siteURL string, info *model.FileInfo) string {
   468  	hash := GeneratePublicLinkHash(info.Id, *a.Config().FileSettings.PublicLinkSalt)
   469  	return fmt.Sprintf("%s/files/%v/public?h=%s", siteURL, info.Id, hash)
   470  }
   471  
   472  func GeneratePublicLinkHash(fileId, salt string) string {
   473  	hash := sha256.New()
   474  	hash.Write([]byte(salt))
   475  	hash.Write([]byte(fileId))
   476  
   477  	return base64.RawURLEncoding.EncodeToString(hash.Sum(nil))
   478  }
   479  
   480  func (a *App) UploadMultipartFiles(teamID string, channelId string, userID string, fileHeaders []*multipart.FileHeader, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) {
   481  	files := make([]io.ReadCloser, len(fileHeaders))
   482  	filenames := make([]string, len(fileHeaders))
   483  
   484  	for i, fileHeader := range fileHeaders {
   485  		file, fileErr := fileHeader.Open()
   486  		if fileErr != nil {
   487  			return nil, model.NewAppError("UploadFiles", "api.file.upload_file.read_request.app_error",
   488  				map[string]interface{}{"Filename": fileHeader.Filename}, fileErr.Error(), http.StatusBadRequest)
   489  		}
   490  
   491  		// Will be closed after UploadFiles returns
   492  		defer file.Close()
   493  
   494  		files[i] = file
   495  		filenames[i] = fileHeader.Filename
   496  	}
   497  
   498  	return a.UploadFiles(teamID, channelId, userID, files, filenames, clientIds, now)
   499  }
   500  
   501  // Uploads some files to the given team and channel as the given user. files and filenames should have
   502  // the same length. clientIds should either not be provided or have the same length as files and filenames.
   503  // The provided files should be closed by the caller so that they are not leaked.
   504  func (a *App) UploadFiles(teamID string, channelId string, userID string, files []io.ReadCloser, filenames []string, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) {
   505  	if *a.Config().FileSettings.DriverName == "" {
   506  		return nil, model.NewAppError("UploadFiles", "api.file.upload_file.storage.app_error", nil, "", http.StatusNotImplemented)
   507  	}
   508  
   509  	if len(filenames) != len(files) || (len(clientIds) > 0 && len(clientIds) != len(files)) {
   510  		return nil, model.NewAppError("UploadFiles", "api.file.upload_file.incorrect_number_of_files.app_error", nil, "", http.StatusBadRequest)
   511  	}
   512  
   513  	resStruct := &model.FileUploadResponse{
   514  		FileInfos: []*model.FileInfo{},
   515  		ClientIds: []string{},
   516  	}
   517  
   518  	previewPathList := []string{}
   519  	thumbnailPathList := []string{}
   520  	imageDataList := [][]byte{}
   521  
   522  	for i, file := range files {
   523  		buf := bytes.NewBuffer(nil)
   524  		io.Copy(buf, file)
   525  		data := buf.Bytes()
   526  
   527  		info, data, err := a.DoUploadFileExpectModification(now, teamID, channelId, userID, filenames[i], data)
   528  		if err != nil {
   529  			return nil, err
   530  		}
   531  
   532  		if info.PreviewPath != "" || info.ThumbnailPath != "" {
   533  			previewPathList = append(previewPathList, info.PreviewPath)
   534  			thumbnailPathList = append(thumbnailPathList, info.ThumbnailPath)
   535  			imageDataList = append(imageDataList, data)
   536  		}
   537  
   538  		resStruct.FileInfos = append(resStruct.FileInfos, info)
   539  
   540  		if len(clientIds) > 0 {
   541  			resStruct.ClientIds = append(resStruct.ClientIds, clientIds[i])
   542  		}
   543  	}
   544  
   545  	a.HandleImages(previewPathList, thumbnailPathList, imageDataList)
   546  
   547  	return resStruct, nil
   548  }
   549  
   550  // UploadFile uploads a single file in form of a completely constructed byte array for a channel.
   551  func (a *App) UploadFile(data []byte, channelId string, filename string) (*model.FileInfo, *model.AppError) {
   552  	_, err := a.GetChannel(channelId)
   553  	if err != nil && channelId != "" {
   554  		return nil, model.NewAppError("UploadFile", "api.file.upload_file.incorrect_channelId.app_error",
   555  			map[string]interface{}{"channelId": channelId}, "", http.StatusBadRequest)
   556  	}
   557  
   558  	info, _, appError := a.DoUploadFileExpectModification(time.Now(), "noteam", channelId, "nouser", filename, data)
   559  	if appError != nil {
   560  		return nil, appError
   561  	}
   562  
   563  	if info.PreviewPath != "" || info.ThumbnailPath != "" {
   564  		previewPathList := []string{info.PreviewPath}
   565  		thumbnailPathList := []string{info.ThumbnailPath}
   566  		imageDataList := [][]byte{data}
   567  
   568  		a.HandleImages(previewPathList, thumbnailPathList, imageDataList)
   569  	}
   570  
   571  	return info, nil
   572  }
   573  
   574  func (a *App) DoUploadFile(now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, *model.AppError) {
   575  	info, _, err := a.DoUploadFileExpectModification(now, rawTeamId, rawChannelId, rawUserId, rawFilename, data)
   576  	return info, err
   577  }
   578  
   579  func UploadFileSetTeamId(teamID string) func(t *UploadFileTask) {
   580  	return func(t *UploadFileTask) {
   581  		t.TeamId = filepath.Base(teamID)
   582  	}
   583  }
   584  
   585  func UploadFileSetUserId(userID string) func(t *UploadFileTask) {
   586  	return func(t *UploadFileTask) {
   587  		t.UserId = filepath.Base(userID)
   588  	}
   589  }
   590  
   591  func UploadFileSetTimestamp(timestamp time.Time) func(t *UploadFileTask) {
   592  	return func(t *UploadFileTask) {
   593  		t.Timestamp = timestamp
   594  	}
   595  }
   596  
   597  func UploadFileSetContentLength(contentLength int64) func(t *UploadFileTask) {
   598  	return func(t *UploadFileTask) {
   599  		t.ContentLength = contentLength
   600  	}
   601  }
   602  
   603  func UploadFileSetClientId(clientId string) func(t *UploadFileTask) {
   604  	return func(t *UploadFileTask) {
   605  		t.ClientId = clientId
   606  	}
   607  }
   608  
   609  func UploadFileSetRaw() func(t *UploadFileTask) {
   610  	return func(t *UploadFileTask) {
   611  		t.Raw = true
   612  	}
   613  }
   614  
   615  type UploadFileTask struct {
   616  	// File name.
   617  	Name string
   618  
   619  	ChannelId string
   620  	TeamId    string
   621  	UserId    string
   622  
   623  	// Time stamp to use when creating the file.
   624  	Timestamp time.Time
   625  
   626  	// The value of the Content-Length http header, when available.
   627  	ContentLength int64
   628  
   629  	// The file data stream.
   630  	Input io.Reader
   631  
   632  	// An optional, client-assigned Id field.
   633  	ClientId string
   634  
   635  	// If Raw, do not execute special processing for images, just upload
   636  	// the file.  Plugins are still invoked.
   637  	Raw bool
   638  
   639  	//=============================================================
   640  	// Internal state
   641  
   642  	buf          *bytes.Buffer
   643  	limit        int64
   644  	limitedInput io.Reader
   645  	teeInput     io.Reader
   646  	fileinfo     *model.FileInfo
   647  	maxFileSize  int64
   648  
   649  	// Cached image data that (may) get initialized in preprocessImage and
   650  	// is used in postprocessImage
   651  	decoded          image.Image
   652  	imageType        string
   653  	imageOrientation int
   654  
   655  	// Testing: overrideable dependency functions
   656  	pluginsEnvironment *plugin.Environment
   657  	writeFile          func(io.Reader, string) (int64, *model.AppError)
   658  	saveToDatabase     func(*model.FileInfo) (*model.FileInfo, error)
   659  }
   660  
   661  func (t *UploadFileTask) init(a *App) {
   662  	t.buf = &bytes.Buffer{}
   663  	if t.ContentLength > 0 {
   664  		t.limit = t.ContentLength
   665  	} else {
   666  		t.limit = t.maxFileSize
   667  	}
   668  
   669  	if t.ContentLength > 0 && t.ContentLength < maxUploadInitialBufferSize {
   670  		t.buf.Grow(int(t.ContentLength))
   671  	} else {
   672  		t.buf.Grow(maxUploadInitialBufferSize)
   673  	}
   674  
   675  	t.fileinfo = model.NewInfo(filepath.Base(t.Name))
   676  	t.fileinfo.Id = model.NewId()
   677  	t.fileinfo.CreatorId = t.UserId
   678  	t.fileinfo.CreateAt = t.Timestamp.UnixNano() / int64(time.Millisecond)
   679  	t.fileinfo.Path = t.pathPrefix() + t.Name
   680  
   681  	t.limitedInput = &io.LimitedReader{
   682  		R: t.Input,
   683  		N: t.limit + 1,
   684  	}
   685  	t.teeInput = io.TeeReader(t.limitedInput, t.buf)
   686  
   687  	t.pluginsEnvironment = a.GetPluginsEnvironment()
   688  	t.writeFile = a.WriteFile
   689  	t.saveToDatabase = a.Srv().Store.FileInfo().Save
   690  }
   691  
   692  // UploadFileX uploads a single file as specified in t. It applies the upload
   693  // constraints, executes plugins and image processing logic as needed. It
   694  // returns a filled-out FileInfo and an optional error. A plugin may reject the
   695  // upload, returning a rejection error. In this case FileInfo would have
   696  // contained the last "good" FileInfo before the execution of that plugin.
   697  func (a *App) UploadFileX(channelId, name string, input io.Reader,
   698  	opts ...func(*UploadFileTask)) (*model.FileInfo, *model.AppError) {
   699  
   700  	t := &UploadFileTask{
   701  		ChannelId:   filepath.Base(channelId),
   702  		Name:        filepath.Base(name),
   703  		Input:       input,
   704  		maxFileSize: *a.Config().FileSettings.MaxFileSize,
   705  	}
   706  	for _, o := range opts {
   707  		o(t)
   708  	}
   709  
   710  	if *a.Config().FileSettings.DriverName == "" {
   711  		return nil, t.newAppError("api.file.upload_file.storage.app_error",
   712  			"", http.StatusNotImplemented)
   713  	}
   714  	if t.ContentLength > t.maxFileSize {
   715  		return nil, t.newAppError("api.file.upload_file.too_large_detailed.app_error",
   716  			"", http.StatusRequestEntityTooLarge, "Length", t.ContentLength, "Limit", t.maxFileSize)
   717  	}
   718  
   719  	t.init(a)
   720  
   721  	var aerr *model.AppError
   722  	if !t.Raw && t.fileinfo.IsImage() {
   723  		aerr = t.preprocessImage()
   724  		if aerr != nil {
   725  			return t.fileinfo, aerr
   726  		}
   727  	}
   728  
   729  	written, aerr := t.writeFile(io.MultiReader(t.buf, t.limitedInput), t.fileinfo.Path)
   730  	if aerr != nil {
   731  		return nil, aerr
   732  	}
   733  
   734  	if written > t.maxFileSize {
   735  		if fileErr := a.RemoveFile(t.fileinfo.Path); fileErr != nil {
   736  			mlog.Error("Failed to remove file", mlog.Err(fileErr))
   737  		}
   738  		return nil, t.newAppError("api.file.upload_file.too_large_detailed.app_error",
   739  			"", http.StatusRequestEntityTooLarge, "Length", t.ContentLength, "Limit", t.maxFileSize)
   740  	}
   741  
   742  	t.fileinfo.Size = written
   743  
   744  	file, aerr := a.FileReader(t.fileinfo.Path)
   745  	if aerr != nil {
   746  		return nil, aerr
   747  	}
   748  	defer file.Close()
   749  
   750  	aerr = a.runPluginsHook(t.fileinfo, file)
   751  	if aerr != nil {
   752  		return nil, aerr
   753  	}
   754  
   755  	if !t.Raw && t.fileinfo.IsImage() {
   756  		file, aerr = a.FileReader(t.fileinfo.Path)
   757  		if aerr != nil {
   758  			return nil, aerr
   759  		}
   760  		defer file.Close()
   761  		t.postprocessImage(file)
   762  	}
   763  
   764  	if _, err := t.saveToDatabase(t.fileinfo); err != nil {
   765  		var appErr *model.AppError
   766  		switch {
   767  		case errors.As(err, &appErr):
   768  			return nil, appErr
   769  		default:
   770  			return nil, model.NewAppError("UploadFileX", "app.file_info.save.app_error", nil, err.Error(), http.StatusInternalServerError)
   771  		}
   772  	}
   773  
   774  	return t.fileinfo, nil
   775  }
   776  
   777  func (t *UploadFileTask) preprocessImage() *model.AppError {
   778  	// If SVG, attempt to extract dimensions and then return
   779  	if t.fileinfo.MimeType == "image/svg+xml" {
   780  		svgInfo, err := parseSVG(t.teeInput)
   781  		if err != nil {
   782  			mlog.Warn("Failed to parse SVG", mlog.Err(err))
   783  		}
   784  		if svgInfo.Width > 0 && svgInfo.Height > 0 {
   785  			t.fileinfo.Width = svgInfo.Width
   786  			t.fileinfo.Height = svgInfo.Height
   787  		}
   788  		t.fileinfo.HasPreviewImage = false
   789  		return nil
   790  	}
   791  
   792  	// If we fail to decode, return "as is".
   793  	config, _, err := image.DecodeConfig(t.teeInput)
   794  	if err != nil {
   795  		return nil
   796  	}
   797  
   798  	t.fileinfo.Width = config.Width
   799  	t.fileinfo.Height = config.Height
   800  
   801  	// Check dimensions before loading the whole thing into memory later on.
   802  	// This casting is done to prevent overflow on 32 bit systems (not needed
   803  	// in 64 bits systems because images can't have more than 32 bits height or
   804  	// width)
   805  	if int64(t.fileinfo.Width)*int64(t.fileinfo.Height) > MaxImageSize {
   806  		return t.newAppError("api.file.upload_file.large_image_detailed.app_error",
   807  			"", http.StatusBadRequest)
   808  	}
   809  	t.fileinfo.HasPreviewImage = true
   810  	nameWithoutExtension := t.Name[:strings.LastIndex(t.Name, ".")]
   811  	t.fileinfo.PreviewPath = t.pathPrefix() + nameWithoutExtension + "_preview.jpg"
   812  	t.fileinfo.ThumbnailPath = t.pathPrefix() + nameWithoutExtension + "_thumb.jpg"
   813  
   814  	// check the image orientation with goexif; consume the bytes we
   815  	// already have first, then keep Tee-ing from input.
   816  	// TODO: try to reuse exif's .Raw buffer rather than Tee-ing
   817  	if t.imageOrientation, err = getImageOrientation(io.MultiReader(bytes.NewReader(t.buf.Bytes()), t.teeInput)); err == nil &&
   818  		(t.imageOrientation == RotatedCWMirrored ||
   819  			t.imageOrientation == RotatedCCW ||
   820  			t.imageOrientation == RotatedCCWMirrored ||
   821  			t.imageOrientation == RotatedCW) {
   822  		t.fileinfo.Width, t.fileinfo.Height = t.fileinfo.Height, t.fileinfo.Width
   823  	}
   824  
   825  	// For animated GIFs disable the preview; since we have to Decode gifs
   826  	// anyway, cache the decoded image for later.
   827  	if t.fileinfo.MimeType == "image/gif" {
   828  		gifConfig, err := gif.DecodeAll(io.MultiReader(bytes.NewReader(t.buf.Bytes()), t.teeInput))
   829  		if err == nil {
   830  			if len(gifConfig.Image) > 0 {
   831  				t.fileinfo.HasPreviewImage = false
   832  				t.decoded = gifConfig.Image[0]
   833  				t.imageType = "gif"
   834  			}
   835  		}
   836  	}
   837  
   838  	return nil
   839  }
   840  
   841  func (t *UploadFileTask) postprocessImage(file io.Reader) {
   842  	// don't try to process SVG files
   843  	if t.fileinfo.MimeType == "image/svg+xml" {
   844  		return
   845  	}
   846  
   847  	decoded, typ := t.decoded, t.imageType
   848  	if decoded == nil {
   849  		var err error
   850  		decoded, typ, err = image.Decode(file)
   851  		if err != nil {
   852  			mlog.Error("Unable to decode image", mlog.Err(err))
   853  			return
   854  		}
   855  	}
   856  
   857  	// Fill in the background of a potentially-transparent png file as
   858  	// white.
   859  	if typ == "png" {
   860  		dst := image.NewRGBA(decoded.Bounds())
   861  		draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src)
   862  		draw.Draw(dst, dst.Bounds(), decoded, decoded.Bounds().Min, draw.Over)
   863  		decoded = dst
   864  	}
   865  
   866  	decoded = makeImageUpright(decoded, t.imageOrientation)
   867  	if decoded == nil {
   868  		return
   869  	}
   870  
   871  	const jpegQuality = 90
   872  	writeJPEG := func(img image.Image, path string) {
   873  		r, w := io.Pipe()
   874  		go func() {
   875  			err := jpeg.Encode(w, img, &jpeg.Options{Quality: jpegQuality})
   876  			if err != nil {
   877  				mlog.Error("Unable to encode image as jpeg", mlog.String("path", path), mlog.Err(err))
   878  				w.CloseWithError(err)
   879  			} else {
   880  				w.Close()
   881  			}
   882  		}()
   883  		_, aerr := t.writeFile(r, path)
   884  		if aerr != nil {
   885  			mlog.Error("Unable to upload", mlog.String("path", path), mlog.Err(aerr))
   886  			return
   887  		}
   888  	}
   889  
   890  	var wg sync.WaitGroup
   891  	wg.Add(3)
   892  	// Generating thumbnail and preview regardless of HasPreviewImage value.
   893  	// This is needed on mobile in case of animated GIFs.
   894  	go func() {
   895  		defer wg.Done()
   896  		writeJPEG(genThumbnail(decoded), t.fileinfo.ThumbnailPath)
   897  	}()
   898  
   899  	go func() {
   900  		defer wg.Done()
   901  		writeJPEG(genPreview(decoded), t.fileinfo.PreviewPath)
   902  	}()
   903  
   904  	go func() {
   905  		defer wg.Done()
   906  		if t.fileinfo.MiniPreview == nil {
   907  			t.fileinfo.MiniPreview = model.GenerateMiniPreviewImage(decoded)
   908  		}
   909  	}()
   910  	wg.Wait()
   911  }
   912  
   913  func (t UploadFileTask) pathPrefix() string {
   914  	return t.Timestamp.Format("20060102") +
   915  		"/teams/" + t.TeamId +
   916  		"/channels/" + t.ChannelId +
   917  		"/users/" + t.UserId +
   918  		"/" + t.fileinfo.Id + "/"
   919  }
   920  
   921  func (t UploadFileTask) newAppError(id string, details interface{}, httpStatus int, extra ...interface{}) *model.AppError {
   922  	params := map[string]interface{}{
   923  		"Name":          t.Name,
   924  		"Filename":      t.Name,
   925  		"ChannelId":     t.ChannelId,
   926  		"TeamId":        t.TeamId,
   927  		"UserId":        t.UserId,
   928  		"ContentLength": t.ContentLength,
   929  		"ClientId":      t.ClientId,
   930  	}
   931  	if t.fileinfo != nil {
   932  		params["Width"] = t.fileinfo.Width
   933  		params["Height"] = t.fileinfo.Height
   934  	}
   935  	for i := 0; i+1 < len(extra); i += 2 {
   936  		params[fmt.Sprintf("%v", extra[i])] = extra[i+1]
   937  	}
   938  
   939  	return model.NewAppError("uploadFileTask", id, params, fmt.Sprintf("%v", details), httpStatus)
   940  }
   941  
   942  func (a *App) DoUploadFileExpectModification(now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, []byte, *model.AppError) {
   943  	filename := filepath.Base(rawFilename)
   944  	teamID := filepath.Base(rawTeamId)
   945  	channelId := filepath.Base(rawChannelId)
   946  	userID := filepath.Base(rawUserId)
   947  
   948  	info, err := model.GetInfoForBytes(filename, bytes.NewReader(data), len(data))
   949  	if err != nil {
   950  		err.StatusCode = http.StatusBadRequest
   951  		return nil, data, err
   952  	}
   953  
   954  	if orientation, err := getImageOrientation(bytes.NewReader(data)); err == nil &&
   955  		(orientation == RotatedCWMirrored ||
   956  			orientation == RotatedCCW ||
   957  			orientation == RotatedCCWMirrored ||
   958  			orientation == RotatedCW) {
   959  		info.Width, info.Height = info.Height, info.Width
   960  	}
   961  
   962  	info.Id = model.NewId()
   963  	info.CreatorId = userID
   964  	info.CreateAt = now.UnixNano() / int64(time.Millisecond)
   965  
   966  	pathPrefix := now.Format("20060102") + "/teams/" + teamID + "/channels/" + channelId + "/users/" + userID + "/" + info.Id + "/"
   967  	info.Path = pathPrefix + filename
   968  
   969  	if info.IsImage() {
   970  		// Check dimensions before loading the whole thing into memory later on
   971  		// This casting is done to prevent overflow on 32 bit systems (not needed
   972  		// in 64 bits systems because images can't have more than 32 bits height or
   973  		// width)
   974  		if int64(info.Width)*int64(info.Height) > MaxImageSize {
   975  			err := model.NewAppError("uploadFile", "api.file.upload_file.large_image.app_error", map[string]interface{}{"Filename": filename}, "", http.StatusBadRequest)
   976  			return nil, data, err
   977  		}
   978  
   979  		nameWithoutExtension := filename[:strings.LastIndex(filename, ".")]
   980  		info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg"
   981  		info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg"
   982  	}
   983  
   984  	if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
   985  		var rejectionError *model.AppError
   986  		pluginContext := a.PluginContext()
   987  		pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool {
   988  			var newBytes bytes.Buffer
   989  			replacementInfo, rejectionReason := hooks.FileWillBeUploaded(pluginContext, info, bytes.NewReader(data), &newBytes)
   990  			if rejectionReason != "" {
   991  				rejectionError = model.NewAppError("DoUploadFile", "File rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest)
   992  				return false
   993  			}
   994  			if replacementInfo != nil {
   995  				info = replacementInfo
   996  			}
   997  			if newBytes.Len() != 0 {
   998  				data = newBytes.Bytes()
   999  				info.Size = int64(len(data))
  1000  			}
  1001  
  1002  			return true
  1003  		}, plugin.FileWillBeUploadedId)
  1004  		if rejectionError != nil {
  1005  			return nil, data, rejectionError
  1006  		}
  1007  	}
  1008  
  1009  	if _, err := a.WriteFile(bytes.NewReader(data), info.Path); err != nil {
  1010  		return nil, data, err
  1011  	}
  1012  
  1013  	if _, err := a.Srv().Store.FileInfo().Save(info); err != nil {
  1014  		var appErr *model.AppError
  1015  		switch {
  1016  		case errors.As(err, &appErr):
  1017  			return nil, data, appErr
  1018  		default:
  1019  			return nil, data, model.NewAppError("DoUploadFileExpectModification", "app.file_info.save.app_error", nil, err.Error(), http.StatusInternalServerError)
  1020  		}
  1021  	}
  1022  
  1023  	return info, data, nil
  1024  }
  1025  
  1026  func (a *App) HandleImages(previewPathList []string, thumbnailPathList []string, fileData [][]byte) {
  1027  	wg := new(sync.WaitGroup)
  1028  
  1029  	for i := range fileData {
  1030  		img, _, _ := prepareImage(fileData[i])
  1031  		if img != nil {
  1032  			wg.Add(2)
  1033  			go func(img image.Image, path string) {
  1034  				defer wg.Done()
  1035  				a.generateThumbnailImage(img, path)
  1036  			}(img, thumbnailPathList[i])
  1037  
  1038  			go func(img image.Image, path string) {
  1039  				defer wg.Done()
  1040  				a.generatePreviewImage(img, path)
  1041  			}(img, previewPathList[i])
  1042  		}
  1043  	}
  1044  	wg.Wait()
  1045  }
  1046  
  1047  func prepareImage(fileData []byte) (image.Image, int, int) {
  1048  	// Decode image bytes into Image object
  1049  	img, imgType, err := image.Decode(bytes.NewReader(fileData))
  1050  	if err != nil {
  1051  		mlog.Error("Unable to decode image", mlog.Err(err))
  1052  		return nil, 0, 0
  1053  	}
  1054  
  1055  	width := img.Bounds().Dx()
  1056  	height := img.Bounds().Dy()
  1057  
  1058  	// Fill in the background of a potentially-transparent png file as white
  1059  	if imgType == "png" {
  1060  		dst := image.NewRGBA(img.Bounds())
  1061  		draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src)
  1062  		draw.Draw(dst, dst.Bounds(), img, img.Bounds().Min, draw.Over)
  1063  		img = dst
  1064  	}
  1065  
  1066  	// Flip the image to be upright
  1067  	orientation, _ := getImageOrientation(bytes.NewReader(fileData))
  1068  	img = makeImageUpright(img, orientation)
  1069  
  1070  	return img, width, height
  1071  }
  1072  
  1073  func makeImageUpright(img image.Image, orientation int) image.Image {
  1074  	switch orientation {
  1075  	case UprightMirrored:
  1076  		return imaging.FlipH(img)
  1077  	case UpsideDown:
  1078  		return imaging.Rotate180(img)
  1079  	case UpsideDownMirrored:
  1080  		return imaging.FlipV(img)
  1081  	case RotatedCWMirrored:
  1082  		return imaging.Transpose(img)
  1083  	case RotatedCCW:
  1084  		return imaging.Rotate270(img)
  1085  	case RotatedCCWMirrored:
  1086  		return imaging.Transverse(img)
  1087  	case RotatedCW:
  1088  		return imaging.Rotate90(img)
  1089  	default:
  1090  		return img
  1091  	}
  1092  }
  1093  
  1094  func getImageOrientation(input io.Reader) (int, error) {
  1095  	exifData, err := exif.Decode(input)
  1096  	if err != nil {
  1097  		return Upright, err
  1098  	}
  1099  
  1100  	tag, err := exifData.Get("Orientation")
  1101  	if err != nil {
  1102  		return Upright, err
  1103  	}
  1104  
  1105  	orientation, err := tag.Int(0)
  1106  	if err != nil {
  1107  		return Upright, err
  1108  	}
  1109  
  1110  	return orientation, nil
  1111  }
  1112  
  1113  func (a *App) generateThumbnailImage(img image.Image, thumbnailPath string) {
  1114  	buf := new(bytes.Buffer)
  1115  	if err := jpeg.Encode(buf, genThumbnail(img), &jpeg.Options{Quality: 90}); err != nil {
  1116  		mlog.Error("Unable to encode image as jpeg", mlog.String("path", thumbnailPath), mlog.Err(err))
  1117  		return
  1118  	}
  1119  
  1120  	if _, err := a.WriteFile(buf, thumbnailPath); err != nil {
  1121  		mlog.Error("Unable to upload thumbnail", mlog.String("path", thumbnailPath), mlog.Err(err))
  1122  		return
  1123  	}
  1124  }
  1125  
  1126  func (a *App) generatePreviewImage(img image.Image, previewPath string) {
  1127  	preview := genPreview(img)
  1128  
  1129  	buf := new(bytes.Buffer)
  1130  
  1131  	if err := jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90}); err != nil {
  1132  		mlog.Error("Unable to encode image as preview jpg", mlog.Err(err), mlog.String("path", previewPath))
  1133  		return
  1134  	}
  1135  
  1136  	if _, err := a.WriteFile(buf, previewPath); err != nil {
  1137  		mlog.Error("Unable to upload preview", mlog.Err(err), mlog.String("path", previewPath))
  1138  		return
  1139  	}
  1140  }
  1141  
  1142  // generateMiniPreview updates mini preview if needed
  1143  // will save fileinfo with the preview added
  1144  func (a *App) generateMiniPreview(fi *model.FileInfo) {
  1145  	if fi.IsImage() && fi.MiniPreview == nil {
  1146  		data, err := a.ReadFile(fi.Path)
  1147  		if err != nil {
  1148  			mlog.Error("error reading image file", mlog.Err(err))
  1149  			return
  1150  		}
  1151  		img, _, _ := prepareImage(data)
  1152  		if img == nil {
  1153  			return
  1154  		}
  1155  		fi.MiniPreview = model.GenerateMiniPreviewImage(img)
  1156  		if _, appErr := a.Srv().Store.FileInfo().Upsert(fi); appErr != nil {
  1157  			mlog.Error("creating mini preview failed", mlog.Err(appErr))
  1158  		} else {
  1159  			a.Srv().Store.FileInfo().InvalidateFileInfosForPostCache(fi.PostId, false)
  1160  		}
  1161  	}
  1162  }
  1163  
  1164  func (a *App) generateMiniPreviewForInfos(fileInfos []*model.FileInfo) {
  1165  	wg := new(sync.WaitGroup)
  1166  
  1167  	wg.Add(len(fileInfos))
  1168  	for _, fileInfo := range fileInfos {
  1169  		go func(fi *model.FileInfo) {
  1170  			defer wg.Done()
  1171  			a.generateMiniPreview(fi)
  1172  		}(fileInfo)
  1173  	}
  1174  	wg.Wait()
  1175  }
  1176  
  1177  func (a *App) GetFileInfo(fileId string) (*model.FileInfo, *model.AppError) {
  1178  	fileInfo, err := a.Srv().Store.FileInfo().Get(fileId)
  1179  	if err != nil {
  1180  		var nfErr *store.ErrNotFound
  1181  		switch {
  1182  		case errors.As(err, &nfErr):
  1183  			return nil, model.NewAppError("GetFileInfo", "app.file_info.get.app_error", nil, nfErr.Error(), http.StatusNotFound)
  1184  		default:
  1185  			return nil, model.NewAppError("GetFileInfo", "app.file_info.get.app_error", nil, err.Error(), http.StatusInternalServerError)
  1186  		}
  1187  	}
  1188  
  1189  	a.generateMiniPreview(fileInfo)
  1190  	return fileInfo, nil
  1191  }
  1192  
  1193  func (a *App) GetFileInfos(page, perPage int, opt *model.GetFileInfosOptions) ([]*model.FileInfo, *model.AppError) {
  1194  	fileInfos, err := a.Srv().Store.FileInfo().GetWithOptions(page, perPage, opt)
  1195  	if err != nil {
  1196  		var invErr *store.ErrInvalidInput
  1197  		var ltErr *store.ErrLimitExceeded
  1198  		switch {
  1199  		case errors.As(err, &invErr):
  1200  			return nil, model.NewAppError("GetFileInfos", "app.file_info.get_with_options.app_error", nil, invErr.Error(), http.StatusBadRequest)
  1201  		case errors.As(err, &ltErr):
  1202  			return nil, model.NewAppError("GetFileInfos", "app.file_info.get_with_options.app_error", nil, ltErr.Error(), http.StatusBadRequest)
  1203  		default:
  1204  			return nil, model.NewAppError("GetFileInfos", "app.file_info.get_with_options.app_error", nil, err.Error(), http.StatusInternalServerError)
  1205  		}
  1206  	}
  1207  
  1208  	a.generateMiniPreviewForInfos(fileInfos)
  1209  
  1210  	return fileInfos, nil
  1211  }
  1212  
  1213  func (a *App) GetFile(fileId string) ([]byte, *model.AppError) {
  1214  	info, err := a.GetFileInfo(fileId)
  1215  	if err != nil {
  1216  		return nil, err
  1217  	}
  1218  
  1219  	data, err := a.ReadFile(info.Path)
  1220  	if err != nil {
  1221  		return nil, err
  1222  	}
  1223  
  1224  	return data, nil
  1225  }
  1226  
  1227  func (a *App) CopyFileInfos(userID string, fileIds []string) ([]string, *model.AppError) {
  1228  	var newFileIds []string
  1229  
  1230  	now := model.GetMillis()
  1231  
  1232  	for _, fileId := range fileIds {
  1233  		fileInfo, err := a.Srv().Store.FileInfo().Get(fileId)
  1234  		if err != nil {
  1235  			var nfErr *store.ErrNotFound
  1236  			switch {
  1237  			case errors.As(err, &nfErr):
  1238  				return nil, model.NewAppError("CopyFileInfos", "app.file_info.get.app_error", nil, nfErr.Error(), http.StatusNotFound)
  1239  			default:
  1240  				return nil, model.NewAppError("CopyFileInfos", "app.file_info.get.app_error", nil, err.Error(), http.StatusInternalServerError)
  1241  			}
  1242  		}
  1243  
  1244  		fileInfo.Id = model.NewId()
  1245  		fileInfo.CreatorId = userID
  1246  		fileInfo.CreateAt = now
  1247  		fileInfo.UpdateAt = now
  1248  		fileInfo.PostId = ""
  1249  
  1250  		if _, err := a.Srv().Store.FileInfo().Save(fileInfo); err != nil {
  1251  			var appErr *model.AppError
  1252  			switch {
  1253  			case errors.As(err, &appErr):
  1254  				return nil, appErr
  1255  			default:
  1256  				return nil, model.NewAppError("CopyFileInfos", "app.file_info.save.app_error", nil, err.Error(), http.StatusInternalServerError)
  1257  			}
  1258  		}
  1259  
  1260  		newFileIds = append(newFileIds, fileInfo.Id)
  1261  	}
  1262  
  1263  	return newFileIds, nil
  1264  }
  1265  
  1266  // This function zip's up all the files in fileDatas array and then saves it to the directory specified with the specified zip file name
  1267  // Ensure the zip file name ends with a .zip
  1268  func (a *App) CreateZipFileAndAddFiles(fileBackend filesstore.FileBackend, fileDatas []model.FileData, zipFileName, directory string) error {
  1269  	// Create Zip File (temporarily stored on disk)
  1270  	conglomerateZipFile, err := os.Create(zipFileName)
  1271  	if err != nil {
  1272  		return err
  1273  	}
  1274  	defer os.Remove(zipFileName)
  1275  
  1276  	// Create a new zip archive.
  1277  	zipFileWriter := zip.NewWriter(conglomerateZipFile)
  1278  
  1279  	// Populate Zip file with File Datas array
  1280  	err = populateZipfile(zipFileWriter, fileDatas)
  1281  	if err != nil {
  1282  		return err
  1283  	}
  1284  
  1285  	conglomerateZipFile.Seek(0, 0)
  1286  	_, err = fileBackend.WriteFile(conglomerateZipFile, path.Join(directory, zipFileName))
  1287  	if err != nil {
  1288  		return err
  1289  	}
  1290  
  1291  	return nil
  1292  }
  1293  
  1294  // This is a implementation of Go's example of writing files to zip (with slight modification)
  1295  // https://golang.org/src/archive/zip/example_test.go
  1296  func populateZipfile(w *zip.Writer, fileDatas []model.FileData) error {
  1297  	defer w.Close()
  1298  	for _, fd := range fileDatas {
  1299  		f, err := w.Create(fd.Filename)
  1300  		if err != nil {
  1301  			return err
  1302  		}
  1303  
  1304  		_, err = f.Write(fd.Body)
  1305  		if err != nil {
  1306  			return err
  1307  		}
  1308  	}
  1309  	return nil
  1310  }