[to #1] support Nextcloud IOS
This commit is contained in:
parent
8497c9375c
commit
a1ebe0a9ed
@ -1,29 +1,96 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"noahcloud/webdav"
|
"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() {
|
func main() {
|
||||||
fs := &webdav.Handler{
|
nc_normal_handler := &nextcloud.Handler{}
|
||||||
FileSystem: webdav.Dir("."),
|
//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) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Println(r.Method, r.URL.Path)
|
fmt.Println(r.Method, r.URL.Path)
|
||||||
username, password, ok := r.BasicAuth()
|
|
||||||
fmt.Println(username, password)
|
writer := httptest.NewRecorder()
|
||||||
if !ok {
|
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
//proxy_handler.ServeHTTP(writer, r)
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
//fmt.Printf("%d - %s", writer.Code, writer.Body.String())
|
||||||
return
|
|
||||||
|
//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")
|
fmt.Println("wait requests")
|
||||||
http.ListenAndServe(":8080", nil)
|
http.ListenAndServe(":8080", nil)
|
||||||
|
77
handlers/nextcloud/nextcloud.go
Normal file
77
handlers/nextcloud/nextcloud.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
88
handlers/proxy/proxy.go
Normal file
88
handlers/proxy/proxy.go
Normal file
@ -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
|
||||||
|
}
|
258
webdav/prop.go
258
webdav/prop.go
@ -107,18 +107,23 @@ var liveProps = map[xml.Name]struct {
|
|||||||
findFn func(context.Context, FileSystem, string, os.FileInfo) (string, error)
|
findFn func(context.Context, FileSystem, string, os.FileInfo) (string, error)
|
||||||
// dir is true if the property applies to directories.
|
// dir is true if the property applies to directories.
|
||||||
dir bool
|
dir bool
|
||||||
|
// file is true if the property applies to files.
|
||||||
|
file bool
|
||||||
}{
|
}{
|
||||||
{Space: "DAV:", Local: "resourcetype"}: {
|
{Space: "DAV:", Local: "resourcetype"}: {
|
||||||
findFn: findResourceType,
|
findFn: findResourceType,
|
||||||
dir: true,
|
dir: true,
|
||||||
|
file: true,
|
||||||
},
|
},
|
||||||
{Space: "DAV:", Local: "displayname"}: {
|
{Space: "DAV:", Local: "displayname"}: {
|
||||||
findFn: findDisplayName,
|
findFn: findDisplayName,
|
||||||
dir: true,
|
dir: true,
|
||||||
|
file: true,
|
||||||
},
|
},
|
||||||
{Space: "DAV:", Local: "getcontentlength"}: {
|
{Space: "DAV:", Local: "getcontentlength"}: {
|
||||||
findFn: findContentLength,
|
findFn: findContentLength,
|
||||||
dir: false,
|
dir: false,
|
||||||
|
file: true,
|
||||||
},
|
},
|
||||||
{Space: "DAV:", Local: "getlastmodified"}: {
|
{Space: "DAV:", Local: "getlastmodified"}: {
|
||||||
findFn: findLastModified,
|
findFn: findLastModified,
|
||||||
@ -130,26 +135,143 @@ var liveProps = map[xml.Name]struct {
|
|||||||
// sortable by getlastmodified date, so this value is true, not false.
|
// sortable by getlastmodified date, so this value is true, not false.
|
||||||
// See golang.org/issue/15334.
|
// See golang.org/issue/15334.
|
||||||
dir: true,
|
dir: true,
|
||||||
|
file:true,
|
||||||
},
|
},
|
||||||
{Space: "DAV:", Local: "creationdate"}: {
|
{Space: "DAV:", Local: "creationdate"}: {
|
||||||
findFn: nil,
|
findFn: nil,
|
||||||
dir: false,
|
dir: false,
|
||||||
|
file: true,
|
||||||
},
|
},
|
||||||
{Space: "DAV:", Local: "getcontentlanguage"}: {
|
{Space: "DAV:", Local: "getcontentlanguage"}: {
|
||||||
findFn: nil,
|
findFn: nil,
|
||||||
dir: false,
|
dir: false,
|
||||||
|
file: true,
|
||||||
},
|
},
|
||||||
{Space: "DAV:", Local: "getcontenttype"}: {
|
{Space: "DAV:", Local: "getcontenttype"}: {
|
||||||
findFn: findContentType,
|
findFn: findContentType,
|
||||||
dir: false,
|
dir: false,
|
||||||
|
file: true,
|
||||||
},
|
},
|
||||||
{Space: "DAV:", Local: "getetag"}: {
|
{Space: "DAV:", Local: "getetag"}: {
|
||||||
findFn: findETag,
|
findFn: findETag,
|
||||||
// findETag implements ETag as the concatenated hex values of a file's
|
dir: true,
|
||||||
// modification time and size. This is not a reliable synchronization
|
file: true,
|
||||||
// mechanism for directories, so we do not advertise getetag for DAV
|
},
|
||||||
// collections.
|
{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,
|
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
|
continue
|
||||||
}
|
}
|
||||||
// Otherwise, it must either be a live property or we don't know it.
|
// 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)
|
innerXML, err := prop.findFn(ctx, fs, name, fi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
func findResourceType(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
|
||||||
if fi.IsDir() {
|
if fi.IsDir() {
|
||||||
return `<D:collection xmlns:D="DAV:"/>`, nil
|
return `<d:collection/>`, nil
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@ -358,33 +481,104 @@ func findContentType(ctx context.Context, fs FileSystem, name string, fi os.File
|
|||||||
return ctype, err
|
return ctype, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ETager is an optional interface for the os.FileInfo objects
|
func findETag(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
|
||||||
// returned by the FileSystem.
|
return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size()), nil
|
||||||
//
|
|
||||||
// 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) {
|
func fnGetQuotaAvailableBytes(ctx context.Context, fs FileSystem, name string, fi os.FileInfo) (string, error) {
|
||||||
if do, ok := fi.(ETager); ok {
|
return "1057129933", nil
|
||||||
etag, err := do.ETag(ctx)
|
}
|
||||||
if err != ErrNotImplemented {
|
|
||||||
return etag, err
|
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
|
return "RGDNVW", nil
|
||||||
// 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 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
|
||||||
}
|
}
|
@ -173,7 +173,19 @@ func (h *Handler) handlePut(w http.ResponseWriter, r *http.Request) (status int,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return http.StatusInternalServerError, err
|
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
|
return http.StatusCreated, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,7 +290,6 @@ func (h *Handler) handlePropfind(w http.ResponseWriter, r *http.Request) (status
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return status, err
|
return status, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mw := multistatusWriter{w: w}
|
mw := multistatusWriter{w: w}
|
||||||
|
|
||||||
walkFn := func(reqPath string, info os.FileInfo, err error) error {
|
walkFn := func(reqPath string, info os.FileInfo, err error) error {
|
||||||
|
@ -158,31 +158,31 @@ type xmlProperty 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 xml.Name `xml:"D:error"`
|
XMLName xml.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"`
|
||||||
Error *xmlError `xml:"D:error"`
|
Error *xmlError `xml:"d:error"`
|
||||||
ResponseDescription string `xml:"D:responsedescription,omitempty"`
|
ResponseDescription string `xml:"d:responsedescription,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// xmlPropstat is the same as the propstat type except it holds an xml.Name
|
// xmlPropstat is the same as the propstat type except it holds an xml.Name
|
||||||
// instead of an xml.Name.
|
// instead of an xml.Name.
|
||||||
type xmlPropstat struct {
|
type xmlPropstat struct {
|
||||||
Prop []xmlProperty `xml:"D:prop>_ignored_"`
|
Prop []xmlProperty `xml:"d:prop>_ignored_"`
|
||||||
Status string `xml:"D:status"`
|
Status string `xml:"d:status"`
|
||||||
Error *xmlError `xml:"D:error"`
|
Error *xmlError `xml:"d:error"`
|
||||||
ResponseDescription string `xml:"D:responsedescription,omitempty"`
|
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.
|
// before encoding. See multistatusWriter.
|
||||||
func (ps propstat) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
func (ps propstat) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
|
||||||
// Convert from a propstat to an xmlPropstat.
|
// 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 {
|
for k, prop := range xmlPs.Prop {
|
||||||
if prop.XMLName.Space == "DAV:" {
|
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
|
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
|
// 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 xml.Name `xml:"D:response"`
|
XMLName xml.Name `xml:"d:response"`
|
||||||
Href []string `xml:"D:href"`
|
Href []string `xml:"d:href"`
|
||||||
Propstat []propstat `xml:"D:propstat"`
|
Propstat []propstat `xml:"d:propstat"`
|
||||||
Status string `xml:"D:status,omitempty"`
|
Status string `xml:"d:status,omitempty"`
|
||||||
Error *xmlError `xml:"D:error"`
|
Error *xmlError `xml:"d:error"`
|
||||||
ResponseDescription string `xml:"D:responsedescription,omitempty"`
|
ResponseDescription string `xml:"d:responsedescription,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MultistatusWriter marshals one or more Responses into a XML
|
// MultistatusWriter marshals one or more Responses into a XML
|
||||||
// multistatus response.
|
// multistatus response.
|
||||||
// See http://www.webdav.org/specs/rfc4918.html#ELEMENT_multistatus
|
// 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
|
// "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
|
// 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
|
// 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 {
|
if w.enc != nil {
|
||||||
return 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)
|
w.w.WriteHeader(http.StatusMultiStatus)
|
||||||
_, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`)
|
_, err := fmt.Fprintf(w.w, `<?xml version="1.0" encoding="UTF-8"?>`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -287,12 +300,22 @@ func (w *multistatusWriter) writeHeader() error {
|
|||||||
return w.enc.EncodeToken(xml.StartElement{
|
return w.enc.EncodeToken(xml.StartElement{
|
||||||
Name: xml.Name{
|
Name: xml.Name{
|
||||||
Space: "",
|
Space: "",
|
||||||
Local: "D:multistatus",
|
Local: "d:multistatus",
|
||||||
},
|
},
|
||||||
Attr: []xml.Attr{{
|
Attr: []xml.Attr{{
|
||||||
Name: xml.Name{Space: "", Local: "xmlns:D"},
|
Name: xml.Name{Space: "", Local: "xmlns:d"},
|
||||||
Value: "DAV:",
|
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{
|
end = append(end, xml.EndElement{
|
||||||
Name: xml.Name{Space: "", Local: "D:multistatus"},
|
Name: xml.Name{Space: "", Local: "d:multistatus"},
|
||||||
})
|
})
|
||||||
for _, t := range end {
|
for _, t := range end {
|
||||||
err := w.enc.EncodeToken(t)
|
err := w.enc.EncodeToken(t)
|
||||||
|
Loading…
Reference in New Issue
Block a user