golang.org/x/net@v0.25.1-0.20240516223405-c87a5b62e243/webdav/xml.go (about) 1 // Copyright 2014 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package webdav 6 7 // The XML encoding is covered by Section 14. 8 // http://www.webdav.org/specs/rfc4918.html#xml.element.definitions 9 10 import ( 11 "bytes" 12 "encoding/xml" 13 "fmt" 14 "io" 15 "net/http" 16 "time" 17 18 // As of https://go-review.googlesource.com/#/c/12772/ which was submitted 19 // in July 2015, this package uses an internal fork of the standard 20 // library's encoding/xml package, due to changes in the way namespaces 21 // were encoded. Such changes were introduced in the Go 1.5 cycle, but were 22 // rolled back in response to https://github.com/golang/go/issues/11841 23 // 24 // However, this package's exported API, specifically the Property and 25 // DeadPropsHolder types, need to refer to the standard library's version 26 // of the xml.Name type, as code that imports this package cannot refer to 27 // the internal version. 28 // 29 // This file therefore imports both the internal and external versions, as 30 // ixml and xml, and converts between them. 31 // 32 // In the long term, this package should use the standard library's version 33 // only, and the internal fork deleted, once 34 // https://github.com/golang/go/issues/13400 is resolved. 35 ixml "golang.org/x/net/webdav/internal/xml" 36 ) 37 38 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo 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 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner 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 // An empty body means to refresh the lock. 58 // http://www.webdav.org/specs/rfc4918.html#refreshing-locks 59 return lockInfo{}, 0, nil 60 } 61 err = errInvalidLockInfo 62 } 63 return lockInfo{}, http.StatusBadRequest, err 64 } 65 // We only support exclusive (non-shared) write locks. In practice, these are 66 // the only types of locks that seem to matter. 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 // next returns the next token, if any, in the XML stream of d. 117 // RFC 4918 requires to ignore comments, processing instructions 118 // and directives. 119 // http://www.webdav.org/specs/rfc4918.html#property_values 120 // http://www.webdav.org/specs/rfc4918.html#xml-extensibility 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 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind) 137 type propfindProps []xml.Name 138 139 // UnmarshalXML appends the property names enclosed within start to pn. 140 // 141 // It returns an error if start does not contain any properties or if 142 // properties contain values. Character data between properties is ignored. 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 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind 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 // An empty body means to propfind allprop. 184 // http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND 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 // Property represents a single DAV resource property as defined in RFC 4918. 208 // See http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties 209 type Property struct { 210 // XMLName is the fully qualified name that identifies this property. 211 XMLName xml.Name 212 213 // Lang is an optional xml:lang attribute. 214 Lang string `xml:"xml:lang,attr,omitempty"` 215 216 // InnerXML contains the XML representation of the property value. 217 // See http://www.webdav.org/specs/rfc4918.html#property_values 218 // 219 // Property values of complex type or mixed-content must have fully 220 // expanded XML namespaces or be self-contained with according 221 // XML namespace declarations. They must not rely on any XML 222 // namespace declarations within the scope of the XML document, 223 // even including the DAV: namespace. 224 InnerXML []byte `xml:",innerxml"` 225 } 226 227 // ixmlProperty is the same as the Property type except it holds an ixml.Name 228 // instead of an xml.Name. 229 type ixmlProperty struct { 230 XMLName ixml.Name 231 Lang string `xml:"xml:lang,attr,omitempty"` 232 InnerXML []byte `xml:",innerxml"` 233 } 234 235 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_error 236 // See multistatusWriter for the "D:" namespace prefix. 237 type xmlError struct { 238 XMLName ixml.Name `xml:"D:error"` 239 InnerXML []byte `xml:",innerxml"` 240 } 241 242 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat 243 // See multistatusWriter for the "D:" namespace prefix. 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 // ixmlPropstat is the same as the propstat type except it holds an ixml.Name 252 // instead of an xml.Name. 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 // MarshalXML prepends the "D:" namespace prefix on properties in the DAV: namespace 261 // before encoding. See multistatusWriter. 262 func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error { 263 // Convert from a propstat to an ixmlPropstat. 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 // Distinct type to avoid infinite recursion of MarshalXML. 285 type newpropstat ixmlPropstat 286 return e.EncodeElement(newpropstat(ixmlPs), start) 287 } 288 289 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_response 290 // See multistatusWriter for the "D:" namespace prefix. 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 // MultistatusWriter marshals one or more Responses into a XML 301 // multistatus response. 302 // See http://www.webdav.org/specs/rfc4918.html#ELEMENT_multistatus 303 // TODO(rsto, mpl): As a workaround, the "D:" namespace prefix, defined as 304 // "DAV:" on this element, is prepended on the nested response, as well as on all 305 // its nested elements. All property names in the DAV: namespace are prefixed as 306 // well. This is because some versions of Mini-Redirector (on windows 7) ignore 307 // elements with a default namespace (no prefixed namespace). A less intrusive fix 308 // should be possible after golang.org/cl/11074. See https://golang.org/issue/11177 309 type multistatusWriter struct { 310 // ResponseDescription contains the optional responsedescription 311 // of the multistatus XML element. Only the latest content before 312 // close will be emitted. Empty response descriptions are not 313 // written. 314 responseDescription string 315 316 w http.ResponseWriter 317 enc *ixml.Encoder 318 } 319 320 // Write validates and emits a DAV response as part of a multistatus response 321 // element. 322 // 323 // It sets the HTTP status code of its underlying http.ResponseWriter to 207 324 // (Multi-Status) and populates the Content-Type header. If r is the 325 // first, valid response to be written, Write prepends the XML representation 326 // of r with a multistatus tag. Callers must call close after the last response 327 // has been written. 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 // writeHeader writes a XML multistatus start element on w's underlying 349 // http.ResponseWriter and returns the result of the write operation. 350 // After the first write attempt, writeHeader becomes a no-op. 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 // Close completes the marshalling of the multistatus response. It returns 375 // an error if the multistatus response could not be completed. If both the 376 // return value and field enc of w are nil, then no multistatus response has 377 // been written. 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 // The XML value of a property can be arbitrary, mixed-content XML. 418 // To make sure that the unmarshalled value contains all required 419 // namespaces, we encode all the property value XML tokens into a 420 // buffer. This forces the encoder to redeclare any used namespaces. 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 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for proppatch) 444 type proppatchProps []Property 445 446 // UnmarshalXML appends the property names and values enclosed within start 447 // to ps. 448 // 449 // An xml:lang attribute that is defined either on the DAV:prop or property 450 // name XML element is propagated to the property's Lang field. 451 // 452 // UnmarshalXML returns an error if start does not contain any properties or if 453 // property values contain syntactically incorrect XML. 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 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_set 482 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_remove 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 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propertyupdate 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 // No-op. 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 }