diff --git a/client.go b/client.go new file mode 100644 index 0000000..8f21fad --- /dev/null +++ b/client.go @@ -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, + ` + + + + + + + `, + &response{}, + parse) + return &files, err +} + +func (c *Client) Read(path string) { + fmt.Println("Read " + path) +} diff --git a/directory.go b/directory.go new file mode 100644 index 0000000..b198bbf --- /dev/null +++ b/directory.go @@ -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) +} diff --git a/file.go b/file.go new file mode 100644 index 0000000..a37079c --- /dev/null +++ b/file.go @@ -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()) +} diff --git a/main/client.go b/main/client.go new file mode 100644 index 0000000..728aa13 --- /dev/null +++ b/main/client.go @@ -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) + } +} + diff --git a/requests.go b/requests.go new file mode 100644 index 0000000..e36fe87 --- /dev/null +++ b/requests.go @@ -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 +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..f43bd0f --- /dev/null +++ b/utils.go @@ -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) +}