github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/net/webdav/prop.go (about) 1 // Copyright 2015 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 import ( 8 "fmt" 9 "io" 10 "mime" 11 "net/http" 12 "os" 13 "path/filepath" 14 "strconv" 15 16 "golang.org/x/net/webdav/internal/xml" 17 ) 18 19 // Proppatch describes a property update instruction as defined in RFC 4918. 20 // See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH 21 type Proppatch struct { 22 // Remove specifies whether this patch removes properties. If it does not 23 // remove them, it sets them. 24 Remove bool 25 // Props contains the properties to be set or removed. 26 Props []Property 27 } 28 29 // Propstat describes a XML propstat element as defined in RFC 4918. 30 // See http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat 31 type Propstat struct { 32 // Props contains the properties for which Status applies. 33 Props []Property 34 35 // Status defines the HTTP status code of the properties in Prop. 36 // Allowed values include, but are not limited to the WebDAV status 37 // code extensions for HTTP/1.1. 38 // http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11 39 Status int 40 41 // XMLError contains the XML representation of the optional error element. 42 // XML content within this field must not rely on any predefined 43 // namespace declarations or prefixes. If empty, the XML error element 44 // is omitted. 45 XMLError string 46 47 // ResponseDescription contains the contents of the optional 48 // responsedescription field. If empty, the XML element is omitted. 49 ResponseDescription string 50 } 51 52 // makePropstats returns a slice containing those of x and y whose Props slice 53 // is non-empty. If both are empty, it returns a slice containing an otherwise 54 // zero Propstat whose HTTP status code is 200 OK. 55 func makePropstats(x, y Propstat) []Propstat { 56 pstats := make([]Propstat, 0, 2) 57 if len(x.Props) != 0 { 58 pstats = append(pstats, x) 59 } 60 if len(y.Props) != 0 { 61 pstats = append(pstats, y) 62 } 63 if len(pstats) == 0 { 64 pstats = append(pstats, Propstat{ 65 Status: http.StatusOK, 66 }) 67 } 68 return pstats 69 } 70 71 // DeadPropsHolder holds the dead properties of a resource. 72 // 73 // Dead properties are those properties that are explicitly defined. In 74 // comparison, live properties, such as DAV:getcontentlength, are implicitly 75 // defined by the underlying resource, and cannot be explicitly overridden or 76 // removed. See the Terminology section of 77 // http://www.webdav.org/specs/rfc4918.html#rfc.section.3 78 // 79 // There is a whitelist of the names of live properties. This package handles 80 // all live properties, and will only pass non-whitelisted names to the Patch 81 // method of DeadPropsHolder implementations. 82 type DeadPropsHolder interface { 83 // DeadProps returns a copy of the dead properties held. 84 DeadProps() (map[xml.Name]Property, error) 85 86 // Patch patches the dead properties held. 87 // 88 // Patching is atomic; either all or no patches succeed. It returns (nil, 89 // non-nil) if an internal server error occurred, otherwise the Propstats 90 // collectively contain one Property for each proposed patch Property. If 91 // all patches succeed, Patch returns a slice of length one and a Propstat 92 // element with a 200 OK HTTP status code. If none succeed, for reasons 93 // other than an internal server error, no Propstat has status 200 OK. 94 // 95 // For more details on when various HTTP status codes apply, see 96 // http://www.webdav.org/specs/rfc4918.html#PROPPATCH-status 97 Patch([]Proppatch) ([]Propstat, error) 98 } 99 100 // liveProps contains all supported, protected DAV: properties. 101 var liveProps = map[xml.Name]struct { 102 // findFn implements the propfind function of this property. If nil, 103 // it indicates a hidden property. 104 findFn func(FileSystem, LockSystem, string, os.FileInfo) (string, error) 105 // dir is true if the property applies to directories. 106 dir bool 107 }{ 108 xml.Name{Space: "DAV:", Local: "resourcetype"}: { 109 findFn: findResourceType, 110 dir: true, 111 }, 112 xml.Name{Space: "DAV:", Local: "displayname"}: { 113 findFn: findDisplayName, 114 dir: true, 115 }, 116 xml.Name{Space: "DAV:", Local: "getcontentlength"}: { 117 findFn: findContentLength, 118 dir: false, 119 }, 120 xml.Name{Space: "DAV:", Local: "getlastmodified"}: { 121 findFn: findLastModified, 122 dir: false, 123 }, 124 xml.Name{Space: "DAV:", Local: "creationdate"}: { 125 findFn: nil, 126 dir: false, 127 }, 128 xml.Name{Space: "DAV:", Local: "getcontentlanguage"}: { 129 findFn: nil, 130 dir: false, 131 }, 132 xml.Name{Space: "DAV:", Local: "getcontenttype"}: { 133 findFn: findContentType, 134 dir: false, 135 }, 136 xml.Name{Space: "DAV:", Local: "getetag"}: { 137 findFn: findETag, 138 // findETag implements ETag as the concatenated hex values of a file's 139 // modification time and size. This is not a reliable synchronization 140 // mechanism for directories, so we do not advertise getetag for DAV 141 // collections. 142 dir: false, 143 }, 144 145 // TODO: The lockdiscovery property requires LockSystem to list the 146 // active locks on a resource. 147 xml.Name{Space: "DAV:", Local: "lockdiscovery"}: {}, 148 xml.Name{Space: "DAV:", Local: "supportedlock"}: { 149 findFn: findSupportedLock, 150 dir: true, 151 }, 152 } 153 154 // TODO(nigeltao) merge props and allprop? 155 156 // Props returns the status of the properties named pnames for resource name. 157 // 158 // Each Propstat has a unique status and each property name will only be part 159 // of one Propstat element. 160 func props(fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Propstat, error) { 161 f, err := fs.OpenFile(name, os.O_RDONLY, 0) 162 if err != nil { 163 return nil, err 164 } 165 defer f.Close() 166 fi, err := f.Stat() 167 if err != nil { 168 return nil, err 169 } 170 isDir := fi.IsDir() 171 172 var deadProps map[xml.Name]Property 173 if dph, ok := f.(DeadPropsHolder); ok { 174 deadProps, err = dph.DeadProps() 175 if err != nil { 176 return nil, err 177 } 178 } 179 180 pstatOK := Propstat{Status: http.StatusOK} 181 pstatNotFound := Propstat{Status: http.StatusNotFound} 182 for _, pn := range pnames { 183 // If this file has dead properties, check if they contain pn. 184 if dp, ok := deadProps[pn]; ok { 185 pstatOK.Props = append(pstatOK.Props, dp) 186 continue 187 } 188 // Otherwise, it must either be a live property or we don't know it. 189 if prop := liveProps[pn]; prop.findFn != nil && (prop.dir || !isDir) { 190 innerXML, err := prop.findFn(fs, ls, name, fi) 191 if err != nil { 192 return nil, err 193 } 194 pstatOK.Props = append(pstatOK.Props, Property{ 195 XMLName: pn, 196 InnerXML: []byte(innerXML), 197 }) 198 } else { 199 pstatNotFound.Props = append(pstatNotFound.Props, Property{ 200 XMLName: pn, 201 }) 202 } 203 } 204 return makePropstats(pstatOK, pstatNotFound), nil 205 } 206 207 // Propnames returns the property names defined for resource name. 208 func propnames(fs FileSystem, ls LockSystem, name string) ([]xml.Name, error) { 209 f, err := fs.OpenFile(name, os.O_RDONLY, 0) 210 if err != nil { 211 return nil, err 212 } 213 defer f.Close() 214 fi, err := f.Stat() 215 if err != nil { 216 return nil, err 217 } 218 isDir := fi.IsDir() 219 220 var deadProps map[xml.Name]Property 221 if dph, ok := f.(DeadPropsHolder); ok { 222 deadProps, err = dph.DeadProps() 223 if err != nil { 224 return nil, err 225 } 226 } 227 228 pnames := make([]xml.Name, 0, len(liveProps)+len(deadProps)) 229 for pn, prop := range liveProps { 230 if prop.findFn != nil && (prop.dir || !isDir) { 231 pnames = append(pnames, pn) 232 } 233 } 234 for pn := range deadProps { 235 pnames = append(pnames, pn) 236 } 237 return pnames, nil 238 } 239 240 // Allprop returns the properties defined for resource name and the properties 241 // named in include. 242 // 243 // Note that RFC 4918 defines 'allprop' to return the DAV: properties defined 244 // within the RFC plus dead properties. Other live properties should only be 245 // returned if they are named in 'include'. 246 // 247 // See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND 248 func allprop(fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]Propstat, error) { 249 pnames, err := propnames(fs, ls, name) 250 if err != nil { 251 return nil, err 252 } 253 // Add names from include if they are not already covered in pnames. 254 nameset := make(map[xml.Name]bool) 255 for _, pn := range pnames { 256 nameset[pn] = true 257 } 258 for _, pn := range include { 259 if !nameset[pn] { 260 pnames = append(pnames, pn) 261 } 262 } 263 return props(fs, ls, name, pnames) 264 } 265 266 // Patch patches the properties of resource name. The return values are 267 // constrained in the same manner as DeadPropsHolder.Patch. 268 func patch(fs FileSystem, ls LockSystem, name string, patches []Proppatch) ([]Propstat, error) { 269 conflict := false 270 loop: 271 for _, patch := range patches { 272 for _, p := range patch.Props { 273 if _, ok := liveProps[p.XMLName]; ok { 274 conflict = true 275 break loop 276 } 277 } 278 } 279 if conflict { 280 pstatForbidden := Propstat{ 281 Status: http.StatusForbidden, 282 XMLError: `<D:cannot-modify-protected-property xmlns:D="DAV:"/>`, 283 } 284 pstatFailedDep := Propstat{ 285 Status: StatusFailedDependency, 286 } 287 for _, patch := range patches { 288 for _, p := range patch.Props { 289 if _, ok := liveProps[p.XMLName]; ok { 290 pstatForbidden.Props = append(pstatForbidden.Props, Property{XMLName: p.XMLName}) 291 } else { 292 pstatFailedDep.Props = append(pstatFailedDep.Props, Property{XMLName: p.XMLName}) 293 } 294 } 295 } 296 return makePropstats(pstatForbidden, pstatFailedDep), nil 297 } 298 299 f, err := fs.OpenFile(name, os.O_RDWR, 0) 300 if err != nil { 301 return nil, err 302 } 303 defer f.Close() 304 if dph, ok := f.(DeadPropsHolder); ok { 305 ret, err := dph.Patch(patches) 306 if err != nil { 307 return nil, err 308 } 309 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat says that 310 // "The contents of the prop XML element must only list the names of 311 // properties to which the result in the status element applies." 312 for _, pstat := range ret { 313 for i, p := range pstat.Props { 314 pstat.Props[i] = Property{XMLName: p.XMLName} 315 } 316 } 317 return ret, nil 318 } 319 // The file doesn't implement the optional DeadPropsHolder interface, so 320 // all patches are forbidden. 321 pstat := Propstat{Status: http.StatusForbidden} 322 for _, patch := range patches { 323 for _, p := range patch.Props { 324 pstat.Props = append(pstat.Props, Property{XMLName: p.XMLName}) 325 } 326 } 327 return []Propstat{pstat}, nil 328 } 329 330 func findResourceType(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) { 331 if fi.IsDir() { 332 return `<D:collection xmlns:D="DAV:"/>`, nil 333 } 334 return "", nil 335 } 336 337 func findDisplayName(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) { 338 if slashClean(name) == "/" { 339 // Hide the real name of a possibly prefixed root directory. 340 return "", nil 341 } 342 return fi.Name(), nil 343 } 344 345 func findContentLength(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) { 346 return strconv.FormatInt(fi.Size(), 10), nil 347 } 348 349 func findLastModified(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) { 350 return fi.ModTime().Format(http.TimeFormat), nil 351 } 352 353 func findContentType(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) { 354 f, err := fs.OpenFile(name, os.O_RDONLY, 0) 355 if err != nil { 356 return "", err 357 } 358 defer f.Close() 359 // This implementation is based on serveContent's code in the standard net/http package. 360 ctype := mime.TypeByExtension(filepath.Ext(name)) 361 if ctype != "" { 362 return ctype, nil 363 } 364 // Read a chunk to decide between utf-8 text and binary. 365 var buf [512]byte 366 n, err := io.ReadFull(f, buf[:]) 367 if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF { 368 return "", err 369 } 370 ctype = http.DetectContentType(buf[:n]) 371 // Rewind file. 372 _, err = f.Seek(0, os.SEEK_SET) 373 return ctype, err 374 } 375 376 func findETag(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) { 377 // The Apache http 2.4 web server by default concatenates the 378 // modification time and size of a file. We replicate the heuristic 379 // with nanosecond granularity. 380 return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size()), nil 381 } 382 383 func findSupportedLock(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) { 384 return `` + 385 `<D:lockentry xmlns:D="DAV:">` + 386 `<D:lockscope><D:exclusive/></D:lockscope>` + 387 `<D:locktype><D:write/></D:locktype>` + 388 `</D:lockentry>`, nil 389 }