diff --git a/cmd/zrok/copy.go b/cmd/zrok/copy.go index 020574d7..58a1e4f1 100644 --- a/cmd/zrok/copy.go +++ b/cmd/zrok/copy.go @@ -7,7 +7,6 @@ import ( "github.com/openziti/zrok/sdk/golang/sdk" "github.com/openziti/zrok/tui" "github.com/openziti/zrok/util/sync" - "github.com/pkg/errors" "github.com/spf13/cobra" "net/url" ) @@ -36,9 +35,6 @@ func (cmd *copyCommand) run(_ *cobra.Command, args []string) { if err != nil { tui.Error(fmt.Sprintf("invalid source URL '%v'", args[0]), err) } - if sourceUrl.Scheme != "zrok" && sourceUrl.Scheme != "file" { - tui.Error("source URL must be 'file://' or 'zrok://", nil) - } targetStr := "file://." if len(args) == 2 { @@ -48,16 +44,6 @@ func (cmd *copyCommand) run(_ *cobra.Command, args []string) { if err != nil { tui.Error(fmt.Sprintf("invalid target URL '%v'", targetStr), err) } - if targetUrl.Scheme != "zrok" && targetUrl.Scheme != "file" { - tui.Error("target URL must be 'file://' or 'zrok://", nil) - } - - if sourceUrl.Scheme != "zrok" && targetUrl.Scheme != "zrok" { - tui.Error("either or must be a 'zrok://' URL", nil) - } - if targetUrl.Scheme != "file" && sourceUrl.Scheme != "file" { - tui.Error("either or must be a 'file://' URL", nil) - } root, err := environment.LoadRoot() if err != nil { @@ -77,12 +63,14 @@ func (cmd *copyCommand) run(_ *cobra.Command, args []string) { tui.Error("error creating access", err) } } - defer func() { - err := sdk.DeleteAccess(root, access) - if err != nil { - tui.Error("error deleting access", err) - } - }() + if access != nil { + defer func() { + err := sdk.DeleteAccess(root, access) + if err != nil { + tui.Error("error deleting access", err) + } + }() + } source, err := cmd.createTarget(sourceUrl, root) if err != nil { @@ -102,17 +90,14 @@ func (cmd *copyCommand) run(_ *cobra.Command, args []string) { func (cmd *copyCommand) createTarget(t *url.URL, root env_core.Root) (sync.Target, error) { switch t.Scheme { - case "zrok": + case "file": + return sync.NewFilesystemTarget(&sync.FilesystemTargetConfig{Root: t.Path}), nil + + default: target, err := sync.NewWebDAVTarget(&sync.WebDAVTargetConfig{URL: t, Username: "", Password: "", Root: root}) if err != nil { return nil, err } return target, nil - - case "file": - return sync.NewFilesystemTarget(&sync.FilesystemTargetConfig{Root: t.Path}), nil - - default: - return nil, errors.Errorf("invalid scheme") } } diff --git a/cmd/zrok/davtest.go b/cmd/zrok/davtest.go index 9a85923d..9d03168f 100644 --- a/cmd/zrok/davtest.go +++ b/cmd/zrok/davtest.go @@ -4,8 +4,9 @@ import ( "context" "github.com/openziti/zrok/util/sync/driveClient" "github.com/spf13/cobra" + "io" "net/http" - "time" + "os" ) func init() { @@ -20,7 +21,7 @@ func newDavtestCommand() *davtestCommand { cmd := &cobra.Command{ Use: "davtest", Short: "WebDAV testing wrapper", - Args: cobra.ExactArgs(2), + Args: cobra.ExactArgs(3), } command := &davtestCommand{cmd: cmd} cmd.Run = command.run @@ -32,7 +33,18 @@ func (cmd *davtestCommand) run(_ *cobra.Command, args []string) { if err != nil { panic(err) } - if err := client.Touch(context.Background(), args[1], time.Now().Add(-(24 * time.Hour))); err != nil { + ws, err := client.Open(context.Background(), args[1]) + if err != nil { panic(err) } + fs, err := os.Create(args[2]) + if err != nil { + panic(err) + } + _, err = io.Copy(fs, ws) + if err != nil { + panic(err) + } + ws.Close() + fs.Close() } diff --git a/util/sync/model.go b/util/sync/model.go index b1a6b043..8fca5ca0 100644 --- a/util/sync/model.go +++ b/util/sync/model.go @@ -15,7 +15,6 @@ type Object struct { type Target interface { Inventory() ([]*Object, error) - IsDir() bool ReadStream(path string) (io.ReadCloser, error) WriteStream(path string, stream io.Reader, mode os.FileMode) error SetModificationTime(path string, mtime time.Time) error diff --git a/util/sync/webdav.go b/util/sync/webdav.go index c349d38b..b9a0c871 100644 --- a/util/sync/webdav.go +++ b/util/sync/webdav.go @@ -1,14 +1,13 @@ package sync import ( - "fmt" + "context" "github.com/openziti/zrok/environment/env_core" - "github.com/openziti/zrok/util/sync/webdavClient" - "github.com/pkg/errors" + "github.com/openziti/zrok/util/sync/driveClient" "io" + "net/http" "net/url" "os" - "path/filepath" "time" ) @@ -21,90 +20,54 @@ type WebDAVTargetConfig struct { type WebDAVTarget struct { cfg *WebDAVTargetConfig - c *webdavClient.Client + dc *driveClient.Client isDir bool } func NewWebDAVTarget(cfg *WebDAVTargetConfig) (*WebDAVTarget, error) { - c, err := webdavClient.NewZrokClient(cfg.URL, cfg.Root, webdavClient.NewAutoAuth(cfg.Username, cfg.Password)) + dc, err := driveClient.NewClient(http.DefaultClient, cfg.URL.String()) if err != nil { return nil, err } - if err := c.Connect(); err != nil { - return nil, errors.Wrap(err, "error connecting to webdav target") - } - return &WebDAVTarget{cfg: cfg, c: c}, nil + return &WebDAVTarget{cfg: cfg, dc: dc}, nil } func (t *WebDAVTarget) Inventory() ([]*Object, error) { - fi, err := t.c.Stat("") - - if fi != nil && !fi.IsDir() { - t.isDir = false - return []*Object{{ - Path: fi.Name(), - Size: fi.Size(), - Modified: fi.ModTime(), - }}, nil - } - - t.isDir = true - tree, err := t.recurse("", nil) + fis, err := t.dc.Readdir(context.Background(), "", true) if err != nil { return nil, err } - return tree, nil -} - -func (t *WebDAVTarget) IsDir() bool { - return t.isDir -} - -func (t *WebDAVTarget) recurse(path string, tree []*Object) ([]*Object, error) { - files, err := t.c.ReadDir(path) - if err != nil { - return nil, err - } - for _, f := range files { - sub := filepath.ToSlash(filepath.Join(path, f.Name())) - if f.IsDir() { - tree, err = t.recurse(sub, tree) - if err != nil { - return nil, err - } - } else { - if v, ok := f.(webdavClient.File); ok { - tree = append(tree, &Object{ - Path: filepath.ToSlash(filepath.Join(path, f.Name())), - Size: v.Size(), - Modified: v.ModTime(), - ETag: v.ETag(), - }) - } + var objects []*Object + for _, fi := range fis { + if !fi.IsDir { + objects = append(objects, &Object{ + Path: fi.Path, + Size: fi.Size, + Modified: fi.ModTime, + ETag: fi.ETag, + }) } } - return tree, nil + return objects, nil } func (t *WebDAVTarget) ReadStream(path string) (io.ReadCloser, error) { - if t.isDir { - return t.c.ReadStream(path) + return t.dc.Open(context.Background(), path) +} + +func (t *WebDAVTarget) WriteStream(path string, rs io.Reader, _ os.FileMode) error { + ws, err := t.dc.Create(context.Background(), path) + if err != nil { + return err } - return t.c.ReadStream("") -} - -func (t *WebDAVTarget) WriteStream(path string, stream io.Reader, mode os.FileMode) error { - return t.c.WriteStream(path, stream, mode) -} - -func (t *WebDAVTarget) SetModificationTime(path string, mtime time.Time) error { - modtimeUnix := mtime.Unix() - body := "" + - "" + - fmt.Sprintf("%d", modtimeUnix) + - "" - if err := t.c.Proppatch(path, body, nil, nil); err != nil { + defer ws.Close() + _, err = io.Copy(ws, rs) + if err != nil { return err } return nil } + +func (t *WebDAVTarget) SetModificationTime(path string, mtime time.Time) error { + return t.dc.Touch(context.Background(), path, mtime) +} diff --git a/util/sync/webdavClient/LICENSE b/util/sync/webdavClient/LICENSE deleted file mode 100644 index a7cd4420..00000000 --- a/util/sync/webdavClient/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2014, Studio B12 GmbH -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -3. Neither the name of the copyright holder nor the names of its contributors - may be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/util/sync/webdavClient/Makefile b/util/sync/webdavClient/Makefile deleted file mode 100644 index 48dbb4e7..00000000 --- a/util/sync/webdavClient/Makefile +++ /dev/null @@ -1,42 +0,0 @@ -BIN := gowebdav -SRC := $(wildcard *.go) cmd/gowebdav/main.go - -all: test cmd - -cmd: ${BIN} - -${BIN}: ${SRC} - go build -o $@ ./cmd/gowebdav - -test: - go test -modfile=go_test.mod -v -short -cover ./... - -api: .go/bin/godoc2md - @sed '/^## API$$/,$$d' -i README.md - @echo '## API' >> README.md - @$< github.com/studio-b12/gowebdav | sed '/^$$/N;/^\n$$/D' |\ - sed '2d' |\ - sed 's/\/src\/github.com\/studio-b12\/gowebdav\//https:\/\/github.com\/studio-b12\/gowebdav\/blob\/master\//g' |\ - sed 's/\/src\/target\//https:\/\/github.com\/studio-b12\/gowebdav\/blob\/master\//g' |\ - sed 's/^#/##/g' >> README.md - -check: .go/bin/gocyclo - gofmt -w -s $(SRC) - @echo - .go/bin/gocyclo -over 15 . - @echo - go vet -modfile=go_test.mod ./... - - -.go/bin/godoc2md: - @mkdir -p $(@D) - @GOPATH="$(CURDIR)/.go" go install github.com/davecheney/godoc2md@latest - -.go/bin/gocyclo: - @mkdir -p $(@D) - @GOPATH="$(CURDIR)/.go" go install github.com/fzipp/gocyclo/cmd/gocyclo@latest - -clean: - @rm -f ${BIN} - -.PHONY: all cmd clean test api check diff --git a/util/sync/webdavClient/README.md b/util/sync/webdavClient/README.md deleted file mode 100644 index 6c9a795a..00000000 --- a/util/sync/webdavClient/README.md +++ /dev/null @@ -1,962 +0,0 @@ -# GoWebDAV - -[![Unit Tests Status](https://github.com/studio-b12/gowebdav/actions/workflows/tests.yml/badge.svg)](https://github.com/studio-b12/gowebdav/actions/workflows/tests.yml) -[![Build Artifacts Status](https://github.com/studio-b12/gowebdav/actions/workflows/artifacts.yml/badge.svg)](https://github.com/studio-b12/gowebdav/actions/workflows/artifacts.yml) -[![GoDoc](https://godoc.org/github.com/studio-b12/gowebdav?status.svg)](https://godoc.org/github.com/studio-b12/gowebdav) -[![Go Report Card](https://goreportcard.com/badge/github.com/studio-b12/gowebdav)](https://goreportcard.com/report/github.com/studio-b12/gowebdav) - -A pure Golang WebDAV client library that comes with -a [reference implementation](https://github.com/studio-b12/gowebdav/tree/master/cmd/gowebdav). - -## Features at a glance - -Our `gowebdav` library allows to perform following actions on the remote WebDAV server: - -* [create path](#create-path-on-a-webdav-server) -* [get files list](#get-files-list) -* [download file](#download-file-to-byte-array) -* [upload file](#upload-file-from-byte-array) -* [get information about specified file/folder](#get-information-about-specified-filefolder) -* [move file to another location](#move-file-to-another-location) -* [copy file to another location](#copy-file-to-another-location) -* [delete file](#delete-file) - -It also provides an [authentication API](#type-authenticator) that makes it easy to encapsulate and control complex -authentication challenges. -The default implementation negotiates the algorithm based on the user's preferences and the methods offered by the -remote server. - -Out-of-box authentication support for: - -* [BasicAuth](https://en.wikipedia.org/wiki/Basic_access_authentication) -* [DigestAuth](https://en.wikipedia.org/wiki/Digest_access_authentication) -* [MS-PASS](https://github.com/studio-b12/gowebdav/pull/70#issuecomment-1421713726) -* [WIP Kerberos](https://github.com/studio-b12/gowebdav/pull/71#issuecomment-1416465334) -* [WIP Bearer Token](https://github.com/studio-b12/gowebdav/issues/61) - -## Usage - -First of all you should create `Client` instance using `NewClient()` function: - -```go -root := "https://webdav.mydomain.me" -user := "user" -password := "password" - -c := gowebdav.NewClient(root, user, password) -c.Connect() -// kick of your work! -``` - -After you can use this `Client` to perform actions, described below. - -**NOTICE:** We will not check for errors in the examples, to focus you on the `gowebdav` library's code, but you should -do it in your code! - -### Create path on a WebDAV server - -```go -err := c.Mkdir("folder", 0644) -``` - -In case you want to create several folders you can use `c.MkdirAll()`: - -```go -err := c.MkdirAll("folder/subfolder/subfolder2", 0644) -``` - -### Get files list - -```go -files, _ := c.ReadDir("folder/subfolder") -for _, file := range files { - //notice that [file] has os.FileInfo type - fmt.Println(file.Name()) -} -``` - -### Download file to byte array - -```go -webdavFilePath := "folder/subfolder/file.txt" -localFilePath := "/tmp/webdav/file.txt" - -bytes, _ := c.Read(webdavFilePath) -os.WriteFile(localFilePath, bytes, 0644) -``` - -### Download file via reader - -Also you can use `c.ReadStream()` method: - -```go -webdavFilePath := "folder/subfolder/file.txt" -localFilePath := "/tmp/webdav/file.txt" - -reader, _ := c.ReadStream(webdavFilePath) - -file, _ := os.Create(localFilePath) -defer file.Close() - -io.Copy(file, reader) -``` - -### Upload file from byte array - -```go -webdavFilePath := "folder/subfolder/file.txt" -localFilePath := "/tmp/webdav/file.txt" - -bytes, _ := os.ReadFile(localFilePath) - -c.Write(webdavFilePath, bytes, 0644) -``` - -### Upload file via writer - -```go -webdavFilePath := "folder/subfolder/file.txt" -localFilePath := "/tmp/webdav/file.txt" - -file, _ := os.Open(localFilePath) -defer file.Close() - -c.WriteStream(webdavFilePath, file, 0644) -``` - -### Get information about specified file/folder - -```go -webdavFilePath := "folder/subfolder/file.txt" - -info := c.Stat(webdavFilePath) -//notice that [info] has os.FileInfo type -fmt.Println(info) -``` - -### Move file to another location - -```go -oldPath := "folder/subfolder/file.txt" -newPath := "folder/subfolder/moved.txt" -isOverwrite := true - -c.Rename(oldPath, newPath, isOverwrite) -``` - -### Copy file to another location - -```go -oldPath := "folder/subfolder/file.txt" -newPath := "folder/subfolder/file-copy.txt" -isOverwrite := true - -c.Copy(oldPath, newPath, isOverwrite) -``` - -### Delete file - -```go -webdavFilePath := "folder/subfolder/file.txt" - -c.Remove(webdavFilePath) -``` - -## Links - -More details about WebDAV server you can read from following resources: - -* [RFC 4918 - HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)](https://tools.ietf.org/html/rfc4918) -* [RFC 5689 - Extended MKCOL for Web Distributed Authoring and Versioning (WebDAV)](https://tools.ietf.org/html/rfc5689) -* [RFC 2616 - HTTP/1.1 Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html "HTTP/1.1 Status Code Definitions") -* [WebDav: Next Generation Collaborative Web Authoring By Lisa Dusseaul](https://books.google.de/books?isbn=0130652083 "WebDav: Next Generation Collaborative Web Authoring By Lisa Dusseault") - -**NOTICE**: RFC 2518 is obsoleted by RFC 4918 in June 2007 - -## Contributing - -All contributing are welcome. If you have any suggestions or find some bug - please create an Issue to let us make this -project better. We appreciate your help! - -## License - -This library is distributed under the BSD 3-Clause license found in -the [LICENSE](https://github.com/studio-b12/gowebdav/blob/master/LICENSE) file. - -## API - -`import "github.com/studio-b12/gowebdav"` - -* [Overview](#pkg-overview) -* [Index](#pkg-index) -* [Examples](#pkg-examples) -* [Subdirectories](#pkg-subdirectories) - -### Overview - -Package gowebdav is a WebDAV client library with a command line tool -included. - -### Index - -* [Constants](#pkg-constants) -* [Variables](#pkg-variables) -* [func FixSlash(s string) string](#FixSlash) -* [func FixSlashes(s string) string](#FixSlashes) -* [func IsErrCode(err error, code int) bool](#IsErrCode) -* [func IsErrNotFound(err error) bool](#IsErrNotFound) -* [func Join(path0 string, path1 string) string](#Join) -* [func NewPathError(op string, path string, statusCode int) error](#NewPathError) -* [func NewPathErrorErr(op string, path string, err error) error](#NewPathErrorErr) -* [func PathEscape(path string) string](#PathEscape) -* [func ReadConfig(uri, netrc string) (string, string)](#ReadConfig) -* [func String(r io.Reader) string](#String) -* [type AuthFactory](#AuthFactory) -* [type Authenticator](#Authenticator) - * [func NewDigestAuth(login, secret string, rs *http.Response) (Authenticator, error)](#NewDigestAuth) - * [func NewPassportAuth(c *http.Client, user, pw, partnerURL string, header *http.Header) (Authenticator, error)](#NewPassportAuth) -* [type Authorizer](#Authorizer) - * [func NewAutoAuth(login string, secret string) Authorizer](#NewAutoAuth) - * [func NewEmptyAuth() Authorizer](#NewEmptyAuth) - * [func NewPreemptiveAuth(auth Authenticator) Authorizer](#NewPreemptiveAuth) -* [type BasicAuth](#BasicAuth) - * [func (b *BasicAuth) Authorize(c *http.Client, rq *http.Request, path string) error](#BasicAuth.Authorize) - * [func (b *BasicAuth) Clone() Authenticator](#BasicAuth.Clone) - * [func (b *BasicAuth) Close() error](#BasicAuth.Close) - * [func (b *BasicAuth) String() string](#BasicAuth.String) - * [func (b *BasicAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)](#BasicAuth.Verify) -* [type Client](#Client) - * [func NewAuthClient(uri string, auth Authorizer) *Client](#NewAuthClient) - * [func NewClient(uri, user, pw string) *Client](#NewClient) - * [func (c *Client) Connect() error](#Client.Connect) - * [func (c *Client) Copy(oldpath, newpath string, overwrite bool) error](#Client.Copy) - * [func (c *Client) Mkdir(path string, _ os.FileMode) (err error)](#Client.Mkdir) - * [func (c *Client) MkdirAll(path string, _ os.FileMode) (err error)](#Client.MkdirAll) - * [func (c *Client) Read(path string) ([]byte, error)](#Client.Read) - * [func (c *Client) ReadDir(path string) ([]os.FileInfo, error)](#Client.ReadDir) - * [func (c *Client) ReadStream(path string) (io.ReadCloser, error)](#Client.ReadStream) - * [func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadCloser, error)](#Client.ReadStreamRange) - * [func (c *Client) Remove(path string) error](#Client.Remove) - * [func (c *Client) RemoveAll(path string) error](#Client.RemoveAll) - * [func (c *Client) Rename(oldpath, newpath string, overwrite bool) error](#Client.Rename) - * [func (c *Client) SetHeader(key, value string)](#Client.SetHeader) - * [func (c *Client) SetInterceptor(interceptor func(method string, rq *http.Request))](#Client.SetInterceptor) - * [func (c *Client) SetJar(jar http.CookieJar)](#Client.SetJar) - * [func (c *Client) SetTimeout(timeout time.Duration)](#Client.SetTimeout) - * [func (c *Client) SetTransport(transport http.RoundTripper)](#Client.SetTransport) - * [func (c *Client) Stat(path string) (os.FileInfo, error)](#Client.Stat) - * [func (c *Client) Write(path string, data []byte, _ os.FileMode) (err error)](#Client.Write) - * [func (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err error)](#Client.WriteStream) -* [type DigestAuth](#DigestAuth) - * [func (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) error](#DigestAuth.Authorize) - * [func (d *DigestAuth) Clone() Authenticator](#DigestAuth.Clone) - * [func (d *DigestAuth) Close() error](#DigestAuth.Close) - * [func (d *DigestAuth) String() string](#DigestAuth.String) - * [func (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)](#DigestAuth.Verify) -* [type File](#File) - * [func (f File) ContentType() string](#File.ContentType) - * [func (f File) ETag() string](#File.ETag) - * [func (f File) IsDir() bool](#File.IsDir) - * [func (f File) ModTime() time.Time](#File.ModTime) - * [func (f File) Mode() os.FileMode](#File.Mode) - * [func (f File) Name() string](#File.Name) - * [func (f File) Path() string](#File.Path) - * [func (f File) Size() int64](#File.Size) - * [func (f File) String() string](#File.String) - * [func (f File) Sys() interface{}](#File.Sys) -* [type PassportAuth](#PassportAuth) - * [func (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error](#PassportAuth.Authorize) - * [func (p *PassportAuth) Clone() Authenticator](#PassportAuth.Clone) - * [func (p *PassportAuth) Close() error](#PassportAuth.Close) - * [func (p *PassportAuth) String() string](#PassportAuth.String) - * [func (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)](#PassportAuth.Verify) -* [type StatusError](#StatusError) - * [func (se StatusError) Error() string](#StatusError.Error) - -##### Examples - -* [PathEscape](#example_PathEscape) - -##### Package files - -[auth.go](https://github.com/studio-b12/gowebdav/blob/master/auth.go) [basicAuth.go](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go) [client.go](https://github.com/studio-b12/gowebdav/blob/master/client.go) [digestAuth.go](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go) [doc.go](https://github.com/studio-b12/gowebdav/blob/master/doc.go) [errors.go](https://github.com/studio-b12/gowebdav/blob/master/errors.go) [file.go](https://github.com/studio-b12/gowebdav/blob/master/file.go) [netrc.go](https://github.com/studio-b12/gowebdav/blob/master/netrc.go) [passportAuth.go](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go) [requests.go](https://github.com/studio-b12/gowebdav/blob/master/requests.go) [utils.go](https://github.com/studio-b12/gowebdav/blob/master/utils.go) - -### Constants - -``` go -const XInhibitRedirect = "X-Gowebdav-Inhibit-Redirect" -``` - -### Variables - -``` go -var ErrAuthChanged = errors.New("authentication failed, change algorithm") -``` - -ErrAuthChanged must be returned from the Verify method as an error -to trigger a re-authentication / negotiation with a new authenticator. - -``` go -var ErrTooManyRedirects = errors.New("stopped after 10 redirects") -``` - -ErrTooManyRedirects will be used as return error if a request exceeds 10 redirects. - -### func [FixSlash](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=354:384#L23) - -``` go -func FixSlash(s string) string -``` - -FixSlash appends a trailing / to our string - -### func [FixSlashes](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=506:538#L31) - -``` go -func FixSlashes(s string) string -``` - -FixSlashes appends and prepends a / if they are missing - -### func [IsErrCode](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=740:780#L29) - -``` go -func IsErrCode(err error, code int) bool -``` - -IsErrCode returns true if the given error -is an os.PathError wrapping a StatusError -with the given status code. - -### func [IsErrNotFound](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=972:1006#L39) - -``` go -func IsErrNotFound(err error) bool -``` - -IsErrNotFound is shorthand for IsErrCode -for status 404. - -### func [Join](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=639:683#L40) - -``` go -func Join(path0 string, path1 string) string -``` - -Join joins two paths - -### func [NewPathError](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=1040:1103#L43) - -``` go -func NewPathError(op string, path string, statusCode int) error -``` - -### func [NewPathErrorErr](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=1194:1255#L51) - -``` go -func NewPathErrorErr(op string, path string, err error) error -``` - -### func [PathEscape](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=153:188#L14) - -``` go -func PathEscape(path string) string -``` - -PathEscape escapes all segments of a given path - -### func [ReadConfig](https://github.com/studio-b12/gowebdav/blob/master/netrc.go?s=428:479#L27) - -``` go -func ReadConfig(uri, netrc string) (string, string) -``` - -ReadConfig reads login and password configuration from ~/.netrc -machine foo.com login username password 123456 - -### func [String](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=813:844#L45) - -``` go -func String(r io.Reader) string -``` - -String pulls a string out of our io.Reader - -### type [AuthFactory](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=150:251#L13) - -``` go -type AuthFactory func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) -``` - -AuthFactory prototype function to create a new Authenticator - -### type [Authenticator](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=2155:2695#L56) - -``` go -type Authenticator interface { - // Authorizes a request. Usually by adding some authorization headers. - Authorize(c *http.Client, rq *http.Request, path string) error - // Verifies the response if the authorization was successful. - // May trigger some round trips to pass the authentication. - // May also trigger a new Authenticator negotiation by returning `ErrAuthChenged` - Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) - // Creates a copy of the underlying Authenticator. - Clone() Authenticator - io.Closer -} -``` - -A Authenticator implements a specific way to authorize requests. -Each request is bound to a separate Authenticator instance. - -The authentication flow itself is broken down into `Authorize` -and `Verify` steps. The former method runs before, and the latter -runs after the `Request` is submitted. -This makes it easy to encapsulate and control complex -authentication challenges. - -Some authentication flows causing authentication round trips, -which can be archived by returning the `redo` of the Verify -method. `True` restarts the authentication process for the -current action: A new `Request` is spawned, which must be -authorized, sent, and re-verified again, until the action -is successfully submitted. -The preferred way is to handle the authentication ping-pong -within `Verify`, and then `redo` with fresh credentials. - -The result of the `Verify` method can also trigger an -`Authenticator` change by returning the `ErrAuthChanged` -as an error. Depending on the `Authorizer` this may trigger -an `Authenticator` negotiation. - -Set the `XInhibitRedirect` header to '1' in the `Authorize` -method to get control over request redirection. -Attention! You must handle the incoming request yourself. - -To store a shared session state the `Clone` method **must** -return a new instance, initialized with the shared state. - -#### func [NewDigestAuth](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=324:406#L21) - -``` go -func NewDigestAuth(login, secret string, rs *http.Response) (Authenticator, error) -``` - -NewDigestAuth creates a new instance of our Digest Authenticator - -#### func [NewPassportAuth](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=386:495#L21) - -``` go -func NewPassportAuth(c *http.Client, user, pw, partnerURL string, header *http.Header) (Authenticator, error) -``` - -constructor for PassportAuth creates a new PassportAuth object and -automatically authenticates against the given partnerURL - -### type [Authorizer](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=349:764#L17) - -``` go -type Authorizer interface { - // Creates a new Authenticator Shim per request. - // It may track request related states and perform payload buffering - // for authentication round trips. - // The underlying Authenticator will perform the real authentication. - NewAuthenticator(body io.Reader) (Authenticator, io.Reader) - // Registers a new Authenticator factory to a key. - AddAuthenticator(key string, fn AuthFactory) -} -``` - -Authorizer our Authenticator factory which creates an -`Authenticator` per action/request. - -#### func [NewAutoAuth](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=3789:3845#L109) - -``` go -func NewAutoAuth(login string, secret string) Authorizer -``` - -NewAutoAuth creates an auto Authenticator factory. -It negotiates the default authentication method -based on the order of the registered Authenticators -and the remotely offered authentication methods. -First In, First Out. - -#### func [NewEmptyAuth](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=4694:4724#L132) - -``` go -func NewEmptyAuth() Authorizer -``` - -NewEmptyAuth creates an empty Authenticator factory -The order of adding the Authenticator matters. -First In, First Out. -It offers the `NewAutoAuth` features. - -#### func [NewPreemptiveAuth](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=5300:5353#L148) - -``` go -func NewPreemptiveAuth(auth Authenticator) Authorizer -``` - -NewPreemptiveAuth creates a preemptive Authenticator -The preemptive authorizer uses the provided Authenticator -for every request regardless of any `Www-Authenticate` header. - -It may only have one authentication method, -so calling `AddAuthenticator` **will panic**! - -Look out!! This offers the skinniest and slickest implementation -without any synchronisation!! -Still applicable with `BasicAuth` within go routines. - -### type [BasicAuth](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=94:145#L9) - -``` go -type BasicAuth struct { - // contains filtered or unexported fields -} - -``` - -BasicAuth structure holds our credentials - -#### func (\*BasicAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=180:262#L15) - -``` go -func (b *BasicAuth) Authorize(c *http.Client, rq *http.Request, path string) error -``` - -Authorize the current request - -#### func (\*BasicAuth) [Clone](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=666:707#L34) - -``` go -func (b *BasicAuth) Clone() Authenticator -``` - -Clone creates a Copy of itself - -#### func (\*BasicAuth) [Close](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=581:614#L29) - -``` go -func (b *BasicAuth) Close() error -``` - -Close cleans up all resources - -#### func (\*BasicAuth) [String](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=778:813#L40) - -``` go -func (b *BasicAuth) String() string -``` - -String toString - -#### func (\*BasicAuth) [Verify](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=352:449#L21) - -``` go -func (b *BasicAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) -``` - -Verify verifies if the authentication - -### type [Client](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=220:388#L19) - -``` go -type Client struct { - // contains filtered or unexported fields -} - -``` - -Client defines our structure - -#### func [NewAuthClient](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=608:663#L33) - -``` go -func NewAuthClient(uri string, auth Authorizer) *Client -``` - -NewAuthClient creates a new client instance with a custom Authorizer - -#### func [NewClient](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=436:480#L28) - -``` go -func NewClient(uri, user, pw string) *Client -``` - -NewClient creates a new instance of client - -#### func (\*Client) [Connect](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1829:1861#L74) - -``` go -func (c *Client) Connect() error -``` - -Connect connects to our dav server - -#### func (\*Client) [Copy](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6815:6883#L310) - -``` go -func (c *Client) Copy(oldpath, newpath string, overwrite bool) error -``` - -Copy copies a file from A to B - -#### func (\*Client) [Mkdir](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5790:5852#L259) - -``` go -func (c *Client) Mkdir(path string, _ os.FileMode) (err error) -``` - -Mkdir makes a directory - -#### func (\*Client) [MkdirAll](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6065:6130#L273) - -``` go -func (c *Client) MkdirAll(path string, _ os.FileMode) (err error) -``` - -MkdirAll like mkdir -p, but for webdav - -#### func (\*Client) [Read](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6989:7039#L315) - -``` go -func (c *Client) Read(path string) ([]byte, error) -``` - -Read reads the contents of a remote file - -#### func (\*Client) [ReadDir](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=2855:2915#L117) - -``` go -func (c *Client) ReadDir(path string) ([]os.FileInfo, error) -``` - -ReadDir reads the contents of a remote directory - -#### func (\*Client) [ReadStream](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=7350:7413#L333) - -``` go -func (c *Client) ReadStream(path string) (io.ReadCloser, error) -``` - -ReadStream reads the stream for a given path - -#### func (\*Client) [ReadStreamRange](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=8162:8252#L355) - -``` go -func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadCloser, error) -``` - -ReadStreamRange reads the stream representing a subset of bytes for a given path, -utilizing HTTP Range Requests if the server supports it. -The range is expressed as offset from the start of the file and length, for example -offset=10, length=10 will return bytes 10 through 19. - -If the server does not support partial content requests and returns full content instead, -this function will emulate the behavior by skipping `offset` bytes and limiting the result -to `length`. - -#### func (\*Client) [Remove](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5296:5338#L236) - -``` go -func (c *Client) Remove(path string) error -``` - -Remove removes a remote file - -#### func (\*Client) [RemoveAll](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5404:5449#L241) - -``` go -func (c *Client) RemoveAll(path string) error -``` - -RemoveAll removes remote files - -#### func (\*Client) [Rename](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6649:6719#L305) - -``` go -func (c *Client) Rename(oldpath, newpath string, overwrite bool) error -``` - -Rename moves a file from A to B - -#### func (\*Client) [SetHeader](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1092:1137#L49) - -``` go -func (c *Client) SetHeader(key, value string) -``` - -SetHeader lets us set arbitrary headers for a given client - -#### func (\*Client) [SetInterceptor](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1244:1326#L54) - -``` go -func (c *Client) SetInterceptor(interceptor func(method string, rq *http.Request)) -``` - -SetInterceptor lets us set an arbitrary interceptor for a given client - -#### func (\*Client) [SetJar](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1727:1770#L69) - -``` go -func (c *Client) SetJar(jar http.CookieJar) -``` - -SetJar exposes the ability to set a cookie jar to the client. - -#### func (\*Client) [SetTimeout](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1428:1478#L59) - -``` go -func (c *Client) SetTimeout(timeout time.Duration) -``` - -SetTimeout exposes the ability to set a time limit for requests - -#### func (\*Client) [SetTransport](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1571:1629#L64) - -``` go -func (c *Client) SetTransport(transport http.RoundTripper) -``` - -SetTransport exposes the ability to define custom transports - -#### func (\*Client) [Stat](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=4241:4296#L184) - -``` go -func (c *Client) Stat(path string) (os.FileInfo, error) -``` - -Stat returns the file stats for a specified path - -#### func (\*Client) [Write](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=9272:9347#L389) - -``` go -func (c *Client) Write(path string, data []byte, _ os.FileMode) (err error) -``` - -Write writes data to a given path - -#### func (\*Client) [WriteStream](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=9771:9857#L419) - -``` go -func (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err error) -``` - -WriteStream writes a stream - -### type [DigestAuth](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=157:254#L14) - -``` go -type DigestAuth struct { - // contains filtered or unexported fields -} - -``` - -DigestAuth structure holds our credentials - -#### func (\*DigestAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=525:608#L26) - -``` go -func (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) error -``` - -Authorize the current request - -#### func (\*DigestAuth) [Clone](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=1228:1270#L49) - -``` go -func (d *DigestAuth) Clone() Authenticator -``` - -Clone creates a copy of itself - -#### func (\*DigestAuth) [Close](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=1142:1176#L44) - -``` go -func (d *DigestAuth) Close() error -``` - -Close cleans up all resources - -#### func (\*DigestAuth) [String](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=1466:1502#L58) - -``` go -func (d *DigestAuth) String() string -``` - -String toString - -#### func (\*DigestAuth) [Verify](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=912:1010#L36) - -``` go -func (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) -``` - -Verify checks for authentication issues and may trigger a re-authentication - -### type [File](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=93:253#L10) - -``` go -type File struct { - // contains filtered or unexported fields -} - -``` - -File is our structure for a given file - -#### func (File) [ContentType](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=476:510#L31) - -``` go -func (f File) ContentType() string -``` - -ContentType returns the content type of a file - -#### func (File) [ETag](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=929:956#L56) - -``` go -func (f File) ETag() string -``` - -ETag returns the ETag of a file - -#### func (File) [IsDir](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=1035:1061#L61) - -``` go -func (f File) IsDir() bool -``` - -IsDir let us see if a given file is a directory or not - -#### func (File) [ModTime](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=836:869#L51) - -``` go -func (f File) ModTime() time.Time -``` - -ModTime returns the modified time of a file - -#### func (File) [Mode](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=665:697#L41) - -``` go -func (f File) Mode() os.FileMode -``` - -Mode will return the mode of a given file - -#### func (File) [Name](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=378:405#L26) - -``` go -func (f File) Name() string -``` - -Name returns the name of a file - -#### func (File) [Path](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=295:322#L21) - -``` go -func (f File) Path() string -``` - -Path returns the full path of a file - -#### func (File) [Size](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=573:599#L36) - -``` go -func (f File) Size() int64 -``` - -Size returns the size of a file - -#### func (File) [String](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=1183:1212#L71) - -``` go -func (f File) String() string -``` - -String lets us see file information - -#### func (File) [Sys](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=1095:1126#L66) - -``` go -func (f File) Sys() interface{} -``` - -Sys ???? - -### type [PassportAuth](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=125:254#L12) - -``` go -type PassportAuth struct { - // contains filtered or unexported fields -} - -``` - -PassportAuth structure holds our credentials - -#### func (\*PassportAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=690:775#L32) - -``` go -func (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error -``` - -Authorize the current request - -#### func (\*PassportAuth) [Clone](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=1701:1745#L69) - -``` go -func (p *PassportAuth) Clone() Authenticator -``` - -Clone creates a Copy of itself - -#### func (\*PassportAuth) [Close](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=1613:1649#L64) - -``` go -func (p *PassportAuth) Close() error -``` - -Close cleans up all resources - -#### func (\*PassportAuth) [String](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=2048:2086#L83) - -``` go -func (p *PassportAuth) String() string -``` - -String toString - -#### func (\*PassportAuth) [Verify](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=1075:1175#L46) - -``` go -func (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) -``` - -Verify verifies if the authentication is good - -### type [StatusError](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=499:538#L18) - -``` go -type StatusError struct { - Status int -} - -``` - -StatusError implements error and wraps -an erroneous status code. - -#### func (StatusError) [Error](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=540:576#L22) - -``` go -func (se StatusError) Error() string -``` - -- - - -Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) diff --git a/util/sync/webdavClient/auth.go b/util/sync/webdavClient/auth.go deleted file mode 100644 index 32d76168..00000000 --- a/util/sync/webdavClient/auth.go +++ /dev/null @@ -1,409 +0,0 @@ -package webdavClient - -import ( - "bytes" - "errors" - "io" - "net/http" - "strings" - "sync" -) - -// AuthFactory prototype function to create a new Authenticator -type AuthFactory func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) - -// Authorizer our Authenticator factory which creates an -// `Authenticator` per action/request. -type Authorizer interface { - // Creates a new Authenticator Shim per request. - // It may track request related states and perform payload buffering - // for authentication round trips. - // The underlying Authenticator will perform the real authentication. - NewAuthenticator(body io.Reader) (Authenticator, io.Reader) - // Registers a new Authenticator factory to a key. - AddAuthenticator(key string, fn AuthFactory) -} - -// A Authenticator implements a specific way to authorize requests. -// Each request is bound to a separate Authenticator instance. -// -// The authentication flow itself is broken down into `Authorize` -// and `Verify` steps. The former method runs before, and the latter -// runs after the `Request` is submitted. -// This makes it easy to encapsulate and control complex -// authentication challenges. -// -// Some authentication flows causing authentication round trips, -// which can be archived by returning the `redo` of the Verify -// method. `True` restarts the authentication process for the -// current action: A new `Request` is spawned, which must be -// authorized, sent, and re-verified again, until the action -// is successfully submitted. -// The preferred way is to handle the authentication ping-pong -// within `Verify`, and then `redo` with fresh credentials. -// -// The result of the `Verify` method can also trigger an -// `Authenticator` change by returning the `ErrAuthChanged` -// as an error. Depending on the `Authorizer` this may trigger -// an `Authenticator` negotiation. -// -// Set the `XInhibitRedirect` header to '1' in the `Authorize` -// method to get control over request redirection. -// Attention! You must handle the incoming request yourself. -// -// To store a shared session state the `Clone` method **must** -// return a new instance, initialized with the shared state. -type Authenticator interface { - // Authorizes a request. Usually by adding some authorization headers. - Authorize(c *http.Client, rq *http.Request, path string) error - // Verifies the response if the authorization was successful. - // May trigger some round trips to pass the authentication. - // May also trigger a new Authenticator negotiation by returning `ErrAuthChenged` - Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) - // Creates a copy of the underlying Authenticator. - Clone() Authenticator - io.Closer -} - -type authfactory struct { - key string - create AuthFactory -} - -// authorizer structure holds our Authenticator create functions -type authorizer struct { - factories []authfactory - defAuthMux sync.Mutex - defAuth Authenticator -} - -// preemptiveAuthorizer structure holds the preemptive Authenticator -type preemptiveAuthorizer struct { - auth Authenticator -} - -// authShim structure that wraps the real Authenticator -type authShim struct { - factory AuthFactory - body io.Reader - auth Authenticator -} - -// negoAuth structure holds the authenticators that are going to be negotiated -type negoAuth struct { - auths []Authenticator - setDefaultAuthenticator func(auth Authenticator) -} - -// nullAuth initializes the whole authentication flow -type nullAuth struct{} - -// noAuth structure to perform no authentication at all -type noAuth struct{} - -// NewAutoAuth creates an auto Authenticator factory. -// It negotiates the default authentication method -// based on the order of the registered Authenticators -// and the remotely offered authentication methods. -// First In, First Out. -func NewAutoAuth(login string, secret string) Authorizer { - fmap := make([]authfactory, 0) - az := &authorizer{factories: fmap, defAuthMux: sync.Mutex{}, defAuth: &nullAuth{}} - - az.AddAuthenticator("basic", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) { - return &BasicAuth{user: login, pw: secret}, nil - }) - - az.AddAuthenticator("digest", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) { - return NewDigestAuth(login, secret, rs) - }) - - az.AddAuthenticator("passport1.4", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) { - return NewPassportAuth(c, login, secret, rs.Request.URL.String(), &rs.Header) - }) - - return az -} - -// NewEmptyAuth creates an empty Authenticator factory -// The order of adding the Authenticator matters. -// First In, First Out. -// It offers the `NewAutoAuth` features. -func NewEmptyAuth() Authorizer { - fmap := make([]authfactory, 0) - az := &authorizer{factories: fmap, defAuthMux: sync.Mutex{}, defAuth: &nullAuth{}} - return az -} - -// NewPreemptiveAuth creates a preemptive Authenticator -// The preemptive authorizer uses the provided Authenticator -// for every request regardless of any `Www-Authenticate` header. -// -// It may only have one authentication method, -// so calling `AddAuthenticator` **will panic**! -// -// Look out!! This offers the skinniest and slickest implementation -// without any synchronisation!! -// Still applicable with `BasicAuth` within go routines. -func NewPreemptiveAuth(auth Authenticator) Authorizer { - return &preemptiveAuthorizer{auth: auth} -} - -// NewAuthenticator creates an Authenticator (Shim) per request -func (a *authorizer) NewAuthenticator(body io.Reader) (Authenticator, io.Reader) { - var retryBuf = body - if body != nil { - // If the authorization fails, we will need to restart reading - // from the passed body stream. - // When body is seekable, use seek to reset the streams - // cursor to the start. - // Otherwise, copy the stream into a buffer while uploading - // and use the buffers content on retry. - if _, ok := retryBuf.(io.Seeker); ok { - body = io.NopCloser(body) - } else { - buff := &bytes.Buffer{} - retryBuf = buff - body = io.TeeReader(body, buff) - } - } - a.defAuthMux.Lock() - defAuth := a.defAuth.Clone() - a.defAuthMux.Unlock() - - return &authShim{factory: a.factory, body: retryBuf, auth: defAuth}, body -} - -// AddAuthenticator appends the AuthFactory to our factories. -// It converts the key to lower case and preserves the order. -func (a *authorizer) AddAuthenticator(key string, fn AuthFactory) { - key = strings.ToLower(key) - for _, f := range a.factories { - if f.key == key { - panic("Authenticator exists: " + key) - } - } - a.factories = append(a.factories, authfactory{key, fn}) -} - -// factory picks all valid Authenticators based on Www-Authenticate headers -func (a *authorizer) factory(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) { - headers := rs.Header.Values("Www-Authenticate") - if len(headers) > 0 { - auths := make([]Authenticator, 0) - for _, f := range a.factories { - for _, header := range headers { - headerLower := strings.ToLower(header) - if strings.Contains(headerLower, f.key) { - rs.Header.Set("Www-Authenticate", header) - if auth, err = f.create(c, rs, path); err == nil { - auths = append(auths, auth) - break - } - } - } - } - - switch len(auths) { - case 0: - return nil, NewPathError("NoAuthenticator", path, rs.StatusCode) - case 1: - auth = auths[0] - default: - auth = &negoAuth{auths: auths, setDefaultAuthenticator: a.setDefaultAuthenticator} - } - } else { - auth = &noAuth{} - } - - a.setDefaultAuthenticator(auth) - - return auth, nil -} - -// setDefaultAuthenticator sets the default Authenticator -func (a *authorizer) setDefaultAuthenticator(auth Authenticator) { - a.defAuthMux.Lock() - a.defAuth.Close() - a.defAuth = auth - a.defAuthMux.Unlock() -} - -// Authorize the current request -func (s *authShim) Authorize(c *http.Client, rq *http.Request, path string) error { - if err := s.auth.Authorize(c, rq, path); err != nil { - return err - } - body := s.body - rq.GetBody = func() (io.ReadCloser, error) { - if body != nil { - if sk, ok := body.(io.Seeker); ok { - if _, err := sk.Seek(0, io.SeekStart); err != nil { - return nil, err - } - } - return io.NopCloser(body), nil - } - return nil, nil - } - return nil -} - -// Verify checks for authentication issues and may trigger a re-authentication. -// Catches AlgoChangedErr to update the current Authenticator -func (s *authShim) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { - redo, err = s.auth.Verify(c, rs, path) - if err != nil && errors.Is(err, ErrAuthChanged) { - if auth, aerr := s.factory(c, rs, path); aerr == nil { - s.auth.Close() - s.auth = auth - return true, nil - } else { - return false, aerr - } - } - return -} - -// Close closes all resources -func (s *authShim) Close() error { - s.auth.Close() - s.auth, s.factory = nil, nil - if s.body != nil { - if closer, ok := s.body.(io.Closer); ok { - return closer.Close() - } - } - return nil -} - -// It's not intend to Clone the shim -// therefore it returns a noAuth instance -func (s *authShim) Clone() Authenticator { - return &noAuth{} -} - -// String toString -func (s *authShim) String() string { - return "AuthShim" -} - -// Authorize authorizes the current request with the top most Authorizer -func (n *negoAuth) Authorize(c *http.Client, rq *http.Request, path string) error { - if len(n.auths) == 0 { - return NewPathError("NoAuthenticator", path, 400) - } - return n.auths[0].Authorize(c, rq, path) -} - -// Verify verifies the authentication and selects the next one based on the result -func (n *negoAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { - if len(n.auths) == 0 { - return false, NewPathError("NoAuthenticator", path, 400) - } - redo, err = n.auths[0].Verify(c, rs, path) - if err != nil { - if len(n.auths) > 1 { - n.auths[0].Close() - n.auths = n.auths[1:] - return true, nil - } - } else if redo { - return - } else { - auth := n.auths[0] - n.auths = n.auths[1:] - n.setDefaultAuthenticator(auth) - return - } - - return false, NewPathError("NoAuthenticator", path, rs.StatusCode) -} - -// Close will close the underlying authenticators. -func (n *negoAuth) Close() error { - for _, a := range n.auths { - a.Close() - } - n.setDefaultAuthenticator = nil - return nil -} - -// Clone clones the underlying authenticators. -func (n *negoAuth) Clone() Authenticator { - auths := make([]Authenticator, len(n.auths)) - for i, e := range n.auths { - auths[i] = e.Clone() - } - return &negoAuth{auths: auths, setDefaultAuthenticator: n.setDefaultAuthenticator} -} - -func (n *negoAuth) String() string { - return "NegoAuth" -} - -// Authorize the current request -func (n *noAuth) Authorize(c *http.Client, rq *http.Request, path string) error { - return nil -} - -// Verify checks for authentication issues and may trigger a re-authentication -func (n *noAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { - if "" != rs.Header.Get("Www-Authenticate") { - err = ErrAuthChanged - } - return -} - -// Close closes all resources -func (n *noAuth) Close() error { - return nil -} - -// Clone creates a copy of itself -func (n *noAuth) Clone() Authenticator { - // no copy due to read only access - return n -} - -// String toString -func (n *noAuth) String() string { - return "NoAuth" -} - -// Authorize the current request -func (n *nullAuth) Authorize(c *http.Client, rq *http.Request, path string) error { - rq.Header.Set(XInhibitRedirect, "1") - return nil -} - -// Verify checks for authentication issues and may trigger a re-authentication -func (n *nullAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { - return true, ErrAuthChanged -} - -// Close closes all resources -func (n *nullAuth) Close() error { - return nil -} - -// Clone creates a copy of itself -func (n *nullAuth) Clone() Authenticator { - // no copy due to read only access - return n -} - -// String toString -func (n *nullAuth) String() string { - return "NullAuth" -} - -// NewAuthenticator creates an Authenticator (Shim) per request -func (b *preemptiveAuthorizer) NewAuthenticator(body io.Reader) (Authenticator, io.Reader) { - return b.auth.Clone(), body -} - -// AddAuthenticator Will PANIC because it may only have a single authentication method -func (b *preemptiveAuthorizer) AddAuthenticator(key string, fn AuthFactory) { - panic("You're funny! A preemptive authorizer may only have a single authentication method") -} diff --git a/util/sync/webdavClient/auth_test.go b/util/sync/webdavClient/auth_test.go deleted file mode 100644 index d12c0f0d..00000000 --- a/util/sync/webdavClient/auth_test.go +++ /dev/null @@ -1,62 +0,0 @@ -package webdavClient - -import ( - "bytes" - "net/http" - "strings" - "testing" -) - -func TestEmptyAuth(t *testing.T) { - auth := NewEmptyAuth() - srv, _, _ := newAuthSrv(t, basicAuth) - defer srv.Close() - cli := NewAuthClient(srv.URL, auth) - if err := cli.Connect(); err == nil { - t.Fatalf("got nil want error") - } -} - -func TestRedirectAuthWIP(t *testing.T) { - hasPassedAuthServer := false - authHandler := func(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if user, passwd, ok := r.BasicAuth(); ok { - if user == "user" && passwd == "password" { - hasPassedAuthServer = true - w.WriteHeader(200) - return - } - } - w.Header().Set("Www-Authenticate", `Basic realm="x"`) - w.WriteHeader(401) - } - } - - psrv, _, _ := newAuthSrv(t, authHandler) - defer psrv.Close() - - dataHandler := func(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - hasAuth := strings.Contains(r.Header.Get("Authorization"), "Basic dXNlcjpwYXNzd29yZA==") - - if hasPassedAuthServer && hasAuth { - h.ServeHTTP(w, r) - return - } - w.Header().Set("Www-Authenticate", `Basic realm="x"`) - http.Redirect(w, r, psrv.URL+"/", 302) - } - } - - srv, _, _ := newAuthSrv(t, dataHandler) - defer srv.Close() - cli := NewClient(srv.URL, "user", "password") - data, err := cli.Read("/hello.txt") - if err != nil { - t.Logf("WIP got error=%v; want nil", err) - } - if bytes.Compare(data, []byte("hello gowebdav\n")) != 0 { - t.Logf("WIP got data=%v; want=hello gowebdav", data) - } -} diff --git a/util/sync/webdavClient/basicAuth.go b/util/sync/webdavClient/basicAuth.go deleted file mode 100644 index 10e5b8f4..00000000 --- a/util/sync/webdavClient/basicAuth.go +++ /dev/null @@ -1,42 +0,0 @@ -package webdavClient - -import ( - "fmt" - "net/http" -) - -// BasicAuth structure holds our credentials -type BasicAuth struct { - user string - pw string -} - -// Authorize the current request -func (b *BasicAuth) Authorize(c *http.Client, rq *http.Request, path string) error { - rq.SetBasicAuth(b.user, b.pw) - return nil -} - -// Verify verifies if the authentication -func (b *BasicAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { - if rs.StatusCode == 401 { - err = NewPathError("Authorize", path, rs.StatusCode) - } - return -} - -// Close cleans up all resources -func (b *BasicAuth) Close() error { - return nil -} - -// Clone creates a Copy of itself -func (b *BasicAuth) Clone() Authenticator { - // no copy due to read only access - return b -} - -// String toString -func (b *BasicAuth) String() string { - return fmt.Sprintf("BasicAuth login: %s", b.user) -} diff --git a/util/sync/webdavClient/basicAuth_test.go b/util/sync/webdavClient/basicAuth_test.go deleted file mode 100644 index 3a62713a..00000000 --- a/util/sync/webdavClient/basicAuth_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package webdavClient - -import ( - "net/http" - "testing" -) - -func TestNewBasicAuth(t *testing.T) { - a := &BasicAuth{user: "user", pw: "password"} - - ex := "BasicAuth login: user" - if a.String() != ex { - t.Error("expected: " + ex + " got: " + a.String()) - } - - if a.Clone() != a { - t.Error("expected the same instance") - } - - if a.Close() != nil { - t.Error("expected close without errors") - } -} - -func TestBasicAuthAuthorize(t *testing.T) { - a := &BasicAuth{user: "user", pw: "password"} - rq, _ := http.NewRequest("GET", "http://localhost/", nil) - a.Authorize(nil, rq, "/") - if rq.Header.Get("Authorization") != "Basic dXNlcjpwYXNzd29yZA==" { - t.Error("got wrong Authorization header: " + rq.Header.Get("Authorization")) - } -} - -func TestPreemtiveBasicAuth(t *testing.T) { - a := &BasicAuth{user: "user", pw: "password"} - auth := NewPreemptiveAuth(a) - n, b := auth.NewAuthenticator(nil) - if b != nil { - t.Error("expected body to be nil") - } - if n != a { - t.Error("expected the same instance") - } - - srv, _, _ := newAuthSrv(t, basicAuth) - defer srv.Close() - cli := NewAuthClient(srv.URL, auth) - if err := cli.Connect(); err != nil { - t.Fatalf("got error: %v, want nil", err) - } -} diff --git a/util/sync/webdavClient/client.go b/util/sync/webdavClient/client.go deleted file mode 100644 index 7d45b30e..00000000 --- a/util/sync/webdavClient/client.go +++ /dev/null @@ -1,474 +0,0 @@ -package webdavClient - -import ( - "bytes" - "context" - "encoding/xml" - "fmt" - "github.com/openziti/zrok/environment/env_core" - "github.com/openziti/zrok/sdk/golang/sdk" - "io" - "net" - "net/http" - "net/url" - "os" - pathpkg "path" - "strings" - "time" -) - -const XInhibitRedirect = "X-Gowebdav-Inhibit-Redirect" - -// Client defines our structure -type Client struct { - root string - headers http.Header - interceptor func(method string, rq *http.Request) - c *http.Client - auth Authorizer -} - -// NewClient creates a new instance of client -func NewClient(uri, user, pw string) *Client { - return NewAuthClient(uri, NewAutoAuth(user, pw)) -} - -func NewZrokClient(zrokUrl *url.URL, root env_core.Root, auth Authorizer) (*Client, error) { - conn, err := sdk.NewDialer(zrokUrl.Host, root) - if err != nil { - return nil, err - } - transport := http.DefaultTransport.(*http.Transport).Clone() - transport.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) { - return conn, nil - } - c := &http.Client{ - Transport: transport, - CheckRedirect: func(rq *http.Request, via []*http.Request) error { - if len(via) >= 10 { - return ErrTooManyRedirects - } - if via[0].Header.Get(XInhibitRedirect) != "" { - return http.ErrUseLastResponse - } - return nil - }, - } - httpUrl, err := url.Parse(zrokUrl.String()) - if err != nil { - return nil, err - } - httpUrl.Scheme = "http" - return &Client{root: FixSlash(httpUrl.String()), headers: make(http.Header), interceptor: nil, c: c, auth: auth}, nil -} - -// NewAuthClient creates a new client instance with a custom Authorizer -func NewAuthClient(uri string, auth Authorizer) *Client { - c := &http.Client{ - CheckRedirect: func(rq *http.Request, via []*http.Request) error { - if len(via) >= 10 { - return ErrTooManyRedirects - } - if via[0].Header.Get(XInhibitRedirect) != "" { - return http.ErrUseLastResponse - } - return nil - }, - } - return &Client{root: FixSlash(uri), headers: make(http.Header), interceptor: nil, c: c, auth: auth} -} - -// SetHeader lets us set arbitrary headers for a given client -func (c *Client) SetHeader(key, value string) { - c.headers.Set(key, value) -} - -// SetInterceptor lets us set an arbitrary interceptor for a given client -func (c *Client) SetInterceptor(interceptor func(method string, rq *http.Request)) { - c.interceptor = interceptor -} - -// SetTimeout exposes the ability to set a time limit for requests -func (c *Client) SetTimeout(timeout time.Duration) { - c.c.Timeout = timeout -} - -// SetTransport exposes the ability to define custom transports -func (c *Client) SetTransport(transport http.RoundTripper) { - c.c.Transport = transport -} - -// SetJar exposes the ability to set a cookie jar to the client. -func (c *Client) SetJar(jar http.CookieJar) { - c.c.Jar = jar -} - -// Connect connects to our dav server -func (c *Client) Connect() error { - rs, err := c.options("/") - if err != nil { - return err - } - - err = rs.Body.Close() - if err != nil { - return err - } - - if rs.StatusCode != 200 { - return NewPathError("Connect", c.root, rs.StatusCode) - } - - return nil -} - -type props struct { - Status string `xml:"DAV: status"` - Name string `xml:"DAV: prop>displayname,omitempty"` - Type xml.Name `xml:"DAV: prop>resourcetype>collection,omitempty"` - Size string `xml:"DAV: prop>getcontentlength,omitempty"` - ContentType string `xml:"DAV: prop>getcontenttype,omitempty"` - ETag string `xml:"DAV: prop>getetag,omitempty"` - Modified string `xml:"DAV: prop>getlastmodified,omitempty"` - Checksum string `xml:"zrok: prop>checksum,omitempty"` -} - -type Response struct { - Href string `xml:"DAV: href"` - Props []props `xml:"DAV: propstat"` -} - -func getProps(r *Response, status string) *props { - for _, prop := range r.Props { - if strings.Contains(prop.Status, status) { - return &prop - } - } - return nil -} - -// ReadDir reads the contents of a remote directory -func (c *Client) ReadDir(path string) ([]os.FileInfo, error) { - path = FixSlashes(path) - files := make([]os.FileInfo, 0) - skipSelf := true - parse := func(resp interface{}) error { - r := resp.(*Response) - - if skipSelf { - skipSelf = false - if p := getProps(r, "200"); p != nil && p.Type.Local == "collection" { - r.Props = nil - return nil - } - return NewPathError("ReadDir", path, 405) - } - - if p := getProps(r, "200"); p != nil { - f := new(File) - if ps, err := url.PathUnescape(r.Href); err == nil { - f.name = pathpkg.Base(ps) - } else { - f.name = p.Name - } - f.path = path + f.name - f.modified = parseModified(&p.Modified) - f.etag = p.ETag - f.contentType = p.ContentType - - if p.Type.Local == "collection" { - f.path += "/" - f.size = 0 - f.isdir = true - } else { - f.size = parseInt64(&p.Size) - f.isdir = false - } - - files = append(files, *f) - } - - r.Props = nil - return nil - } - - err := c.propfind(path, false, - ` - - - - - - - - - - - `, - &Response{}, - parse) - - if err != nil { - if _, ok := err.(*os.PathError); !ok { - err = NewPathErrorErr("ReadDir", path, err) - } - } - return files, err -} - -// Stat returns the file stats for a specified path -func (c *Client) Stat(path string) (os.FileInfo, error) { - var f *File - parse := func(resp interface{}) error { - r := resp.(*Response) - if p := getProps(r, "200"); p != nil && f == nil { - f = new(File) - f.name = p.Name - f.path = path - f.etag = p.ETag - f.contentType = p.ContentType - - if p.Type.Local == "collection" { - if !strings.HasSuffix(f.path, "/") { - f.path += "/" - } - f.size = 0 - f.modified = parseModified(&p.Modified) - f.isdir = true - } else { - f.size = parseInt64(&p.Size) - f.modified = parseModified(&p.Modified) - f.isdir = false - } - } - - r.Props = nil - return nil - } - - err := c.propfind(path, true, - ` - - - - - - - - - - `, - &Response{}, - parse) - - if err != nil { - if _, ok := err.(*os.PathError); !ok { - err = NewPathErrorErr("ReadDir", path, err) - } - } - return f, err -} - -// Remove removes a remote file -func (c *Client) Remove(path string) error { - return c.RemoveAll(path) -} - -// RemoveAll removes remote files -func (c *Client) RemoveAll(path string) error { - rs, err := c.req("DELETE", path, nil, nil) - if err != nil { - return NewPathError("Remove", path, 400) - } - err = rs.Body.Close() - if err != nil { - return err - } - - if rs.StatusCode == 200 || rs.StatusCode == 204 || rs.StatusCode == 404 { - return nil - } - - return NewPathError("Remove", path, rs.StatusCode) -} - -// Mkdir makes a directory -func (c *Client) Mkdir(path string, _ os.FileMode) (err error) { - path = FixSlashes(path) - status, err := c.mkcol(path) - if err != nil { - return - } - if status == 201 { - return nil - } - - return NewPathError("Mkdir", path, status) -} - -// MkdirAll like mkdir -p, but for webdav -func (c *Client) MkdirAll(path string, _ os.FileMode) (err error) { - path = FixSlashes(path) - status, err := c.mkcol(path) - if err != nil { - return - } - if status == 201 { - return nil - } - if status == 409 { - paths := strings.Split(path, "/") - sub := "/" - for _, e := range paths { - if e == "" { - continue - } - sub += e + "/" - status, err = c.mkcol(sub) - if err != nil { - return - } - if status != 201 { - return NewPathError("MkdirAll", sub, status) - } - } - return nil - } - - return NewPathError("MkdirAll", path, status) -} - -// Rename moves a file from A to B -func (c *Client) Rename(oldpath, newpath string, overwrite bool) error { - return c.copymove("MOVE", oldpath, newpath, overwrite) -} - -// Copy copies a file from A to B -func (c *Client) Copy(oldpath, newpath string, overwrite bool) error { - return c.copymove("COPY", oldpath, newpath, overwrite) -} - -// Read reads the contents of a remote file -func (c *Client) Read(path string) ([]byte, error) { - var stream io.ReadCloser - var err error - - if stream, err = c.ReadStream(path); err != nil { - return nil, err - } - defer stream.Close() - - buf := new(bytes.Buffer) - _, err = buf.ReadFrom(stream) - if err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// ReadStream reads the stream for a given path -func (c *Client) ReadStream(path string) (io.ReadCloser, error) { - rs, err := c.req("GET", path, nil, nil) - if err != nil { - return nil, NewPathErrorErr("ReadStream", path, err) - } - - if rs.StatusCode == 200 { - return rs.Body, nil - } - - rs.Body.Close() - return nil, NewPathError("ReadStream", path, rs.StatusCode) -} - -// ReadStreamRange reads the stream representing a subset of bytes for a given path, -// utilizing HTTP Range Requests if the server supports it. -// The range is expressed as offset from the start of the file and length, for example -// offset=10, length=10 will return bytes 10 through 19. -// -// If the server does not support partial content requests and returns full content instead, -// this function will emulate the behavior by skipping `offset` bytes and limiting the result -// to `length`. -func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadCloser, error) { - rs, err := c.req("GET", path, nil, func(r *http.Request) { - if length > 0 { - r.Header.Add("Range", fmt.Sprintf("bytes=%d-%d", offset, offset+length-1)) - } else { - r.Header.Add("Range", fmt.Sprintf("bytes=%d-", offset)) - } - }) - if err != nil { - return nil, NewPathErrorErr("ReadStreamRange", path, err) - } - - if rs.StatusCode == http.StatusPartialContent { - // server supported partial content, return as-is. - return rs.Body, nil - } - - // server returned success, but did not support partial content, so we have the whole - // stream in rs.Body - if rs.StatusCode == 200 { - // discard first 'offset' bytes. - if _, err := io.Copy(io.Discard, io.LimitReader(rs.Body, offset)); err != nil { - return nil, NewPathErrorErr("ReadStreamRange", path, err) - } - - // return a io.ReadCloser that is limited to `length` bytes. - return &limitedReadCloser{rc: rs.Body, remaining: int(length)}, nil - } - - rs.Body.Close() - return nil, NewPathError("ReadStream", path, rs.StatusCode) -} - -// Write writes data to a given path -func (c *Client) Write(path string, data []byte, _ os.FileMode) (err error) { - s, err := c.put(path, bytes.NewReader(data)) - if err != nil { - return - } - - switch s { - - case 200, 201, 204: - return nil - - case 404, 409: - err = c.createParentCollection(path) - if err != nil { - return - } - - s, err = c.put(path, bytes.NewReader(data)) - if err != nil { - return - } - if s == 200 || s == 201 || s == 204 { - return - } - } - - return NewPathError("Write", path, s) -} - -// WriteStream writes a stream -func (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err error) { - err = c.createParentCollection(path) - if err != nil { - return err - } - - s, err := c.put(path, stream) - if err != nil { - return err - } - - switch s { - case 200, 201, 204: - return nil - - default: - return NewPathError("WriteStream", path, s) - } -} diff --git a/util/sync/webdavClient/client_test.go b/util/sync/webdavClient/client_test.go deleted file mode 100644 index 8d60477a..00000000 --- a/util/sync/webdavClient/client_test.go +++ /dev/null @@ -1,574 +0,0 @@ -package webdavClient - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "net/http" - "net/http/httptest" - "os" - "strings" - "sync" - "testing" - "time" - - "golang.org/x/net/webdav" -) - -func noAuthHndl(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - h.ServeHTTP(w, r) - } -} - -func basicAuth(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if user, passwd, ok := r.BasicAuth(); ok { - if user == "user" && passwd == "password" { - h.ServeHTTP(w, r) - return - } - - http.Error(w, "not authorized", 403) - } else { - w.Header().Set("WWW-Authenticate", `Basic realm="x"`) - w.WriteHeader(401) - } - } -} - -func multipleAuth(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - notAuthed := false - if r.Header.Get("Authorization") == "" { - notAuthed = true - } else if user, passwd, ok := r.BasicAuth(); ok { - if user == "user" && passwd == "password" { - h.ServeHTTP(w, r) - return - } - notAuthed = true - } else if strings.HasPrefix(r.Header.Get("Authorization"), "Digest ") { - pairs := strings.TrimPrefix(r.Header.Get("Authorization"), "Digest ") - digestParts := make(map[string]string) - for _, pair := range strings.Split(pairs, ",") { - kv := strings.SplitN(strings.TrimSpace(pair), "=", 2) - key, value := kv[0], kv[1] - value = strings.Trim(value, `"`) - digestParts[key] = value - } - if digestParts["qop"] == "" { - digestParts["qop"] = "auth" - } - - ha1 := getMD5(fmt.Sprint(digestParts["username"], ":", digestParts["realm"], ":", "digestPW")) - ha2 := getMD5(fmt.Sprint(r.Method, ":", digestParts["uri"])) - expected := getMD5(fmt.Sprint(ha1, - ":", digestParts["nonce"], - ":", digestParts["nc"], - ":", digestParts["cnonce"], - ":", digestParts["qop"], - ":", ha2)) - - if expected == digestParts["response"] { - h.ServeHTTP(w, r) - return - } - notAuthed = true - } - - if notAuthed { - w.Header().Add("WWW-Authenticate", `Digest realm="testrealm@host.com", qop="auth,auth-int",nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",opaque="5ccc069c403ebaf9f0171e9517f40e41"`) - w.Header().Add("WWW-Authenticate", `Basic realm="x"`) - w.WriteHeader(401) - } - } -} - -func fillFs(t *testing.T, fs webdav.FileSystem) context.Context { - ctx := context.Background() - f, err := fs.OpenFile(ctx, "hello.txt", os.O_CREATE, 0644) - if err != nil { - t.Errorf("fail to crate file: %v", err) - } - f.Write([]byte("hello gowebdav\n")) - f.Close() - err = fs.Mkdir(ctx, "/test", 0755) - if err != nil { - t.Errorf("fail to crate directory: %v", err) - } - f, err = fs.OpenFile(ctx, "/test/test.txt", os.O_CREATE, 0644) - if err != nil { - t.Errorf("fail to crate file: %v", err) - } - f.Write([]byte("test test gowebdav\n")) - f.Close() - return ctx -} - -func newServer(t *testing.T) (*Client, *httptest.Server, webdav.FileSystem, context.Context) { - return newAuthServer(t, basicAuth) -} - -func newAuthServer(t *testing.T, auth func(h http.Handler) http.HandlerFunc) (*Client, *httptest.Server, webdav.FileSystem, context.Context) { - srv, fs, ctx := newAuthSrv(t, auth) - cli := NewClient(srv.URL, "user", "password") - return cli, srv, fs, ctx -} - -func newAuthSrv(t *testing.T, auth func(h http.Handler) http.HandlerFunc) (*httptest.Server, webdav.FileSystem, context.Context) { - mux := http.NewServeMux() - fs := webdav.NewMemFS() - ctx := fillFs(t, fs) - mux.HandleFunc("/", auth(&webdav.Handler{ - FileSystem: fs, - LockSystem: webdav.NewMemLS(), - })) - srv := httptest.NewServer(mux) - return srv, fs, ctx -} - -func TestConnect(t *testing.T) { - cli, srv, _, _ := newServer(t) - defer srv.Close() - if err := cli.Connect(); err != nil { - t.Fatalf("got error: %v, want nil", err) - } - - cli = NewClient(srv.URL, "no", "no") - if err := cli.Connect(); err == nil { - t.Fatalf("got nil, want error: %v", err) - } -} - -func TestConnectMultipleAuth(t *testing.T) { - cli, srv, _, _ := newAuthServer(t, multipleAuth) - defer srv.Close() - if err := cli.Connect(); err != nil { - t.Fatalf("got error: %v, want nil", err) - } - - cli = NewClient(srv.URL, "digestUser", "digestPW") - if err := cli.Connect(); err != nil { - t.Fatalf("got nil, want error: %v", err) - } - - cli = NewClient(srv.URL, "no", "no") - if err := cli.Connect(); err == nil { - t.Fatalf("got nil, want error: %v", err) - } -} - -func TestConnectMultiAuthII(t *testing.T) { - cli, srv, _, _ := newAuthServer(t, func(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if user, passwd, ok := r.BasicAuth(); ok { - if user == "user" && passwd == "password" { - h.ServeHTTP(w, r) - return - } - - http.Error(w, "not authorized", 403) - } else { - w.Header().Add("WWW-Authenticate", `FooAuth`) - w.Header().Add("WWW-Authenticate", `BazAuth`) - w.Header().Add("WWW-Authenticate", `BarAuth`) - w.Header().Add("WWW-Authenticate", `Basic realm="x"`) - w.WriteHeader(401) - } - } - }) - defer srv.Close() - if err := cli.Connect(); err != nil { - t.Fatalf("got error: %v, want nil", err) - } - - cli = NewClient(srv.URL, "no", "no") - if err := cli.Connect(); err == nil { - t.Fatalf("got nil, want error: %v", err) - } -} - -func TestReadDirConcurrent(t *testing.T) { - cli, srv, _, _ := newServer(t) - defer srv.Close() - - var wg sync.WaitGroup - errs := make(chan error, 2) - for i := 0; i < 2; i++ { - wg.Add(1) - - go func() { - defer wg.Done() - f, err := cli.ReadDir("/") - if err != nil { - errs <- errors.New(fmt.Sprintf("got error: %v, want file listing: %v", err, f)) - } - if len(f) != 2 { - errs <- errors.New(fmt.Sprintf("f: %v err: %v", f, err)) - } - if f[0].Name() != "hello.txt" && f[1].Name() != "hello.txt" { - errs <- errors.New(fmt.Sprintf("got: %v, want file: %s", f, "hello.txt")) - } - if f[0].Name() != "test" && f[1].Name() != "test" { - errs <- errors.New(fmt.Sprintf("got: %v, want directory: %s", f, "test")) - } - }() - } - - wg.Wait() - close(errs) - - for err := range errs { - if err != nil { - t.Fatal(err) - } - } -} - -func TestRead(t *testing.T) { - cli, srv, _, _ := newServer(t) - defer srv.Close() - - data, err := cli.Read("/hello.txt") - if err != nil || bytes.Compare(data, []byte("hello gowebdav\n")) != 0 { - t.Fatalf("got: %v, want data: %s", err, []byte("hello gowebdav\n")) - } - - data, err = cli.Read("/404.txt") - if err == nil { - t.Fatalf("got: %v, want error: %v", data, err) - } - if !IsErrNotFound(err) { - t.Fatalf("got: %v, want 404 error", err) - } -} - -func TestReadNoAuth(t *testing.T) { - cli, srv, _, _ := newAuthServer(t, noAuthHndl) - defer srv.Close() - - data, err := cli.Read("/hello.txt") - if err != nil || bytes.Compare(data, []byte("hello gowebdav\n")) != 0 { - t.Fatalf("got: %v, want data: %s", err, []byte("hello gowebdav\n")) - } - - data, err = cli.Read("/404.txt") - if err == nil { - t.Fatalf("got: %v, want error: %v", data, err) - } - if !IsErrNotFound(err) { - t.Fatalf("got: %v, want 404 error", err) - } -} - -func TestReadStream(t *testing.T) { - cli, srv, _, _ := newServer(t) - defer srv.Close() - - stream, err := cli.ReadStream("/hello.txt") - if err != nil { - t.Fatalf("got: %v, want data: %v", err, stream) - } - buf := new(bytes.Buffer) - buf.ReadFrom(stream) - if buf.String() != "hello gowebdav\n" { - t.Fatalf("got: %v, want stream: hello gowebdav", buf.String()) - } - - stream, err = cli.ReadStream("/404/hello.txt") - if err == nil { - t.Fatalf("got: %v, want error: %v", stream, err) - } -} - -func TestReadStreamRange(t *testing.T) { - cli, srv, _, _ := newServer(t) - defer srv.Close() - - stream, err := cli.ReadStreamRange("/hello.txt", 4, 4) - if err != nil { - t.Fatalf("got: %v, want data: %v", err, stream) - } - buf := new(bytes.Buffer) - buf.ReadFrom(stream) - if buf.String() != "o go" { - t.Fatalf("got: %v, want stream: o go", buf.String()) - } - - stream, err = cli.ReadStream("/404/hello.txt") - if err == nil { - t.Fatalf("got: %v, want error: %v", stream, err) - } -} - -func TestReadStreamRangeUnkownLength(t *testing.T) { - cli, srv, _, _ := newServer(t) - defer srv.Close() - - stream, err := cli.ReadStreamRange("/hello.txt", 6, 0) - if err != nil { - t.Fatalf("got: %v, want data: %v", err, stream) - } - buf := new(bytes.Buffer) - buf.ReadFrom(stream) - if buf.String() != "gowebdav\n" { - t.Fatalf("got: %v, want stream: gowebdav\n", buf.String()) - } - - stream, err = cli.ReadStream("/404/hello.txt") - if err == nil { - t.Fatalf("got: %v, want error: %v", stream, err) - } -} - -func TestStat(t *testing.T) { - cli, srv, _, _ := newServer(t) - defer srv.Close() - - info, err := cli.Stat("/hello.txt") - if err != nil { - t.Fatalf("got: %v, want os.Info: %v", err, info) - } - if info.Name() != "hello.txt" { - t.Fatalf("got: %v, want file hello.txt", info) - } - - info, err = cli.Stat("/404.txt") - if err == nil { - t.Fatalf("got: %v, want error: %v", info, err) - } - if !IsErrNotFound(err) { - t.Fatalf("got: %v, want 404 error", err) - } -} - -func TestMkdir(t *testing.T) { - cli, srv, fs, ctx := newServer(t) - defer srv.Close() - - info, err := cli.Stat("/newdir") - if err == nil { - t.Fatalf("got: %v, want error: %v", info, err) - } - - if err := cli.Mkdir("/newdir", 0755); err != nil { - t.Fatalf("got: %v, want mkdir /newdir", err) - } - - if err := cli.Mkdir("/newdir", 0755); err != nil { - t.Fatalf("got: %v, want mkdir /newdir", err) - } - - info, err = fs.Stat(ctx, "/newdir") - if err != nil { - t.Fatalf("got: %v, want dir info: %v", err, info) - } - - if err := cli.Mkdir("/404/newdir", 0755); err == nil { - t.Fatalf("expected Mkdir error due to missing parent directory") - } -} - -func TestMkdirAll(t *testing.T) { - cli, srv, fs, ctx := newServer(t) - defer srv.Close() - - if err := cli.MkdirAll("/dir/dir/dir", 0755); err != nil { - t.Fatalf("got: %v, want mkdirAll /dir/dir/dir", err) - } - - info, err := fs.Stat(ctx, "/dir/dir/dir") - if err != nil { - t.Fatalf("got: %v, want dir info: %v", err, info) - } -} - -func TestCopy(t *testing.T) { - cli, srv, fs, ctx := newServer(t) - defer srv.Close() - - info, err := fs.Stat(ctx, "/copy.txt") - if err == nil { - t.Fatalf("got: %v, want error: %v", info, err) - } - - if err := cli.Copy("/hello.txt", "/copy.txt", false); err != nil { - t.Fatalf("got: %v, want copy /hello.txt to /copy.txt", err) - } - - info, err = fs.Stat(ctx, "/copy.txt") - if err != nil { - t.Fatalf("got: %v, want file info: %v", err, info) - } - if info.Size() != 15 { - t.Fatalf("got: %v, want file size: %d bytes", info.Size(), 15) - } - - info, err = fs.Stat(ctx, "/hello.txt") - if err != nil { - t.Fatalf("got: %v, want file info: %v", err, info) - } - if info.Size() != 15 { - t.Fatalf("got: %v, want file size: %d bytes", info.Size(), 15) - } - - if err := cli.Copy("/hello.txt", "/copy.txt", false); err == nil { - t.Fatalf("expected copy error due to overwrite false") - } - - if err := cli.Copy("/hello.txt", "/copy.txt", true); err != nil { - t.Fatalf("got: %v, want overwrite /copy.txt with /hello.txt", err) - } -} - -func TestRename(t *testing.T) { - cli, srv, fs, ctx := newServer(t) - defer srv.Close() - - info, err := fs.Stat(ctx, "/copy.txt") - if err == nil { - t.Fatalf("got: %v, want error: %v", info, err) - } - - if err := cli.Rename("/hello.txt", "/copy.txt", false); err != nil { - t.Fatalf("got: %v, want mv /hello.txt to /copy.txt", err) - } - - info, err = fs.Stat(ctx, "/copy.txt") - if err != nil { - t.Fatalf("got: %v, want file info: %v", err, info) - } - if info.Size() != 15 { - t.Fatalf("got: %v, want file size: %d bytes", info.Size(), 15) - } - - if info, err = fs.Stat(ctx, "/hello.txt"); err == nil { - t.Fatalf("got: %v, want error: %v", info, err) - } - - if err := cli.Rename("/test/test.txt", "/copy.txt", true); err != nil { - t.Fatalf("got: %v, want overwrite /copy.txt with /hello.txt", err) - } - info, err = fs.Stat(ctx, "/copy.txt") - if err != nil { - t.Fatalf("got: %v, want file info: %v", err, info) - } - if info.Size() != 19 { - t.Fatalf("got: %v, want file size: %d bytes", info.Size(), 19) - } -} - -func TestRemove(t *testing.T) { - cli, srv, fs, ctx := newServer(t) - defer srv.Close() - - if err := cli.Remove("/hello.txt"); err != nil { - t.Fatalf("got: %v, want nil", err) - } - - if info, err := fs.Stat(ctx, "/hello.txt"); err == nil { - t.Fatalf("got: %v, want error: %v", info, err) - } - - if err := cli.Remove("/404.txt"); err != nil { - t.Fatalf("got: %v, want nil", err) - } -} - -func TestRemoveAll(t *testing.T) { - cli, srv, fs, ctx := newServer(t) - defer srv.Close() - - if err := cli.RemoveAll("/test/test.txt"); err != nil { - t.Fatalf("got: %v, want nil", err) - } - - if info, err := fs.Stat(ctx, "/test/test.txt"); err == nil { - t.Fatalf("got: %v, want error: %v", info, err) - } - - if err := cli.RemoveAll("/404.txt"); err != nil { - t.Fatalf("got: %v, want nil", err) - } - - if err := cli.RemoveAll("/404/404/404.txt"); err != nil { - t.Fatalf("got: %v, want nil", err) - } -} - -func TestWrite(t *testing.T) { - cli, srv, fs, ctx := newServer(t) - defer srv.Close() - - if err := cli.Write("/newfile.txt", []byte("foo bar\n"), 0660); err != nil { - t.Fatalf("got: %v, want nil", err) - } - - info, err := fs.Stat(ctx, "/newfile.txt") - if err != nil { - t.Fatalf("got: %v, want file info: %v", err, info) - } - if info.Size() != 8 { - t.Fatalf("got: %v, want file size: %d bytes", info.Size(), 8) - } - - if err := cli.Write("/404/newfile.txt", []byte("foo bar\n"), 0660); err != nil { - t.Fatalf("got: %v, want nil", err) - } -} - -func TestWriteStream(t *testing.T) { - cli, srv, fs, ctx := newServer(t) - defer srv.Close() - - if err := cli.WriteStream("/newfile.txt", strings.NewReader("foo bar\n"), 0660); err != nil { - t.Fatalf("got: %v, want nil", err) - } - - info, err := fs.Stat(ctx, "/newfile.txt") - if err != nil { - t.Fatalf("got: %v, want file info: %v", err, info) - } - if info.Size() != 8 { - t.Fatalf("got: %v, want file size: %d bytes", info.Size(), 8) - } - - if err := cli.WriteStream("/404/works.txt", strings.NewReader("foo bar\n"), 0660); err != nil { - t.Fatalf("got: %v, want nil", err) - } - - if info, err := fs.Stat(ctx, "/404/works.txt"); err != nil { - t.Fatalf("got: %v, want file info: %v", err, info) - } -} - -func TestWriteStreamFromPipe(t *testing.T) { - cli, srv, fs, ctx := newServer(t) - defer srv.Close() - - r, w := io.Pipe() - - go func() { - defer w.Close() - fmt.Fprint(w, "foo") - time.Sleep(1 * time.Second) - fmt.Fprint(w, " ") - time.Sleep(1 * time.Second) - fmt.Fprint(w, "bar\n") - }() - - if err := cli.WriteStream("/newfile.txt", r, 0660); err != nil { - t.Fatalf("got: %v, want nil", err) - } - - info, err := fs.Stat(ctx, "/newfile.txt") - if err != nil { - t.Fatalf("got: %v, want file info: %v", err, info) - } - if info.Size() != 8 { - t.Fatalf("got: %v, want file size: %d bytes", info.Size(), 8) - } -} diff --git a/util/sync/webdavClient/digestAuth.go b/util/sync/webdavClient/digestAuth.go deleted file mode 100644 index 5ac63205..00000000 --- a/util/sync/webdavClient/digestAuth.go +++ /dev/null @@ -1,164 +0,0 @@ -package webdavClient - -import ( - "crypto/md5" - "crypto/rand" - "encoding/hex" - "fmt" - "io" - "net/http" - "strings" -) - -// DigestAuth structure holds our credentials -type DigestAuth struct { - user string - pw string - digestParts map[string]string -} - -// NewDigestAuth creates a new instance of our Digest Authenticator -func NewDigestAuth(login, secret string, rs *http.Response) (Authenticator, error) { - return &DigestAuth{user: login, pw: secret, digestParts: digestParts(rs)}, nil -} - -// Authorize the current request -func (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) error { - d.digestParts["uri"] = path - d.digestParts["method"] = rq.Method - d.digestParts["username"] = d.user - d.digestParts["password"] = d.pw - rq.Header.Set("Authorization", getDigestAuthorization(d.digestParts)) - return nil -} - -// Verify checks for authentication issues and may trigger a re-authentication -func (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { - if rs.StatusCode == 401 { - err = NewPathError("Authorize", path, rs.StatusCode) - } - return -} - -// Close cleans up all resources -func (d *DigestAuth) Close() error { - return nil -} - -// Clone creates a copy of itself -func (d *DigestAuth) Clone() Authenticator { - parts := make(map[string]string, len(d.digestParts)) - for k, v := range d.digestParts { - parts[k] = v - } - return &DigestAuth{user: d.user, pw: d.pw, digestParts: parts} -} - -// String toString -func (d *DigestAuth) String() string { - return fmt.Sprintf("DigestAuth login: %s", d.user) -} - -func digestParts(resp *http.Response) map[string]string { - result := map[string]string{} - if len(resp.Header["Www-Authenticate"]) > 0 { - wantedHeaders := []string{"nonce", "realm", "qop", "opaque", "algorithm", "entityBody"} - responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",") - for _, r := range responseHeaders { - for _, w := range wantedHeaders { - if strings.Contains(r, w) { - result[w] = strings.Trim( - strings.SplitN(r, `=`, 2)[1], - `"`, - ) - } - } - } - } - return result -} - -func getMD5(text string) string { - hasher := md5.New() - hasher.Write([]byte(text)) - return hex.EncodeToString(hasher.Sum(nil)) -} - -func getCnonce() string { - b := make([]byte, 8) - io.ReadFull(rand.Reader, b) - return fmt.Sprintf("%x", b)[:16] -} - -func getDigestAuthorization(digestParts map[string]string) string { - d := digestParts - // These are the correct ha1 and ha2 for qop=auth. We should probably check for other types of qop. - - var ( - ha1 string - ha2 string - nonceCount = 00000001 - cnonce = getCnonce() - response string - ) - - // 'ha1' value depends on value of "algorithm" field - switch d["algorithm"] { - case "MD5", "": - ha1 = getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"]) - case "MD5-sess": - ha1 = getMD5( - fmt.Sprintf("%s:%v:%s", - getMD5(d["username"]+":"+d["realm"]+":"+d["password"]), - nonceCount, - cnonce, - ), - ) - } - - // 'ha2' value depends on value of "qop" field - switch d["qop"] { - case "auth", "": - ha2 = getMD5(d["method"] + ":" + d["uri"]) - case "auth-int": - if d["entityBody"] != "" { - ha2 = getMD5(d["method"] + ":" + d["uri"] + ":" + getMD5(d["entityBody"])) - } - } - - // 'response' value depends on value of "qop" field - switch d["qop"] { - case "": - response = getMD5( - fmt.Sprintf("%s:%s:%s", - ha1, - d["nonce"], - ha2, - ), - ) - case "auth", "auth-int": - response = getMD5( - fmt.Sprintf("%s:%s:%v:%s:%s:%s", - ha1, - d["nonce"], - nonceCount, - cnonce, - d["qop"], - ha2, - ), - ) - } - - authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", nc=%v, cnonce="%s", response="%s"`, - d["username"], d["realm"], d["nonce"], d["uri"], nonceCount, cnonce, response) - - if d["qop"] != "" { - authorization += fmt.Sprintf(`, qop=%s`, d["qop"]) - } - - if d["opaque"] != "" { - authorization += fmt.Sprintf(`, opaque="%s"`, d["opaque"]) - } - - return authorization -} diff --git a/util/sync/webdavClient/digestAuth_test.go b/util/sync/webdavClient/digestAuth_test.go deleted file mode 100644 index 2adae4a6..00000000 --- a/util/sync/webdavClient/digestAuth_test.go +++ /dev/null @@ -1,35 +0,0 @@ -package webdavClient - -import ( - "net/http" - "strings" - "testing" -) - -func TestNewDigestAuth(t *testing.T) { - a := &DigestAuth{user: "user", pw: "password", digestParts: make(map[string]string, 0)} - - ex := "DigestAuth login: user" - if a.String() != ex { - t.Error("expected: " + ex + " got: " + a.String()) - } - - if a.Clone() == a { - t.Error("expected a different instance") - } - - if a.Close() != nil { - t.Error("expected close without errors") - } -} - -func TestDigestAuthAuthorize(t *testing.T) { - a := &DigestAuth{user: "user", pw: "password", digestParts: make(map[string]string, 0)} - rq, _ := http.NewRequest("GET", "http://localhost/", nil) - a.Authorize(nil, rq, "/") - // TODO this is a very lazy test it cuts of cnonce - ex := `Digest username="user", realm="", nonce="", uri="/", nc=1, cnonce="` - if strings.Index(rq.Header.Get("Authorization"), ex) != 0 { - t.Error("got wrong Authorization header: " + rq.Header.Get("Authorization")) - } -} diff --git a/util/sync/webdavClient/doc.go b/util/sync/webdavClient/doc.go deleted file mode 100644 index db5c2455..00000000 --- a/util/sync/webdavClient/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package gowebdav is a WebDAV client library with a command line tool -// included. -package webdavClient diff --git a/util/sync/webdavClient/errors.go b/util/sync/webdavClient/errors.go deleted file mode 100644 index 13488f81..00000000 --- a/util/sync/webdavClient/errors.go +++ /dev/null @@ -1,57 +0,0 @@ -package webdavClient - -import ( - "errors" - "fmt" - "os" -) - -// ErrAuthChanged must be returned from the Verify method as an error -// to trigger a re-authentication / negotiation with a new authenticator. -var ErrAuthChanged = errors.New("authentication failed, change algorithm") - -// ErrTooManyRedirects will be used as return error if a request exceeds 10 redirects. -var ErrTooManyRedirects = errors.New("stopped after 10 redirects") - -// StatusError implements error and wraps -// an erroneous status code. -type StatusError struct { - Status int -} - -func (se StatusError) Error() string { - return fmt.Sprintf("%d", se.Status) -} - -// IsErrCode returns true if the given error -// is an os.PathError wrapping a StatusError -// with the given status code. -func IsErrCode(err error, code int) bool { - if pe, ok := err.(*os.PathError); ok { - se, ok := pe.Err.(StatusError) - return ok && se.Status == code - } - return false -} - -// IsErrNotFound is shorthand for IsErrCode -// for status 404. -func IsErrNotFound(err error) bool { - return IsErrCode(err, 404) -} - -func NewPathError(op string, path string, statusCode int) error { - return &os.PathError{ - Op: op, - Path: path, - Err: StatusError{statusCode}, - } -} - -func NewPathErrorErr(op string, path string, err error) error { - return &os.PathError{ - Op: op, - Path: path, - Err: err, - } -} diff --git a/util/sync/webdavClient/file.go b/util/sync/webdavClient/file.go deleted file mode 100644 index ce1033ef..00000000 --- a/util/sync/webdavClient/file.go +++ /dev/null @@ -1,82 +0,0 @@ -package webdavClient - -import ( - "fmt" - "os" - "time" -) - -// File is our structure for a given file -type File struct { - path string - name string - contentType string - size int64 - modified time.Time - etag string - isdir bool - checksum string -} - -// Path returns the full path of a file -func (f File) Path() string { - return f.path -} - -// Name returns the name of a file -func (f File) Name() string { - return f.name -} - -// ContentType returns the content type of a file -func (f File) ContentType() string { - return f.contentType -} - -// Size returns the size of a file -func (f File) Size() int64 { - return f.size -} - -// Mode will return the mode of a given file -func (f File) Mode() os.FileMode { - // TODO check webdav perms - if f.isdir { - return 0775 | os.ModeDir - } - - return 0664 -} - -// ModTime returns the modified time of a file -func (f File) ModTime() time.Time { - return f.modified -} - -// ETag returns the ETag of a file -func (f File) ETag() string { - return f.etag -} - -// IsDir let us see if a given file is a directory or not -func (f File) IsDir() bool { - return f.isdir -} - -func (f File) Checksum() string { - return f.checksum -} - -// Sys ???? -func (f File) Sys() interface{} { - return nil -} - -// String lets us see file information -func (f File) String() string { - if f.isdir { - return fmt.Sprintf("Dir : '%s' - '%s'", f.path, f.name) - } - - return fmt.Sprintf("File: '%s' SIZE: %d MODIFIED: %s ETAG: %s CTYPE: %s", f.path, f.size, f.modified.String(), f.etag, f.contentType) -} diff --git a/util/sync/webdavClient/netrc.go b/util/sync/webdavClient/netrc.go deleted file mode 100644 index 1bf0eaab..00000000 --- a/util/sync/webdavClient/netrc.go +++ /dev/null @@ -1,54 +0,0 @@ -package webdavClient - -import ( - "bufio" - "fmt" - "net/url" - "os" - "regexp" - "strings" -) - -func parseLine(s string) (login, pass string) { - fields := strings.Fields(s) - for i, f := range fields { - if f == "login" { - login = fields[i+1] - } - if f == "password" { - pass = fields[i+1] - } - } - return login, pass -} - -// ReadConfig reads login and password configuration from ~/.netrc -// machine foo.com login username password 123456 -func ReadConfig(uri, netrc string) (string, string) { - u, err := url.Parse(uri) - if err != nil { - return "", "" - } - - file, err := os.Open(netrc) - if err != nil { - return "", "" - } - defer file.Close() - - re := fmt.Sprintf(`^.*machine %s.*$`, u.Host) - scanner := bufio.NewScanner(file) - for scanner.Scan() { - s := scanner.Text() - - matched, err := regexp.MatchString(re, s) - if err != nil { - return "", "" - } - if matched { - return parseLine(s) - } - } - - return "", "" -} diff --git a/util/sync/webdavClient/passportAuth.go b/util/sync/webdavClient/passportAuth.go deleted file mode 100644 index 35633849..00000000 --- a/util/sync/webdavClient/passportAuth.go +++ /dev/null @@ -1,181 +0,0 @@ -package webdavClient - -import ( - "fmt" - "io" - "net/http" - "net/url" - "strings" -) - -// PassportAuth structure holds our credentials -type PassportAuth struct { - user string - pw string - cookies []http.Cookie - inhibitRedirect bool -} - -// constructor for PassportAuth creates a new PassportAuth object and -// automatically authenticates against the given partnerURL -func NewPassportAuth(c *http.Client, user, pw, partnerURL string, header *http.Header) (Authenticator, error) { - p := &PassportAuth{ - user: user, - pw: pw, - inhibitRedirect: true, - } - err := p.genCookies(c, partnerURL, header) - return p, err -} - -// Authorize the current request -func (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error { - // prevent redirects to detect subsequent authentication requests - if p.inhibitRedirect { - rq.Header.Set(XInhibitRedirect, "1") - } else { - p.inhibitRedirect = true - } - for _, cookie := range p.cookies { - rq.AddCookie(&cookie) - } - return nil -} - -// Verify verifies if the authentication is good -func (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { - switch rs.StatusCode { - case 301, 302, 307, 308: - redo = true - if rs.Header.Get("Www-Authenticate") != "" { - // re-authentication required as we are redirected to the login page - err = p.genCookies(c, rs.Request.URL.String(), &rs.Header) - } else { - // just a redirect, follow it - p.inhibitRedirect = false - } - case 401: - err = NewPathError("Authorize", path, rs.StatusCode) - } - return -} - -// Close cleans up all resources -func (p *PassportAuth) Close() error { - return nil -} - -// Clone creates a Copy of itself -func (p *PassportAuth) Clone() Authenticator { - // create a copy to allow independent cookie updates - clonedCookies := make([]http.Cookie, len(p.cookies)) - copy(clonedCookies, p.cookies) - - return &PassportAuth{ - user: p.user, - pw: p.pw, - cookies: clonedCookies, - inhibitRedirect: true, - } -} - -// String toString -func (p *PassportAuth) String() string { - return fmt.Sprintf("PassportAuth login: %s", p.user) -} - -func (p *PassportAuth) genCookies(c *http.Client, partnerUrl string, header *http.Header) error { - // For more details refer to: - // https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pass/2c80637d-438c-4d4b-adc5-903170a779f3 - // Skipping step 1 and 2 as we already have the partner server challenge - - baseAuthenticationServer := header.Get("Location") - baseAuthenticationServerURL, err := url.Parse(baseAuthenticationServer) - if err != nil { - return err - } - - // Skipping step 3 and 4 as we already know that we need and have the user's credentials - // Step 5 (Sign-in request) - authenticationServerUrl := url.URL{ - Scheme: baseAuthenticationServerURL.Scheme, - Host: baseAuthenticationServerURL.Host, - Path: "/login2.srf", - } - - partnerServerChallenge := strings.Split(header.Get("Www-Authenticate"), " ")[1] - - req := http.Request{ - Method: "GET", - URL: &authenticationServerUrl, - Header: http.Header{ - "Authorization": []string{"Passport1.4 sign-in=" + url.QueryEscape(p.user) + ",pwd=" + url.QueryEscape(p.pw) + ",OrgVerb=GET,OrgUrl=" + partnerUrl + "," + partnerServerChallenge}, - }, - } - - rs, err := c.Do(&req) - if err != nil { - return err - } - io.Copy(io.Discard, rs.Body) - rs.Body.Close() - if rs.StatusCode != 200 { - return NewPathError("Authorize", "/", rs.StatusCode) - } - - // Step 6 (Token Response from Authentication Server) - tokenResponseHeader := rs.Header.Get("Authentication-Info") - if tokenResponseHeader == "" { - return NewPathError("Authorize", "/", 401) - } - tokenResponseHeaderList := strings.Split(tokenResponseHeader, ",") - token := "" - for _, tokenResponseHeader := range tokenResponseHeaderList { - if strings.HasPrefix(tokenResponseHeader, "from-PP='") { - token = tokenResponseHeader - break - } - } - if token == "" { - return NewPathError("Authorize", "/", 401) - } - - // Step 7 (First Authentication Request to Partner Server) - origUrl, err := url.Parse(partnerUrl) - if err != nil { - return err - } - req = http.Request{ - Method: "GET", - URL: origUrl, - Header: http.Header{ - "Authorization": []string{"Passport1.4 " + token}, - }, - } - - rs, err = c.Do(&req) - if err != nil { - return err - } - io.Copy(io.Discard, rs.Body) - rs.Body.Close() - if rs.StatusCode != 200 && rs.StatusCode != 302 { - return NewPathError("Authorize", "/", rs.StatusCode) - } - - // Step 8 (Set Token Message from Partner Server) - cookies := rs.Header.Values("Set-Cookie") - p.cookies = make([]http.Cookie, len(cookies)) - for i, cookie := range cookies { - cookieParts := strings.Split(cookie, ";") - cookieName := strings.Split(cookieParts[0], "=")[0] - cookieValue := strings.Split(cookieParts[0], "=")[1] - - p.cookies[i] = http.Cookie{ - Name: cookieName, - Value: cookieValue, - } - } - - return nil -} diff --git a/util/sync/webdavClient/passportAuth_test.go b/util/sync/webdavClient/passportAuth_test.go deleted file mode 100644 index 27b8b6f0..00000000 --- a/util/sync/webdavClient/passportAuth_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package webdavClient - -import ( - "bytes" - "net/http" - "net/url" - "regexp" - "testing" -) - -// testing the creation is enough as it handles the authorization during init -func TestNewPassportAuth(t *testing.T) { - user := "user" - pass := "password" - p1 := "some,comma,separated,values" - token := "from-PP='token'" - - authHandler := func(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - reg, err := regexp.Compile("Passport1\\.4 sign-in=" + url.QueryEscape(user) + ",pwd=" + url.QueryEscape(pass) + ",OrgVerb=GET,OrgUrl=.*," + p1) - if err != nil { - t.Error(err) - } - if reg.MatchString(r.Header.Get("Authorization")) { - w.Header().Set("Authentication-Info", token) - w.WriteHeader(200) - return - } - } - } - authsrv, _, _ := newAuthSrv(t, authHandler) - defer authsrv.Close() - - dataHandler := func(h http.Handler) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - reg, err := regexp.Compile("Passport1\\.4 " + token) - if err != nil { - t.Error(err) - } - if reg.MatchString(r.Header.Get("Authorization")) { - w.Header().Set("Set-Cookie", "Pass=port") - h.ServeHTTP(w, r) - return - } - for _, c := range r.Cookies() { - if c.Name == "Pass" && c.Value == "port" { - h.ServeHTTP(w, r) - return - } - } - w.Header().Set("Www-Authenticate", "Passport1.4 "+p1) - http.Redirect(w, r, authsrv.URL+"/", 302) - } - } - srv, _, _ := newAuthSrv(t, dataHandler) - defer srv.Close() - - cli := NewClient(srv.URL, user, pass) - data, err := cli.Read("/hello.txt") - if err != nil { - t.Errorf("got error=%v; want nil", err) - } - if !bytes.Equal(data, []byte("hello gowebdav\n")) { - t.Logf("got data=%v; want=hello gowebdav", data) - } -} diff --git a/util/sync/webdavClient/requests.go b/util/sync/webdavClient/requests.go deleted file mode 100644 index 40b1c90a..00000000 --- a/util/sync/webdavClient/requests.go +++ /dev/null @@ -1,201 +0,0 @@ -package webdavClient - -import ( - "io" - "log" - "net/http" - "path" - "strings" -) - -func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (rs *http.Response, err error) { - var redo bool - var r *http.Request - var uri = PathEscape(Join(c.root, path)) - auth, body := c.auth.NewAuthenticator(body) - defer auth.Close() - - for { // TODO auth.continue() strategy(true|n times|until)? - if r, err = http.NewRequest(method, uri, body); err != nil { - return - } - - for k, vals := range c.headers { - for _, v := range vals { - r.Header.Add(k, v) - } - } - - if err = auth.Authorize(c.c, r, path); err != nil { - return - } - - if intercept != nil { - intercept(r) - } - - if c.interceptor != nil { - c.interceptor(method, r) - } - - if rs, err = c.c.Do(r); err != nil { - return - } - - if redo, err = auth.Verify(c.c, rs, path); err != nil { - rs.Body.Close() - return nil, err - } - if redo { - rs.Body.Close() - if body, err = r.GetBody(); err != nil { - return nil, err - } - continue - } - break - } - - 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") - // TODO add support for 'gzip,deflate;q=0.8,q=0.7' - 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) Proppatch(path string, body string, resp interface{}, parse func(resp interface{}) error) error { - rs, err := c.req("PROPPATCH", path, strings.NewReader(body), func(rq *http.Request) { - 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") - // TODO add support for 'gzip,deflate;q=0.8,q=0.7' - rq.Header.Add("Accept-Encoding", "") - }) - if err != nil { - return err - } - defer rs.Body.Close() - - if rs.StatusCode != 207 { - return NewPathError("PROPPATCH", 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: - // TODO handle multistat errors, worst case ... - log.Printf("TODO handle %s - %s multistatus result %s\n", 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) (status int, err error) { - rs, err := c.req("PUT", path, stream, nil) - 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) -} diff --git a/util/sync/webdavClient/utils.go b/util/sync/webdavClient/utils.go deleted file mode 100644 index e1d4c678..00000000 --- a/util/sync/webdavClient/utils.go +++ /dev/null @@ -1,113 +0,0 @@ -package webdavClient - -import ( - "bytes" - "encoding/xml" - "io" - "net/url" - "strconv" - "strings" - "time" -) - -// PathEscape escapes all segments of a given path -func PathEscape(path string) string { - s := strings.Split(path, "/") - for i, e := range s { - s[i] = url.PathEscape(e) - } - return strings.Join(s, "/") -} - -// FixSlash appends a trailing / to our string -func FixSlash(s string) string { - if !strings.HasSuffix(s, "/") { - s += "/" - } - return s -} - -// FixSlashes appends and prepends a / if they are missing -func FixSlashes(s string) string { - if !strings.HasPrefix(s, "/") { - s = "/" + s - } - - return FixSlash(s) -} - -// Join joins two paths -func Join(path0 string, path1 string) string { - return strings.TrimSuffix(path0, "/") + "/" + strings.TrimPrefix(path1, "/") -} - -// String pulls a string out of our io.Reader -func String(r io.Reader) string { - buf := new(bytes.Buffer) - // TODO - make String return an error as well - _, _ = buf.ReadFrom(r) - return buf.String() -} - -func parseUint(s *string) uint { - if n, e := strconv.ParseUint(*s, 10, 32); e == nil { - return uint(n) - } - return 0 -} - -func parseInt64(s *string) int64 { - if n, e := strconv.ParseInt(*s, 10, 64); e == nil { - return n - } - return 0 -} - -func parseModified(s *string) time.Time { - if t, e := time.Parse(time.RFC1123, *s); e == nil { - return t - } - return time.Unix(0, 0) -} - -func parseXML(data io.Reader, resp interface{}, parse func(resp interface{}) error) error { - decoder := xml.NewDecoder(data) - for t, _ := decoder.Token(); t != nil; t, _ = decoder.Token() { - switch se := t.(type) { - case xml.StartElement: - if se.Name.Local == "response" { - if e := decoder.DecodeElement(resp, &se); e == nil { - if err := parse(resp); err != nil { - return err - } - } - } - } - } - return nil -} - -// limitedReadCloser wraps a io.ReadCloser and limits the number of bytes that can be read from it. -type limitedReadCloser struct { - rc io.ReadCloser - remaining int -} - -func (l *limitedReadCloser) Read(buf []byte) (int, error) { - if l.remaining <= 0 { - return 0, io.EOF - } - - if len(buf) > l.remaining { - buf = buf[0:l.remaining] - } - - n, err := l.rc.Read(buf) - l.remaining -= n - - return n, err -} - -func (l *limitedReadCloser) Close() error { - return l.rc.Close() -} diff --git a/util/sync/webdavClient/utils_test.go b/util/sync/webdavClient/utils_test.go deleted file mode 100644 index 74cb2506..00000000 --- a/util/sync/webdavClient/utils_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package webdavClient - -import ( - "fmt" - "net/url" - "testing" -) - -func TestJoin(t *testing.T) { - eq(t, "/", "", "") - eq(t, "/", "/", "/") - eq(t, "/foo", "", "/foo") - eq(t, "foo/foo", "foo/", "/foo") - eq(t, "foo/foo", "foo/", "foo") -} - -func eq(t *testing.T, expected string, s0 string, s1 string) { - s := Join(s0, s1) - if s != expected { - t.Error("For", "'"+s0+"','"+s1+"'", "expeted", "'"+expected+"'", "got", "'"+s+"'") - } -} - -func ExamplePathEscape() { - fmt.Println(PathEscape("")) - fmt.Println(PathEscape("/")) - fmt.Println(PathEscape("/web")) - fmt.Println(PathEscape("/web/")) - fmt.Println(PathEscape("/w e b/d a v/s%u&c#k:s/")) - - // Output: - // - // / - // /web - // /web/ - // /w%20e%20b/d%20a%20v/s%25u&c%23k:s/ -} - -func TestEscapeURL(t *testing.T) { - ex := "https://foo.com/w%20e%20b/d%20a%20v/s%25u&c%23k:s/" - u, _ := url.Parse("https://foo.com" + PathEscape("/w e b/d a v/s%u&c#k:s/")) - if ex != u.String() { - t.Error("expected: " + ex + " got: " + u.String()) - } -} - -func TestFixSlashes(t *testing.T) { - expected := "/" - - if got := FixSlashes(""); got != expected { - t.Errorf("expected: %q, got: %q", expected, got) - } - - expected = "/path/" - - if got := FixSlashes("path"); got != expected { - t.Errorf("expected: %q, got: %q", expected, got) - } - - if got := FixSlashes("/path"); got != expected { - t.Errorf("expected: %q, got: %q", expected, got) - } - - if got := FixSlashes("path/"); got != expected { - t.Errorf("expected: %q, got: %q", expected, got) - } -}