github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/net/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 "time" 16 17 http "github.com/hxx258456/ccgo/gmhttp" 18 19 // As of https://go-review.googlesource.com/#/c/12772/ which was submitted 20 // in July 2015, this package uses an internal fork of the standard 21 // library's encoding/xml package, due to changes in the way namespaces 22 // were encoded. Such changes were introduced in the Go 1.5 cycle, but were 23 // rolled back in response to https://github.com/golang/go/issues/11841 24 // 25 // However, this package's exported API, specifically the Property and 26 // DeadPropsHolder types, need to refer to the standard library's version 27 // of the xml.Name type, as code that imports this package cannot refer to 28 // the internal version. 29 // 30 // This file therefore imports both the internal and external versions, as 31 // ixml and xml, and converts between them. 32 // 33 // In the long term, this package should use the standard library's version 34 // only, and the internal fork deleted, once 35 // https://github.com/golang/go/issues/13400 is resolved. 36 ixml "github.com/hxx258456/ccgo/net/webdav/internal/xml" 37 ) 38 39 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_lockinfo 40 type lockInfo struct { 41 XMLName ixml.Name `xml:"lockinfo"` 42 Exclusive *struct{} `xml:"lockscope>exclusive"` 43 Shared *struct{} `xml:"lockscope>shared"` 44 Write *struct{} `xml:"locktype>write"` 45 Owner owner `xml:"owner"` 46 } 47 48 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_owner 49 type owner struct { 50 InnerXML string `xml:",innerxml"` 51 } 52 53 func readLockInfo(r io.Reader) (li lockInfo, status int, err error) { 54 c := &countingReader{r: r} 55 if err = ixml.NewDecoder(c).Decode(&li); err != nil { 56 if err == io.EOF { 57 if c.n == 0 { 58 // An empty body means to refresh the lock. 59 // http://www.webdav.org/specs/rfc4918.html#refreshing-locks 60 return lockInfo{}, 0, nil 61 } 62 err = errInvalidLockInfo 63 } 64 return lockInfo{}, http.StatusBadRequest, err 65 } 66 // We only support exclusive (non-shared) write locks. In practice, these are 67 // the only types of locks that seem to matter. 68 if li.Exclusive == nil || li.Shared != nil || li.Write == nil { 69 return lockInfo{}, http.StatusNotImplemented, errUnsupportedLockInfo 70 } 71 return li, 0, nil 72 } 73 74 type countingReader struct { 75 n int 76 r io.Reader 77 } 78 79 func (c *countingReader) Read(p []byte) (int, error) { 80 n, err := c.r.Read(p) 81 c.n += n 82 return n, err 83 } 84 85 func writeLockInfo(w io.Writer, token string, ld LockDetails) (int, error) { 86 depth := "infinity" 87 if ld.ZeroDepth { 88 depth = "0" 89 } 90 timeout := ld.Duration / time.Second 91 return fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+ 92 "<D:prop xmlns:D=\"DAV:\"><D:lockdiscovery><D:activelock>\n"+ 93 " <D:locktype><D:write/></D:locktype>\n"+ 94 " <D:lockscope><D:exclusive/></D:lockscope>\n"+ 95 " <D:depth>%s</D:depth>\n"+ 96 " <D:owner>%s</D:owner>\n"+ 97 " <D:timeout>Second-%d</D:timeout>\n"+ 98 " <D:locktoken><D:href>%s</D:href></D:locktoken>\n"+ 99 " <D:lockroot><D:href>%s</D:href></D:lockroot>\n"+ 100 "</D:activelock></D:lockdiscovery></D:prop>", 101 depth, ld.OwnerXML, timeout, escape(token), escape(ld.Root), 102 ) 103 } 104 105 func escape(s string) string { 106 for i := 0; i < len(s); i++ { 107 switch s[i] { 108 case '"', '&', '\'', '<', '>': 109 b := bytes.NewBuffer(nil) 110 ixml.EscapeText(b, []byte(s)) 111 return b.String() 112 } 113 } 114 return s 115 } 116 117 // Next returns the next token, if any, in the XML stream of d. 118 // RFC 4918 requires to ignore comments, processing instructions 119 // and directives. 120 // http://www.webdav.org/specs/rfc4918.html#property_values 121 // http://www.webdav.org/specs/rfc4918.html#xml-extensibility 122 func next(d *ixml.Decoder) (ixml.Token, error) { 123 for { 124 t, err := d.Token() 125 if err != nil { 126 return t, err 127 } 128 switch t.(type) { 129 case ixml.Comment, ixml.Directive, ixml.ProcInst: 130 continue 131 default: 132 return t, nil 133 } 134 } 135 } 136 137 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for propfind) 138 type propfindProps []xml.Name 139 140 // UnmarshalXML appends the property names enclosed within start to pn. 141 // 142 // It returns an error if start does not contain any properties or if 143 // properties contain values. Character data between properties is ignored. 144 func (pn *propfindProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error { 145 for { 146 t, err := next(d) 147 if err != nil { 148 return err 149 } 150 switch t.(type) { 151 case ixml.EndElement: 152 if len(*pn) == 0 { 153 return fmt.Errorf("%s must not be empty", start.Name.Local) 154 } 155 return nil 156 case ixml.StartElement: 157 name := t.(ixml.StartElement).Name 158 t, err = next(d) 159 if err != nil { 160 return err 161 } 162 if _, ok := t.(ixml.EndElement); !ok { 163 return fmt.Errorf("unexpected token %T", t) 164 } 165 *pn = append(*pn, xml.Name(name)) 166 } 167 } 168 } 169 170 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propfind 171 type propfind struct { 172 XMLName ixml.Name `xml:"DAV: propfind"` 173 Allprop *struct{} `xml:"DAV: allprop"` 174 Propname *struct{} `xml:"DAV: propname"` 175 Prop propfindProps `xml:"DAV: prop"` 176 Include propfindProps `xml:"DAV: include"` 177 } 178 179 func readPropfind(r io.Reader) (pf propfind, status int, err error) { 180 c := countingReader{r: r} 181 if err = ixml.NewDecoder(&c).Decode(&pf); err != nil { 182 if err == io.EOF { 183 if c.n == 0 { 184 // An empty body means to propfind allprop. 185 // http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND 186 return propfind{Allprop: new(struct{})}, 0, nil 187 } 188 err = errInvalidPropfind 189 } 190 return propfind{}, http.StatusBadRequest, err 191 } 192 193 if pf.Allprop == nil && pf.Include != nil { 194 return propfind{}, http.StatusBadRequest, errInvalidPropfind 195 } 196 if pf.Allprop != nil && (pf.Prop != nil || pf.Propname != nil) { 197 return propfind{}, http.StatusBadRequest, errInvalidPropfind 198 } 199 if pf.Prop != nil && pf.Propname != nil { 200 return propfind{}, http.StatusBadRequest, errInvalidPropfind 201 } 202 if pf.Propname == nil && pf.Allprop == nil && pf.Prop == nil { 203 return propfind{}, http.StatusBadRequest, errInvalidPropfind 204 } 205 return pf, 0, nil 206 } 207 208 // Property represents a single DAV resource property as defined in RFC 4918. 209 // See http://www.webdav.org/specs/rfc4918.html#data.model.for.resource.properties 210 type Property struct { 211 // XMLName is the fully qualified name that identifies this property. 212 XMLName xml.Name 213 214 // Lang is an optional xml:lang attribute. 215 Lang string `xml:"xml:lang,attr,omitempty"` 216 217 // InnerXML contains the XML representation of the property value. 218 // See http://www.webdav.org/specs/rfc4918.html#property_values 219 // 220 // Property values of complex type or mixed-content must have fully 221 // expanded XML namespaces or be self-contained with according 222 // XML namespace declarations. They must not rely on any XML 223 // namespace declarations within the scope of the XML document, 224 // even including the DAV: namespace. 225 InnerXML []byte `xml:",innerxml"` 226 } 227 228 // ixmlProperty is the same as the Property type except it holds an ixml.Name 229 // instead of an xml.Name. 230 type ixmlProperty struct { 231 XMLName ixml.Name 232 Lang string `xml:"xml:lang,attr,omitempty"` 233 InnerXML []byte `xml:",innerxml"` 234 } 235 236 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_error 237 // See multistatusWriter for the "D:" namespace prefix. 238 type xmlError struct { 239 XMLName ixml.Name `xml:"D:error"` 240 InnerXML []byte `xml:",innerxml"` 241 } 242 243 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat 244 // See multistatusWriter for the "D:" namespace prefix. 245 type propstat struct { 246 Prop []Property `xml:"D:prop>_ignored_"` 247 Status string `xml:"D:status"` 248 Error *xmlError `xml:"D:error"` 249 ResponseDescription string `xml:"D:responsedescription,omitempty"` 250 } 251 252 // ixmlPropstat is the same as the propstat type except it holds an ixml.Name 253 // instead of an xml.Name. 254 type ixmlPropstat struct { 255 Prop []ixmlProperty `xml:"D:prop>_ignored_"` 256 Status string `xml:"D:status"` 257 Error *xmlError `xml:"D:error"` 258 ResponseDescription string `xml:"D:responsedescription,omitempty"` 259 } 260 261 // MarshalXML prepends the "D:" namespace prefix on properties in the DAV: namespace 262 // before encoding. See multistatusWriter. 263 func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error { 264 // Convert from a propstat to an ixmlPropstat. 265 ixmlPs := ixmlPropstat{ 266 Prop: make([]ixmlProperty, len(ps.Prop)), 267 Status: ps.Status, 268 Error: ps.Error, 269 ResponseDescription: ps.ResponseDescription, 270 } 271 for k, prop := range ps.Prop { 272 ixmlPs.Prop[k] = ixmlProperty{ 273 XMLName: ixml.Name(prop.XMLName), 274 Lang: prop.Lang, 275 InnerXML: prop.InnerXML, 276 } 277 } 278 279 for k, prop := range ixmlPs.Prop { 280 if prop.XMLName.Space == "DAV:" { 281 prop.XMLName = ixml.Name{Space: "", Local: "D:" + prop.XMLName.Local} 282 ixmlPs.Prop[k] = prop 283 } 284 } 285 // Distinct type to avoid infinite recursion of MarshalXML. 286 type newpropstat ixmlPropstat 287 return e.EncodeElement(newpropstat(ixmlPs), start) 288 } 289 290 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_response 291 // See multistatusWriter for the "D:" namespace prefix. 292 type response struct { 293 XMLName ixml.Name `xml:"D:response"` 294 Href []string `xml:"D:href"` 295 Propstat []propstat `xml:"D:propstat"` 296 Status string `xml:"D:status,omitempty"` 297 Error *xmlError `xml:"D:error"` 298 ResponseDescription string `xml:"D:responsedescription,omitempty"` 299 } 300 301 // MultistatusWriter marshals one or more Responses into a XML 302 // multistatus response. 303 // See http://www.webdav.org/specs/rfc4918.html#ELEMENT_multistatus 304 // TODO(rsto, mpl): As a workaround, the "D:" namespace prefix, defined as 305 // "DAV:" on this element, is prepended on the nested response, as well as on all 306 // its nested elements. All property names in the DAV: namespace are prefixed as 307 // well. This is because some versions of Mini-Redirector (on windows 7) ignore 308 // elements with a default namespace (no prefixed namespace). A less intrusive fix 309 // should be possible after golang.org/cl/11074. See https://golang.org/issue/11177 310 type multistatusWriter struct { 311 // ResponseDescription contains the optional responsedescription 312 // of the multistatus XML element. Only the latest content before 313 // close will be emitted. Empty response descriptions are not 314 // written. 315 responseDescription string 316 317 w http.ResponseWriter 318 enc *ixml.Encoder 319 } 320 321 // Write validates and emits a DAV response as part of a multistatus response 322 // element. 323 // 324 // It sets the HTTP status code of its underlying http.ResponseWriter to 207 325 // (Multi-Status) and populates the Content-Type header. If r is the 326 // first, valid response to be written, Write prepends the XML representation 327 // of r with a multistatus tag. Callers must call close after the last response 328 // has been written. 329 func (w *multistatusWriter) write(r *response) error { 330 switch len(r.Href) { 331 case 0: 332 return errInvalidResponse 333 case 1: 334 if len(r.Propstat) > 0 != (r.Status == "") { 335 return errInvalidResponse 336 } 337 default: 338 if len(r.Propstat) > 0 || r.Status == "" { 339 return errInvalidResponse 340 } 341 } 342 err := w.writeHeader() 343 if err != nil { 344 return err 345 } 346 return w.enc.Encode(r) 347 } 348 349 // writeHeader writes a XML multistatus start element on w's underlying 350 // http.ResponseWriter and returns the result of the write operation. 351 // After the first write attempt, writeHeader becomes a no-op. 352 func (w *multistatusWriter) writeHeader() error { 353 if w.enc != nil { 354 return nil 355 } 356 w.w.Header().Add("Content-Type", "text/xml; charset=utf-8") 357 w.w.WriteHeader(StatusMulti) 358 _, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`) 359 if err != nil { 360 return err 361 } 362 w.enc = ixml.NewEncoder(w.w) 363 return w.enc.EncodeToken(ixml.StartElement{ 364 Name: ixml.Name{ 365 Space: "DAV:", 366 Local: "multistatus", 367 }, 368 Attr: []ixml.Attr{{ 369 Name: ixml.Name{Space: "xmlns", Local: "D"}, 370 Value: "DAV:", 371 }}, 372 }) 373 } 374 375 // Close completes the marshalling of the multistatus response. It returns 376 // an error if the multistatus response could not be completed. If both the 377 // return value and field enc of w are nil, then no multistatus response has 378 // been written. 379 func (w *multistatusWriter) close() error { 380 if w.enc == nil { 381 return nil 382 } 383 var end []ixml.Token 384 if w.responseDescription != "" { 385 name := ixml.Name{Space: "DAV:", Local: "responsedescription"} 386 end = append(end, 387 ixml.StartElement{Name: name}, 388 ixml.CharData(w.responseDescription), 389 ixml.EndElement{Name: name}, 390 ) 391 } 392 end = append(end, ixml.EndElement{ 393 Name: ixml.Name{Space: "DAV:", Local: "multistatus"}, 394 }) 395 for _, t := range end { 396 err := w.enc.EncodeToken(t) 397 if err != nil { 398 return err 399 } 400 } 401 return w.enc.Flush() 402 } 403 404 var xmlLangName = ixml.Name{Space: "http://www.w3.org/XML/1998/namespace", Local: "lang"} 405 406 func xmlLang(s ixml.StartElement, d string) string { 407 for _, attr := range s.Attr { 408 if attr.Name == xmlLangName { 409 return attr.Value 410 } 411 } 412 return d 413 } 414 415 type xmlValue []byte 416 417 func (v *xmlValue) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error { 418 // The XML value of a property can be arbitrary, mixed-content XML. 419 // To make sure that the unmarshalled value contains all required 420 // namespaces, we encode all the property value XML tokens into a 421 // buffer. This forces the encoder to redeclare any used namespaces. 422 var b bytes.Buffer 423 e := ixml.NewEncoder(&b) 424 for { 425 t, err := next(d) 426 if err != nil { 427 return err 428 } 429 if e, ok := t.(ixml.EndElement); ok && e.Name == start.Name { 430 break 431 } 432 if err = e.EncodeToken(t); err != nil { 433 return err 434 } 435 } 436 err := e.Flush() 437 if err != nil { 438 return err 439 } 440 *v = b.Bytes() 441 return nil 442 } 443 444 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_prop (for proppatch) 445 type proppatchProps []Property 446 447 // UnmarshalXML appends the property names and values enclosed within start 448 // to ps. 449 // 450 // An xml:lang attribute that is defined either on the DAV:prop or property 451 // name XML element is propagated to the property's Lang field. 452 // 453 // UnmarshalXML returns an error if start does not contain any properties or if 454 // property values contain syntactically incorrect XML. 455 func (ps *proppatchProps) UnmarshalXML(d *ixml.Decoder, start ixml.StartElement) error { 456 lang := xmlLang(start, "") 457 for { 458 t, err := next(d) 459 if err != nil { 460 return err 461 } 462 switch elem := t.(type) { 463 case ixml.EndElement: 464 if len(*ps) == 0 { 465 return fmt.Errorf("%s must not be empty", start.Name.Local) 466 } 467 return nil 468 case ixml.StartElement: 469 p := Property{ 470 XMLName: xml.Name(t.(ixml.StartElement).Name), 471 Lang: xmlLang(t.(ixml.StartElement), lang), 472 } 473 err = d.DecodeElement(((*xmlValue)(&p.InnerXML)), &elem) 474 if err != nil { 475 return err 476 } 477 *ps = append(*ps, p) 478 } 479 } 480 } 481 482 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_set 483 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_remove 484 type setRemove struct { 485 XMLName ixml.Name 486 Lang string `xml:"xml:lang,attr,omitempty"` 487 Prop proppatchProps `xml:"DAV: prop"` 488 } 489 490 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propertyupdate 491 type propertyupdate struct { 492 XMLName ixml.Name `xml:"DAV: propertyupdate"` 493 Lang string `xml:"xml:lang,attr,omitempty"` 494 SetRemove []setRemove `xml:",any"` 495 } 496 497 func readProppatch(r io.Reader) (patches []Proppatch, status int, err error) { 498 var pu propertyupdate 499 if err = ixml.NewDecoder(r).Decode(&pu); err != nil { 500 return nil, http.StatusBadRequest, err 501 } 502 for _, op := range pu.SetRemove { 503 remove := false 504 switch op.XMLName { 505 case ixml.Name{Space: "DAV:", Local: "set"}: 506 // No-op. 507 case ixml.Name{Space: "DAV:", Local: "remove"}: 508 for _, p := range op.Prop { 509 if len(p.InnerXML) > 0 { 510 return nil, http.StatusBadRequest, errInvalidProppatch 511 } 512 } 513 remove = true 514 default: 515 return nil, http.StatusBadRequest, errInvalidProppatch 516 } 517 patches = append(patches, Proppatch{Remove: remove, Props: op.Prop}) 518 } 519 return patches, 0, nil 520 }