2020-01-14 17:51:17 +00:00
|
|
|
package webdav
|
|
|
|
|
|
|
|
import (
|
2020-01-20 09:56:25 +00:00
|
|
|
"fmt"
|
2020-01-21 17:47:29 +00:00
|
|
|
"io"
|
2020-01-20 12:17:19 +00:00
|
|
|
"net/http"
|
2020-01-21 17:41:46 +00:00
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"time"
|
2020-01-14 17:51:17 +00:00
|
|
|
|
|
|
|
"github.com/emersion/go-webdav/internal"
|
|
|
|
)
|
|
|
|
|
2020-01-21 20:01:18 +00:00
|
|
|
// Client provides access to a remote WebDAV filesystem.
|
2020-01-14 17:51:17 +00:00
|
|
|
type Client struct {
|
2020-01-21 17:41:46 +00:00
|
|
|
ic *internal.Client
|
2020-01-14 17:51:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func NewClient(c *http.Client, endpoint string) (*Client, error) {
|
|
|
|
ic, err := internal.NewClient(c, endpoint)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &Client{ic}, nil
|
|
|
|
}
|
|
|
|
|
2020-01-15 22:45:37 +00:00
|
|
|
func (c *Client) SetBasicAuth(username, password string) {
|
2020-01-21 17:41:46 +00:00
|
|
|
c.ic.SetBasicAuth(username, password)
|
2020-01-15 22:45:37 +00:00
|
|
|
}
|
|
|
|
|
2020-01-14 17:51:17 +00:00
|
|
|
func (c *Client) FindCurrentUserPrincipal() (string, error) {
|
2020-01-20 09:56:25 +00:00
|
|
|
propfind := internal.NewPropNamePropfind(internal.CurrentUserPrincipalName)
|
2020-01-14 19:00:54 +00:00
|
|
|
|
2020-01-21 17:41:46 +00:00
|
|
|
resp, err := c.ic.PropfindFlat("/", propfind)
|
2020-01-14 20:29:54 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2020-01-14 17:51:17 +00:00
|
|
|
}
|
|
|
|
|
2020-01-20 09:56:25 +00:00
|
|
|
var prop internal.CurrentUserPrincipal
|
2020-01-15 10:44:27 +00:00
|
|
|
if err := resp.DecodeProp(&prop); err != nil {
|
2020-01-14 17:51:17 +00:00
|
|
|
return "", err
|
|
|
|
}
|
2020-01-20 09:56:25 +00:00
|
|
|
if prop.Unauthenticated != nil {
|
|
|
|
return "", fmt.Errorf("webdav: unauthenticated")
|
|
|
|
}
|
2020-01-14 17:51:17 +00:00
|
|
|
|
|
|
|
return prop.Href, nil
|
|
|
|
}
|
2020-01-21 17:41:46 +00:00
|
|
|
|
|
|
|
type fileInfo struct {
|
|
|
|
filename string
|
|
|
|
size int64
|
|
|
|
modTime time.Time
|
|
|
|
isDir bool
|
|
|
|
}
|
|
|
|
|
2020-01-21 17:55:29 +00:00
|
|
|
func fileInfoFromResponse(resp *internal.Response) (*fileInfo, error) {
|
|
|
|
href, err := resp.Href()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
filename, _ := path.Split(href)
|
|
|
|
fi := &fileInfo{filename: filename}
|
|
|
|
|
|
|
|
var resType internal.ResourceType
|
|
|
|
if err := resp.DecodeProp(&resType); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if resType.Is(internal.CollectionName) {
|
|
|
|
fi.isDir = true
|
|
|
|
} else {
|
|
|
|
var getLen internal.GetContentLength
|
|
|
|
var getMod internal.GetLastModified
|
|
|
|
if err := resp.DecodeProp(&getLen, &getMod); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
fi.size = getLen.Length
|
|
|
|
fi.modTime = time.Time(getMod.LastModified)
|
|
|
|
}
|
|
|
|
|
|
|
|
return fi, nil
|
|
|
|
}
|
|
|
|
|
2020-01-21 17:41:46 +00:00
|
|
|
func (fi *fileInfo) Name() string {
|
|
|
|
return fi.filename
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fi *fileInfo) Size() int64 {
|
|
|
|
return fi.size
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fi *fileInfo) Mode() os.FileMode {
|
|
|
|
if fi.isDir {
|
|
|
|
return os.ModePerm | os.ModeDir
|
|
|
|
} else {
|
|
|
|
return os.ModePerm
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fi *fileInfo) ModTime() time.Time {
|
|
|
|
return fi.modTime
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fi *fileInfo) IsDir() bool {
|
|
|
|
return fi.isDir
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fi *fileInfo) Sys() interface{} {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-21 17:55:29 +00:00
|
|
|
// TODO: getetag, getcontenttype
|
|
|
|
var fileInfoPropfind = internal.NewPropNamePropfind(
|
|
|
|
internal.ResourceTypeName,
|
|
|
|
internal.GetContentLengthName,
|
|
|
|
internal.GetLastModifiedName,
|
|
|
|
)
|
|
|
|
|
2020-01-21 17:41:46 +00:00
|
|
|
func (c *Client) Stat(name string) (os.FileInfo, error) {
|
2020-01-21 17:55:29 +00:00
|
|
|
resp, err := c.ic.PropfindFlat(name, fileInfoPropfind)
|
2020-01-21 17:41:46 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-01-21 17:55:29 +00:00
|
|
|
return fileInfoFromResponse(resp)
|
2020-01-21 17:41:46 +00:00
|
|
|
}
|
2020-01-21 17:47:29 +00:00
|
|
|
|
|
|
|
func (c *Client) Open(name string) (io.ReadCloser, error) {
|
|
|
|
req, err := c.ic.NewRequest(http.MethodGet, name, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := c.ic.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp.Body, nil
|
|
|
|
}
|
2020-01-21 17:55:29 +00:00
|
|
|
|
|
|
|
func (c *Client) Readdir(name string) ([]os.FileInfo, error) {
|
|
|
|
// TODO: filter out the directory we're listing
|
|
|
|
|
|
|
|
ms, err := c.ic.Propfind(name, internal.DepthOne, fileInfoPropfind)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
l := make([]os.FileInfo, 0, len(ms.Responses))
|
|
|
|
for _, resp := range ms.Responses {
|
|
|
|
fi, err := fileInfoFromResponse(&resp)
|
|
|
|
if err != nil {
|
|
|
|
return l, err
|
|
|
|
}
|
|
|
|
l = append(l, fi)
|
|
|
|
}
|
|
|
|
|
|
|
|
return l, nil
|
|
|
|
}
|
2020-01-21 20:32:43 +00:00
|
|
|
|
|
|
|
type fileWriter struct {
|
2020-01-21 20:46:01 +00:00
|
|
|
pw *io.PipeWriter
|
2020-01-21 20:32:43 +00:00
|
|
|
done <-chan error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fw *fileWriter) Write(b []byte) (int, error) {
|
|
|
|
return fw.pw.Write(b)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fw *fileWriter) Close() error {
|
|
|
|
if err := fw.pw.Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return <-fw.done
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *Client) Create(name string) (io.WriteCloser, error) {
|
|
|
|
pr, pw := io.Pipe()
|
|
|
|
|
|
|
|
req, err := c.ic.NewRequest(http.MethodPut, name, pr)
|
|
|
|
if err != nil {
|
|
|
|
pw.Close()
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
done := make(chan error, 1)
|
|
|
|
go func() {
|
|
|
|
_, err := c.ic.Do(req)
|
|
|
|
done <- err
|
|
|
|
}()
|
|
|
|
|
|
|
|
return &fileWriter{pw, done}, nil
|
|
|
|
}
|
2020-01-21 20:48:07 +00:00
|
|
|
|
|
|
|
func (c *Client) RemoveAll(name string) error {
|
|
|
|
req, err := c.ic.NewRequest(http.MethodDelete, name, nil)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = c.ic.Do(req)
|
|
|
|
return err
|
|
|
|
}
|