package callback import ( "context" "fmt" model "github.com/cloudreve/Cloudreve/v3/models" "strings" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/cos" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/onedrive" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/driver/s3" "github.com/cloudreve/Cloudreve/v3/pkg/filesystem/fsctx" "github.com/cloudreve/Cloudreve/v3/pkg/serializer" "github.com/gin-gonic/gin" ) // CallbackProcessService 上传请求回调正文接口 type CallbackProcessService interface { GetBody() serializer.UploadCallback } // RemoteUploadCallbackService 远程存储上传回调请求服务 type RemoteUploadCallbackService struct { Data serializer.UploadCallback `json:"data" binding:"required"` } // GetBody 返回回调正文 func (service RemoteUploadCallbackService) GetBody() serializer.UploadCallback { return service.Data } // UploadCallbackService OOS/七牛云存储上传回调请求服务 type UploadCallbackService struct { Name string `json:"name"` SourceName string `json:"source_name"` PicInfo string `json:"pic_info"` Size uint64 `json:"size"` } // UpyunCallbackService 又拍云上传回调请求服务 type UpyunCallbackService struct { Code int `form:"code" binding:"required"` Message string `form:"message" binding:"required"` SourceName string `form:"url" binding:"required"` Width string `form:"image-width"` Height string `form:"image-height"` Size uint64 `form:"file_size"` } // OneDriveCallback OneDrive 客户端回调正文 type OneDriveCallback struct { ID string `json:"id" binding:"required"` Meta *onedrive.FileInfo } // COSCallback COS 客户端回调正文 type COSCallback struct { Bucket string `form:"bucket"` Etag string `form:"etag"` } // S3Callback S3 客户端回调正文 type S3Callback struct { Bucket string `form:"bucket"` Etag string `form:"etag"` Key string `form:"key"` } // GetBody 返回回调正文 func (service UpyunCallbackService) GetBody() serializer.UploadCallback { res := serializer.UploadCallback{} if service.Width != "" { res.PicInfo = service.Width + "," + service.Height } return res } // GetBody 返回回调正文 func (service UploadCallbackService) GetBody() serializer.UploadCallback { return serializer.UploadCallback{ PicInfo: service.PicInfo, } } // GetBody 返回回调正文 func (service OneDriveCallback) GetBody() serializer.UploadCallback { var picInfo = "0,0" if service.Meta.Image.Width != 0 { picInfo = fmt.Sprintf("%d,%d", service.Meta.Image.Width, service.Meta.Image.Height) } return serializer.UploadCallback{ PicInfo: picInfo, } } // GetBody 返回回调正文 func (service COSCallback) GetBody() serializer.UploadCallback { return serializer.UploadCallback{ PicInfo: "", } } // GetBody 返回回调正文 func (service S3Callback) GetBody() serializer.UploadCallback { return serializer.UploadCallback{ PicInfo: "", } } // ProcessCallback 处理上传结果回调 func ProcessCallback(service CallbackProcessService, c *gin.Context) serializer.Response { callbackBody := service.GetBody() // 创建文件系统 fs, err := filesystem.NewFileSystemFromCallback(c) if err != nil { return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) } defer fs.Recycle() // 获取上传会话 uploadSession := c.MustGet(filesystem.UploadSessionCtx).(*serializer.UploadSession) // 查找上传会话创建的占位文件 file, err := model.GetFilesByUploadSession(uploadSession.Key, fs.User.ID) if err != nil { return serializer.Err(serializer.CodeUploadSessionExpired, "LocalUpload session file placeholder not exist", err) } fileData := fsctx.FileStream{ Size: uploadSession.Size, Name: uploadSession.Name, VirtualPath: uploadSession.VirtualPath, SavePath: uploadSession.SavePath, Mode: fsctx.Nop, Model: file, LastModified: uploadSession.LastModified, } // 占位符未扣除容量需要校验和扣除 if !fs.Policy.IsUploadPlaceholderWithSize() { fs.Use("AfterUpload", filesystem.HookValidateCapacity) fs.Use("AfterUpload", filesystem.HookChunkUploaded) } fs.Use("AfterUpload", filesystem.HookPopPlaceholderToFile(callbackBody.PicInfo)) fs.Use("AfterValidateFailed", filesystem.HookDeleteTempFile) err = fs.Upload(context.Background(), &fileData) if err != nil { return serializer.Err(serializer.CodeUploadFailed, err.Error(), err) } return serializer.Response{} } // PreProcess 对OneDrive客户端回调进行预处理验证 func (service *OneDriveCallback) PreProcess(c *gin.Context) serializer.Response { // 创建文件系统 fs, err := filesystem.NewFileSystemFromCallback(c) if err != nil { return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) } defer fs.Recycle() // 获取回调会话 callbackSessionRaw, _ := c.Get("callbackSession") callbackSession := callbackSessionRaw.(*serializer.UploadSession) // 获取文件信息 info, err := fs.Handler.(onedrive.Driver).Client.Meta(context.Background(), service.ID, "") if err != nil { return serializer.Err(serializer.CodeUploadFailed, "文件元信息查询失败", err) } // 验证与回调会话中是否一致 actualPath := strings.TrimPrefix(callbackSession.SavePath, "/") isSizeCheckFailed := callbackSession.Size != info.Size // SharePoint 会对 Office 文档增加 meta data 导致文件大小不一致,这里增加 10 KB 宽容 // See: https://github.com/OneDrive/onedrive-api-docs/issues/935 if strings.Contains(fs.Policy.OptionsSerialized.OdDriver, "sharepoint.com") && isSizeCheckFailed && (info.Size > callbackSession.Size) && (info.Size-callbackSession.Size <= 10240) { isSizeCheckFailed = false } if isSizeCheckFailed || info.GetSourcePath() != actualPath { fs.Handler.(onedrive.Driver).Client.Delete(context.Background(), []string{info.GetSourcePath()}) return serializer.Err(serializer.CodeUploadFailed, "文件信息不一致", err) } service.Meta = info return ProcessCallback(service, c) } // PreProcess 对COS客户端回调进行预处理 func (service *COSCallback) PreProcess(c *gin.Context) serializer.Response { // 创建文件系统 fs, err := filesystem.NewFileSystemFromCallback(c) if err != nil { return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) } defer fs.Recycle() // 获取回调会话 callbackSessionRaw, _ := c.Get("callbackSession") callbackSession := callbackSessionRaw.(*serializer.UploadSession) // 获取文件信息 info, err := fs.Handler.(cos.Driver).Meta(context.Background(), callbackSession.SavePath) if err != nil { return serializer.Err(serializer.CodeUploadFailed, "文件信息不一致", err) } // 验证实际文件信息与回调会话中是否一致 if callbackSession.Size != info.Size || callbackSession.Key != info.CallbackKey { return serializer.Err(serializer.CodeUploadFailed, "文件信息不一致", err) } return ProcessCallback(service, c) } // PreProcess 对S3客户端回调进行预处理 func (service *S3Callback) PreProcess(c *gin.Context) serializer.Response { // 创建文件系统 fs, err := filesystem.NewFileSystemFromCallback(c) if err != nil { return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err) } defer fs.Recycle() // 获取回调会话 callbackSessionRaw, _ := c.Get("callbackSession") callbackSession := callbackSessionRaw.(*serializer.UploadSession) // 获取文件信息 info, err := fs.Handler.(s3.Driver).Meta(context.Background(), callbackSession.SavePath) if err != nil { return serializer.Err(serializer.CodeUploadFailed, "文件信息不一致", err) } // 验证实际文件信息与回调会话中是否一致 if callbackSession.Size != info.Size || service.Etag != info.Etag { return serializer.Err(serializer.CodeUploadFailed, "文件信息不一致", err) } return ProcessCallback(service, c) }