Repository: AlistGo/alist (language: go)

Issue:
alist的WebDAV服务协议中能否添加alist的hashinfo，使用rclone就可以增加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 this feature is not implemented.
- [ ] I'm sure it's a reasonable and popular requirement.

### Description of the feature / 需求描述

alist的api中存在hashinfo，但WebDAV传递不了例如md5的内容，导致下载后无法进行校验，能否在原有WebDAV协议基础上进行增强，添加alist的相关属性例如hashinfo，这样就可以直接使用checksum检查文件了

### Suggested solution / 实现思路

实现思路参考[https://github.com/rclone/rclone/issues/2379]，可以实现alist的自定义的标签，也可以做兼容，相关讨论[https://central.owncloud.org/t/reading-hashes-md5-or-sha1-via-webdav/14348/3]，以下方案参考[https://github.com/rclone/rclone/issues/3147]。
为了兼容性可以这样处理，普通属性查询请求例如：
```
<?xml version="1.0" ?>
<D:propfind xmlns:D="DAV:">
 <D:allprop/>
</D:propfind>
```
不返回自定义属性，使用标准的WebDAV协议，需要指定自定义属性才返回自定义属性
```
<?xml version="1.0"?>
<d:propfind  xmlns:d="DAV:" xmlns:al="http://alist.nn.ci/ns">
 <d:prop>
  <d:displayname />
  <d:getlastmodified />
  <d:getcontentlength />
  <d:resourcetype />
  <d:getcontenttype />
  <al:checksums />
 </d:prop>
</d:propfind>
```
或者
```
<?xml version="1.0"?>
<d:propfind  xmlns:d="DAV:" xmlns:al="http://alist.nn.ci/ns">
 <d:prop>
  <d:displayname />
  <d:getlastmodified />
  <d:getcontentlength />
  <d:resourcetype />
  <d:getcontenttype />
  <al:hashinfo />
 </d:prop>
</d:propfind>
```
返回内容增加
`<al:checksums><al:checksum>SHA1:f572d396fae9206628714fb2ce00f72e94f2258f MD5:b1946ac92492d2347c6235b4d2611184 ADLER32:084b021f</al:checksum></al:checksums>`
或者
`<al:hashinfo><al:md5>b1946ac92492d2347c6235b4d2611184</al:md5></al:hashinfo>`
可以有效增强校验，减少传输错误导致的文件错误

### Additional context / 附件

_No response_


Discussion / hints:

Fix patch:
diff --git a/pkg/utils/hash.go b/pkg/utils/hash.go
index fa06bcc24c2..a281dd4e6e5 100644
--- a/pkg/utils/hash.go
+++ b/pkg/utils/hash.go
@@ -10,6 +10,7 @@ import (
 	"errors"
 	"hash"
 	"io"
+	"iter"
 
 	"github.com/alist-org/alist/v3/internal/errs"
 	log "github.com/sirupsen/logrus"
@@ -226,3 +227,13 @@ func (hi HashInfo) GetHash(ht *HashType) string {
 func (hi HashInfo) Export() map[*HashType]string {
 	return hi.h
 }
+
+func (hi HashInfo) All() iter.Seq2[*HashType, string] {
+	return func(yield func(*HashType, string) bool) {
+		for hashType, hashValue := range hi.h {
+			if !yield(hashType, hashValue) {
+				return
+			}
+		}
+	}
+}
diff --git a/server/webdav/prop.go b/server/webdav/prop.go
index 5e053af4b1a..a81f31b05c7 100644
--- a/server/webdav/prop.go
+++ b/server/webdav/prop.go
@@ -9,6 +9,7 @@ import (
 	"context"
 	"encoding/xml"
 	"errors"
+	"fmt"
 	"mime"
 	"net/http"
 	"path"
@@ -101,7 +102,7 @@ type DeadPropsHolder interface {
 	Patch([]Proppatch) ([]Propstat, error)
 }
 
-// liveProps contains all supported, protected DAV: properties.
+// liveProps contains all supported properties.
 var liveProps = map[xml.Name]struct {
 	// findFn implements the propfind function of this property. If nil,
 	// it indicates a hidden property.
@@ -160,6 +161,10 @@ var liveProps = map[xml.Name]struct {
 		findFn: findSupportedLock,
 		dir:    true,
 	},
+	{Space: "http://owncloud.org/ns", Local: "checksums"}: {
+		findFn: findChecksums,
+		dir:    false,
+	},
 }
 
 // TODO(nigeltao) merge props and allprop?
@@ -483,3 +488,11 @@ func findSupportedLock(ctx context.Context, ls LockSystem, name string, fi model
 		`<D:locktype><D:write/></D:locktype>` +
 		`</D:lockentry>`, nil
 }
+
+func findChecksums(ctx context.Context, ls LockSystem, name string, fi model.Obj) (string, error) {
+	checksums := ""
+	for hashType, hashValue := range fi.GetHash().All() {
+		checksums += fmt.Sprintf("<checksum>%s:%s</checksum>", hashType.Name, hashValue)
+	}
+	return checksums, nil
+}


Test patch: