mirror of
https://gitlab.bertha.cloud/partitio/Nextcloud-Partitio/gonextcloud
synced 2024-11-22 03:36:24 +00:00
fix #6: added gowebdav and webdav interface (TODO: webdav tests)
This commit is contained in:
parent
bb0b884521
commit
018fc569d3
5
auth.go
5
auth.go
@ -2,7 +2,10 @@ package gonextcloud
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
req "github.com/levigross/grequests"
|
req "github.com/levigross/grequests"
|
||||||
|
"github.com/studio-b12/gowebdav"
|
||||||
|
|
||||||
"gitlab.bertha.cloud/partitio/Nextcloud-Partitio/gonextcloud/types"
|
"gitlab.bertha.cloud/partitio/Nextcloud-Partitio/gonextcloud/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,6 +36,8 @@ func (c *Client) Login(username string, password string) error {
|
|||||||
e := types.APIError{Message: "authentication failed"}
|
e := types.APIError{Message: "authentication failed"}
|
||||||
return &e
|
return &e
|
||||||
}
|
}
|
||||||
|
// Create webdav client
|
||||||
|
c.webdav = gowebdav.NewClient(c.baseURL.String()+"/remote.php/webdav", c.username, c.password)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
17
client.go
17
client.go
@ -1,9 +1,12 @@
|
|||||||
package gonextcloud
|
package gonextcloud
|
||||||
|
|
||||||
import (
|
import (
|
||||||
req "github.com/levigross/grequests"
|
|
||||||
"gitlab.bertha.cloud/partitio/Nextcloud-Partitio/gonextcloud/types"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
req "github.com/levigross/grequests"
|
||||||
|
"github.com/studio-b12/gowebdav"
|
||||||
|
|
||||||
|
"gitlab.bertha.cloud/partitio/Nextcloud-Partitio/gonextcloud/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is the API client that performs all operations against a Nextcloud server.
|
// Client is the API client that performs all operations against a Nextcloud server.
|
||||||
@ -23,6 +26,7 @@ type Client struct {
|
|||||||
shares *Shares
|
shares *Shares
|
||||||
users *Users
|
users *Users
|
||||||
groups *Groups
|
groups *Groups
|
||||||
|
webdav *gowebdav.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient create a new Client from the Nextcloud Instance URL
|
// NewClient create a new Client from the Nextcloud Instance URL
|
||||||
@ -42,6 +46,7 @@ func NewClient(hostname string) (*Client, error) {
|
|||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
c.apps = &Apps{c}
|
c.apps = &Apps{c}
|
||||||
c.appsConfig = &AppsConfig{c}
|
c.appsConfig = &AppsConfig{c}
|
||||||
c.groupFolders = &GroupFolders{c}
|
c.groupFolders = &GroupFolders{c}
|
||||||
@ -49,6 +54,9 @@ func NewClient(hostname string) (*Client, error) {
|
|||||||
c.shares = &Shares{c}
|
c.shares = &Shares{c}
|
||||||
c.users = &Users{c}
|
c.users = &Users{c}
|
||||||
c.groups = &Groups{c}
|
c.groups = &Groups{c}
|
||||||
|
// Create empty webdav client
|
||||||
|
// It will be replaced after login
|
||||||
|
c.webdav = &gowebdav.Client{}
|
||||||
return c, nil
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,3 +94,8 @@ func (c *Client) Users() types.Users {
|
|||||||
func (c *Client) Groups() types.Groups {
|
func (c *Client) Groups() types.Groups {
|
||||||
return c.groups
|
return c.groups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WebDav return the WebDav client Interface
|
||||||
|
func (c *Client) WebDav() types.WebDav {
|
||||||
|
return c.webdav
|
||||||
|
}
|
||||||
|
1
go.mod
1
go.mod
@ -9,6 +9,7 @@ require (
|
|||||||
github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0
|
github.com/pkg/errors v0.0.0-20181023235946-059132a15dd0
|
||||||
github.com/sirupsen/logrus v1.4.2
|
github.com/sirupsen/logrus v1.4.2
|
||||||
github.com/stretchr/testify v1.2.2
|
github.com/stretchr/testify v1.2.2
|
||||||
|
github.com/studio-b12/gowebdav v0.0.0-20190103184047-38f79aeaf1ac
|
||||||
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76 // indirect
|
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76 // indirect
|
||||||
gopkg.in/yaml.v2 v2.2.1
|
gopkg.in/yaml.v2 v2.2.1
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@ -18,6 +18,8 @@ github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
|
|||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/studio-b12/gowebdav v0.0.0-20190103184047-38f79aeaf1ac h1:xQ9gCVzqb939vjhxuES4IXYe4AlHB4Q71/K06aazQmQ=
|
||||||
|
github.com/studio-b12/gowebdav v0.0.0-20190103184047-38f79aeaf1ac/go.mod h1:gCcfDlA1Y7GqOaeEKw5l9dOGx1VLdc/HuQSlQAaZ30s=
|
||||||
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76 h1:xx5MUFyRQRbPk6VjWjIE1epE/K5AoDD8QUN116NCy8k=
|
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76 h1:xx5MUFyRQRbPk6VjWjIE1epE/K5AoDD8QUN116NCy8k=
|
||||||
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
||||||
|
@ -9,6 +9,7 @@ type Client interface {
|
|||||||
Shares() Shares
|
Shares() Shares
|
||||||
Users() Users
|
Users() Users
|
||||||
Groups() Groups
|
Groups() Groups
|
||||||
|
WebDav() WebDav
|
||||||
Login(username string, password string) error
|
Login(username string, password string) error
|
||||||
Logout() error
|
Logout() error
|
||||||
}
|
}
|
||||||
|
34
types/webdav.go
Normal file
34
types/webdav.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WebDav available methods
|
||||||
|
type WebDav interface {
|
||||||
|
// ReadDir reads the contents of a remote directory
|
||||||
|
ReadDir(path string) ([]os.FileInfo, error)
|
||||||
|
// Stat returns the file stats for a specified path
|
||||||
|
Stat(path string) (os.FileInfo, error)
|
||||||
|
// Remove removes a remote file
|
||||||
|
Remove(path string) error
|
||||||
|
// RemoveAll removes remote files
|
||||||
|
RemoveAll(path string) error
|
||||||
|
// Mkdir makes a directory
|
||||||
|
Mkdir(path string, _ os.FileMode) error
|
||||||
|
// MkdirAll like mkdir -p, but for webdav
|
||||||
|
MkdirAll(path string, _ os.FileMode) error
|
||||||
|
// Rename moves a file from A to B
|
||||||
|
Rename(oldpath, newpath string, overwrite bool) error
|
||||||
|
// Copy copies a file from A to B
|
||||||
|
Copy(oldpath, newpath string, overwrite bool) error
|
||||||
|
// Read reads the contents of a remote file
|
||||||
|
Read(path string) ([]byte, error)
|
||||||
|
// ReadStream reads the stream for a given path
|
||||||
|
ReadStream(path string) (io.ReadCloser, error)
|
||||||
|
// Write writes data to a given path
|
||||||
|
Write(path string, data []byte, _ os.FileMode) error
|
||||||
|
// WriteStream writes a stream
|
||||||
|
WriteStream(path string, stream io.Reader, _ os.FileMode) error
|
||||||
|
}
|
19
vendor/github.com/studio-b12/gowebdav/.gitignore
generated
vendored
Normal file
19
vendor/github.com/studio-b12/gowebdav/.gitignore
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Folders to ignore
|
||||||
|
/src
|
||||||
|
/bin
|
||||||
|
/pkg
|
||||||
|
/gowebdav
|
||||||
|
/.idea
|
||||||
|
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
10
vendor/github.com/studio-b12/gowebdav/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/studio-b12/gowebdav/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- "1.x"
|
||||||
|
|
||||||
|
install:
|
||||||
|
- go get ./...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v --short ./...
|
27
vendor/github.com/studio-b12/gowebdav/LICENSE
generated
vendored
Normal file
27
vendor/github.com/studio-b12/gowebdav/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2014, Studio B12 GmbH
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its contributors
|
||||||
|
may be used to endorse or promote products derived from this software without
|
||||||
|
specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
33
vendor/github.com/studio-b12/gowebdav/Makefile
generated
vendored
Normal file
33
vendor/github.com/studio-b12/gowebdav/Makefile
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
BIN := gowebdav
|
||||||
|
SRC := $(wildcard *.go) cmd/gowebdav/main.go
|
||||||
|
|
||||||
|
all: test cmd
|
||||||
|
|
||||||
|
cmd: ${BIN}
|
||||||
|
|
||||||
|
${BIN}: ${SRC}
|
||||||
|
go build -o $@ ./cmd/gowebdav
|
||||||
|
|
||||||
|
test:
|
||||||
|
go test -v --short ./...
|
||||||
|
|
||||||
|
api:
|
||||||
|
@sed '/^## API$$/,$$d' -i README.md
|
||||||
|
@echo '## API' >> README.md
|
||||||
|
@godoc2md github.com/studio-b12/gowebdav | sed '/^$$/N;/^\n$$/D' |\
|
||||||
|
sed '2d' |\
|
||||||
|
sed 's/\/src\/github.com\/studio-b12\/gowebdav\//https:\/\/github.com\/studio-b12\/gowebdav\/blob\/master\//g' |\
|
||||||
|
sed 's/\/src\/target\//https:\/\/github.com\/studio-b12\/gowebdav\/blob\/master\//g' |\
|
||||||
|
sed 's/^#/##/g' >> README.md
|
||||||
|
|
||||||
|
check:
|
||||||
|
gofmt -w -s $(SRC)
|
||||||
|
@echo
|
||||||
|
gocyclo -over 15 .
|
||||||
|
@echo
|
||||||
|
golint ./...
|
||||||
|
|
||||||
|
clean:
|
||||||
|
@rm -f ${BIN}
|
||||||
|
|
||||||
|
.PHONY: all cmd clean test api check
|
147
vendor/github.com/studio-b12/gowebdav/README.md
generated
vendored
Normal file
147
vendor/github.com/studio-b12/gowebdav/README.md
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# GoWebDAV
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/studio-b12/gowebdav.svg?branch=master)](https://travis-ci.org/studio-b12/gowebdav)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/studio-b12/gowebdav?status.svg)](https://godoc.org/github.com/studio-b12/gowebdav)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/studio-b12/gowebdav)](https://goreportcard.com/report/github.com/studio-b12/gowebdav)
|
||||||
|
|
||||||
|
A golang WebDAV client library.
|
||||||
|
|
||||||
|
## Main features
|
||||||
|
`gowebdav` library allows to perform following actions on the remote WebDAV server:
|
||||||
|
* [create path](#create-path-on-a-webdav-server)
|
||||||
|
* [get files list](#get-files-list)
|
||||||
|
* [download file](#download-file-to-byte-array)
|
||||||
|
* [upload file](#upload-file-from-byte-array)
|
||||||
|
* [get information about specified file/folder](#get-information-about-specified-filefolder)
|
||||||
|
* [move file to another location](#move-file-to-another-location)
|
||||||
|
* [copy file to another location](#copy-file-to-another-location)
|
||||||
|
* [delete file](#delete-file)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
First of all you should create `Client` instance using `NewClient()` function:
|
||||||
|
|
||||||
|
```go
|
||||||
|
root := "https://webdav.mydomain.me"
|
||||||
|
user := "user"
|
||||||
|
password := "password"
|
||||||
|
|
||||||
|
c := gowebdav.NewClient(root, user, password)
|
||||||
|
```
|
||||||
|
|
||||||
|
After you can use this `Client` to perform actions, described below.
|
||||||
|
|
||||||
|
**NOTICE:** we will not check errors in examples, to focus you on the `gowebdav` library's code, but you should do it in your code!
|
||||||
|
|
||||||
|
### Create path on a WebDAV server
|
||||||
|
```go
|
||||||
|
err := c.Mkdir("folder", 0644)
|
||||||
|
```
|
||||||
|
In case you want to create several folders you can use `c.MkdirAll()`:
|
||||||
|
```go
|
||||||
|
err := c.MkdirAll("folder/subfolder/subfolder2", 0644)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get files list
|
||||||
|
```go
|
||||||
|
files, _ := c.ReadDir("folder/subfolder")
|
||||||
|
for _, file := range files {
|
||||||
|
//notice that [file] has os.FileInfo type
|
||||||
|
fmt.Println(file.Name())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Download file to byte array
|
||||||
|
```go
|
||||||
|
webdavFilePath := "folder/subfolder/file.txt"
|
||||||
|
localFilePath := "/tmp/webdav/file.txt"
|
||||||
|
|
||||||
|
bytes, _ := c.Read(webdavFilePath)
|
||||||
|
ioutil.WriteFile(localFilePath, bytes, 0644)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Download file via reader
|
||||||
|
Also you can use `c.ReadStream()` method:
|
||||||
|
```go
|
||||||
|
webdavFilePath := "folder/subfolder/file.txt"
|
||||||
|
localFilePath := "/tmp/webdav/file.txt"
|
||||||
|
|
||||||
|
reader, _ := c.ReadStream(webdavFilePath)
|
||||||
|
|
||||||
|
file, _ := os.Create(localFilePath)
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
io.Copy(file, reader)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload file from byte array
|
||||||
|
```go
|
||||||
|
webdavFilePath := "folder/subfolder/file.txt"
|
||||||
|
localFilePath := "/tmp/webdav/file.txt"
|
||||||
|
|
||||||
|
bytes, _ := ioutil.ReadFile(localFilePath)
|
||||||
|
|
||||||
|
c.Write(webdavFilePath, bytes, 0644)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Upload file via writer
|
||||||
|
```go
|
||||||
|
webdavFilePath := "folder/subfolder/file.txt"
|
||||||
|
localFilePath := "/tmp/webdav/file.txt"
|
||||||
|
|
||||||
|
file, _ := os.Open(localFilePath)
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
c.WriteStream(webdavFilePath, file, 0644)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Get information about specified file/folder
|
||||||
|
```go
|
||||||
|
webdavFilePath := "folder/subfolder/file.txt"
|
||||||
|
|
||||||
|
info := c.Stat(webdavFilePath)
|
||||||
|
//notice that [info] has os.FileInfo type
|
||||||
|
fmt.Println(info)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Move file to another location
|
||||||
|
```go
|
||||||
|
oldPath := "folder/subfolder/file.txt"
|
||||||
|
newPath := "folder/subfolder/moved.txt"
|
||||||
|
isOverwrite := true
|
||||||
|
|
||||||
|
c.Rename(oldPath, newPath, isOverwrite)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Copy file to another location
|
||||||
|
```go
|
||||||
|
oldPath := "folder/subfolder/file.txt"
|
||||||
|
newPath := "folder/subfolder/file-copy.txt"
|
||||||
|
isOverwrite := true
|
||||||
|
|
||||||
|
c.Copy(oldPath, newPath, isOverwrite)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Delete file
|
||||||
|
```go
|
||||||
|
webdavFilePath := "folder/subfolder/file.txt"
|
||||||
|
|
||||||
|
c.Remove(webdavFilePath)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
More details about WebDAV server you can read from following resources:
|
||||||
|
|
||||||
|
* [RFC 4918 - HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)](https://tools.ietf.org/html/rfc4918)
|
||||||
|
* [RFC 5689 - Extended MKCOL for Web Distributed Authoring and Versioning (WebDAV)](https://tools.ietf.org/html/rfc5689)
|
||||||
|
* [RFC 2616 - HTTP/1.1 Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html "HTTP/1.1 Status Code Definitions")
|
||||||
|
* [WebDav: Next Generation Collaborative Web Authoring By Lisa Dusseaul](https://books.google.de/books?isbn=0130652083 "WebDav: Next Generation Collaborative Web Authoring By Lisa Dusseault")
|
||||||
|
|
||||||
|
**NOTICE**: RFC 2518 is obsoleted by RFC 4918 in June 2007
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
All contributing are welcome. If you have any suggestions or find some bug - please create an Issue to let us make this project better. We appreciate your help!
|
||||||
|
|
||||||
|
## License
|
||||||
|
This library is distributed under the BSD 3-Clause license found in the [LICENSE](https://github.com/studio-b12/gowebdav/blob/master/LICENSE) file.
|
33
vendor/github.com/studio-b12/gowebdav/basicAuth.go
generated
vendored
Normal file
33
vendor/github.com/studio-b12/gowebdav/basicAuth.go
generated
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BasicAuth structure holds our credentials
|
||||||
|
type BasicAuth struct {
|
||||||
|
user string
|
||||||
|
pw string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type identifies the BasicAuthenticator
|
||||||
|
func (b *BasicAuth) Type() string {
|
||||||
|
return "BasicAuth"
|
||||||
|
}
|
||||||
|
|
||||||
|
// User holds the BasicAuth username
|
||||||
|
func (b *BasicAuth) User() string {
|
||||||
|
return b.user
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass holds the BasicAuth password
|
||||||
|
func (b *BasicAuth) Pass() string {
|
||||||
|
return b.pw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorize the current request
|
||||||
|
func (b *BasicAuth) Authorize(c *Client, method string, path string) {
|
||||||
|
a := b.user + ":" + b.pw
|
||||||
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(a))
|
||||||
|
c.headers.Set("Authorization", auth)
|
||||||
|
}
|
380
vendor/github.com/studio-b12/gowebdav/client.go
generated
vendored
Normal file
380
vendor/github.com/studio-b12/gowebdav/client.go
generated
vendored
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
pathpkg "path"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client defines our structure
|
||||||
|
type Client struct {
|
||||||
|
root string
|
||||||
|
headers http.Header
|
||||||
|
c *http.Client
|
||||||
|
auth Authenticator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticator stub
|
||||||
|
type Authenticator interface {
|
||||||
|
Type() string
|
||||||
|
User() string
|
||||||
|
Pass() string
|
||||||
|
Authorize(*Client, string, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoAuth structure holds our credentials
|
||||||
|
type NoAuth struct {
|
||||||
|
user string
|
||||||
|
pw string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type identifies the authenticator
|
||||||
|
func (n *NoAuth) Type() string {
|
||||||
|
return "NoAuth"
|
||||||
|
}
|
||||||
|
|
||||||
|
// User returns the current user
|
||||||
|
func (n *NoAuth) User() string {
|
||||||
|
return n.user
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass returns the current password
|
||||||
|
func (n *NoAuth) Pass() string {
|
||||||
|
return n.pw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorize the current request
|
||||||
|
func (n *NoAuth) Authorize(c *Client, method string, path string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new instance of client
|
||||||
|
func NewClient(uri, user, pw string) *Client {
|
||||||
|
return &Client{FixSlash(uri), make(http.Header), &http.Client{}, &NoAuth{user, pw}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetHeader lets us set arbitrary headers for a given client
|
||||||
|
func (c *Client) SetHeader(key, value string) {
|
||||||
|
c.headers.Add(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTimeout exposes the ability to set a time limit for requests
|
||||||
|
func (c *Client) SetTimeout(timeout time.Duration) {
|
||||||
|
c.c.Timeout = timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTransport exposes the ability to define custom transports
|
||||||
|
func (c *Client) SetTransport(transport http.RoundTripper) {
|
||||||
|
c.c.Transport = transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect connects to our dav server
|
||||||
|
func (c *Client) Connect() error {
|
||||||
|
rs, err := c.options("/")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = rs.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.StatusCode != 200 {
|
||||||
|
return newPathError("Connect", c.root, rs.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type props struct {
|
||||||
|
Status string `xml:"DAV: status"`
|
||||||
|
Name string `xml:"DAV: prop>displayname,omitempty"`
|
||||||
|
Type xml.Name `xml:"DAV: prop>resourcetype>collection,omitempty"`
|
||||||
|
Size string `xml:"DAV: prop>getcontentlength,omitempty"`
|
||||||
|
ContentType string `xml:"DAV: prop>getcontenttype,omitempty"`
|
||||||
|
ETag string `xml:"DAV: prop>getetag,omitempty"`
|
||||||
|
Modified string `xml:"DAV: prop>getlastmodified,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type response struct {
|
||||||
|
Href string `xml:"DAV: href"`
|
||||||
|
Props []props `xml:"DAV: propstat"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProps(r *response, status string) *props {
|
||||||
|
for _, prop := range r.Props {
|
||||||
|
if strings.Contains(prop.Status, status) {
|
||||||
|
return &prop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadDir reads the contents of a remote directory
|
||||||
|
func (c *Client) ReadDir(path string) ([]os.FileInfo, error) {
|
||||||
|
path = FixSlashes(path)
|
||||||
|
files := make([]os.FileInfo, 0)
|
||||||
|
skipSelf := true
|
||||||
|
parse := func(resp interface{}) error {
|
||||||
|
r := resp.(*response)
|
||||||
|
|
||||||
|
if skipSelf {
|
||||||
|
skipSelf = false
|
||||||
|
if p := getProps(r, "200"); p != nil && p.Type.Local == "collection" {
|
||||||
|
r.Props = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return newPathError("ReadDir", path, 405)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p := getProps(r, "200"); p != nil {
|
||||||
|
f := new(File)
|
||||||
|
if ps, err := url.QueryUnescape(r.Href); err == nil {
|
||||||
|
f.name = pathpkg.Base(ps)
|
||||||
|
} else {
|
||||||
|
f.name = p.Name
|
||||||
|
}
|
||||||
|
f.path = path + f.name
|
||||||
|
f.modified = parseModified(&p.Modified)
|
||||||
|
f.etag = p.ETag
|
||||||
|
f.contentType = p.ContentType
|
||||||
|
|
||||||
|
if p.Type.Local == "collection" {
|
||||||
|
f.path += "/"
|
||||||
|
f.size = 0
|
||||||
|
f.isdir = true
|
||||||
|
} else {
|
||||||
|
f.size = parseInt64(&p.Size)
|
||||||
|
f.isdir = false
|
||||||
|
}
|
||||||
|
|
||||||
|
files = append(files, *f)
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Props = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.propfind(path, false,
|
||||||
|
`<d:propfind xmlns:d='DAV:'>
|
||||||
|
<d:prop>
|
||||||
|
<d:displayname/>
|
||||||
|
<d:resourcetype/>
|
||||||
|
<d:getcontentlength/>
|
||||||
|
<d:getcontenttype/>
|
||||||
|
<d:getetag/>
|
||||||
|
<d:getlastmodified/>
|
||||||
|
</d:prop>
|
||||||
|
</d:propfind>`,
|
||||||
|
&response{},
|
||||||
|
parse)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*os.PathError); !ok {
|
||||||
|
err = newPathErrorErr("ReadDir", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return files, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stat returns the file stats for a specified path
|
||||||
|
func (c *Client) Stat(path string) (os.FileInfo, error) {
|
||||||
|
var f *File
|
||||||
|
parse := func(resp interface{}) error {
|
||||||
|
r := resp.(*response)
|
||||||
|
if p := getProps(r, "200"); p != nil && f == nil {
|
||||||
|
f = new(File)
|
||||||
|
f.name = p.Name
|
||||||
|
f.path = path
|
||||||
|
f.etag = p.ETag
|
||||||
|
f.contentType = p.ContentType
|
||||||
|
|
||||||
|
if p.Type.Local == "collection" {
|
||||||
|
if !strings.HasSuffix(f.path, "/") {
|
||||||
|
f.path += "/"
|
||||||
|
}
|
||||||
|
f.size = 0
|
||||||
|
f.modified = time.Unix(0, 0)
|
||||||
|
f.isdir = true
|
||||||
|
} else {
|
||||||
|
f.size = parseInt64(&p.Size)
|
||||||
|
f.modified = parseModified(&p.Modified)
|
||||||
|
f.isdir = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Props = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.propfind(path, true,
|
||||||
|
`<d:propfind xmlns:d='DAV:'>
|
||||||
|
<d:prop>
|
||||||
|
<d:displayname/>
|
||||||
|
<d:resourcetype/>
|
||||||
|
<d:getcontentlength/>
|
||||||
|
<d:getcontenttype/>
|
||||||
|
<d:getetag/>
|
||||||
|
<d:getlastmodified/>
|
||||||
|
</d:prop>
|
||||||
|
</d:propfind>`,
|
||||||
|
&response{},
|
||||||
|
parse)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*os.PathError); !ok {
|
||||||
|
err = newPathErrorErr("ReadDir", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove removes a remote file
|
||||||
|
func (c *Client) Remove(path string) error {
|
||||||
|
return c.RemoveAll(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveAll removes remote files
|
||||||
|
func (c *Client) RemoveAll(path string) error {
|
||||||
|
rs, err := c.req("DELETE", path, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return newPathError("Remove", path, 400)
|
||||||
|
}
|
||||||
|
err = rs.Body.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.StatusCode == 200 || rs.StatusCode == 204 || rs.StatusCode == 404 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPathError("Remove", path, rs.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mkdir makes a directory
|
||||||
|
func (c *Client) Mkdir(path string, _ os.FileMode) error {
|
||||||
|
path = FixSlashes(path)
|
||||||
|
status := c.mkcol(path)
|
||||||
|
if status == 201 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPathError("Mkdir", path, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MkdirAll like mkdir -p, but for webdav
|
||||||
|
func (c *Client) MkdirAll(path string, _ os.FileMode) error {
|
||||||
|
path = FixSlashes(path)
|
||||||
|
status := c.mkcol(path)
|
||||||
|
if status == 201 {
|
||||||
|
return nil
|
||||||
|
} else if status == 409 {
|
||||||
|
paths := strings.Split(path, "/")
|
||||||
|
sub := "/"
|
||||||
|
for _, e := range paths {
|
||||||
|
if e == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
sub += e + "/"
|
||||||
|
status = c.mkcol(sub)
|
||||||
|
if status != 201 {
|
||||||
|
return newPathError("MkdirAll", sub, status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPathError("MkdirAll", path, status)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rename moves a file from A to B
|
||||||
|
func (c *Client) Rename(oldpath, newpath string, overwrite bool) error {
|
||||||
|
return c.copymove("MOVE", oldpath, newpath, overwrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy copies a file from A to B
|
||||||
|
func (c *Client) Copy(oldpath, newpath string, overwrite bool) error {
|
||||||
|
return c.copymove("COPY", oldpath, newpath, overwrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads the contents of a remote file
|
||||||
|
func (c *Client) Read(path string) ([]byte, error) {
|
||||||
|
var stream io.ReadCloser
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if stream, err = c.ReadStream(path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer stream.Close()
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
_, err = buf.ReadFrom(stream)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadStream reads the stream for a given path
|
||||||
|
func (c *Client) ReadStream(path string) (io.ReadCloser, error) {
|
||||||
|
rs, err := c.req("GET", path, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, newPathErrorErr("ReadStream", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.StatusCode == 200 {
|
||||||
|
return rs.Body, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rs.Body.Close()
|
||||||
|
return nil, newPathError("ReadStream", path, rs.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes data to a given path
|
||||||
|
func (c *Client) Write(path string, data []byte, _ os.FileMode) error {
|
||||||
|
s := c.put(path, bytes.NewReader(data))
|
||||||
|
switch s {
|
||||||
|
|
||||||
|
case 200, 201, 204:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case 409:
|
||||||
|
err := c.createParentCollection(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s = c.put(path, bytes.NewReader(data))
|
||||||
|
if s == 200 || s == 201 || s == 204 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPathError("Write", path, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteStream writes a stream
|
||||||
|
func (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) error {
|
||||||
|
|
||||||
|
err := c.createParentCollection(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := c.put(path, stream)
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case 200, 201, 204:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return newPathError("WriteStream", path, s)
|
||||||
|
}
|
||||||
|
}
|
146
vendor/github.com/studio-b12/gowebdav/digestAuth.go
generated
vendored
Normal file
146
vendor/github.com/studio-b12/gowebdav/digestAuth.go
generated
vendored
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/hex"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DigestAuth structure holds our credentials
|
||||||
|
type DigestAuth struct {
|
||||||
|
user string
|
||||||
|
pw string
|
||||||
|
digestParts map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type identifies the DigestAuthenticator
|
||||||
|
func (d *DigestAuth) Type() string {
|
||||||
|
return "DigestAuth"
|
||||||
|
}
|
||||||
|
|
||||||
|
// User holds the DigestAuth username
|
||||||
|
func (d *DigestAuth) User() string {
|
||||||
|
return d.user
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass holds the DigestAuth password
|
||||||
|
func (d *DigestAuth) Pass() string {
|
||||||
|
return d.pw
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorize the current request
|
||||||
|
func (d *DigestAuth) Authorize(c *Client, method string, path string) {
|
||||||
|
d.digestParts["uri"] = path
|
||||||
|
d.digestParts["method"] = method
|
||||||
|
d.digestParts["username"] = d.user
|
||||||
|
d.digestParts["password"] = d.pw
|
||||||
|
c.headers.Set("Authorization", getDigestAuthorization(d.digestParts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func digestParts(resp *http.Response) map[string]string {
|
||||||
|
result := map[string]string{}
|
||||||
|
if len(resp.Header["Www-Authenticate"]) > 0 {
|
||||||
|
wantedHeaders := []string{"nonce", "realm", "qop", "opaque", "algorithm", "entityBody"}
|
||||||
|
responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",")
|
||||||
|
for _, r := range responseHeaders {
|
||||||
|
for _, w := range wantedHeaders {
|
||||||
|
if strings.Contains(r, w) {
|
||||||
|
result[w] = strings.Trim(
|
||||||
|
strings.SplitN(r, `=`, 2)[1],
|
||||||
|
`"`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMD5(text string) string {
|
||||||
|
hasher := md5.New()
|
||||||
|
hasher.Write([]byte(text))
|
||||||
|
return hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCnonce() string {
|
||||||
|
b := make([]byte, 8)
|
||||||
|
io.ReadFull(rand.Reader, b)
|
||||||
|
return fmt.Sprintf("%x", b)[:16]
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDigestAuthorization(digestParts map[string]string) string {
|
||||||
|
d := digestParts
|
||||||
|
// These are the correct ha1 and ha2 for qop=auth. We should probably check for other types of qop.
|
||||||
|
|
||||||
|
var (
|
||||||
|
ha1 string
|
||||||
|
ha2 string
|
||||||
|
nonceCount = 00000001
|
||||||
|
cnonce = getCnonce()
|
||||||
|
response string
|
||||||
|
)
|
||||||
|
|
||||||
|
// 'ha1' value depends on value of "algorithm" field
|
||||||
|
switch d["algorithm"] {
|
||||||
|
case "MD5", "":
|
||||||
|
ha1 = getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"])
|
||||||
|
case "MD5-sess":
|
||||||
|
ha1 = getMD5(
|
||||||
|
fmt.Sprintf("%s:%v:%s",
|
||||||
|
getMD5(d["username"]+":"+d["realm"]+":"+d["password"]),
|
||||||
|
nonceCount,
|
||||||
|
cnonce,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'ha2' value depends on value of "qop" field
|
||||||
|
switch d["qop"] {
|
||||||
|
case "auth", "":
|
||||||
|
ha2 = getMD5(d["method"] + ":" + d["uri"])
|
||||||
|
case "auth-int":
|
||||||
|
if d["entityBody"] != "" {
|
||||||
|
ha2 = getMD5(d["method"] + ":" + d["uri"] + ":" + getMD5(d["entityBody"]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'response' value depends on value of "qop" field
|
||||||
|
switch d["qop"] {
|
||||||
|
case "":
|
||||||
|
response = getMD5(
|
||||||
|
fmt.Sprintf("%s:%s:%s",
|
||||||
|
ha1,
|
||||||
|
d["nonce"],
|
||||||
|
ha2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
case "auth", "auth-int":
|
||||||
|
response = getMD5(
|
||||||
|
fmt.Sprintf("%s:%s:%v:%s:%s:%s",
|
||||||
|
ha1,
|
||||||
|
d["nonce"],
|
||||||
|
nonceCount,
|
||||||
|
cnonce,
|
||||||
|
d["qop"],
|
||||||
|
ha2,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", nc=%v, cnonce="%s", response="%s"`,
|
||||||
|
d["username"], d["realm"], d["nonce"], d["uri"], nonceCount, cnonce, response)
|
||||||
|
|
||||||
|
if d["qop"] != "" {
|
||||||
|
authorization += fmt.Sprintf(`, qop=%s`, d["qop"])
|
||||||
|
}
|
||||||
|
|
||||||
|
if d["opaque"] != "" {
|
||||||
|
authorization += fmt.Sprintf(`, opaque="%s"`, d["opaque"])
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorization
|
||||||
|
}
|
3
vendor/github.com/studio-b12/gowebdav/doc.go
generated
vendored
Normal file
3
vendor/github.com/studio-b12/gowebdav/doc.go
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
// Package gowebdav is a WebDAV client library with a command line tool
|
||||||
|
// included.
|
||||||
|
package gowebdav
|
72
vendor/github.com/studio-b12/gowebdav/file.go
generated
vendored
Normal file
72
vendor/github.com/studio-b12/gowebdav/file.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// File is our structure for a given file
|
||||||
|
type File struct {
|
||||||
|
path string
|
||||||
|
name string
|
||||||
|
contentType string
|
||||||
|
size int64
|
||||||
|
modified time.Time
|
||||||
|
etag string
|
||||||
|
isdir bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the name of a file
|
||||||
|
func (f File) Name() string {
|
||||||
|
return f.name
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContentType returns the content type of a file
|
||||||
|
func (f File) ContentType() string {
|
||||||
|
return f.contentType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the size of a file
|
||||||
|
func (f File) Size() int64 {
|
||||||
|
return f.size
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mode will return the mode of a given file
|
||||||
|
func (f File) Mode() os.FileMode {
|
||||||
|
// TODO check webdav perms
|
||||||
|
if f.isdir {
|
||||||
|
return 0775 | os.ModeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0664
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModTime returns the modified time of a file
|
||||||
|
func (f File) ModTime() time.Time {
|
||||||
|
return f.modified
|
||||||
|
}
|
||||||
|
|
||||||
|
// ETag returns the ETag of a file
|
||||||
|
func (f File) ETag() string {
|
||||||
|
return f.etag
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDir let us see if a given file is a directory or not
|
||||||
|
func (f File) IsDir() bool {
|
||||||
|
return f.isdir
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sys ????
|
||||||
|
func (f File) Sys() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String lets us see file information
|
||||||
|
func (f File) String() string {
|
||||||
|
if f.isdir {
|
||||||
|
return fmt.Sprintf("Dir : '%s' - '%s'", f.path, f.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("File: '%s' SIZE: %d MODIFIED: %s ETAG: %s CTYPE: %s", f.path, f.size, f.modified.String(), f.etag, f.contentType)
|
||||||
|
}
|
54
vendor/github.com/studio-b12/gowebdav/netrc.go
generated
vendored
Normal file
54
vendor/github.com/studio-b12/gowebdav/netrc.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseLine(s string) (login, pass string) {
|
||||||
|
fields := strings.Fields(s)
|
||||||
|
for i, f := range fields {
|
||||||
|
if f == "login" {
|
||||||
|
login = fields[i+1]
|
||||||
|
}
|
||||||
|
if f == "password" {
|
||||||
|
pass = fields[i+1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return login, pass
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadConfig reads login and password configuration from ~/.netrc
|
||||||
|
// machine foo.com login username password 123456
|
||||||
|
func ReadConfig(uri, netrc string) (string, string) {
|
||||||
|
u, err := url.Parse(uri)
|
||||||
|
if err != nil {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Open(netrc)
|
||||||
|
if err != nil {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
re := fmt.Sprintf(`^.*machine %s.*$`, u.Host)
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
s := scanner.Text()
|
||||||
|
|
||||||
|
matched, err := regexp.MatchString(re, s)
|
||||||
|
if err != nil {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
if matched {
|
||||||
|
return parseLine(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", ""
|
||||||
|
}
|
164
vendor/github.com/studio-b12/gowebdav/requests.go
generated
vendored
Normal file
164
vendor/github.com/studio-b12/gowebdav/requests.go
generated
vendored
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (req *http.Response, err error) {
|
||||||
|
// Tee the body, because if authorization fails we will need to read from it again.
|
||||||
|
var r *http.Request
|
||||||
|
var ba bytes.Buffer
|
||||||
|
bb := io.TeeReader(body, &ba)
|
||||||
|
|
||||||
|
if body == nil {
|
||||||
|
r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), nil)
|
||||||
|
} else {
|
||||||
|
r, err = http.NewRequest(method, PathEscape(Join(c.root, path)), bb)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
c.auth.Authorize(c, method, path)
|
||||||
|
|
||||||
|
for k, vals := range c.headers {
|
||||||
|
for _, v := range vals {
|
||||||
|
r.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if intercept != nil {
|
||||||
|
intercept(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
rs, err := c.c.Do(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.StatusCode == 401 && c.auth.Type() == "NoAuth" {
|
||||||
|
if strings.Index(rs.Header.Get("Www-Authenticate"), "Digest") > -1 {
|
||||||
|
c.auth = &DigestAuth{c.auth.User(), c.auth.Pass(), digestParts(rs)}
|
||||||
|
} else if strings.Index(rs.Header.Get("Www-Authenticate"), "Basic") > -1 {
|
||||||
|
c.auth = &BasicAuth{c.auth.User(), c.auth.Pass()}
|
||||||
|
} else {
|
||||||
|
return rs, newPathError("Authorize", c.root, rs.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if body == nil {
|
||||||
|
return c.req(method, path, nil, intercept)
|
||||||
|
} else {
|
||||||
|
return c.req(method, path, &ba, intercept)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if rs.StatusCode == 401 {
|
||||||
|
return rs, newPathError("Authorize", c.root, rs.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) mkcol(path string) int {
|
||||||
|
rs, err := c.req("MKCOL", path, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 400
|
||||||
|
}
|
||||||
|
defer rs.Body.Close()
|
||||||
|
|
||||||
|
if rs.StatusCode == 201 || rs.StatusCode == 405 {
|
||||||
|
return 201
|
||||||
|
}
|
||||||
|
|
||||||
|
return rs.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) options(path string) (*http.Response, error) {
|
||||||
|
return c.req("OPTIONS", path, nil, func(rq *http.Request) {
|
||||||
|
rq.Header.Add("Depth", "0")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) propfind(path string, self bool, body string, resp interface{}, parse func(resp interface{}) error) error {
|
||||||
|
rs, err := c.req("PROPFIND", path, strings.NewReader(body), func(rq *http.Request) {
|
||||||
|
if self {
|
||||||
|
rq.Header.Add("Depth", "0")
|
||||||
|
} else {
|
||||||
|
rq.Header.Add("Depth", "1")
|
||||||
|
}
|
||||||
|
rq.Header.Add("Content-Type", "application/xml;charset=UTF-8")
|
||||||
|
rq.Header.Add("Accept", "application/xml,text/xml")
|
||||||
|
rq.Header.Add("Accept-Charset", "utf-8")
|
||||||
|
// TODO add support for 'gzip,deflate;q=0.8,q=0.7'
|
||||||
|
rq.Header.Add("Accept-Encoding", "")
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rs.Body.Close()
|
||||||
|
|
||||||
|
if rs.StatusCode != 207 {
|
||||||
|
return fmt.Errorf("%s - %s %s", rs.Status, "PROPFIND", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseXML(rs.Body, resp, parse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) doCopyMove(method string, oldpath string, newpath string, overwrite bool) (int, io.ReadCloser) {
|
||||||
|
rs, err := c.req(method, oldpath, nil, func(rq *http.Request) {
|
||||||
|
rq.Header.Add("Destination", Join(c.root, newpath))
|
||||||
|
if overwrite {
|
||||||
|
rq.Header.Add("Overwrite", "T")
|
||||||
|
} else {
|
||||||
|
rq.Header.Add("Overwrite", "F")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 400, nil
|
||||||
|
}
|
||||||
|
return rs.StatusCode, rs.Body
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) copymove(method string, oldpath string, newpath string, overwrite bool) error {
|
||||||
|
s, data := c.doCopyMove(method, oldpath, newpath, overwrite)
|
||||||
|
defer data.Close()
|
||||||
|
|
||||||
|
switch s {
|
||||||
|
case 201, 204:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
case 207:
|
||||||
|
// TODO handle multistat errors, worst case ...
|
||||||
|
log(fmt.Sprintf(" TODO handle %s - %s multistatus result %s", method, oldpath, String(data)))
|
||||||
|
|
||||||
|
case 409:
|
||||||
|
err := c.createParentCollection(newpath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.copymove(method, oldpath, newpath, overwrite)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPathError(method, oldpath, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) put(path string, stream io.Reader) int {
|
||||||
|
rs, err := c.req("PUT", path, stream, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 400
|
||||||
|
}
|
||||||
|
defer rs.Body.Close()
|
||||||
|
|
||||||
|
return rs.StatusCode
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) createParentCollection(itemPath string) (err error) {
|
||||||
|
parentPath := path.Dir(itemPath)
|
||||||
|
return c.MkdirAll(parentPath, 0755)
|
||||||
|
}
|
109
vendor/github.com/studio-b12/gowebdav/utils.go
generated
vendored
Normal file
109
vendor/github.com/studio-b12/gowebdav/utils.go
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func log(msg interface{}) {
|
||||||
|
fmt.Println(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPathError(op string, path string, statusCode int) error {
|
||||||
|
return &os.PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: fmt.Errorf("%d", statusCode),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPathErrorErr(op string, path string, err error) error {
|
||||||
|
return &os.PathError{
|
||||||
|
Op: op,
|
||||||
|
Path: path,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PathEscape escapes all segemnts of a given path
|
||||||
|
func PathEscape(path string) string {
|
||||||
|
s := strings.Split(path, "/")
|
||||||
|
for i, e := range s {
|
||||||
|
s[i] = url.PathEscape(e)
|
||||||
|
}
|
||||||
|
return strings.Join(s, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixSlash appends a trailing / to our string
|
||||||
|
func FixSlash(s string) string {
|
||||||
|
if !strings.HasSuffix(s, "/") {
|
||||||
|
s += "/"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixSlashes appends and prepends a / if they are missing
|
||||||
|
func FixSlashes(s string) string {
|
||||||
|
if s[0] != '/' {
|
||||||
|
s = "/" + s
|
||||||
|
}
|
||||||
|
return FixSlash(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join joins two paths
|
||||||
|
func Join(path0 string, path1 string) string {
|
||||||
|
return strings.TrimSuffix(path0, "/") + "/" + strings.TrimPrefix(path1, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
// String pulls a string out of our io.Reader
|
||||||
|
func String(r io.Reader) string {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
// TODO - make String return an error as well
|
||||||
|
_, _ = buf.ReadFrom(r)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseUint(s *string) uint {
|
||||||
|
if n, e := strconv.ParseUint(*s, 10, 32); e == nil {
|
||||||
|
return uint(n)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt64(s *string) int64 {
|
||||||
|
if n, e := strconv.ParseInt(*s, 10, 64); e == nil {
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseModified(s *string) time.Time {
|
||||||
|
if t, e := time.Parse(time.RFC1123, *s); e == nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return time.Unix(0, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseXML(data io.Reader, resp interface{}, parse func(resp interface{}) error) error {
|
||||||
|
decoder := xml.NewDecoder(data)
|
||||||
|
for t, _ := decoder.Token(); t != nil; t, _ = decoder.Token() {
|
||||||
|
switch se := t.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
if se.Name.Local == "response" {
|
||||||
|
if e := decoder.DecodeElement(resp, &se); e == nil {
|
||||||
|
if err := parse(resp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
2
vendor/modules.txt
vendored
2
vendor/modules.txt
vendored
@ -19,6 +19,8 @@ github.com/stretchr/objx
|
|||||||
# github.com/stretchr/testify v1.2.2
|
# github.com/stretchr/testify v1.2.2
|
||||||
github.com/stretchr/testify/mock
|
github.com/stretchr/testify/mock
|
||||||
github.com/stretchr/testify/assert
|
github.com/stretchr/testify/assert
|
||||||
|
# github.com/studio-b12/gowebdav v0.0.0-20190103184047-38f79aeaf1ac
|
||||||
|
github.com/studio-b12/gowebdav
|
||||||
# golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76
|
# golang.org/x/net v0.0.0-20181129055619-fae4c4e3ad76
|
||||||
golang.org/x/net/publicsuffix
|
golang.org/x/net/publicsuffix
|
||||||
# golang.org/x/sys v0.0.0-20190422165155-953cdadca894
|
# golang.org/x/sys v0.0.0-20190422165155-953cdadca894
|
||||||
|
Loading…
Reference in New Issue
Block a user