github.com/adacta-ru/mattermost-server/v6@v6.0.0/app/upload.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 "errors" 8 "io" 9 "net/http" 10 "path/filepath" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/adacta-ru/mattermost-server/v6/mlog" 16 "github.com/adacta-ru/mattermost-server/v6/model" 17 "github.com/adacta-ru/mattermost-server/v6/plugin" 18 "github.com/adacta-ru/mattermost-server/v6/store" 19 ) 20 21 const minFirstPartSize = 5 * 1024 * 1024 // 5MB 22 const incompleteUploadSuffix = ".tmp" 23 24 func (a *App) runPluginsHook(info *model.FileInfo, file io.Reader) *model.AppError { 25 pluginsEnvironment := a.GetPluginsEnvironment() 26 if pluginsEnvironment == nil { 27 return nil 28 } 29 30 filePath := info.Path 31 // using a pipe to avoid loading the whole file content in memory. 32 r, w := io.Pipe() 33 errChan := make(chan *model.AppError, 1) 34 hookHasRunCh := make(chan struct{}) 35 36 go func() { 37 defer w.Close() 38 defer close(hookHasRunCh) 39 defer close(errChan) 40 var rejErr *model.AppError 41 var once sync.Once 42 pluginContext := a.PluginContext() 43 pluginsEnvironment.RunMultiPluginHook(func(hooks plugin.Hooks) bool { 44 once.Do(func() { 45 hookHasRunCh <- struct{}{} 46 }) 47 newInfo, rejStr := hooks.FileWillBeUploaded(pluginContext, info, file, w) 48 if rejStr != "" { 49 rejErr = model.NewAppError("runPluginsHook", "app.upload.run_plugins_hook.rejected", 50 map[string]interface{}{"Filename": info.Name, "Reason": rejStr}, "", http.StatusBadRequest) 51 return false 52 } 53 if newInfo != nil { 54 info = newInfo 55 } 56 return true 57 }, plugin.FileWillBeUploadedId) 58 if rejErr != nil { 59 errChan <- rejErr 60 } 61 }() 62 63 // If the plugin hook has not run we can return early. 64 if _, ok := <-hookHasRunCh; !ok { 65 return nil 66 } 67 68 tmpPath := filePath + ".tmp" 69 written, err := a.WriteFile(r, tmpPath) 70 if err != nil { 71 if fileErr := a.RemoveFile(tmpPath); fileErr != nil { 72 mlog.Error("Failed to remove file", mlog.Err(fileErr)) 73 } 74 return err 75 } 76 77 if err = <-errChan; err != nil { 78 if fileErr := a.RemoveFile(info.Path); fileErr != nil { 79 mlog.Error("Failed to remove file", mlog.Err(fileErr)) 80 } 81 if fileErr := a.RemoveFile(tmpPath); fileErr != nil { 82 mlog.Error("Failed to remove file", mlog.Err(fileErr)) 83 } 84 return err 85 } 86 87 if written > 0 { 88 info.Size = written 89 if fileErr := a.MoveFile(tmpPath, info.Path); fileErr != nil { 90 mlog.Error("Failed to move file", mlog.Err(fileErr)) 91 return model.NewAppError("runPluginsHook", "app.upload.run_plugins_hook.move_fail", 92 nil, fileErr.Error(), http.StatusInternalServerError) 93 } 94 } else { 95 if fileErr := a.RemoveFile(tmpPath); fileErr != nil { 96 mlog.Error("Failed to remove file", mlog.Err(fileErr)) 97 } 98 } 99 100 return nil 101 } 102 103 func (a *App) CreateUploadSession(us *model.UploadSession) (*model.UploadSession, *model.AppError) { 104 if us.FileSize > *a.Config().FileSettings.MaxFileSize { 105 return nil, model.NewAppError("CreateUploadSession", "app.upload.create.upload_too_large.app_error", 106 map[string]interface{}{"channelId": us.ChannelId}, "", http.StatusRequestEntityTooLarge) 107 } 108 109 us.FileOffset = 0 110 now := time.Now() 111 us.CreateAt = model.GetMillisForTime(now) 112 if us.Type == model.UploadTypeAttachment { 113 us.Path = now.Format("20060102") + "/teams/noteam/channels/" + us.ChannelId + "/users/" + us.UserId + "/" + us.Id + "/" + filepath.Base(us.Filename) 114 } else if us.Type == model.UploadTypeImport { 115 us.Path = *a.Config().ImportSettings.Directory + "/" + us.Id + "_" + filepath.Base(us.Filename) 116 } 117 if err := us.IsValid(); err != nil { 118 return nil, err 119 } 120 121 if us.Type == model.UploadTypeAttachment { 122 channel, err := a.GetChannel(us.ChannelId) 123 if err != nil { 124 return nil, model.NewAppError("CreateUploadSession", "app.upload.create.incorrect_channel_id.app_error", 125 map[string]interface{}{"channelId": us.ChannelId}, "", http.StatusBadRequest) 126 } 127 if channel.DeleteAt != 0 { 128 return nil, model.NewAppError("CreateUploadSession", "app.upload.create.cannot_upload_to_deleted_channel.app_error", 129 map[string]interface{}{"channelId": us.ChannelId}, "", http.StatusBadRequest) 130 } 131 } 132 133 us, storeErr := a.Srv().Store.UploadSession().Save(us) 134 if storeErr != nil { 135 return nil, model.NewAppError("CreateUploadSession", "app.upload.create.save.app_error", nil, storeErr.Error(), http.StatusInternalServerError) 136 } 137 138 return us, nil 139 } 140 141 func (a *App) GetUploadSession(uploadId string) (*model.UploadSession, *model.AppError) { 142 us, err := a.Srv().Store.UploadSession().Get(uploadId) 143 if err != nil { 144 var nfErr *store.ErrNotFound 145 switch { 146 case errors.As(err, &nfErr): 147 return nil, model.NewAppError("GetUpload", "app.upload.get.app_error", 148 nil, nfErr.Error(), http.StatusNotFound) 149 default: 150 return nil, model.NewAppError("GetUpload", "app.upload.get.app_error", 151 nil, err.Error(), http.StatusInternalServerError) 152 } 153 } 154 return us, nil 155 } 156 157 func (a *App) GetUploadSessionsForUser(userId string) ([]*model.UploadSession, *model.AppError) { 158 uss, err := a.Srv().Store.UploadSession().GetForUser(userId) 159 if err != nil { 160 return nil, model.NewAppError("GetUploadsForUser", "app.upload.get_for_user.app_error", 161 nil, err.Error(), http.StatusInternalServerError) 162 } 163 return uss, nil 164 } 165 166 func (a *App) UploadData(us *model.UploadSession, rd io.Reader) (*model.FileInfo, *model.AppError) { 167 // prevent more than one caller to upload data at the same time for a given upload session. 168 // This is to avoid possible inconsistencies. 169 a.Srv().uploadLockMapMut.Lock() 170 locked := a.Srv().uploadLockMap[us.Id] 171 if locked { 172 // session lock is already taken, return error. 173 a.Srv().uploadLockMapMut.Unlock() 174 return nil, model.NewAppError("UploadData", "app.upload.upload_data.concurrent.app_error", 175 nil, "", http.StatusBadRequest) 176 } 177 // grab the session lock. 178 a.Srv().uploadLockMap[us.Id] = true 179 a.Srv().uploadLockMapMut.Unlock() 180 181 // reset the session lock on exit. 182 defer func() { 183 a.Srv().uploadLockMapMut.Lock() 184 delete(a.Srv().uploadLockMap, us.Id) 185 a.Srv().uploadLockMapMut.Unlock() 186 }() 187 188 // fetch the session from store to check for inconsistencies. 189 if storedSession, err := a.GetUploadSession(us.Id); err != nil { 190 return nil, err 191 } else if us.FileOffset != storedSession.FileOffset { 192 return nil, model.NewAppError("UploadData", "app.upload.upload_data.concurrent.app_error", 193 nil, "FileOffset mismatch", http.StatusBadRequest) 194 } 195 196 uploadPath := us.Path 197 if us.Type == model.UploadTypeImport { 198 uploadPath += incompleteUploadSuffix 199 } 200 201 // make sure it's not possible to upload more data than what is expected. 202 lr := &io.LimitedReader{ 203 R: rd, 204 N: us.FileSize - us.FileOffset, 205 } 206 var err *model.AppError 207 var written int64 208 if us.FileOffset == 0 { 209 // new upload 210 written, err = a.WriteFile(lr, uploadPath) 211 if err != nil && written == 0 { 212 return nil, err 213 } 214 if written < minFirstPartSize && written != us.FileSize { 215 a.RemoveFile(uploadPath) 216 var errStr string 217 if err != nil { 218 errStr = err.Error() 219 } 220 return nil, model.NewAppError("UploadData", "app.upload.upload_data.first_part_too_small.app_error", 221 map[string]interface{}{"Size": minFirstPartSize}, errStr, http.StatusBadRequest) 222 } 223 } else if us.FileOffset < us.FileSize { 224 // resume upload 225 written, err = a.AppendFile(lr, uploadPath) 226 } 227 if written > 0 { 228 us.FileOffset += written 229 if storeErr := a.Srv().Store.UploadSession().Update(us); storeErr != nil { 230 return nil, model.NewAppError("UploadData", "app.upload.upload_data.update.app_error", nil, storeErr.Error(), http.StatusInternalServerError) 231 } 232 } 233 if err != nil { 234 return nil, err 235 } 236 237 // upload is incomplete 238 if us.FileOffset != us.FileSize { 239 return nil, nil 240 } 241 242 // upload is done, create FileInfo 243 file, err := a.FileReader(uploadPath) 244 if err != nil { 245 return nil, model.NewAppError("UploadData", "app.upload.upload_data.read_file.app_error", nil, err.Error(), http.StatusInternalServerError) 246 } 247 248 info, err := model.GetInfoForBytes(us.Filename, file, int(us.FileSize)) 249 file.Close() 250 if err != nil { 251 return nil, err 252 } 253 254 info.CreatorId = us.UserId 255 info.Path = us.Path 256 257 // run plugins upload hook 258 if err := a.runPluginsHook(info, file); err != nil { 259 return nil, err 260 } 261 262 // image post-processing 263 if info.IsImage() { 264 // Check dimensions before loading the whole thing into memory later on 265 // This casting is done to prevent overflow on 32 bit systems (not needed 266 // in 64 bits systems because images can't have more than 32 bits height or 267 // width) 268 if int64(info.Width)*int64(info.Height) > MaxImageSize { 269 return nil, model.NewAppError("uploadData", "app.upload.upload_data.large_image.app_error", 270 map[string]interface{}{"Filename": us.Filename, "Width": info.Width, "Height": info.Height}, "", http.StatusBadRequest) 271 } 272 nameWithoutExtension := info.Name[:strings.LastIndex(info.Name, ".")] 273 info.PreviewPath = filepath.Dir(info.Path) + "/" + nameWithoutExtension + "_preview.jpg" 274 info.ThumbnailPath = filepath.Dir(info.Path) + "/" + nameWithoutExtension + "_thumb.jpg" 275 imgData, fileErr := a.ReadFile(uploadPath) 276 if fileErr != nil { 277 return nil, fileErr 278 } 279 a.HandleImages([]string{info.PreviewPath}, []string{info.ThumbnailPath}, [][]byte{imgData}) 280 } 281 282 if us.Type == model.UploadTypeImport { 283 if err := a.MoveFile(uploadPath, us.Path); err != nil { 284 return nil, model.NewAppError("UploadData", "app.upload.upload_data.move_file.app_error", nil, err.Error(), http.StatusInternalServerError) 285 } 286 } 287 288 var storeErr error 289 if info, storeErr = a.Srv().Store.FileInfo().Save(info); storeErr != nil { 290 var appErr *model.AppError 291 switch { 292 case errors.As(storeErr, &appErr): 293 return nil, appErr 294 default: 295 return nil, model.NewAppError("uploadData", "app.upload.upload_data.save.app_error", nil, storeErr.Error(), http.StatusInternalServerError) 296 } 297 } 298 299 // delete upload session 300 if storeErr := a.Srv().Store.UploadSession().Delete(us.Id); storeErr != nil { 301 mlog.Error("Failed to delete UploadSession", mlog.Err(storeErr)) 302 } 303 304 return info, nil 305 }