github.com/coincircle/mattermost-server@v4.8.1-0.20180321182714-9d701c704416+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  	l4g "github.com/alecthomas/log4go"
    26  	"github.com/disintegration/imaging"
    27  	"github.com/rwcarlsen/goexif/exif"
    28  	_ "golang.org/x/image/bmp"
    29  
    30  	"github.com/mattermost/mattermost-server/model"
    31  	"github.com/mattermost/mattermost-server/utils"
    32  )
    33  
    34  const (
    35  	/*
    36  	  EXIF Image Orientations
    37  	  1        2       3      4         5            6           7          8
    38  
    39  	  888888  888888      88  88      8888888888  88                  88  8888888888
    40  	  88          88      88  88      88  88      88  88          88  88      88  88
    41  	  8888      8888    8888  8888    88          8888888888  8888888888          88
    42  	  88          88      88  88
    43  	  88          88  888888  888888
    44  	*/
    45  	Upright            = 1
    46  	UprightMirrored    = 2
    47  	UpsideDown         = 3
    48  	UpsideDownMirrored = 4
    49  	RotatedCWMirrored  = 5
    50  	RotatedCCW         = 6
    51  	RotatedCCWMirrored = 7
    52  	RotatedCW          = 8
    53  
    54  	MaxImageSize                 = 6048 * 4032 // 24 megapixels, roughly 36MB as a raw image
    55  	IMAGE_THUMBNAIL_PIXEL_WIDTH  = 120
    56  	IMAGE_THUMBNAIL_PIXEL_HEIGHT = 100
    57  	IMAGE_PREVIEW_PIXEL_WIDTH    = 1024
    58  )
    59  
    60  func (a *App) FileBackend() (utils.FileBackend, *model.AppError) {
    61  	license := a.License()
    62  	return utils.NewFileBackend(&a.Config().FileSettings, license != nil && *license.Features.Compliance)
    63  }
    64  
    65  func (a *App) ReadFile(path string) ([]byte, *model.AppError) {
    66  	backend, err := a.FileBackend()
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	return backend.ReadFile(path)
    71  }
    72  
    73  func (a *App) MoveFile(oldPath, newPath string) *model.AppError {
    74  	backend, err := a.FileBackend()
    75  	if err != nil {
    76  		return err
    77  	}
    78  	return backend.MoveFile(oldPath, newPath)
    79  }
    80  
    81  func (a *App) WriteFile(f []byte, path string) *model.AppError {
    82  	backend, err := a.FileBackend()
    83  	if err != nil {
    84  		return err
    85  	}
    86  	return backend.WriteFile(f, path)
    87  }
    88  
    89  func (a *App) RemoveFile(path string) *model.AppError {
    90  	backend, err := a.FileBackend()
    91  	if err != nil {
    92  		return err
    93  	}
    94  	return backend.RemoveFile(path)
    95  }
    96  
    97  func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename string) *model.FileInfo {
    98  	// Find the path from the Filename of the form /{channelId}/{userId}/{uid}/{nameWithExtension}
    99  	split := strings.SplitN(filename, "/", 5)
   100  	if len(split) < 5 {
   101  		l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.unexpected_filename.error"), post.Id, filename)
   102  		return nil
   103  	}
   104  
   105  	channelId := split[1]
   106  	userId := split[2]
   107  	oldId := split[3]
   108  	name, _ := url.QueryUnescape(split[4])
   109  
   110  	if split[0] != "" || split[1] != post.ChannelId || split[2] != post.UserId || strings.Contains(split[4], "/") {
   111  		l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.mismatched_filename.warn"), post.Id, post.ChannelId, post.UserId, filename)
   112  	}
   113  
   114  	pathPrefix := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/", teamId, channelId, userId, oldId)
   115  	path := pathPrefix + name
   116  
   117  	// Open the file and populate the fields of the FileInfo
   118  	var info *model.FileInfo
   119  	if data, err := a.ReadFile(path); err != nil {
   120  		l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.file_not_found.error"), post.Id, filename, path, err)
   121  		return nil
   122  	} else {
   123  		var err *model.AppError
   124  		info, err = model.GetInfoForBytes(name, data)
   125  		if err != nil {
   126  			l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.info.app_error"), post.Id, filename, err)
   127  		}
   128  	}
   129  
   130  	// Generate a new ID because with the old system, you could very rarely get multiple posts referencing the same file
   131  	info.Id = model.NewId()
   132  	info.CreatorId = post.UserId
   133  	info.PostId = post.Id
   134  	info.CreateAt = post.CreateAt
   135  	info.UpdateAt = post.UpdateAt
   136  	info.Path = path
   137  
   138  	if info.IsImage() {
   139  		nameWithoutExtension := name[:strings.LastIndex(name, ".")]
   140  		info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg"
   141  		info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg"
   142  	}
   143  
   144  	return info
   145  }
   146  
   147  func (a *App) FindTeamIdForFilename(post *model.Post, filename string) string {
   148  	split := strings.SplitN(filename, "/", 5)
   149  	id := split[3]
   150  	name, _ := url.QueryUnescape(split[4])
   151  
   152  	// This post is in a direct channel so we need to figure out what team the files are stored under.
   153  	if result := <-a.Srv.Store.Team().GetTeamsByUserId(post.UserId); result.Err != nil {
   154  		l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.teams.app_error"), post.Id, result.Err)
   155  	} else if teams := result.Data.([]*model.Team); len(teams) == 1 {
   156  		// The user has only one team so the post must've been sent from it
   157  		return teams[0].Id
   158  	} else {
   159  		for _, team := range teams {
   160  			path := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/%s", team.Id, post.ChannelId, post.UserId, id, name)
   161  			if _, err := a.ReadFile(path); err == nil {
   162  				// Found the team that this file was posted from
   163  				return team.Id
   164  			}
   165  		}
   166  	}
   167  
   168  	return ""
   169  }
   170  
   171  var fileMigrationLock sync.Mutex
   172  
   173  // Creates and stores FileInfos for a post created before the FileInfos table existed.
   174  func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
   175  	if len(post.Filenames) == 0 {
   176  		l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.no_filenames.warn"), post.Id)
   177  		return []*model.FileInfo{}
   178  	}
   179  
   180  	cchan := a.Srv.Store.Channel().Get(post.ChannelId, true)
   181  
   182  	// There's a weird bug that rarely happens where a post ends up with duplicate Filenames so remove those
   183  	filenames := utils.RemoveDuplicatesFromStringArray(post.Filenames)
   184  
   185  	var channel *model.Channel
   186  	if result := <-cchan; result.Err != nil {
   187  		l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.channel.app_error"), post.Id, post.ChannelId, result.Err)
   188  		return []*model.FileInfo{}
   189  	} else {
   190  		channel = result.Data.(*model.Channel)
   191  	}
   192  
   193  	// Find the team that was used to make this post since its part of the file path that isn't saved in the Filename
   194  	var teamId string
   195  	if channel.TeamId == "" {
   196  		// This post was made in a cross-team DM channel so we need to find where its files were saved
   197  		teamId = a.FindTeamIdForFilename(post, filenames[0])
   198  	} else {
   199  		teamId = channel.TeamId
   200  	}
   201  
   202  	// Create FileInfo objects for this post
   203  	infos := make([]*model.FileInfo, 0, len(filenames))
   204  	if teamId == "" {
   205  		l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.team_id.error"), post.Id, filenames)
   206  	} else {
   207  		for _, filename := range filenames {
   208  			info := a.GetInfoForFilename(post, teamId, filename)
   209  			if info == nil {
   210  				continue
   211  			}
   212  
   213  			infos = append(infos, info)
   214  		}
   215  	}
   216  
   217  	// Lock to prevent only one migration thread from trying to update the post at once, preventing duplicate FileInfos from being created
   218  	fileMigrationLock.Lock()
   219  	defer fileMigrationLock.Unlock()
   220  
   221  	if result := <-a.Srv.Store.Post().Get(post.Id); result.Err != nil {
   222  		l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.get_post_again.app_error"), post.Id, result.Err)
   223  		return []*model.FileInfo{}
   224  	} else if newPost := result.Data.(*model.PostList).Posts[post.Id]; len(newPost.Filenames) != len(post.Filenames) {
   225  		// Another thread has already created FileInfos for this post, so just return those
   226  		if result := <-a.Srv.Store.FileInfo().GetForPost(post.Id, true, false); result.Err != nil {
   227  			l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.get_post_file_infos_again.app_error"), post.Id, result.Err)
   228  			return []*model.FileInfo{}
   229  		} else {
   230  			l4g.Debug(utils.T("api.file.migrate_filenames_to_file_infos.not_migrating_post.debug"), post.Id)
   231  			return result.Data.([]*model.FileInfo)
   232  		}
   233  	}
   234  
   235  	l4g.Debug(utils.T("api.file.migrate_filenames_to_file_infos.migrating_post.debug"), post.Id)
   236  
   237  	savedInfos := make([]*model.FileInfo, 0, len(infos))
   238  	fileIds := make([]string, 0, len(filenames))
   239  	for _, info := range infos {
   240  		if result := <-a.Srv.Store.FileInfo().Save(info); result.Err != nil {
   241  			l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.save_file_info.app_error"), post.Id, info.Id, info.Path, result.Err)
   242  			continue
   243  		}
   244  
   245  		savedInfos = append(savedInfos, info)
   246  		fileIds = append(fileIds, info.Id)
   247  	}
   248  
   249  	// Copy and save the updated post
   250  	newPost := &model.Post{}
   251  	*newPost = *post
   252  
   253  	newPost.Filenames = []string{}
   254  	newPost.FileIds = fileIds
   255  
   256  	// Update Posts to clear Filenames and set FileIds
   257  	if result := <-a.Srv.Store.Post().Update(newPost, post); result.Err != nil {
   258  		l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.save_post.app_error"), post.Id, newPost.FileIds, post.Filenames, result.Err)
   259  		return []*model.FileInfo{}
   260  	} else {
   261  		return savedInfos
   262  	}
   263  }
   264  
   265  func (a *App) GeneratePublicLink(siteURL string, info *model.FileInfo) string {
   266  	hash := GeneratePublicLinkHash(info.Id, *a.Config().FileSettings.PublicLinkSalt)
   267  	return fmt.Sprintf("%s/files/%v/public?h=%s", siteURL, info.Id, hash)
   268  }
   269  
   270  func (a *App) GeneratePublicLinkV3(siteURL string, info *model.FileInfo) string {
   271  	hash := GeneratePublicLinkHash(info.Id, *a.Config().FileSettings.PublicLinkSalt)
   272  	return fmt.Sprintf("%s%s/public/files/%v/get?h=%s", siteURL, model.API_URL_SUFFIX_V3, info.Id, hash)
   273  }
   274  
   275  func GeneratePublicLinkHash(fileId, salt string) string {
   276  	hash := sha256.New()
   277  	hash.Write([]byte(salt))
   278  	hash.Write([]byte(fileId))
   279  
   280  	return base64.RawURLEncoding.EncodeToString(hash.Sum(nil))
   281  }
   282  
   283  func (a *App) UploadMultipartFiles(teamId string, channelId string, userId string, fileHeaders []*multipart.FileHeader, clientIds []string) (*model.FileUploadResponse, *model.AppError) {
   284  	files := make([]io.ReadCloser, len(fileHeaders))
   285  	filenames := make([]string, len(fileHeaders))
   286  
   287  	for i, fileHeader := range fileHeaders {
   288  		file, fileErr := fileHeader.Open()
   289  		if fileErr != nil {
   290  			return nil, model.NewAppError("UploadFiles", "api.file.upload_file.bad_parse.app_error", nil, fileErr.Error(), http.StatusBadRequest)
   291  		}
   292  
   293  		// Will be closed after UploadFiles returns
   294  		defer file.Close()
   295  
   296  		files[i] = file
   297  		filenames[i] = fileHeader.Filename
   298  	}
   299  
   300  	return a.UploadFiles(teamId, channelId, userId, files, filenames, clientIds)
   301  }
   302  
   303  // Uploads some files to the given team and channel as the given user. files and filenames should have
   304  // the same length. clientIds should either not be provided or have the same length as files and filenames.
   305  // The provided files should be closed by the caller so that they are not leaked.
   306  func (a *App) UploadFiles(teamId string, channelId string, userId string, files []io.ReadCloser, filenames []string, clientIds []string) (*model.FileUploadResponse, *model.AppError) {
   307  	if len(*a.Config().FileSettings.DriverName) == 0 {
   308  		return nil, model.NewAppError("uploadFile", "api.file.upload_file.storage.app_error", nil, "", http.StatusNotImplemented)
   309  	}
   310  
   311  	if len(filenames) != len(files) || (len(clientIds) > 0 && len(clientIds) != len(files)) {
   312  		return nil, model.NewAppError("UploadFiles", "api.file.upload_file.incorrect_number_of_files.app_error", nil, "", http.StatusBadRequest)
   313  	}
   314  
   315  	resStruct := &model.FileUploadResponse{
   316  		FileInfos: []*model.FileInfo{},
   317  		ClientIds: []string{},
   318  	}
   319  
   320  	previewPathList := []string{}
   321  	thumbnailPathList := []string{}
   322  	imageDataList := [][]byte{}
   323  
   324  	for i, file := range files {
   325  		buf := bytes.NewBuffer(nil)
   326  		io.Copy(buf, file)
   327  		data := buf.Bytes()
   328  
   329  		info, err := a.DoUploadFile(time.Now(), teamId, channelId, userId, filenames[i], data)
   330  		if err != nil {
   331  			return nil, err
   332  		}
   333  
   334  		if info.PreviewPath != "" || info.ThumbnailPath != "" {
   335  			previewPathList = append(previewPathList, info.PreviewPath)
   336  			thumbnailPathList = append(thumbnailPathList, info.ThumbnailPath)
   337  			imageDataList = append(imageDataList, data)
   338  		}
   339  
   340  		resStruct.FileInfos = append(resStruct.FileInfos, info)
   341  
   342  		if len(clientIds) > 0 {
   343  			resStruct.ClientIds = append(resStruct.ClientIds, clientIds[i])
   344  		}
   345  	}
   346  
   347  	a.HandleImages(previewPathList, thumbnailPathList, imageDataList)
   348  
   349  	return resStruct, nil
   350  }
   351  
   352  func (a *App) DoUploadFile(now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte) (*model.FileInfo, *model.AppError) {
   353  	filename := filepath.Base(rawFilename)
   354  	teamId := filepath.Base(rawTeamId)
   355  	channelId := filepath.Base(rawChannelId)
   356  	userId := filepath.Base(rawUserId)
   357  
   358  	info, err := model.GetInfoForBytes(filename, data)
   359  	if err != nil {
   360  		err.StatusCode = http.StatusBadRequest
   361  		return nil, err
   362  	}
   363  
   364  	info.Id = model.NewId()
   365  	info.CreatorId = userId
   366  
   367  	pathPrefix := now.Format("20060102") + "/teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + info.Id + "/"
   368  	info.Path = pathPrefix + filename
   369  
   370  	if info.IsImage() {
   371  		// Check dimensions before loading the whole thing into memory later on
   372  		if info.Width*info.Height > MaxImageSize {
   373  			err := model.NewAppError("uploadFile", "api.file.upload_file.large_image.app_error", map[string]interface{}{"Filename": filename}, "", http.StatusBadRequest)
   374  			return nil, err
   375  		}
   376  
   377  		nameWithoutExtension := filename[:strings.LastIndex(filename, ".")]
   378  		info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg"
   379  		info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg"
   380  	}
   381  
   382  	if err := a.WriteFile(data, info.Path); err != nil {
   383  		return nil, err
   384  	}
   385  
   386  	if result := <-a.Srv.Store.FileInfo().Save(info); result.Err != nil {
   387  		return nil, result.Err
   388  	}
   389  
   390  	return info, nil
   391  }
   392  
   393  func (a *App) HandleImages(previewPathList []string, thumbnailPathList []string, fileData [][]byte) {
   394  	wg := new(sync.WaitGroup)
   395  
   396  	for i := range fileData {
   397  		img, width, height := prepareImage(fileData[i])
   398  		if img != nil {
   399  			wg.Add(2)
   400  			go func(img *image.Image, path string, width int, height int) {
   401  				defer wg.Done()
   402  				a.generateThumbnailImage(*img, path, width, height)
   403  			}(img, thumbnailPathList[i], width, height)
   404  
   405  			go func(img *image.Image, path string, width int) {
   406  				defer wg.Done()
   407  				a.generatePreviewImage(*img, path, width)
   408  			}(img, previewPathList[i], width)
   409  		}
   410  	}
   411  	wg.Wait()
   412  }
   413  
   414  func prepareImage(fileData []byte) (*image.Image, int, int) {
   415  	// Decode image bytes into Image object
   416  	img, imgType, err := image.Decode(bytes.NewReader(fileData))
   417  	if err != nil {
   418  		l4g.Error(utils.T("api.file.handle_images_forget.decode.error"), err)
   419  		return nil, 0, 0
   420  	}
   421  
   422  	width := img.Bounds().Dx()
   423  	height := img.Bounds().Dy()
   424  
   425  	// Fill in the background of a potentially-transparent png file as white
   426  	if imgType == "png" {
   427  		dst := image.NewRGBA(img.Bounds())
   428  		draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src)
   429  		draw.Draw(dst, dst.Bounds(), img, img.Bounds().Min, draw.Over)
   430  		img = dst
   431  	}
   432  
   433  	// Flip the image to be upright
   434  	orientation, _ := getImageOrientation(bytes.NewReader(fileData))
   435  	img = makeImageUpright(img, orientation)
   436  
   437  	return &img, width, height
   438  }
   439  
   440  func makeImageUpright(img image.Image, orientation int) image.Image {
   441  	switch orientation {
   442  	case UprightMirrored:
   443  		return imaging.FlipH(img)
   444  	case UpsideDown:
   445  		return imaging.Rotate180(img)
   446  	case UpsideDownMirrored:
   447  		return imaging.FlipV(img)
   448  	case RotatedCWMirrored:
   449  		return imaging.Transpose(img)
   450  	case RotatedCCW:
   451  		return imaging.Rotate270(img)
   452  	case RotatedCCWMirrored:
   453  		return imaging.Transverse(img)
   454  	case RotatedCW:
   455  		return imaging.Rotate90(img)
   456  	default:
   457  		return img
   458  	}
   459  }
   460  
   461  func getImageOrientation(input io.Reader) (int, error) {
   462  	if exifData, err := exif.Decode(input); err != nil {
   463  		return Upright, err
   464  	} else {
   465  		if tag, err := exifData.Get("Orientation"); err != nil {
   466  			return Upright, err
   467  		} else {
   468  			orientation, err := tag.Int(0)
   469  			if err != nil {
   470  				return Upright, err
   471  			} else {
   472  				return orientation, nil
   473  			}
   474  		}
   475  	}
   476  }
   477  
   478  func (a *App) generateThumbnailImage(img image.Image, thumbnailPath string, width int, height int) {
   479  	thumbWidth := float64(IMAGE_THUMBNAIL_PIXEL_WIDTH)
   480  	thumbHeight := float64(IMAGE_THUMBNAIL_PIXEL_HEIGHT)
   481  	imgWidth := float64(width)
   482  	imgHeight := float64(height)
   483  
   484  	var thumbnail image.Image
   485  	if imgHeight < IMAGE_THUMBNAIL_PIXEL_HEIGHT && imgWidth < thumbWidth {
   486  		thumbnail = img
   487  	} else if imgHeight/imgWidth < thumbHeight/thumbWidth {
   488  		thumbnail = imaging.Resize(img, 0, IMAGE_THUMBNAIL_PIXEL_HEIGHT, imaging.Lanczos)
   489  	} else {
   490  		thumbnail = imaging.Resize(img, IMAGE_THUMBNAIL_PIXEL_WIDTH, 0, imaging.Lanczos)
   491  	}
   492  
   493  	buf := new(bytes.Buffer)
   494  	if err := jpeg.Encode(buf, thumbnail, &jpeg.Options{Quality: 90}); err != nil {
   495  		l4g.Error(utils.T("api.file.handle_images_forget.encode_jpeg.error"), thumbnailPath, err)
   496  		return
   497  	}
   498  
   499  	if err := a.WriteFile(buf.Bytes(), thumbnailPath); err != nil {
   500  		l4g.Error(utils.T("api.file.handle_images_forget.upload_thumb.error"), thumbnailPath, err)
   501  		return
   502  	}
   503  }
   504  
   505  func (a *App) generatePreviewImage(img image.Image, previewPath string, width int) {
   506  	var preview image.Image
   507  
   508  	if width > IMAGE_PREVIEW_PIXEL_WIDTH {
   509  		preview = imaging.Resize(img, IMAGE_PREVIEW_PIXEL_WIDTH, 0, imaging.Lanczos)
   510  	} else {
   511  		preview = img
   512  	}
   513  
   514  	buf := new(bytes.Buffer)
   515  
   516  	if err := jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90}); err != nil {
   517  		l4g.Error(utils.T("api.file.handle_images_forget.encode_preview.error"), previewPath, err)
   518  		return
   519  	}
   520  
   521  	if err := a.WriteFile(buf.Bytes(), previewPath); err != nil {
   522  		l4g.Error(utils.T("api.file.handle_images_forget.upload_preview.error"), previewPath, err)
   523  		return
   524  	}
   525  }
   526  
   527  func (a *App) GetFileInfo(fileId string) (*model.FileInfo, *model.AppError) {
   528  	if result := <-a.Srv.Store.FileInfo().Get(fileId); result.Err != nil {
   529  		return nil, result.Err
   530  	} else {
   531  		return result.Data.(*model.FileInfo), nil
   532  	}
   533  }