github.com/vnforks/kid@v5.11.1+incompatible/app/file.go (about)

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