1 From b9c0e1efdb88ac08ae6053eaeb2f3034a3416e51 Mon Sep 17 00:00:00 2001
2 From: Jan Palus <jpalus@fastmail.com>
3 Date: Sat, 16 Apr 2022 23:06:53 +0200
4 Subject: [PATCH 1/3] webdav: add support for spec compliant modification time
7 rfc4918 section 15.7 states that getlastmodified SHOULD be protected
8 (read-only) but does not mandate it. implementations are free to choose
9 whether it is possible to update modification time. one such
10 implementation operating in the wild that allows it is FastMail WebDav
11 access. since specification recommends property to be protected make it
14 backend/webdav/webdav.go | 63 ++++++++++++++++++++++++++++++++++++++--
15 1 file changed, 61 insertions(+), 2 deletions(-)
17 diff --git a/backend/webdav/webdav.go b/backend/webdav/webdav.go
18 index 9564ae601..20f2b45ea 100644
19 --- a/backend/webdav/webdav.go
20 +++ b/backend/webdav/webdav.go
21 @@ -124,6 +124,11 @@ You can set multiple headers, e.g. '"Cookie","name=value","Authorization","xxx"'
23 Default: fs.CommaSepList{},
26 + Name: "update_modtime",
27 + Help: "Adjust modification time on servers which allow DAV:getlastmodified property update",
33 @@ -138,6 +143,7 @@ type Options struct {
34 BearerTokenCommand string `config:"bearer_token_command"`
35 Enc encoder.MultiEncoder `config:"encoding"`
36 Headers fs.CommaSepList `config:"headers"`
37 + UpdateModTime bool `config:"update_modtime"`
40 // Fs represents a remote webdav
41 @@ -405,6 +411,13 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
45 + var precision time.Duration
46 + if opt.UpdateModTime {
47 + precision = time.Second
49 + precision = fs.ModTimeNotSupported
55 @@ -412,7 +425,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
57 endpointURL: u.String(),
58 pacer: fs.NewPacer(ctx, pacer.NewDefault(pacer.MinSleep(minSleep), pacer.MaxSleep(maxSleep), pacer.DecayConstant(decayConstant))),
59 - precision: fs.ModTimeNotSupported,
60 + precision: precision,
63 client := fshttp.NewClient(ctx)
64 @@ -634,6 +647,16 @@ var owncloudProps = []byte(`<?xml version="1.0"?>
68 +var modtimeUpdatePropStart = `<?xml version="1.0"?>
69 +<d:propertyupdate xmlns:d="DAV:">
72 + <d:getlastmodified>`
73 +var modtimeUpdatePropEnd = `</d:getlastmodified>
79 // list the objects into the function supplied
81 @@ -1251,7 +1274,37 @@ func (o *Object) ModTime(ctx context.Context) time.Time {
83 // SetModTime sets the modification time of the local fs object
84 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
85 - return fs.ErrorCantSetModTime
86 + if !o.fs.opt.UpdateModTime {
87 + return fs.ErrorCantSetModTime
90 + Method: "PROPPATCH",
93 + var body bytes.Buffer
94 + body.WriteString(modtimeUpdatePropStart)
95 + body.WriteString(modTime.Format(time.RFC1123))
96 + body.WriteString(modtimeUpdatePropEnd)
99 + var result api.Multistatus
100 + var resp *http.Response
102 + err = o.fs.pacer.Call(func() (bool, error) {
103 + resp, err = o.fs.srv.CallXML(ctx, &opts, nil, &result)
104 + return o.fs.shouldRetry(ctx, resp, err)
109 + if len(result.Responses) < 1 {
110 + return fs.ErrorObjectNotFound
112 + item := result.Responses[0]
113 + if !item.Props.StatusOK() {
114 + return fs.ErrorObjectNotFound
119 // Storable returns a boolean showing whether this object storable
120 @@ -1337,6 +1390,12 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
124 + if !o.fs.useOCMtime && o.fs.opt.UpdateModTime {
125 + err = o.SetModTime(ctx, src.ModTime(ctx))
127 + return fmt.Errorf("Update ModTime failed: %w", err)
130 // read metadata from remote
131 o.hasMetaData = false
132 return o.readMetaData(ctx)
136 From 9394b57866019df139fc0ce96a7a3572fad57666 Mon Sep 17 00:00:00 2001
137 From: Jan Palus <jpalus@fastmail.com>
138 Date: Wed, 27 Apr 2022 10:17:51 +0200
139 Subject: [PATCH 2/3] webdav: switch to tristate for update_modtime
142 backend/webdav/webdav.go | 17 ++++++++++-------
143 1 file changed, 10 insertions(+), 7 deletions(-)
145 diff --git a/backend/webdav/webdav.go b/backend/webdav/webdav.go
146 index 20f2b45ea..d4c331de8 100644
147 --- a/backend/webdav/webdav.go
148 +++ b/backend/webdav/webdav.go
149 @@ -125,9 +125,12 @@ You can set multiple headers, e.g. '"Cookie","name=value","Authorization","xxx"'
150 Default: fs.CommaSepList{},
153 - Name: "update_modtime",
154 - Help: "Adjust modification time on servers which allow DAV:getlastmodified property update",
156 + Name: "update_modtime",
157 + Help: `Adjust modification time on servers which allow DAV:getlastmodified property update.
159 +Use provider's default if unset.
161 + Default: fs.Tristate{},
165 @@ -143,7 +146,7 @@ type Options struct {
166 BearerTokenCommand string `config:"bearer_token_command"`
167 Enc encoder.MultiEncoder `config:"encoding"`
168 Headers fs.CommaSepList `config:"headers"`
169 - UpdateModTime bool `config:"update_modtime"`
170 + UpdateModTime fs.Tristate `config:"update_modtime"`
173 // Fs represents a remote webdav
174 @@ -412,7 +415,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e
177 var precision time.Duration
178 - if opt.UpdateModTime {
179 + if opt.UpdateModTime.Valid && opt.UpdateModTime.Value {
180 precision = time.Second
182 precision = fs.ModTimeNotSupported
183 @@ -1274,7 +1277,7 @@ func (o *Object) ModTime(ctx context.Context) time.Time {
185 // SetModTime sets the modification time of the local fs object
186 func (o *Object) SetModTime(ctx context.Context, modTime time.Time) error {
187 - if !o.fs.opt.UpdateModTime {
188 + if !o.fs.opt.UpdateModTime.Valid || !o.fs.opt.UpdateModTime.Value {
189 return fs.ErrorCantSetModTime
192 @@ -1390,7 +1393,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op
196 - if !o.fs.useOCMtime && o.fs.opt.UpdateModTime {
197 + if !o.fs.useOCMtime && o.fs.opt.UpdateModTime.Valid && o.fs.opt.UpdateModTime.Value {
198 err = o.SetModTime(ctx, src.ModTime(ctx))
200 return fmt.Errorf("Update ModTime failed: %w", err)
204 From 6775d10cc030e2e4b78ca774087bbb494cb0f79b Mon Sep 17 00:00:00 2001
205 From: Jan Palus <jpalus@fastmail.com>
206 Date: Thu, 28 Apr 2022 22:20:40 +0200
207 Subject: [PATCH 3/3] webdav: add fastmail provider
209 enables by default modtime update with PROPPATCH
211 backend/webdav/webdav.go | 9 +++++++++
212 1 file changed, 9 insertions(+)
214 diff --git a/backend/webdav/webdav.go b/backend/webdav/webdav.go
215 index d4c331de8..50152503e 100644
216 --- a/backend/webdav/webdav.go
217 +++ b/backend/webdav/webdav.go
218 @@ -87,6 +87,9 @@ func init() {
220 Value: "sharepoint-ntlm",
221 Help: "Sharepoint with NTLM authentication, usually self-hosted or on-premises",
227 Help: "Other site/service or software",
228 @@ -596,6 +599,12 @@ func (f *Fs) setQuirks(ctx context.Context, vendor string) error {
229 // so we must perform an extra check to detect this
230 // condition and return a proper error code.
231 f.checkBeforePurge = true
233 + if !f.opt.UpdateModTime.Valid {
234 + f.precision = time.Second
235 + f.opt.UpdateModTime.Valid = true
236 + f.opt.UpdateModTime.Value = true
240 fs.Debugf(f, "Unknown vendor %q", vendor)