add initial source
This commit is contained in:
parent
a224ac9c70
commit
95f1f68142
104
client.go
Normal file
104
client.go
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
root string
|
||||||
|
headers http.Header
|
||||||
|
c *http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(uri string, user string, pw string) *Client {
|
||||||
|
c := &Client{uri, make(http.Header), &http.Client{}}
|
||||||
|
|
||||||
|
if len(user) > 0 && len(pw) > 0 {
|
||||||
|
a := user + ":" + pw
|
||||||
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(a))
|
||||||
|
c.headers.Add("Authorization", auth)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(c.root, "/") {
|
||||||
|
c.root += "/"
|
||||||
|
}
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Connect() error {
|
||||||
|
if rs, err := c.Options("/"); err == nil {
|
||||||
|
defer rs.Body.Close()
|
||||||
|
|
||||||
|
if rs.StatusCode != 200 || (rs.Header.Get("Dav") == "" && rs.Header.Get("DAV") == "") {
|
||||||
|
return errors.New(fmt.Sprintf("Bad Request: %d - %s", rs.StatusCode, c.root))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO check PROPFIND if path is collection
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
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.Index(prop.Status, status) != -1 {
|
||||||
|
return &prop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) List(path string) (*[]*File, error) {
|
||||||
|
files := make([]*File, 0)
|
||||||
|
parse := func(resp interface{}) {
|
||||||
|
r := resp.(*response)
|
||||||
|
if p := getProps(r, "200"); p != nil {
|
||||||
|
var f File
|
||||||
|
if p.Type.Local == "collection" {
|
||||||
|
f = directory{p.Name}
|
||||||
|
} else {
|
||||||
|
f = file{p.Name, parseUint(&p.Size), parseModified(&p.Modified)}
|
||||||
|
}
|
||||||
|
|
||||||
|
files = append(files, &f)
|
||||||
|
r.Props = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.Propfind(path, false,
|
||||||
|
`<d:propfind xmlns:d='DAV:'>
|
||||||
|
<d:prop>
|
||||||
|
<d:displayname/>
|
||||||
|
<d:resourcetype/>
|
||||||
|
<d:getcontentlength/>
|
||||||
|
<d:getlastmodified/>
|
||||||
|
</d:prop>
|
||||||
|
</d:propfind>`,
|
||||||
|
&response{},
|
||||||
|
parse)
|
||||||
|
return &files, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Read(path string) {
|
||||||
|
fmt.Println("Read " + path)
|
||||||
|
}
|
32
directory.go
Normal file
32
directory.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Directory interface {
|
||||||
|
}
|
||||||
|
|
||||||
|
type directory struct {
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d directory) Name() string {
|
||||||
|
return d.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ directory) Size() uint {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ directory) IsDirectory() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ directory) Modified() time.Time {
|
||||||
|
return time.Unix(0, 9)
|
||||||
|
}
|
||||||
|
func (d directory) String() string {
|
||||||
|
return fmt.Sprintf("DIRECTORY: %s", d.name)
|
||||||
|
}
|
40
file.go
Normal file
40
file.go
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type File interface {
|
||||||
|
Name() string
|
||||||
|
Size() uint
|
||||||
|
Modified() time.Time
|
||||||
|
IsDirectory() bool
|
||||||
|
String() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type file struct {
|
||||||
|
name string
|
||||||
|
size uint
|
||||||
|
modified time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_ file) IsDirectory() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f file) Modified() time.Time {
|
||||||
|
return f.modified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f file) Name() string {
|
||||||
|
return f.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f file) Size() uint {
|
||||||
|
return f.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f file) String() string {
|
||||||
|
return fmt.Sprintf("FILE: %s SIZE: %d MODIFIED: %s", f.name, f.size, f.modified.String())
|
||||||
|
}
|
56
main/client.go
Normal file
56
main/client.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
d "gowebdav"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Fail(err interface{}) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
} else {
|
||||||
|
fmt.Println("Usage: client FLAGS ARGS")
|
||||||
|
fmt.Println("Flags:")
|
||||||
|
flag.PrintDefaults()
|
||||||
|
}
|
||||||
|
os.Exit(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
root := flag.String("root", "URL", "WebDAV Endpoint")
|
||||||
|
usr := flag.String("user", "", "user")
|
||||||
|
pw := flag.String("pw", "", "password")
|
||||||
|
m := flag.String("X", "GET", "Method: LIST aka PROPFIND, GET")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if *root == "URL" {
|
||||||
|
Fail(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := d.NewClient(*root, *usr, *pw)
|
||||||
|
if err := c.Connect(); err != nil {
|
||||||
|
Fail(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(flag.Args()) > 0 {
|
||||||
|
path := flag.Args()[0]
|
||||||
|
switch *m {
|
||||||
|
case "LIST", "PROPFIND":
|
||||||
|
if files, err := c.List(path); err == nil {
|
||||||
|
fmt.Println(len(*files))
|
||||||
|
for _, f := range *files {
|
||||||
|
fmt.Println(*f)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
case "GET": c.Read(path)
|
||||||
|
default: Fail(nil)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Fail(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
75
requests.go
Normal file
75
requests.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) req(method string, path string, body io.Reader) (req *http.Request, err error) {
|
||||||
|
req, err = http.NewRequest(method, Join(c.root, path), body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for k, vals := range c.headers {
|
||||||
|
for _, v := range vals {
|
||||||
|
req.Header.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return req, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Options(path string) (*http.Response, error) {
|
||||||
|
rq, err := c.req("OPTIONS", path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rq.Header.Add("Depth", "0")
|
||||||
|
|
||||||
|
return c.c.Do(rq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Propfind(path string, self bool, body string, resp interface{}, parse func(resp interface{})) error {
|
||||||
|
rq, err := c.req("PROPFIND", path, strings.NewReader(body))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if self {
|
||||||
|
rq.Header.Add("Depth", "0")
|
||||||
|
} else {
|
||||||
|
rq.Header.Add("Depth", "1")
|
||||||
|
}
|
||||||
|
rq.Header.Add("Content-Type", "text/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", "")
|
||||||
|
|
||||||
|
rs, err := c.c.Do(rq)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rs.Body.Close()
|
||||||
|
|
||||||
|
if rs.StatusCode != 207 {
|
||||||
|
return errors.New(fmt.Sprintf("%s - %s %s", rs.Status, rq.Method, rq.URL.String()))
|
||||||
|
}
|
||||||
|
|
||||||
|
decoder := xml.NewDecoder(rs.Body)
|
||||||
|
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 {
|
||||||
|
parse(resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
33
utils.go
Normal file
33
utils.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package gowebdav
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Join(path0 string, path1 string) string {
|
||||||
|
return strings.TrimSuffix(path0, "/") + "/" + strings.TrimPrefix(path1, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func String(r io.Reader) string {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
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 parseModified(s *string) time.Time {
|
||||||
|
if t, e := time.Parse(time.RFC1123, *s); e == nil {
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return time.Unix(0, 0)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user