94 lines
2.2 KiB
Go
94 lines
2.2 KiB
Go
package webdav
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
var ErrConflict = errors.New("webdav conflict")
|
|
|
|
type Version struct {
|
|
ETag string
|
|
}
|
|
|
|
type Client struct {
|
|
HTTPClient *http.Client
|
|
BaseURL string
|
|
Username string
|
|
Password string
|
|
}
|
|
|
|
func (c Client) Open(path string) ([]byte, Version, error) {
|
|
req, err := http.NewRequest(http.MethodGet, c.url(path), nil)
|
|
if err != nil {
|
|
return nil, Version{}, fmt.Errorf("build GET request: %w", err)
|
|
}
|
|
c.applyAuth(req)
|
|
|
|
resp, err := c.httpClient().Do(req)
|
|
if err != nil {
|
|
return nil, Version{}, fmt.Errorf("GET %s: %w", path, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, Version{}, fmt.Errorf("GET %s: unexpected status %d", path, resp.StatusCode)
|
|
}
|
|
|
|
content, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, Version{}, fmt.Errorf("read %s: %w", path, err)
|
|
}
|
|
|
|
return content, Version{ETag: resp.Header.Get("ETag")}, nil
|
|
}
|
|
|
|
func (c Client) Save(path string, content io.Reader, version Version) (Version, error) {
|
|
req, err := http.NewRequest(http.MethodPut, c.url(path), content)
|
|
if err != nil {
|
|
return Version{}, fmt.Errorf("build PUT request: %w", err)
|
|
}
|
|
c.applyAuth(req)
|
|
if version.ETag != "" {
|
|
req.Header.Set("If-Match", version.ETag)
|
|
}
|
|
|
|
resp, err := c.httpClient().Do(req)
|
|
if err != nil {
|
|
return Version{}, fmt.Errorf("PUT %s: %w", path, err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusPreconditionFailed {
|
|
return Version{}, ErrConflict
|
|
}
|
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusNoContent {
|
|
return Version{}, fmt.Errorf("PUT %s: unexpected status %d", path, resp.StatusCode)
|
|
}
|
|
|
|
return Version{ETag: resp.Header.Get("ETag")}, nil
|
|
}
|
|
|
|
func (c Client) httpClient() *http.Client {
|
|
if c.HTTPClient != nil {
|
|
return c.HTTPClient
|
|
}
|
|
return http.DefaultClient
|
|
}
|
|
|
|
func (c Client) applyAuth(req *http.Request) {
|
|
if c.Username == "" && c.Password == "" {
|
|
return
|
|
}
|
|
req.SetBasicAuth(c.Username, c.Password)
|
|
}
|
|
|
|
func (c Client) url(path string) string {
|
|
base := strings.TrimRight(c.BaseURL, "/")
|
|
path = strings.TrimLeft(path, "/")
|
|
return base + "/" + path
|
|
}
|