Refine some webdav methods.
This commit is contained in:
@ -697,7 +697,7 @@ func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bo
|
|||||||
if err := fs.Mkdir(ctx, dst, srcPerm); err != nil {
|
if err := fs.Mkdir(ctx, dst, srcPerm); err != nil {
|
||||||
return http.StatusForbidden, err
|
return http.StatusForbidden, err
|
||||||
}
|
}
|
||||||
if depth == infiniteDepth {
|
if depth == InfiniteDepth {
|
||||||
children, err := srcFile.Readdir(-1)
|
children, err := srcFile.Readdir(-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusForbidden, err
|
return http.StatusForbidden, err
|
||||||
@ -745,7 +745,7 @@ func copyFiles(ctx context.Context, fs FileSystem, src, dst string, overwrite bo
|
|||||||
|
|
||||||
// walkFS traverses filesystem fs starting at name up to depth levels.
|
// walkFS traverses filesystem fs starting at name up to depth levels.
|
||||||
//
|
//
|
||||||
// Allowed values for depth are 0, 1 or infiniteDepth. For each visited node,
|
// Allowed values for depth are 0, 1 or InfiniteDepth. For each visited node,
|
||||||
// walkFS calls walkFn. If a visited file system node is a directory and
|
// walkFS calls walkFn. If a visited file system node is a directory and
|
||||||
// walkFn returns filepath.SkipDir, walkFS will skip traversal of this node.
|
// walkFn returns filepath.SkipDir, walkFS will skip traversal of this node.
|
||||||
func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
func walkFS(ctx context.Context, fs FileSystem, depth int, name string, info os.FileInfo, walkFn filepath.WalkFunc) error {
|
||||||
|
@ -467,7 +467,7 @@ func testFS(t *testing.T, fs FileSystem) {
|
|||||||
case "copy__":
|
case "copy__":
|
||||||
depth := 0
|
depth := 0
|
||||||
if parts[1] == "d=∞" {
|
if parts[1] == "d=∞" {
|
||||||
depth = infiniteDepth
|
depth = InfiniteDepth
|
||||||
}
|
}
|
||||||
_, opErr = copyFiles(ctx, fs, parts[2], parts[3], parts[0] == "o=T", depth, 0)
|
_, opErr = copyFiles(ctx, fs, parts[2], parts[3], parts[0] == "o=T", depth, 0)
|
||||||
case "mk-dir":
|
case "mk-dir":
|
||||||
@ -913,7 +913,7 @@ func TestCopyMoveProps(t *testing.T) {
|
|||||||
if err := patch("/src", Proppatch{Props: []Property{p0, p1}}); err != nil {
|
if err := patch("/src", Proppatch{Props: []Property{p0, p1}}); err != nil {
|
||||||
t.Fatalf("patch /src +p0 +p1: %v", err)
|
t.Fatalf("patch /src +p0 +p1: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := copyFiles(ctx, fs, "/src", "/tmp", true, infiniteDepth, 0); err != nil {
|
if _, err := copyFiles(ctx, fs, "/src", "/tmp", true, InfiniteDepth, 0); err != nil {
|
||||||
t.Fatalf("copyFiles /src /tmp: %v", err)
|
t.Fatalf("copyFiles /src /tmp: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := moveFiles(ctx, fs, "/tmp", "/dst", true); err != nil {
|
if _, err := moveFiles(ctx, fs, "/tmp", "/dst", true); err != nil {
|
||||||
@ -969,7 +969,7 @@ func TestWalkFS(t *testing.T) {
|
|||||||
"just root",
|
"just root",
|
||||||
[]string{},
|
[]string{},
|
||||||
"/",
|
"/",
|
||||||
infiniteDepth,
|
InfiniteDepth,
|
||||||
nil,
|
nil,
|
||||||
[]string{
|
[]string{
|
||||||
"/",
|
"/",
|
||||||
@ -985,7 +985,7 @@ func TestWalkFS(t *testing.T) {
|
|||||||
"touch /f",
|
"touch /f",
|
||||||
},
|
},
|
||||||
"/",
|
"/",
|
||||||
infiniteDepth,
|
InfiniteDepth,
|
||||||
nil,
|
nil,
|
||||||
[]string{
|
[]string{
|
||||||
"/",
|
"/",
|
||||||
@ -1007,7 +1007,7 @@ func TestWalkFS(t *testing.T) {
|
|||||||
"touch /f",
|
"touch /f",
|
||||||
},
|
},
|
||||||
"/a",
|
"/a",
|
||||||
infiniteDepth,
|
InfiniteDepth,
|
||||||
nil,
|
nil,
|
||||||
[]string{
|
[]string{
|
||||||
"/a",
|
"/a",
|
||||||
@ -1096,7 +1096,7 @@ func TestWalkFS(t *testing.T) {
|
|||||||
"touch /a/b/z",
|
"touch /a/b/z",
|
||||||
},
|
},
|
||||||
"/",
|
"/",
|
||||||
infiniteDepth,
|
InfiniteDepth,
|
||||||
func(path string, info os.FileInfo, err error) error {
|
func(path string, info os.FileInfo, err error) error {
|
||||||
if path == "/a/b/g" {
|
if path == "/a/b/g" {
|
||||||
return filepath.SkipDir
|
return filepath.SkipDir
|
||||||
|
@ -160,13 +160,13 @@ var liveProps = map[xml.Name]struct {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(nigeltao) merge props and allprop?
|
// TODO(nigeltao) merge Props and Allprop?
|
||||||
|
|
||||||
// Props returns the status of the properties named pnames for resource name.
|
// 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
|
// Each Propstat has a unique status and each property name will only be part
|
||||||
// of one Propstat element.
|
// of one Propstat element.
|
||||||
func props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pnames []xml.Name) ([]Propstat, error) {
|
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)
|
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -214,7 +214,7 @@ func props(ctx context.Context, fs FileSystem, ls LockSystem, name string, pname
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Propnames returns the property names defined for resource name.
|
// Propnames returns the property names defined for resource name.
|
||||||
func propnames(ctx context.Context, fs FileSystem, ls LockSystem, name string) ([]xml.Name, error) {
|
func PropNames(ctx context.Context, fs FileSystem, ls LockSystem, name string) ([]xml.Name, error) {
|
||||||
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -249,13 +249,13 @@ func propnames(ctx context.Context, fs FileSystem, ls LockSystem, name string) (
|
|||||||
// Allprop returns the properties defined for resource name and the properties
|
// Allprop returns the properties defined for resource name and the properties
|
||||||
// named in include.
|
// named in include.
|
||||||
//
|
//
|
||||||
// Note that RFC 4918 defines 'allprop' to return the DAV: properties defined
|
// Note that RFC 4918 defines 'Allprop' to return the DAV: properties defined
|
||||||
// within the RFC plus dead properties. Other live properties should only be
|
// within the RFC plus dead properties. Other live properties should only be
|
||||||
// returned if they are named in 'include'.
|
// returned if they are named in 'include'.
|
||||||
//
|
//
|
||||||
// See http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
|
// 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) {
|
func Allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, include []xml.Name) ([]Propstat, error) {
|
||||||
pnames, err := propnames(ctx, fs, ls, name)
|
pnames, err := PropNames(ctx, fs, ls, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -269,7 +269,7 @@ func allprop(ctx context.Context, fs FileSystem, ls LockSystem, name string, inc
|
|||||||
pnames = append(pnames, pn)
|
pnames = append(pnames, pn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return props(ctx, fs, ls, name, pnames)
|
return Props(ctx, fs, ls, name, pnames)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch patches the properties of resource name. The return values are
|
// Patch patches the properties of resource name. The return values are
|
||||||
|
@ -522,7 +522,7 @@ func TestMemPS(t *testing.T) {
|
|||||||
var propstats []Propstat
|
var propstats []Propstat
|
||||||
switch op.op {
|
switch op.op {
|
||||||
case "propname":
|
case "propname":
|
||||||
pnames, err := propnames(ctx, fs, ls, op.name)
|
pnames, err := PropNames(ctx, fs, ls, op.name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%s: got error %v, want nil", desc, err)
|
t.Errorf("%s: got error %v, want nil", desc, err)
|
||||||
continue
|
continue
|
||||||
@ -534,9 +534,9 @@ func TestMemPS(t *testing.T) {
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case "allprop":
|
case "allprop":
|
||||||
propstats, err = allprop(ctx, fs, ls, op.name, op.pnames)
|
propstats, err = Allprop(ctx, fs, ls, op.name, op.pnames)
|
||||||
case "propfind":
|
case "propfind":
|
||||||
propstats, err = props(ctx, fs, ls, op.name, op.pnames)
|
propstats, err = Props(ctx, fs, ls, op.name, op.pnames)
|
||||||
case "proppatch":
|
case "proppatch":
|
||||||
propstats, err = patch(ctx, fs, ls, op.name, op.patches)
|
propstats, err = patch(ctx, fs, ls, op.name, op.patches)
|
||||||
default:
|
default:
|
||||||
@ -546,7 +546,7 @@ func TestMemPS(t *testing.T) {
|
|||||||
t.Errorf("%s: got error %v, want nil", desc, err)
|
t.Errorf("%s: got error %v, want nil", desc, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Compare return values from allprop, propfind or proppatch.
|
// Compare return values from Allprop, propfind or proppatch.
|
||||||
for _, pst := range propstats {
|
for _, pst := range propstats {
|
||||||
sort.Sort(byPropname(pst.Props))
|
sort.Sort(byPropname(pst.Props))
|
||||||
}
|
}
|
||||||
|
@ -360,10 +360,10 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
|
|||||||
|
|
||||||
// Section 9.8.3 says that "The COPY method on a collection without a Depth
|
// Section 9.8.3 says that "The COPY method on a collection without a Depth
|
||||||
// header must act as if a Depth header with value "infinity" was included".
|
// header must act as if a Depth header with value "infinity" was included".
|
||||||
depth := infiniteDepth
|
depth := InfiniteDepth
|
||||||
if hdr := r.Header.Get("Depth"); hdr != "" {
|
if hdr := r.Header.Get("Depth"); hdr != "" {
|
||||||
depth = parseDepth(hdr)
|
depth = ParseDepth(hdr)
|
||||||
if depth != 0 && depth != infiniteDepth {
|
if depth != 0 && depth != InfiniteDepth {
|
||||||
// Section 9.8.3 says that "A client may submit a Depth header on a
|
// Section 9.8.3 says that "A client may submit a Depth header on a
|
||||||
// COPY on a collection with a value of "0" or "infinity"."
|
// COPY on a collection with a value of "0" or "infinity"."
|
||||||
return http.StatusBadRequest, errInvalidDepth
|
return http.StatusBadRequest, errInvalidDepth
|
||||||
@ -382,7 +382,7 @@ func (h *Handler) handleCopyMove(w http.ResponseWriter, r *http.Request) (status
|
|||||||
// a "Depth: infinity" header was used on it. A client must not submit a
|
// a "Depth: infinity" header was used on it. A client must not submit a
|
||||||
// Depth header on a MOVE on a collection with any value but "infinity"."
|
// Depth header on a MOVE on a collection with any value but "infinity"."
|
||||||
if hdr := r.Header.Get("Depth"); hdr != "" {
|
if hdr := r.Header.Get("Depth"); hdr != "" {
|
||||||
if parseDepth(hdr) != infiniteDepth {
|
if ParseDepth(hdr) != InfiniteDepth {
|
||||||
return http.StatusBadRequest, errInvalidDepth
|
return http.StatusBadRequest, errInvalidDepth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -424,10 +424,10 @@ func (h *Handler) handleLock(w http.ResponseWriter, r *http.Request) (retStatus
|
|||||||
} else {
|
} else {
|
||||||
// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
|
// Section 9.10.3 says that "If no Depth header is submitted on a LOCK request,
|
||||||
// then the request MUST act as if a "Depth:infinity" had been submitted."
|
// then the request MUST act as if a "Depth:infinity" had been submitted."
|
||||||
depth := infiniteDepth
|
depth := InfiniteDepth
|
||||||
if hdr := r.Header.Get("Depth"); hdr != "" {
|
if hdr := r.Header.Get("Depth"); hdr != "" {
|
||||||
depth = parseDepth(hdr)
|
depth = ParseDepth(hdr)
|
||||||
if depth != 0 && depth != infiniteDepth {
|
if depth != 0 && depth != InfiniteDepth {
|
||||||
// Section 9.10.3 says that "Values other than 0 or infinity must not be
|
// Section 9.10.3 says that "Values other than 0 or infinity must not be
|
||||||
// used with the Depth header on a LOCK method".
|
// used with the Depth header on a LOCK method".
|
||||||
return http.StatusBadRequest, errInvalidDepth
|
return http.StatusBadRequest, errInvalidDepth
|
||||||
@ -519,10 +519,10 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||||||
}
|
}
|
||||||
return http.StatusMethodNotAllowed, err
|
return http.StatusMethodNotAllowed, err
|
||||||
}
|
}
|
||||||
depth := infiniteDepth
|
depth := InfiniteDepth
|
||||||
if hdr := r.Header.Get("Depth"); hdr != "" {
|
if hdr := r.Header.Get("Depth"); hdr != "" {
|
||||||
depth = parseDepth(hdr)
|
depth = ParseDepth(hdr)
|
||||||
if depth == invalidDepth {
|
if depth == InvalidDepth {
|
||||||
return http.StatusBadRequest, errInvalidDepth
|
return http.StatusBadRequest, errInvalidDepth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -531,7 +531,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mw := multistatusWriter{w: w}
|
mw := MultiStatusWriter{Writer: w}
|
||||||
|
|
||||||
walkFn := func(reqPath string, info os.FileInfo, err error) error {
|
walkFn := func(reqPath string, info os.FileInfo, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -539,7 +539,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||||||
}
|
}
|
||||||
var pstats []Propstat
|
var pstats []Propstat
|
||||||
if pf.Propname != nil {
|
if pf.Propname != nil {
|
||||||
pnames, err := propnames(ctx, h.FileSystem, h.LockSystem, reqPath)
|
pnames, err := PropNames(ctx, h.FileSystem, h.LockSystem, reqPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -549,9 +549,9 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||||||
}
|
}
|
||||||
pstats = append(pstats, pstat)
|
pstats = append(pstats, pstat)
|
||||||
} else if pf.Allprop != nil {
|
} else if pf.Allprop != nil {
|
||||||
pstats, err = allprop(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
|
pstats, err = Allprop(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
|
||||||
} else {
|
} else {
|
||||||
pstats, err = props(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
|
pstats, err = Props(ctx, h.FileSystem, h.LockSystem, reqPath, pf.Prop)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -560,7 +560,7 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||||||
if href != "/" && info.IsDir() {
|
if href != "/" && info.IsDir() {
|
||||||
href += "/"
|
href += "/"
|
||||||
}
|
}
|
||||||
return mw.write(makePropstatResponse(href, pstats))
|
return mw.write(MakePropstatResponse(href, pstats))
|
||||||
}
|
}
|
||||||
|
|
||||||
walkErr := walkFS(ctx, h.FileSystem, depth, reqPath, fi, walkFn)
|
walkErr := walkFS(ctx, h.FileSystem, depth, reqPath, fi, walkFn)
|
||||||
@ -601,8 +601,8 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
return http.StatusInternalServerError, err
|
||||||
}
|
}
|
||||||
mw := multistatusWriter{w: w}
|
mw := MultiStatusWriter{Writer: w}
|
||||||
writeErr := mw.write(makePropstatResponse(r.URL.Path, pstats))
|
writeErr := mw.write(MakePropstatResponse(r.URL.Path, pstats))
|
||||||
closeErr := mw.close()
|
closeErr := mw.close()
|
||||||
if writeErr != nil {
|
if writeErr != nil {
|
||||||
return http.StatusInternalServerError, writeErr
|
return http.StatusInternalServerError, writeErr
|
||||||
@ -613,7 +613,7 @@ func (h *Handler) handleProppatch(w http.ResponseWriter, r *http.Request) (statu
|
|||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func makePropstatResponse(href string, pstats []Propstat) *response {
|
func MakePropstatResponse(href string, pstats []Propstat) *response {
|
||||||
resp := response{
|
resp := response{
|
||||||
Href: []string{(&url.URL{Path: href}).EscapedPath()},
|
Href: []string{(&url.URL{Path: href}).EscapedPath()},
|
||||||
Propstat: make([]propstat, 0, len(pstats)),
|
Propstat: make([]propstat, 0, len(pstats)),
|
||||||
@ -634,12 +634,12 @@ func makePropstatResponse(href string, pstats []Propstat) *response {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
infiniteDepth = -1
|
InfiniteDepth = -1
|
||||||
invalidDepth = -2
|
InvalidDepth = -2
|
||||||
)
|
)
|
||||||
|
|
||||||
// parseDepth maps the strings "0", "1" and "infinity" to 0, 1 and
|
// ParseDepth maps the strings "0", "1" and "infinity" to 0, 1 and
|
||||||
// infiniteDepth. Parsing any other string returns invalidDepth.
|
// InfiniteDepth. Parsing any other string returns InvalidDepth.
|
||||||
//
|
//
|
||||||
// Different WebDAV methods have further constraints on valid depths:
|
// Different WebDAV methods have further constraints on valid depths:
|
||||||
// - PROPFIND has no further restrictions, as per section 9.1.
|
// - PROPFIND has no further restrictions, as per section 9.1.
|
||||||
@ -647,16 +647,16 @@ const (
|
|||||||
// - MOVE accepts only "infinity", as per section 9.9.2.
|
// - MOVE accepts only "infinity", as per section 9.9.2.
|
||||||
// - LOCK accepts only "0" or "infinity", as per section 9.10.3.
|
// - LOCK accepts only "0" or "infinity", as per section 9.10.3.
|
||||||
// These constraints are enforced by the handleXxx methods.
|
// These constraints are enforced by the handleXxx methods.
|
||||||
func parseDepth(s string) int {
|
func ParseDepth(s string) int {
|
||||||
switch s {
|
switch s {
|
||||||
case "0":
|
case "0":
|
||||||
return 0
|
return 0
|
||||||
case "1":
|
case "1":
|
||||||
return 1
|
return 1
|
||||||
case "infinity":
|
case "infinity":
|
||||||
return infiniteDepth
|
return InfiniteDepth
|
||||||
}
|
}
|
||||||
return invalidDepth
|
return InvalidDepth
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
|
// http://www.webdav.org/specs/rfc4918.html#status.code.extensions.to.http11
|
||||||
|
@ -180,7 +180,7 @@ func ReadPropfind(r io.Reader) (pf Propfind, status int, err error) {
|
|||||||
if err = ixml.NewDecoder(&c).Decode(&pf); err != nil {
|
if err = ixml.NewDecoder(&c).Decode(&pf); err != nil {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
if c.n == 0 {
|
if c.n == 0 {
|
||||||
// An empty body means to propfind allprop.
|
// An empty body means to propfind Allprop.
|
||||||
// http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
|
// http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND
|
||||||
return Propfind{Allprop: new(struct{})}, 0, nil
|
return Propfind{Allprop: new(struct{})}, 0, nil
|
||||||
}
|
}
|
||||||
@ -233,14 +233,14 @@ type ixmlProperty struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
|
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
|
||||||
// See multistatusWriter for the "D:" namespace prefix.
|
// See MultiStatusWriter for the "D:" namespace prefix.
|
||||||
type xmlError struct {
|
type xmlError struct {
|
||||||
XMLName ixml.Name `xml:"D:error"`
|
XMLName ixml.Name `xml:"D:error"`
|
||||||
InnerXML []byte `xml:",innerxml"`
|
InnerXML []byte `xml:",innerxml"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat
|
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat
|
||||||
// See multistatusWriter for the "D:" namespace prefix.
|
// See MultiStatusWriter for the "D:" namespace prefix.
|
||||||
type propstat struct {
|
type propstat struct {
|
||||||
Prop []Property `xml:"D:prop>_ignored_"`
|
Prop []Property `xml:"D:prop>_ignored_"`
|
||||||
Status string `xml:"D:status"`
|
Status string `xml:"D:status"`
|
||||||
@ -258,7 +258,7 @@ type ixmlPropstat struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// MarshalXML prepends the "D:" namespace prefix on properties in the DAV: namespace
|
// MarshalXML prepends the "D:" namespace prefix on properties in the DAV: namespace
|
||||||
// before encoding. See multistatusWriter.
|
// before encoding. See MultiStatusWriter.
|
||||||
func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error {
|
func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error {
|
||||||
// Convert from a propstat to an ixmlPropstat.
|
// Convert from a propstat to an ixmlPropstat.
|
||||||
ixmlPs := ixmlPropstat{
|
ixmlPs := ixmlPropstat{
|
||||||
@ -287,7 +287,7 @@ func (ps propstat) MarshalXML(e *ixml.Encoder, start ixml.StartElement) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_response
|
// http://www.webdav.org/specs/rfc4918.html#ELEMENT_response
|
||||||
// See multistatusWriter for the "D:" namespace prefix.
|
// See MultiStatusWriter for the "D:" namespace prefix.
|
||||||
type response struct {
|
type response struct {
|
||||||
XMLName ixml.Name `xml:"D:response"`
|
XMLName ixml.Name `xml:"D:response"`
|
||||||
Href []string `xml:"D:href"`
|
Href []string `xml:"D:href"`
|
||||||
@ -306,14 +306,14 @@ type response struct {
|
|||||||
// well. This is because some versions of Mini-Redirector (on windows 7) ignore
|
// well. This is because some versions of Mini-Redirector (on windows 7) ignore
|
||||||
// elements with a default namespace (no prefixed namespace). A less intrusive fix
|
// elements with a default namespace (no prefixed namespace). A less intrusive fix
|
||||||
// should be possible after golang.org/cl/11074. See https://golang.org/issue/11177
|
// should be possible after golang.org/cl/11074. See https://golang.org/issue/11177
|
||||||
type multistatusWriter struct {
|
type MultiStatusWriter struct {
|
||||||
// ResponseDescription contains the optional responsedescription
|
// ResponseDescription contains the optional responsedescription
|
||||||
// of the multistatus XML element. Only the latest content before
|
// of the multistatus XML element. Only the latest content before
|
||||||
// close will be emitted. Empty response descriptions are not
|
// close will be emitted. Empty response descriptions are not
|
||||||
// written.
|
// written.
|
||||||
responseDescription string
|
responseDescription string
|
||||||
|
|
||||||
w http.ResponseWriter
|
Writer http.ResponseWriter
|
||||||
enc *ixml.Encoder
|
enc *ixml.Encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,7 +325,7 @@ type multistatusWriter struct {
|
|||||||
// first, valid response to be written, Write prepends the XML representation
|
// first, valid response to be written, Write prepends the XML representation
|
||||||
// of r with a multistatus tag. Callers must call close after the last response
|
// of r with a multistatus tag. Callers must call close after the last response
|
||||||
// has been written.
|
// has been written.
|
||||||
func (w *multistatusWriter) write(r *response) error {
|
func (w *MultiStatusWriter) write(r *response) error {
|
||||||
switch len(r.Href) {
|
switch len(r.Href) {
|
||||||
case 0:
|
case 0:
|
||||||
return errInvalidResponse
|
return errInvalidResponse
|
||||||
@ -348,17 +348,17 @@ func (w *multistatusWriter) write(r *response) error {
|
|||||||
// writeHeader writes a XML multistatus start element on w's underlying
|
// writeHeader writes a XML multistatus start element on w's underlying
|
||||||
// http.ResponseWriter and returns the result of the write operation.
|
// http.ResponseWriter and returns the result of the write operation.
|
||||||
// After the first write attempt, writeHeader becomes a no-op.
|
// After the first write attempt, writeHeader becomes a no-op.
|
||||||
func (w *multistatusWriter) writeHeader() error {
|
func (w *MultiStatusWriter) writeHeader() error {
|
||||||
if w.enc != nil {
|
if w.enc != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
w.w.Header().Add("Content-Type", "text/xml; charset=utf-8")
|
w.Writer.Header().Add("Content-Type", "text/xml; charset=utf-8")
|
||||||
w.w.WriteHeader(StatusMulti)
|
w.Writer.WriteHeader(StatusMulti)
|
||||||
_, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`)
|
_, err := fmt.Fprintf(w.Writer, `<?xml version="1.0" encoding="UTF-8"?>`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
w.enc = ixml.NewEncoder(w.w)
|
w.enc = ixml.NewEncoder(w.Writer)
|
||||||
return w.enc.EncodeToken(ixml.StartElement{
|
return w.enc.EncodeToken(ixml.StartElement{
|
||||||
Name: ixml.Name{
|
Name: ixml.Name{
|
||||||
Space: "DAV:",
|
Space: "DAV:",
|
||||||
@ -375,7 +375,7 @@ func (w *multistatusWriter) writeHeader() error {
|
|||||||
// an error if the multistatus response could not be completed. If both the
|
// an error if the multistatus response could not be completed. If both the
|
||||||
// return value and field enc of w are nil, then no multistatus response has
|
// return value and field enc of w are nil, then no multistatus response has
|
||||||
// been written.
|
// been written.
|
||||||
func (w *multistatusWriter) close() error {
|
func (w *MultiStatusWriter) close() error {
|
||||||
if w.enc == nil {
|
if w.enc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -566,7 +566,7 @@ func TestMultistatusWriter(t *testing.T) {
|
|||||||
loop:
|
loop:
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
rec := httptest.NewRecorder()
|
rec := httptest.NewRecorder()
|
||||||
w := multistatusWriter{w: rec, responseDescription: tc.respdesc}
|
w := MultiStatusWriter{Writer: rec, responseDescription: tc.respdesc}
|
||||||
if tc.writeHeader {
|
if tc.writeHeader {
|
||||||
if err := w.writeHeader(); err != nil {
|
if err := w.writeHeader(); err != nil {
|
||||||
t.Errorf("%s: got writeHeader error %v, want nil", tc.desc, err)
|
t.Errorf("%s: got writeHeader error %v, want nil", tc.desc, err)
|
||||||
|
1
go.sum
1
go.sum
@ -175,6 +175,7 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r
|
|||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65 h1:+rhAzEzT3f4JtomfC371qB+0Ola2caSKcY69NUBZrRQ=
|
||||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
|
Reference in New Issue
Block a user