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