[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\nHello Collaborators,\n\n**Describe the bug**\nA short description of what you think the bug is.\n\n**Software**\n - OS:\n - Golang:\n - Version:\n\n**To Reproduce**\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected**\nA short description of what you expected to happen.\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/workflows/artifacts.yml",
    "content": "name: Build Artifacts\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - master\n    paths-ignore:\n      - \"**.md\"\n\njobs:\n  build_artifacts:\n    name: Build Artifcats\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        goos:\n          - linux\n          - windows\n          - darwin\n        goarch:\n          - amd64\n          - arm64\n    steps:\n      - name: Set up Go\n        uses: actions/setup-go@v6\n        with:\n          check-latest: true\n          go-version: \"1.25\"\n      - name: Check out code into the Go module directory\n        uses: actions/checkout@v6\n      - name: Get dependencies\n        run: go get ./...\n      - name: Build Client (${{ matrix.goos }}-${{ matrix.goarch }})\n        env:\n          GOOS: ${{ matrix.goos }}\n          GOARCH: ${{ matrix.goarch }}\n        run: go build -v -o ./bin/gowebdav-${{ matrix.goos }}-${{ matrix.goarch }} ./cmd/gowebdav/main.go\n      - name: Rename Windows Binary\n        if: ${{ matrix.goos == 'windows' }}\n        env:\n          FNAME: ./bin/gowebdav-${{ matrix.goos }}-${{ matrix.goarch }}\n        run: mv ${{ env.FNAME }} ${{ env.FNAME }}.exe\n      - name: Upload Artifcats\n        uses: actions/upload-artifact@v6\n        with:\n          name: ${{ matrix.goos }}-${{ matrix.goarch }}\n          path: ./bin/\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Unit Tests\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - \"*\"\n    paths-ignore:\n      - \"**.md\"\n  pull_request:\n\njobs:\n  unit_tests:\n    name: Unit Tests\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        goversion:\n          - \"1.25\"\n          - \"1.24\"\n    steps:\n      - name: Set up Go\n        uses: actions/setup-go@v6\n        with:\n          check-latest: true\n          go-version: ${{ matrix.goversion }}\n      - name: Check out code into the Go module directory\n        uses: actions/checkout@v6\n      - name: Get dependencies\n        run: go get ./...\n      - name: Run Unit Tests\n        run: go test -modfile=go_test.mod -v -cover -race ./...\n"
  },
  {
    "path": ".gitignore",
    "content": "# Folders to ignore\n/src\n/bin\n/pkg\n/gowebdav\n/.idea\n\n# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n\n# Test binary, build with `go test -c`\n*.test\n\n# Output of the go coverage tool, specifically when used with LiteIDE\n*.out\n\n.vscode/"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2014, Studio B12 GmbH\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its contributors\n   may be used to endorse or promote products derived from this software without\n   specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "Makefile",
    "content": "BIN := gowebdav\nSRC := $(wildcard *.go) cmd/gowebdav/main.go\n\nall: test cmd\n\ncmd: ${BIN}\n\n${BIN}: ${SRC}\n\tgo build -o $@ ./cmd/gowebdav\n\ntest:\n\tgo test -modfile=go_test.mod -v -short -cover ./...\n\napi: .go/bin/godoc2md\n\t@sed '/^## API$$/,$$d' -i README.md\n\t@echo '## API' >> README.md\n\t@$< github.com/studio-b12/gowebdav | sed '/^$$/N;/^\\n$$/D' |\\\n\tsed '2d' |\\\n\tsed 's/\\/src\\/github.com\\/studio-b12\\/gowebdav\\//https:\\/\\/github.com\\/studio-b12\\/gowebdav\\/blob\\/master\\//g' |\\\n\tsed 's/\\/src\\/target\\//https:\\/\\/github.com\\/studio-b12\\/gowebdav\\/blob\\/master\\//g' |\\\n\tsed 's/^#/##/g' >> README.md\n\ncheck: .go/bin/gocyclo\n\tgofmt -w -s $(SRC)\n\t@echo\n\t.go/bin/gocyclo -over 15 .\n\t@echo\n\tgo vet -modfile=go_test.mod ./...\n\n\n.go/bin/godoc2md:\n\t@mkdir -p $(@D)\n\t@GOPATH=\"$(CURDIR)/.go\" go install github.com/davecheney/godoc2md@latest\n\n.go/bin/gocyclo:\n\t@mkdir -p $(@D)\n\t@GOPATH=\"$(CURDIR)/.go\" go install github.com/fzipp/gocyclo/cmd/gocyclo@latest\n\nclean:\n\t@rm -f ${BIN}\n\n.PHONY: all cmd clean test api check\n"
  },
  {
    "path": "README.md",
    "content": "# GoWebDAV\n\n[![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)\n[![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)\n[![GoDoc](https://godoc.org/github.com/studio-b12/gowebdav?status.svg)](https://godoc.org/github.com/studio-b12/gowebdav)\n[![Go Report Card](https://goreportcard.com/badge/github.com/studio-b12/gowebdav)](https://goreportcard.com/report/github.com/studio-b12/gowebdav)\n\nA pure Golang WebDAV client library that comes with a [reference implementation](https://github.com/studio-b12/gowebdav/tree/master/cmd/gowebdav).\n\n## Features at a glance\n\nOur `gowebdav` library allows to perform following actions on the remote WebDAV server:\n\n* [create path](#create-path-on-a-webdav-server)\n* [get files list](#get-files-list)\n* [download file](#download-file-to-byte-array)\n* [upload file](#upload-file-from-byte-array)\n* [get information about specified file/folder](#get-information-about-specified-filefolder)\n* [move file to another location](#move-file-to-another-location)\n* [copy file to another location](#copy-file-to-another-location)\n* [delete file](#delete-file)\n\nIt also provides an [authentication API](#type-authenticator) that makes it easy to encapsulate and control complex authentication challenges.\nThe default implementation negotiates the algorithm based on the user's preferences and the methods offered by the remote server.\n\nOut-of-box authentication support for:\n\n* [BasicAuth](https://en.wikipedia.org/wiki/Basic_access_authentication)\n* [DigestAuth](https://en.wikipedia.org/wiki/Digest_access_authentication)\n* [MS-PASS](https://github.com/studio-b12/gowebdav/pull/70#issuecomment-1421713726)\n* [WIP Kerberos](https://github.com/studio-b12/gowebdav/pull/71#issuecomment-1416465334)\n* [WIP Bearer Token](https://github.com/studio-b12/gowebdav/issues/61)\n\n## Usage\n\nFirst of all you should create `Client` instance using `NewClient()` function:\n\n```go\nroot := \"https://webdav.mydomain.me\"\nuser := \"user\"\npassword := \"password\"\n\nc := gowebdav.NewClient(root, user, password)\nc.Connect()\n// kick of your work!\n```\n\nAfter you can use this `Client` to perform actions, described below.\n\n**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!\n\n### Create path on a WebDAV server\n```go\nerr := c.Mkdir(\"folder\", 0644)\n```\nIn case you want to create several folders you can use `c.MkdirAll()`:\n```go\nerr := c.MkdirAll(\"folder/subfolder/subfolder2\", 0644)\n```\n\n### Get files list\n```go\nfiles, _ := c.ReadDir(\"folder/subfolder\")\nfor _, file := range files {\n    //notice that [file] has os.FileInfo type\n    fmt.Println(file.Name())\n}\n```\n\n### Download file to byte array\n```go\nwebdavFilePath := \"folder/subfolder/file.txt\"\nlocalFilePath := \"/tmp/webdav/file.txt\"\n\nbytes, _ := c.Read(webdavFilePath)\nos.WriteFile(localFilePath, bytes, 0644)\n```\n\n### Download file via reader\nAlso you can use `c.ReadStream()` method:\n```go\nwebdavFilePath := \"folder/subfolder/file.txt\"\nlocalFilePath := \"/tmp/webdav/file.txt\"\n\nreader, _ := c.ReadStream(webdavFilePath)\n\nfile, _ := os.Create(localFilePath)\ndefer file.Close()\n\nio.Copy(file, reader)\n```\n\n### Upload file from byte array\n```go\nwebdavFilePath := \"folder/subfolder/file.txt\"\nlocalFilePath := \"/tmp/webdav/file.txt\"\n\nbytes, _ := os.ReadFile(localFilePath)\n\nc.Write(webdavFilePath, bytes, 0644)\n```\n\n### Upload file via writer\n```go\nwebdavFilePath := \"folder/subfolder/file.txt\"\nlocalFilePath := \"/tmp/webdav/file.txt\"\n\nfile, _ := os.Open(localFilePath)\ndefer file.Close()\n\nc.WriteStream(webdavFilePath, file, 0644)\n```\n\nFor non-seekable stream, this will read data into memory first\nto discover content length.\n\n### Upload file via writer with known length\n```go\nwebdavFilePath := \"folder/subfolder/file.txt\"\n\nbytes := []byte{0x20, 0x20}\n\nc.WriteStreamWithLength(webdavFilePath, bytes.NewBuffer(bytes), int64(len(data)), 0644)\n```\n\n### Get information about specified file/folder\n```go\nwebdavFilePath := \"folder/subfolder/file.txt\"\n\ninfo := c.Stat(webdavFilePath)\n//notice that [info] has os.FileInfo type\nfmt.Println(info)\n```\n\n### Move file to another location\n```go\noldPath := \"folder/subfolder/file.txt\"\nnewPath := \"folder/subfolder/moved.txt\"\nisOverwrite := true\n\nc.Rename(oldPath, newPath, isOverwrite)\n```\n\n### Copy file to another location\n```go\noldPath := \"folder/subfolder/file.txt\"\nnewPath := \"folder/subfolder/file-copy.txt\"\nisOverwrite := true\n\nc.Copy(oldPath, newPath, isOverwrite)\n```\n\n### Delete file\n```go\nwebdavFilePath := \"folder/subfolder/file.txt\"\n\nc.Remove(webdavFilePath)\n```\n\n## Links\n\nMore details about WebDAV server you can read from following resources:\n\n* [RFC 4918 - HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)](https://tools.ietf.org/html/rfc4918)\n* [RFC 5689 - Extended MKCOL for Web Distributed Authoring and Versioning (WebDAV)](https://tools.ietf.org/html/rfc5689)\n* [RFC 2616 - HTTP/1.1 Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html \"HTTP/1.1 Status Code Definitions\")\n* [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\")\n\n**NOTICE**: RFC 2518 is obsoleted by RFC 4918 in June 2007\n\n## Contributing\nAll 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!\n\n## License\nThis library is distributed under the BSD 3-Clause license found in the [LICENSE](https://github.com/studio-b12/gowebdav/blob/master/LICENSE) file.\n\n## Stargazers over time\n[![Stargazers over time](https://starchart.cc/studio-b12/gowebdav.svg?variant=adaptive)](https://starchart.cc/studio-b12/gowebdav)\n\n## API\n\n`import \"github.com/studio-b12/gowebdav\"`\n\n* [Overview](#pkg-overview)\n* [Index](#pkg-index)\n* [Examples](#pkg-examples)\n* [Subdirectories](#pkg-subdirectories)\n\n### <a name=\"pkg-overview\">Overview</a>\nPackage gowebdav is a WebDAV client library with a command line tool\nincluded.\n\n### <a name=\"pkg-index\">Index</a>\n* [Constants](#pkg-constants)\n* [Variables](#pkg-variables)\n* [func FixSlash(s string) string](#FixSlash)\n* [func FixSlashes(s string) string](#FixSlashes)\n* [func IsErrCode(err error, code int) bool](#IsErrCode)\n* [func IsErrNotFound(err error) bool](#IsErrNotFound)\n* [func Join(path0 string, path1 string) string](#Join)\n* [func NewPathError(op string, path string, statusCode int) error](#NewPathError)\n* [func NewPathErrorErr(op string, path string, err error) error](#NewPathErrorErr)\n* [func PathEscape(path string) string](#PathEscape)\n* [func ReadConfig(uri, netrc string) (string, string)](#ReadConfig)\n* [func String(r io.Reader) string](#String)\n* [type AuthFactory](#AuthFactory)\n* [type Authenticator](#Authenticator)\n  * [func NewDigestAuth(login, secret string, rs *http.Response) (Authenticator, error)](#NewDigestAuth)\n  * [func NewPassportAuth(c *http.Client, user, pw, partnerURL string, header *http.Header) (Authenticator, error)](#NewPassportAuth)\n* [type Authorizer](#Authorizer)\n  * [func NewAutoAuth(login string, secret string) Authorizer](#NewAutoAuth)\n  * [func NewEmptyAuth() Authorizer](#NewEmptyAuth)\n  * [func NewPreemptiveAuth(auth Authenticator) Authorizer](#NewPreemptiveAuth)\n* [type BasicAuth](#BasicAuth)\n  * [func (b *BasicAuth) Authorize(c *http.Client, rq *http.Request, path string) error](#BasicAuth.Authorize)\n  * [func (b *BasicAuth) Clone() Authenticator](#BasicAuth.Clone)\n  * [func (b *BasicAuth) Close() error](#BasicAuth.Close)\n  * [func (b *BasicAuth) String() string](#BasicAuth.String)\n  * [func (b *BasicAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)](#BasicAuth.Verify)\n* [type Client](#Client)\n  * [func NewAuthClient(uri string, auth Authorizer) *Client](#NewAuthClient)\n  * [func NewClient(uri, user, pw string) *Client](#NewClient)\n  * [func (c *Client) Connect() error](#Client.Connect)\n  * [func (c *Client) Copy(oldpath, newpath string, overwrite bool) error](#Client.Copy)\n  * [func (c *Client) Mkdir(path string, _ os.FileMode) (err error)](#Client.Mkdir)\n  * [func (c *Client) MkdirAll(path string, _ os.FileMode) (err error)](#Client.MkdirAll)\n  * [func (c *Client) Read(path string) ([]byte, error)](#Client.Read)\n  * [func (c *Client) ReadDir(path string) ([]os.FileInfo, error)](#Client.ReadDir)\n  * [func (c *Client) ReadStream(path string) (io.ReadCloser, error)](#Client.ReadStream)\n  * [func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadCloser, error)](#Client.ReadStreamRange)\n  * [func (c *Client) Remove(path string) error](#Client.Remove)\n  * [func (c *Client) RemoveAll(path string) error](#Client.RemoveAll)\n  * [func (c *Client) Rename(oldpath, newpath string, overwrite bool) error](#Client.Rename)\n  * [func (c *Client) SetHeader(key, value string)](#Client.SetHeader)\n  * [func (c *Client) SetInterceptor(interceptor func(method string, rq *http.Request))](#Client.SetInterceptor)\n  * [func (c *Client) SetJar(jar http.CookieJar)](#Client.SetJar)\n  * [func (c *Client) SetTimeout(timeout time.Duration)](#Client.SetTimeout)\n  * [func (c *Client) SetTransport(transport http.RoundTripper)](#Client.SetTransport)\n  * [func (c *Client) Stat(path string) (os.FileInfo, error)](#Client.Stat)\n  * [func (c *Client) Write(path string, data []byte, _ os.FileMode) (err error)](#Client.Write)\n  * [func (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err error)](#Client.WriteStream)\n  * [func (c *Client) WriteStreamWithLength(path string, stream io.Reader, contentLength int64, _ os.FileMode) (err error)](#Client.WriteStreamWithLength)\n* [type DigestAuth](#DigestAuth)\n  * [func (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) error](#DigestAuth.Authorize)\n  * [func (d *DigestAuth) Clone() Authenticator](#DigestAuth.Clone)\n  * [func (d *DigestAuth) Close() error](#DigestAuth.Close)\n  * [func (d *DigestAuth) String() string](#DigestAuth.String)\n  * [func (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)](#DigestAuth.Verify)\n* [type File](#File)\n  * [func (f File) ContentType() string](#File.ContentType)\n  * [func (f File) ETag() string](#File.ETag)\n  * [func (f File) IsDir() bool](#File.IsDir)\n  * [func (f File) ModTime() time.Time](#File.ModTime)\n  * [func (f File) Mode() os.FileMode](#File.Mode)\n  * [func (f File) Name() string](#File.Name)\n  * [func (f File) Path() string](#File.Path)\n  * [func (f File) Size() int64](#File.Size)\n  * [func (f File) String() string](#File.String)\n  * [func (f File) Sys() interface{}](#File.Sys)\n* [type PassportAuth](#PassportAuth)\n  * [func (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error](#PassportAuth.Authorize)\n  * [func (p *PassportAuth) Clone() Authenticator](#PassportAuth.Clone)\n  * [func (p *PassportAuth) Close() error](#PassportAuth.Close)\n  * [func (p *PassportAuth) String() string](#PassportAuth.String)\n  * [func (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)](#PassportAuth.Verify)\n* [type StatusError](#StatusError)\n  * [func (se StatusError) Error() string](#StatusError.Error)\n\n##### <a name=\"pkg-examples\">Examples</a>\n* [PathEscape](#example_PathEscape)\n\n##### <a name=\"pkg-files\">Package files</a>\n[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) \n\n### <a name=\"pkg-constants\">Constants</a>\n``` go\nconst XInhibitRedirect = \"X-Gowebdav-Inhibit-Redirect\"\n```\n\n### <a name=\"pkg-variables\">Variables</a>\n``` go\nvar ErrAuthChanged = errors.New(\"authentication failed, change algorithm\")\n```\nErrAuthChanged must be returned from the Verify method as an error\nto trigger a re-authentication / negotiation with a new authenticator.\n\n``` go\nvar ErrTooManyRedirects = errors.New(\"stopped after 10 redirects\")\n```\nErrTooManyRedirects will be used as return error if a request exceeds 10 redirects.\n\n### <a name=\"FixSlash\">func</a> [FixSlash](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=354:384#L23)\n``` go\nfunc FixSlash(s string) string\n```\nFixSlash appends a trailing / to our string\n\n### <a name=\"FixSlashes\">func</a> [FixSlashes](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=506:538#L31)\n``` go\nfunc FixSlashes(s string) string\n```\nFixSlashes appends and prepends a / if they are missing\n\n### <a name=\"IsErrCode\">func</a> [IsErrCode](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=740:780#L29)\n``` go\nfunc IsErrCode(err error, code int) bool\n```\nIsErrCode returns true if the given error\nis an os.PathError wrapping a StatusError\nwith the given status code.\n\n### <a name=\"IsErrNotFound\">func</a> [IsErrNotFound](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=972:1006#L39)\n``` go\nfunc IsErrNotFound(err error) bool\n```\nIsErrNotFound is shorthand for IsErrCode\nfor status 404.\n\n### <a name=\"Join\">func</a> [Join](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=639:683#L40)\n``` go\nfunc Join(path0 string, path1 string) string\n```\nJoin joins two paths\n\n### <a name=\"NewPathError\">func</a> [NewPathError](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=1040:1103#L43)\n``` go\nfunc NewPathError(op string, path string, statusCode int) error\n```\n\n### <a name=\"NewPathErrorErr\">func</a> [NewPathErrorErr](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=1194:1255#L51)\n``` go\nfunc NewPathErrorErr(op string, path string, err error) error\n```\n\n### <a name=\"PathEscape\">func</a> [PathEscape](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=153:188#L14)\n``` go\nfunc PathEscape(path string) string\n```\nPathEscape escapes all segments of a given path\n\n### <a name=\"ReadConfig\">func</a> [ReadConfig](https://github.com/studio-b12/gowebdav/blob/master/netrc.go?s=428:479#L27)\n``` go\nfunc ReadConfig(uri, netrc string) (string, string)\n```\nReadConfig reads login and password configuration from ~/.netrc\nmachine foo.com login username password 123456\n\n### <a name=\"String\">func</a> [String](https://github.com/studio-b12/gowebdav/blob/master/utils.go?s=813:844#L45)\n``` go\nfunc String(r io.Reader) string\n```\nString pulls a string out of our io.Reader\n\n### <a name=\"AuthFactory\">type</a> [AuthFactory](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=150:251#L13)\n``` go\ntype AuthFactory func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error)\n```\nAuthFactory prototype function to create a new Authenticator\n\n### <a name=\"Authenticator\">type</a> [Authenticator](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=2156:2696#L56)\n``` go\ntype Authenticator interface {\n    // Authorizes a request. Usually by adding some authorization headers.\n    Authorize(c *http.Client, rq *http.Request, path string) error\n    // Verifies the response if the authorization was successful.\n    // May trigger some round trips to pass the authentication.\n    // May also trigger a new Authenticator negotiation by returning `ErrAuthChenged`\n    Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)\n    // Creates a copy of the underlying Authenticator.\n    Clone() Authenticator\n    io.Closer\n}\n```\nAn Authenticator implements a specific way to authorize requests.\nEach request is bound to a separate Authenticator instance.\n\nThe authentication flow itself is broken down into `Authorize`\nand `Verify` steps. The former method runs before, and the latter\nruns after the `Request` is submitted.\nThis makes it easy to encapsulate and control complex\nauthentication challenges.\n\nSome authentication flows causing authentication round trips,\nwhich can be archived by returning the `redo` of the Verify\nmethod. `True` restarts the authentication process for the\ncurrent action: A new `Request` is spawned, which must be\nauthorized, sent, and re-verified again, until the action\nis successfully submitted.\nThe preferred way is to handle the authentication ping-pong\nwithin `Verify`, and then `redo` with fresh credentials.\n\nThe result of the `Verify` method can also trigger an\n`Authenticator` change by returning the `ErrAuthChanged`\nas an error. Depending on the `Authorizer` this may trigger\nan `Authenticator` negotiation.\n\nSet the `XInhibitRedirect` header to '1' in the `Authorize`\nmethod to get control over request redirection.\nAttention! You must handle the incoming request yourself.\n\nTo store a shared session state the `Clone` method **must**\nreturn a new instance, initialized with the shared state.\n\n#### <a name=\"NewDigestAuth\">func</a> [NewDigestAuth](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=324:406#L21)\n``` go\nfunc NewDigestAuth(login, secret string, rs *http.Response) (Authenticator, error)\n```\nNewDigestAuth creates a new instance of our Digest Authenticator\n\n#### <a name=\"NewPassportAuth\">func</a> [NewPassportAuth](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=386:495#L21)\n``` go\nfunc NewPassportAuth(c *http.Client, user, pw, partnerURL string, header *http.Header) (Authenticator, error)\n```\nconstructor for PassportAuth creates a new PassportAuth object and\nautomatically authenticates against the given partnerURL\n\n### <a name=\"Authorizer\">type</a> [Authorizer](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=349:764#L17)\n``` go\ntype Authorizer interface {\n    // Creates a new Authenticator Shim per request.\n    // It may track request related states and perform payload buffering\n    // for authentication round trips.\n    // The underlying Authenticator will perform the real authentication.\n    NewAuthenticator(body io.Reader) (Authenticator, io.Reader)\n    // Registers a new Authenticator factory to a key.\n    AddAuthenticator(key string, fn AuthFactory)\n}\n```\nAuthorizer our Authenticator factory which creates an\n`Authenticator` per action/request.\n\n#### <a name=\"NewAutoAuth\">func</a> [NewAutoAuth](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=3790:3846#L109)\n``` go\nfunc NewAutoAuth(login string, secret string) Authorizer\n```\nNewAutoAuth creates an auto Authenticator factory.\nIt negotiates the default authentication method\nbased on the order of the registered Authenticators\nand the remotely offered authentication methods.\nFirst In, First Out.\n\n#### <a name=\"NewEmptyAuth\">func</a> [NewEmptyAuth](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=4695:4725#L132)\n``` go\nfunc NewEmptyAuth() Authorizer\n```\nNewEmptyAuth creates an empty Authenticator factory\nThe order of adding the Authenticator matters.\nFirst In, First Out.\nIt offers the `NewAutoAuth` features.\n\n#### <a name=\"NewPreemptiveAuth\">func</a> [NewPreemptiveAuth](https://github.com/studio-b12/gowebdav/blob/master/auth.go?s=5301:5354#L148)\n``` go\nfunc NewPreemptiveAuth(auth Authenticator) Authorizer\n```\nNewPreemptiveAuth creates a preemptive Authenticator\nThe preemptive authorizer uses the provided Authenticator\nfor every request regardless of any `Www-Authenticate` header.\n\nIt may only have one authentication method,\nso calling `AddAuthenticator` **will panic**!\n\nLook out!! This offers the skinniest and slickest implementation\nwithout any synchronisation!!\nStill applicable with `BasicAuth` within go routines.\n\n### <a name=\"BasicAuth\">type</a> [BasicAuth](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=94:145#L9)\n``` go\ntype BasicAuth struct {\n    // contains filtered or unexported fields\n}\n\n```\nBasicAuth structure holds our credentials\n\n#### <a name=\"BasicAuth.Authorize\">func</a> (\\*BasicAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=180:262#L15)\n``` go\nfunc (b *BasicAuth) Authorize(c *http.Client, rq *http.Request, path string) error\n```\nAuthorize the current request\n\n#### <a name=\"BasicAuth.Clone\">func</a> (\\*BasicAuth) [Clone](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=666:707#L34)\n``` go\nfunc (b *BasicAuth) Clone() Authenticator\n```\nClone creates a Copy of itself\n\n#### <a name=\"BasicAuth.Close\">func</a> (\\*BasicAuth) [Close](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=581:614#L29)\n``` go\nfunc (b *BasicAuth) Close() error\n```\nClose cleans up all resources\n\n#### <a name=\"BasicAuth.String\">func</a> (\\*BasicAuth) [String](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=778:813#L40)\n``` go\nfunc (b *BasicAuth) String() string\n```\nString toString\n\n#### <a name=\"BasicAuth.Verify\">func</a> (\\*BasicAuth) [Verify](https://github.com/studio-b12/gowebdav/blob/master/basicAuth.go?s=352:449#L21)\n``` go\nfunc (b *BasicAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)\n```\nVerify verifies if the authentication\n\n### <a name=\"Client\">type</a> [Client](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=220:388#L19)\n``` go\ntype Client struct {\n    // contains filtered or unexported fields\n}\n\n```\nClient defines our structure\n\n#### <a name=\"NewAuthClient\">func</a> [NewAuthClient](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=608:663#L33)\n``` go\nfunc NewAuthClient(uri string, auth Authorizer) *Client\n```\nNewAuthClient creates a new client instance with a custom Authorizer\n\n#### <a name=\"NewClient\">func</a> [NewClient](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=436:480#L28)\n``` go\nfunc NewClient(uri, user, pw string) *Client\n```\nNewClient creates a new instance of client\n\n#### <a name=\"Client.Connect\">func</a> (\\*Client) [Connect](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1829:1861#L74)\n``` go\nfunc (c *Client) Connect() error\n```\nConnect connects to our dav server\n\n#### <a name=\"Client.Copy\">func</a> (\\*Client) [Copy](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6815:6883#L310)\n``` go\nfunc (c *Client) Copy(oldpath, newpath string, overwrite bool) error\n```\nCopy copies a file from A to B\n\n#### <a name=\"Client.Mkdir\">func</a> (\\*Client) [Mkdir](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5790:5852#L259)\n``` go\nfunc (c *Client) Mkdir(path string, _ os.FileMode) (err error)\n```\nMkdir makes a directory\n\n#### <a name=\"Client.MkdirAll\">func</a> (\\*Client) [MkdirAll](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6065:6130#L273)\n``` go\nfunc (c *Client) MkdirAll(path string, _ os.FileMode) (err error)\n```\nMkdirAll like mkdir -p, but for webdav\n\n#### <a name=\"Client.Read\">func</a> (\\*Client) [Read](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6989:7039#L315)\n``` go\nfunc (c *Client) Read(path string) ([]byte, error)\n```\nRead reads the contents of a remote file\n\n#### <a name=\"Client.ReadDir\">func</a> (\\*Client) [ReadDir](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=2855:2915#L117)\n``` go\nfunc (c *Client) ReadDir(path string) ([]os.FileInfo, error)\n```\nReadDir reads the contents of a remote directory\n\n#### <a name=\"Client.ReadStream\">func</a> (\\*Client) [ReadStream](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=7350:7413#L333)\n``` go\nfunc (c *Client) ReadStream(path string) (io.ReadCloser, error)\n```\nReadStream reads the stream for a given path\n\n#### <a name=\"Client.ReadStreamRange\">func</a> (\\*Client) [ReadStreamRange](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=8162:8252#L355)\n``` go\nfunc (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadCloser, error)\n```\nReadStreamRange reads the stream representing a subset of bytes for a given path,\nutilizing HTTP Range Requests if the server supports it.\nThe range is expressed as offset from the start of the file and length, for example\noffset=10, length=10 will return bytes 10 through 19.\n\nIf the server does not support partial content requests and returns full content instead,\nthis function will emulate the behavior by skipping `offset` bytes and limiting the result\nto `length`.\n\n#### <a name=\"Client.Remove\">func</a> (\\*Client) [Remove](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5296:5338#L236)\n``` go\nfunc (c *Client) Remove(path string) error\n```\nRemove removes a remote file\n\n#### <a name=\"Client.RemoveAll\">func</a> (\\*Client) [RemoveAll](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=5404:5449#L241)\n``` go\nfunc (c *Client) RemoveAll(path string) error\n```\nRemoveAll removes remote files\n\n#### <a name=\"Client.Rename\">func</a> (\\*Client) [Rename](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=6649:6719#L305)\n``` go\nfunc (c *Client) Rename(oldpath, newpath string, overwrite bool) error\n```\nRename moves a file from A to B\n\n#### <a name=\"Client.SetHeader\">func</a> (\\*Client) [SetHeader](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1092:1137#L49)\n``` go\nfunc (c *Client) SetHeader(key, value string)\n```\nSetHeader lets us set arbitrary headers for a given client\n\n#### <a name=\"Client.SetInterceptor\">func</a> (\\*Client) [SetInterceptor](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1244:1326#L54)\n``` go\nfunc (c *Client) SetInterceptor(interceptor func(method string, rq *http.Request))\n```\nSetInterceptor lets us set an arbitrary interceptor for a given client\n\n#### <a name=\"Client.SetJar\">func</a> (\\*Client) [SetJar](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1727:1770#L69)\n``` go\nfunc (c *Client) SetJar(jar http.CookieJar)\n```\nSetJar exposes the ability to set a cookie jar to the client.\n\n#### <a name=\"Client.SetTimeout\">func</a> (\\*Client) [SetTimeout](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1428:1478#L59)\n``` go\nfunc (c *Client) SetTimeout(timeout time.Duration)\n```\nSetTimeout exposes the ability to set a time limit for requests\n\n#### <a name=\"Client.SetTransport\">func</a> (\\*Client) [SetTransport](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=1571:1629#L64)\n``` go\nfunc (c *Client) SetTransport(transport http.RoundTripper)\n```\nSetTransport exposes the ability to define custom transports\n\n#### <a name=\"Client.Stat\">func</a> (\\*Client) [Stat](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=4241:4296#L184)\n``` go\nfunc (c *Client) Stat(path string) (os.FileInfo, error)\n```\nStat returns the file stats for a specified path\n\n#### <a name=\"Client.Write\">func</a> (\\*Client) [Write](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=9272:9347#L389)\n``` go\nfunc (c *Client) Write(path string, data []byte, _ os.FileMode) (err error)\n```\nWrite writes data to a given path\n\n#### <a name=\"Client.WriteStream\">func</a> (\\*Client) [WriteStream](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=9857:9943#L419)\n``` go\nfunc (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err error)\n```\nWriteStream writes a stream - it will copy to memory for non-seekable streams\n\n#### <a name=\"Client.WriteStreamWithLength\">func</a> (\\*Client) [WriteStreamWithLength](https://github.com/studio-b12/gowebdav/blob/master/client.go?s=10683:10800#L463)\n``` go\nfunc (c *Client) WriteStreamWithLength(path string, stream io.Reader, contentLength int64, _ os.FileMode) (err error)\n```\nWriteStream writes a stream with a known content length\n\n### <a name=\"DigestAuth\">type</a> [DigestAuth](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=157:254#L14)\n``` go\ntype DigestAuth struct {\n    // contains filtered or unexported fields\n}\n\n```\nDigestAuth structure holds our credentials\n\n#### <a name=\"DigestAuth.Authorize\">func</a> (\\*DigestAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=525:608#L26)\n``` go\nfunc (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) error\n```\nAuthorize the current request\n\n#### <a name=\"DigestAuth.Clone\">func</a> (\\*DigestAuth) [Clone](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=1303:1345#L54)\n``` go\nfunc (d *DigestAuth) Clone() Authenticator\n```\nClone creates a copy of itself\n\n#### <a name=\"DigestAuth.Close\">func</a> (\\*DigestAuth) [Close](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=1217:1251#L49)\n``` go\nfunc (d *DigestAuth) Close() error\n```\nClose cleans up all resources\n\n#### <a name=\"DigestAuth.String\">func</a> (\\*DigestAuth) [String](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=1541:1577#L63)\n``` go\nfunc (d *DigestAuth) String() string\n```\nString toString\n\n#### <a name=\"DigestAuth.Verify\">func</a> (\\*DigestAuth) [Verify](https://github.com/studio-b12/gowebdav/blob/master/digestAuth.go?s=912:1010#L36)\n``` go\nfunc (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)\n```\nVerify checks for authentication issues and may trigger a re-authentication\n\n### <a name=\"File\">type</a> [File](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=93:253#L10)\n``` go\ntype File struct {\n    // contains filtered or unexported fields\n}\n\n```\nFile is our structure for a given file\n\n#### <a name=\"File.ContentType\">func</a> (File) [ContentType](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=476:510#L31)\n``` go\nfunc (f File) ContentType() string\n```\nContentType returns the content type of a file\n\n#### <a name=\"File.ETag\">func</a> (File) [ETag](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=929:956#L56)\n``` go\nfunc (f File) ETag() string\n```\nETag returns the ETag of a file\n\n#### <a name=\"File.IsDir\">func</a> (File) [IsDir](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=1035:1061#L61)\n``` go\nfunc (f File) IsDir() bool\n```\nIsDir let us see if a given file is a directory or not\n\n#### <a name=\"File.ModTime\">func</a> (File) [ModTime](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=836:869#L51)\n``` go\nfunc (f File) ModTime() time.Time\n```\nModTime returns the modified time of a file\n\n#### <a name=\"File.Mode\">func</a> (File) [Mode](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=665:697#L41)\n``` go\nfunc (f File) Mode() os.FileMode\n```\nMode will return the mode of a given file\n\n#### <a name=\"File.Name\">func</a> (File) [Name](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=378:405#L26)\n``` go\nfunc (f File) Name() string\n```\nName returns the name of a file\n\n#### <a name=\"File.Path\">func</a> (File) [Path](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=295:322#L21)\n``` go\nfunc (f File) Path() string\n```\nPath returns the full path of a file\n\n#### <a name=\"File.Size\">func</a> (File) [Size](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=573:599#L36)\n``` go\nfunc (f File) Size() int64\n```\nSize returns the size of a file\n\n#### <a name=\"File.String\">func</a> (File) [String](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=1183:1212#L71)\n``` go\nfunc (f File) String() string\n```\nString lets us see file information\n\n#### <a name=\"File.Sys\">func</a> (File) [Sys](https://github.com/studio-b12/gowebdav/blob/master/file.go?s=1095:1126#L66)\n``` go\nfunc (f File) Sys() interface{}\n```\nSys ????\n\n### <a name=\"PassportAuth\">type</a> [PassportAuth](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=125:254#L12)\n``` go\ntype PassportAuth struct {\n    // contains filtered or unexported fields\n}\n\n```\nPassportAuth structure holds our credentials\n\n#### <a name=\"PassportAuth.Authorize\">func</a> (\\*PassportAuth) [Authorize](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=690:775#L32)\n``` go\nfunc (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error\n```\nAuthorize the current request\n\n#### <a name=\"PassportAuth.Clone\">func</a> (\\*PassportAuth) [Clone](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=1701:1745#L69)\n``` go\nfunc (p *PassportAuth) Clone() Authenticator\n```\nClone creates a Copy of itself\n\n#### <a name=\"PassportAuth.Close\">func</a> (\\*PassportAuth) [Close](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=1613:1649#L64)\n``` go\nfunc (p *PassportAuth) Close() error\n```\nClose cleans up all resources\n\n#### <a name=\"PassportAuth.String\">func</a> (\\*PassportAuth) [String](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=2048:2086#L83)\n``` go\nfunc (p *PassportAuth) String() string\n```\nString toString\n\n#### <a name=\"PassportAuth.Verify\">func</a> (\\*PassportAuth) [Verify](https://github.com/studio-b12/gowebdav/blob/master/passportAuth.go?s=1075:1175#L46)\n``` go\nfunc (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error)\n```\nVerify verifies if the authentication is good\n\n### <a name=\"StatusError\">type</a> [StatusError](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=499:538#L18)\n``` go\ntype StatusError struct {\n    Status int\n}\n\n```\nStatusError implements error and wraps\nan erroneous status code.\n\n#### <a name=\"StatusError.Error\">func</a> (StatusError) [Error](https://github.com/studio-b12/gowebdav/blob/master/errors.go?s=540:576#L22)\n``` go\nfunc (se StatusError) Error() string\n```\n\n- - -\nGenerated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md)\n"
  },
  {
    "path": "auth.go",
    "content": "package gowebdav\n\nimport (\n\t\"bytes\"\n\t\"errors\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"sync\"\n)\n\n// AuthFactory prototype function to create a new Authenticator\ntype AuthFactory func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error)\n\n// Authorizer our Authenticator factory which creates an\n// `Authenticator` per action/request.\ntype Authorizer interface {\n\t// Creates a new Authenticator Shim per request.\n\t// It may track request related states and perform payload buffering\n\t// for authentication round trips.\n\t// The underlying Authenticator will perform the real authentication.\n\tNewAuthenticator(body io.Reader) (Authenticator, io.Reader)\n\t// Registers a new Authenticator factory to a key.\n\tAddAuthenticator(key string, fn AuthFactory)\n}\n\n// An Authenticator implements a specific way to authorize requests.\n// Each request is bound to a separate Authenticator instance.\n//\n// The authentication flow itself is broken down into `Authorize`\n// and `Verify` steps. The former method runs before, and the latter\n// runs after the `Request` is submitted.\n// This makes it easy to encapsulate and control complex\n// authentication challenges.\n//\n// Some authentication flows causing authentication round trips,\n// which can be archived by returning the `redo` of the Verify\n// method. `True` restarts the authentication process for the\n// current action: A new `Request` is spawned, which must be\n// authorized, sent, and re-verified again, until the action\n// is successfully submitted.\n// The preferred way is to handle the authentication ping-pong\n// within `Verify`, and then `redo` with fresh credentials.\n//\n// The result of the `Verify` method can also trigger an\n// `Authenticator` change by returning the `ErrAuthChanged`\n// as an error. Depending on the `Authorizer` this may trigger\n// an `Authenticator` negotiation.\n//\n// Set the `XInhibitRedirect` header to '1' in the `Authorize`\n// method to get control over request redirection.\n// Attention! You must handle the incoming request yourself.\n//\n// To store a shared session state the `Clone` method **must**\n// return a new instance, initialized with the shared state.\ntype Authenticator interface {\n\t// Authorizes a request. Usually by adding some authorization headers.\n\tAuthorize(c *http.Client, rq *http.Request, path string) error\n\t// Verifies the response if the authorization was successful.\n\t// May trigger some round trips to pass the authentication.\n\t// May also trigger a new Authenticator negotiation by returning `ErrAuthChenged`\n\tVerify(c *http.Client, rs *http.Response, path string) (redo bool, err error)\n\t// Creates a copy of the underlying Authenticator.\n\tClone() Authenticator\n\tio.Closer\n}\n\ntype authfactory struct {\n\tkey    string\n\tcreate AuthFactory\n}\n\n// authorizer structure holds our Authenticator create functions\ntype authorizer struct {\n\tfactories  []authfactory\n\tdefAuthMux sync.Mutex\n\tdefAuth    Authenticator\n}\n\n// preemptiveAuthorizer structure holds the preemptive Authenticator\ntype preemptiveAuthorizer struct {\n\tauth Authenticator\n}\n\n// authShim structure that wraps the real Authenticator\ntype authShim struct {\n\tfactory AuthFactory\n\tbody    io.Reader\n\tauth    Authenticator\n}\n\n// negoAuth structure holds the authenticators that are going to be negotiated\ntype negoAuth struct {\n\tauths                   []Authenticator\n\tsetDefaultAuthenticator func(auth Authenticator)\n}\n\n// nullAuth initializes the whole authentication flow\ntype nullAuth struct{}\n\n// noAuth structure to perform no authentication at all\ntype noAuth struct{}\n\n// NewAutoAuth creates an auto Authenticator factory.\n// It negotiates the default authentication method\n// based on the order of the registered Authenticators\n// and the remotely offered authentication methods.\n// First In, First Out.\nfunc NewAutoAuth(login string, secret string) Authorizer {\n\tfmap := make([]authfactory, 0)\n\taz := &authorizer{factories: fmap, defAuthMux: sync.Mutex{}, defAuth: &nullAuth{}}\n\n\taz.AddAuthenticator(\"basic\", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) {\n\t\treturn &BasicAuth{user: login, pw: secret}, nil\n\t})\n\n\taz.AddAuthenticator(\"digest\", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) {\n\t\treturn NewDigestAuth(login, secret, rs)\n\t})\n\n\taz.AddAuthenticator(\"passport1.4\", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) {\n\t\treturn NewPassportAuth(c, login, secret, rs.Request.URL.String(), &rs.Header)\n\t})\n\n\treturn az\n}\n\n// NewEmptyAuth creates an empty Authenticator factory\n// The order of adding the Authenticator matters.\n// First In, First Out.\n// It offers the `NewAutoAuth` features.\nfunc NewEmptyAuth() Authorizer {\n\tfmap := make([]authfactory, 0)\n\taz := &authorizer{factories: fmap, defAuthMux: sync.Mutex{}, defAuth: &nullAuth{}}\n\treturn az\n}\n\n// NewPreemptiveAuth creates a preemptive Authenticator\n// The preemptive authorizer uses the provided Authenticator\n// for every request regardless of any `Www-Authenticate` header.\n//\n// It may only have one authentication method,\n// so calling `AddAuthenticator` **will panic**!\n//\n// Look out!! This offers the skinniest and slickest implementation\n// without any synchronisation!!\n// Still applicable with `BasicAuth` within go routines.\nfunc NewPreemptiveAuth(auth Authenticator) Authorizer {\n\treturn &preemptiveAuthorizer{auth: auth}\n}\n\n// NewAuthenticator creates an Authenticator (Shim) per request\nfunc (a *authorizer) NewAuthenticator(body io.Reader) (Authenticator, io.Reader) {\n\tvar retryBuf io.Reader = body\n\tif body != nil {\n\t\t// If the authorization fails, we will need to restart reading\n\t\t// from the passed body stream.\n\t\t// When body is seekable, use seek to reset the streams\n\t\t// cursor to the start.\n\t\t// Otherwise, copy the stream into a buffer while uploading\n\t\t// and use the buffers content on retry.\n\t\tif _, ok := retryBuf.(io.Seeker); ok {\n\t\t\tbody = io.NopCloser(body)\n\t\t} else {\n\t\t\tbuff := &bytes.Buffer{}\n\t\t\tretryBuf = buff\n\t\t\tbody = io.TeeReader(body, buff)\n\t\t}\n\t}\n\ta.defAuthMux.Lock()\n\tdefAuth := a.defAuth.Clone()\n\ta.defAuthMux.Unlock()\n\n\treturn &authShim{factory: a.factory, body: retryBuf, auth: defAuth}, body\n}\n\n// AddAuthenticator appends the AuthFactory to our factories.\n// It converts the key to lower case and preserves the order.\nfunc (a *authorizer) AddAuthenticator(key string, fn AuthFactory) {\n\tkey = strings.ToLower(key)\n\tfor _, f := range a.factories {\n\t\tif f.key == key {\n\t\t\tpanic(\"Authenticator exists: \" + key)\n\t\t}\n\t}\n\ta.factories = append(a.factories, authfactory{key, fn})\n}\n\n// factory picks all valid Authenticators based on Www-Authenticate headers\nfunc (a *authorizer) factory(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) {\n\theaders := rs.Header.Values(\"Www-Authenticate\")\n\tif len(headers) > 0 {\n\t\tauths := make([]Authenticator, 0)\n\t\tfor _, f := range a.factories {\n\t\t\tfor _, header := range headers {\n\t\t\t\theaderLower := strings.ToLower(header)\n\t\t\t\tif strings.Contains(headerLower, f.key) {\n\t\t\t\t\trs.Header.Set(\"Www-Authenticate\", header)\n\t\t\t\t\tif auth, err = f.create(c, rs, path); err == nil {\n\t\t\t\t\t\tauths = append(auths, auth)\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tswitch len(auths) {\n\t\tcase 0:\n\t\t\treturn nil, NewPathError(\"NoAuthenticator\", path, rs.StatusCode)\n\t\tcase 1:\n\t\t\tauth = auths[0]\n\t\tdefault:\n\t\t\tauth = &negoAuth{auths: auths, setDefaultAuthenticator: a.setDefaultAuthenticator}\n\t\t}\n\t} else {\n\t\tauth = &noAuth{}\n\t}\n\n\ta.setDefaultAuthenticator(auth)\n\n\treturn auth, nil\n}\n\n// setDefaultAuthenticator sets the default Authenticator\nfunc (a *authorizer) setDefaultAuthenticator(auth Authenticator) {\n\ta.defAuthMux.Lock()\n\ta.defAuth.Close()\n\ta.defAuth = auth\n\ta.defAuthMux.Unlock()\n}\n\n// Authorize the current request\nfunc (s *authShim) Authorize(c *http.Client, rq *http.Request, path string) error {\n\tif err := s.auth.Authorize(c, rq, path); err != nil {\n\t\treturn err\n\t}\n\tbody := s.body\n\trq.GetBody = func() (io.ReadCloser, error) {\n\t\tif body != nil {\n\t\t\tif sk, ok := body.(io.Seeker); ok {\n\t\t\t\tif _, err := sk.Seek(0, io.SeekStart); err != nil {\n\t\t\t\t\treturn nil, err\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn io.NopCloser(body), nil\n\t\t}\n\t\treturn nil, nil\n\t}\n\treturn nil\n}\n\n// Verify checks for authentication issues and may trigger a re-authentication.\n// Catches AlgoChangedErr to update the current Authenticator\nfunc (s *authShim) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {\n\tredo, err = s.auth.Verify(c, rs, path)\n\tif err != nil && errors.Is(err, ErrAuthChanged) {\n\t\tif auth, aerr := s.factory(c, rs, path); aerr == nil {\n\t\t\ts.auth.Close()\n\t\t\ts.auth = auth\n\t\t\treturn true, nil\n\t\t} else {\n\t\t\treturn false, aerr\n\t\t}\n\t}\n\treturn\n}\n\n// Close closes all resources\nfunc (s *authShim) Close() error {\n\ts.auth.Close()\n\ts.auth, s.factory = nil, nil\n\tif s.body != nil {\n\t\tif closer, ok := s.body.(io.Closer); ok {\n\t\t\treturn closer.Close()\n\t\t}\n\t}\n\treturn nil\n}\n\n// It's not intend to Clone the shim\n// therefore it returns a noAuth instance\nfunc (s *authShim) Clone() Authenticator {\n\treturn &noAuth{}\n}\n\n// String toString\nfunc (s *authShim) String() string {\n\treturn \"AuthShim\"\n}\n\n// Authorize authorizes the current request with the top most Authorizer\nfunc (n *negoAuth) Authorize(c *http.Client, rq *http.Request, path string) error {\n\tif len(n.auths) == 0 {\n\t\treturn NewPathError(\"NoAuthenticator\", path, 400)\n\t}\n\treturn n.auths[0].Authorize(c, rq, path)\n}\n\n// Verify verifies the authentication and selects the next one based on the result\nfunc (n *negoAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {\n\tif len(n.auths) == 0 {\n\t\treturn false, NewPathError(\"NoAuthenticator\", path, 400)\n\t}\n\tredo, err = n.auths[0].Verify(c, rs, path)\n\tif err != nil {\n\t\tif len(n.auths) > 1 {\n\t\t\tn.auths[0].Close()\n\t\t\tn.auths = n.auths[1:]\n\t\t\treturn true, nil\n\t\t}\n\t} else if redo {\n\t\treturn\n\t} else {\n\t\tauth := n.auths[0]\n\t\tn.auths = n.auths[1:]\n\t\tn.setDefaultAuthenticator(auth)\n\t\treturn\n\t}\n\n\treturn false, NewPathError(\"NoAuthenticator\", path, rs.StatusCode)\n}\n\n// Close will close the underlying authenticators.\nfunc (n *negoAuth) Close() error {\n\tfor _, a := range n.auths {\n\t\ta.Close()\n\t}\n\tn.setDefaultAuthenticator = nil\n\treturn nil\n}\n\n// Clone clones the underlying authenticators.\nfunc (n *negoAuth) Clone() Authenticator {\n\tauths := make([]Authenticator, len(n.auths))\n\tfor i, e := range n.auths {\n\t\tauths[i] = e.Clone()\n\t}\n\treturn &negoAuth{auths: auths, setDefaultAuthenticator: n.setDefaultAuthenticator}\n}\n\nfunc (n *negoAuth) String() string {\n\treturn \"NegoAuth\"\n}\n\n// Authorize the current request\nfunc (n *noAuth) Authorize(c *http.Client, rq *http.Request, path string) error {\n\treturn nil\n}\n\n// Verify checks for authentication issues and may trigger a re-authentication\nfunc (n *noAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {\n\tif \"\" != rs.Header.Get(\"Www-Authenticate\") {\n\t\terr = ErrAuthChanged\n\t}\n\treturn\n}\n\n// Close closes all resources\nfunc (n *noAuth) Close() error {\n\treturn nil\n}\n\n// Clone creates a copy of itself\nfunc (n *noAuth) Clone() Authenticator {\n\t// no copy due to read only access\n\treturn n\n}\n\n// String toString\nfunc (n *noAuth) String() string {\n\treturn \"NoAuth\"\n}\n\n// Authorize the current request\nfunc (n *nullAuth) Authorize(c *http.Client, rq *http.Request, path string) error {\n\trq.Header.Set(XInhibitRedirect, \"1\")\n\treturn nil\n}\n\n// Verify checks for authentication issues and may trigger a re-authentication\nfunc (n *nullAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {\n\treturn true, ErrAuthChanged\n}\n\n// Close closes all resources\nfunc (n *nullAuth) Close() error {\n\treturn nil\n}\n\n// Clone creates a copy of itself\nfunc (n *nullAuth) Clone() Authenticator {\n\t// no copy due to read only access\n\treturn n\n}\n\n// String toString\nfunc (n *nullAuth) String() string {\n\treturn \"NullAuth\"\n}\n\n// NewAuthenticator creates an Authenticator (Shim) per request\nfunc (b *preemptiveAuthorizer) NewAuthenticator(body io.Reader) (Authenticator, io.Reader) {\n\treturn b.auth.Clone(), body\n}\n\n// AddAuthenticator Will PANIC because it may only have a single authentication method\nfunc (b *preemptiveAuthorizer) AddAuthenticator(key string, fn AuthFactory) {\n\tpanic(\"You're funny! A preemptive authorizer may only have a single authentication method\")\n}\n"
  },
  {
    "path": "auth_test.go",
    "content": "package gowebdav\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestEmptyAuth(t *testing.T) {\n\tauth := NewEmptyAuth()\n\tsrv, _, _ := newAuthSrv(t, basicAuth)\n\tdefer srv.Close()\n\tcli := NewAuthClient(srv.URL, auth)\n\tif err := cli.Connect(); err == nil {\n\t\tt.Fatalf(\"got nil want error\")\n\t}\n}\n\nfunc TestRedirectAuthWIP(t *testing.T) {\n\thasPassedAuthServer := false\n\tauthHandler := func(h http.Handler) http.HandlerFunc {\n\t\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif user, passwd, ok := r.BasicAuth(); ok {\n\t\t\t\tif user == \"user\" && passwd == \"password\" {\n\t\t\t\t\thasPassedAuthServer = true\n\t\t\t\t\tw.WriteHeader(200)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tw.Header().Set(\"Www-Authenticate\", `Basic realm=\"x\"`)\n\t\t\tw.WriteHeader(401)\n\t\t}\n\t}\n\n\tpsrv, _, _ := newAuthSrv(t, authHandler)\n\tdefer psrv.Close()\n\n\tdataHandler := func(h http.Handler) http.HandlerFunc {\n\t\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t\thasAuth := strings.Contains(r.Header.Get(\"Authorization\"), \"Basic dXNlcjpwYXNzd29yZA==\")\n\n\t\t\tif hasPassedAuthServer && hasAuth {\n\t\t\t\th.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tw.Header().Set(\"Www-Authenticate\", `Basic realm=\"x\"`)\n\t\t\thttp.Redirect(w, r, psrv.URL+\"/\", 302)\n\t\t}\n\t}\n\n\tsrv, _, _ := newAuthSrv(t, dataHandler)\n\tdefer srv.Close()\n\tcli := NewClient(srv.URL, \"user\", \"password\")\n\tdata, err := cli.Read(\"/hello.txt\")\n\tif err != nil {\n\t\tt.Logf(\"WIP got error=%v; want nil\", err)\n\t}\n\tif bytes.Compare(data, []byte(\"hello gowebdav\\n\")) != 0 {\n\t\tt.Logf(\"WIP got data=%v; want=hello gowebdav\", data)\n\t}\n}\n"
  },
  {
    "path": "basicAuth.go",
    "content": "package gowebdav\n\nimport (\n\t\"fmt\"\n\t\"net/http\"\n)\n\n// BasicAuth structure holds our credentials\ntype BasicAuth struct {\n\tuser string\n\tpw   string\n}\n\n// Authorize the current request\nfunc (b *BasicAuth) Authorize(c *http.Client, rq *http.Request, path string) error {\n\trq.SetBasicAuth(b.user, b.pw)\n\treturn nil\n}\n\n// Verify verifies if the authentication\nfunc (b *BasicAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {\n\tif rs.StatusCode == 401 {\n\t\terr = NewPathError(\"Authorize\", path, rs.StatusCode)\n\t}\n\treturn\n}\n\n// Close cleans up all resources\nfunc (b *BasicAuth) Close() error {\n\treturn nil\n}\n\n// Clone creates a Copy of itself\nfunc (b *BasicAuth) Clone() Authenticator {\n\t// no copy due to read only access\n\treturn b\n}\n\n// String toString\nfunc (b *BasicAuth) String() string {\n\treturn fmt.Sprintf(\"BasicAuth login: %s\", b.user)\n}\n"
  },
  {
    "path": "basicAuth_test.go",
    "content": "package gowebdav\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n)\n\nfunc TestNewBasicAuth(t *testing.T) {\n\ta := &BasicAuth{user: \"user\", pw: \"password\"}\n\n\tex := \"BasicAuth login: user\"\n\tif a.String() != ex {\n\t\tt.Error(\"expected: \" + ex + \" got: \" + a.String())\n\t}\n\n\tif a.Clone() != a {\n\t\tt.Error(\"expected the same instance\")\n\t}\n\n\tif a.Close() != nil {\n\t\tt.Error(\"expected close without errors\")\n\t}\n}\n\nfunc TestBasicAuthAuthorize(t *testing.T) {\n\ta := &BasicAuth{user: \"user\", pw: \"password\"}\n\trq, _ := http.NewRequest(\"GET\", \"http://localhost/\", nil)\n\ta.Authorize(nil, rq, \"/\")\n\tif rq.Header.Get(\"Authorization\") != \"Basic dXNlcjpwYXNzd29yZA==\" {\n\t\tt.Error(\"got wrong Authorization header: \" + rq.Header.Get(\"Authorization\"))\n\t}\n}\n\nfunc TestPreemtiveBasicAuth(t *testing.T) {\n\ta := &BasicAuth{user: \"user\", pw: \"password\"}\n\tauth := NewPreemptiveAuth(a)\n\tn, b := auth.NewAuthenticator(nil)\n\tif b != nil {\n\t\tt.Error(\"expected body to be nil\")\n\t}\n\tif n != a {\n\t\tt.Error(\"expected the same instance\")\n\t}\n\n\tsrv, _, _ := newAuthSrv(t, basicAuth)\n\tdefer srv.Close()\n\tcli := NewAuthClient(srv.URL, auth)\n\tif err := cli.Connect(); err != nil {\n\t\tt.Fatalf(\"got error: %v, want nil\", err)\n\t}\n}\n"
  },
  {
    "path": "client.go",
    "content": "package gowebdav\n\nimport (\n\t\"bytes\"\n\t\"encoding/xml\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"os\"\n\tpathpkg \"path\"\n\t\"strings\"\n\t\"time\"\n)\n\nconst XInhibitRedirect = \"X-Gowebdav-Inhibit-Redirect\"\n\n// Client defines our structure\ntype Client struct {\n\troot        string\n\theaders     http.Header\n\tinterceptor func(method string, rq *http.Request)\n\tc           *http.Client\n\tauth        Authorizer\n}\n\n// NewClient creates a new instance of client\nfunc NewClient(uri, user, pw string) *Client {\n\treturn NewAuthClient(uri, NewAutoAuth(user, pw))\n}\n\n// NewAuthClient creates a new client instance with a custom Authorizer\nfunc NewAuthClient(uri string, auth Authorizer) *Client {\n\tc := &http.Client{\n\t\tCheckRedirect: func(rq *http.Request, via []*http.Request) error {\n\t\t\tif len(via) >= 10 {\n\t\t\t\treturn ErrTooManyRedirects\n\t\t\t}\n\t\t\tif via[0].Header.Get(XInhibitRedirect) != \"\" {\n\t\t\t\treturn http.ErrUseLastResponse\n\t\t\t}\n\t\t\treturn nil\n\t\t},\n\t}\n\treturn &Client{root: FixSlash(uri), headers: make(http.Header), interceptor: nil, c: c, auth: auth}\n}\n\n// SetHeader lets us set arbitrary headers for a given client\nfunc (c *Client) SetHeader(key, value string) {\n\tc.headers.Add(key, value)\n}\n\n// SetInterceptor lets us set an arbitrary interceptor for a given client\nfunc (c *Client) SetInterceptor(interceptor func(method string, rq *http.Request)) {\n\tc.interceptor = interceptor\n}\n\n// SetTimeout exposes the ability to set a time limit for requests\nfunc (c *Client) SetTimeout(timeout time.Duration) {\n\tc.c.Timeout = timeout\n}\n\n// SetTransport exposes the ability to define custom transports\nfunc (c *Client) SetTransport(transport http.RoundTripper) {\n\tc.c.Transport = transport\n}\n\n// SetJar exposes the ability to set a cookie jar to the client.\nfunc (c *Client) SetJar(jar http.CookieJar) {\n\tc.c.Jar = jar\n}\n\n// Connect connects to our dav server\nfunc (c *Client) Connect() error {\n\trs, err := c.options(\"/\")\n\tif err != nil {\n\t\treturn err\n\t}\n\n\terr = rs.Body.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif rs.StatusCode < 200 || rs.StatusCode >= 300 {\n\t\treturn NewPathError(\"Connect\", c.root, rs.StatusCode)\n\t}\n\n\treturn nil\n}\n\ntype props struct {\n\tStatus      string   `xml:\"DAV: status\"`\n\tName        string   `xml:\"DAV: prop>displayname,omitempty\"`\n\tType        xml.Name `xml:\"DAV: prop>resourcetype>collection,omitempty\"`\n\tSize        string   `xml:\"DAV: prop>getcontentlength,omitempty\"`\n\tContentType string   `xml:\"DAV: prop>getcontenttype,omitempty\"`\n\tETag        string   `xml:\"DAV: prop>getetag,omitempty\"`\n\tModified    string   `xml:\"DAV: prop>getlastmodified,omitempty\"`\n}\n\ntype response struct {\n\tHref  string  `xml:\"DAV: href\"`\n\tProps []props `xml:\"DAV: propstat\"`\n}\n\nfunc getProps(r *response, status string) *props {\n\tfor _, prop := range r.Props {\n\t\tif strings.Contains(prop.Status, status) {\n\t\t\treturn &prop\n\t\t}\n\t}\n\treturn nil\n}\n\n// ReadDir reads the contents of a remote directory\nfunc (c *Client) ReadDir(path string) ([]os.FileInfo, error) {\n\tpath = FixSlashes(path)\n\tfiles := make([]os.FileInfo, 0)\n\tskipSelf := true\n\tparse := func(resp interface{}) error {\n\t\tr := resp.(*response)\n\n\t\tif skipSelf {\n\t\t\tskipSelf = false\n\t\t\tif p := getProps(r, \"200\"); p != nil && p.Type.Local == \"collection\" {\n\t\t\t\tr.Props = nil\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn NewPathError(\"ReadDir\", path, 405)\n\t\t}\n\n\t\tif p := getProps(r, \"200\"); p != nil {\n\t\t\tf := new(File)\n\t\t\tif ps, err := url.PathUnescape(r.Href); err == nil {\n\t\t\t\tf.name = pathpkg.Base(ps)\n\t\t\t} else {\n\t\t\t\tf.name = p.Name\n\t\t\t}\n\t\t\tf.path = path + f.name\n\t\t\tf.modified = parseModified(&p.Modified)\n\t\t\tf.etag = p.ETag\n\t\t\tf.contentType = p.ContentType\n\n\t\t\tif p.Type.Local == \"collection\" {\n\t\t\t\tf.path += \"/\"\n\t\t\t\tf.size = 0\n\t\t\t\tf.isdir = true\n\t\t\t} else {\n\t\t\t\tf.size = parseInt64(&p.Size)\n\t\t\t\tf.isdir = false\n\t\t\t}\n\n\t\t\tfiles = append(files, *f)\n\t\t}\n\n\t\tr.Props = nil\n\t\treturn nil\n\t}\n\n\terr := c.propfind(path, false,\n\t\t`<d:propfind xmlns:d='DAV:'>\n\t\t\t<d:prop>\n\t\t\t\t<d:displayname/>\n\t\t\t\t<d:resourcetype/>\n\t\t\t\t<d:getcontentlength/>\n\t\t\t\t<d:getcontenttype/>\n\t\t\t\t<d:getetag/>\n\t\t\t\t<d:getlastmodified/>\n\t\t\t</d:prop>\n\t\t</d:propfind>`,\n\t\t&response{},\n\t\tparse)\n\n\tif err != nil {\n\t\tif _, ok := err.(*os.PathError); !ok {\n\t\t\terr = NewPathErrorErr(\"ReadDir\", path, err)\n\t\t}\n\t}\n\treturn files, err\n}\n\n// Stat returns the file stats for a specified path\nfunc (c *Client) Stat(path string) (os.FileInfo, error) {\n\tvar f *File\n\tparse := func(resp interface{}) error {\n\t\tr := resp.(*response)\n\t\tif p := getProps(r, \"200\"); p != nil && f == nil {\n\t\t\tf = new(File)\n\t\t\tf.name = p.Name\n\t\t\tf.path = path\n\t\t\tf.etag = p.ETag\n\t\t\tf.contentType = p.ContentType\n\n\t\t\tif p.Type.Local == \"collection\" {\n\t\t\t\tif !strings.HasSuffix(f.path, \"/\") {\n\t\t\t\t\tf.path += \"/\"\n\t\t\t\t}\n\t\t\t\tf.size = 0\n\t\t\t\tf.modified = parseModified(&p.Modified)\n\t\t\t\tf.isdir = true\n\t\t\t} else {\n\t\t\t\tf.size = parseInt64(&p.Size)\n\t\t\t\tf.modified = parseModified(&p.Modified)\n\t\t\t\tf.isdir = false\n\t\t\t}\n\t\t}\n\n\t\tr.Props = nil\n\t\treturn nil\n\t}\n\n\terr := c.propfind(path, true,\n\t\t`<d:propfind xmlns:d='DAV:'>\n\t\t\t<d:prop>\n\t\t\t\t<d:displayname/>\n\t\t\t\t<d:resourcetype/>\n\t\t\t\t<d:getcontentlength/>\n\t\t\t\t<d:getcontenttype/>\n\t\t\t\t<d:getetag/>\n\t\t\t\t<d:getlastmodified/>\n\t\t\t</d:prop>\n\t\t</d:propfind>`,\n\t\t&response{},\n\t\tparse)\n\n\tif err != nil {\n\t\tif _, ok := err.(*os.PathError); !ok {\n\t\t\terr = NewPathErrorErr(\"ReadDir\", path, err)\n\t\t}\n\t}\n\treturn f, err\n}\n\n// Remove removes a remote file\nfunc (c *Client) Remove(path string) error {\n\treturn c.RemoveAll(path)\n}\n\n// RemoveAll removes remote files\nfunc (c *Client) RemoveAll(path string) error {\n\trs, err := c.req(\"DELETE\", path, nil, nil)\n\tif err != nil {\n\t\treturn NewPathError(\"Remove\", path, 400)\n\t}\n\terr = rs.Body.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif rs.StatusCode == 200 || rs.StatusCode == 204 || rs.StatusCode == 404 {\n\t\treturn nil\n\t}\n\n\treturn NewPathError(\"Remove\", path, rs.StatusCode)\n}\n\n// Mkdir makes a directory\nfunc (c *Client) Mkdir(path string, _ os.FileMode) (err error) {\n\tpath = FixSlashes(path)\n\tstatus, err := c.mkcol(path)\n\tif err != nil {\n\t\treturn\n\t}\n\tif status == 201 {\n\t\treturn nil\n\t}\n\n\treturn NewPathError(\"Mkdir\", path, status)\n}\n\n// MkdirAll like mkdir -p, but for webdav\nfunc (c *Client) MkdirAll(path string, _ os.FileMode) (err error) {\n\tpath = FixSlashes(path)\n\tstatus, err := c.mkcol(path)\n\tif err != nil {\n\t\treturn\n\t}\n\tif status == 201 {\n\t\treturn nil\n\t}\n\tif status == 409 {\n\t\tpaths := strings.Split(path, \"/\")\n\t\tsub := \"/\"\n\t\tfor _, e := range paths {\n\t\t\tif e == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tsub += e + \"/\"\n\t\t\tstatus, err = c.mkcol(sub)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif status != 201 {\n\t\t\t\treturn NewPathError(\"MkdirAll\", sub, status)\n\t\t\t}\n\t\t}\n\t\treturn nil\n\t}\n\n\treturn NewPathError(\"MkdirAll\", path, status)\n}\n\n// Rename moves a file from A to B\nfunc (c *Client) Rename(oldpath, newpath string, overwrite bool) error {\n\treturn c.copymove(\"MOVE\", oldpath, newpath, overwrite)\n}\n\n// Copy copies a file from A to B\nfunc (c *Client) Copy(oldpath, newpath string, overwrite bool) error {\n\treturn c.copymove(\"COPY\", oldpath, newpath, overwrite)\n}\n\n// Read reads the contents of a remote file\nfunc (c *Client) Read(path string) ([]byte, error) {\n\tvar stream io.ReadCloser\n\tvar err error\n\n\tif stream, err = c.ReadStream(path); err != nil {\n\t\treturn nil, err\n\t}\n\tdefer stream.Close()\n\n\tbuf := new(bytes.Buffer)\n\t_, err = buf.ReadFrom(stream)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn buf.Bytes(), nil\n}\n\n// ReadStream reads the stream for a given path\nfunc (c *Client) ReadStream(path string) (io.ReadCloser, error) {\n\trs, err := c.req(\"GET\", path, nil, nil)\n\tif err != nil {\n\t\treturn nil, NewPathErrorErr(\"ReadStream\", path, err)\n\t}\n\n\tif rs.StatusCode == 200 {\n\t\treturn rs.Body, nil\n\t}\n\n\trs.Body.Close()\n\treturn nil, NewPathError(\"ReadStream\", path, rs.StatusCode)\n}\n\n// ReadStreamRange reads the stream representing a subset of bytes for a given path,\n// utilizing HTTP Range Requests if the server supports it.\n// The range is expressed as offset from the start of the file and length, for example\n// offset=10, length=10 will return bytes 10 through 19.\n//\n// If the server does not support partial content requests and returns full content instead,\n// this function will emulate the behavior by skipping `offset` bytes and limiting the result\n// to `length`.\nfunc (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadCloser, error) {\n\trs, err := c.req(\"GET\", path, nil, func(r *http.Request) {\n\t\tif length > 0 {\n\t\t\tr.Header.Add(\"Range\", fmt.Sprintf(\"bytes=%d-%d\", offset, offset+length-1))\n\t\t} else {\n\t\t\tr.Header.Add(\"Range\", fmt.Sprintf(\"bytes=%d-\", offset))\n\t\t}\n\t})\n\tif err != nil {\n\t\treturn nil, NewPathErrorErr(\"ReadStreamRange\", path, err)\n\t}\n\n\tif rs.StatusCode == http.StatusPartialContent {\n\t\t// server supported partial content, return as-is.\n\t\treturn rs.Body, nil\n\t}\n\n\t// server returned success, but did not support partial content, so we have the whole\n\t// stream in rs.Body\n\tif rs.StatusCode == 200 {\n\t\t// discard first 'offset' bytes.\n\t\tif _, err := io.Copy(io.Discard, io.LimitReader(rs.Body, offset)); err != nil {\n\t\t\treturn nil, NewPathErrorErr(\"ReadStreamRange\", path, err)\n\t\t}\n\n\t\t// return a io.ReadCloser that is limited to `length` bytes.\n\t\treturn &limitedReadCloser{rc: rs.Body, remaining: int(length)}, nil\n\t}\n\n\trs.Body.Close()\n\treturn nil, NewPathError(\"ReadStream\", path, rs.StatusCode)\n}\n\n// Write writes data to a given path\nfunc (c *Client) Write(path string, data []byte, _ os.FileMode) (err error) {\n\ts, err := c.put(path, bytes.NewReader(data), int64(len(data)))\n\tif err != nil {\n\t\treturn\n\t}\n\n\tswitch s {\n\n\tcase 200, 201, 204:\n\t\treturn nil\n\n\tcase 404, 409:\n\t\terr = c.createParentCollection(path)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\n\t\ts, err = c.put(path, bytes.NewReader(data), int64(len(data)))\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tif s == 200 || s == 201 || s == 204 {\n\t\t\treturn\n\t\t}\n\t}\n\n\treturn NewPathError(\"Write\", path, s)\n}\n\n// WriteStream writes a stream - it will copy to memory for non-seekable streams\nfunc (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err error) {\n\n\terr = c.createParentCollection(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tcontentLength := int64(0)\n\tif seeker, ok := stream.(io.Seeker); ok {\n\t\tcontentLength, err = seeker.Seek(0, io.SeekEnd)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t_, err = seeker.Seek(0, io.SeekStart)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t} else {\n\t\tbuffer := bytes.NewBuffer(make([]byte, 0, 1024*1024 /* 1MB */))\n\n\t\tcontentLength, err = io.Copy(buffer, stream)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tstream = buffer\n\t}\n\n\ts, err := c.put(path, stream, contentLength)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch s {\n\tcase 200, 201, 204:\n\t\treturn nil\n\n\tdefault:\n\t\treturn NewPathError(\"WriteStream\", path, s)\n\t}\n}\n\n// WriteStream writes a stream with a known content length\nfunc (c *Client) WriteStreamWithLength(path string, stream io.Reader, contentLength int64, _ os.FileMode) (err error) {\n\terr = c.createParentCollection(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ts, err := c.put(path, stream, contentLength)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tswitch s {\n\tcase 200, 201, 204:\n\t\treturn nil\n\n\tdefault:\n\t\treturn NewPathError(\"WriteStreamWithLength\", path, s)\n\t}\n}\n"
  },
  {
    "path": "client_test.go",
    "content": "package gowebdav\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"crypto/rand\"\n\t\"errors\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"golang.org/x/net/webdav\"\n)\n\nfunc noAuthHndl(h http.Handler) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\th.ServeHTTP(w, r)\n\t}\n}\n\nfunc basicAuth(h http.Handler) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tif user, passwd, ok := r.BasicAuth(); ok {\n\t\t\tif user == \"user\" && passwd == \"password\" {\n\t\t\t\th.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\thttp.Error(w, \"not authorized\", 403)\n\t\t} else {\n\t\t\tw.Header().Set(\"WWW-Authenticate\", `Basic realm=\"x\"`)\n\t\t\tw.WriteHeader(401)\n\t\t}\n\t}\n}\n\nfunc basicAuthNoContent(h http.Handler) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tif user, passwd, ok := r.BasicAuth(); ok {\n\t\t\tif user == \"user\" && passwd == \"password\" {\n\t\t\t\tw.WriteHeader(http.StatusNoContent)\n\t\t\t\th.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\thttp.Error(w, \"not authorized\", 403)\n\t\t} else {\n\t\t\tw.Header().Set(\"WWW-Authenticate\", `Basic realm=\"x\"`)\n\t\t\tw.WriteHeader(401)\n\t\t}\n\t}\n}\n\nfunc basicAuthWithPostHandlerFunc(h http.Handler, postHandlerFunc http.HandlerFunc) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tuser, passwd, ok := r.BasicAuth()\n\t\tif !ok {\n\t\t\tw.Header().Set(\"WWW-Authenticate\", `Basic realm=\"x\"`)\n\t\t\tw.WriteHeader(401)\n\t\t\treturn\n\t\t}\n\n\t\tif user != \"user\" || passwd != \"password\" {\n\t\t\thttp.Error(w, \"not authorized\", 403)\n\t\t\treturn\n\t\t}\n\n\t\th.ServeHTTP(w, r)\n\t\tpostHandlerFunc(w, r)\n\t}\n}\n\nfunc multipleAuth(h http.Handler) http.HandlerFunc {\n\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\tnotAuthed := false\n\t\tif r.Header.Get(\"Authorization\") == \"\" {\n\t\t\tnotAuthed = true\n\t\t} else if user, passwd, ok := r.BasicAuth(); ok {\n\t\t\tif user == \"user\" && passwd == \"password\" {\n\t\t\t\th.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tnotAuthed = true\n\t\t} else if strings.HasPrefix(r.Header.Get(\"Authorization\"), \"Digest \") {\n\t\t\tpairs := strings.TrimPrefix(r.Header.Get(\"Authorization\"), \"Digest \")\n\t\t\tdigestParts := make(map[string]string)\n\t\t\tfor _, pair := range strings.Split(pairs, \",\") {\n\t\t\t\tkv := strings.SplitN(strings.TrimSpace(pair), \"=\", 2)\n\t\t\t\tkey, value := kv[0], kv[1]\n\t\t\t\tvalue = strings.Trim(value, `\"`)\n\t\t\t\tdigestParts[key] = value\n\t\t\t}\n\t\t\tif digestParts[\"qop\"] == \"\" {\n\t\t\t\tdigestParts[\"qop\"] = \"auth\"\n\t\t\t}\n\n\t\t\tha1 := getMD5(fmt.Sprint(digestParts[\"username\"], \":\", digestParts[\"realm\"], \":\", \"digestPW\"))\n\t\t\tha2 := getMD5(fmt.Sprint(r.Method, \":\", digestParts[\"uri\"]))\n\t\t\texpected := getMD5(fmt.Sprint(ha1,\n\t\t\t\t\":\", digestParts[\"nonce\"],\n\t\t\t\t\":\", digestParts[\"nc\"],\n\t\t\t\t\":\", digestParts[\"cnonce\"],\n\t\t\t\t\":\", digestParts[\"qop\"],\n\t\t\t\t\":\", ha2))\n\n\t\t\tif expected == digestParts[\"response\"] {\n\t\t\t\th.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tnotAuthed = true\n\t\t}\n\n\t\tif notAuthed {\n\t\t\tw.Header().Add(\"WWW-Authenticate\", `Digest realm=\"testrealm@host.com\", qop=\"auth,auth-int\",nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\",opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"`)\n\t\t\tw.Header().Add(\"WWW-Authenticate\", `Basic realm=\"x\"`)\n\t\t\tw.WriteHeader(401)\n\t\t}\n\t}\n}\n\nfunc fillFs(t *testing.T, fs webdav.FileSystem) context.Context {\n\tctx := context.Background()\n\tf, err := fs.OpenFile(ctx, \"hello.txt\", os.O_CREATE, 0644)\n\tif err != nil {\n\t\tt.Errorf(\"fail to crate file: %v\", err)\n\t}\n\tf.Write([]byte(\"hello gowebdav\\n\"))\n\tf.Close()\n\terr = fs.Mkdir(ctx, \"/test\", 0755)\n\tif err != nil {\n\t\tt.Errorf(\"fail to crate directory: %v\", err)\n\t}\n\tf, err = fs.OpenFile(ctx, \"/test/test.txt\", os.O_CREATE, 0644)\n\tif err != nil {\n\t\tt.Errorf(\"fail to crate file: %v\", err)\n\t}\n\tf.Write([]byte(\"test test gowebdav\\n\"))\n\tf.Close()\n\treturn ctx\n}\n\nfunc newServer(t *testing.T) (*Client, *httptest.Server, webdav.FileSystem, context.Context) {\n\treturn newAuthServer(t, basicAuth)\n}\n\nfunc newServerAuthNoContent(t *testing.T) (*Client, *httptest.Server, webdav.FileSystem, context.Context) {\n\treturn newAuthServer(t, basicAuthNoContent)\n}\n\nfunc newAuthServer(t *testing.T, auth func(h http.Handler) http.HandlerFunc) (*Client, *httptest.Server, webdav.FileSystem, context.Context) {\n\tsrv, fs, ctx := newAuthSrv(t, auth)\n\tcli := NewClient(srv.URL, \"user\", \"password\")\n\treturn cli, srv, fs, ctx\n}\n\nfunc newAuthSrv(t *testing.T, auth func(h http.Handler) http.HandlerFunc) (*httptest.Server, webdav.FileSystem, context.Context) {\n\tmux := http.NewServeMux()\n\tfs := webdav.NewMemFS()\n\tctx := fillFs(t, fs)\n\tmux.HandleFunc(\"/\", auth(&webdav.Handler{\n\t\tFileSystem: fs,\n\t\tLockSystem: webdav.NewMemLS(),\n\t}))\n\tsrv := httptest.NewServer(mux)\n\treturn srv, fs, ctx\n}\n\nfunc newAuthServerAcquireContentLength(t *testing.T) (*Client, *httptest.Server, webdav.FileSystem, context.Context) {\n\tsrv, fs, ctx := newAuthSrvAcquireContentLength(t, basicAuthWithPostHandlerFunc)\n\tcli := NewClient(srv.URL, \"user\", \"password\")\n\treturn cli, srv, fs, ctx\n}\n\nfunc newAuthSrvAcquireContentLength(t *testing.T, authWithPostHandlerFunc func(h http.Handler, postHandlerFunc http.HandlerFunc) http.HandlerFunc) (*httptest.Server, webdav.FileSystem, context.Context) {\n\tmux := http.NewServeMux()\n\tfs := webdav.NewMemFS()\n\tctx := fillFs(t, fs)\n\tmux.HandleFunc(\"/\", authWithPostHandlerFunc(&webdav.Handler{\n\t\tFileSystem: fs,\n\t\tLockSystem: webdav.NewMemLS(),\n\t}, func(w http.ResponseWriter, r *http.Request) {\n\t\tif r.Method != http.MethodPut {\n\t\t\treturn\n\t\t}\n\n\t\tfileName := strings.TrimPrefix(r.URL.Path, \"/\")\n\t\tstat, err := fs.Stat(ctx, fileName)\n\t\tif err != nil {\n\t\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t\t}\n\n\t\tif r.ContentLength != stat.Size() {\n\t\t\tt.Fatalf(\"acquire content length got: %v, want %v\", r.ContentLength, stat.Size())\n\t\t}\n\t}))\n\tsrv := httptest.NewServer(mux)\n\treturn srv, fs, ctx\n}\n\nfunc TestConnect(t *testing.T) {\n\tcli, srv, _, _ := newServer(t)\n\tdefer srv.Close()\n\tif err := cli.Connect(); err != nil {\n\t\tt.Fatalf(\"got error: %v, want nil\", err)\n\t}\n\n\tcli = NewClient(srv.URL, \"no\", \"no\")\n\tif err := cli.Connect(); err == nil {\n\t\tt.Fatalf(\"got nil, want error: %v\", err)\n\t}\n}\n\nfunc TestConnectAuthNoContent(t *testing.T) {\n\tcli, srv, _, _ := newServerAuthNoContent(t)\n\tdefer srv.Close()\n\tif err := cli.Connect(); err != nil {\n\t\tt.Fatalf(\"got error: %v, want nil\", err)\n\t}\n\n\tcli = NewClient(srv.URL, \"no\", \"no\")\n\tif err := cli.Connect(); err == nil {\n\t\tt.Fatalf(\"got nil, want error: %v\", err)\n\t}\n}\n\nfunc TestConnectMultipleAuth(t *testing.T) {\n\tcli, srv, _, _ := newAuthServer(t, multipleAuth)\n\tdefer srv.Close()\n\tif err := cli.Connect(); err != nil {\n\t\tt.Fatalf(\"got error: %v, want nil\", err)\n\t}\n\n\tcli = NewClient(srv.URL, \"digestUser\", \"digestPW\")\n\tif err := cli.Connect(); err != nil {\n\t\tt.Fatalf(\"got nil, want error: %v\", err)\n\t}\n\n\tcli = NewClient(srv.URL, \"no\", \"no\")\n\tif err := cli.Connect(); err == nil {\n\t\tt.Fatalf(\"got nil, want error: %v\", err)\n\t}\n}\n\nfunc TestConnectMultiAuthII(t *testing.T) {\n\tcli, srv, _, _ := newAuthServer(t, func(h http.Handler) http.HandlerFunc {\n\t\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t\tif user, passwd, ok := r.BasicAuth(); ok {\n\t\t\t\tif user == \"user\" && passwd == \"password\" {\n\t\t\t\t\th.ServeHTTP(w, r)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\thttp.Error(w, \"not authorized\", 403)\n\t\t\t} else {\n\t\t\t\tw.Header().Add(\"WWW-Authenticate\", `FooAuth`)\n\t\t\t\tw.Header().Add(\"WWW-Authenticate\", `BazAuth`)\n\t\t\t\tw.Header().Add(\"WWW-Authenticate\", `BarAuth`)\n\t\t\t\tw.Header().Add(\"WWW-Authenticate\", `Basic realm=\"x\"`)\n\t\t\t\tw.WriteHeader(401)\n\t\t\t}\n\t\t}\n\t})\n\tdefer srv.Close()\n\tif err := cli.Connect(); err != nil {\n\t\tt.Fatalf(\"got error: %v, want nil\", err)\n\t}\n\n\tcli = NewClient(srv.URL, \"no\", \"no\")\n\tif err := cli.Connect(); err == nil {\n\t\tt.Fatalf(\"got nil, want error: %v\", err)\n\t}\n}\n\nfunc TestReadDirConcurrent(t *testing.T) {\n\tcli, srv, _, _ := newServer(t)\n\tdefer srv.Close()\n\n\tvar wg sync.WaitGroup\n\terrs := make(chan error, 2)\n\tfor i := 0; i < 2; i++ {\n\t\twg.Add(1)\n\n\t\tgo func() {\n\t\t\tdefer wg.Done()\n\t\t\tf, err := cli.ReadDir(\"/\")\n\t\t\tif err != nil {\n\t\t\t\terrs <- errors.New(fmt.Sprintf(\"got error: %v, want file listing: %v\", err, f))\n\t\t\t}\n\t\t\tif len(f) != 2 {\n\t\t\t\terrs <- errors.New(fmt.Sprintf(\"f: %v err: %v\", f, err))\n\t\t\t}\n\t\t\tif f[0].Name() != \"hello.txt\" && f[1].Name() != \"hello.txt\" {\n\t\t\t\terrs <- errors.New(fmt.Sprintf(\"got: %v, want file: %s\", f, \"hello.txt\"))\n\t\t\t}\n\t\t\tif f[0].Name() != \"test\" && f[1].Name() != \"test\" {\n\t\t\t\terrs <- errors.New(fmt.Sprintf(\"got: %v, want directory: %s\", f, \"test\"))\n\t\t\t}\n\t\t}()\n\t}\n\n\twg.Wait()\n\tclose(errs)\n\n\tfor err := range errs {\n\t\tif err != nil {\n\t\t\tt.Fatal(err)\n\t\t}\n\t}\n}\n\nfunc TestRead(t *testing.T) {\n\tcli, srv, _, _ := newServer(t)\n\tdefer srv.Close()\n\n\tdata, err := cli.Read(\"/hello.txt\")\n\tif err != nil || bytes.Compare(data, []byte(\"hello gowebdav\\n\")) != 0 {\n\t\tt.Fatalf(\"got: %v, want data: %s\", err, []byte(\"hello gowebdav\\n\"))\n\t}\n\n\tdata, err = cli.Read(\"/404.txt\")\n\tif err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", data, err)\n\t}\n\tif !IsErrNotFound(err) {\n\t\tt.Fatalf(\"got: %v, want 404 error\", err)\n\t}\n}\n\nfunc TestReadNoAuth(t *testing.T) {\n\tcli, srv, _, _ := newAuthServer(t, noAuthHndl)\n\tdefer srv.Close()\n\n\tdata, err := cli.Read(\"/hello.txt\")\n\tif err != nil || bytes.Compare(data, []byte(\"hello gowebdav\\n\")) != 0 {\n\t\tt.Fatalf(\"got: %v, want data: %s\", err, []byte(\"hello gowebdav\\n\"))\n\t}\n\n\tdata, err = cli.Read(\"/404.txt\")\n\tif err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", data, err)\n\t}\n\tif !IsErrNotFound(err) {\n\t\tt.Fatalf(\"got: %v, want 404 error\", err)\n\t}\n}\n\nfunc TestReadStream(t *testing.T) {\n\tcli, srv, _, _ := newServer(t)\n\tdefer srv.Close()\n\n\tstream, err := cli.ReadStream(\"/hello.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want data: %v\", err, stream)\n\t}\n\tbuf := new(bytes.Buffer)\n\tbuf.ReadFrom(stream)\n\tif buf.String() != \"hello gowebdav\\n\" {\n\t\tt.Fatalf(\"got: %v, want stream: hello gowebdav\", buf.String())\n\t}\n\n\tstream, err = cli.ReadStream(\"/404/hello.txt\")\n\tif err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", stream, err)\n\t}\n}\n\nfunc TestReadStreamRange(t *testing.T) {\n\tcli, srv, _, _ := newServer(t)\n\tdefer srv.Close()\n\n\tstream, err := cli.ReadStreamRange(\"/hello.txt\", 4, 4)\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want data: %v\", err, stream)\n\t}\n\tbuf := new(bytes.Buffer)\n\tbuf.ReadFrom(stream)\n\tif buf.String() != \"o go\" {\n\t\tt.Fatalf(\"got: %v, want stream: o go\", buf.String())\n\t}\n\n\tstream, err = cli.ReadStream(\"/404/hello.txt\")\n\tif err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", stream, err)\n\t}\n}\n\nfunc TestReadStreamRangeUnkownLength(t *testing.T) {\n\tcli, srv, _, _ := newServer(t)\n\tdefer srv.Close()\n\n\tstream, err := cli.ReadStreamRange(\"/hello.txt\", 6, 0)\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want data: %v\", err, stream)\n\t}\n\tbuf := new(bytes.Buffer)\n\tbuf.ReadFrom(stream)\n\tif buf.String() != \"gowebdav\\n\" {\n\t\tt.Fatalf(\"got: %v, want stream: gowebdav\\n\", buf.String())\n\t}\n\n\tstream, err = cli.ReadStream(\"/404/hello.txt\")\n\tif err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", stream, err)\n\t}\n}\n\nfunc TestStat(t *testing.T) {\n\tcli, srv, _, _ := newServer(t)\n\tdefer srv.Close()\n\n\tinfo, err := cli.Stat(\"/hello.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want os.Info: %v\", err, info)\n\t}\n\tif info.Name() != \"hello.txt\" {\n\t\tt.Fatalf(\"got: %v, want file hello.txt\", info)\n\t}\n\n\tinfo, err = cli.Stat(\"/404.txt\")\n\tif err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", info, err)\n\t}\n\tif !IsErrNotFound(err) {\n\t\tt.Fatalf(\"got: %v, want 404 error\", err)\n\t}\n}\n\nfunc TestMkdir(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tinfo, err := cli.Stat(\"/newdir\")\n\tif err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", info, err)\n\t}\n\n\tif err := cli.Mkdir(\"/newdir\", 0755); err != nil {\n\t\tt.Fatalf(\"got: %v, want mkdir /newdir\", err)\n\t}\n\n\tif err := cli.Mkdir(\"/newdir\", 0755); err != nil {\n\t\tt.Fatalf(\"got: %v, want mkdir /newdir\", err)\n\t}\n\n\tinfo, err = fs.Stat(ctx, \"/newdir\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want dir info: %v\", err, info)\n\t}\n\n\tif err := cli.Mkdir(\"/404/newdir\", 0755); err == nil {\n\t\tt.Fatalf(\"expected Mkdir error due to missing parent directory\")\n\t}\n}\n\nfunc TestMkdirAll(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tif err := cli.MkdirAll(\"/dir/dir/dir\", 0755); err != nil {\n\t\tt.Fatalf(\"got: %v, want mkdirAll /dir/dir/dir\", err)\n\t}\n\n\tinfo, err := fs.Stat(ctx, \"/dir/dir/dir\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want dir info: %v\", err, info)\n\t}\n}\n\nfunc TestCopy(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tinfo, err := fs.Stat(ctx, \"/copy.txt\")\n\tif err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", info, err)\n\t}\n\n\tif err := cli.Copy(\"/hello.txt\", \"/copy.txt\", false); err != nil {\n\t\tt.Fatalf(\"got: %v, want copy /hello.txt to /copy.txt\", err)\n\t}\n\n\tinfo, err = fs.Stat(ctx, \"/copy.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n\tif info.Size() != 15 {\n\t\tt.Fatalf(\"got: %v, want file size: %d bytes\", info.Size(), 15)\n\t}\n\n\tinfo, err = fs.Stat(ctx, \"/hello.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n\tif info.Size() != 15 {\n\t\tt.Fatalf(\"got: %v, want file size: %d bytes\", info.Size(), 15)\n\t}\n\n\tif err := cli.Copy(\"/hello.txt\", \"/copy.txt\", false); err == nil {\n\t\tt.Fatalf(\"expected copy error due to overwrite false\")\n\t}\n\n\tif err := cli.Copy(\"/hello.txt\", \"/copy.txt\", true); err != nil {\n\t\tt.Fatalf(\"got: %v, want overwrite /copy.txt with /hello.txt\", err)\n\t}\n}\n\nfunc TestRename(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tinfo, err := fs.Stat(ctx, \"/copy.txt\")\n\tif err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", info, err)\n\t}\n\n\tif err := cli.Rename(\"/hello.txt\", \"/copy.txt\", false); err != nil {\n\t\tt.Fatalf(\"got: %v, want mv /hello.txt to /copy.txt\", err)\n\t}\n\n\tinfo, err = fs.Stat(ctx, \"/copy.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n\tif info.Size() != 15 {\n\t\tt.Fatalf(\"got: %v, want file size: %d bytes\", info.Size(), 15)\n\t}\n\n\tif info, err = fs.Stat(ctx, \"/hello.txt\"); err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", info, err)\n\t}\n\n\tif err := cli.Rename(\"/test/test.txt\", \"/copy.txt\", true); err != nil {\n\t\tt.Fatalf(\"got: %v, want overwrite /copy.txt with /hello.txt\", err)\n\t}\n\tinfo, err = fs.Stat(ctx, \"/copy.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n\tif info.Size() != 19 {\n\t\tt.Fatalf(\"got: %v, want file size: %d bytes\", info.Size(), 19)\n\t}\n}\n\nfunc TestRemove(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tif err := cli.Remove(\"/hello.txt\"); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tif info, err := fs.Stat(ctx, \"/hello.txt\"); err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", info, err)\n\t}\n\n\tif err := cli.Remove(\"/404.txt\"); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n}\n\nfunc TestRemoveAll(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tif err := cli.RemoveAll(\"/test/test.txt\"); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tif info, err := fs.Stat(ctx, \"/test/test.txt\"); err == nil {\n\t\tt.Fatalf(\"got: %v, want error: %v\", info, err)\n\t}\n\n\tif err := cli.RemoveAll(\"/404.txt\"); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tif err := cli.RemoveAll(\"/404/404/404.txt\"); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n}\n\nfunc TestWrite(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tif err := cli.Write(\"/newfile.txt\", []byte(\"foo bar\\n\"), 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tinfo, err := fs.Stat(ctx, \"/newfile.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n\tif info.Size() != 8 {\n\t\tt.Fatalf(\"got: %v, want file size: %d bytes\", info.Size(), 8)\n\t}\n\n\tif err := cli.Write(\"/404/newfile.txt\", []byte(\"foo bar\\n\"), 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n}\n\nfunc TestWriteStream(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tif err := cli.WriteStream(\"/newfile.txt\", strings.NewReader(\"foo bar\\n\"), 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tinfo, err := fs.Stat(ctx, \"/newfile.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n\tif info.Size() != 8 {\n\t\tt.Fatalf(\"got: %v, want file size: %d bytes\", info.Size(), 8)\n\t}\n\n\tif err := cli.WriteStream(\"/404/works.txt\", strings.NewReader(\"foo bar\\n\"), 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tif info, err := fs.Stat(ctx, \"/404/works.txt\"); err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n}\n\nfunc TestWriteStreamFromPipe(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tr, w := io.Pipe()\n\n\tgo func() {\n\t\tdefer w.Close()\n\t\tfmt.Fprint(w, \"foo\")\n\t\ttime.Sleep(1 * time.Second)\n\t\tfmt.Fprint(w, \" \")\n\t\ttime.Sleep(1 * time.Second)\n\t\tfmt.Fprint(w, \"bar\\n\")\n\t}()\n\n\tif err := cli.WriteStream(\"/newfile.txt\", r, 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tinfo, err := fs.Stat(ctx, \"/newfile.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n\tif info.Size() != 8 {\n\t\tt.Fatalf(\"got: %v, want file size: %d bytes\", info.Size(), 8)\n\t}\n}\n\nfunc TestWriteToServerAcquireContentLength(t *testing.T) {\n\tcli, srv, _, _ := newAuthServerAcquireContentLength(t)\n\tdefer srv.Close()\n\n\tif err := cli.Write(\"/newfile.txt\", []byte(\"foo bar\\n\"), 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n}\n\nfunc TestWriteStreamToServerAcquireContentLength(t *testing.T) {\n\tcli, srv, _, _ := newAuthServerAcquireContentLength(t)\n\tdefer srv.Close()\n\n\tif err := cli.WriteStream(\"/newfile.txt\", strings.NewReader(\"foo bar\\n\"), 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tlf := make([]byte, 10*1024*1024)\n\trand.Read(lf)\n\tif err := cli.WriteStream(\"/largefile.bin\", bytes.NewBuffer(lf), 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tlf2, err := cli.Read(\"/largefile.bin\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tif !bytes.Equal(lf, lf2) {\n\t\tt.Fatalf(\"%s largefile.bin doesn't match\", t.Name())\n\t}\n}\n\nfunc TestWriteStreamWithLength(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tcontent := \"foo bar\\n\"\n\tif err := cli.WriteStreamWithLength(\"/newfile.txt\", strings.NewReader(content), int64(len(content)), 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tinfo, err := fs.Stat(ctx, \"/newfile.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n\tif info.Size() != 8 {\n\t\tt.Fatalf(\"got: %v, want file size: %d bytes\", info.Size(), 8)\n\t}\n\n\tif err := cli.WriteStreamWithLength(\"/404/works.txt\", strings.NewReader(content), int64(len(content)), 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tif info, err := fs.Stat(ctx, \"/404/works.txt\"); err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n}\n\nfunc TestWriteStreamWithLengthFromPipe(t *testing.T) {\n\tcli, srv, fs, ctx := newServer(t)\n\tdefer srv.Close()\n\n\tr, w := io.Pipe()\n\n\tgo func() {\n\t\tdefer w.Close()\n\t\tfmt.Fprint(w, \"foo\")\n\t\ttime.Sleep(1 * time.Second)\n\t\tfmt.Fprint(w, \" \")\n\t\ttime.Sleep(1 * time.Second)\n\t\tfmt.Fprint(w, \"bar\\n\")\n\t}()\n\n\tif err := cli.WriteStreamWithLength(\"/newfile.txt\", r, 8, 0660); err != nil {\n\t\tt.Fatalf(\"got: %v, want nil\", err)\n\t}\n\n\tinfo, err := fs.Stat(ctx, \"/newfile.txt\")\n\tif err != nil {\n\t\tt.Fatalf(\"got: %v, want file info: %v\", err, info)\n\t}\n\tif info.Size() != 8 {\n\t\tt.Fatalf(\"got: %v, want file size: %d bytes\", info.Size(), 8)\n\t}\n}\n\nfunc TestWriteStreamWithLengthIncorrectContentLength(t *testing.T) {\n\tcli, srv, _, _ := newServer(t)\n\tdefer srv.Close()\n\n\tcontent := \"foo bar\\n\"\n\tif err := cli.WriteStreamWithLength(\"/newfile1.txt\", strings.NewReader(content), int64(len(content)+10), 0660); err == nil {\n\t\tt.Fatalf(\"got: expected error on invalid content length\")\n\t}\n\n\tlongContent := \"this is a very long content that exceeds the declared length\"\n\tif err := cli.WriteStreamWithLength(\"/newfile2.txt\", strings.NewReader(longContent), 10, 0660); err == nil {\n\t\tt.Fatalf(\"got: expected error on invalid content length\")\n\t}\n}\n"
  },
  {
    "path": "cmd/gowebdav/README.md",
    "content": "# Description\nCommand line tool for [gowebdav](https://github.com/studio-b12/gowebdav) library.\n\n# Prerequisites\n## Software\n* **OS**: all, which are supported by `Golang`\n* **Golang**: version 1.x\n* **Git**: version 2.14.2 at higher (required to install via `go get`)\n\n# Install\n```sh\ngo get -u github.com/studio-b12/gowebdav/cmd/gowebdav\n```\n\n# Usage\nIt is recommended to set following environment variables to improve your experience with this tool:\n* `ROOT` is an URL of target WebDAV server (e.g. `https://webdav.mydomain.me/user_root_folder`)\n* `USER` is a login to connect to specified server (e.g. `user`)\n* `PASSWORD` is a password to connect to specified server (e.g. `p@s$w0rD`)\n\nIn following examples we suppose that:\n* environment variable `ROOT` is set to `https://webdav.mydomain.me/ufolder`\n* environment variable `USER` is set to `user`\n* environment variable `PASSWORD` is set `p@s$w0rD`\n* folder `/ufolder/temp` exists on the server\n* file `/ufolder/temp/file.txt` exists on the server\n* file `/ufolder/temp/document.rtf` exists on the server\n* file `/tmp/webdav/to_upload.txt` exists on the local machine\n* folder `/tmp/webdav/` is used to download files from the server\n\n## Examples\n\n#### Get content of specified folder\n```sh\ngowebdav -X LS temp\n```\n\n#### Get info about file/folder\n```sh\ngowebdav -X STAT temp\ngowebdav -X STAT temp/file.txt\n```\n\n#### Create folder on the remote server\n```sh\ngowebdav -X MKDIR temp2\ngowebdav -X MKDIRALL all/folders/which-you-want/to_create\n```\n\n#### Download file\n```sh\ngowebdav -X GET temp/document.rtf /tmp/webdav/document.rtf\n```\n\nYou may do not specify target local path, in this case file will be downloaded to the current folder with the\n\n#### Upload file\n```sh\ngowebdav -X PUT temp/uploaded.txt /tmp/webdav/to_upload.txt\n```\n\n#### Move file on the remote server\n```sh\ngowebdav -X MV temp/file.txt temp/moved_file.txt\n```\n\n#### Copy file to another location\n```sh\ngowebdav -X MV temp/file.txt temp/file-copy.txt\n```\n\n#### Delete file from the remote server\n```sh\ngowebdav -X DEL temp/file.txt\n```\n\n# Wrapper script\n\nYou can create wrapper script for your server (via `$EDITOR ./dav && chmod a+x ./dav`) and add following content to it:\n```sh\n#!/bin/sh\n\nROOT=\"https://my.dav.server/\" \\\nUSER=\"foo\" \\\nPASSWORD=\"$(pass dav/foo@my.dav.server)\" \\\ngowebdav $@\n```\n\nIt allows you to use [pass](https://www.passwordstore.org/ \"the standard unix password manager\") or similar tools to retrieve the password.\n\n## Examples\n\nUsing the `dav` wrapper:\n\n```sh\n$ ./dav -X LS /\n\n$ echo hi dav! > hello && ./dav -X PUT /hello\n$ ./dav -X STAT /hello\n$ ./dav -X PUT /hello_dav hello\n$ ./dav -X GET /hello_dav\n$ ./dav -X GET /hello_dav hello.txt\n```"
  },
  {
    "path": "cmd/gowebdav/main.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"os/user\"\n\t\"path\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"strings\"\n\n\td \"github.com/studio-b12/gowebdav\"\n)\n\nfunc main() {\n\troot := flag.String(\"root\", os.Getenv(\"ROOT\"), \"WebDAV Endpoint [ENV.ROOT]\")\n\tuser := flag.String(\"user\", os.Getenv(\"USER\"), \"User [ENV.USER]\")\n\tpassword := flag.String(\"pw\", os.Getenv(\"PASSWORD\"), \"Password [ENV.PASSWORD]\")\n\tnetrc := flag.String(\"netrc-file\", filepath.Join(getHome(), \".netrc\"), \"read login from netrc file\")\n\tmethod := flag.String(\"X\", \"\", `Method:\n\tLS <PATH>\n\tSTAT <PATH>\n\n\tMKDIR <PATH>\n\tMKDIRALL <PATH>\n\n\tGET <PATH> [<FILE>]\n\tPUT <PATH> [<FILE>]\n\n\tMV <OLD> <NEW>\n\tCP <OLD> <NEW>\n\n\tDEL <PATH>\n\t`)\n\tflag.Parse()\n\n\tif *root == \"\" {\n\t\tfail(\"Set WebDAV ROOT\")\n\t}\n\n\tif argsLength := len(flag.Args()); argsLength == 0 || argsLength > 2 {\n\t\tfail(\"Unsupported arguments\")\n\t}\n\n\tif *password == \"\" {\n\t\tif u, p := d.ReadConfig(*root, *netrc); u != \"\" && p != \"\" {\n\t\t\tuser = &u\n\t\t\tpassword = &p\n\t\t}\n\t}\n\n\tc := d.NewClient(*root, *user, *password)\n\n\tif e := c.Connect(); e != nil {\n\t\tpanic(e)\n\t}\n\n\tcmd := getCmd(*method)\n\n\tif e := cmd(c, flag.Arg(0), flag.Arg(1)); e != nil {\n\t\tfail(e)\n\t}\n}\n\nfunc fail(err interface{}) {\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tos.Exit(-1)\n}\n\nfunc getHome() string {\n\tu, e := user.Current()\n\tif e != nil {\n\t\treturn os.Getenv(\"HOME\")\n\t}\n\n\tif u != nil {\n\t\treturn u.HomeDir\n\t}\n\n\tswitch runtime.GOOS {\n\tcase \"windows\":\n\t\treturn \"\"\n\tdefault:\n\t\treturn \"~/\"\n\t}\n}\n\nfunc getCmd(method string) func(c *d.Client, p0, p1 string) error {\n\tswitch strings.ToUpper(method) {\n\tcase \"LS\", \"LIST\", \"PROPFIND\":\n\t\treturn cmdLs\n\n\tcase \"STAT\":\n\t\treturn cmdStat\n\n\tcase \"GET\", \"PULL\", \"READ\":\n\t\treturn cmdGet\n\n\tcase \"DELETE\", \"RM\", \"DEL\":\n\t\treturn cmdRm\n\n\tcase \"MKCOL\", \"MKDIR\":\n\t\treturn cmdMkdir\n\n\tcase \"MKCOLALL\", \"MKDIRALL\", \"MKDIRP\":\n\t\treturn cmdMkdirAll\n\n\tcase \"RENAME\", \"MV\", \"MOVE\":\n\t\treturn cmdMv\n\n\tcase \"COPY\", \"CP\":\n\t\treturn cmdCp\n\n\tcase \"PUT\", \"PUSH\", \"WRITE\":\n\t\treturn cmdPut\n\n\tdefault:\n\t\treturn func(c *d.Client, p0, p1 string) (err error) {\n\t\t\treturn errors.New(\"Unsupported method: \" + method)\n\t\t}\n\t}\n}\n\nfunc cmdLs(c *d.Client, p0, _ string) (err error) {\n\tfiles, err := c.ReadDir(p0)\n\tif err == nil {\n\t\tfmt.Println(fmt.Sprintf(\"ReadDir: '%s' entries: %d \", p0, len(files)))\n\t\tfor _, f := range files {\n\t\t\tfmt.Println(f)\n\t\t}\n\t}\n\treturn\n}\n\nfunc cmdStat(c *d.Client, p0, _ string) (err error) {\n\tfile, err := c.Stat(p0)\n\tif err == nil {\n\t\tfmt.Println(file)\n\t}\n\treturn\n}\n\nfunc cmdGet(c *d.Client, p0, p1 string) (err error) {\n\tbytes, err := c.Read(p0)\n\tif err == nil {\n\t\tif p1 == \"\" {\n\t\t\tp1 = filepath.Join(\".\", p0)\n\t\t}\n\t\terr = writeFile(p1, bytes, 0644)\n\t\tif err == nil {\n\t\t\tfmt.Println(fmt.Sprintf(\"Written %d bytes to: %s\", len(bytes), p1))\n\t\t}\n\t}\n\treturn\n}\n\nfunc cmdRm(c *d.Client, p0, _ string) (err error) {\n\tif err = c.Remove(p0); err == nil {\n\t\tfmt.Println(\"Remove: \" + p0)\n\t}\n\treturn\n}\n\nfunc cmdMkdir(c *d.Client, p0, _ string) (err error) {\n\tif err = c.Mkdir(p0, 0755); err == nil {\n\t\tfmt.Println(\"Mkdir: \" + p0)\n\t}\n\treturn\n}\n\nfunc cmdMkdirAll(c *d.Client, p0, _ string) (err error) {\n\tif err = c.MkdirAll(p0, 0755); err == nil {\n\t\tfmt.Println(\"MkdirAll: \" + p0)\n\t}\n\treturn\n}\n\nfunc cmdMv(c *d.Client, p0, p1 string) (err error) {\n\tif err = c.Rename(p0, p1, true); err == nil {\n\t\tfmt.Println(\"Rename: \" + p0 + \" -> \" + p1)\n\t}\n\treturn\n}\n\nfunc cmdCp(c *d.Client, p0, p1 string) (err error) {\n\tif err = c.Copy(p0, p1, true); err == nil {\n\t\tfmt.Println(\"Copy: \" + p0 + \" -> \" + p1)\n\t}\n\treturn\n}\n\nfunc cmdPut(c *d.Client, p0, p1 string) (err error) {\n\tif p1 == \"\" {\n\t\tp1 = path.Join(\".\", p0)\n\t} else {\n\t\tvar fi fs.FileInfo\n\t\tfi, err = c.Stat(p0)\n\t\tif err != nil && !d.IsErrNotFound(err) {\n\t\t\treturn\n\t\t}\n\t\tif !d.IsErrNotFound(err) && fi.IsDir() {\n\t\t\tp0 = path.Join(p0, p1)\n\t\t}\n\t}\n\n\tstream, err := getStream(p1)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer stream.Close()\n\n\tif err = c.WriteStream(p0, stream, 0644); err == nil {\n\t\tfmt.Println(\"Put: \" + p1 + \" -> \" + p0)\n\t}\n\treturn\n}\n\nfunc writeFile(path string, bytes []byte, mode os.FileMode) error {\n\tparent := filepath.Dir(path)\n\tif _, e := os.Stat(parent); os.IsNotExist(e) {\n\t\tif e := os.MkdirAll(parent, os.ModePerm); e != nil {\n\t\t\treturn e\n\t\t}\n\t}\n\n\tf, err := os.Create(path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\n\t_, err = f.Write(bytes)\n\treturn err\n}\n\nfunc getStream(pathOrString string) (io.ReadCloser, error) {\n\n\tfi, err := os.Stat(pathOrString)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif fi.IsDir() {\n\t\treturn nil, &os.PathError{\n\t\t\tOp:   \"Open\",\n\t\t\tPath: pathOrString,\n\t\t\tErr:  errors.New(\"Path: '\" + pathOrString + \"' is a directory\"),\n\t\t}\n\t}\n\n\tf, err := os.Open(pathOrString)\n\tif err == nil {\n\t\treturn f, nil\n\t}\n\n\treturn nil, &os.PathError{\n\t\tOp:   \"Open\",\n\t\tPath: pathOrString,\n\t\tErr:  err,\n\t}\n}\n"
  },
  {
    "path": "digestAuth.go",
    "content": "package gowebdav\n\nimport (\n\t\"crypto/md5\"\n\t\"crypto/rand\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n)\n\n// DigestAuth structure holds our credentials\ntype DigestAuth struct {\n\tuser        string\n\tpw          string\n\tdigestParts map[string]string\n}\n\n// NewDigestAuth creates a new instance of our Digest Authenticator\nfunc NewDigestAuth(login, secret string, rs *http.Response) (Authenticator, error) {\n\treturn &DigestAuth{user: login, pw: secret, digestParts: digestParts(rs)}, nil\n}\n\n// Authorize the current request\nfunc (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) error {\n\td.digestParts[\"uri\"] = path\n\td.digestParts[\"method\"] = rq.Method\n\td.digestParts[\"username\"] = d.user\n\td.digestParts[\"password\"] = d.pw\n\trq.Header.Set(\"Authorization\", getDigestAuthorization(d.digestParts))\n\treturn nil\n}\n\n// Verify checks for authentication issues and may trigger a re-authentication\nfunc (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {\n\tif rs.StatusCode == 401 {\n\t\tif isStaled(rs) {\n\t\t\tredo = true\n\t\t\terr = ErrAuthChanged\n\t\t} else {\n\t\t\terr = NewPathError(\"Authorize\", path, rs.StatusCode)\n\t\t}\n\t}\n\treturn\n}\n\n// Close cleans up all resources\nfunc (d *DigestAuth) Close() error {\n\treturn nil\n}\n\n// Clone creates a copy of itself\nfunc (d *DigestAuth) Clone() Authenticator {\n\tparts := make(map[string]string, len(d.digestParts))\n\tfor k, v := range d.digestParts {\n\t\tparts[k] = v\n\t}\n\treturn &DigestAuth{user: d.user, pw: d.pw, digestParts: parts}\n}\n\n// String toString\nfunc (d *DigestAuth) String() string {\n\treturn fmt.Sprintf(\"DigestAuth login: %s\", d.user)\n}\n\nfunc digestParts(resp *http.Response) map[string]string {\n\tresult := map[string]string{}\n\tif len(resp.Header[\"Www-Authenticate\"]) > 0 {\n\t\twantedHeaders := []string{\"nonce\", \"realm\", \"qop\", \"opaque\", \"algorithm\", \"entityBody\"}\n\t\tresponseHeaders := strings.Split(resp.Header[\"Www-Authenticate\"][0], \",\")\n\t\tfor _, r := range responseHeaders {\n\t\t\tfor _, w := range wantedHeaders {\n\t\t\t\tif strings.Contains(r, w) {\n\t\t\t\t\tresult[w] = strings.Trim(\n\t\t\t\t\t\tstrings.SplitN(r, `=`, 2)[1],\n\t\t\t\t\t\t`\"`,\n\t\t\t\t\t)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn result\n}\n\nfunc getMD5(text string) string {\n\thasher := md5.New()\n\thasher.Write([]byte(text))\n\treturn hex.EncodeToString(hasher.Sum(nil))\n}\n\nfunc getCnonce() string {\n\tb := make([]byte, 8)\n\tio.ReadFull(rand.Reader, b)\n\treturn fmt.Sprintf(\"%x\", b)[:16]\n}\n\nfunc getDigestAuthorization(digestParts map[string]string) string {\n\td := digestParts\n\t// These are the correct ha1 and ha2 for qop=auth. We should probably check for other types of qop.\n\n\tvar (\n\t\tha1        string\n\t\tha2        string\n\t\tnonceCount = 00000001\n\t\tcnonce     = getCnonce()\n\t\tresponse   string\n\t)\n\n\t// 'ha1' value depends on value of \"algorithm\" field\n\tswitch d[\"algorithm\"] {\n\tcase \"MD5\", \"\":\n\t\tha1 = getMD5(d[\"username\"] + \":\" + d[\"realm\"] + \":\" + d[\"password\"])\n\tcase \"MD5-sess\":\n\t\tha1 = getMD5(\n\t\t\tfmt.Sprintf(\"%s:%v:%s\",\n\t\t\t\tgetMD5(d[\"username\"]+\":\"+d[\"realm\"]+\":\"+d[\"password\"]),\n\t\t\t\tnonceCount,\n\t\t\t\tcnonce,\n\t\t\t),\n\t\t)\n\t}\n\n\t// 'ha2' value depends on value of \"qop\" field\n\tswitch d[\"qop\"] {\n\tcase \"auth\", \"\":\n\t\tha2 = getMD5(d[\"method\"] + \":\" + d[\"uri\"])\n\tcase \"auth-int\":\n\t\tif d[\"entityBody\"] != \"\" {\n\t\t\tha2 = getMD5(d[\"method\"] + \":\" + d[\"uri\"] + \":\" + getMD5(d[\"entityBody\"]))\n\t\t}\n\t}\n\n\t// 'response' value depends on value of \"qop\" field\n\tswitch d[\"qop\"] {\n\tcase \"\":\n\t\tresponse = getMD5(\n\t\t\tfmt.Sprintf(\"%s:%s:%s\",\n\t\t\t\tha1,\n\t\t\t\td[\"nonce\"],\n\t\t\t\tha2,\n\t\t\t),\n\t\t)\n\tcase \"auth\", \"auth-int\":\n\t\tresponse = getMD5(\n\t\t\tfmt.Sprintf(\"%s:%s:%v:%s:%s:%s\",\n\t\t\t\tha1,\n\t\t\t\td[\"nonce\"],\n\t\t\t\tnonceCount,\n\t\t\t\tcnonce,\n\t\t\t\td[\"qop\"],\n\t\t\t\tha2,\n\t\t\t),\n\t\t)\n\t}\n\n\tauthorization := fmt.Sprintf(`Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=%v, cnonce=\"%s\", response=\"%s\"`,\n\t\td[\"username\"], d[\"realm\"], d[\"nonce\"], d[\"uri\"], nonceCount, cnonce, response)\n\n\tif d[\"qop\"] != \"\" {\n\t\tauthorization += fmt.Sprintf(`, qop=%s`, d[\"qop\"])\n\t}\n\n\tif d[\"opaque\"] != \"\" {\n\t\tauthorization += fmt.Sprintf(`, opaque=\"%s\"`, d[\"opaque\"])\n\t}\n\n\treturn authorization\n}\n\nfunc isStaled(rs *http.Response) bool {\n\theader := rs.Header.Get(\"Www-Authenticate\")\n\tif len(header) > 0 {\n\t\tdirectives := strings.Split(header, \",\")\n\t\tfor i := range directives {\n\t\t\tname, value, _ := strings.Cut(strings.Trim(directives[i], \" \"), \"=\")\n\t\t\tif strings.EqualFold(name, \"stale\") {\n\t\t\t\treturn strings.EqualFold(value, \"true\")\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n"
  },
  {
    "path": "digestAuth_test.go",
    "content": "package gowebdav\n\nimport (\n\t\"errors\"\n\t\"net/http\"\n\t\"strings\"\n\t\"testing\"\n)\n\nfunc TestNewDigestAuth(t *testing.T) {\n\ta := &DigestAuth{user: \"user\", pw: \"password\", digestParts: make(map[string]string, 0)}\n\n\tex := \"DigestAuth login: user\"\n\tif a.String() != ex {\n\t\tt.Error(\"expected: \" + ex + \" got: \" + a.String())\n\t}\n\n\tif a.Clone() == a {\n\t\tt.Error(\"expected a different instance\")\n\t}\n\n\tif a.Close() != nil {\n\t\tt.Error(\"expected close without errors\")\n\t}\n}\n\nfunc TestDigestAuthAuthorize(t *testing.T) {\n\ta := &DigestAuth{user: \"user\", pw: \"password\", digestParts: make(map[string]string, 0)}\n\trq, _ := http.NewRequest(\"GET\", \"http://localhost/\", nil)\n\ta.Authorize(nil, rq, \"/\")\n\t// TODO this is a very lazy test it cuts of cnonce\n\tex := `Digest username=\"user\", realm=\"\", nonce=\"\", uri=\"/\", nc=1, cnonce=\"`\n\tif strings.Index(rq.Header.Get(\"Authorization\"), ex) != 0 {\n\t\tt.Error(\"got wrong Authorization header: \" + rq.Header.Get(\"Authorization\"))\n\t}\n}\n\nfunc TestDigestAuthVerify(t *testing.T) {\n\ta := &DigestAuth{user: \"user\", pw: \"password\", digestParts: make(map[string]string, 0)}\n\n\t// Nominal test: 200 OK response\n\trs := &http.Response{\n\t\tStatus:     \"200 OK\",\n\t\tStatusCode: http.StatusOK,\n\t}\n\n\tredo, err := a.Verify(nil, rs, \"/\")\n\n\tif err != nil {\n\t\tt.Errorf(\"got error: %v, want nil\", err)\n\t}\n\n\tif redo {\n\t\tt.Errorf(\"got redo: %t, want false\", redo)\n\t}\n\n\t// Digest expiration test: 401 Unauthorized response with stale directive in WWW-Authenticate header\n\trs = &http.Response{\n\t\tStatus:     \"401 Unauthorized\",\n\t\tStatusCode: http.StatusUnauthorized,\n\t\tHeader: http.Header{\n\t\t\t\"Www-Authenticate\": []string{\"Digest realm=\\\"webdav\\\", nonce=\\\"YVvALpkdBgA=931bbf2b6fa9dda227361dba38a735f005fd9f97\\\", algorithm=MD5, qop=\\\"auth\\\", stale=true\"},\n\t\t},\n\t}\n\n\tredo, err = a.Verify(nil, rs, \"/\")\n\n\tif !errors.Is(err, ErrAuthChanged) {\n\t\tt.Errorf(\"got error: %v, want ErrAuthChanged\", err)\n\t}\n\n\tif !redo {\n\t\tt.Errorf(\"got redo: %t, want true\", redo)\n\t}\n}\n"
  },
  {
    "path": "doc.go",
    "content": "// Package gowebdav is a WebDAV client library with a command line tool\n// included.\npackage gowebdav\n"
  },
  {
    "path": "errors.go",
    "content": "package gowebdav\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"os\"\n)\n\n// ErrAuthChanged must be returned from the Verify method as an error\n// to trigger a re-authentication / negotiation with a new authenticator.\nvar ErrAuthChanged = errors.New(\"authentication failed, change algorithm\")\n\n// ErrTooManyRedirects will be used as return error if a request exceeds 10 redirects.\nvar ErrTooManyRedirects = errors.New(\"stopped after 10 redirects\")\n\n// StatusError implements error and wraps\n// an erroneous status code.\ntype StatusError struct {\n\tStatus int\n}\n\nfunc (se StatusError) Error() string {\n\treturn fmt.Sprintf(\"%d\", se.Status)\n}\n\n// IsErrCode returns true if the given error\n// is an os.PathError wrapping a StatusError\n// with the given status code.\nfunc IsErrCode(err error, code int) bool {\n\tif pe, ok := err.(*os.PathError); ok {\n\t\tse, ok := pe.Err.(StatusError)\n\t\treturn ok && se.Status == code\n\t}\n\treturn false\n}\n\n// IsErrNotFound is shorthand for IsErrCode\n// for status 404.\nfunc IsErrNotFound(err error) bool {\n\treturn IsErrCode(err, 404)\n}\n\nfunc NewPathError(op string, path string, statusCode int) error {\n\treturn &os.PathError{\n\t\tOp:   op,\n\t\tPath: path,\n\t\tErr:  StatusError{statusCode},\n\t}\n}\n\nfunc NewPathErrorErr(op string, path string, err error) error {\n\treturn &os.PathError{\n\t\tOp:   op,\n\t\tPath: path,\n\t\tErr:  err,\n\t}\n}\n"
  },
  {
    "path": "file.go",
    "content": "package gowebdav\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"time\"\n)\n\n// File is our structure for a given file\ntype File struct {\n\tpath        string\n\tname        string\n\tcontentType string\n\tsize        int64\n\tmodified    time.Time\n\tetag        string\n\tisdir       bool\n}\n\n// Path returns the full path of a file\nfunc (f File) Path() string {\n\treturn f.path\n}\n\n// Name returns the name of a file\nfunc (f File) Name() string {\n\treturn f.name\n}\n\n// ContentType returns the content type of a file\nfunc (f File) ContentType() string {\n\treturn f.contentType\n}\n\n// Size returns the size of a file\nfunc (f File) Size() int64 {\n\treturn f.size\n}\n\n// Mode will return the mode of a given file\nfunc (f File) Mode() os.FileMode {\n\t// TODO check webdav perms\n\tif f.isdir {\n\t\treturn 0775 | os.ModeDir\n\t}\n\n\treturn 0664\n}\n\n// ModTime returns the modified time of a file\nfunc (f File) ModTime() time.Time {\n\treturn f.modified\n}\n\n// ETag returns the ETag of a file\nfunc (f File) ETag() string {\n\treturn f.etag\n}\n\n// IsDir let us see if a given file is a directory or not\nfunc (f File) IsDir() bool {\n\treturn f.isdir\n}\n\n// Sys ????\nfunc (f File) Sys() interface{} {\n\treturn nil\n}\n\n// String lets us see file information\nfunc (f File) String() string {\n\tif f.isdir {\n\t\treturn fmt.Sprintf(\"Dir : '%s' - '%s'\", f.path, f.name)\n\t}\n\n\treturn fmt.Sprintf(\"File: '%s' SIZE: %d MODIFIED: %s ETAG: %s CTYPE: %s\", f.path, f.size, f.modified.String(), f.etag, f.contentType)\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/studio-b12/gowebdav\n\ngo 1.17\n"
  },
  {
    "path": "go_test.mod",
    "content": "module github.com/studio-b12/gowebdav\n\ngo 1.17\n\nrequire golang.org/x/net v0.0.0-20221014081412-f15817d10f9b\n"
  },
  {
    "path": "go_test.sum",
    "content": "golang.org/x/net v0.0.0-20221014081412-f15817d10f9b h1:tvrvnPFcdzp294diPnrdZZZ8XUt2Tyj7svb7X52iDuU=\ngolang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=\ngolang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=\ngolang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\n"
  },
  {
    "path": "netrc.go",
    "content": "package gowebdav\n\nimport (\n\t\"bufio\"\n\t\"fmt\"\n\t\"net/url\"\n\t\"os\"\n\t\"regexp\"\n\t\"strings\"\n)\n\nfunc parseLine(s string) (login, pass string) {\n\tfields := strings.Fields(s)\n\tfor i, f := range fields {\n\t\tif f == \"login\" {\n\t\t\tlogin = fields[i+1]\n\t\t}\n\t\tif f == \"password\" {\n\t\t\tpass = fields[i+1]\n\t\t}\n\t}\n\treturn login, pass\n}\n\n// ReadConfig reads login and password configuration from ~/.netrc\n// machine foo.com login username password 123456\nfunc ReadConfig(uri, netrc string) (string, string) {\n\tu, err := url.Parse(uri)\n\tif err != nil {\n\t\treturn \"\", \"\"\n\t}\n\n\tfile, err := os.Open(netrc)\n\tif err != nil {\n\t\treturn \"\", \"\"\n\t}\n\tdefer file.Close()\n\n\tre := fmt.Sprintf(`^.*machine %s.*$`, u.Host)\n\tscanner := bufio.NewScanner(file)\n\tfor scanner.Scan() {\n\t\ts := scanner.Text()\n\n\t\tmatched, err := regexp.MatchString(re, s)\n\t\tif err != nil {\n\t\t\treturn \"\", \"\"\n\t\t}\n\t\tif matched {\n\t\t\treturn parseLine(s)\n\t\t}\n\t}\n\n\treturn \"\", \"\"\n}\n"
  },
  {
    "path": "passportAuth.go",
    "content": "package gowebdav\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n)\n\n// PassportAuth structure holds our credentials\ntype PassportAuth struct {\n\tuser            string\n\tpw              string\n\tcookies         []http.Cookie\n\tinhibitRedirect bool\n}\n\n// constructor for PassportAuth creates a new PassportAuth object and\n// automatically authenticates against the given partnerURL\nfunc NewPassportAuth(c *http.Client, user, pw, partnerURL string, header *http.Header) (Authenticator, error) {\n\tp := &PassportAuth{\n\t\tuser:            user,\n\t\tpw:              pw,\n\t\tinhibitRedirect: true,\n\t}\n\terr := p.genCookies(c, partnerURL, header)\n\treturn p, err\n}\n\n// Authorize the current request\nfunc (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error {\n\t// prevent redirects to detect subsequent authentication requests\n\tif p.inhibitRedirect {\n\t\trq.Header.Set(XInhibitRedirect, \"1\")\n\t} else {\n\t\tp.inhibitRedirect = true\n\t}\n\tfor _, cookie := range p.cookies {\n\t\trq.AddCookie(&cookie)\n\t}\n\treturn nil\n}\n\n// Verify verifies if the authentication is good\nfunc (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) {\n\tswitch rs.StatusCode {\n\tcase 301, 302, 307, 308:\n\t\tredo = true\n\t\tif rs.Header.Get(\"Www-Authenticate\") != \"\" {\n\t\t\t// re-authentication required as we are redirected to the login page\n\t\t\terr = p.genCookies(c, rs.Request.URL.String(), &rs.Header)\n\t\t} else {\n\t\t\t// just a redirect, follow it\n\t\t\tp.inhibitRedirect = false\n\t\t}\n\tcase 401:\n\t\terr = NewPathError(\"Authorize\", path, rs.StatusCode)\n\t}\n\treturn\n}\n\n// Close cleans up all resources\nfunc (p *PassportAuth) Close() error {\n\treturn nil\n}\n\n// Clone creates a Copy of itself\nfunc (p *PassportAuth) Clone() Authenticator {\n\t// create a copy to allow independent cookie updates\n\tclonedCookies := make([]http.Cookie, len(p.cookies))\n\tcopy(clonedCookies, p.cookies)\n\n\treturn &PassportAuth{\n\t\tuser:            p.user,\n\t\tpw:              p.pw,\n\t\tcookies:         clonedCookies,\n\t\tinhibitRedirect: true,\n\t}\n}\n\n// String toString\nfunc (p *PassportAuth) String() string {\n\treturn fmt.Sprintf(\"PassportAuth login: %s\", p.user)\n}\n\nfunc (p *PassportAuth) genCookies(c *http.Client, partnerUrl string, header *http.Header) error {\n\t// For more details refer to:\n\t// https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-pass/2c80637d-438c-4d4b-adc5-903170a779f3\n\t// Skipping step 1 and 2 as we already have the partner server challenge\n\n\tbaseAuthenticationServer := header.Get(\"Location\")\n\tbaseAuthenticationServerURL, err := url.Parse(baseAuthenticationServer)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// Skipping step 3 and 4 as we already know that we need and have the user's credentials\n\t// Step 5 (Sign-in request)\n\tauthenticationServerUrl := url.URL{\n\t\tScheme: baseAuthenticationServerURL.Scheme,\n\t\tHost:   baseAuthenticationServerURL.Host,\n\t\tPath:   \"/login2.srf\",\n\t}\n\n\tpartnerServerChallenge := strings.Split(header.Get(\"Www-Authenticate\"), \" \")[1]\n\n\treq := http.Request{\n\t\tMethod: \"GET\",\n\t\tURL:    &authenticationServerUrl,\n\t\tHeader: http.Header{\n\t\t\t\"Authorization\": []string{\"Passport1.4 sign-in=\" + url.QueryEscape(p.user) + \",pwd=\" + url.QueryEscape(p.pw) + \",OrgVerb=GET,OrgUrl=\" + partnerUrl + \",\" + partnerServerChallenge},\n\t\t},\n\t}\n\n\trs, err := c.Do(&req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tio.Copy(io.Discard, rs.Body)\n\trs.Body.Close()\n\tif rs.StatusCode != 200 {\n\t\treturn NewPathError(\"Authorize\", \"/\", rs.StatusCode)\n\t}\n\n\t// Step 6 (Token Response from Authentication Server)\n\ttokenResponseHeader := rs.Header.Get(\"Authentication-Info\")\n\tif tokenResponseHeader == \"\" {\n\t\treturn NewPathError(\"Authorize\", \"/\", 401)\n\t}\n\ttokenResponseHeaderList := strings.Split(tokenResponseHeader, \",\")\n\ttoken := \"\"\n\tfor _, tokenResponseHeader := range tokenResponseHeaderList {\n\t\tif strings.HasPrefix(tokenResponseHeader, \"from-PP='\") {\n\t\t\ttoken = tokenResponseHeader\n\t\t\tbreak\n\t\t}\n\t}\n\tif token == \"\" {\n\t\treturn NewPathError(\"Authorize\", \"/\", 401)\n\t}\n\n\t// Step 7 (First Authentication Request to Partner Server)\n\torigUrl, err := url.Parse(partnerUrl)\n\tif err != nil {\n\t\treturn err\n\t}\n\treq = http.Request{\n\t\tMethod: \"GET\",\n\t\tURL:    origUrl,\n\t\tHeader: http.Header{\n\t\t\t\"Authorization\": []string{\"Passport1.4 \" + token},\n\t\t},\n\t}\n\n\trs, err = c.Do(&req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tio.Copy(io.Discard, rs.Body)\n\trs.Body.Close()\n\tif rs.StatusCode != 200 && rs.StatusCode != 302 {\n\t\treturn NewPathError(\"Authorize\", \"/\", rs.StatusCode)\n\t}\n\n\t// Step 8 (Set Token Message from Partner Server)\n\tcookies := rs.Header.Values(\"Set-Cookie\")\n\tp.cookies = make([]http.Cookie, len(cookies))\n\tfor i, cookie := range cookies {\n\t\tcookieParts := strings.Split(cookie, \";\")\n\t\tcookieName := strings.Split(cookieParts[0], \"=\")[0]\n\t\tcookieValue := strings.Split(cookieParts[0], \"=\")[1]\n\n\t\tp.cookies[i] = http.Cookie{\n\t\t\tName:  cookieName,\n\t\t\tValue: cookieValue,\n\t\t}\n\t}\n\n\treturn nil\n}\n"
  },
  {
    "path": "passportAuth_test.go",
    "content": "package gowebdav\n\nimport (\n\t\"bytes\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"regexp\"\n\t\"testing\"\n)\n\n// testing the creation is enough as it handles the authorization during init\nfunc TestNewPassportAuth(t *testing.T) {\n\tuser := \"user\"\n\tpass := \"password\"\n\tp1 := \"some,comma,separated,values\"\n\ttoken := \"from-PP='token'\"\n\n\tauthHandler := func(h http.Handler) http.HandlerFunc {\n\t\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t\treg, err := regexp.Compile(\"Passport1\\\\.4 sign-in=\" + url.QueryEscape(user) + \",pwd=\" + url.QueryEscape(pass) + \",OrgVerb=GET,OrgUrl=.*,\" + p1)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif reg.MatchString(r.Header.Get(\"Authorization\")) {\n\t\t\t\tw.Header().Set(\"Authentication-Info\", token)\n\t\t\t\tw.WriteHeader(200)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n\tauthsrv, _, _ := newAuthSrv(t, authHandler)\n\tdefer authsrv.Close()\n\n\tdataHandler := func(h http.Handler) http.HandlerFunc {\n\t\treturn func(w http.ResponseWriter, r *http.Request) {\n\t\t\treg, err := regexp.Compile(\"Passport1\\\\.4 \" + token)\n\t\t\tif err != nil {\n\t\t\t\tt.Error(err)\n\t\t\t}\n\t\t\tif reg.MatchString(r.Header.Get(\"Authorization\")) {\n\t\t\t\tw.Header().Set(\"Set-Cookie\", \"Pass=port\")\n\t\t\t\th.ServeHTTP(w, r)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tfor _, c := range r.Cookies() {\n\t\t\t\tif c.Name == \"Pass\" && c.Value == \"port\" {\n\t\t\t\t\th.ServeHTTP(w, r)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\tw.Header().Set(\"Www-Authenticate\", \"Passport1.4 \"+p1)\n\t\t\thttp.Redirect(w, r, authsrv.URL+\"/\", 302)\n\t\t}\n\t}\n\tsrv, _, _ := newAuthSrv(t, dataHandler)\n\tdefer srv.Close()\n\n\tcli := NewClient(srv.URL, user, pass)\n\tdata, err := cli.Read(\"/hello.txt\")\n\tif err != nil {\n\t\tt.Errorf(\"got error=%v; want nil\", err)\n\t}\n\tif !bytes.Equal(data, []byte(\"hello gowebdav\\n\")) {\n\t\tt.Logf(\"got data=%v; want=hello gowebdav\", data)\n\t}\n}\n"
  },
  {
    "path": "requests.go",
    "content": "package gowebdav\n\nimport (\n\t\"io\"\n\t\"log\"\n\t\"net/http\"\n\t\"path\"\n\t\"strings\"\n)\n\nfunc (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (rs *http.Response, err error) {\n\tvar redo bool\n\tvar r *http.Request\n\tvar uri = PathEscape(Join(c.root, path))\n\tauth, body := c.auth.NewAuthenticator(body)\n\tdefer auth.Close()\n\n\tfor { // TODO auth.continue() strategy(true|n times|until)?\n\t\tif r, err = http.NewRequest(method, uri, body); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tfor k, vals := range c.headers {\n\t\t\tfor _, v := range vals {\n\t\t\t\tr.Header.Add(k, v)\n\t\t\t}\n\t\t}\n\n\t\tif err = auth.Authorize(c.c, r, path); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif intercept != nil {\n\t\t\tintercept(r)\n\t\t}\n\n\t\tif c.interceptor != nil {\n\t\t\tc.interceptor(method, r)\n\t\t}\n\n\t\tif rs, err = c.c.Do(r); err != nil {\n\t\t\treturn\n\t\t}\n\n\t\tif redo, err = auth.Verify(c.c, rs, path); err != nil {\n\t\t\trs.Body.Close()\n\t\t\treturn nil, err\n\t\t}\n\t\tif redo {\n\t\t\trs.Body.Close()\n\t\t\tif body, err = r.GetBody(); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tbreak\n\t}\n\n\treturn rs, err\n}\n\nfunc (c *Client) mkcol(path string) (status int, err error) {\n\trs, err := c.req(\"MKCOL\", path, nil, nil)\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer rs.Body.Close()\n\n\tstatus = rs.StatusCode\n\tif status == 405 {\n\t\tstatus = 201\n\t}\n\n\treturn\n}\n\nfunc (c *Client) options(path string) (*http.Response, error) {\n\treturn c.req(\"OPTIONS\", path, nil, func(rq *http.Request) {\n\t\trq.Header.Add(\"Depth\", \"0\")\n\t})\n}\n\nfunc (c *Client) propfind(path string, self bool, body string, resp interface{}, parse func(resp interface{}) error) error {\n\trs, err := c.req(\"PROPFIND\", path, strings.NewReader(body), func(rq *http.Request) {\n\t\tif self {\n\t\t\trq.Header.Add(\"Depth\", \"0\")\n\t\t} else {\n\t\t\trq.Header.Add(\"Depth\", \"1\")\n\t\t}\n\t\trq.Header.Add(\"Content-Type\", \"application/xml;charset=UTF-8\")\n\t\trq.Header.Add(\"Accept\", \"application/xml,text/xml\")\n\t\trq.Header.Add(\"Accept-Charset\", \"utf-8\")\n\t\t// TODO add support for 'gzip,deflate;q=0.8,q=0.7'\n\t\trq.Header.Add(\"Accept-Encoding\", \"\")\n\t})\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer rs.Body.Close()\n\n\tif rs.StatusCode != 207 {\n\t\treturn NewPathError(\"PROPFIND\", path, rs.StatusCode)\n\t}\n\n\treturn parseXML(rs.Body, resp, parse)\n}\n\nfunc (c *Client) doCopyMove(\n\tmethod string,\n\toldpath string,\n\tnewpath string,\n\toverwrite bool,\n) (\n\tstatus int,\n\tr io.ReadCloser,\n\terr error,\n) {\n\trs, err := c.req(method, oldpath, nil, func(rq *http.Request) {\n\t\trq.Header.Add(\"Destination\", PathEscape(Join(c.root, newpath)))\n\t\tif overwrite {\n\t\t\trq.Header.Add(\"Overwrite\", \"T\")\n\t\t} else {\n\t\t\trq.Header.Add(\"Overwrite\", \"F\")\n\t\t}\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\tstatus = rs.StatusCode\n\tr = rs.Body\n\treturn\n}\n\nfunc (c *Client) copymove(method string, oldpath string, newpath string, overwrite bool) (err error) {\n\ts, data, err := c.doCopyMove(method, oldpath, newpath, overwrite)\n\tif err != nil {\n\t\treturn\n\t}\n\tif data != nil {\n\t\tdefer data.Close()\n\t}\n\n\tswitch s {\n\tcase 201, 204:\n\t\treturn nil\n\n\tcase 207:\n\t\t// TODO handle multistat errors, worst case ...\n\t\tlog.Printf(\"TODO handle %s - %s multistatus result %s\\n\", method, oldpath, String(data))\n\n\tcase 409:\n\t\terr := c.createParentCollection(newpath)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\treturn c.copymove(method, oldpath, newpath, overwrite)\n\t}\n\n\treturn NewPathError(method, oldpath, s)\n}\n\nfunc (c *Client) put(path string, stream io.Reader, contentLength int64) (status int, err error) {\n\trs, err := c.req(\"PUT\", path, stream, func(r *http.Request) {\n\t\tr.ContentLength = contentLength\n\t})\n\tif err != nil {\n\t\treturn\n\t}\n\tdefer rs.Body.Close()\n\n\tstatus = rs.StatusCode\n\treturn\n}\n\nfunc (c *Client) createParentCollection(itemPath string) (err error) {\n\tparentPath := path.Dir(itemPath)\n\tif parentPath == \".\" || parentPath == \"/\" {\n\t\treturn nil\n\t}\n\n\treturn c.MkdirAll(parentPath, 0755)\n}\n"
  },
  {
    "path": "utils.go",
    "content": "package gowebdav\n\nimport (\n\t\"bytes\"\n\t\"encoding/xml\"\n\t\"io\"\n\t\"net/url\"\n\t\"strconv\"\n\t\"strings\"\n\t\"time\"\n)\n\n// PathEscape escapes all segments of a given path\nfunc PathEscape(path string) string {\n\ts := strings.Split(path, \"/\")\n\tfor i, e := range s {\n\t\ts[i] = url.PathEscape(e)\n\t}\n\treturn strings.Join(s, \"/\")\n}\n\n// FixSlash appends a trailing / to our string\nfunc FixSlash(s string) string {\n\tif !strings.HasSuffix(s, \"/\") {\n\t\ts += \"/\"\n\t}\n\treturn s\n}\n\n// FixSlashes appends and prepends a / if they are missing\nfunc FixSlashes(s string) string {\n\tif !strings.HasPrefix(s, \"/\") {\n\t\ts = \"/\" + s\n\t}\n\n\treturn FixSlash(s)\n}\n\n// Join joins two paths\nfunc Join(path0 string, path1 string) string {\n\treturn strings.TrimSuffix(path0, \"/\") + \"/\" + strings.TrimPrefix(path1, \"/\")\n}\n\n// String pulls a string out of our io.Reader\nfunc String(r io.Reader) string {\n\tbuf := new(bytes.Buffer)\n\t// TODO - make String return an error as well\n\t_, _ = buf.ReadFrom(r)\n\treturn buf.String()\n}\n\nfunc parseUint(s *string) uint {\n\tif n, e := strconv.ParseUint(*s, 10, 32); e == nil {\n\t\treturn uint(n)\n\t}\n\treturn 0\n}\n\nfunc parseInt64(s *string) int64 {\n\tif n, e := strconv.ParseInt(*s, 10, 64); e == nil {\n\t\treturn n\n\t}\n\treturn 0\n}\n\nfunc parseModified(s *string) time.Time {\n\tif t, e := time.Parse(time.RFC1123, *s); e == nil {\n\t\treturn t\n\t}\n\treturn time.Unix(0, 0)\n}\n\nfunc parseXML(data io.Reader, resp interface{}, parse func(resp interface{}) error) error {\n\tdecoder := xml.NewDecoder(data)\n\tfor t, _ := decoder.Token(); t != nil; t, _ = decoder.Token() {\n\t\tswitch se := t.(type) {\n\t\tcase xml.StartElement:\n\t\t\tif se.Name.Local == \"response\" {\n\t\t\t\tif e := decoder.DecodeElement(resp, &se); e == nil {\n\t\t\t\t\tif err := parse(resp); err != nil {\n\t\t\t\t\t\treturn err\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n\n// limitedReadCloser wraps a io.ReadCloser and limits the number of bytes that can be read from it.\ntype limitedReadCloser struct {\n\trc        io.ReadCloser\n\tremaining int\n}\n\nfunc (l *limitedReadCloser) Read(buf []byte) (int, error) {\n\tif l.remaining <= 0 {\n\t\treturn 0, io.EOF\n\t}\n\n\tif len(buf) > l.remaining {\n\t\tbuf = buf[0:l.remaining]\n\t}\n\n\tn, err := l.rc.Read(buf)\n\tl.remaining -= n\n\n\treturn n, err\n}\n\nfunc (l *limitedReadCloser) Close() error {\n\treturn l.rc.Close()\n}\n"
  },
  {
    "path": "utils_test.go",
    "content": "package gowebdav\n\nimport (\n\t\"fmt\"\n\t\"net/url\"\n\t\"testing\"\n)\n\nfunc TestJoin(t *testing.T) {\n\teq(t, \"/\", \"\", \"\")\n\teq(t, \"/\", \"/\", \"/\")\n\teq(t, \"/foo\", \"\", \"/foo\")\n\teq(t, \"foo/foo\", \"foo/\", \"/foo\")\n\teq(t, \"foo/foo\", \"foo/\", \"foo\")\n}\n\nfunc eq(t *testing.T, expected string, s0 string, s1 string) {\n\ts := Join(s0, s1)\n\tif s != expected {\n\t\tt.Error(\"For\", \"'\"+s0+\"','\"+s1+\"'\", \"expeted\", \"'\"+expected+\"'\", \"got\", \"'\"+s+\"'\")\n\t}\n}\n\nfunc ExamplePathEscape() {\n\tfmt.Println(PathEscape(\"\"))\n\tfmt.Println(PathEscape(\"/\"))\n\tfmt.Println(PathEscape(\"/web\"))\n\tfmt.Println(PathEscape(\"/web/\"))\n\tfmt.Println(PathEscape(\"/w e b/d a v/s%u&c#k:s/\"))\n\n\t// Output:\n\t//\n\t// /\n\t// /web\n\t// /web/\n\t// /w%20e%20b/d%20a%20v/s%25u&c%23k:s/\n}\n\nfunc TestEscapeURL(t *testing.T) {\n\tex := \"https://foo.com/w%20e%20b/d%20a%20v/s%25u&c%23k:s/\"\n\tu, _ := url.Parse(\"https://foo.com\" + PathEscape(\"/w e b/d a v/s%u&c#k:s/\"))\n\tif ex != u.String() {\n\t\tt.Error(\"expected: \" + ex + \" got: \" + u.String())\n\t}\n}\n\nfunc TestFixSlashes(t *testing.T) {\n\texpected := \"/\"\n\n\tif got := FixSlashes(\"\"); got != expected {\n\t\tt.Errorf(\"expected: %q, got: %q\", expected, got)\n\t}\n\n\texpected = \"/path/\"\n\n\tif got := FixSlashes(\"path\"); got != expected {\n\t\tt.Errorf(\"expected: %q, got: %q\", expected, got)\n\t}\n\n\tif got := FixSlashes(\"/path\"); got != expected {\n\t\tt.Errorf(\"expected: %q, got: %q\", expected, got)\n\t}\n\n\tif got := FixSlashes(\"path/\"); got != expected {\n\t\tt.Errorf(\"expected: %q, got: %q\", expected, got)\n\t}\n}\n"
  }
]