diff --git a/pkg/filesystem/archive.go b/pkg/filesystem/archive.go index fe71993..1527884 100644 --- a/pkg/filesystem/archive.go +++ b/pkg/filesystem/archive.go @@ -93,7 +93,6 @@ func (fs *FileSystem) Compress(ctx context.Context, folderIDs, fileIDs []uint) ( } // cancelCompress 取消压缩进程 -// TODO 测试 func (fs *FileSystem) cancelCompress(ctx context.Context, zipWriter *zip.Writer, file *os.File, path string) { util.Log().Debug("客户端取消压缩请求") zipWriter.Close() diff --git a/pkg/filesystem/file.go b/pkg/filesystem/file.go index 1113441..efa17a8 100644 --- a/pkg/filesystem/file.go +++ b/pkg/filesystem/file.go @@ -88,14 +88,23 @@ func (fs *FileSystem) GetPhysicalFileContent(ctx context.Context, path string) ( } // Preview 预览文件 -func (fs *FileSystem) Preview(ctx context.Context, path string) (*response.ContentResponse, error) { +// path - 文件虚拟路径 +// isText - 是否为文本文件,文本文件会忽略重定向,直接由 +// 服务端拉取中转给用户,故会对文件大小进行限制 +func (fs *FileSystem) Preview(ctx context.Context, path string, isText bool) (*response.ContentResponse, error) { err := fs.resetFileIfNotExist(ctx, path) if err != nil { return nil, err } + // 如果是文本文件预览,需要检查大小限制 + sizeLimit := model.GetIntSetting("maxEditSize", 2<<20) + if fs.FileTarget[0].Size > uint64(sizeLimit) { + return nil, ErrFileSizeTooBig + } + // 是否直接返回文件内容 - if fs.Policy.IsDirectlyPreview() { + if isText || fs.Policy.IsDirectlyPreview() { resp, err := fs.GetDownloadContent(ctx, path) if err != nil { return nil, err diff --git a/pkg/filesystem/file_test.go b/pkg/filesystem/file_test.go index bdc377a..2d58a15 100644 --- a/pkg/filesystem/file_test.go +++ b/pkg/filesystem/file_test.go @@ -510,7 +510,7 @@ func TestFileSystem_Preview(t *testing.T) { User: &model.User{}, } mock.ExpectQuery("SELECT(.+)").WillReturnRows(sqlmock.NewRows([]string{"id"})) - resp, err := fs.Preview(ctx, "/1.txt") + resp, err := fs.Preview(ctx, "/1.txt", false) asserts.NoError(mock.ExpectationsWereMet()) asserts.Error(err) asserts.Nil(resp) @@ -530,7 +530,7 @@ func TestFileSystem_Preview(t *testing.T) { }, }, } - resp, err := fs.Preview(ctx, "/1.txt") + resp, err := fs.Preview(ctx, "/1.txt", false) asserts.Error(err) asserts.Nil(resp) } @@ -550,7 +550,7 @@ func TestFileSystem_Preview(t *testing.T) { }, }, } - resp, err := fs.Preview(ctx, "/1.txt") + resp, err := fs.Preview(ctx, "/1.txt", false) asserts.NoError(err) asserts.NotNil(resp) asserts.False(resp.Redirect) @@ -573,9 +573,31 @@ func TestFileSystem_Preview(t *testing.T) { }, } asserts.NoError(cache.Set("setting_preview_timeout", "233", 0)) - resp, err := fs.Preview(ctx, "/1.txt") + resp, err := fs.Preview(ctx, "/1.txt", false) asserts.NoError(err) asserts.NotNil(resp) asserts.True(resp.Redirect) } + + // 文本文件,大小超出限制 + { + fs := FileSystem{ + User: &model.User{}, + } + fs.FileTarget = []model.File{ + { + SourceName: "tests/file1.txt", + PolicyID: 1, + Policy: model.Policy{ + Model: gorm.Model{ID: 1}, + Type: "remote", + }, + Size: 11, + }, + } + asserts.NoError(cache.Set("setting_maxEditSize", "10", 0)) + resp, err := fs.Preview(ctx, "/1.txt", true) + asserts.Equal(ErrFileSizeTooBig, err) + asserts.Nil(resp) + } } diff --git a/pkg/request/request.go b/pkg/request/request.go index df95547..7d5e496 100644 --- a/pkg/request/request.go +++ b/pkg/request/request.go @@ -151,6 +151,7 @@ func (resp *Response) CheckHTTPResponse(status int) *Response { type nopRSCloser struct { body io.ReadCloser + size int64 } // GetRSCloser 返回带有空seeker的body reader @@ -161,6 +162,7 @@ func (resp *Response) GetRSCloser() (response.RSCloser, error) { return nopRSCloser{ body: resp.Response.Body, + size: resp.Response.ContentLength, }, resp.Err } @@ -174,7 +176,16 @@ func (instance nopRSCloser) Close() error { return instance.body.Close() } -// 实现 nopRSCloser seeker +// 实现 nopRSCloser seeker, 只实现seek开头/结尾以便http.ServeContent用于确定正文大小 func (instance nopRSCloser) Seek(offset int64, whence int) (int64, error) { + if offset == 0 { + switch whence { + case io.SeekStart: + return 0, nil + case io.SeekEnd: + return instance.size, nil + } + } return 0, errors.New("未实现") + } diff --git a/pkg/request/request_test.go b/pkg/request/request_test.go index af8157d..212bd44 100644 --- a/pkg/request/request_test.go +++ b/pkg/request/request_test.go @@ -156,14 +156,20 @@ func TestResponse_GetRSCloser(t *testing.T) { // 正常 { resp := Response{ - Response: &http.Response{Body: ioutil.NopCloser(strings.NewReader("123"))}, + Response: &http.Response{ContentLength: 3, Body: ioutil.NopCloser(strings.NewReader("123"))}, } res, err := resp.GetRSCloser() asserts.NoError(err) content, err := ioutil.ReadAll(res) asserts.NoError(err) asserts.Equal("123", string(content)) - _, err = res.Seek(0, 0) + offset, err := res.Seek(0, 0) + asserts.NoError(err) + asserts.Equal(int64(0), offset) + offset, err = res.Seek(0, 2) + asserts.NoError(err) + asserts.Equal(int64(3), offset) + _, err = res.Seek(1, 2) asserts.Error(err) asserts.NoError(res.Close()) } diff --git a/pkg/request/slave_test.go b/pkg/request/slave_test.go index b77816b..a0fc0b4 100644 --- a/pkg/request/slave_test.go +++ b/pkg/request/slave_test.go @@ -26,7 +26,7 @@ func TestRemoteCallback(t *testing.T) { "http://test/test/url", testMock.Anything, testMock.Anything, - ).Return(Response{ + ).Return(&Response{ Err: nil, Response: &http.Response{ StatusCode: 200, diff --git a/pkg/webdav/webdav.go b/pkg/webdav/webdav.go index f76e049..1e78413 100644 --- a/pkg/webdav/webdav.go +++ b/pkg/webdav/webdav.go @@ -230,7 +230,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request, fs * ctx := r.Context() - rs, err := fs.Preview(ctx, reqPath) + rs, err := fs.Preview(ctx, reqPath, false) if err != nil { if err == filesystem.ErrObjectNotExist { return http.StatusNotFound, err diff --git a/routers/controllers/file.go b/routers/controllers/file.go index 21e10b2..a724ace 100644 --- a/routers/controllers/file.go +++ b/routers/controllers/file.go @@ -143,7 +143,7 @@ func Preview(c *gin.Context) { var service explorer.SingleFileService if err := c.ShouldBindUri(&service); err == nil { - res := service.PreviewContent(ctx, c) + res := service.PreviewContent(ctx, c, false) // 是否需要重定向 if res.Code == -301 { c.Redirect(301, res.Data.(string)) @@ -158,6 +158,24 @@ func Preview(c *gin.Context) { } } +// PreviewText 预览文本文件 +func PreviewText(c *gin.Context) { + // 创建上下文 + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + var service explorer.SingleFileService + if err := c.ShouldBindUri(&service); err == nil { + res := service.PreviewContent(ctx, c, true) + // 是否有错误发生 + if res.Code != 0 { + c.JSON(200, res) + } + } else { + c.JSON(200, ErrorResponse(err)) + } +} + // GetDocPreview 获取DOC文件预览地址 func GetDocPreview(c *gin.Context) { // 创建上下文 diff --git a/routers/router.go b/routers/router.go index cac6f7d..fb9a4d8 100644 --- a/routers/router.go +++ b/routers/router.go @@ -166,6 +166,8 @@ func InitMasterRouter() *gin.Engine { file.PUT("download/*path", controllers.CreateDownloadSession) // 预览文件 file.GET("preview/*path", controllers.Preview) + // 获取文本文件内容 + file.GET("content/*path", controllers.PreviewText) // 取得Office文档预览地址 file.GET("doc/*path", controllers.GetDocPreview) // 获取缩略图 diff --git a/service/explorer/file.go b/service/explorer/file.go index d6bb7fa..49b55dd 100644 --- a/service/explorer/file.go +++ b/service/explorer/file.go @@ -208,8 +208,9 @@ func (service *DownloadService) Download(ctx context.Context, c *gin.Context) se } } -// PreviewContent 预览文件,需要登录会话 -func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Context) serializer.Response { +// PreviewContent 预览文件,需要登录会话, isText - 是否为文本文件,文本文件会 +// 强制经由服务端中转 +func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Context, isText bool) serializer.Response { // 创建文件系统 fs, err := filesystem.NewFileSystemFromContext(c) if err != nil { @@ -218,7 +219,7 @@ func (service *SingleFileService) PreviewContent(ctx context.Context, c *gin.Con defer fs.Recycle() // 获取文件预览响应 - resp, err := fs.Preview(ctx, service.Path) + resp, err := fs.Preview(ctx, service.Path, isText) if err != nil { return serializer.Err(serializer.CodeNotSet, err.Error(), err) }