|
package gowebdav |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"io" |
|
"net/http" |
|
"path" |
|
"strings" |
|
) |
|
|
|
func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (req *http.Response, err error) { |
|
var r *http.Request |
|
var retryBuf io.Reader |
|
canRetry := true |
|
if body != nil { |
|
|
|
|
|
|
|
|
|
|
|
|
|
if sk, ok := body.(io.Seeker); ok { |
|
if _, err = sk.Seek(0, io.SeekStart); err != nil { |
|
return |
|
} |
|
retryBuf = body |
|
} else if method == http.MethodPut { |
|
canRetry = false |
|
} else { |
|
buff := &bytes.Buffer{} |
|
retryBuf = buff |
|
body = io.TeeReader(body, buff) |
|
} |
|
r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), body) |
|
} else { |
|
r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), nil) |
|
} |
|
|
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
for k, vals := range c.headers { |
|
for _, v := range vals { |
|
r.Header.Add(k, v) |
|
} |
|
} |
|
|
|
|
|
|
|
c.authMutex.Lock() |
|
auth := c.auth |
|
c.authMutex.Unlock() |
|
|
|
auth.Authorize(r, method, path) |
|
|
|
if intercept != nil { |
|
intercept(r) |
|
} |
|
|
|
if c.interceptor != nil { |
|
c.interceptor(method, r) |
|
} |
|
|
|
rs, err := c.c.Do(r) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
if rs.StatusCode == 401 && auth.Type() == "NoAuth" { |
|
wwwAuthenticateHeader := strings.ToLower(rs.Header.Get("Www-Authenticate")) |
|
|
|
if strings.Index(wwwAuthenticateHeader, "digest") > -1 { |
|
c.authMutex.Lock() |
|
c.auth = &DigestAuth{auth.User(), auth.Pass(), digestParts(rs)} |
|
c.authMutex.Unlock() |
|
} else if strings.Index(wwwAuthenticateHeader, "basic") > -1 { |
|
c.authMutex.Lock() |
|
c.auth = &BasicAuth{auth.User(), auth.Pass()} |
|
c.authMutex.Unlock() |
|
} else { |
|
return rs, newPathError("Authorize", c.root, rs.StatusCode) |
|
} |
|
|
|
|
|
|
|
if canRetry { |
|
return c.req(method, path, retryBuf, intercept) |
|
} |
|
} else if rs.StatusCode == 401 { |
|
return rs, newPathError("Authorize", c.root, rs.StatusCode) |
|
} |
|
|
|
return rs, err |
|
} |
|
|
|
func (c *Client) mkcol(path string) (status int, err error) { |
|
rs, err := c.req("MKCOL", path, nil, nil) |
|
if err != nil { |
|
return |
|
} |
|
defer rs.Body.Close() |
|
|
|
status = rs.StatusCode |
|
if status == 405 { |
|
status = 201 |
|
} |
|
|
|
return |
|
} |
|
|
|
func (c *Client) options(path string) (*http.Response, error) { |
|
return c.req("OPTIONS", path, nil, func(rq *http.Request) { |
|
rq.Header.Add("Depth", "0") |
|
}) |
|
} |
|
|
|
func (c *Client) propfind(path string, self bool, body string, resp interface{}, parse func(resp interface{}) error) error { |
|
rs, err := c.req("PROPFIND", path, strings.NewReader(body), func(rq *http.Request) { |
|
if self { |
|
rq.Header.Add("Depth", "0") |
|
} else { |
|
rq.Header.Add("Depth", "1") |
|
} |
|
rq.Header.Add("Content-Type", "application/xml;charset=UTF-8") |
|
rq.Header.Add("Accept", "application/xml,text/xml") |
|
rq.Header.Add("Accept-Charset", "utf-8") |
|
|
|
rq.Header.Add("Accept-Encoding", "") |
|
}) |
|
if err != nil { |
|
return err |
|
} |
|
defer rs.Body.Close() |
|
|
|
if rs.StatusCode != 207 { |
|
return newPathError("PROPFIND", path, rs.StatusCode) |
|
} |
|
|
|
return parseXML(rs.Body, resp, parse) |
|
} |
|
|
|
func (c *Client) doCopyMove( |
|
method string, |
|
oldpath string, |
|
newpath string, |
|
overwrite bool, |
|
) ( |
|
status int, |
|
r io.ReadCloser, |
|
err error, |
|
) { |
|
rs, err := c.req(method, oldpath, nil, func(rq *http.Request) { |
|
rq.Header.Add("Destination", PathEscape(Join(c.root, newpath))) |
|
if overwrite { |
|
rq.Header.Add("Overwrite", "T") |
|
} else { |
|
rq.Header.Add("Overwrite", "F") |
|
} |
|
}) |
|
if err != nil { |
|
return |
|
} |
|
status = rs.StatusCode |
|
r = rs.Body |
|
return |
|
} |
|
|
|
func (c *Client) copymove(method string, oldpath string, newpath string, overwrite bool) (err error) { |
|
s, data, err := c.doCopyMove(method, oldpath, newpath, overwrite) |
|
if err != nil { |
|
return |
|
} |
|
if data != nil { |
|
defer data.Close() |
|
} |
|
|
|
switch s { |
|
case 201, 204: |
|
return nil |
|
|
|
case 207: |
|
|
|
log(fmt.Sprintf(" TODO handle %s - %s multistatus result %s", method, oldpath, String(data))) |
|
|
|
case 409: |
|
err := c.createParentCollection(newpath) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
return c.copymove(method, oldpath, newpath, overwrite) |
|
} |
|
|
|
return newPathError(method, oldpath, s) |
|
} |
|
|
|
func (c *Client) put(path string, stream io.Reader, callback func(r *http.Request)) (status int, err error) { |
|
rs, err := c.req(http.MethodPut, path, stream, callback) |
|
if err != nil { |
|
return |
|
} |
|
defer rs.Body.Close() |
|
|
|
|
|
status = rs.StatusCode |
|
return |
|
} |
|
|
|
func (c *Client) createParentCollection(itemPath string) (err error) { |
|
parentPath := path.Dir(itemPath) |
|
if parentPath == "." || parentPath == "/" { |
|
return nil |
|
} |
|
|
|
return c.MkdirAll(parentPath, 0755) |
|
} |
|
|