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