mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-10-28 04:05:21 +08:00
262 lines
6.2 KiB
Go
262 lines
6.2 KiB
Go
package plg_backend_webdav
|
|
|
|
import (
|
|
"encoding/xml"
|
|
. "github.com/mickael-kerjean/filestash/server/common"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type WebDav struct {
|
|
params *WebDavParams
|
|
}
|
|
|
|
type WebDavParams struct {
|
|
url string
|
|
username string
|
|
password string
|
|
path string
|
|
}
|
|
|
|
func init() {
|
|
Backend.Register("webdav", WebDav{})
|
|
}
|
|
|
|
func (w WebDav) Init(params map[string]string, app *App) (IBackend, error) {
|
|
params["url"] = regexp.MustCompile(`\/$`).ReplaceAllString(params["url"], "")
|
|
if strings.HasPrefix(params["url"], "http://") == false && strings.HasPrefix(params["url"], "https://") == false {
|
|
return nil, NewError("Malformed URL - missing http or https", 400)
|
|
}
|
|
backend := WebDav{
|
|
params: &WebDavParams{
|
|
strings.ReplaceAll(params["url"], "%{username}", url.PathEscape(params["username"])),
|
|
params["username"],
|
|
params["password"],
|
|
params["path"],
|
|
},
|
|
}
|
|
return backend, nil
|
|
}
|
|
|
|
func (w WebDav) LoginForm() Form {
|
|
return Form{
|
|
Elmnts: []FormElement{
|
|
FormElement{
|
|
Name: "type",
|
|
Type: "hidden",
|
|
Value: "webdav",
|
|
},
|
|
FormElement{
|
|
Name: "url",
|
|
Type: "text",
|
|
Placeholder: "Address",
|
|
},
|
|
FormElement{
|
|
Name: "username",
|
|
Type: "text",
|
|
Placeholder: "Username",
|
|
},
|
|
FormElement{
|
|
Name: "password",
|
|
Type: "password",
|
|
Placeholder: "Password",
|
|
},
|
|
FormElement{
|
|
Name: "advanced",
|
|
Type: "enable",
|
|
Placeholder: "Advanced",
|
|
Target: []string{"webdav_path"},
|
|
},
|
|
FormElement{
|
|
Id: "webdav_path",
|
|
Name: "path",
|
|
Type: "text",
|
|
Placeholder: "Path",
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (w WebDav) Ls(path string) ([]os.FileInfo, error) {
|
|
files := make([]os.FileInfo, 0)
|
|
query := `<d:propfind xmlns:d='DAV:'>
|
|
<d:prop>
|
|
<d:displayname/>
|
|
<d:resourcetype/>
|
|
<d:getlastmodified/>
|
|
<d:getcontentlength/>
|
|
</d:prop>
|
|
</d:propfind>`
|
|
res, err := w.request("PROPFIND", w.params.url+encodeURL(path), strings.NewReader(query), func(req *http.Request) {
|
|
req.Header.Add("Depth", "1")
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer res.Body.Close()
|
|
if res.StatusCode >= 400 {
|
|
return nil, NewError(HTTPFriendlyStatus(res.StatusCode)+": can't get things in "+filepath.Base(path), res.StatusCode)
|
|
}
|
|
|
|
var r WebDavResp
|
|
decoder := xml.NewDecoder(res.Body)
|
|
decoder.Decode(&r)
|
|
if len(r.Responses) == 0 {
|
|
return nil, NewError("Server not found", 404)
|
|
}
|
|
|
|
LongURLDav := w.params.url + path
|
|
ShortURLDav := regexp.MustCompile(`^http[s]?://[^/]*`).ReplaceAllString(LongURLDav, "")
|
|
for _, tag := range r.Responses {
|
|
decodedHref := decodeURL(tag.Href)
|
|
if decodedHref == ShortURLDav || decodedHref == LongURLDav {
|
|
continue
|
|
}
|
|
|
|
for i, prop := range tag.Props {
|
|
if i > 0 {
|
|
break
|
|
}
|
|
files = append(files, File{
|
|
FName: filepath.Base(decodedHref),
|
|
FType: func(p string) string {
|
|
if p == "collection" {
|
|
return "directory"
|
|
}
|
|
return "file"
|
|
}(prop.Type.Local),
|
|
FTime: func() int64 {
|
|
t, err := time.Parse(time.RFC1123, prop.Modified)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
return t.Unix()
|
|
}(),
|
|
FSize: int64(prop.Size),
|
|
})
|
|
}
|
|
}
|
|
return files, nil
|
|
}
|
|
|
|
func (w WebDav) Cat(path string) (io.ReadCloser, error) {
|
|
res, err := w.request("GET", w.params.url+encodeURL(path), nil, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if res.StatusCode >= 400 {
|
|
return nil, NewError(HTTPFriendlyStatus(res.StatusCode)+": can't fetch "+filepath.Base(path), res.StatusCode)
|
|
}
|
|
return res.Body, nil
|
|
}
|
|
func (w WebDav) Mkdir(path string) error {
|
|
res, err := w.request("MKCOL", w.params.url+encodeURL(path), nil, func(req *http.Request) {
|
|
req.Header.Add("Overwrite", "F")
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res.Body.Close()
|
|
if res.StatusCode >= 400 {
|
|
return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't create "+filepath.Base(path), res.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
func (w WebDav) Rm(path string) error {
|
|
res, err := w.request("DELETE", w.params.url+encodeURL(path), nil, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res.Body.Close()
|
|
if res.StatusCode >= 400 {
|
|
return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't remove "+filepath.Base(path), res.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
func (w WebDav) Mv(from string, to string) error {
|
|
res, err := w.request("MOVE", w.params.url+encodeURL(from), nil, func(req *http.Request) {
|
|
req.Header.Add("Destination", w.params.url+encodeURL(to))
|
|
req.Header.Add("Overwrite", "T")
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res.Body.Close()
|
|
if res.StatusCode >= 400 {
|
|
return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't do that", res.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
func (w WebDav) Touch(path string) error {
|
|
return w.Save(path, strings.NewReader(""))
|
|
}
|
|
func (w WebDav) Save(path string, file io.Reader) error {
|
|
res, err := w.request("PUT", w.params.url+encodeURL(path), file, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
res.Body.Close()
|
|
if res.StatusCode >= 400 {
|
|
return NewError(HTTPFriendlyStatus(res.StatusCode)+": can't do that", res.StatusCode)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (w WebDav) request(method string, url string, body io.Reader, fn func(req *http.Request)) (*http.Response, error) {
|
|
req, err := http.NewRequest(method, url, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if w.params.username != "" {
|
|
req.SetBasicAuth(w.params.username, w.params.password)
|
|
}
|
|
|
|
req.Header.Add("Content-Type", "text/xml;charset=UTF-8")
|
|
req.Header.Add("Accept-Charset", "utf-8")
|
|
switch method {
|
|
case "GET":
|
|
req.Header.Add("Accept", "*/*")
|
|
default:
|
|
req.Header.Add("Accept", "application/xml,text/xml")
|
|
}
|
|
|
|
if req.Body != nil {
|
|
defer req.Body.Close()
|
|
}
|
|
if fn != nil {
|
|
fn(req)
|
|
}
|
|
return HTTPClient.Do(req)
|
|
}
|
|
|
|
type WebDavResp struct {
|
|
Responses []struct {
|
|
Href string `xml:"href"`
|
|
Props []struct {
|
|
Name string `xml:"prop>displayname,omitempty"`
|
|
Type xml.Name `xml:"prop>resourcetype>collection,omitempty"`
|
|
Size int64 `xml:"prop>getcontentlength,omitempty"`
|
|
Modified string `xml:"prop>getlastmodified,omitempty"`
|
|
} `xml:"propstat"`
|
|
} `xml:"response"`
|
|
}
|
|
|
|
func encodeURL(path string) string {
|
|
p := url.PathEscape(path)
|
|
return strings.Replace(p, "%2F", "/", -1)
|
|
}
|
|
|
|
func decodeURL(path string) string {
|
|
str, err := url.PathUnescape(path)
|
|
if err != nil {
|
|
return path
|
|
}
|
|
return str
|
|
}
|