Remove some useless Codes.
This commit is contained in:
parent
ae8829dbc8
commit
0879eb2f95
486
rest/dav/file.go
486
rest/dav/file.go
@ -6,15 +6,10 @@ package dav
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SlashClean is equivalent to but slightly more efficient than
|
// SlashClean is equivalent to but slightly more efficient than
|
||||||
@ -53,484 +48,3 @@ type File interface {
|
|||||||
http.File
|
http.File
|
||||||
io.Writer
|
io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Dir implements FileSystem using the native file system restricted to a
|
|
||||||
// specific directory tree.
|
|
||||||
//
|
|
||||||
// While the FileSystem.OpenFile method takes '/'-separated paths, a Dir's
|
|
||||||
// string value is a filename on the native file system, not a URL, so it is
|
|
||||||
// separated by filepath.Separator, which isn't necessarily '/'.
|
|
||||||
//
|
|
||||||
// An empty Dir is treated as ".".
|
|
||||||
type Dir string
|
|
||||||
|
|
||||||
func (d Dir) resolve(name string) string {
|
|
||||||
// This implementation is based on Dir.Open's code in the standard net/http package.
|
|
||||||
if filepath.Separator != '/' && strings.IndexRune(name, filepath.Separator) >= 0 ||
|
|
||||||
strings.Contains(name, "\x00") {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
dir := string(d)
|
|
||||||
if dir == "" {
|
|
||||||
dir = "."
|
|
||||||
}
|
|
||||||
return filepath.Join(dir, filepath.FromSlash(SlashClean(name)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Dir) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
|
||||||
if name = d.resolve(name); name == "" {
|
|
||||||
return os.ErrNotExist
|
|
||||||
}
|
|
||||||
return os.Mkdir(name, perm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Dir) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
|
|
||||||
if name = d.resolve(name); name == "" {
|
|
||||||
return nil, os.ErrNotExist
|
|
||||||
}
|
|
||||||
f, err := os.OpenFile(name, flag, perm)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Dir) RemoveAll(ctx context.Context, name string) error {
|
|
||||||
if name = d.resolve(name); name == "" {
|
|
||||||
return os.ErrNotExist
|
|
||||||
}
|
|
||||||
if name == filepath.Clean(string(d)) {
|
|
||||||
// Prohibit removing the virtual root directory.
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
return os.RemoveAll(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Dir) Rename(ctx context.Context, oldName, newName string) error {
|
|
||||||
if oldName = d.resolve(oldName); oldName == "" {
|
|
||||||
return os.ErrNotExist
|
|
||||||
}
|
|
||||||
if newName = d.resolve(newName); newName == "" {
|
|
||||||
return os.ErrNotExist
|
|
||||||
}
|
|
||||||
if root := filepath.Clean(string(d)); root == oldName || root == newName {
|
|
||||||
// Prohibit renaming from or to the virtual root directory.
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
return os.Rename(oldName, newName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d Dir) Stat(ctx context.Context, name string) (os.FileInfo, error) {
|
|
||||||
if name = d.resolve(name); name == "" {
|
|
||||||
return nil, os.ErrNotExist
|
|
||||||
}
|
|
||||||
return os.Stat(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A memFS implements FileSystem, storing all metadata and actual file data
|
|
||||||
// in-memory. No limits on filesystem size are used, so it is not recommended
|
|
||||||
// this be used where the clients are untrusted.
|
|
||||||
//
|
|
||||||
// Concurrent access is permitted. The tree structure is protected by a mutex,
|
|
||||||
// and each node's contents and metadata are protected by a per-node mutex.
|
|
||||||
//
|
|
||||||
// TODO: Enforce file permissions.
|
|
||||||
type memFS struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
root memFSNode
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: clean up and rationalize the walk/find code.
|
|
||||||
|
|
||||||
// walk walks the directory tree for the fullname, calling f at each step. If f
|
|
||||||
// returns an error, the walk will be aborted and return that same error.
|
|
||||||
//
|
|
||||||
// dir is the directory at that step, frag is the name fragment, and final is
|
|
||||||
// whether it is the final step. For example, walking "/foo/bar/x" will result
|
|
||||||
// in 3 calls to f:
|
|
||||||
// - "/", "foo", false
|
|
||||||
// - "/foo/", "bar", false
|
|
||||||
// - "/foo/bar/", "x", true
|
|
||||||
// The frag argument will be empty only if dir is the root node and the walk
|
|
||||||
// ends at that root node.
|
|
||||||
func (fs *memFS) walk(op, fullname string, f func(dir *memFSNode, frag string, final bool) error) error {
|
|
||||||
original := fullname
|
|
||||||
fullname = SlashClean(fullname)
|
|
||||||
|
|
||||||
// Strip any leading "/"s to make fullname a relative path, as the walk
|
|
||||||
// starts at fs.root.
|
|
||||||
if fullname[0] == '/' {
|
|
||||||
fullname = fullname[1:]
|
|
||||||
}
|
|
||||||
dir := &fs.root
|
|
||||||
|
|
||||||
for {
|
|
||||||
frag, remaining := fullname, ""
|
|
||||||
i := strings.IndexRune(fullname, '/')
|
|
||||||
final := i < 0
|
|
||||||
if !final {
|
|
||||||
frag, remaining = fullname[:i], fullname[i+1:]
|
|
||||||
}
|
|
||||||
if frag == "" && dir != &fs.root {
|
|
||||||
panic("webdav: empty path fragment for a clean path")
|
|
||||||
}
|
|
||||||
if err := f(dir, frag, final); err != nil {
|
|
||||||
return &os.PathError{
|
|
||||||
Op: op,
|
|
||||||
Path: original,
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if final {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
child := dir.children[frag]
|
|
||||||
if child == nil {
|
|
||||||
return &os.PathError{
|
|
||||||
Op: op,
|
|
||||||
Path: original,
|
|
||||||
Err: os.ErrNotExist,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !child.mode.IsDir() {
|
|
||||||
return &os.PathError{
|
|
||||||
Op: op,
|
|
||||||
Path: original,
|
|
||||||
Err: os.ErrInvalid,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dir, fullname = child, remaining
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// find returns the parent of the named node and the relative name fragment
|
|
||||||
// from the parent to the child. For example, if finding "/foo/bar/baz" then
|
|
||||||
// parent will be the node for "/foo/bar" and frag will be "baz".
|
|
||||||
//
|
|
||||||
// If the fullname names the root node, then parent, frag and err will be zero.
|
|
||||||
//
|
|
||||||
// find returns an error if the parent does not already exist or the parent
|
|
||||||
// isn't a directory, but it will not return an error per se if the child does
|
|
||||||
// not already exist. The error returned is either nil or an *os.PathError
|
|
||||||
// whose Op is op.
|
|
||||||
func (fs *memFS) find(op, fullname string) (parent *memFSNode, frag string, err error) {
|
|
||||||
err = fs.walk(op, fullname, func(parent0 *memFSNode, frag0 string, final bool) error {
|
|
||||||
if !final {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if frag0 != "" {
|
|
||||||
parent, frag = parent0, frag0
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
return parent, frag, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs *memFS) Mkdir(ctx context.Context, name string, perm os.FileMode) error {
|
|
||||||
fs.mu.Lock()
|
|
||||||
defer fs.mu.Unlock()
|
|
||||||
|
|
||||||
dir, frag, err := fs.find("mkdir", name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if dir == nil {
|
|
||||||
// We can't create the root.
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
if _, ok := dir.children[frag]; ok {
|
|
||||||
return os.ErrExist
|
|
||||||
}
|
|
||||||
dir.children[frag] = &memFSNode{
|
|
||||||
children: make(map[string]*memFSNode),
|
|
||||||
mode: perm.Perm() | os.ModeDir,
|
|
||||||
modTime: time.Now(),
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs *memFS) OpenFile(ctx context.Context, name string, flag int, perm os.FileMode) (File, error) {
|
|
||||||
fs.mu.Lock()
|
|
||||||
defer fs.mu.Unlock()
|
|
||||||
|
|
||||||
dir, frag, err := fs.find("open", name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var n *memFSNode
|
|
||||||
if dir == nil {
|
|
||||||
// We're opening the root.
|
|
||||||
if flag&(os.O_WRONLY|os.O_RDWR) != 0 {
|
|
||||||
return nil, os.ErrPermission
|
|
||||||
}
|
|
||||||
n, frag = &fs.root, "/"
|
|
||||||
|
|
||||||
} else {
|
|
||||||
n = dir.children[frag]
|
|
||||||
if flag&(os.O_SYNC|os.O_APPEND) != 0 {
|
|
||||||
// memFile doesn't support these flags yet.
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
if flag&os.O_CREATE != 0 {
|
|
||||||
if flag&os.O_EXCL != 0 && n != nil {
|
|
||||||
return nil, os.ErrExist
|
|
||||||
}
|
|
||||||
if n == nil {
|
|
||||||
n = &memFSNode{
|
|
||||||
mode: perm.Perm(),
|
|
||||||
}
|
|
||||||
dir.children[frag] = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if n == nil {
|
|
||||||
return nil, os.ErrNotExist
|
|
||||||
}
|
|
||||||
if flag&(os.O_WRONLY|os.O_RDWR) != 0 && flag&os.O_TRUNC != 0 {
|
|
||||||
n.mu.Lock()
|
|
||||||
n.data = nil
|
|
||||||
n.mu.Unlock()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
children := make([]os.FileInfo, 0, len(n.children))
|
|
||||||
for cName, c := range n.children {
|
|
||||||
children = append(children, c.stat(cName))
|
|
||||||
}
|
|
||||||
return &memFile{
|
|
||||||
n: n,
|
|
||||||
nameSnapshot: frag,
|
|
||||||
childrenSnapshot: children,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs *memFS) RemoveAll(ctx context.Context, name string) error {
|
|
||||||
fs.mu.Lock()
|
|
||||||
defer fs.mu.Unlock()
|
|
||||||
|
|
||||||
dir, frag, err := fs.find("remove", name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if dir == nil {
|
|
||||||
// We can't remove the root.
|
|
||||||
return os.ErrInvalid
|
|
||||||
}
|
|
||||||
delete(dir.children, frag)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs *memFS) Stat(ctx context.Context, name string) (os.FileInfo, error) {
|
|
||||||
fs.mu.Lock()
|
|
||||||
defer fs.mu.Unlock()
|
|
||||||
|
|
||||||
dir, frag, err := fs.find("stat", name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if dir == nil {
|
|
||||||
// We're stat'ting the root.
|
|
||||||
return fs.root.stat("/"), nil
|
|
||||||
}
|
|
||||||
if n, ok := dir.children[frag]; ok {
|
|
||||||
return n.stat(path.Base(name)), nil
|
|
||||||
}
|
|
||||||
return nil, os.ErrNotExist
|
|
||||||
}
|
|
||||||
|
|
||||||
// A memFSNode represents a single entry in the in-memory filesystem and also
|
|
||||||
// implements os.FileInfo.
|
|
||||||
type memFSNode struct {
|
|
||||||
// children is protected by memFS.mu.
|
|
||||||
children map[string]*memFSNode
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
data []byte
|
|
||||||
mode os.FileMode
|
|
||||||
modTime time.Time
|
|
||||||
deadProps map[xml.Name]Property
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *memFSNode) stat(name string) *memFileInfo {
|
|
||||||
n.mu.Lock()
|
|
||||||
defer n.mu.Unlock()
|
|
||||||
return &memFileInfo{
|
|
||||||
name: name,
|
|
||||||
size: int64(len(n.data)),
|
|
||||||
mode: n.mode,
|
|
||||||
modTime: n.modTime,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *memFSNode) DeadProps() (map[xml.Name]Property, error) {
|
|
||||||
n.mu.Lock()
|
|
||||||
defer n.mu.Unlock()
|
|
||||||
if len(n.deadProps) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
ret := make(map[xml.Name]Property, len(n.deadProps))
|
|
||||||
for k, v := range n.deadProps {
|
|
||||||
ret[k] = v
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *memFSNode) Patch(patches []Proppatch) ([]Propstat, error) {
|
|
||||||
n.mu.Lock()
|
|
||||||
defer n.mu.Unlock()
|
|
||||||
pstat := Propstat{Status: http.StatusOK}
|
|
||||||
for _, patch := range patches {
|
|
||||||
for _, p := range patch.Props {
|
|
||||||
pstat.Props = append(pstat.Props, Property{XMLName: p.XMLName})
|
|
||||||
if patch.Remove {
|
|
||||||
delete(n.deadProps, p.XMLName)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if n.deadProps == nil {
|
|
||||||
n.deadProps = map[xml.Name]Property{}
|
|
||||||
}
|
|
||||||
n.deadProps[p.XMLName] = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []Propstat{pstat}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type memFileInfo struct {
|
|
||||||
name string
|
|
||||||
size int64
|
|
||||||
mode os.FileMode
|
|
||||||
modTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *memFileInfo) Name() string { return f.name }
|
|
||||||
func (f *memFileInfo) Size() int64 { return f.size }
|
|
||||||
func (f *memFileInfo) Mode() os.FileMode { return f.mode }
|
|
||||||
func (f *memFileInfo) ModTime() time.Time { return f.modTime }
|
|
||||||
func (f *memFileInfo) IsDir() bool { return f.mode.IsDir() }
|
|
||||||
func (f *memFileInfo) Sys() interface{} { return nil }
|
|
||||||
|
|
||||||
// A memFile is a File implementation for a memFSNode. It is a per-file (not
|
|
||||||
// per-node) read/write position, and a snapshot of the memFS' tree structure
|
|
||||||
// (a node's name and children) for that node.
|
|
||||||
type memFile struct {
|
|
||||||
n *memFSNode
|
|
||||||
nameSnapshot string
|
|
||||||
childrenSnapshot []os.FileInfo
|
|
||||||
// pos is protected by n.mu.
|
|
||||||
pos int
|
|
||||||
}
|
|
||||||
|
|
||||||
// A *memFile implements the optional DeadPropsHolder interface.
|
|
||||||
var _ DeadPropsHolder = (*memFile)(nil)
|
|
||||||
|
|
||||||
func (f *memFile) DeadProps() (map[xml.Name]Property, error) { return f.n.DeadProps() }
|
|
||||||
func (f *memFile) Patch(patches []Proppatch) ([]Propstat, error) { return f.n.Patch(patches) }
|
|
||||||
|
|
||||||
func (f *memFile) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *memFile) Read(p []byte) (int, error) {
|
|
||||||
f.n.mu.Lock()
|
|
||||||
defer f.n.mu.Unlock()
|
|
||||||
if f.n.mode.IsDir() {
|
|
||||||
return 0, os.ErrInvalid
|
|
||||||
}
|
|
||||||
if f.pos >= len(f.n.data) {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
n := copy(p, f.n.data[f.pos:])
|
|
||||||
f.pos += n
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *memFile) Readdir(count int) ([]os.FileInfo, error) {
|
|
||||||
f.n.mu.Lock()
|
|
||||||
defer f.n.mu.Unlock()
|
|
||||||
if !f.n.mode.IsDir() {
|
|
||||||
return nil, os.ErrInvalid
|
|
||||||
}
|
|
||||||
old := f.pos
|
|
||||||
if old >= len(f.childrenSnapshot) {
|
|
||||||
// The os.File Readdir docs say that at the end of a directory,
|
|
||||||
// the error is io.EOF if count > 0 and nil if count <= 0.
|
|
||||||
if count > 0 {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
f.pos += count
|
|
||||||
if f.pos > len(f.childrenSnapshot) {
|
|
||||||
f.pos = len(f.childrenSnapshot)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
f.pos = len(f.childrenSnapshot)
|
|
||||||
old = 0
|
|
||||||
}
|
|
||||||
return f.childrenSnapshot[old:f.pos], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *memFile) Seek(offset int64, whence int) (int64, error) {
|
|
||||||
f.n.mu.Lock()
|
|
||||||
defer f.n.mu.Unlock()
|
|
||||||
npos := f.pos
|
|
||||||
// TODO: How to handle offsets greater than the size of system int?
|
|
||||||
switch whence {
|
|
||||||
case os.SEEK_SET:
|
|
||||||
npos = int(offset)
|
|
||||||
case os.SEEK_CUR:
|
|
||||||
npos += int(offset)
|
|
||||||
case os.SEEK_END:
|
|
||||||
npos = len(f.n.data) + int(offset)
|
|
||||||
default:
|
|
||||||
npos = -1
|
|
||||||
}
|
|
||||||
if npos < 0 {
|
|
||||||
return 0, os.ErrInvalid
|
|
||||||
}
|
|
||||||
f.pos = npos
|
|
||||||
return int64(f.pos), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *memFile) Stat() (os.FileInfo, error) {
|
|
||||||
return f.n.stat(f.nameSnapshot), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *memFile) Write(p []byte) (int, error) {
|
|
||||||
lenp := len(p)
|
|
||||||
f.n.mu.Lock()
|
|
||||||
defer f.n.mu.Unlock()
|
|
||||||
|
|
||||||
if f.n.mode.IsDir() {
|
|
||||||
return 0, os.ErrInvalid
|
|
||||||
}
|
|
||||||
if f.pos < len(f.n.data) {
|
|
||||||
n := copy(f.n.data[f.pos:], p)
|
|
||||||
f.pos += n
|
|
||||||
p = p[n:]
|
|
||||||
} else if f.pos > len(f.n.data) {
|
|
||||||
// Write permits the creation of holes, if we've seek'ed past the
|
|
||||||
// existing end of file.
|
|
||||||
if f.pos <= cap(f.n.data) {
|
|
||||||
oldLen := len(f.n.data)
|
|
||||||
f.n.data = f.n.data[:f.pos]
|
|
||||||
hole := f.n.data[oldLen:]
|
|
||||||
for i := range hole {
|
|
||||||
hole[i] = 0
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
d := make([]byte, f.pos, f.pos+len(p))
|
|
||||||
copy(d, f.n.data)
|
|
||||||
f.n.data = d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(p) > 0 {
|
|
||||||
// We should only get here if f.pos == len(f.n.data).
|
|
||||||
f.n.data = append(f.n.data, p...)
|
|
||||||
f.pos = len(f.n.data)
|
|
||||||
}
|
|
||||||
f.n.modTime = time.Now()
|
|
||||||
return lenp, nil
|
|
||||||
}
|
|
||||||
|
417
rest/dav/lock.go
417
rest/dav/lock.go
@ -1,417 +0,0 @@
|
|||||||
// Copyright 2014 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package dav
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/heap"
|
|
||||||
"errors"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrConfirmationFailed is returned by a LockSystem's Confirm method.
|
|
||||||
ErrConfirmationFailed = errors.New("webdav: confirmation failed")
|
|
||||||
// ErrForbidden is returned by a LockSystem's Unlock method.
|
|
||||||
ErrForbidden = errors.New("webdav: forbidden")
|
|
||||||
// ErrLocked is returned by a LockSystem's Create, Refresh and Unlock methods.
|
|
||||||
ErrLocked = errors.New("webdav: locked")
|
|
||||||
// ErrNoSuchLock is returned by a LockSystem's Refresh and Unlock methods.
|
|
||||||
ErrNoSuchLock = errors.New("webdav: no such lock")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Condition can match a WebDAV resource, based on a token or ETag.
|
|
||||||
// Exactly one of Token and ETag should be non-empty.
|
|
||||||
type Condition struct {
|
|
||||||
Not bool
|
|
||||||
Token string
|
|
||||||
ETag string
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockSystem manages access to a collection of named resources. The elements
|
|
||||||
// in a lock name are separated by slash ('/', U+002F) characters, regardless
|
|
||||||
// of host operating system convention.
|
|
||||||
type LockSystem interface {
|
|
||||||
// Confirm confirms that the caller can claim all of the locks specified by
|
|
||||||
// the given conditions, and that holding the union of all of those locks
|
|
||||||
// gives exclusive access to all of the named resources. Up to two resources
|
|
||||||
// can be named. Empty names are ignored.
|
|
||||||
//
|
|
||||||
// Exactly one of release and err will be non-nil. If release is non-nil,
|
|
||||||
// all of the requested locks are held until release is called. Calling
|
|
||||||
// release does not unlock the lock, in the WebDAV UNLOCK sense, but once
|
|
||||||
// Confirm has confirmed that a lock claim is valid, that lock cannot be
|
|
||||||
// Confirmed again until it has been released.
|
|
||||||
//
|
|
||||||
// If Confirm returns ErrConfirmationFailed then the Handler will continue
|
|
||||||
// to try any other set of locks presented (a WebDAV HTTP request can
|
|
||||||
// present more than one set of locks). If it returns any other non-nil
|
|
||||||
// error, the Handler will write a "500 Internal Server Error" HTTP status.
|
|
||||||
Confirm(now time.Time, name0, name1 string, conditions ...Condition) (release func(), err error)
|
|
||||||
|
|
||||||
// Create creates a lock with the given depth, duration, owner and root
|
|
||||||
// (name). The depth will either be negative (meaning infinite) or zero.
|
|
||||||
//
|
|
||||||
// If Create returns ErrLocked then the Handler will write a "423 Locked"
|
|
||||||
// HTTP status. If it returns any other non-nil error, the Handler will
|
|
||||||
// write a "500 Internal Server Error" HTTP status.
|
|
||||||
//
|
|
||||||
// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for
|
|
||||||
// when to use each error.
|
|
||||||
//
|
|
||||||
// The token returned identifies the created lock. It should be an absolute
|
|
||||||
// URI as defined by RFC 3986, Section 4.3. In particular, it should not
|
|
||||||
// contain whitespace.
|
|
||||||
Create(now time.Time, details LockDetails) (token string, err error)
|
|
||||||
|
|
||||||
// Refresh refreshes the lock with the given token.
|
|
||||||
//
|
|
||||||
// If Refresh returns ErrLocked then the Handler will write a "423 Locked"
|
|
||||||
// HTTP Status. If Refresh returns ErrNoSuchLock then the Handler will write
|
|
||||||
// a "412 Precondition Failed" HTTP Status. If it returns any other non-nil
|
|
||||||
// error, the Handler will write a "500 Internal Server Error" HTTP status.
|
|
||||||
//
|
|
||||||
// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.6 for
|
|
||||||
// when to use each error.
|
|
||||||
Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error)
|
|
||||||
|
|
||||||
// Unlock unlocks the lock with the given token.
|
|
||||||
//
|
|
||||||
// If Unlock returns ErrForbidden then the Handler will write a "403
|
|
||||||
// Forbidden" HTTP Status. If Unlock returns ErrLocked then the Handler
|
|
||||||
// will write a "423 Locked" HTTP status. If Unlock returns ErrNoSuchLock
|
|
||||||
// then the Handler will write a "409 Conflict" HTTP Status. If it returns
|
|
||||||
// any other non-nil error, the Handler will write a "500 Internal Server
|
|
||||||
// Error" HTTP status.
|
|
||||||
//
|
|
||||||
// See http://www.webdav.org/specs/rfc4918.html#rfc.section.9.11.1 for
|
|
||||||
// when to use each error.
|
|
||||||
Unlock(now time.Time, token string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// LockDetails are a lock's metadata.
|
|
||||||
type LockDetails struct {
|
|
||||||
// Root is the root resource name being locked. For a zero-depth lock, the
|
|
||||||
// root is the only resource being locked.
|
|
||||||
Root string
|
|
||||||
// Duration is the lock timeout. A negative duration means infinite.
|
|
||||||
Duration time.Duration
|
|
||||||
// OwnerXML is the verbatim <owner> XML given in a LOCK HTTP request.
|
|
||||||
//
|
|
||||||
// TODO: does the "verbatim" nature play well with XML namespaces?
|
|
||||||
// Does the OwnerXML field need to have more structure? See
|
|
||||||
// https://codereview.appspot.com/175140043/#msg2
|
|
||||||
OwnerXML string
|
|
||||||
// ZeroDepth is whether the lock has zero depth. If it does not have zero
|
|
||||||
// depth, it has infinite depth.
|
|
||||||
ZeroDepth bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMemLS returns a new in-memory LockSystem.
|
|
||||||
func NewMemLS() LockSystem {
|
|
||||||
return &memLS{
|
|
||||||
byName: make(map[string]*memLSNode),
|
|
||||||
byToken: make(map[string]*memLSNode),
|
|
||||||
gen: uint64(time.Now().Unix()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type memLS struct {
|
|
||||||
mu sync.Mutex
|
|
||||||
byName map[string]*memLSNode
|
|
||||||
byToken map[string]*memLSNode
|
|
||||||
gen uint64
|
|
||||||
// byExpiry only contains those nodes whose LockDetails have a finite
|
|
||||||
// Duration and are yet to expire.
|
|
||||||
byExpiry byExpiry
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) nextToken() string {
|
|
||||||
m.gen++
|
|
||||||
return strconv.FormatUint(m.gen, 10)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) collectExpiredNodes(now time.Time) {
|
|
||||||
for len(m.byExpiry) > 0 {
|
|
||||||
if now.Before(m.byExpiry[0].expiry) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
m.remove(m.byExpiry[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) Confirm(now time.Time, name0, name1 string, conditions ...Condition) (func(), error) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.collectExpiredNodes(now)
|
|
||||||
|
|
||||||
var n0, n1 *memLSNode
|
|
||||||
if name0 != "" {
|
|
||||||
if n0 = m.lookup(SlashClean(name0), conditions...); n0 == nil {
|
|
||||||
return nil, ErrConfirmationFailed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if name1 != "" {
|
|
||||||
if n1 = m.lookup(SlashClean(name1), conditions...); n1 == nil {
|
|
||||||
return nil, ErrConfirmationFailed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't hold the same node twice.
|
|
||||||
if n1 == n0 {
|
|
||||||
n1 = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if n0 != nil {
|
|
||||||
m.hold(n0)
|
|
||||||
}
|
|
||||||
if n1 != nil {
|
|
||||||
m.hold(n1)
|
|
||||||
}
|
|
||||||
return func() {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
if n1 != nil {
|
|
||||||
m.unhold(n1)
|
|
||||||
}
|
|
||||||
if n0 != nil {
|
|
||||||
m.unhold(n0)
|
|
||||||
}
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup returns the node n that locks the named resource, provided that n
|
|
||||||
// matches at least one of the given conditions and that lock isn't held by
|
|
||||||
// another party. Otherwise, it returns nil.
|
|
||||||
//
|
|
||||||
// n may be a parent of the named resource, if n is an infinite depth lock.
|
|
||||||
func (m *memLS) lookup(name string, conditions ...Condition) (n *memLSNode) {
|
|
||||||
// TODO: support Condition.Not and Condition.ETag.
|
|
||||||
for _, c := range conditions {
|
|
||||||
n = m.byToken[c.Token]
|
|
||||||
if n == nil || n.held {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if name == n.details.Root {
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
if n.details.ZeroDepth {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if n.details.Root == "/" || strings.HasPrefix(name, n.details.Root+"/") {
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) hold(n *memLSNode) {
|
|
||||||
if n.held {
|
|
||||||
panic("webdav: memLS inconsistent held state")
|
|
||||||
}
|
|
||||||
n.held = true
|
|
||||||
if n.details.Duration >= 0 && n.byExpiryIndex >= 0 {
|
|
||||||
heap.Remove(&m.byExpiry, n.byExpiryIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) unhold(n *memLSNode) {
|
|
||||||
if !n.held {
|
|
||||||
panic("webdav: memLS inconsistent held state")
|
|
||||||
}
|
|
||||||
n.held = false
|
|
||||||
if n.details.Duration >= 0 {
|
|
||||||
heap.Push(&m.byExpiry, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) Create(now time.Time, details LockDetails) (string, error) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.collectExpiredNodes(now)
|
|
||||||
details.Root = SlashClean(details.Root)
|
|
||||||
|
|
||||||
if !m.canCreate(details.Root, details.ZeroDepth) {
|
|
||||||
return "", ErrLocked
|
|
||||||
}
|
|
||||||
n := m.create(details.Root)
|
|
||||||
n.token = m.nextToken()
|
|
||||||
m.byToken[n.token] = n
|
|
||||||
n.details = details
|
|
||||||
if n.details.Duration >= 0 {
|
|
||||||
n.expiry = now.Add(n.details.Duration)
|
|
||||||
heap.Push(&m.byExpiry, n)
|
|
||||||
}
|
|
||||||
return n.token, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) Refresh(now time.Time, token string, duration time.Duration) (LockDetails, error) {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.collectExpiredNodes(now)
|
|
||||||
|
|
||||||
n := m.byToken[token]
|
|
||||||
if n == nil {
|
|
||||||
return LockDetails{}, ErrNoSuchLock
|
|
||||||
}
|
|
||||||
if n.held {
|
|
||||||
return LockDetails{}, ErrLocked
|
|
||||||
}
|
|
||||||
if n.byExpiryIndex >= 0 {
|
|
||||||
heap.Remove(&m.byExpiry, n.byExpiryIndex)
|
|
||||||
}
|
|
||||||
n.details.Duration = duration
|
|
||||||
if n.details.Duration >= 0 {
|
|
||||||
n.expiry = now.Add(n.details.Duration)
|
|
||||||
heap.Push(&m.byExpiry, n)
|
|
||||||
}
|
|
||||||
return n.details, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) Unlock(now time.Time, token string) error {
|
|
||||||
m.mu.Lock()
|
|
||||||
defer m.mu.Unlock()
|
|
||||||
m.collectExpiredNodes(now)
|
|
||||||
|
|
||||||
n := m.byToken[token]
|
|
||||||
if n == nil {
|
|
||||||
return ErrNoSuchLock
|
|
||||||
}
|
|
||||||
if n.held {
|
|
||||||
return ErrLocked
|
|
||||||
}
|
|
||||||
m.remove(n)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) canCreate(name string, zeroDepth bool) bool {
|
|
||||||
return walkToRoot(name, func(name0 string, first bool) bool {
|
|
||||||
n := m.byName[name0]
|
|
||||||
if n == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if first {
|
|
||||||
if n.token != "" {
|
|
||||||
// The target node is already locked.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if !zeroDepth {
|
|
||||||
// The requested lock depth is infinite, and the fact that n exists
|
|
||||||
// (n != nil) means that a descendent of the target node is locked.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else if n.token != "" && !n.details.ZeroDepth {
|
|
||||||
// An ancestor of the target node is locked with infinite depth.
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) create(name string) (ret *memLSNode) {
|
|
||||||
walkToRoot(name, func(name0 string, first bool) bool {
|
|
||||||
n := m.byName[name0]
|
|
||||||
if n == nil {
|
|
||||||
n = &memLSNode{
|
|
||||||
details: LockDetails{
|
|
||||||
Root: name0,
|
|
||||||
},
|
|
||||||
byExpiryIndex: -1,
|
|
||||||
}
|
|
||||||
m.byName[name0] = n
|
|
||||||
}
|
|
||||||
n.refCount++
|
|
||||||
if first {
|
|
||||||
ret = n
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memLS) remove(n *memLSNode) {
|
|
||||||
delete(m.byToken, n.token)
|
|
||||||
n.token = ""
|
|
||||||
walkToRoot(n.details.Root, func(name0 string, first bool) bool {
|
|
||||||
x := m.byName[name0]
|
|
||||||
x.refCount--
|
|
||||||
if x.refCount == 0 {
|
|
||||||
delete(m.byName, name0)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
if n.byExpiryIndex >= 0 {
|
|
||||||
heap.Remove(&m.byExpiry, n.byExpiryIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func walkToRoot(name string, f func(name0 string, first bool) bool) bool {
|
|
||||||
for first := true; ; first = false {
|
|
||||||
if !f(name, first) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if name == "/" {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
name = name[:strings.LastIndex(name, "/")]
|
|
||||||
if name == "" {
|
|
||||||
name = "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type memLSNode struct {
|
|
||||||
// details are the lock metadata. Even if this node's name is not explicitly locked,
|
|
||||||
// details.Root will still equal the node's name.
|
|
||||||
details LockDetails
|
|
||||||
// token is the unique identifier for this node's lock. An empty token means that
|
|
||||||
// this node is not explicitly locked.
|
|
||||||
token string
|
|
||||||
// refCount is the number of self-or-descendent nodes that are explicitly locked.
|
|
||||||
refCount int
|
|
||||||
// expiry is when this node's lock expires.
|
|
||||||
expiry time.Time
|
|
||||||
// byExpiryIndex is the index of this node in memLS.byExpiry. It is -1
|
|
||||||
// if this node does not expire, or has expired.
|
|
||||||
byExpiryIndex int
|
|
||||||
// held is whether this node's lock is actively held by a Confirm call.
|
|
||||||
held bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type byExpiry []*memLSNode
|
|
||||||
|
|
||||||
func (b *byExpiry) Len() int {
|
|
||||||
return len(*b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *byExpiry) Less(i, j int) bool {
|
|
||||||
return (*b)[i].expiry.Before((*b)[j].expiry)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *byExpiry) Swap(i, j int) {
|
|
||||||
(*b)[i], (*b)[j] = (*b)[j], (*b)[i]
|
|
||||||
(*b)[i].byExpiryIndex = i
|
|
||||||
(*b)[j].byExpiryIndex = j
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *byExpiry) Push(x interface{}) {
|
|
||||||
n := x.(*memLSNode)
|
|
||||||
n.byExpiryIndex = len(*b)
|
|
||||||
*b = append(*b, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *byExpiry) Pop() interface{} {
|
|
||||||
i := len(*b) - 1
|
|
||||||
n := (*b)[i]
|
|
||||||
(*b)[i] = nil
|
|
||||||
n.byExpiryIndex = -1
|
|
||||||
*b = (*b)[:i]
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
const infiniteTimeout = -1
|
|
290
rest/dav/prop.go
290
rest/dav/prop.go
@ -8,14 +8,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"mime"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Proppatch describes a property update instruction as defined in RFC 4918.
|
// Proppatch describes a property update instruction as defined in RFC 4918.
|
||||||
@ -51,24 +43,6 @@ type Propstat struct {
|
|||||||
ResponseDescription string
|
ResponseDescription string
|
||||||
}
|
}
|
||||||
|
|
||||||
// makePropstats returns a slice containing those of x and y whose Props slice
|
|
||||||
// is non-empty. If both are empty, it returns a slice containing an otherwise
|
|
||||||
// zero Propstat whose HTTP status code is 200 OK.
|
|
||||||
func makePropstats(x, y Propstat) []Propstat {
|
|
||||||
pstats := make([]Propstat, 0, 2)
|
|
||||||
if len(x.Props) != 0 {
|
|
||||||
pstats = append(pstats, x)
|
|
||||||
}
|
|
||||||
if len(y.Props) != 0 {
|
|
||||||
pstats = append(pstats, y)
|
|
||||||
}
|
|
||||||
if len(pstats) == 0 {
|
|
||||||
pstats = append(pstats, Propstat{
|
|
||||||
Status: http.StatusOK,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return pstats
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeadPropsHolder holds the dead properties of a resource.
|
// DeadPropsHolder holds the dead properties of a resource.
|
||||||
//
|
//
|
||||||
@ -99,178 +73,6 @@ type DeadPropsHolder interface {
|
|||||||
Patch([]Proppatch) ([]Propstat, error)
|
Patch([]Proppatch) ([]Propstat, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// liveProps contains all supported, protected DAV: properties.
|
|
||||||
var liveProps = map[xml.Name]struct {
|
|
||||||
// findFn implements the propfind function of this property. If nil,
|
|
||||||
// it indicates a hidden property.
|
|
||||||
findFn func(context.Context, FileSystem, LockSystem, string, os.FileInfo) (string, error)
|
|
||||||
// dir is true if the property applies to directories.
|
|
||||||
dir bool
|
|
||||||
}{
|
|
||||||
{Space: "DAV:", Local: "resourcetype"}: {
|
|
||||||
findFn: findResourceType,
|
|
||||||
dir: true,
|
|
||||||
},
|
|
||||||
{Space: "DAV:", Local: "displayname"}: {
|
|
||||||
findFn: findDisplayName,
|
|
||||||
dir: true,
|
|
||||||
},
|
|
||||||
{Space: "DAV:", Local: "getcontentlength"}: {
|
|
||||||
findFn: findContentLength,
|
|
||||||
dir: false,
|
|
||||||
},
|
|
||||||
{Space: "DAV:", Local: "getlastmodified"}: {
|
|
||||||
findFn: findLastModified,
|
|
||||||
// http://webdav.org/specs/rfc4918.html#PROPERTY_getlastmodified
|
|
||||||
// suggests that getlastmodified should only apply to GETable
|
|
||||||
// resources, and this package does not support GET on directories.
|
|
||||||
//
|
|
||||||
// Nonetheless, some WebDAV clients expect child directories to be
|
|
||||||
// sortable by getlastmodified date, so this value is true, not false.
|
|
||||||
// See golang.org/issue/15334.
|
|
||||||
dir: true,
|
|
||||||
},
|
|
||||||
{Space: "DAV:", Local: "creationdate"}: {
|
|
||||||
findFn: nil,
|
|
||||||
dir: false,
|
|
||||||
},
|
|
||||||
{Space: "DAV:", Local: "getcontentlanguage"}: {
|
|
||||||
findFn: nil,
|
|
||||||
dir: false,
|
|
||||||
},
|
|
||||||
{Space: "DAV:", Local: "getcontenttype"}: {
|
|
||||||
findFn: findContentType,
|
|
||||||
dir: false,
|
|
||||||
},
|
|
||||||
{Space: "DAV:", Local: "getetag"}: {
|
|
||||||
findFn: findETag,
|
|
||||||
// findETag implements ETag as the concatenated hex values of a file's
|
|
||||||
// modification time and size. This is not a reliable synchronization
|
|
||||||
// mechanism for directories, so we do not advertise getetag for DAV
|
|
||||||
// collections.
|
|
||||||
dir: false,
|
|
||||||
},
|
|
||||||
|
|
||||||
// TODO: The lockdiscovery property requires LockSystem to list the
|
|
||||||
// active locks on a resource.
|
|
||||||
{Space: "DAV:", Local: "lockdiscovery"}: {},
|
|
||||||
{Space: "DAV:", Local: "supportedlock"}: {
|
|
||||||
findFn: findSupportedLock,
|
|
||||||
dir: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(nigeltao) merge props and allprop?
|
|
||||||
|
|
||||||
// Props returns the status of the properties named pnames for resource name.
|
|
||||||
//
|
|
||||||
// Each Propstat has a unique status and each property name will only be part
|
|
||||||
// of one Propstat element.
|
|
||||||
func props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Propstat, error) {
|
|
||||||
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
fi, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
isDir := fi.IsDir()
|
|
||||||
|
|
||||||
var deadProps map[xml.Name]Property
|
|
||||||
if dph, ok := f.(DeadPropsHolder); ok {
|
|
||||||
deadProps, err = dph.DeadProps()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pstatOK := Propstat{Status: http.StatusOK}
|
|
||||||
pstatNotFound := Propstat{Status: http.StatusNotFound}
|
|
||||||
for _, pn := range pnames {
|
|
||||||
// If this file has dead properties, check if they contain pn.
|
|
||||||
if dp, ok := deadProps[pn]; ok {
|
|
||||||
pstatOK.Props = append(pstatOK.Props, dp)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Otherwise, it must either be a live property or we don't know it.
|
|
||||||
if prop := liveProps[pn]; prop.findFn != nil && (prop.dir || !isDir) {
|
|
||||||
innerXML, err := prop.findFn(ctx, fs, ls, name, fi)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
pstatOK.Props = append(pstatOK.Props, Property{
|
|
||||||
XMLName: pn,
|
|
||||||
InnerXML: []byte(innerXML),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
pstatNotFound.Props = append(pstatNotFound.Props, Property{
|
|
||||||
XMLName: pn,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return makePropstats(pstatOK, pstatNotFound), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Propnames returns the property names defined for resource name.
|
|
||||||
func Propnames(ctx context.Context, fs FileSystem, ls LockSystem, name string) ([]xml.Name, error) {
|
|
||||||
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
fi, err := f.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
isDir := fi.IsDir()
|
|
||||||
|
|
||||||
var deadProps map[xml.Name]Property
|
|
||||||
if dph, ok := f.(DeadPropsHolder); ok {
|
|
||||||
deadProps, err = dph.DeadProps()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pnames := make([]xml.Name, 0, len(liveProps)+len(deadProps))
|
|
||||||
for pn, prop := range liveProps {
|
|
||||||
if prop.findFn != nil && (prop.dir || !isDir) {
|
|
||||||
pnames = append(pnames, pn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for pn := range deadProps {
|
|
||||||
pnames = append(pnames, pn)
|
|
||||||
}
|
|
||||||
return pnames, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allprop returns the properties defined for resource name and the properties
|
|
||||||
// named in include.
|
|
||||||
//
|
|
||||||
// Note that RFC 4918 defines 'allprop' to return the DAV: properties defined
|
|
||||||
// within the RFC plus dead properties. Other live properties should only be
|
|
||||||
// returned if they are named in 'include'.
|
|
||||||
//
|
|
||||||
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
|
|
||||||
func allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]Propstat, error) {
|
|
||||||
pnames, err := Propnames(ctx, fs, ls, name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Add names from include if they are not already covered in pnames.
|
|
||||||
nameset := make(map[xml.Name]bool)
|
|
||||||
for _, pn := range pnames {
|
|
||||||
nameset[pn] = true
|
|
||||||
}
|
|
||||||
for _, pn := range include {
|
|
||||||
if !nameset[pn] {
|
|
||||||
pnames = append(pnames, pn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return props(ctx, fs, ls, name, pnames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func EscapeXML(s string) string {
|
func EscapeXML(s string) string {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
@ -292,32 +94,7 @@ func EscapeXML(s string) string {
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func findResourceType(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
|
||||||
if fi.IsDir() {
|
|
||||||
return `<D:collection xmlns:D="DAV:"/>`, nil
|
|
||||||
}
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findDisplayName(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
|
||||||
if SlashClean(name) == "/" {
|
|
||||||
// Hide the real name of a possibly prefixed root directory.
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
return EscapeXML(fi.Name()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findContentLength(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
|
||||||
return strconv.FormatInt(fi.Size(), 10), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findLastModified(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
|
||||||
return fi.ModTime().UTC().Format(http.TimeFormat), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrNotImplemented should be returned by optional interfaces if they
|
|
||||||
// want the original implementation to be used.
|
|
||||||
var ErrNotImplemented = errors.New("not implemented")
|
|
||||||
|
|
||||||
// ContentTyper is an optional interface for the os.FileInfo
|
// ContentTyper is an optional interface for the os.FileInfo
|
||||||
// objects returned by the FileSystem.
|
// objects returned by the FileSystem.
|
||||||
@ -336,70 +113,3 @@ type ContentTyper interface {
|
|||||||
ContentType(ctx context.Context) (string, error)
|
ContentType(ctx context.Context) (string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func findContentType(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
|
||||||
if do, ok := fi.(ContentTyper); ok {
|
|
||||||
ctype, err := do.ContentType(ctx)
|
|
||||||
if err != ErrNotImplemented {
|
|
||||||
return ctype, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
// This implementation is based on serveContent's code in the standard net/http package.
|
|
||||||
ctype := mime.TypeByExtension(filepath.Ext(name))
|
|
||||||
if ctype != "" {
|
|
||||||
return ctype, nil
|
|
||||||
}
|
|
||||||
// Read a chunk to decide between utf-8 text and binary.
|
|
||||||
var buf [512]byte
|
|
||||||
n, err := io.ReadFull(f, buf[:])
|
|
||||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
ctype = http.DetectContentType(buf[:n])
|
|
||||||
// Rewind file.
|
|
||||||
_, err = f.Seek(0, os.SEEK_SET)
|
|
||||||
return ctype, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ETager is an optional interface for the os.FileInfo objects
|
|
||||||
// returned by the FileSystem.
|
|
||||||
//
|
|
||||||
// If this interface is defined then it will be used to read the ETag
|
|
||||||
// for the object.
|
|
||||||
//
|
|
||||||
// If this interface is not defined an ETag will be computed using the
|
|
||||||
// ModTime() and the Size() methods of the os.FileInfo object.
|
|
||||||
type ETager interface {
|
|
||||||
// ETag returns an ETag for the file. This should be of the
|
|
||||||
// form "value" or W/"value"
|
|
||||||
//
|
|
||||||
// If this returns error ErrNotImplemented then the error will
|
|
||||||
// be ignored and the base implementation will be used
|
|
||||||
// instead.
|
|
||||||
ETag(ctx context.Context) (string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findETag(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
|
||||||
if do, ok := fi.(ETager); ok {
|
|
||||||
etag, err := do.ETag(ctx)
|
|
||||||
if err != ErrNotImplemented {
|
|
||||||
return etag, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The Apache http 2.4 web server by default concatenates the
|
|
||||||
// modification time and size of a file. We replicate the heuristic
|
|
||||||
// with nanosecond granularity.
|
|
||||||
return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size()), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findSupportedLock(ctx context.Context, fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
|
|
||||||
return `` +
|
|
||||||
`<D:lockentry xmlns:D="DAV:">` +
|
|
||||||
`<D:lockscope><D:exclusive/></D:lockscope>` +
|
|
||||||
`<D:locktype><D:write/></D:locktype>` +
|
|
||||||
`</D:lockentry>`, nil
|
|
||||||
}
|
|
||||||
|
@ -14,7 +14,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
// As of https://go-review.googlesource.com/#/c/12772/ which was submitted
|
// As of https://go-review.googlesource.com/#/c/12772/ which was submitted
|
||||||
// in July 2015, this package uses an internal fork of the standard
|
// in July 2015, this package uses an internal fork of the standard
|
||||||
@ -64,24 +63,8 @@ func StatusText(code int) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errDestinationEqualsSource = errors.New("webdav: destination equals source")
|
|
||||||
errDirectoryNotEmpty = errors.New("webdav: directory not empty")
|
|
||||||
errInvalidDepth = errors.New("webdav: invalid depth")
|
|
||||||
errInvalidDestination = errors.New("webdav: invalid destination")
|
|
||||||
errInvalidIfHeader = errors.New("webdav: invalid If header")
|
|
||||||
errInvalidLockInfo = errors.New("webdav: invalid lock info")
|
|
||||||
errInvalidLockToken = errors.New("webdav: invalid lock token")
|
|
||||||
errInvalidPropfind = errors.New("webdav: invalid propfind")
|
errInvalidPropfind = errors.New("webdav: invalid propfind")
|
||||||
errInvalidProppatch = errors.New("webdav: invalid proppatch")
|
|
||||||
errInvalidResponse = errors.New("webdav: invalid response")
|
errInvalidResponse = errors.New("webdav: invalid response")
|
||||||
errInvalidTimeout = errors.New("webdav: invalid timeout")
|
|
||||||
errNoFileSystem = errors.New("webdav: no file system")
|
|
||||||
errNoLockSystem = errors.New("webdav: no lock system")
|
|
||||||
errNotADirectory = errors.New("webdav: not a directory")
|
|
||||||
errPrefixMismatch = errors.New("webdav: prefix mismatch")
|
|
||||||
errRecursionTooDeep = errors.New("webdav: recursion too deep")
|
|
||||||
errUnsupportedLockInfo = errors.New("webdav: unsupported lock info")
|
|
||||||
errUnsupportedMethod = errors.New("webdav: unsupported method")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -113,26 +96,6 @@ func (c *CountingReader) Read(p []byte) (int, error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteLockInfo(w io.Writer, token string, ld LockDetails) (int, error) {
|
|
||||||
depth := "infinity"
|
|
||||||
if ld.ZeroDepth {
|
|
||||||
depth = "0"
|
|
||||||
}
|
|
||||||
timeout := ld.Duration / time.Second
|
|
||||||
return fmt.Fprintf(w, "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"+
|
|
||||||
"<D:prop xmlns:D=\"DAV:\"><D:lockdiscovery><D:activelock>\n"+
|
|
||||||
" <D:locktype><D:write/></D:locktype>\n"+
|
|
||||||
" <D:lockscope><D:exclusive/></D:lockscope>\n"+
|
|
||||||
" <D:depth>%s</D:depth>\n"+
|
|
||||||
" <D:owner>%s</D:owner>\n"+
|
|
||||||
" <D:timeout>Second-%d</D:timeout>\n"+
|
|
||||||
" <D:locktoken><D:href>%s</D:href></D:locktoken>\n"+
|
|
||||||
" <D:lockroot><D:href>%s</D:href></D:lockroot>\n"+
|
|
||||||
"</D:activelock></D:lockdiscovery></D:prop>",
|
|
||||||
depth, ld.OwnerXML, timeout, escape(token), escape(ld.Root),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
func escape(s string) string {
|
func escape(s string) string {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
switch s[i] {
|
switch s[i] {
|
||||||
@ -526,28 +489,3 @@ type PropertyUpdate struct {
|
|||||||
Lang string `xml:"xml:lang,attr,omitempty"`
|
Lang string `xml:"xml:lang,attr,omitempty"`
|
||||||
SetRemove []SetRemove `xml:",any"`
|
SetRemove []SetRemove `xml:",any"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func ReadProppatch(r io.Reader) (patches []Proppatch, status int, err error) {
|
|
||||||
var pu PropertyUpdate
|
|
||||||
if err = ixml.NewDecoder(r).Decode(&pu); err != nil {
|
|
||||||
return nil, http.StatusBadRequest, err
|
|
||||||
}
|
|
||||||
for _, op := range pu.SetRemove {
|
|
||||||
remove := false
|
|
||||||
switch op.XMLName {
|
|
||||||
case ixml.Name{Space: "DAV:", Local: "set"}:
|
|
||||||
// No-op.
|
|
||||||
case ixml.Name{Space: "DAV:", Local: "remove"}:
|
|
||||||
for _, p := range op.Prop {
|
|
||||||
if len(p.InnerXML) > 0 {
|
|
||||||
return nil, http.StatusBadRequest, errInvalidProppatch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
remove = true
|
|
||||||
default:
|
|
||||||
return nil, http.StatusBadRequest, errInvalidProppatch
|
|
||||||
}
|
|
||||||
patches = append(patches, Proppatch{Remove: remove, Props: op.Prop})
|
|
||||||
}
|
|
||||||
return patches, 0, nil
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user