]>
Commit | Line | Data |
---|---|---|
a5880a6d JP |
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 | |
5 | update | |
6 | ||
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 | |
12 | disabled by default. | |
13 | --- | |
14 | backend/webdav/webdav.go | 63 ++++++++++++++++++++++++++++++++++++++-- | |
15 | 1 file changed, 61 insertions(+), 2 deletions(-) | |
16 | ||
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"' | |
22 | `, | |
23 | Default: fs.CommaSepList{}, | |
24 | Advanced: true, | |
25 | + }, { | |
26 | + Name: "update_modtime", | |
27 | + Help: "Adjust modification time on servers which allow DAV:getlastmodified property update", | |
28 | + Default: false, | |
29 | + Advanced: true, | |
30 | }}, | |
31 | }) | |
32 | } | |
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"` | |
38 | } | |
39 | ||
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 | |
42 | return nil, err | |
43 | } | |
44 | ||
45 | + var precision time.Duration | |
46 | + if opt.UpdateModTime { | |
47 | + precision = time.Second | |
48 | + } else { | |
49 | + precision = fs.ModTimeNotSupported | |
50 | + } | |
51 | + | |
52 | f := &Fs{ | |
53 | name: name, | |
54 | root: root, | |
55 | @@ -412,7 +425,7 @@ func NewFs(ctx context.Context, name, root string, m configmap.Mapper) (fs.Fs, e | |
56 | endpoint: u, | |
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, | |
61 | } | |
62 | ||
63 | client := fshttp.NewClient(ctx) | |
64 | @@ -634,6 +647,16 @@ var owncloudProps = []byte(`<?xml version="1.0"?> | |
65 | </d:prop> | |
66 | </d:propfind> | |
67 | `) | |
68 | +var modtimeUpdatePropStart = `<?xml version="1.0"?> | |
69 | +<d:propertyupdate xmlns:d="DAV:"> | |
70 | + <d:set> | |
71 | + <d:prop> | |
72 | + <d:getlastmodified>` | |
73 | +var modtimeUpdatePropEnd = `</d:getlastmodified> | |
74 | + </d:prop> | |
75 | + </d:set> | |
76 | +</d:propertyupdate> | |
77 | +` | |
78 | ||
79 | // list the objects into the function supplied | |
80 | // | |
81 | @@ -1251,7 +1274,37 @@ func (o *Object) ModTime(ctx context.Context) time.Time { | |
82 | ||
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 | |
88 | + } | |
89 | + opts := rest.Opts{ | |
90 | + Method: "PROPPATCH", | |
91 | + Path: o.filePath(), | |
92 | + } | |
93 | + var body bytes.Buffer | |
94 | + body.WriteString(modtimeUpdatePropStart) | |
95 | + body.WriteString(modTime.Format(time.RFC1123)) | |
96 | + body.WriteString(modtimeUpdatePropEnd) | |
97 | + opts.Body = &body | |
98 | + | |
99 | + var result api.Multistatus | |
100 | + var resp *http.Response | |
101 | + var err error | |
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) | |
105 | + }) | |
106 | + if err != nil { | |
107 | + return err | |
108 | + } | |
109 | + if len(result.Responses) < 1 { | |
110 | + return fs.ErrorObjectNotFound | |
111 | + } | |
112 | + item := result.Responses[0] | |
113 | + if !item.Props.StatusOK() { | |
114 | + return fs.ErrorObjectNotFound | |
115 | + } | |
116 | + return nil | |
117 | } | |
118 | ||
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 | |
121 | _ = o.Remove(ctx) | |
122 | return err | |
123 | } | |
124 | + if !o.fs.useOCMtime && o.fs.opt.UpdateModTime { | |
125 | + err = o.SetModTime(ctx, src.ModTime(ctx)) | |
126 | + if err != nil { | |
127 | + return fmt.Errorf("Update ModTime failed: %w", err) | |
128 | + } | |
129 | + } | |
130 | // read metadata from remote | |
131 | o.hasMetaData = false | |
132 | return o.readMetaData(ctx) | |
133 | -- | |
134 | 2.37.0 | |
135 | ||
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 | |
140 | ||
141 | --- | |
142 | backend/webdav/webdav.go | 17 ++++++++++------- | |
143 | 1 file changed, 10 insertions(+), 7 deletions(-) | |
144 | ||
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{}, | |
151 | Advanced: true, | |
152 | }, { | |
153 | - Name: "update_modtime", | |
154 | - Help: "Adjust modification time on servers which allow DAV:getlastmodified property update", | |
155 | - Default: false, | |
156 | + Name: "update_modtime", | |
157 | + Help: `Adjust modification time on servers which allow DAV:getlastmodified property update. | |
158 | + | |
159 | +Use provider's default if unset. | |
160 | +`, | |
161 | + Default: fs.Tristate{}, | |
162 | Advanced: true, | |
163 | }}, | |
164 | }) | |
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"` | |
171 | } | |
172 | ||
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 | |
175 | } | |
176 | ||
177 | var precision time.Duration | |
178 | - if opt.UpdateModTime { | |
179 | + if opt.UpdateModTime.Valid && opt.UpdateModTime.Value { | |
180 | precision = time.Second | |
181 | } else { | |
182 | precision = fs.ModTimeNotSupported | |
183 | @@ -1274,7 +1277,7 @@ func (o *Object) ModTime(ctx context.Context) time.Time { | |
184 | ||
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 | |
190 | } | |
191 | opts := rest.Opts{ | |
192 | @@ -1390,7 +1393,7 @@ func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, op | |
193 | _ = o.Remove(ctx) | |
194 | return err | |
195 | } | |
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)) | |
199 | if err != nil { | |
200 | return fmt.Errorf("Update ModTime failed: %w", err) | |
201 | -- | |
202 | 2.37.0 | |
203 | ||
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 | |
208 | ||
209 | enables by default modtime update with PROPPATCH | |
210 | --- | |
211 | backend/webdav/webdav.go | 9 +++++++++ | |
212 | 1 file changed, 9 insertions(+) | |
213 | ||
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() { | |
219 | }, { | |
220 | Value: "sharepoint-ntlm", | |
221 | Help: "Sharepoint with NTLM authentication, usually self-hosted or on-premises", | |
222 | + }, { | |
223 | + Value: "fastmail", | |
224 | + Help: "Fastmail", | |
225 | }, { | |
226 | Value: "other", | |
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 | |
232 | + case "fastmail": | |
233 | + if !f.opt.UpdateModTime.Valid { | |
234 | + f.precision = time.Second | |
235 | + f.opt.UpdateModTime.Valid = true | |
236 | + f.opt.UpdateModTime.Value = true | |
237 | + } | |
238 | case "other": | |
239 | default: | |
240 | fs.Debugf(f, "Unknown vendor %q", vendor) | |
241 | -- | |
242 | 2.37.0 | |
243 |