Browse Source

webdav: make it work for Mini-Redirector, emit namespace-prefixed XML.

The default webDAV client for windows explorer, Mini-Redirector,
apparently can not handle XML elements with a default namespace.

This change modifies the emitted XML so that elements are prefixed with
the D: namespace, defined as as "DAV:" in the multistatus tag.

Fixes golang/go#11177

Change-Id: Ib323ca312fa10bd5aa6e6c61d90812d066543bac
Reviewed-on: https://go-review.googlesource.com/10942
Reviewed-by: Nigel Tao <nigeltao@golang.org>
mpl 10 years ago
parent
commit
34ff4cd5e6
3 changed files with 60 additions and 43 deletions
  1. 6 6
      webdav/prop.go
  2. 19 25
      webdav/prop_test.go
  3. 35 12
      webdav/xml.go

+ 6 - 6
webdav/prop.go

@@ -278,7 +278,7 @@ loop:
 	if conflict {
 		pstatForbidden := Propstat{
 			Status:   http.StatusForbidden,
-			XMLError: `<error xmlns="DAV:"><cannot-modify-protected-property/></error>`,
+			XMLError: `<D:cannot-modify-protected-property xmlns:D="DAV:"/>`,
 		}
 		pstatFailedDep := Propstat{
 			Status: StatusFailedDependency,
@@ -328,7 +328,7 @@ loop:
 
 func findResourceType(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
 	if fi.IsDir() {
-		return `<collection xmlns="DAV:"/>`, nil
+		return `<D:collection xmlns:D="DAV:"/>`, nil
 	}
 	return "", nil
 }
@@ -377,8 +377,8 @@ func findETag(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string
 
 func findSupportedLock(fs FileSystem, ls LockSystem, name string, fi os.FileInfo) (string, error) {
 	return `` +
-		`<lockentry xmlns="DAV:">` +
-		`<lockscope><exclusive/></lockscope>` +
-		`<locktype><write/></locktype>` +
-		`</lockentry>`, nil
+		`<D:lockentry xmlns:D="DAV:">` +
+		`<D:lockscope><D:exclusive/></D:lockscope>` +
+		`<D:locktype><D:write/></D:locktype>` +
+		`</D:lockentry>`, nil
 }

+ 19 - 25
webdav/prop_test.go

@@ -44,6 +44,15 @@ func TestMemPS(t *testing.T) {
 		return nil
 	}
 
+	const (
+		lockEntry = `` +
+			`<D:lockentry xmlns:D="DAV:">` +
+			`<D:lockscope><D:exclusive/></D:lockscope>` +
+			`<D:locktype><D:write/></D:locktype>` +
+			`</D:lockentry>`
+		statForbiddenError = `<D:cannot-modify-protected-property xmlns:D="DAV:"/>`
+	)
+
 	type propOp struct {
 		op            string
 		name          string
@@ -95,7 +104,7 @@ func TestMemPS(t *testing.T) {
 				Status: http.StatusOK,
 				Props: []Property{{
 					XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
-					InnerXML: []byte(`<collection xmlns="DAV:"/>`),
+					InnerXML: []byte(`<D:collection xmlns:D="DAV:"/>`),
 				}, {
 					XMLName:  xml.Name{Space: "DAV:", Local: "displayname"},
 					InnerXML: []byte("dir"),
@@ -109,13 +118,8 @@ func TestMemPS(t *testing.T) {
 					XMLName:  xml.Name{Space: "DAV:", Local: "getcontenttype"},
 					InnerXML: []byte("text/plain; charset=utf-8"),
 				}, {
-					XMLName: xml.Name{Space: "DAV:", Local: "supportedlock"},
-					InnerXML: []byte(`` +
-						`<lockentry xmlns="DAV:">` +
-						`<lockscope><exclusive/></lockscope>` +
-						`<locktype><write/></locktype>` +
-						`</lockentry>`,
-					),
+					XMLName:  xml.Name{Space: "DAV:", Local: "supportedlock"},
+					InnerXML: []byte(lockEntry),
 				}},
 			}},
 		}, {
@@ -142,13 +146,8 @@ func TestMemPS(t *testing.T) {
 					XMLName:  xml.Name{Space: "DAV:", Local: "getetag"},
 					InnerXML: nil, // Calculated during test.
 				}, {
-					XMLName: xml.Name{Space: "DAV:", Local: "supportedlock"},
-					InnerXML: []byte(`` +
-						`<lockentry xmlns="DAV:">` +
-						`<lockscope><exclusive/></lockscope>` +
-						`<locktype><write/></locktype>` +
-						`</lockentry>`,
-					),
+					XMLName:  xml.Name{Space: "DAV:", Local: "supportedlock"},
+					InnerXML: []byte(lockEntry),
 				}},
 			}},
 		}, {
@@ -179,13 +178,8 @@ func TestMemPS(t *testing.T) {
 					XMLName:  xml.Name{Space: "DAV:", Local: "getetag"},
 					InnerXML: nil, // Calculated during test.
 				}, {
-					XMLName: xml.Name{Space: "DAV:", Local: "supportedlock"},
-					InnerXML: []byte(`` +
-						`<lockentry xmlns="DAV:">` +
-						`<lockscope><exclusive/></lockscope>` +
-						`<locktype><write/></locktype>` +
-						`</lockentry>`,
-					),
+					XMLName:  xml.Name{Space: "DAV:", Local: "supportedlock"},
+					InnerXML: []byte(lockEntry),
 				}}}, {
 				Status: http.StatusNotFound,
 				Props: []Property{{
@@ -204,7 +198,7 @@ func TestMemPS(t *testing.T) {
 				Status: http.StatusOK,
 				Props: []Property{{
 					XMLName:  xml.Name{Space: "DAV:", Local: "resourcetype"},
-					InnerXML: []byte(`<collection xmlns="DAV:"/>`),
+					InnerXML: []byte(`<D:collection xmlns:D="DAV:"/>`),
 				}},
 			}},
 		}, {
@@ -296,7 +290,7 @@ func TestMemPS(t *testing.T) {
 			}},
 			wantPropstats: []Propstat{{
 				Status:   http.StatusForbidden,
-				XMLError: `<error xmlns="DAV:"><cannot-modify-protected-property/></error>`,
+				XMLError: statForbiddenError,
 				Props: []Property{{
 					XMLName: xml.Name{Space: "DAV:", Local: "getetag"},
 				}},
@@ -351,7 +345,7 @@ func TestMemPS(t *testing.T) {
 			}},
 			wantPropstats: []Propstat{{
 				Status:   http.StatusForbidden,
-				XMLError: `<error xmlns="DAV:"><cannot-modify-protected-property/></error>`,
+				XMLError: statForbiddenError,
 				Props: []Property{{
 					XMLName: xml.Name{Space: "DAV:", Local: "displayname"},
 				}},

+ 35 - 12
webdav/xml.go

@@ -206,32 +206,55 @@ type Property struct {
 }
 
 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_error
+// See multistatusWriter for the "D:" namespace prefix.
 type xmlError struct {
-	XMLName  xml.Name `xml:"DAV: error"`
+	XMLName  xml.Name `xml:"D:error"`
 	InnerXML []byte   `xml:",innerxml"`
 }
 
 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_propstat
+// See multistatusWriter for the "D:" namespace prefix.
 type propstat struct {
-	Prop                []Property `xml:"DAV: prop>_ignored_"`
-	Status              string     `xml:"DAV: status"`
-	Error               *xmlError  `xml:"DAV: error"`
-	ResponseDescription string     `xml:"DAV: responsedescription,omitempty"`
+	Prop                []Property `xml:"D:prop>_ignored_"`
+	Status              string     `xml:"D:status"`
+	Error               *xmlError  `xml:"D:error"`
+	ResponseDescription string     `xml:"D:responsedescription,omitempty"`
+}
+
+// MarshalXML prepends the "D:" namespace prefix on properties in the DAV: namespace
+// before encoding. See multistatusWriter.
+func (ps propstat) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
+	for k, prop := range ps.Prop {
+		if prop.XMLName.Space == "DAV:" {
+			prop.XMLName = xml.Name{Space: "", Local: "D:" + prop.XMLName.Local}
+			ps.Prop[k] = prop
+		}
+	}
+	// Distinct type to avoid infinite recursion of MarshalXML.
+	type newpropstat propstat
+	return e.EncodeElement(newpropstat(ps), start)
 }
 
 // http://www.webdav.org/specs/rfc4918.html#ELEMENT_response
+// See multistatusWriter for the "D:" namespace prefix.
 type response struct {
-	XMLName             xml.Name   `xml:"DAV: response"`
-	Href                []string   `xml:"DAV: href"`
-	Propstat            []propstat `xml:"DAV: propstat"`
-	Status              string     `xml:"DAV: status,omitempty"`
-	Error               *xmlError  `xml:"DAV: error"`
-	ResponseDescription string     `xml:"DAV: responsedescription,omitempty"`
+	XMLName             xml.Name   `xml:"D:response"`
+	Href                []string   `xml:"D:href"`
+	Propstat            []propstat `xml:"D:propstat"`
+	Status              string     `xml:"D:status,omitempty"`
+	Error               *xmlError  `xml:"D:error"`
+	ResponseDescription string     `xml:"D:responsedescription,omitempty"`
 }
 
 // MultistatusWriter marshals one or more Responses into a XML
 // multistatus response.
 // See http://www.webdav.org/specs/rfc4918.html#ELEMENT_multistatus
+// TODO(rsto, mpl): As a workaround, the "D:" namespace prefix, defined as
+// "DAV:" on this element, is prepended on the nested response, as well as on all
+// its nested elements. All property names in the DAV: namespace are prefixed as
+// 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
+// should be possible after golang.org/cl/11074. See https://golang.org/issue/11177
 type multistatusWriter struct {
 	// ResponseDescription contains the optional responsedescription
 	// of the multistatus XML element. Only the latest content before
@@ -291,7 +314,7 @@ func (w *multistatusWriter) writeHeader() error {
 			Local: "multistatus",
 		},
 		Attr: []xml.Attr{{
-			Name:  xml.Name{Local: "xmlns"},
+			Name:  xml.Name{Space: "xmlns", Local: "D"},
 			Value: "DAV:",
 		}},
 	})