Feat: sign file source url

This commit is contained in:
HFO4
2019-12-10 17:10:34 +08:00
parent 1963a495fb
commit 36d5f51495
15 changed files with 174 additions and 47 deletions

View File

@@ -3,6 +3,7 @@ package filesystem
import (
"context"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/juju/ratelimit"
@@ -33,8 +34,8 @@ func withSpeedLimit(rs io.ReadSeeker, speed int) io.ReadSeeker {
// AddFile 新增文件记录
func (fs *FileSystem) AddFile(ctx context.Context, parent *model.Folder) (*model.File, error) {
file := ctx.Value(FileHeaderCtx).(FileHeader)
filePath := ctx.Value(SavePathCtx).(string)
file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
filePath := ctx.Value(fsctx.SavePathCtx).(string)
newFile := model.File{
Name: file.GetFileName(),
@@ -86,7 +87,7 @@ func (fs *FileSystem) GetContent(ctx context.Context, path string) (io.ReadSeeke
return nil, ErrObjectNotExist
}
fs.FileTarget = []model.File{*file}
ctx = context.WithValue(ctx, fsctx.FileModelCtx, file)
// 将当前存储策略重设为文件使用的
fs.Policy = file.GetPolicy()
err = fs.dispatchHandler()
@@ -161,6 +162,8 @@ func (fs *FileSystem) GetSource(ctx context.Context, fileID uint) (string, error
}
fs.FileTarget = []model.File{fileObject[0]}
ctx = context.WithValue(ctx, fsctx.FileModelCtx, fileObject[0])
// 将当前存储策略重设为文件使用的
fs.Policy = fileObject[0].GetPolicy()
err = fs.dispatchHandler()
@@ -172,5 +175,13 @@ func (fs *FileSystem) GetSource(ctx context.Context, fileID uint) (string, error
if !fs.Policy.IsOriginLinkEnable {
return "", serializer.NewError(serializer.CodePolicyNotAllowed, "当前存储策略无法获得外链", nil)
}
return "", nil
// 生成外链地址
siteURL := model.GetSiteURL()
source, err := fs.Handler.Source(ctx, fileObject[0].SourceName, *siteURL, 0)
if err != nil {
return "", serializer.NewError(serializer.CodeNotSet, "无法获取外链", err)
}
return source, nil
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/filesystem/local"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/jinzhu/gorm"
@@ -35,8 +36,8 @@ func TestFileSystem_AddFile(t *testing.T) {
},
},
}
ctx := context.WithValue(context.Background(), FileHeaderCtx, file)
ctx = context.WithValue(ctx, SavePathCtx, "/Uploads/1_sad.txt")
ctx := context.WithValue(context.Background(), fsctx.FileHeaderCtx, file)
ctx = context.WithValue(ctx, fsctx.SavePathCtx, "/Uploads/1_sad.txt")
_, err := fs.AddFile(ctx, &folder)

View File

@@ -8,6 +8,7 @@ import (
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/gin-gonic/gin"
"io"
"net/url"
)
// FileHeader 上传来的文件数据处理器
@@ -30,6 +31,8 @@ type Handler interface {
Get(ctx context.Context, path string) (io.ReadSeeker, error)
// 获取缩略图
Thumb(ctx context.Context, path string) (*response.ContentResponse, error)
// 获取外链地址url
Source(ctx context.Context, path string, url url.URL, expires int64) (string, error)
}
// FileSystem 管理文件的文件系统
@@ -79,17 +82,23 @@ func NewFileSystem(user *model.User) (*FileSystem, error) {
// dispatchHandler 根据存储策略分配文件适配器
func (fs *FileSystem) dispatchHandler() error {
var policyType string
var currentPolicy *model.Policy
if fs.Policy == nil {
// 如果没有具体指定,就是用用户当前存储策略
policyType = fs.User.Policy.Type
currentPolicy = &fs.User.Policy
} else {
policyType = fs.Policy.Type
currentPolicy = fs.Policy
}
// 根据存储策略类型分配适配器
switch policyType {
case "local":
fs.Handler = local.Handler{}
fs.Handler = local.Handler{
Policy: currentPolicy,
}
return nil
default:
return ErrUnknownPolicyType

View File

@@ -1,4 +1,4 @@
package filesystem
package fsctx
type key int
@@ -11,4 +11,6 @@ const (
FileHeaderCtx
// PathCtx 文件或目录的虚拟路径
PathCtx
// FileModelCtx 文件数据库模型
FileModelCtx
)

View File

@@ -3,6 +3,7 @@ package filesystem
import (
"context"
"errors"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/util"
)
@@ -40,7 +41,7 @@ func (fs *FileSystem) Trigger(ctx context.Context, hooks []Hook) error {
// HookIsFileExist 检查虚拟路径文件是否存在
func HookIsFileExist(ctx context.Context, fs *FileSystem) error {
filePath := ctx.Value(PathCtx).(string)
filePath := ctx.Value(fsctx.PathCtx).(string)
if ok, _ := fs.IsFileExist(filePath); ok {
return nil
}
@@ -49,7 +50,7 @@ func HookIsFileExist(ctx context.Context, fs *FileSystem) error {
// HookValidateFile 一系列对文件检验的集合
func HookValidateFile(ctx context.Context, fs *FileSystem) error {
file := ctx.Value(FileHeaderCtx).(FileHeader)
file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
// 验证单文件尺寸
if !fs.ValidateFileSize(ctx, file.GetSize()) {
@@ -72,7 +73,7 @@ func HookValidateFile(ctx context.Context, fs *FileSystem) error {
// HookValidateCapacity 验证并扣除用户容量,包含数据库操作
func HookValidateCapacity(ctx context.Context, fs *FileSystem) error {
file := ctx.Value(FileHeaderCtx).(FileHeader)
file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
// 验证并扣除容量
if !fs.ValidateCapacity(ctx, file.GetSize()) {
return ErrInsufficientCapacity
@@ -82,7 +83,7 @@ func HookValidateCapacity(ctx context.Context, fs *FileSystem) error {
// HookDeleteTempFile 删除已保存的临时文件
func HookDeleteTempFile(ctx context.Context, fs *FileSystem) error {
filePath := ctx.Value(SavePathCtx).(string)
filePath := ctx.Value(fsctx.SavePathCtx).(string)
// 删除临时文件
if util.Exists(filePath) {
_, err := fs.Handler.Delete(ctx, []string{filePath})
@@ -96,7 +97,7 @@ func HookDeleteTempFile(ctx context.Context, fs *FileSystem) error {
// HookGiveBackCapacity 归还用户容量
func HookGiveBackCapacity(ctx context.Context, fs *FileSystem) error {
file := ctx.Value(FileHeaderCtx).(FileHeader)
file := ctx.Value(fsctx.FileHeaderCtx).(FileHeader)
// 归还用户容量
if !fs.User.DeductionStorage(file.GetSize()) {
@@ -108,7 +109,7 @@ func HookGiveBackCapacity(ctx context.Context, fs *FileSystem) error {
// GenericAfterUpload 文件上传完成后,包含数据库操作
func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
// 文件存放的虚拟路径
virtualPath := ctx.Value(FileHeaderCtx).(FileHeader).GetVirtualPath()
virtualPath := ctx.Value(fsctx.FileHeaderCtx).(FileHeader).GetVirtualPath()
// 检查路径是否存在
isExist, folder := fs.IsPathExist(virtualPath)
@@ -119,7 +120,7 @@ func GenericAfterUpload(ctx context.Context, fs *FileSystem) error {
// 检查文件是否存在
if ok, _ := fs.IsChildFileExist(
folder,
ctx.Value(FileHeaderCtx).(FileHeader).GetFileName(),
ctx.Value(fsctx.FileHeaderCtx).(FileHeader).GetFileName(),
); ok {
return ErrFileExisted
}

View File

@@ -5,6 +5,7 @@ import (
"errors"
"github.com/DATA-DOG/go-sqlmock"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/filesystem/local"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/assert"
@@ -18,7 +19,7 @@ func TestGenericBeforeUpload(t *testing.T) {
Size: 5,
Name: "1.txt",
}
ctx := context.WithValue(context.Background(), FileHeaderCtx, file)
ctx := context.WithValue(context.Background(), fsctx.FileHeaderCtx, file)
fs := FileSystem{
User: &model.User{
Storage: 0,
@@ -38,15 +39,15 @@ func TestGenericBeforeUpload(t *testing.T) {
file.Size = 1
file.Name = "1"
ctx = context.WithValue(context.Background(), FileHeaderCtx, file)
ctx = context.WithValue(context.Background(), fsctx.FileHeaderCtx, file)
asserts.Error(HookValidateFile(ctx, &fs))
file.Name = "1.txt"
ctx = context.WithValue(context.Background(), FileHeaderCtx, file)
ctx = context.WithValue(context.Background(), fsctx.FileHeaderCtx, file)
asserts.NoError(HookValidateFile(ctx, &fs))
file.Name = "1.t/xt"
ctx = context.WithValue(context.Background(), FileHeaderCtx, file)
ctx = context.WithValue(context.Background(), fsctx.FileHeaderCtx, file)
asserts.Error(HookValidateFile(ctx, &fs))
}
@@ -59,8 +60,8 @@ func TestGenericAfterUploadCanceled(t *testing.T) {
Size: 5,
Name: "TestGenericAfterUploadCanceled",
}
ctx := context.WithValue(context.Background(), SavePathCtx, "TestGenericAfterUploadCanceled")
ctx = context.WithValue(ctx, FileHeaderCtx, file)
ctx := context.WithValue(context.Background(), fsctx.SavePathCtx, "TestGenericAfterUploadCanceled")
ctx = context.WithValue(ctx, fsctx.FileHeaderCtx, file)
fs := FileSystem{
User: &model.User{Storage: 5},
Handler: local.Handler{},
@@ -97,11 +98,11 @@ func TestGenericAfterUpload(t *testing.T) {
},
}
ctx := context.WithValue(context.Background(), FileHeaderCtx, local.FileStream{
ctx := context.WithValue(context.Background(), fsctx.FileHeaderCtx, local.FileStream{
VirtualPath: "/我的文件",
Name: "test.txt",
})
ctx = context.WithValue(ctx, SavePathCtx, "")
ctx = context.WithValue(ctx, fsctx.SavePathCtx, "")
// 正常
mock.ExpectQuery("SELECT(.+)").
@@ -228,7 +229,7 @@ func TestHookIsFileExist(t *testing.T) {
ID: 1,
},
}}
ctx := context.WithValue(context.Background(), PathCtx, "/test.txt")
ctx := context.WithValue(context.Background(), fsctx.PathCtx, "/test.txt")
{
mock.ExpectQuery("SELECT(.+)").
WithArgs(1).
@@ -263,7 +264,7 @@ func TestHookValidateCapacity(t *testing.T) {
MaxStorage: 11,
},
}}
ctx := context.WithValue(context.Background(), FileHeaderCtx, local.FileStream{Size: 10})
ctx := context.WithValue(context.Background(), fsctx.FileHeaderCtx, local.FileStream{Size: 10})
{
err := HookValidateCapacity(ctx, fs)
asserts.NoError(err)

View File

@@ -2,16 +2,25 @@ package local
import (
"context"
"errors"
"fmt"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/auth"
"github.com/HFO4/cloudreve/pkg/conf"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/HFO4/cloudreve/pkg/serializer"
"github.com/HFO4/cloudreve/pkg/util"
"io"
"net/url"
"os"
"path/filepath"
)
// Handler 本地策略适配器
type Handler struct{}
type Handler struct {
Policy *model.Policy
}
// Get 获取文件内容
func (handler Handler) Get(ctx context.Context, path string) (io.ReadSeeker, error) {
@@ -97,3 +106,23 @@ func (handler Handler) Thumb(ctx context.Context, path string) (*response.Conten
Content: file,
}, nil
}
// Source 获取外链URL
func (handler Handler) Source(ctx context.Context, path string, url url.URL, expires int64) (string, error) {
file, ok := ctx.Value(fsctx.FileModelCtx).(model.File)
if !ok {
return "", errors.New("无法获取文件记录上下文")
}
// 签名生成文件记录
signedURI, err := auth.SignURI(
fmt.Sprintf("/api/v3/file/get/%d/%s", file.ID, file.Name),
0,
)
if err != nil {
return "", serializer.NewError(serializer.CodeEncryptError, "无法对URL进行签名", err)
}
finalURL := url.ResolveReference(signedURI).String()
return finalURL, nil
}

View File

@@ -2,6 +2,7 @@ package filesystem
import (
"context"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/util"
"github.com/gin-gonic/gin"
"path/filepath"
@@ -14,7 +15,7 @@ import (
// Upload 上传文件
func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
ctx = context.WithValue(ctx, FileHeaderCtx, file)
ctx = context.WithValue(ctx, fsctx.FileHeaderCtx, file)
// 上传前的钩子
err = fs.Trigger(ctx, fs.BeforeUpload)
@@ -35,7 +36,7 @@ func (fs *FileSystem) Upload(ctx context.Context, file FileHeader) (err error) {
}
// 上传完成后的钩子
ctx = context.WithValue(ctx, SavePathCtx, savePath)
ctx = context.WithValue(ctx, fsctx.SavePathCtx, savePath)
err = fs.Trigger(ctx, fs.AfterUpload)
if err != nil {
@@ -70,7 +71,7 @@ func (fs *FileSystem) GenerateSavePath(ctx context.Context, file FileHeader) str
// CancelUpload 监测客户端取消上传
func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHeader) {
ginCtx := ctx.Value(GinCtx).(*gin.Context)
ginCtx := ctx.Value(fsctx.GinCtx).(*gin.Context)
select {
case <-ginCtx.Request.Context().Done():
select {
@@ -82,7 +83,7 @@ func (fs *FileSystem) CancelUpload(ctx context.Context, path string, file FileHe
if fs.AfterUploadCanceled == nil {
return
}
ctx = context.WithValue(ctx, SavePathCtx, path)
ctx = context.WithValue(ctx, fsctx.SavePathCtx, path)
err := fs.Trigger(ctx, fs.AfterUploadCanceled)
if err != nil {
util.Log().Debug("执行 AfterUploadCanceled 钩子出错,%s", err)

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
model "github.com/HFO4/cloudreve/models"
"github.com/HFO4/cloudreve/pkg/filesystem/fsctx"
"github.com/HFO4/cloudreve/pkg/filesystem/local"
"github.com/HFO4/cloudreve/pkg/filesystem/response"
"github.com/gin-gonic/gin"
@@ -13,6 +14,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)
@@ -40,6 +42,11 @@ func (m FileHeaderMock) Thumb(ctx context.Context, files string) (*response.Cont
return args.Get(0).(*response.ContentResponse), args.Error(1)
}
func (m FileHeaderMock) Source(ctx context.Context, path string, url url.URL, expires int64) (string, error) {
args := m.Called(ctx, path, url, expires)
return args.Get(0).(string), args.Error(1)
}
func TestFileSystem_Upload(t *testing.T) {
asserts := assert.New(t)
@@ -61,7 +68,7 @@ func TestFileSystem_Upload(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
c, _ := gin.CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", nil)
ctx = context.WithValue(ctx, GinCtx, c)
ctx = context.WithValue(ctx, fsctx.GinCtx, c)
cancel()
file := local.FileStream{
Size: 5,