go-webdav/client.go

269 lines
5.5 KiB
Go
Raw Normal View History

2020-01-14 17:51:17 +00:00
package webdav
import (
"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
"time"
2020-01-14 17:51:17 +00:00
"github.com/emersion/go-webdav/internal"
)
// HTTPClient performs HTTP requests. It's implemented by *http.Client.
type HTTPClient interface {
Do(req *http.Request) (*http.Response, error)
}
type basicAuthHTTPClient struct {
c HTTPClient
username, password string
}
func (c *basicAuthHTTPClient) Do(req *http.Request) (*http.Response, error) {
req.SetBasicAuth(c.username, c.password)
return c.c.Do(req)
}
// HTTPClientWithBasicAuth returns an HTTP client that adds basic
// authentication to all outgoing requests. If c is nil, http.DefaultClient is
// used.
func HTTPClientWithBasicAuth(c HTTPClient, username, password string) HTTPClient {
if c == nil {
c = http.DefaultClient
}
return &basicAuthHTTPClient{c, username, password}
}
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 HTTPClient, endpoint string) (*Client, error) {
2020-01-14 17:51:17 +00:00
ic, err := internal.NewClient(c, endpoint)
if err != nil {
return nil, err
}
return &Client{ic}, nil
}
func (c *Client) FindCurrentUserPrincipal() (string, error) {
propfind := internal.NewPropNamePropFind(internal.CurrentUserPrincipalName)
// TODO: consider retrying on the root URI "/" if this fails, as suggested
// by the RFC?
resp, err := c.ic.PropFindFlat("", propfind)
if err != nil {
return "", err
2020-01-14 17:51:17 +00:00
}
var prop internal.CurrentUserPrincipal
if err := resp.DecodeProp(&prop); err != nil {
2020-01-14 17:51:17 +00:00
return "", err
}
if prop.Unauthenticated != nil {
return "", fmt.Errorf("webdav: unauthenticated")
}
2020-01-14 17:51:17 +00:00
return prop.Href.Path, nil
2020-01-14 17:51:17 +00:00
}
2020-01-21 17:41:46 +00:00
var fileInfoPropFind = internal.NewPropNamePropFind(
internal.ResourceTypeName,
internal.GetContentLengthName,
internal.GetLastModifiedName,
internal.GetContentTypeName,
internal.GetETagName,
)
func fileInfoFromResponse(resp *internal.Response) (*FileInfo, error) {
path, err := resp.Path()
2020-01-21 17:55:29 +00:00
if err != nil {
return nil, err
}
fi := &FileInfo{Path: path}
2020-01-21 17:55:29 +00:00
var resType internal.ResourceType
if err := resp.DecodeProp(&resType); err != nil {
return nil, err
}
2020-01-21 17:55:29 +00:00
if resType.Is(internal.CollectionName) {
fi.IsDir = true
2020-01-21 17:55:29 +00:00
} else {
var getLen internal.GetContentLength
2020-01-21 22:14:57 +00:00
if err := resp.DecodeProp(&getLen); err != nil {
2020-01-21 17:55:29 +00:00
return nil, err
}
var getType internal.GetContentType
if err := resp.DecodeProp(&getType); err != nil && !internal.IsNotFound(err) {
return nil, err
}
var getETag internal.GetETag
if err := resp.DecodeProp(&getETag); err != nil && !internal.IsNotFound(err) {
return nil, err
}
fi.Size = getLen.Length
fi.MIMEType = getType.Type
2020-02-03 20:48:31 +00:00
fi.ETag = string(getETag.ETag)
2020-01-21 17:55:29 +00:00
}
var getMod internal.GetLastModified
if err := resp.DecodeProp(&getMod); err != nil && !internal.IsNotFound(err) {
return nil, err
}
fi.ModTime = time.Time(getMod.LastModified)
2020-01-21 17:55:29 +00:00
return fi, nil
}
func (c *Client) Stat(name string) (*FileInfo, error) {
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, recursive bool) ([]FileInfo, error) {
depth := internal.DepthOne
if recursive {
depth = internal.DepthInfinity
}
2020-01-21 17:55:29 +00:00
ms, err := c.ic.PropFind(name, depth, fileInfoPropFind)
2020-01-21 17:55:29 +00:00
if err != nil {
return nil, err
}
l := make([]FileInfo, 0, len(ms.Responses))
2020-01-21 17:55:29 +00:00
for _, resp := range ms.Responses {
fi, err := fileInfoFromResponse(&resp)
if err != nil {
return l, err
}
l = append(l, *fi)
2020-01-21 17:55:29 +00:00
}
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() {
resp, err := c.ic.Do(req)
if err != nil {
done <- err
return
}
resp.Body.Close()
done <- nil
2020-01-21 20:32:43 +00:00
}()
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
}
resp, err := c.ic.Do(req)
if err != nil {
return err
}
resp.Body.Close()
return nil
2020-01-21 20:48:07 +00:00
}
2020-01-21 21:06:47 +00:00
func (c *Client) Mkdir(name string) error {
req, err := c.ic.NewRequest("MKCOL", name, nil)
if err != nil {
return err
}
resp, err := c.ic.Do(req)
if err != nil {
return err
}
resp.Body.Close()
return nil
2020-01-21 21:06:47 +00:00
}
2020-01-22 09:15:15 +00:00
2020-01-22 09:16:48 +00:00
func (c *Client) CopyAll(name, dest string, overwrite bool) error {
req, err := c.ic.NewRequest("COPY", name, nil)
if err != nil {
return err
}
req.Header.Set("Destination", c.ic.ResolveHref(dest).String())
2020-01-22 09:16:48 +00:00
req.Header.Set("Overwrite", internal.FormatOverwrite(overwrite))
resp, err := c.ic.Do(req)
if err != nil {
return err
}
resp.Body.Close()
return nil
2020-01-22 09:16:48 +00:00
}
2020-01-22 09:15:15 +00:00
func (c *Client) MoveAll(name, dest string, overwrite bool) error {
req, err := c.ic.NewRequest("MOVE", name, nil)
if err != nil {
return err
}
req.Header.Set("Destination", c.ic.ResolveHref(dest).String())
2020-01-22 09:15:15 +00:00
req.Header.Set("Overwrite", internal.FormatOverwrite(overwrite))
resp, err := c.ic.Do(req)
if err != nil {
return err
}
resp.Body.Close()
return nil
2020-01-22 09:15:15 +00:00
}