Files
2025-01-13 17:41:29 +11:00

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
}