Repository: AlistGo/alist (language: go)

Issue:
alist挂载蓝奏云后，通过webdav挂载到本地磁盘，蓝奏云的内容无法进行读写
### Please make sure of the following things

- [ ] I have read the [documentation](https://alist.nn.ci).
- [ ] I'm sure there are no duplicate issues or discussions.
- [ ] I'm sure it's due to `alist` and not something else(such as `Dependencies` or `Operational`).
- [ ] I'm sure I'm using the latest version

### Alist Version / Alist 版本

v3.6.0

### Driver used / 使用的存储驱动

raiDriver

### Describe the bug / 问题描述

alist挂载蓝奏云后，通过raidrive用webdav方式挂载到本地磁盘，蓝奏云的内容只能查看具体文件结构，但是文件内容无法正常读取与拷贝，提示你没有权限进行操作

### Reproduction / 复现链接

alist挂载蓝奏云后，再用raidrive挂载本地，既可复现

### Logs / 日志

[GIN] 2022/12/13 - 16:15:18 | 404 |   76.472157ms | 121.196.127.101 | HEAD     "/dav/重生：回到1983当富翁.txt"
[31mERRO[0m[2022-12-13 16:15:18] failed put /蓝奏/webdav: 错误300
github.com/alist-org/alist/v3/internal/op.Put
	/home/runner/work/alist/alist/internal/op/fs.go:365
github.com/alist-org/alist/v3/internal/fs.putDirectly
	/home/runner/work/alist/alist/internal/fs/put.go:54
github.com/alist-org/alist/v3/internal/fs.PutDirectly
	/home/runner/work/alist/alist/internal/fs/fs.go:84
github.com/alist-org/alist/v3/server/webdav.(*Handler).handlePut
	/home/runner/work/alist/alist/server/webdav/webdav.go:325
github.com/alist-org/alist/v3/server/webdav.(*Handler).ServeHTTP
	/home/runner/work/alist/alist/server/webdav/webdav.go:62
github.com/alist-org/alist/v3/server.ServeWebDAV
	/home/runner/work/alist/alist/server/webdav.go:44
github.com/gin-gonic/gin.(*Context).Next
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173
github.com/alist-org/alist/v3/server.WebDAVAuth
	/home/runner/work/alist/alist/server/webdav.go:93
github.com/gin-gonic/gin.(*Context).Next
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173
github.com/alist-org/alist/v3/server/middlewares.StoragesLoaded
	/home/runner/work/alist/alist/server/middlewares/check.go:11
github.com/gin-gonic/gin.(*Context).Next
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173
github.com/gin-gonic/gin.CustomRecoveryWithWriter.func1
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/recovery.go:101
github.com/gin-gonic/gin.(*Context).Next
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173
github.com/gin-gonic/gin.LoggerWithConfig.func1
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/logger.go:240
github.com/gin-gonic/gin.(*Context).Next
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/context.go:173
github.com/gin-gonic/gin.(*Engine).handleHTTPRequest
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/gin.go:616
github.com/gin-gonic/gin.(*Engine).ServeHTTP
	/home/runner/go/pkg/mod/github.com/gin-gonic/gin@v1.8.1/gin.go:572
net/http.serverHandler.ServeHTTP
	/opt/hostedtoolcache/go/1.19.3/x64/src/net/http/server.go:2947
net/http.(*conn).serve
	/opt/hostedtoolcache/go/1.19.3/x64/src/net/http/server.go:1991
runtime.goexit
	/opt/hostedtoolcache/go/1.19.3/x64/src/runtime/asm_amd64.s:1594 


Discussion / hints:
Thanks for opening your first issue here! Be sure to follow the issue template!

raidrive上传文件需要预先创建空占位文件，所以：对于所有不支持上传空文件的存储均无法上传。显然 蓝奏云就属于其中一种。
> raidrive上传文件需要预先创建空占位文件，所以：对于所有不支持上传空文件的存储均无法上传。显然 蓝奏云就属于其中一种。

但是，它读都不行。。。文件拷贝不到其他盘符
蓝奏云的直链对ua有特定要求 对于一些ua它还会经过一个网页跳转，所以raidirve可能完全不支持蓝奏云。
谁能告诉我，v3版本的webdav怎么配置，我已经找了一个星期资料，就是没有办法链接
> 谁能告诉我，v3版本的webdav怎么配置，我已经找了一个星期资料，就是没有办法链接

额。。。不知道你找了一个星期资料所谓的「webdav怎么配置」具体是什么，是否看了文档说明，里面的webdav部分是不是你要找的
> raidrive上传文件需要预先创建空占位文件，所以：对于所有不支持上传空文件的存储均无法上传。显然 蓝奏云就属于其中一种。

你这么一说，那估计天翼云也是同样道理，无法上传
> 谁能告诉我，v3版本的webdav怎么配置，我已经找了一个星期资料，就是没有办法链接

一样用不了webdav，挂载和彩云，网页都能正常显示文件目录，但是用owncloud或是rclone都没法用，随便列个目录都卡在那边.....
> > 谁能告诉我，v3版本的webdav怎么配置，我已经找了一个星期资料，就是没有办法链接
> 
> 额。。。不知道你找了一个星期资料所谓的「webdav怎么配置」具体是什么，是否看了文档说明，里面的webdav部分是不是你要找的

测试了下我知道原因了。默认情况下，假设部署在服务器A上，A服务器ip假设为192.168.0.2 那么webdav的地址访问只能用127.0.0.1(ssh用隧道映射到本地)，而用服务器地址webdav无法访问....（防火墙已开...）。我觉得alist是不是有类似于 信任域名这一类的东西啊？或者单纯就是一个bug，webdav只绑定了127.0.0.1，而不是0.0.0.0.。导致其他客户端访问不了。版本3.6.0
> 蓝奏云的直链对ua有特定要求 对于一些ua它还会经过一个网页跳转，所以raidirve可能完全不支持蓝奏云。

额，这么说的话，raidirve不行，有没有推荐的本地挂载dav的软件能够支持到这种特性的吗，直接在alist上面操作是没问题的，主要是想本地挂载蓝奏云，可以拿来当云音乐库、云相册之类的用，蓝奏云的特性非常适合这类的小文件
> > > 谁能告诉我，v3版本的webdav怎么配置，我已经找了一个星期资料，就是没有办法链接
> > 
> > 
> > 额。。。不知道你找了一个星期资料所谓的「webdav怎么配置」具体是什么，是否看了文档说明，里面的webdav部分是不是你要找的
> 
> 测试了下我知道原因了。默认情况下，假设部署在服务器A上，A服务器ip假设为192.168.0.2 那么webdav的地址访问只能用127.0.0.1(ssh用隧道映射到本地)，而用服务器地址webdav无法访问....（防火墙已开...）。我觉得alist是不是有类似于 信任域名这一类的东西啊？或者单纯就是一个bug，webdav只绑定了127.0.0.1，而不是0.0.0.0.。导致其他客户端访问不了。版本3.6.0

最后折腾了一圈丢掉了频繁出问题的owncloud，alist也基本成为了和彩云的挂载工具，服务器上webdav附加到rclone，通过rclone的serve模式提供各种协议给各种可视化软件用了。还可以顺带加解密保护xjj。
loryncien ***@***.***>于2022年12月13日 周二21:28写道：

> 谁能告诉我，v3版本的webdav怎么配置，我已经找了一个星期资料，就是没有办法链接
>
> 额。。。不知道你找了一个星期资料所谓的「webdav怎么配置」具体是什么，是否看文档说明，里面的webdav是不是你要得
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/alist-org/alist/issues/2700#issuecomment-1348545970>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ASUBRJFJ5KDQ7T7PVJCXGL3WNB2WNANCNFSM6AAAAAAS44WXPQ>
> .
> You are receiving this because you commented.Message ID:
> ***@***.***>
>
哈哈哈，已经解决了，谢谢，

蓝奏云太狗了,每个doupload.php后面加了个uid.😂

Fix patch:
diff --git a/drivers/lanzou/driver.go b/drivers/lanzou/driver.go
index 810b0193b06..4e96ad1db82 100644
--- a/drivers/lanzou/driver.go
+++ b/drivers/lanzou/driver.go
@@ -52,30 +52,65 @@ func (d *LanZou) Drop(ctx context.Context) error {
 // 获取的大小和时间不准确
 func (d *LanZou) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
 	if d.IsCookie() {
-		return d.GetFiles(ctx, dir.GetID())
+		return d.GetAllFiles(dir.GetID())
 	} else {
-		return d.GetFileOrFolderByShareUrl(ctx, dir.GetID(), d.SharePassword)
+		return d.GetFileOrFolderByShareUrl(dir.GetID(), d.SharePassword)
 	}
 }
 
 func (d *LanZou) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
-	downID := file.GetID()
-	pwd := d.SharePassword
-	if d.IsCookie() {
-		share, err := d.getFileShareUrlByID(ctx, file.GetID())
+	var (
+		err   error
+		dfile *FileOrFolderByShareUrl
+	)
+	switch file := file.(type) {
+	case *FileOrFolder:
+		// 先获取分享链接
+		sfile := file.GetShareInfo()
+		if sfile == nil {
+			sfile, err = d.getFileShareUrlByID(file.GetID())
+			if err != nil {
+				return nil, err
+			}
+			file.SetShareInfo(sfile)
+		}
+
+		// 然后获取下载链接
+		dfile, err = d.GetFilesByShareUrl(sfile.FID, sfile.Pwd)
 		if err != nil {
 			return nil, err
 		}
-		downID = share.FID
-		pwd = share.Pwd
-	}
-	fileInfo, err := d.getFilesByShareUrl(ctx, downID, pwd, nil)
-	if err != nil {
-		return nil, err
+		// 修复文件大小
+		if d.RepairFileInfo && !file.repairFlag {
+			size, time := d.getFileRealInfo(dfile.Url)
+			if size != nil {
+				file.size = size
+				file.repairFlag = true
+			}
+			if file.time != nil {
+				file.time = time
+			}
+		}
+	case *FileOrFolderByShareUrl:
+		dfile, err = d.GetFilesByShareUrl(file.GetID(), file.Pwd)
+		if err != nil {
+			return nil, err
+		}
+		// 修复文件大小
+		if d.RepairFileInfo && !file.repairFlag {
+			size, time := d.getFileRealInfo(dfile.Url)
+			if size != nil {
+				file.size = size
+				file.repairFlag = true
+			}
+			if file.time != nil {
+				file.time = time
+			}
+		}
 	}
 
 	return &model.Link{
-		URL: fileInfo.Url,
+		URL: dfile.Url,
 		Header: http.Header{
 			"User-Agent": []string{base.UserAgent},
 		},
@@ -133,10 +168,6 @@ func (d *LanZou) Rename(ctx context.Context, srcObj model.Obj, newName string) e
 	return errs.NotImplement
 }
 
-func (d *LanZou) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
-	return errs.NotImplement
-}
-
 func (d *LanZou) Remove(ctx context.Context, obj model.Obj) error {
 	if d.IsCookie() {
 		_, err := d.doupload(func(req *resty.Request) {
diff --git a/drivers/lanzou/help.go b/drivers/lanzou/help.go
index f48a936fbeb..4cb646c421a 100644
--- a/drivers/lanzou/help.go
+++ b/drivers/lanzou/help.go
@@ -17,6 +17,7 @@ const DAY time.Duration = 84600000000000
 // 解析时间
 var timeSplitReg = regexp.MustCompile("([0-9.]*)\\s*([\u4e00-\u9fa5]+)")
 
+// 如果解析失败,则返回当前时间
 func MustParseTime(str string) time.Time {
 	lastOpTime, err := time.ParseInLocation("2006-01-02 -07", str+" +08", time.Local)
 	if err != nil {
@@ -47,6 +48,7 @@ func MustParseTime(str string) time.Time {
 // 解析大小
 var sizeSplitReg = regexp.MustCompile(`(?i)([0-9.]+)\s*([bkm]+)`)
 
+// 解析失败返回0
 func SizeStrToInt64(size string) int64 {
 	strs := sizeSplitReg.FindStringSubmatch(size)
 	if len(strs) < 3 {
@@ -66,8 +68,13 @@ func SizeStrToInt64(size string) int64 {
 }
 
 // 移除注释
-func RemoveNotes(html []byte) []byte {
-	return regexp.MustCompile(`<!--.*?-->|//.*|/\*.*?\*/`).ReplaceAll(html, []byte{})
+func RemoveNotes(html string) string {
+	return regexp.MustCompile(`<!--.*?-->|[^:]//.*|/\*.*?\*/`).ReplaceAllStringFunc(html, func(b string) string {
+		if b[1:3] == "//" {
+			return b[:1]
+		}
+		return "\n"
+	})
 }
 
 var findAcwScV2Reg = regexp.MustCompile(`arg1='([0-9A-Z]+)'`)
diff --git a/drivers/lanzou/meta.go b/drivers/lanzou/meta.go
index 8b2267e947a..5fe21d26ac8 100644
--- a/drivers/lanzou/meta.go
+++ b/drivers/lanzou/meta.go
@@ -7,11 +7,12 @@ import (
 
 type Addition struct {
 	Type   string `json:"type" type:"select" options:"cookie,url" default:"cookie"`
-	Cookie string `json:"cookie" required:"true" help:"about 15 days valid"`
+	Cookie string `json:"cookie" required:"true" help:"about 15 days valid, ignore if shareUrl is used"`
 	driver.RootID
-	SharePassword string `json:"share_password"`
-	BaseUrl       string `json:"baseUrl" required:"true" default:"https://pc.woozooo.com"`
-	ShareUrl      string `json:"shareUrl" required:"true" default:"https://pan.lanzouo.com"`
+	SharePassword  string `json:"share_password"`
+	BaseUrl        string `json:"baseUrl" required:"true" default:"https://pc.woozooo.com" help:"basic URL for file operation"`
+	ShareUrl       string `json:"shareUrl" required:"true" default:"https://pan.lanzouo.com" help:"used to get the sharing page"`
+	RepairFileInfo bool   `json:"repair_file_info" help:"To use webdav, you need to enable it"`
 }
 
 func (a *Addition) IsCookie() bool {
diff --git a/drivers/lanzou/types.go b/drivers/lanzou/types.go
index 50f79b095d6..9044b2967fe 100644
--- a/drivers/lanzou/types.go
+++ b/drivers/lanzou/types.go
@@ -1,12 +1,14 @@
 package lanzou
 
 import (
+	"errors"
 	"fmt"
 	"time"
-
-	"github.com/alist-org/alist/v3/internal/model"
 )
 
+var ErrFileShareCancel = errors.New("file sharing cancellation")
+var ErrFileNotExist = errors.New("file does not exist")
+
 type FilesOrFoldersResp struct {
 	Text []FileOrFolder `json:"text"`
 }
@@ -34,27 +36,51 @@ type FileOrFolder struct {
 	FolID string `json:"fol_id"`
 	//Folderlock string `json:"folderlock"`
 	//FolderDes  string `json:"folder_des"`
+
+	// 缓存字段
+	size       *int64     `json:"-"`
+	time       *time.Time `json:"-"`
+	repairFlag bool       `json:"-"`
+	shareInfo  *FileShare `json:"-"`
 }
 
-func (f *FileOrFolder) isFloder() bool {
-	return f.FolID != ""
-}
-func (f *FileOrFolder) ToObj() model.Obj {
-	obj := &model.Object{}
-	if f.isFloder() {
-		obj.ID = f.FolID
-		obj.Name = f.Name
-		obj.Modified = time.Now()
-		obj.IsFolder = true
-	} else {
-		obj.ID = f.ID
-		obj.Name = f.NameAll
-		obj.Modified = MustParseTime(f.Time)
-		obj.Size = SizeStrToInt64(f.Size)
+func (f *FileOrFolder) GetID() string {
+	if f.IsDir() {
+		return f.FolID
+	}
+	return f.ID
+}
+func (f *FileOrFolder) GetName() string {
+	if f.IsDir() {
+		return f.Name
+	}
+	return f.NameAll
+}
+func (f *FileOrFolder) GetPath() string { return "" }
+func (f *FileOrFolder) GetSize() int64 {
+	if f.size == nil {
+		size := SizeStrToInt64(f.Size)
+		f.size = &size
 	}
-	return obj
+	return *f.size
+}
+func (f *FileOrFolder) IsDir() bool { return f.FolID != "" }
+func (f *FileOrFolder) ModTime() time.Time {
+	if f.time == nil {
+		time := MustParseTime(f.Time)
+		f.time = &time
+	}
+	return *f.time
+}
+
+func (f *FileOrFolder) SetShareInfo(fs *FileShare) {
+	f.shareInfo = fs
+}
+func (f *FileOrFolder) GetShareInfo() *FileShare {
+	return f.shareInfo
 }
 
+/* 通过ID获取文件/文件夹分享信息 */
 type FileShareResp struct {
 	Info FileShare `json:"info"`
 }
@@ -73,31 +99,55 @@ type FileShare struct {
 	Des    string `json:"des"`
 }
 
+/* 分享类型为文件夹 */
 type FileOrFolderByShareUrlResp struct {
 	Text []FileOrFolderByShareUrl `json:"text"`
 }
 type FileOrFolderByShareUrl struct {
 	ID      string `json:"id"`
 	NameAll string `json:"name_all"`
-	Size    string `json:"size"`
-	Time    string `json:"time"`
-	Duan    string `json:"duan"`
+
+	// 文件特有
+	Duan string `json:"duan"`
+	Size string `json:"size"`
+	Time string `json:"time"`
 	//Icon          string `json:"icon"`
 	//PIco int `json:"p_ico"`
 	//T int `json:"t"`
-	IsFloder bool
+
+	// 文件夹特有
+	IsFloder bool `json:"-"`
+
+	//
+	Url string `json:"-"`
+	Pwd string `json:"-"`
+
+	// 缓存字段
+	size       *int64     `json:"-"`
+	time       *time.Time `json:"-"`
+	repairFlag bool       `json:"-"`
 }
 
-func (f *FileOrFolderByShareUrl) ToObj() model.Obj {
-	return &model.Object{
-		ID:       f.ID,
-		Name:     f.NameAll,
-		Size:     SizeStrToInt64(f.Size),
-		Modified: MustParseTime(f.Time),
-		IsFolder: f.IsFloder,
+func (f *FileOrFolderByShareUrl) GetID() string   { return f.ID }
+func (f *FileOrFolderByShareUrl) GetName() string { return f.NameAll }
+func (f *FileOrFolderByShareUrl) GetPath() string { return "" }
+func (f *FileOrFolderByShareUrl) GetSize() int64 {
+	if f.size == nil {
+		size := SizeStrToInt64(f.Size)
+		f.size = &size
+	}
+	return *f.size
+}
+func (f *FileOrFolderByShareUrl) IsDir() bool { return f.IsFloder }
+func (f *FileOrFolderByShareUrl) ModTime() time.Time {
+	if f.time == nil {
+		time := MustParseTime(f.Time)
+		f.time = &time
 	}
+	return *f.time
 }
 
+// 获取下载链接的响应
 type FileShareInfoAndUrlResp[T string | int] struct {
 	Dom string `json:"dom"`
 	URL string `json:"url"`
@@ -111,21 +161,3 @@ func (u *FileShareInfoAndUrlResp[T]) GetBaseUrl() string {
 func (u *FileShareInfoAndUrlResp[T]) GetDownloadUrl() string {
 	return fmt.Sprint(u.GetBaseUrl(), "/", u.URL)
 }
-
-// 通过分享链接获取文件信息和下载链接
-type FileInfoAndUrlByShareUrl struct {
-	ID   string
-	Name string
-	Size string
-	Time string
-	Url  string
-}
-
-func (f *FileInfoAndUrlByShareUrl) ToObj() model.Obj {
-	return &model.Object{
-		ID:       f.ID,
-		Name:     f.Name,
-		Size:     SizeStrToInt64(f.Size),
-		Modified: MustParseTime(f.Time),
-	}
-}
diff --git a/drivers/lanzou/util.go b/drivers/lanzou/util.go
index 7b5d01fdbeb..7bdc079956a 100644
--- a/drivers/lanzou/util.go
+++ b/drivers/lanzou/util.go
@@ -1,7 +1,7 @@
 package lanzou
 
 import (
-	"context"
+	"errors"
 	"fmt"
 	"net/http"
 	"regexp"
@@ -32,7 +32,16 @@ func (d *LanZou) post(url string, callback base.ReqCallback, resp interface{}) (
 }
 
 func (d *LanZou) _post(url string, callback base.ReqCallback, resp interface{}, up bool) ([]byte, error) {
-	data, err := d.request(url, http.MethodPost, callback, up)
+	data, err := d.request(url, http.MethodPost, func(req *resty.Request) {
+		req.AddRetryCondition(func(r *resty.Response, err error) bool {
+			if utils.Json.Get(r.Body(), "zt").ToInt() == 4 {
+				time.Sleep(time.Second)
+				return true
+			}
+			return false
+		})
+		callback(req)
+	}, up)
 	if err != nil {
 		return nil, err
 	}
@@ -85,29 +94,28 @@ func (d *LanZou) request(url string, method string, callback base.ReqCallback, u
 */
 
 // 获取文件和文件夹,获取到的文件大小、更改时间不可信
-func (d *LanZou) GetFiles(ctx context.Context, folderID string) ([]model.Obj, error) {
-	folders, err := d.getFolders(ctx, folderID)
+func (d *LanZou) GetAllFiles(folderID string) ([]model.Obj, error) {
+	folders, err := d.GetFolders(folderID)
 	if err != nil {
 		return nil, err
 	}
-	files, err := d.getFiles(ctx, folderID)
+	files, err := d.GetFiles(folderID)
 	if err != nil {
 		return nil, err
 	}
 	return append(
 		utils.MustSliceConvert(folders, func(folder FileOrFolder) model.Obj {
-			return folder.ToObj()
+			return &folder
 		}), utils.MustSliceConvert(files, func(file FileOrFolder) model.Obj {
-			return file.ToObj()
+			return &file
 		})...,
 	), nil
 }
 
 // 通过ID获取文件夹
-func (d *LanZou) getFolders(ctx context.Context, folderID string) ([]FileOrFolder, error) {
+func (d *LanZou) GetFolders(folderID string) ([]FileOrFolder, error) {
 	var resp FilesOrFoldersResp
 	_, err := d.doupload(func(req *resty.Request) {
-		req.SetContext(ctx)
 		req.SetFormData(map[string]string{
 			"task":      "47",
 			"folder_id": folderID,
@@ -120,12 +128,11 @@ func (d *LanZou) getFolders(ctx context.Context, folderID string) ([]FileOrFolde
 }
 
 // 通过ID获取文件
-func (d *LanZou) getFiles(ctx context.Context, folderID string) ([]FileOrFolder, error) {
+func (d *LanZou) GetFiles(folderID string) ([]FileOrFolder, error) {
 	files := make([]FileOrFolder, 0)
 	for pg := 1; ; pg++ {
 		var resp FilesOrFoldersResp
 		_, err := d.doupload(func(req *resty.Request) {
-			req.SetContext(ctx)
 			req.SetFormData(map[string]string{
 				"task":      "5",
 				"folder_id": folderID,
@@ -144,37 +151,33 @@ func (d *LanZou) getFiles(ctx context.Context, folderID string) ([]FileOrFolder,
 }
 
 // 通过ID获取文件夹分享地址
-func (d *LanZou) getFolderShareUrlByID(ctx context.Context, fileID string) (share FileShare, err error) {
+func (d *LanZou) getFolderShareUrlByID(fileID string) (*FileShare, error) {
 	var resp FileShareResp
-	_, err = d.doupload(func(req *resty.Request) {
-		req.SetContext(ctx)
+	_, err := d.doupload(func(req *resty.Request) {
 		req.SetFormData(map[string]string{
 			"task":    "18",
 			"file_id": fileID,
 		})
 	}, &resp)
 	if err != nil {
-		return
+		return nil, err
 	}
-	share = resp.Info
-	return
+	return &resp.Info, nil
 }
 
 // 通过ID获取文件分享地址
-func (d *LanZou) getFileShareUrlByID(ctx context.Context, fileID string) (share FileShare, err error) {
+func (d *LanZou) getFileShareUrlByID(fileID string) (*FileShare, error) {
 	var resp FileShareResp
-	_, err = d.doupload(func(req *resty.Request) {
-		req.SetContext(ctx)
+	_, err := d.doupload(func(req *resty.Request) {
 		req.SetFormData(map[string]string{
 			"task":    "22",
 			"file_id": fileID,
 		})
 	}, &resp)
 	if err != nil {
-		return
+		return nil, err
 	}
-	share = resp.Info
-	return
+	return &resp.Info, nil
 }
 
 /*
@@ -186,234 +189,252 @@ var isFileReg = regexp.MustCompile(`class="fileinfo"|id="file"|文件描述`)
 var isFolderReg = regexp.MustCompile(`id="infos"`)
 
 // 获取文件文件夹基础信息
+
+// 获取文件名称
 var nameFindReg = regexp.MustCompile(`<title>(.+?) - 蓝奏云</title>|id="filenajax">(.+?)</div>|var filename = '(.+?)';|<div style="font-size.+?>([^<>].+?)</div>|<div class="filethetext".+?>([^<>]+?)</div>`)
+
+// 获取文件大小
 var sizeFindReg = regexp.MustCompile(`(?i)大小\W*([0-9.]+\s*[bkm]+)`)
+
+// 获取文件时间
 var timeFindReg = regexp.MustCompile(`\d+\s*[秒天分小][钟时]?前|[昨前]天|\d{4}-\d{2}-\d{2}`)
 
-var findSubFolaerReg = regexp.MustCompile(`(folderlink|mbxfolder).+href="/(.+?)"(.+filename")?>(.+?)<`) // 查找分享文件夹子文件夹ID和名称
+// 查找分享文件夹子文件夹ID和名称
+var findSubFolaerReg = regexp.MustCompile(`(?i)(?:folderlink|mbxfolder).+href="/(.+?)"(?:.+filename")?>(.+?)<`)
 
-// 获取关键数据
+// 获取下载页面链接
 var findDownPageParamReg = regexp.MustCompile(`<iframe.*?src="(.+?)"`)
 
-// 通过分享链接获取文件或文件夹,如果是文件则会返回下载链接
-func (d *LanZou) GetFileOrFolderByShareUrl(ctx context.Context, downID, pwd string) ([]model.Obj, error) {
-	pageData, err := d.get(fmt.Sprint(d.ShareUrl, "/", downID), func(req *resty.Request) { req.SetContext(ctx) }, nil)
+// 获取分享链接主界面
+func (d *LanZou) getShareUrlHtml(shareID string) (string, error) {
+	var vs string
+	for i := 0; i < 3; i++ {
+		firstPageData, err := d.get(fmt.Sprint(d.ShareUrl, "/", shareID),
+			func(req *resty.Request) {
+				if vs != "" {
+					req.SetCookie(&http.Cookie{
+						Name:  "acw_sc__v2",
+						Value: vs,
+					})
+				}
+			}, nil)
+		if err != nil {
+			return "", err
+		}
+
+		firstPageDataStr := RemoveNotes(string(firstPageData))
+		if strings.Contains(firstPageDataStr, "取消分享") {
+			return "", ErrFileShareCancel
+		}
+		if strings.Contains(firstPageDataStr, "文件不存在") {
+			return "", ErrFileNotExist
+		}
+
+		// acw_sc__v2
+		if strings.Contains(firstPageDataStr, "acw_sc__v2") {
+			if vs, err = CalcAcwScV2(firstPageDataStr); err != nil {
+				log.Errorf("lanzou: err => acw_sc__v2 validation error  ,data => %s\n", firstPageDataStr)
+				return "", err
+			}
+			continue
+		}
+		return firstPageDataStr, nil
+	}
+	return "", errors.New("acw_sc__v2 validation error")
+}
+
+// 通过分享链接获取文件或文件夹
+func (d *LanZou) GetFileOrFolderByShareUrl(shareID, pwd string) ([]model.Obj, error) {
+	pageData, err := d.getShareUrlHtml(shareID)
 	if err != nil {
 		return nil, err
 	}
-	pageData = RemoveNotes(pageData)
 
-	if !isFileReg.Match(pageData) {
-		files, err := d.getFolderByShareUrl(ctx, downID, pwd, pageData)
+	if !isFileReg.MatchString(pageData) {
+		files, err := d.getFolderByShareUrl(pwd, pageData)
 		if err != nil {
 			return nil, err
 		}
 		return utils.MustSliceConvert(files, func(file FileOrFolderByShareUrl) model.Obj {
-			return file.ToObj()
+			return &file
 		}), nil
 	} else {
-		file, err := d.getFilesByShareUrl(ctx, downID, pwd, pageData)
+		file, err := d.getFilesByShareUrl(shareID, pwd, pageData)
 		if err != nil {
 			return nil, err
 		}
-		return []model.Obj{file.ToObj()}, nil
+		return []model.Obj{file}, nil
 	}
 }
 
 // 通过分享链接获取文件(下载链接也使用此方法)
+// FileOrFolderByShareUrl 包含 pwd 和 url 字段
 // 参考 https://github.com/zaxtyson/LanZouCloud-API/blob/ab2e9ec715d1919bf432210fc16b91c6775fbb99/lanzou/api/core.py#L440
-func (d *LanZou) getFilesByShareUrl(ctx context.Context, downID, pwd string, firstPageData []byte) (file FileInfoAndUrlByShareUrl, err error) {
-	if firstPageData == nil {
-		firstPageData, err = d.get(fmt.Sprint(d.ShareUrl, "/", downID), func(req *resty.Request) { req.SetContext(ctx) }, nil)
-		if err != nil {
-			return
-		}
-		firstPageData = RemoveNotes(firstPageData)
-	}
-	firstPageDataStr := string(firstPageData)
-
-	if strings.Contains(firstPageDataStr, "acw_sc__v2") {
-		var vs string
-		if vs, err = CalcAcwScV2(firstPageDataStr); err != nil {
-			return
-		}
-		firstPageData, err = d.get(fmt.Sprint(d.ShareUrl, "/", downID), func(req *resty.Request) {
-			req.SetCookie(&http.Cookie{
-				Name:  "acw_sc__v2",
-				Value: vs,
-			})
-			req.SetContext(ctx)
-		}, nil)
-		if err != nil {
-			return
-		}
-		firstPageData = RemoveNotes(firstPageData)
-		firstPageDataStr = string(firstPageData)
+func (d *LanZou) GetFilesByShareUrl(shareID, pwd string) (file *FileOrFolderByShareUrl, err error) {
+	pageData, err := d.getShareUrlHtml(shareID)
+	if err != nil {
+		return nil, err
 	}
+	return d.getFilesByShareUrl(shareID, pwd, pageData)
+}
 
+func (d *LanZou) getFilesByShareUrl(shareID, pwd string, sharePageData string) (*FileOrFolderByShareUrl, error) {
 	var (
 		param       map[string]string
 		downloadUrl string
 		baseUrl     string
+		file        FileOrFolderByShareUrl
 	)
 
 	// 需要密码
-	if strings.Contains(firstPageDataStr, "pwdload") || strings.Contains(firstPageDataStr, "passwddiv") {
-		param, err = htmlFormToMap(firstPageDataStr)
+	if strings.Contains(sharePageData, "pwdload") || strings.Contains(sharePageData, "passwddiv") {
+		param, err := htmlFormToMap(sharePageData)
 		if err != nil {
-			return
+			return nil, err
 		}
 		param["p"] = pwd
 		var resp FileShareInfoAndUrlResp[string]
-		_, err = d.post(d.ShareUrl+"/ajaxm.php", func(req *resty.Request) { req.SetFormData(param).SetContext(ctx) }, &resp)
+		_, err = d.post(d.ShareUrl+"/ajaxm.php", func(req *resty.Request) { req.SetFormData(param) }, &resp)
 		if err != nil {
-			return
+			return nil, err
 		}
-		file.Name = resp.Inf
+		file.NameAll = resp.Inf
+		file.Pwd = pwd
 		baseUrl = resp.GetBaseUrl()
 		downloadUrl = resp.GetDownloadUrl()
 	} else {
-		urlpaths := findDownPageParamReg.FindStringSubmatch(firstPageDataStr)
+		urlpaths := findDownPageParamReg.FindStringSubmatch(sharePageData)
 		if len(urlpaths) != 2 {
-			err = fmt.Errorf("not find file page param")
-			return
+			log.Errorf("lanzou: err => not find file page param ,data => %s\n", sharePageData)
+			return nil, fmt.Errorf("not find file page param")
 		}
-		var nextPageData []byte
-		nextPageData, err = d.get(fmt.Sprint(d.ShareUrl, urlpaths[1]), func(req *resty.Request) { req.SetContext(ctx) }, nil)
+		data, err := d.get(fmt.Sprint(d.ShareUrl, urlpaths[1]), nil, nil)
 		if err != nil {
-			return
+			return nil, err
 		}
-		nextPageData = RemoveNotes(nextPageData)
-		nextPageDataStr := string(nextPageData)
+		nextPageData := RemoveNotes(string(data))
 
-		param, err = htmlJsonToMap(nextPageDataStr)
+		param, err = htmlJsonToMap(nextPageData)
 		if err != nil {
-			return
+			return nil, err
 		}
 
 		var resp FileShareInfoAndUrlResp[int]
-		_, err = d.post(d.ShareUrl+"/ajaxm.php", func(req *resty.Request) { req.SetFormData(param).SetContext(ctx) }, &resp)
+		_, err = d.post(d.ShareUrl+"/ajaxm.php", func(req *resty.Request) { req.SetFormData(param) }, &resp)
 		if err != nil {
-			return
+			return nil, err
 		}
 		baseUrl = resp.GetBaseUrl()
 		downloadUrl = resp.GetDownloadUrl()
 
-		names := nameFindReg.FindStringSubmatch(firstPageDataStr)
+		names := nameFindReg.FindStringSubmatch(sharePageData)
 		if len(names) > 1 {
 			for _, name := range names[1:] {
 				if name != "" {
-					file.Name = name
+					file.NameAll = name
 					break
 				}
 			}
 		}
 	}
 
-	sizes := sizeFindReg.FindStringSubmatch(firstPageDataStr)
+	sizes := sizeFindReg.FindStringSubmatch(sharePageData)
 	if len(sizes) == 2 {
 		file.Size = sizes[1]
 	}
-	file.ID = downID
-	file.Time = timeFindReg.FindString(firstPageDataStr)
+	file.ID = shareID
+	file.Time = timeFindReg.FindString(sharePageData)
 
 	// 重定向获取真实链接
 	res, err := base.NoRedirectClient.R().SetHeaders(map[string]string{
 		"accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
-	}).SetContext(ctx).Get(downloadUrl)
+	}).Get(downloadUrl)
 	if err != nil {
-		return
+		return nil, err
 	}
 
 	file.Url = res.Header().Get("location")
 
 	// 触发验证
-	rPageDataStr := res.String()
-	if res.StatusCode() != 302 && strings.Contains(rPageDataStr, "网络异常") {
-		param, err = htmlJsonToMap(rPageDataStr)
+	rPageData := res.String()
+	if res.StatusCode() != 302 {
+		param, err = htmlJsonToMap(rPageData)
 		if err != nil {
-			return
+			return nil, err
 		}
 		param["el"] = "2"
 		time.Sleep(time.Second * 2)
 
 		// 通过验证获取直连
-		var rUrl struct {
-			Url string `json:"url"`
-		}
-		_, err = d.post(fmt.Sprint(baseUrl, "/ajax.php"), func(req *resty.Request) { req.SetContext(ctx).SetFormData(param) }, &rUrl)
+		data, err := d.post(fmt.Sprint(baseUrl, "/ajax.php"), func(req *resty.Request) { req.SetFormData(param) }, nil)
 		if err != nil {
-			return
+			return nil, err
 		}
-		file.Url = rUrl.Url
+		file.Url = utils.Json.Get(data, "url").ToString()
 	}
-	return
+	return &file, nil
 }
 
 // 通过分享链接获取文件夹
+// 似乎子目录和文件不会加密
 // 参考 https://github.com/zaxtyson/LanZouCloud-API/blob/ab2e9ec715d1919bf432210fc16b91c6775fbb99/lanzou/api/core.py#L1089
-func (d *LanZou) getFolderByShareUrl(ctx context.Context, downID, pwd string, firstPageData []byte) ([]FileOrFolderByShareUrl, error) {
-	if firstPageData == nil {
-		var err error
-		firstPageData, err = d.get(fmt.Sprint(d.ShareUrl, "/", downID), func(req *resty.Request) { req.SetContext(ctx) }, nil)
-		if err != nil {
-			return nil, err
-		}
-		firstPageData = RemoveNotes(firstPageData)
-	}
-	firstPageDataStr := string(firstPageData)
-
-	//
-	if strings.Contains(firstPageDataStr, "acw_sc__v2") {
-		vs, err := CalcAcwScV2(firstPageDataStr)
-		if err != nil {
-			return nil, err
-		}
-		firstPageData, err = d.get(fmt.Sprint(d.ShareUrl, "/", downID), func(req *resty.Request) {
-			req.SetCookie(&http.Cookie{
-				Name:  "acw_sc__v2",
-				Value: vs,
-			})
-			req.SetContext(ctx)
-		}, nil)
-		if err != nil {
-			return nil, err
-		}
-		firstPageData = RemoveNotes(firstPageData)
-		firstPageDataStr = string(firstPageData)
+func (d *LanZou) GetFolderByShareUrl(shareID, pwd string) ([]FileOrFolderByShareUrl, error) {
+	pageData, err := d.getShareUrlHtml(shareID)
+	if err != nil {
+		return nil, err
 	}
+	return d.getFolderByShareUrl(pwd, pageData)
+}
 
-	from, err := htmlJsonToMap(firstPageDataStr)
+func (d *LanZou) getFolderByShareUrl(pwd string, sharePageData string) ([]FileOrFolderByShareUrl, error) {
+	from, err := htmlJsonToMap(sharePageData)
 	if err != nil {
 		return nil, err
 	}
-	from["pwd"] = pwd
 
 	files := make([]FileOrFolderByShareUrl, 0)
 	// vip获取文件夹
-	floders := findSubFolaerReg.FindAllStringSubmatch(firstPageDataStr, -1)
+	floders := findSubFolaerReg.FindAllStringSubmatch(sharePageData, -1)
 	for _, floder := range floders {
-		if len(floder) == 5 {
+		if len(floder) == 3 {
 			files = append(files, FileOrFolderByShareUrl{
-				ID:       floder[2],
-				NameAll:  floder[4],
+				// Pwd: pwd, // 子文件夹不加密
+				ID:       floder[1],
+				NameAll:  floder[2],
 				IsFloder: true,
 			})
 		}
 	}
 
+	// 获取文件
+	from["pwd"] = pwd
 	for page := 1; ; page++ {
 		from["pg"] = strconv.Itoa(page)
 		var resp FileOrFolderByShareUrlResp
-		_, err := d.post(d.ShareUrl+"/filemoreajax.php", func(req *resty.Request) { req.SetFormData(from).SetContext(ctx) }, &resp)
+		_, err := d.post(d.ShareUrl+"/filemoreajax.php", func(req *resty.Request) { req.SetFormData(from) }, &resp)
 		if err != nil {
 			return nil, err
 		}
-		files = append(files, resp.Text...)
+		/*// 文件夹中的文件也不加密
+		for i := 0; i < len(resp.Text); i++ {
+			resp.Text[i].Pwd = pwd
+		}*/
 		if len(resp.Text) == 0 {
 			break
 		}
-		time.Sleep(time.Millisecond * 600)
+		files = append(files, resp.Text...)
+		time.Sleep(time.Second)
 	}
 	return files, nil
 }
+
+// 通过下载头获取真实文件信息
+func (d *LanZou) getFileRealInfo(downURL string) (*int64, *time.Time) {
+	res, _ := base.RestyClient.R().Head(downURL)
+	if res == nil {
+		return nil, nil
+	}
+	time, _ := http.ParseTime(res.Header().Get("Last-Modified"))
+	size, _ := strconv.ParseInt(res.Header().Get("Content-Length"), 10, 64)
+	return &size, &time
+}


Test patch: