1
2
3
4
5 package webdav
6
7
8
9
10 import (
11 "bytes"
12 "encoding/xml"
13 "fmt"
14 "io"
15 "net/http"
16 "time"
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 ixml "golang.org/x/net/webdav/internal/xml"
36 )
37
38
39 type lockInfo struct {
40 XMLName ixml.Name `xml:"lockinfo"`
41 Exclusive *struct{} `xml:"lockscope>exclusive"`
42 Shared *struct{} `xml:"lockscope>shared"`
43 Write *struct{} `xml:"locktype>write"`
44 Owner owner `xml:"owner"`
45 }
46
47
48 type owner struct {
49 InnerXML string `xml:",innerxml"`
50 }
51
52 func readLockInfo(r io.Reader) (li lockInfo, status int, err error) {
53 c := &countingReader{r: r}
54 if err = ixml.NewDecoder(c).Decode(&li); err != nil {
55 if err == io.EOF {
56 if c.n == 0 {
57
58
59 return lockInfo{}, 0, nil
60 }
61 err = errInvalidLockInfo
62 }
63 return lockInfo{}, http.StatusBadRequest, err
64 }
65
66
67 if li.Exclusive == nil || li.Shared != nil || li.Write == nil {
68 return lockInfo{}, http.StatusNotImplemented, errUnsupportedLockInfo
69 }
70 return li, 0, nil
71 }
72
73 type countingReader struct {
74 n int
75 r io.Reader
76 }
77
78 func (c *countingReader) Read(p []byte) (int, error) {
79 n, err := c.r.Read(p)
80 c.n += n
81 return n, err
82 }
83
84 func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) {
85 depth := "infinity"
86 if ld.ZeroDepth {
87 depth = "0"
88 }
89 timeout := ld.Duration / time.Second
90 return fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
91 "<D:prop xmlns:D=\"DAV:\"><D:lockdiscovery><D:activelock>\n"+
92 " <D:locktype><D:write/></D:locktype>\n"+
93 " <D:lockscope><D:exclusive/></D:lockscope>\n"+
94 " <D:depth>%s</D:depth>\n"+
95 " <D:owner>%s</D:owner>\n"+
96 " <D:timeout>Second-%d</D:timeout>\n"+
97 " <D:locktoken><D:href>%s</D:href></D:locktoken>\n"+
98 " <D:lockroot><D:href>%s</D:href></D:lockroot>\n"+
99 "</D:activelock></D:lockdiscovery></D:prop>",
100 depth, ld.OwnerXML, timeout, escape(token), escape(ld.Root),
101 )
102 }
103
104 func escape(s string) string {
105 for i := 0; i < len(s); i++ {
106 switch s[i] {
107 case '"', '&', '\'', '<', '>':
108 b := bytes.NewBuffer(nil)
109 ixml.EscapeText(b, []byte(s))
110 return b.String()
111 }
112 }
113 return s
114 }
115
116
117
118
119
120
121 func next(d *ixml.Decoder) (ixml.Token, error) {
122 for {
123 t, err := d.Token()
124 if err != nil {
125 return t, err
126 }
127 switch t.(type) {
128 case ixml.Comment, ixml.Directive, ixml.ProcInst:
129 continue
130 default:
131 return t, nil
132 }
133 }
134 }
135
136
137 type propfindProps []xml.Name
138
139
140
141
142
143 func (pn *propfindProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
144 for {
145 t, err := next(d)
146 if err != nil {
147 return err
148 }
149 switch t.(type) {
150 case ixml.EndElement:
151 if len(*pn) == 0 {
152 return fmt.Errorf("%s must not be empty", start.Name.Local)
153 }
154 return nil
155 case ixml.StartElement:
156 name := t.(ixml.StartElement).Name
157 t, err = next(d)
158 if err != nil {
159 return err
160 }
161 if _, ok := t.(ixml.EndElement); !ok {
162 return fmt.Errorf("unexpected token %T", t)
163 }
164 *pn = append(*pn, xml.Name(name))
165 }
166 }
167 }
168
169
170 type propfind struct {
171 XMLName ixml.Name `xml:"DAV: propfind"`
172 Allprop *struct{} `xml:"DAV: allprop"`
173 Propname *struct{} `xml:"DAV: propname"`
174 Prop propfindProps `xml:"DAV: prop"`
175 Include propfindProps `xml:"DAV: include"`
176 }
177
178 func readPropfind(r io.Reader) (pf propfind, status int, err error) {
179 c := countingReader{r: r}
180 if err = ixml.NewDecoder(&c).Decode(&pf); err != nil {
181 if err == io.EOF {
182 if c.n == 0 {
183
184
185 return propfind{Allprop: new(struct{})}, 0, nil
186 }
187 err = errInvalidPropfind
188 }
189 return propfind{}, http.StatusBadRequest, err
190 }
191
192 if pf.Allprop == nil && pf.Include != nil {
193 return propfind{}, http.StatusBadRequest, errInvalidPropfind
194 }
195 if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) {
196 return propfind{}, http.StatusBadRequest, errInvalidPropfind
197 }
198 if pf.Prop != nil && pf.Propname != nil {
199 return propfind{}, http.StatusBadRequest, errInvalidPropfind
200 }
201 if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil {
202 return propfind{}, http.StatusBadRequest, errInvalidPropfind
203 }
204 return pf, 0, nil
205 }
206
207
208
209 type Property struct {
210
211 XMLName xml.Name
212
213
214 Lang string `xml:"xml:lang,attr,omitempty"`
215
216
217
218
219
220
221
222
223
224 InnerXML []byte `xml:",innerxml"`
225 }
226
227
228
229 type ixmlProperty struct {
230 XMLName ixml.Name
231 Lang string `xml:"xml:lang,attr,omitempty"`
232 InnerXML []byte `xml:",innerxml"`
233 }
234
235
236
237 type xmlError struct {
238 XMLName ixml.Name `xml:"D:error"`
239 InnerXML []byte `xml:",innerxml"`
240 }
241
242
243
244 type propstat struct {
245 Prop []Property `xml:"D:prop>_ignored_"`
246 Status string `xml:"D:status"`
247 Error *xmlError `xml:"D:error"`
248 ResponseDescription string `xml:"D:responsedescription,omitempty"`
249 }
250
251
252
253 type ixmlPropstat struct {
254 Prop []ixmlProperty `xml:"D:prop>_ignored_"`
255 Status string `xml:"D:status"`
256 Error *xmlError `xml:"D:error"`
257 ResponseDescription string `xml:"D:responsedescription,omitempty"`
258 }
259
260
261
262 func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error {
263
264 ixmlPs := ixmlPropstat{
265 Prop: make([]ixmlProperty, len(ps.Prop)),
266 Status: ps.Status,
267 Error: ps.Error,
268 ResponseDescription: ps.ResponseDescription,
269 }
270 for k, prop := range ps.Prop {
271 ixmlPs.Prop[k] = ixmlProperty{
272 XMLName: ixml.Name(prop.XMLName),
273 Lang: prop.Lang,
274 InnerXML: prop.InnerXML,
275 }
276 }
277
278 for k, prop := range ixmlPs.Prop {
279 if prop.XMLName.Space == "DAV:" {
280 prop.XMLName = ixml.Name{Space: "", Local: "D:" + prop.XMLName.Local}
281 ixmlPs.Prop[k] = prop
282 }
283 }
284
285 type newpropstat ixmlPropstat
286 return e.EncodeElement(newpropstat(ixmlPs), start)
287 }
288
289
290
291 type response struct {
292 XMLName ixml.Name `xml:"D:response"`
293 Href []string `xml:"D:href"`
294 Propstat []propstat `xml:"D:propstat"`
295 Status string `xml:"D:status,omitempty"`
296 Error *xmlError `xml:"D:error"`
297 ResponseDescription string `xml:"D:responsedescription,omitempty"`
298 }
299
300
301
302
303
304
305
306
307
308
309 type multistatusWriter struct {
310
311
312
313
314 responseDescription string
315
316 w http.ResponseWriter
317 enc *ixml.Encoder
318 }
319
320
321
322
323
324
325
326
327
328 func (w *multistatusWriter) write(r *response) error {
329 switch len(r.Href) {
330 case 0:
331 return errInvalidResponse
332 case 1:
333 if len(r.Propstat) > 0 != (r.Status == "") {
334 return errInvalidResponse
335 }
336 default:
337 if len(r.Propstat) > 0 || r.Status == "" {
338 return errInvalidResponse
339 }
340 }
341 err := w.writeHeader()
342 if err != nil {
343 return err
344 }
345 return w.enc.Encode(r)
346 }
347
348
349
350
351 func (w *multistatusWriter) writeHeader() error {
352 if w.enc != nil {
353 return nil
354 }
355 w.w.Header().Add("Content-Type", "text/xml; charset=utf-8")
356 w.w.WriteHeader(StatusMulti)
357 _, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`)
358 if err != nil {
359 return err
360 }
361 w.enc = ixml.NewEncoder(w.w)
362 return w.enc.EncodeToken(ixml.StartElement{
363 Name: ixml.Name{
364 Space: "DAV:",
365 Local: "multistatus",
366 },
367 Attr: []ixml.Attr{{
368 Name: ixml.Name{Space: "xmlns", Local: "D"},
369 Value: "DAV:",
370 }},
371 })
372 }
373
374
375
376
377
378 func (w *multistatusWriter) close() error {
379 if w.enc == nil {
380 return nil
381 }
382 var end []ixml.Token
383 if w.responseDescription != "" {
384 name := ixml.Name{Space: "DAV:", Local: "responsedescription"}
385 end = append(end,
386 ixml.StartElement{Name: name},
387 ixml.CharData(w.responseDescription),
388 ixml.EndElement{Name: name},
389 )
390 }
391 end = append(end, ixml.EndElement{
392 Name: ixml.Name{Space: "DAV:", Local: "multistatus"},
393 })
394 for _, t := range end {
395 err := w.enc.EncodeToken(t)
396 if err != nil {
397 return err
398 }
399 }
400 return w.enc.Flush()
401 }
402
403 var xmlLangName = ixml.Name{Space: "http://www.w3.org/XML/1998/namespace", Local: "lang"}
404
405 func xmlLang(s ixml.StartElement, d string) string {
406 for _, attr := range s.Attr {
407 if attr.Name == xmlLangName {
408 return attr.Value
409 }
410 }
411 return d
412 }
413
414 type xmlValue []byte
415
416 func (v *xmlValue) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
417
418
419
420
421 var b bytes.Buffer
422 e := ixml.NewEncoder(&b)
423 for {
424 t, err := next(d)
425 if err != nil {
426 return err
427 }
428 if e, ok := t.(ixml.EndElement); ok && e.Name == start.Name {
429 break
430 }
431 if err = e.EncodeToken(t); err != nil {
432 return err
433 }
434 }
435 err := e.Flush()
436 if err != nil {
437 return err
438 }
439 *v = b.Bytes()
440 return nil
441 }
442
443
444 type proppatchProps []Property
445
446
447
448
449
450
451
452
453
454 func (ps *proppatchProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error {
455 lang := xmlLang(start, "")
456 for {
457 t, err := next(d)
458 if err != nil {
459 return err
460 }
461 switch elem := t.(type) {
462 case ixml.EndElement:
463 if len(*ps) == 0 {
464 return fmt.Errorf("%s must not be empty", start.Name.Local)
465 }
466 return nil
467 case ixml.StartElement:
468 p := Property{
469 XMLName: xml.Name(t.(ixml.StartElement).Name),
470 Lang: xmlLang(t.(ixml.StartElement), lang),
471 }
472 err = d.DecodeElement(((*xmlValue)(&p.InnerXML)), &elem)
473 if err != nil {
474 return err
475 }
476 *ps = append(*ps, p)
477 }
478 }
479 }
480
481
482
483 type setRemove struct {
484 XMLName ixml.Name
485 Lang string `xml:"xml:lang,attr,omitempty"`
486 Prop proppatchProps `xml:"DAV: prop"`
487 }
488
489
490 type propertyupdate struct {
491 XMLName ixml.Name `xml:"DAV: propertyupdate"`
492 Lang string `xml:"xml:lang,attr,omitempty"`
493 SetRemove []setRemove `xml:",any"`
494 }
495
496 func readProppatch(r io.Reader) (patches []Proppatch, status int, err error) {
497 var pu propertyupdate
498 if err = ixml.NewDecoder(r).Decode(&pu); err != nil {
499 return nil, http.StatusBadRequest, err
500 }
501 for _, op := range pu.SetRemove {
502 remove := false
503 switch op.XMLName {
504 case ixml.Name{Space: "DAV:", Local: "set"}:
505
506 case ixml.Name{Space: "DAV:", Local: "remove"}:
507 for _, p := range op.Prop {
508 if len(p.InnerXML) > 0 {
509 return nil, http.StatusBadRequest, errInvalidProppatch
510 }
511 }
512 remove = true
513 default:
514 return nil, http.StatusBadRequest, errInvalidProppatch
515 }
516 patches = append(patches, Proppatch{Remove: remove, Props: op.Prop})
517 }
518 return patches, 0, nil
519 }
520
View as plain text