From 3beb076950711e119269003e26d41e28e86a123f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Tue, 14 Jan 2020 18:51:17 +0100 Subject: [PATCH] webdav: add very basic Client --- client.go | 63 ++++++++++++++++++++++++++++++++++++++++++++ elements.go | 5 ++++ internal/client.go | 63 ++++++++++++++++++++++++++++++++++++++++++++ internal/elements.go | 29 ++++++++++++++++++++ 4 files changed, 160 insertions(+) create mode 100644 client.go create mode 100644 elements.go create mode 100644 internal/client.go create mode 100644 internal/elements.go diff --git a/client.go b/client.go new file mode 100644 index 0000000..815b4a9 --- /dev/null +++ b/client.go @@ -0,0 +1,63 @@ +package webdav + +import ( + "encoding/xml" + "fmt" + "net/http" + "strings" + + "github.com/emersion/go-webdav/internal" +) + +type Client struct { + c *internal.Client +} + +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 +} + +func (c *Client) FindCurrentUserPrincipal() (string, error) { + r := strings.NewReader(` + + + + + +`) + + req, err := c.c.NewRequest("PROPFIND", "", r) + if err != nil { + return "", err + } + + req.Header.Add("Depth", "0") + req.Header.Add("Content-Type", "text/xml; charset=\"utf-8\"") + + resps, err := c.c.DoMultiStatus(req) + if err != nil { + return "", err + } + + if len(resps) != 1 { + return "", fmt.Errorf("expected exactly one response in multistatus, got %v", len(resps)) + } + resp := &resps[0] + + // TODO: handle propstats with errors + if len(resp.Propstats) != 1 { + return "", fmt.Errorf("expected exactly one propstat in response") + } + propstat := &resp.Propstats[0] + + var prop currentUserPrincipalProp + if err := xml.NewTokenDecoder(propstat.Prop.TokenReader()).Decode(&prop); err != nil { + return "", err + } + + return prop.Href, nil +} diff --git a/elements.go b/elements.go new file mode 100644 index 0000000..28e85d7 --- /dev/null +++ b/elements.go @@ -0,0 +1,5 @@ +package webdav + +type currentUserPrincipalProp struct { + Href string `xml:"current-user-principal>href"` +} diff --git a/internal/client.go b/internal/client.go new file mode 100644 index 0000000..3a83f74 --- /dev/null +++ b/internal/client.go @@ -0,0 +1,63 @@ +package internal + +import ( + "encoding/xml" + "fmt" + "io" + "net/http" + "net/url" + "path" +) + +type Client struct { + http *http.Client + endpoint *url.URL +} + +func NewClient(c *http.Client, endpoint string) (*Client, error) { + if c == nil { + c = http.DefaultClient + } + + u, err := url.Parse(endpoint) + if err != nil { + return nil, err + } + return &Client{c, u}, nil +} + +func (c *Client) NewRequest(method string, p string, body io.Reader) (*http.Request, error) { + u := url.URL{ + Scheme: c.endpoint.Scheme, + User: c.endpoint.User, + Host: c.endpoint.Host, + Path: path.Join(c.endpoint.Path, p), + } + return http.NewRequest(method, u.String(), body) +} + +func (c *Client) Do(req *http.Request) (*http.Response, error) { + // TODO: remove this quirk + req.SetBasicAuth("simon", "") + return c.http.Do(req) +} + +func (c *Client) DoMultiStatus(req *http.Request) ([]Response, error) { + resp, err := c.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusMultiStatus { + return nil, fmt.Errorf("HTTP multi-status request failed: %v", resp.Status) + } + + // TODO: the response can be quite large, support streaming Response elements + var ms multistatus + if err := xml.NewDecoder(resp.Body).Decode(&ms); err != nil { + return nil, err + } + + return ms.Responses, nil +} diff --git a/internal/elements.go b/internal/elements.go new file mode 100644 index 0000000..7f84e12 --- /dev/null +++ b/internal/elements.go @@ -0,0 +1,29 @@ +package internal + +import ( + "encoding/xml" +) + +// https://tools.ietf.org/html/rfc4918#section-14.16 +type multistatus struct { + XMLName xml.Name `xml:"DAV: multistatus"` + Responses []Response `xml:"DAV: response"` + // TODO: responsedescription? +} + +// https://tools.ietf.org/html/rfc4918#section-14.24 +type Response struct { + XMLName xml.Name `xml:"DAV: response"` + Href string `xml:"DAV: href"` + Propstats []Propstat `xml:"DAV: propstat"` + // TODO: (href*, status) + // TODO: error?, responsedescription? , location? +} + +// https://tools.ietf.org/html/rfc4918#section-14.22 +type Propstat struct { + XMLName xml.Name `xml:"DAV: propstat"` + Prop RawXMLValue `xml:"DAV: prop"` + Status string `xml:"DAV: status"` + // TODO: error?, responsedescription? +}