Skip to content

feat(webdav): implement RFC 4331 WebDAV Quotas for server and client(driver)#2040

Open
hcrgm wants to merge 1 commit intoOpenListTeam:mainfrom
hcrgm:feat/webdav-quota
Open

feat(webdav): implement RFC 4331 WebDAV Quotas for server and client(driver)#2040
hcrgm wants to merge 1 commit intoOpenListTeam:mainfrom
hcrgm:feat/webdav-quota

Conversation

@hcrgm
Copy link
Contributor

@hcrgm hcrgm commented Jan 28, 2026

Description / 描述

为 WebDAV 服务器和客户端(驱动)实现 RFC 4331,从而展示和对外提供空间用量信息。

Motivation and Context / 背景

How Has This Been Tested? / 测试

使用支持 WebDAV Quota 的客户端(如 Rclone)和服务器测试。

Checklist / 检查清单

  • I have read the CONTRIBUTING document.
    我已阅读 CONTRIBUTING 文档。
  • I have formatted my code with go fmt or prettier.
    我已使用 go fmtprettier 格式化提交的代码。
  • I have added appropriate labels to this PR (or mentioned needed labels in the description if lacking permissions).
    我已为此 PR 添加了适当的标签(如无权限或需要的标签不存在,请在描述中说明,管理员将后续处理)。
  • I have requested review from relevant code authors using the "Request review" feature when applicable.
    我已在适当情况下使用"Request review"功能请求相关代码作者进行审查。
  • I have updated the repository accordingly (If it’s needed).
    我已相应更新了相关仓库(若适用)。

@xrgzs xrgzs added enhancement Module: Driver Driver-Related Issue/PR Module: Server API and protocol changes labels Jan 28, 2026
@xrgzs xrgzs requested a review from Copilot February 2, 2026 11:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements RFC 4331 WebDAV Quotas support for both the server and client (driver) components of OpenList. The implementation adds quota-available-bytes and quota-used-bytes live properties to WebDAV PROPFIND responses on the server side, and provides a Quota method in the gowebdav client package to retrieve this information. The driver for WebDAV storage now implements the GetDetails interface to expose storage quota information.

Changes:

  • Added RFC 4331 WebDAV quota properties (quota-available-bytes and quota-used-bytes) to the server-side WebDAV implementation
  • Implemented client-side quota retrieval in the gowebdav package with a new Quota method
  • Extended the WebDAV driver to implement the GetDetails interface for exposing storage quota information

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
server/webdav/prop_additional.go New file implementing RFC 4331 quota properties as additional live properties that are excluded from allprop responses
server/webdav/prop.go Modified propnames function signature to support filtering additional live properties for allprop requests, and integrated quota property handling in props function
server/webdav/webdav.go Updated propnames function call to pass the fromAllprop parameter
pkg/gowebdav/quota.go New file defining the Quota struct for client-side quota representation
pkg/gowebdav/client.go Added Quota method to retrieve quota information and extended props struct with quota fields
drivers/webdav/driver.go Implemented GetDetails method for the WebDAV driver to expose storage quota information

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +259 to +271
func (c *Client) Quota(path string) (*Quota, error) {
var quota *Quota
parse := func(resp interface{}) error {
r := resp.(*response)
if p := getProps(r, "200"); p != nil && quota == nil {
quota = new(Quota)
quota.Available = p.QuotaAvailableBytes
quota.Used = p.QuotaUsedBytes
}

r.Props = nil
return nil
}
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Quota method doesn't validate that both properties are present in the response before returning. If the server only returns one of the two quota properties (quota-available-bytes or quota-used-bytes), the zero value (0) will be used for the missing field, which could be misleading. Consider checking if both properties were actually returned by the server and return nil if essential quota information is missing or incomplete.

Copilot uses AI. Check for mistakes.
if usage == nil {
return "", false, nil
}
available := usage.TotalSpace - usage.UsedSpace
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Available space calculation can produce negative values if UsedSpace exceeds TotalSpace. While this may represent a valid over-quota situation, RFC 4331 states that quota-available-bytes should be the number of bytes available, which cannot be negative. Consider checking if the result is negative and returning 0 or an appropriate value in such cases to conform with the RFC specification.

Suggested change
available := usage.TotalSpace - usage.UsedSpace
available := usage.TotalSpace - usage.UsedSpace
if available < 0 {
available = 0
}

Copilot uses AI. Check for mistakes.
@@ -222,7 +237,7 @@ func props(ctx context.Context, ls LockSystem, fi model.Obj, pnames []xml.Name)
}

// Propnames returns the property names defined for resource name.
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The propnames function signature was changed to include a fromAllprop parameter, but the function documentation comment was not updated to explain this new parameter's purpose. Consider adding documentation to explain that the fromAllprop parameter controls whether additionalLiveProps should be included in the returned property names list, following RFC 4331 section 3 which specifies that quota properties should not be returned for allprop PROPFIND requests.

Suggested change
// Propnames returns the property names defined for resource name.
// Propnames returns the property names defined for the given resource.
// The fromAllprop parameter indicates whether this call is serving an
// "allprop" PROPFIND request. When fromAllprop is true, additionalLiveProps
// (such as quota-related properties) are omitted from the result, in
// accordance with RFC 4331 section 3, which specifies that quota properties
// are not returned for allprop requests.

Copilot uses AI. Check for mistakes.
if quota == nil {
return nil, errs.NotImplement
}
total := quota.Available + quota.Used
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential arithmetic overflow when calculating total space. If quota.Available and quota.Used are both close to the maximum value of int64, their sum could overflow. According to RFC 4331, quota-available-bytes represents the available bytes remaining, not necessarily part of a fixed total. Consider checking if the server provides an explicit total space value, and handle cases where Available + Used might not accurately represent total capacity.

Copilot uses AI. Check for mistakes.
// findFn implements the propfind function of this property. If nil,
// it indicates a hidden property.
findFn func(context.Context, LockSystem, string, model.Obj) (string, bool, error)
// showFn indicates whether the prop show be returned for the resource.
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spelling error in the comment. Change "show be" to "should be" for grammatical correctness.

Suggested change
// showFn indicates whether the prop show be returned for the resource.
// it indicates a hidden property.
// showFn indicates whether the prop should be returned for the resource.

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +47
func getStorageUsage(ctx context.Context, fi model.Obj) (*model.StorageDetails, error) {
user := ctx.Value(conf.UserKey).(*model.User)
reqPath, err := user.JoinPath(fi.GetPath())
if err != nil {
return nil, err
}
storage, err := fs.GetStorage(reqPath, &fs.GetStoragesArgs{})
if err != nil {
return nil, err
}
details, err := op.GetStorageDetails(ctx, storage)
if err != nil {
return nil, err
}
return details, nil
}
Copy link

Copilot AI Feb 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling should be improved. When GetStorageDetails returns an error (especially errs.NotImplement or errs.StorageNotInit), the function propagates it as-is. According to RFC 4331, if quota information is unavailable, the properties should be omitted from the response rather than causing the entire PROPFIND to fail. Consider checking for NotImplement and StorageNotInit errors and returning (nil, nil) in those cases to gracefully handle storages that don't support quota reporting.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Module: Driver Driver-Related Issue/PR Module: Server API and protocol changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants