Merge remote-tracking branch 'source/master'

pull/1255/head
lsCoding666 3 years ago
commit 5f1da5644d

@ -1 +1 @@
Subproject commit c4a7dfea2c05077e6e7c7042c8598886bf6f5531
Subproject commit 47493d67d7b08192a406f68cf2e9a31a46f37104

@ -24,6 +24,11 @@ type req struct {
Randstr string `json:"randstr"`
}
const (
captchaNotMatch = "CAPTCHA not match."
captchaRefresh = "Verification failed, please refresh the page and retry."
)
// CaptchaRequired 验证请求签名
func CaptchaRequired(configName string) gin.HandlerFunc {
return func(c *gin.Context) {
@ -43,7 +48,7 @@ func CaptchaRequired(configName string) gin.HandlerFunc {
bodyCopy := new(bytes.Buffer)
_, err := io.Copy(bodyCopy, c.Request.Body)
if err != nil {
c.JSON(200, serializer.ParamErr("验证码错误", err))
c.JSON(200, serializer.Err(serializer.CodeCaptchaError, captchaNotMatch, err))
c.Abort()
return
}
@ -51,7 +56,7 @@ func CaptchaRequired(configName string) gin.HandlerFunc {
bodyData := bodyCopy.Bytes()
err = json.Unmarshal(bodyData, &service)
if err != nil {
c.JSON(200, serializer.ParamErr("验证码错误", err))
c.JSON(200, serializer.Err(serializer.CodeCaptchaError, captchaNotMatch, err))
c.Abort()
return
}
@ -62,7 +67,7 @@ func CaptchaRequired(configName string) gin.HandlerFunc {
captchaID := util.GetSession(c, "captchaID")
util.DeleteSession(c, "captchaID")
if captchaID == nil || !base64Captcha.VerifyCaptcha(captchaID.(string), service.CaptchaCode) {
c.JSON(200, serializer.ParamErr("验证码错误", nil))
c.JSON(200, serializer.Err(serializer.CodeCaptchaError, captchaNotMatch, err))
c.Abort()
return
}
@ -71,15 +76,15 @@ func CaptchaRequired(configName string) gin.HandlerFunc {
case "recaptcha":
reCAPTCHA, err := recaptcha.NewReCAPTCHA(options["captcha_ReCaptchaSecret"], recaptcha.V2, 10*time.Second)
if err != nil {
util.Log().Warning("reCAPTCHA 验证错误, %s", err)
util.Log().Warning("reCAPTCHA verification failed, %s", err)
c.Abort()
break
}
err = reCAPTCHA.Verify(service.CaptchaCode)
if err != nil {
util.Log().Warning("reCAPTCHA 验证错误, %s", err)
c.JSON(200, serializer.ParamErr("验证失败,请刷新网页后再次验证", nil))
util.Log().Warning("reCAPTCHA verification failed, %s", err)
c.JSON(200, serializer.Err(serializer.CodeCaptchaRefreshNeeded, captchaRefresh, nil))
c.Abort()
return
}
@ -103,13 +108,13 @@ func CaptchaRequired(configName string) gin.HandlerFunc {
request.UserIp = common.StringPtr(c.ClientIP())
response, err := client.DescribeCaptchaResult(request)
if err != nil {
util.Log().Warning("TCaptcha 验证错误, %s", err)
util.Log().Warning("TCaptcha verification failed, %s", err)
c.Abort()
break
}
if *response.Response.CaptchaCode != int64(1) {
c.JSON(200, serializer.ParamErr("验证失败,请刷新网页后再次验证", nil))
c.JSON(200, serializer.Err(serializer.CodeCaptchaRefreshNeeded, captchaRefresh, nil))
c.Abort()
return
}

@ -30,7 +30,7 @@ func HashID(IDType int) gin.HandlerFunc {
func IsFunctionEnabled(key string) gin.HandlerFunc {
return func(c *gin.Context) {
if !model.IsTrueVal(model.GetSettingByName(key)) {
c.JSON(200, serializer.Err(serializer.CodeNoPermissionErr, "未开启此功能", nil))
c.JSON(200, serializer.Err(serializer.CodeFeatureNotEnabled, "This feature is not enabled", nil))
c.Abort()
return
}

@ -117,8 +117,8 @@ func GetFilesByIDsFromTX(tx *gorm.DB, ids []uint, uid uint) ([]File, error) {
}
// GetFilesByKeywords 根据关键字搜索文件,
// UID为0表示忽略用户只根据文件ID检索
func GetFilesByKeywords(uid uint, keywords ...interface{}) ([]File, error) {
// UID为0表示忽略用户只根据文件ID检索. 如果 parents 非空, 则只限制在 parent 包含的目录下搜索
func GetFilesByKeywords(uid uint, parents []uint, keywords ...interface{}) ([]File, error) {
var (
files []File
result = DB
@ -136,6 +136,11 @@ func GetFilesByKeywords(uid uint, keywords ...interface{}) ([]File, error) {
if uid != 0 {
result = result.Where("user_id = ?", uid)
}
if len(parents) > 0 {
result = result.Where("folder_id in (?)", parents)
}
result = result.Where("("+conditions+")", keywords...).Find(&files)
return files, result.Error
@ -143,7 +148,7 @@ func GetFilesByKeywords(uid uint, keywords ...interface{}) ([]File, error) {
// GetChildFilesOfFolders 批量检索目录子文件
func GetChildFilesOfFolders(folders *[]Folder) ([]File, error) {
// 将所有待删除目录ID抽离以便检索文件
// 将所有待检索目录ID抽离以便检索文件
folderIDs := make([]uint, 0, len(*folders))
for _, value := range *folders {
folderIDs = append(folderIDs, value.ID)

@ -561,7 +561,7 @@ func TestGetFilesByKeywords(t *testing.T) {
// 未指定用户
{
mock.ExpectQuery("SELECT(.+)").WithArgs("k1", "k2").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
res, err := GetFilesByKeywords(0, "k1", "k2")
res, err := GetFilesByKeywords(0, nil, "k1", "k2")
asserts.NoError(mock.ExpectationsWereMet())
asserts.NoError(err)
asserts.Len(res, 1)
@ -570,7 +570,16 @@ func TestGetFilesByKeywords(t *testing.T) {
// 指定用户
{
mock.ExpectQuery("SELECT(.+)").WithArgs(1, "k1", "k2").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
res, err := GetFilesByKeywords(1, "k1", "k2")
res, err := GetFilesByKeywords(1, nil, "k1", "k2")
asserts.NoError(mock.ExpectationsWereMet())
asserts.NoError(err)
asserts.Len(res, 1)
}
// 指定父目录
{
mock.ExpectQuery("SELECT(.+)").WithArgs(1, 12, "k1", "k2").WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(1))
res, err := GetFilesByKeywords(1, []uint{12}, "k1", "k2")
asserts.NoError(mock.ExpectationsWereMet())
asserts.NoError(err)
asserts.Len(res, 1)

@ -31,6 +31,8 @@ type GroupOption struct {
ShareDownload bool `json:"share_download,omitempty"`
Aria2 bool `json:"aria2,omitempty"` // 离线下载
Aria2Options map[string]interface{} `json:"aria2_options,omitempty"` // 离线下载用户组配置
SourceBatchSize int `json:"source_batch,omitempty"`
Aria2BatchSize int `json:"aria2_batch,omitempty"`
}
// GetGroupByID 用ID获取用户组

@ -108,6 +108,8 @@ func addDefaultGroups() {
ArchiveTask: true,
ShareDownload: true,
Aria2: true,
SourceBatchSize: 1000,
Aria2BatchSize: 50,
},
}
if err := DB.Create(&defaultAdminGroup).Error; err != nil {
@ -126,7 +128,9 @@ func addDefaultGroups() {
ShareEnabled: true,
WebDAVEnabled: true,
OptionsSerialized: GroupOption{
ShareDownload: true,
ShareDownload: true,
SourceBatchSize: 10,
Aria2BatchSize: 1,
},
}
if err := DB.Create(&defaultAdminGroup).Error; err != nil {

@ -63,6 +63,7 @@ type PolicyOption struct {
PlaceholderWithSize bool `json:"placeholder_with_size,omitempty"`
}
// thumbSuffix 支持缩略图处理的文件扩展名
var thumbSuffix = map[string][]string{
"local": {},
"qiniu": {".psd", ".jpg", ".jpeg", ".png", ".gif", ".webp", ".tiff", ".bmp"},

@ -1,13 +1,13 @@
package conf
// BackendVersion 当前后端版本号
var BackendVersion = "3.5.2"
var BackendVersion = "3.5.3"
// RequiredDBVersion 与当前版本匹配的数据库版本
var RequiredDBVersion = "3.5.2"
// RequiredStaticVersion 与当前版本匹配的静态资源版本
var RequiredStaticVersion = "3.5.2"
var RequiredStaticVersion = "3.5.3"
// IsPro 是否为Pro版本
var IsPro = "false"

@ -2,6 +2,7 @@ package filesystem
import (
"context"
"fmt"
"io"
model "github.com/cloudreve/Cloudreve/v3/models"
@ -361,7 +362,21 @@ func (fs *FileSystem) resetPolicyToFirstFile(ctx context.Context) error {
// Search 搜索文件
func (fs *FileSystem) Search(ctx context.Context, keywords ...interface{}) ([]serializer.Object, error) {
files, _ := model.GetFilesByKeywords(fs.User.ID, keywords...)
parents := make([]uint, 0)
// 如果限定了根目录,则只在这个根目录下搜索。
if fs.Root != nil {
allFolders, err := model.GetRecursiveChildFolder([]uint{fs.Root.ID}, fs.User.ID, true)
if err != nil {
return nil, fmt.Errorf("failed to list all folders: %w", err)
}
for _, folder := range allFolders {
parents = append(parents, folder.ID)
}
}
files, _ := model.GetFilesByKeywords(fs.User.ID, parents, keywords...)
fs.SetTargetFile(&files)
return fs.listObjects(ctx, "/", files, nil, nil), nil

@ -284,6 +284,10 @@ func HookPopPlaceholderToFile(picInfo string) Hook {
return func(ctx context.Context, fs *FileSystem, fileHeader fsctx.FileHeader) error {
fileInfo := fileHeader.Info()
fileModel := fileInfo.Model.(*model.File)
if picInfo == "" && fs.Policy.IsThumbExist(fileInfo.FileName) {
picInfo = "1,1"
}
return fileModel.PopChunkToFile(fileInfo.LastModified, picInfo)
}
}

@ -671,6 +671,25 @@ func TestHookPopPlaceholderToFile(t *testing.T) {
a.NoError(mock.ExpectationsWereMet())
}
func TestHookPopPlaceholderToFileBySuffix(t *testing.T) {
a := assert.New(t)
fs := &FileSystem{
Policy: &model.Policy{Type: "cos"},
}
file := &fsctx.FileStream{
Name: "1.png",
Model: &model.File{
Model: gorm.Model{ID: 1},
},
}
mock.ExpectBegin()
mock.ExpectExec("UPDATE(.+)files(.+)").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
a.NoError(HookPopPlaceholderToFile("")(context.Background(), fs, file))
a.NoError(mock.ExpectationsWereMet())
}
func TestHookDeleteUploadSession(t *testing.T) {
a := assert.New(t)
fs := &FileSystem{}

@ -58,8 +58,6 @@ const (
CodeConflict = 409
// CodeUploadFailed 上传出错
CodeUploadFailed = 40002
// CodeCredentialInvalid 凭证无效
CodeCredentialInvalid = 40001
// CodeCreateFolderFailed 目录创建失败
CodeCreateFolderFailed = 40003
// CodeObjectExist 对象已存在
@ -80,6 +78,40 @@ const (
CodeInvalidChunkIndex = 400012
// CodeInvalidContentLength 无效的正文长度
CodeInvalidContentLength = 400013
// CodeBatchSourceSize 超出批量获取外链限制
CodeBatchSourceSize = 40014
// CodeBatchAria2Size 超出最大 Aria2 任务数量限制
CodeBatchAria2Size = 40015
// CodeParentNotExist 父目录不存在
CodeParentNotExist = 40016
// CodeUserBaned 用户不活跃
CodeUserBaned = 40017
// CodeUserNotActivated 用户不活跃
CodeUserNotActivated = 40018
// CodeFeatureNotEnabled 此功能未开启
CodeFeatureNotEnabled = 40019
// CodeCredentialInvalid 凭证无效
CodeCredentialInvalid = 40020
// CodeUserNotFound 用户不存在
CodeUserNotFound = 40021
// Code2FACodeErr 二步验证代码错误
Code2FACodeErr = 40022
// CodeLoginSessionNotExist 登录会话不存在
CodeLoginSessionNotExist = 40023
// CodeInitializeAuthn 无法初始化 WebAuthn
CodeInitializeAuthn = 40024
// CodeWebAuthnCredentialError WebAuthn 凭证无效
CodeWebAuthnCredentialError = 40025
// CodeCaptchaError 验证码错误
CodeCaptchaError = 40026
// CodeCaptchaRefreshNeeded 验证码需要刷新
CodeCaptchaRefreshNeeded = 40027
// CodeFailedSendEmail 邮件发送失败
CodeFailedSendEmail = 40028
// CodeInvalidTempLink 临时链接无效
CodeInvalidTempLink = 40029
// CodeTempLinkExpired 临时链接过期
CodeTempLinkExpired = 40030
// CodeDBError 数据库操作失败
CodeDBError = 50001
// CodeEncryptError 加密失败

@ -76,3 +76,11 @@ func BuildObjectList(parent uint, objects []Object, policy *model.Policy) Object
return res
}
// Sources 获取外链的结果响应
type Sources struct {
URL string `json:"url"`
Name string `json:"name"`
Parent uint `json:"parent"`
Error string `json:"error,omitempty"`
}

@ -40,6 +40,7 @@ type group struct {
ShareDownload bool `json:"shareDownload"`
CompressEnabled bool `json:"compress"`
WebDAVEnabled bool `json:"webdav"`
SourceBatchSize int `json:"sourceBatch"`
}
type tag struct {
@ -98,6 +99,7 @@ func BuildUser(user model.User) User {
ShareDownload: user.Group.OptionsSerialized.ShareDownload,
CompressEnabled: user.Group.OptionsSerialized.ArchiveTask,
WebDAVEnabled: user.Group.WebDAVEnabled,
SourceBatchSize: user.Group.OptionsSerialized.SourceBatchSize,
},
Tags: buildTagRes(tags),
}

@ -11,7 +11,7 @@ import (
// AddAria2URL 添加离线下载URL
func AddAria2URL(c *gin.Context) {
var addService aria2.AddURLService
var addService aria2.BatchAddURLService
if err := c.ShouldBindJSON(&addService); err == nil {
res := addService.Add(c, common.URLTask)
c.JSON(200, res)
@ -52,7 +52,7 @@ func AddAria2Torrent(c *gin.Context) {
if err := c.ShouldBindJSON(&addService); err == nil {
addService.URL = res.Data.(string)
res := addService.Add(c, common.URLTask)
res := addService.Add(c, nil, common.URLTask)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))

@ -3,10 +3,10 @@ package controllers
import (
"context"
"fmt"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"net/http"
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/filesystem"
"github.com/cloudreve/Cloudreve/v3/pkg/request"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/cloudreve/Cloudreve/v3/service/explorer"
@ -102,39 +102,18 @@ func AnonymousPermLink(c *gin.Context) {
}
}
// GetSource 获取文件的外链地址
func GetSource(c *gin.Context) {
// 创建上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err))
return
}
defer fs.Recycle()
// 获取文件ID
fileID, ok := c.Get("object_id")
if !ok {
c.JSON(200, serializer.ParamErr("文件不存在", err))
return
}
sourceURL, err := fs.GetSource(ctx, fileID.(uint))
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeNotSet, err.Error(), err))
return
var service explorer.ItemIDService
if err := c.ShouldBindJSON(&service); err == nil {
res := service.Sources(ctx, c)
c.JSON(200, res)
} else {
c.JSON(200, ErrorResponse(err))
}
c.JSON(200, serializer.Response{
Code: 0,
Data: struct {
URL string `json:"url"`
}{URL: sourceURL},
})
}
// Thumb 获取文件缩略图
@ -383,12 +362,18 @@ func GetUploadSession(c *gin.Context) {
// SearchFile 搜索文件
func SearchFile(c *gin.Context) {
var service explorer.ItemSearchService
if err := c.ShouldBindUri(&service); err == nil {
res := service.Search(c)
c.JSON(200, res)
} else {
if err := c.ShouldBindUri(&service); err != nil {
c.JSON(200, ErrorResponse(err))
return
}
if err := c.ShouldBindQuery(&service); err != nil {
c.JSON(200, ErrorResponse(err))
return
}
res := service.Search(c)
c.JSON(200, res)
}
// CreateFile 创建空白文件

@ -6,32 +6,35 @@ import (
model "github.com/cloudreve/Cloudreve/v3/models"
"github.com/cloudreve/Cloudreve/v3/pkg/serializer"
"github.com/gin-gonic/gin"
"gopkg.in/go-playground/validator.v9"
"github.com/go-playground/validator/v10"
)
// ParamErrorMsg 根据Validator返回的错误信息给出错误提示
func ParamErrorMsg(filed string, tag string) string {
// 未通过验证的表单域与中文对应
fieldMap := map[string]string{
"UserName": "邮箱",
"Password": "密码",
"Path": "路径",
"SourceID": "原始资源",
"URL": "链接",
"Nick": "昵称",
"UserName": "Email",
"Password": "Password",
"Path": "Path",
"SourceID": "Source resource",
"URL": "URL",
"Nick": "Nickname",
}
// 未通过的规则与中文对应
tagMap := map[string]string{
"required": "不能为空",
"min": "太短",
"max": "太长",
"email": "格式不正确",
"required": "cannot be empty",
"min": "too short",
"max": "too long",
"email": "format error",
}
fieldVal, findField := fieldMap[filed]
if !findField {
fieldVal = filed
}
tagVal, findTag := tagMap[tag]
if findField && findTag {
if findTag {
// 返回拼接出来的错误信息
return fieldVal + tagVal
return fieldVal + " " + tagVal
}
return ""
}
@ -49,10 +52,10 @@ func ErrorResponse(err error) serializer.Response {
}
if _, ok := err.(*json.UnmarshalTypeError); ok {
return serializer.ParamErr("JSON类型不匹配", err)
return serializer.ParamErr("JSON marshall error", err)
}
return serializer.ParamErr("参数错误", err)
return serializer.ParamErr("Parameter error", err)
}
// CurrentUser 获取当前用户

@ -184,6 +184,23 @@ func ListSharedFolder(c *gin.Context) {
}
}
// SearchSharedFolder 搜索分享的目录下的对象
func SearchSharedFolder(c *gin.Context) {
var service share.SearchService
if err := c.ShouldBindUri(&service); err != nil {
c.JSON(200, ErrorResponse(err))
return
}
if err := c.ShouldBindQuery(&service); err != nil {
c.JSON(200, ErrorResponse(err))
return
}
res := service.Search(c)
c.JSON(200, res)
}
// ArchiveShare 打包要下载的分享
func ArchiveShare(c *gin.Context) {
var service share.ArchiveService

@ -20,13 +20,13 @@ func StartLoginAuthn(c *gin.Context) {
userName := c.Param("username")
expectedUser, err := model.GetActiveUserByEmail(userName)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeNotFound, "用户不存在", err))
c.JSON(200, serializer.Err(serializer.CodeUserNotFound, "User not exist", err))
return
}
instance, err := authn.NewAuthnInstance()
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeInternalSetting, "无法初始化Authn", err))
c.JSON(200, serializer.Err(serializer.CodeInitializeAuthn, "Cannot initialize authn", err))
return
}
@ -54,7 +54,7 @@ func FinishLoginAuthn(c *gin.Context) {
userName := c.Param("username")
expectedUser, err := model.GetActiveUserByEmail(userName)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeCredentialInvalid, "用户邮箱或密码错误", err))
c.JSON(200, serializer.Err(serializer.CodeUserNotFound, "User not exist", err))
return
}
@ -65,14 +65,14 @@ func FinishLoginAuthn(c *gin.Context) {
instance, err := authn.NewAuthnInstance()
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeInternalSetting, "无法初始化Authn", err))
c.JSON(200, serializer.Err(serializer.CodeInitializeAuthn, "Cannot initialize authn", err))
return
}
_, err = instance.FinishLogin(expectedUser, sessionData, c.Request)
if err != nil {
c.JSON(200, serializer.Err(serializer.CodeCredentialInvalid, "登录验证失败", err))
c.JSON(200, serializer.Err(serializer.CodeWebAuthnCredentialError, "Verification failed", err))
return
}

@ -335,6 +335,11 @@ func InitMasterRouter() *gin.Engine {
middleware.CheckShareUnlocked(),
controllers.ListSharedFolder,
)
// 分享目录搜索
share.GET("search/:id/:type/:keywords",
middleware.CheckShareUnlocked(),
controllers.SearchSharedFolder,
)
// 归档打包下载
share.POST("archive/:id",
middleware.CheckShareUnlocked(),
@ -558,7 +563,7 @@ func InitMasterRouter() *gin.Engine {
// 获取缩略图
file.GET("thumb/:id", controllers.Thumb)
// 取得文件外链
file.GET("source/:id", controllers.GetSource)
file.POST("source", controllers.GetSource)
// 打包要下载的文件
file.POST("archive", controllers.Archive)
// 创建文件压缩任务

@ -14,13 +14,13 @@ import (
)
// AddURLService 添加URL离线下载服务
type AddURLService struct {
URL string `json:"url" binding:"required"`
Dst string `json:"dst" binding:"required,min=1"`
type BatchAddURLService struct {
URLs []string `json:"url" binding:"required"`
Dst string `json:"dst" binding:"required,min=1"`
}
// Add 主机创建新的链接离线下载任务
func (service *AddURLService) Add(c *gin.Context, taskType int) serializer.Response {
// Add 主机批量创建新的链接离线下载任务
func (service *BatchAddURLService) Add(c *gin.Context, taskType int) serializer.Response {
// 创建文件系统
fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil {
@ -38,6 +38,60 @@ func (service *AddURLService) Add(c *gin.Context, taskType int) serializer.Respo
return serializer.Err(serializer.CodeNotFound, "存放路径不存在", nil)
}
// 检查批量任务数量
limit := fs.User.Group.OptionsSerialized.Aria2BatchSize
if limit > 0 && len(service.URLs) > limit {
return serializer.Err(serializer.CodeBatchAria2Size, "Exceed aria2 batch size", nil)
}
res := make([]serializer.Response, 0, len(service.URLs))
for _, target := range service.URLs {
subService := &AddURLService{
URL: target,
Dst: service.Dst,
}
addRes := subService.Add(c, fs, taskType)
res = append(res, addRes)
}
return serializer.Response{Data: res}
}
// AddURLService 添加URL离线下载服务
type AddURLService struct {
URL string `json:"url" binding:"required"`
Dst string `json:"dst" binding:"required,min=1"`
}
// Add 主机创建新的链接离线下载任务
func (service *AddURLService) Add(c *gin.Context, fs *filesystem.FileSystem, taskType int) serializer.Response {
if fs == nil {
var err error
// 创建文件系统
fs, err = filesystem.NewFileSystemFromContext(c)
if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
}
defer fs.Recycle()
// 检查用户组权限
if !fs.User.Group.OptionsSerialized.Aria2 {
return serializer.Err(serializer.CodeGroupNotAllowed, "当前用户组无法进行此操作", nil)
}
// 存放目录是否存在
if exist, _ := fs.IsPathExist(service.Dst); !exist {
return serializer.Err(serializer.CodeNotFound, "存放路径不存在", nil)
}
}
downloads := model.GetDownloadsByStatusAndUser(0, fs.User.ID, common.Downloading, common.Paused, common.Ready)
limit := fs.User.Group.OptionsSerialized.Aria2BatchSize
if limit > 0 && len(downloads)+1 > limit {
return serializer.Err(serializer.CodeBatchAria2Size, "Exceed aria2 batch size", nil)
}
// 创建任务
task := &model.Download{
Status: common.Ready,

@ -173,9 +173,9 @@ func (service *OneDriveCallback) PreProcess(c *gin.Context) serializer.Response
actualPath := strings.TrimPrefix(uploadSession.SavePath, "/")
isSizeCheckFailed := uploadSession.Size != info.Size
// SharePoint 会对 Office 文档增加 meta data 导致文件大小不一致,这里增加 100 KB 宽容
// SharePoint 会对 Office 文档增加 meta data 导致文件大小不一致,这里增加 1 MB 宽容
// See: https://github.com/OneDrive/onedrive-api-docs/issues/935
if strings.Contains(fs.Policy.OptionsSerialized.OdDriver, "sharepoint.com") && isSizeCheckFailed && (info.Size > uploadSession.Size) && (info.Size-uploadSession.Size <= 102400) {
if strings.Contains(fs.Policy.OptionsSerialized.OdDriver, "sharepoint.com") && isSizeCheckFailed && (info.Size > uploadSession.Size) && (info.Size-uploadSession.Size <= 1048576) {
isSizeCheckFailed = false
}

@ -428,3 +428,40 @@ func (service *FileIDService) PutContent(ctx context.Context, c *gin.Context) se
Code: 0,
}
}
// Sources 批量获取对象的外链
func (s *ItemIDService) Sources(ctx context.Context, c *gin.Context) serializer.Response {
fs, err := filesystem.NewFileSystemFromContext(c)
if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, "无法初始化文件系统", err)
}
defer fs.Recycle()
if len(s.Raw().Items) > fs.User.Group.OptionsSerialized.SourceBatchSize {
return serializer.Err(serializer.CodeBatchSourceSize, "超出批量获取外链的最大数量限制", err)
}
res := make([]serializer.Sources, 0, len(s.Raw().Items))
for _, id := range s.Raw().Items {
fs.FileTarget = []model.File{}
sourceURL, err := fs.GetSource(ctx, id)
if len(fs.FileTarget) > 0 {
current := serializer.Sources{
URL: sourceURL,
Name: fs.FileTarget[0].Name,
Parent: fs.FileTarget[0].FolderID,
}
if err != nil {
current.Error = err.Error()
}
res = append(res, current)
}
}
return serializer.Response{
Code: 0,
Data: res,
}
}

@ -15,6 +15,7 @@ import (
type ItemSearchService struct {
Type string `uri:"type" binding:"required"`
Keywords string `uri:"keywords" binding:"required"`
Path string `form:"path"`
}
// Search 执行搜索
@ -26,6 +27,15 @@ func (service *ItemSearchService) Search(c *gin.Context) serializer.Response {
}
defer fs.Recycle()
if service.Path != "" {
ok, parent := fs.IsPathExist(service.Path)
if !ok {
return serializer.Err(serializer.CodeParentNotExist, "Cannot find parent folder", nil)
}
fs.Root = parent
}
switch service.Type {
case "keywords":
return service.SearchKeywords(c, fs, "%"+service.Keywords+"%")

@ -366,3 +366,50 @@ func (service *ArchiveService) Archive(c *gin.Context) serializer.Response {
return subService.Archive(ctx, c)
}
// SearchService 对分享的目录进行搜索
type SearchService struct {
explorer.ItemSearchService
}
// Search 执行搜索
func (service *SearchService) Search(c *gin.Context) serializer.Response {
shareCtx, _ := c.Get("share")
share := shareCtx.(*model.Share)
if !share.IsDir {
return serializer.ParamErr("此分享无法列目录", nil)
}
if service.Path != "" && !path.IsAbs(service.Path) {
return serializer.ParamErr("路径无效", nil)
}
// 创建文件系统
fs, err := filesystem.NewFileSystem(share.Creator())
if err != nil {
return serializer.Err(serializer.CodePolicyNotAllowed, err.Error(), err)
}
defer fs.Recycle()
// 上下文
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 重设根目录
fs.Root = share.Source().(*model.Folder)
fs.Root.Name = "/"
if service.Path != "" {
ok, parent := fs.IsPathExist(service.Path)
if !ok {
return serializer.Err(serializer.CodeParentNotExist, "Cannot find parent folder", nil)
}
fs.Root = parent
}
// 分享Key上下文
ctx = context.WithValue(ctx, fsctx.ShareKeyCtx, hashid.HashID(share.ID, hashid.ShareID))
return service.SearchKeywords(c, fs, "%"+service.Keywords+"%")
}

@ -37,24 +37,24 @@ func (service *UserResetService) Reset(c *gin.Context) serializer.Response {
// 取得原始用户ID
uid, err := hashid.DecodeHashID(service.ID, hashid.UserID)
if err != nil {
return serializer.Err(serializer.CodeNotFound, "重设链接无效", err)
return serializer.Err(serializer.CodeInvalidTempLink, "Invalid link", err)
}
// 检查重设会话
resetSession, exist := cache.Get(fmt.Sprintf("user_reset_%d", uid))
if !exist || resetSession.(string) != service.Secret {
return serializer.Err(serializer.CodeNotFound, "链接已过期", err)
return serializer.Err(serializer.CodeTempLinkExpired, "Link is expired", err)
}
// 重设用户密码
user, err := model.GetActiveUserByID(uid)
if err != nil {
return serializer.Err(serializer.CodeNotFound, "用户不存在", err)
return serializer.Err(serializer.CodeUserNotFound, "User not found", nil)
}
user.SetPassword(service.Password)
if err := user.Update(map[string]interface{}{"password": user.Password}); err != nil {
return serializer.DBErr("无法重设密码", err)
return serializer.DBErr("Failed to reset password", err)
}
cache.Deletes([]string{fmt.Sprintf("%d", uid)}, "user_reset_")
@ -67,10 +67,10 @@ func (service *UserResetEmailService) Reset(c *gin.Context) serializer.Response
if user, err := model.GetUserByEmail(service.UserName); err == nil {
if user.Status == model.Baned || user.Status == model.OveruseBaned {
return serializer.Err(403, "该账号已被封禁", nil)
return serializer.Err(serializer.CodeUserBaned, "This user is banned", nil)
}
if user.Status == model.NotActivicated {
return serializer.Err(403, "该账号未激活", nil)
return serializer.Err(serializer.CodeUserNotActivated, "This user is not activated", nil)
}
// 创建密码重设会话
secret := util.RandStringRunes(32)
@ -87,7 +87,7 @@ func (service *UserResetEmailService) Reset(c *gin.Context) serializer.Response
// 发送密码重设邮件
title, body := email.NewResetEmail(user.Nick, finalURL.String())
if err := email.Send(user.Email, title, body); err != nil {
return serializer.Err(serializer.CodeInternalSetting, "无法发送密码重设邮件", err)
return serializer.Err(serializer.CodeFailedSendEmail, "Failed to send email", err)
}
}
@ -101,12 +101,12 @@ func (service *Enable2FA) Login(c *gin.Context) serializer.Response {
// 查找用户
expectedUser, err := model.GetActiveUserByID(uid)
if err != nil {
return serializer.Err(serializer.CodeNotFound, "用户不存在", nil)
return serializer.Err(serializer.CodeUserNotFound, "User not found", nil)
}
// 验证二步验证代码
if !totp.Validate(service.Code, expectedUser.TwoFactor) {
return serializer.ParamErr("验证代码不正确", nil)
return serializer.Err(serializer.Code2FACodeErr, "2FA code not correct", nil)
}
//登陆成功清空并设置session
@ -118,7 +118,7 @@ func (service *Enable2FA) Login(c *gin.Context) serializer.Response {
return serializer.BuildUserResponse(expectedUser)
}
return serializer.Err(serializer.CodeNotFound, "登录会话不存在", nil)
return serializer.Err(serializer.CodeLoginSessionNotExist, "Login session not exist", nil)
}
// Login 用户登录函数
@ -126,16 +126,16 @@ func (service *UserLoginService) Login(c *gin.Context) serializer.Response {
expectedUser, err := model.GetUserByEmail(service.UserName)
// 一系列校验
if err != nil {
return serializer.Err(serializer.CodeCredentialInvalid, "用户邮箱或密码错误", err)
return serializer.Err(serializer.CodeCredentialInvalid, "Wrong password or email address", err)
}
if authOK, _ := expectedUser.CheckPassword(service.Password); !authOK {
return serializer.Err(serializer.CodeCredentialInvalid, "用户邮箱或密码错误", nil)
return serializer.Err(serializer.CodeCredentialInvalid, "Wrong password or email address", nil)
}
if expectedUser.Status == model.Baned || expectedUser.Status == model.OveruseBaned {
return serializer.Err(403, "该账号已被封禁", nil)
return serializer.Err(serializer.CodeUserBaned, "This account has been blocked", nil)
}
if expectedUser.Status == model.NotActivicated {
return serializer.Err(403, "该账号未激活", nil)
return serializer.Err(serializer.CodeUserNotActivated, "This account is not activated", nil)
}
if expectedUser.TwoFactor != "" {

Loading…
Cancel
Save