-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat(webdav): implement RFC 4331 WebDAV Quotas for server and client(driver) #2040
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -117,6 +117,9 @@ type props struct { | |
| ContentType string `xml:"DAV: prop>getcontenttype,omitempty"` | ||
| ETag string `xml:"DAV: prop>getetag,omitempty"` | ||
| Modified string `xml:"DAV: prop>getlastmodified,omitempty"` | ||
|
|
||
| QuotaAvailableBytes int64 `xml:"DAV: prop>quota-available-bytes,omitempty"` | ||
| QuotaUsedBytes int64 `xml:"DAV: prop>quota-used-bytes,omitempty"` | ||
| } | ||
|
|
||
| type response struct { | ||
|
|
@@ -252,6 +255,39 @@ func (c *Client) Stat(path string) (os.FileInfo, error) { | |
| return f, err | ||
| } | ||
|
|
||
| // Quota returns the quota information about a specified path | ||
| 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 | ||
| } | ||
|
Comment on lines
+259
to
+271
|
||
|
|
||
| err := c.propfind(path, true, | ||
| `<d:propfind xmlns:d='DAV:'> | ||
| <d:prop> | ||
| <d:quota-available-bytes/> | ||
| <d:quota-used-bytes/> | ||
| </d:prop> | ||
| </d:propfind>`, | ||
| &response{}, | ||
| parse) | ||
|
|
||
| if err != nil { | ||
| if _, ok := err.(*os.PathError); !ok { | ||
| err = newPathErrorErr("Quota", path, err) | ||
| } | ||
| } | ||
| return quota, err | ||
| } | ||
|
|
||
| // Remove removes a remote file | ||
| func (c *Client) Remove(path string) error { | ||
| return c.RemoveAll(path) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package gowebdav | ||
|
|
||
| type Quota struct { | ||
| Available int64 | ||
| Used int64 | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -212,6 +212,21 @@ func props(ctx context.Context, ls LockSystem, fi model.Obj, pnames []xml.Name) | |||||||||||||||
| XMLName: pn, | ||||||||||||||||
| InnerXML: []byte(innerXML), | ||||||||||||||||
| }) | ||||||||||||||||
| } else if prop := additionalLiveProps[pn]; prop.findFn != nil { | ||||||||||||||||
| innerXML, show, err := prop.findFn(ctx, ls, fi.GetName(), fi) | ||||||||||||||||
| if err != nil { | ||||||||||||||||
| return nil, err | ||||||||||||||||
| } | ||||||||||||||||
| if !show { | ||||||||||||||||
| pstatNotFound.Props = append(pstatNotFound.Props, Property{ | ||||||||||||||||
| XMLName: pn, | ||||||||||||||||
| }) | ||||||||||||||||
| } else { | ||||||||||||||||
| pstatOK.Props = append(pstatOK.Props, Property{ | ||||||||||||||||
| XMLName: pn, | ||||||||||||||||
| InnerXML: []byte(innerXML), | ||||||||||||||||
| }) | ||||||||||||||||
| } | ||||||||||||||||
| } else { | ||||||||||||||||
| pstatNotFound.Props = append(pstatNotFound.Props, Property{ | ||||||||||||||||
| XMLName: pn, | ||||||||||||||||
|
|
@@ -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. | ||||||||||||||||
|
||||||||||||||||
| // 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. |
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,84 @@ | ||||||||||||
| package webdav | ||||||||||||
|
|
||||||||||||
| import ( | ||||||||||||
| "context" | ||||||||||||
| "encoding/xml" | ||||||||||||
| "strconv" | ||||||||||||
|
|
||||||||||||
| "github.com/OpenListTeam/OpenList/v4/internal/conf" | ||||||||||||
| "github.com/OpenListTeam/OpenList/v4/internal/fs" | ||||||||||||
| "github.com/OpenListTeam/OpenList/v4/internal/model" | ||||||||||||
| "github.com/OpenListTeam/OpenList/v4/internal/op" | ||||||||||||
| ) | ||||||||||||
|
|
||||||||||||
| // additionalLiveProps are live properties that should not be returned for "allprop" PROPFIND requests. | ||||||||||||
| var additionalLiveProps = map[xml.Name]struct { | ||||||||||||
| // 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. | ||||||||||||
|
||||||||||||
| // 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
AI
Feb 2, 2026
There was a problem hiding this comment.
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
AI
Feb 2, 2026
There was a problem hiding this comment.
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.
| available := usage.TotalSpace - usage.UsedSpace | |
| available := usage.TotalSpace - usage.UsedSpace | |
| if available < 0 { | |
| available = 0 | |
| } |
There was a problem hiding this comment.
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.