diff --git a/cmd/noah-server/noah-server.go b/cmd/noah-server/noah-server.go
index ae3b2d8..64add1d 100644
--- a/cmd/noah-server/noah-server.go
+++ b/cmd/noah-server/noah-server.go
@@ -1,29 +1,96 @@
package main
import (
+ "io"
"fmt"
"net/http"
+ "strings"
"noahcloud/webdav"
+ "noahcloud/handlers/nextcloud"
+ //"noahcloud/handlers/proxy"
+ "net/http/httptest"
)
+func copyHeader(dst, src http.Header) {
+ for k, vv := range src {
+ for _, v := range vv {
+ dst.Add(k, v)
+ }
+ }
+}
+
func main() {
- fs := &webdav.Handler{
- FileSystem: webdav.Dir("."),
+ nc_normal_handler := &nextcloud.Handler{}
+ //proxy_handler := &proxy.Handler{}
+ nc_webdav_handler := &webdav.Handler{
+ Prefix: "/remote.php/webdav",
+ FileSystem: webdav.Dir("/data/ngo/files/"),
}
+ webdav_handler := &webdav.Handler{
+ Prefix: "/webdav",
+ FileSystem: webdav.Dir("/data/ngo/files/"),
+ }
+
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Println(r.Method, r.URL.Path)
- username, password, ok := r.BasicAuth()
- fmt.Println(username, password)
- if !ok {
- w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
- w.WriteHeader(http.StatusUnauthorized)
- return
+
+ writer := httptest.NewRecorder()
+
+ //proxy_handler.ServeHTTP(writer, r)
+ //fmt.Printf("%d - %s", writer.Code, writer.Body.String())
+
+ //copyHeader(w.Header(), writer.Header)
+ //w.WriteHeader(writer.StatusCode)
+ //io.Copy(w, writer.Body)
+ //return
+
+ if strings.HasPrefix(r.URL.Path, "/remote.php/webdav") {
+ username, password, ok := r.BasicAuth()
+ fmt.Println(username, password)
+ if !ok {
+ w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+ if username != "vscode" || password != "password" {
+ //http.Error(w, "WebDAV: need authorized!", http.StatusUnauthorized)
+ //return
+ }
+ nc_webdav_handler.ServeHTTP(writer, r)
+
+ copyHeader(w.Header(), writer.Header())
+ w.WriteHeader(writer.Code)
+ for k, vv := range writer.Header() {
+ for _, v := range vv {
+ fmt.Println(k, v)
+ }
+ }
+ fmt.Printf("%s\n", writer.Body.String())
+ io.Copy(w, writer.Body)
+ } else if strings.HasPrefix(r.URL.Path, "/webdav") {
+ username, password, ok := r.BasicAuth()
+ fmt.Println(username, password)
+ if !ok {
+ w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+ if username != "vscode" || password != "password" {
+ http.Error(w, "WebDAV: need authorized!", http.StatusUnauthorized)
+ return
+ }
+ webdav_handler.ServeHTTP(w, r)
+ } else if r.URL.Path == "/status.php" {
+ nc_normal_handler.ServeHTTP(w, r)
+ } else if r.URL.Path == "/ocs/v2.php/cloud/user" {
+ nc_normal_handler.ServeHTTP(w, r)
+ } else if r.URL.Path == "/index.php/login/v2" { // POST
+ nc_normal_handler.ServeHTTP(w, r)
+ } else if r.URL.Path == "/index.php/login/flow" {
+ nc_normal_handler.ServeHTTP(w, r)
+ } else {
+ http.Error(w, "Not Found", http.StatusNotFound)
}
- if username != "vscode" || password != "password" {
- //http.Error(w, "WebDAV: need authorized!", http.StatusUnauthorized)
- //return
- }
- fs.ServeHTTP(w, r)
})
fmt.Println("wait requests")
http.ListenAndServe(":8080", nil)
diff --git a/handlers/nextcloud/nextcloud.go b/handlers/nextcloud/nextcloud.go
new file mode 100644
index 0000000..2171ff2
--- /dev/null
+++ b/handlers/nextcloud/nextcloud.go
@@ -0,0 +1,77 @@
+package nextcloud
+
+import (
+ "net/http"
+ "encoding/json"
+ "io/ioutil"
+)
+
+type Handler struct {
+
+}
+
+type Status struct {
+ Installed bool `json:"installed"`
+ Maintenance bool `json:"maintenance"`
+ NeedsDbUpgrade bool `json:"needsDbUpgrade"`
+ Version string `json:"version"`
+ VersionString string `json:"versionstring"`
+ Edition string `json:"edition"`
+ ProductName string `json:"productname"`
+ ExtendedSupport bool `json:"extendedSupport"`
+}
+
+type PollMsg struct {
+ Endpoint string `json:"endpoint"`
+ Token string `json:"token"`
+}
+
+type TokenMsg struct {
+ Login string `json:"login"`
+ Poll PollMsg `json:"poll"`
+}
+
+func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ if r.URL.Path == "/status.php" {
+ status := Status{
+ Installed: true,
+ Maintenance: false,
+ NeedsDbUpgrade: false,
+ Version: "21.0.1.1",
+ VersionString: "21.0.1",
+ Edition: "",
+ ProductName: "Nextcloud",
+ ExtendedSupport: false,
+ }
+ js, _ := json.Marshal(status)
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(js)
+
+ } else if r.URL.Path == "/index.php/login/v2" { // #1 POST to start login session
+ tokenMsg := TokenMsg{
+ Login: "http://" + r.Host + "/login/v2/flow/mqNBzQ3AY1QFfOIzPIn5hZYeNZlZ0RDbT32b42cpmdkFjgGvqjSTsKh6OG7floh7z2JHKmWlZnl7kBFxNEQYcDnmYArkOFjXeBsAB5w3fbOcyJBmi5JBS1k1jYGL8V1D",
+ Poll: PollMsg {
+ Endpoint: "http://" + r.Host + "/login/v2/poll",
+ Token: "zbF2EOo2de8gNOOePERCHQgLp1FQHB8zcSQ5t1KQhtNH0fRqV85Nh5UGRm9ZqpHmigH52jCYuxLSVZpu4FjHq9j8va233OXEHqQnBnMGr4p3BoRcc957jvJEjynPvJcB",
+ },
+ }
+ js, _ := json.Marshal(tokenMsg)
+ w.Header().Set("Content-Type", "application/json")
+ w.Write(js)
+
+ } else if r.URL.Path == "/index.php/login/flow" { // # goto login page
+ var NewUrl string = "nc://login/server:http://" + r.Host + "&user:vscode&password:TEwNL0Sbkeln0TKdlcmNukkhQaOc6is0GBWlXIoO1YOByPweiSpoISuN5UI4fSGRBOgGE61I"
+ http.Redirect(w, r, NewUrl, http.StatusSeeOther)
+
+ } else if r.URL.Path == "/login/flow/grant" { // #3 redirect to grant page
+
+ } else if r.URL.Path == "/ocs/v2.php/cloud/user" { // #4 retreive user metadata
+ dat, _ := ioutil.ReadFile("/data/ngo/conf/ocs_v2.php_cloud_user.xml")
+ js := []byte(string(dat))
+ w.Header().Set("Content-Type", "application/xml; charset=utf-8")
+ w.Write(js)
+
+ } else {
+ http.Error(w, "Not Found", http.StatusNotFound)
+ }
+}
\ No newline at end of file
diff --git a/handlers/proxy/proxy.go b/handlers/proxy/proxy.go
new file mode 100644
index 0000000..8deaf48
--- /dev/null
+++ b/handlers/proxy/proxy.go
@@ -0,0 +1,88 @@
+package proxy
+
+import (
+ "net/http"
+ "net/http/httputil"
+ "io/ioutil"
+ "fmt"
+ "strings"
+ "bytes"
+ "io"
+)
+
+type Handler struct {
+
+}
+
+func copyHeader(dst, src http.Header) {
+ for k, vv := range src {
+ for _, v := range vv {
+ dst.Add(k, v)
+ }
+ }
+}
+
+func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ // we need to buffer the body if we want to read it here and send it
+ // in the request.
+ body, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ fmt.Println("request body--start")
+ fmt.Println(string(body))
+ fmt.Println("request body--stop")
+
+ // you can reassign the body if you need to parse it as multipart
+ r.Body = ioutil.NopCloser(bytes.NewReader(body))
+
+ // create a new url from the raw RequestURI sent by the client
+ url := fmt.Sprintf("%s://%s%s", "http", r.Host, r.RequestURI)
+ fmt.Println("-----Request-----", r.RequestURI)
+ fmt.Println(r.Method, url)
+
+ proxyReq, err := http.NewRequest(r.Method, url, bytes.NewReader(body))
+
+ // We may want to filter some headers, otherwise we could just use a shallow copy
+ // proxyReq.Header = r.Header
+ proxyReq.Header = make(http.Header)
+ for h, val := range r.Header {
+ tmp := []string{}
+ for _, v := range val{
+ if h == "Accept-Encoding" && strings.Contains(v, "gzip") {
+ continue
+ }
+ tmp = append(tmp, v)
+ }
+ proxyReq.Header[h] = tmp
+ fmt.Println(h, ":", tmp)
+ }
+
+ httpClient := &http.Client{
+ CheckRedirect: func(req *http.Request, via []*http.Request) error {
+ return http.ErrUseLastResponse
+ },
+ }
+ resp, err := httpClient.Do(proxyReq)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadGateway)
+ return
+ }
+
+ copyHeader(w.Header(), resp.Header)
+ w.WriteHeader(resp.StatusCode)
+
+ responseData, err := httputil.DumpResponse(resp, true)
+ if err != nil {
+ fmt.Println("err", err)
+ }
+ responseString := string(responseData)
+ fmt.Println("-----Response-----", r.RequestURI)
+ fmt.Println(responseString)
+
+ io.Copy(w, resp.Body)
+
+ defer resp.Body.Close()
+ // legacy code
+}
\ No newline at end of file
diff --git a/webdav/prop.go b/webdav/prop.go
index d65232c..9fc1892 100644
--- a/webdav/prop.go
+++ b/webdav/prop.go
@@ -107,18 +107,23 @@ var liveProps = map[xml.Name]struct {
findFn func(context.Context, FileSystem, string, os.FileInfo) (string, error)
// dir is true if the property applies to directories.
dir bool
+ // file is true if the property applies to files.
+ file bool
}{
{Space: "DAV:", Local: "resourcetype"}: {
findFn: findResourceType,
dir: true,
+ file: true,
},
{Space: "DAV:", Local: "displayname"}: {
findFn: findDisplayName,
dir: true,
+ file: true,
},
{Space: "DAV:", Local: "getcontentlength"}: {
findFn: findContentLength,
dir: false,
+ file: true,
},
{Space: "DAV:", Local: "getlastmodified"}: {
findFn: findLastModified,
@@ -130,26 +135,143 @@ var liveProps = map[xml.Name]struct {
// sortable by getlastmodified date, so this value is true, not false.
// See golang.org/issue/15334.
dir: true,
+ file:true,
},
{Space: "DAV:", Local: "creationdate"}: {
findFn: nil,
dir: false,
+ file: true,
},
{Space: "DAV:", Local: "getcontentlanguage"}: {
findFn: nil,
dir: false,
+ file: true,
},
{Space: "DAV:", Local: "getcontenttype"}: {
findFn: findContentType,
dir: false,
+ file: true,
},
{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: true,
+ file: true,
+ },
+ {Space: "DAV:", Local: "quota-available-bytes"}: {
+ findFn: fnGetQuotaAvailableBytes,
+ dir: true,
+ file: false,
+ },
+ {Space: "DAV:", Local: "quota-used-bytes"}: {
+ findFn: fnGetQuotaUsedBytes,
+ dir: true,
+ file: false,
+ },
+
+ {Space: "http://owncloud.org/ns", Local: "permissions"}: {
+ findFn: fnGetPermissions,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://owncloud.org/ns", Local: "id"}: {
+ findFn: fnGetID,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://owncloud.org/ns", Local: "fileid"}: {
+ findFn: fnGetFileID,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://owncloud.org/ns", Local: "size"}: {
+ findFn: fnGetSize,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://owncloud.org/ns", Local: "favorite"}: {
+ findFn: fnGetFavorite,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://owncloud.org/ns", Local: "share-types"}: {
+ findFn: fnGetShareTypes,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://owncloud.org/ns", Local: "owner-id"}: {
+ findFn: fnGetOwnerID,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://owncloud.org/ns", Local: "owner-display-name"}: {
+ findFn: fnGetOwnerDisplayName,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://owncloud.org/ns", Local: "comments-unread"}: {
+ findFn: fnGetCommentsUnread,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://owncloud.org/ns", Local: "checksums"}: {
+ findFn: fnGetChecksums,
dir: false,
+ file: false,
+ },
+ {Space: "http://owncloud.org/ns", Local: "downloadURL"}: {
+ findFn: fnGetDownloadURL,
+ dir: false,
+ file: true,
+ },
+ {Space: "http://owncloud.org/ns", Local: "data-fingerprint"}: {
+ findFn: fnGetDataFingerprint,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://nextcloud.org/ns", Local: "creation_time"}: {
+ findFn: fnGetCreationTime,
+ dir: false,
+ file: true,
+ },
+ {Space: "http://nextcloud.org/ns", Local: "upload_time"}: {
+ findFn: fnGetUploadTime,
+ dir: false,
+ file: true,
+ },
+ {Space: "http://nextcloud.org/ns", Local: "is-encrypted"}: {
+ findFn: fnGetIsEncrypted,
+ dir: true,
+ file: false,
+ },
+ {Space: "http://nextcloud.org/ns", Local: "has-preview"}: {
+ findFn: fnGetHasPreview,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://nextcloud.org/ns", Local: "mount-type"}: {
+ findFn: fnGetMountType,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://nextcloud.org/ns", Local: "rich-workspace"}: {
+ findFn: fnGetRichWorkspace,
+ dir: false,
+ file: false,
+ },
+ {Space: "http://nextcloud.org/ns", Local: "note"}: {
+ findFn: fnGetNote,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://open-collaboration-services.org/ns", Local: "share-permissions"}: {
+ findFn: fnGetSharePermissionsOCS,
+ dir: true,
+ file: true,
+ },
+ {Space: "http://open-cloud-mesh.org/ns", Local: "share-permissions"}: {
+ findFn: fnGetSharePermissionsOCM,
+ dir: true,
+ file: true,
},
}
@@ -188,7 +310,8 @@ func props(ctx context.Context, fs FileSystem, name string, pnames []xml.Name) (
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) {
+ //fmt.Println(liveProps[pn], liveProps[pn].findFn, name, pn)
+ if prop := liveProps[pn]; prop.findFn != nil && ((prop.dir && isDir) || (prop.file && !isDir)){
innerXML, err := prop.findFn(ctx, fs, name, fi)
if err != nil {
return nil, err
@@ -287,7 +410,7 @@ func escapeXML(s string) string {
func findResourceType(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
if fi.IsDir() {
- return ``, nil
+ return ``, nil
}
return "", nil
}
@@ -358,33 +481,104 @@ func findContentType(ctx context.Context, fs FileSystem, name string, fi os.File
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, name string, fi os.FileInfo) (string, error) {
+ return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size()), nil
}
-func findETag(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
- if do, ok := fi.(ETager); ok {
- etag, err := do.ETag(ctx)
- if err != ErrNotImplemented {
- return etag, err
- }
+func fnGetQuotaAvailableBytes(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "1057129933", nil
+}
+
+func fnGetQuotaUsedBytes(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "16611891", nil
+}
+
+func fnGetPermissions(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ if fi.IsDir() {
+ return "RGDNVCK", nil
}
- // 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
+ return "RGDNVW", nil
+}
+
+func fnGetID(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return name, nil
+}
+
+func fnGetFileID(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "0", nil
+}
+
+func fnGetSize(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "16611891", nil
+}
+
+func fnGetFavorite(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "0", nil
+}
+
+func fnGetShareTypes(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "", nil
+}
+
+func fnGetOwnerID(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "vscode", nil
+}
+
+func fnGetOwnerDisplayName(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "vscode", nil
+}
+
+func fnGetCommentsUnread(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "0", nil
+}
+
+func fnGetChecksums(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "", nil
+}
+
+func fnGetDownloadURL(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "", nil
+}
+
+func fnGetDataFingerprint(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "", nil
+}
+
+func fnGetCreationTime(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "0", nil
+}
+
+func fnGetUploadTime(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "0", nil
+}
+
+func fnGetIsEncrypted(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "0", nil
+}
+
+func fnGetHasPreview(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "false", nil
+}
+
+func fnGetMountType(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "", nil
+}
+
+func fnGetRichWorkspace(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "", nil
+}
+
+func fnGetNote(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "", nil
+}
+
+func fnGetSharePermissionsOCS(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ if fi.IsDir() {
+ return "31", nil
+ }
+ return "19", nil
+}
+
+func fnGetSharePermissionsOCM(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
+ return "[\"share\",\"read\",\"write\"]", nil
}
\ No newline at end of file
diff --git a/webdav/webdav.go b/webdav/webdav.go
index 4ceca99..a69da6b 100644
--- a/webdav/webdav.go
+++ b/webdav/webdav.go
@@ -173,7 +173,19 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
if err != nil {
return http.StatusInternalServerError, err
}
- w.Header().Set("ETag", etag)
+
+ w.Header().Set("Etag", etag)
+ w.Header().Set("Oc-Etag", etag)
+ w.Header().Set("Oc-Fileid", etag)
+ w.Header().Set("X-Hash-Md5", "97a81b5ce26cfa53e66c21da5c3d60ee")
+ w.Header().Set("X-Hash-Sha1", "0bdaebd3d8915d4a7de79cdc76cc42ad757a073d")
+ w.Header().Set("X-Hash-Sha256", "7de442457c55ce163ca64e7952cf4e1c796488f24f2adae8140f4f3160ea94c2")
+ if r.Header.Get("X-Oc-Ctime") != "" {
+ w.Header().Set("X-Oc-Ctime", "accepted")
+ }
+ if r.Header.Get("X-Oc-Mtime") != "" {
+ w.Header().Set("X-Oc-Mtime", "accepted")
+ }
return http.StatusCreated, nil
}
@@ -278,7 +290,6 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
if err != nil {
return status, err
}
-
mw := multistatusWriter{w: w}
walkFn := func(reqPath string, info os.FileInfo, err error) error {
diff --git a/webdav/xml.go b/webdav/xml.go
index b0d704e..a52db5f 100644
--- a/webdav/xml.go
+++ b/webdav/xml.go
@@ -158,31 +158,31 @@ type xmlProperty struct {
}
// 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 {
- XMLName xml.Name `xml:"D: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.
+// See multistatusWriter for the "d:" namespace prefix.
type propstat struct {
- Prop []Property `xml:"D:prop>_ignored_"`
- Status string `xml:"D:status"`
- Error *xmlError `xml:"D:error"`
- ResponseDescription string `xml:"D:responsedescription,omitempty"`
+ Prop []Property `xml:"d:prop>_ignored_"`
+ Status string `xml:"d:status"`
+ Error *xmlError `xml:"d:error"`
+ ResponseDescription string `xml:"d:responsedescription,omitempty"`
}
// xmlPropstat is the same as the propstat type except it holds an xml.Name
// instead of an xml.Name.
type xmlPropstat struct {
- Prop []xmlProperty `xml:"D:prop>_ignored_"`
- Status string `xml:"D:status"`
- Error *xmlError `xml:"D:error"`
- ResponseDescription string `xml:"D:responsedescription,omitempty"`
+ Prop []xmlProperty `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
+// 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 {
// Convert from a propstat to an xmlPropstat.
@@ -202,7 +202,19 @@ func (ps propstat) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
for k, prop := range xmlPs.Prop {
if prop.XMLName.Space == "DAV:" {
- prop.XMLName = xml.Name{Space: "", Local: "D:" + prop.XMLName.Local}
+ prop.XMLName = xml.Name{Space: "", Local: "d:" + prop.XMLName.Local}
+ xmlPs.Prop[k] = prop
+ } else if prop.XMLName.Space == "http://owncloud.org/ns" {
+ prop.XMLName = xml.Name{Space: "", Local: "oc:" + prop.XMLName.Local}
+ xmlPs.Prop[k] = prop
+ } else if prop.XMLName.Space == "http://nextcloud.org/ns" {
+ prop.XMLName = xml.Name{Space: "", Local: "nc:" + prop.XMLName.Local}
+ xmlPs.Prop[k] = prop
+ } else if prop.XMLName.Space == "http://open-collaboration-services.org/ns" {
+ prop.XMLName = xml.Name{Space: "http://open-collaboration-services.org/ns", Local: "x1:" + prop.XMLName.Local}
+ xmlPs.Prop[k] = prop
+ } else if prop.XMLName.Space == "http://open-cloud-mesh.org/ns" {
+ prop.XMLName = xml.Name{Space: "http://open-cloud-mesh.org/ns", Local: "x2:" + prop.XMLName.Local}
xmlPs.Prop[k] = prop
}
}
@@ -212,20 +224,20 @@ func (ps propstat) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
}
// 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 {
- 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"`
+ 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
+// 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
@@ -277,7 +289,8 @@ func (w *multistatusWriter) writeHeader() error {
if w.enc != nil {
return nil
}
- w.w.Header().Add("Content-Type", "text/xml; charset=utf-8")
+ w.w.Header().Add("Content-Type", "application/xml; charset=utf-8")
+ w.w.Header().Set("DAV", "1, 3, extended-mkcol")
w.w.WriteHeader(http.StatusMultiStatus)
_, err := fmt.Fprintf(w.w, ``)
if err != nil {
@@ -287,12 +300,22 @@ func (w *multistatusWriter) writeHeader() error {
return w.enc.EncodeToken(xml.StartElement{
Name: xml.Name{
Space: "",
- Local: "D:multistatus",
+ Local: "d:multistatus",
},
Attr: []xml.Attr{{
- Name: xml.Name{Space: "", Local: "xmlns:D"},
+ Name: xml.Name{Space: "", Local: "xmlns:d"},
Value: "DAV:",
- }},
+ },{
+ Name: xml.Name{Space: "", Local: "xmlns:s"},
+ Value: "http://sabredav.org/ns",
+ },{
+ Name: xml.Name{Space: "", Local: "xmlns:oc"},
+ Value: "http://owncloud.org/ns",
+ },{
+ Name: xml.Name{Space: "", Local: "xmlns:nc"},
+ Value: "http://nextcloud.org/ns",
+ },
+ },
})
}
@@ -314,7 +337,7 @@ func (w *multistatusWriter) close() error {
)
}
end = append(end, xml.EndElement{
- Name: xml.Name{Space: "", Local: "D:multistatus"},
+ Name: xml.Name{Space: "", Local: "d:multistatus"},
})
for _, t := range end {
err := w.enc.EncodeToken(t)