Code Monkey home page Code Monkey logo

go-webdav's Introduction

go-webdav's People

Contributors

almogbaku avatar apehaenger avatar bitfehler avatar cheif avatar chuhlomin avatar deepdiver1975 avatar dmitshur avatar emersion avatar krystiancha avatar miterion avatar myml avatar mzjulian avatar proletarius101 avatar sbinet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

go-webdav's Issues

contacts.icloud.com uses multiple hosts

FindAddressBookHomeSet returns only the path of the URL returned from the PROPFIND request. However, iCloud returns a different host name as well as the path. This is then discarded. Trying to retrieve any address objects will then fail with an error as the wrong host is being queried.

Looking at other carddav servers this is not normal behaviour but there doesn't seem to be anything in RFC 6352 that disallows it.

Library rewrite

The current library design makes it very difficult to properly support WebDAV extensions such as CardDAV and CalDAV. It's also completely missing a client implementation.

After reviewing prior art, in the last few days I've began rewriting the library from the ground up in the next branch. I have a minimal CardDAV client and WebDAV server working.

My plan is to release v0.1 with the current code so that existing users don't break, then release 0.2 with the rewrite. I'll start closing old issues once 0.1 is released.


  • WebDAV filesystem
    • Server
      • OPTIONS
      • GET, HEAD
      • PROPFIND
      • PUT
      • DELETE
      • PROPPATCH
      • MKCOL
      • COPY
      • MOVE
    • Client
      • PROPFIND
      • GET
      • PUT
      • DELETE
      • PROPPATCH
      • MKCOL
      • COPY
      • MOVE
  • CardDAV
    • Server
      • OPTIONS
      • GET, HEAD
      • PROPFIND
      • REPORT
      • PUT
      • DELETE
      • PROPPATCH
      • MKCOL
      • COPY
      • MOVE
    • Client
      • REPORT
      • PROPFIND
      • PUT
      • DELETE
      • PROPPATCH
      • MKCOL
      • COPY
      • MOVE

Design an internal library

For v0.2 I'd like to have:

  • A user-facing webdav library, exposing a filesystem-like API
  • A user-facing carddav library, exposing a CardDAV-friendly API
  • An internal library which can be used from webdav and carddav exposing low-level details (e.g. XML props)

How the internal library should look like is not clear yet, at least for the server part.

encoding/xml issues

  • Namespace handling when mashalling is not great, replicating the namespace in each element
  • It's not very extensible: hard to define elements that would be parsed later on

Maybe we should consider using another library.

Aggregate multiple servers

It may be desirable to have a base WebDAV filesystem, with some CalDAV/CardDAV endpoints. This should work fine except for principals. We need to figure out a way to share principals between servers.

nil check for multiGet pointless?

I'm looking at the following code and I'm wondering whether it makes sense:

go-webdav/carddav/client.go

Lines 308 to 315 in 13fa812

propReq, err := encodeAddressPropReq(&multiGet.DataRequest)
if err != nil {
return nil, err
}
addressbookMultiget := addressbookMultiget{Prop: propReq}
if multiGet == nil || len(multiGet.Paths) == 0 {

From what I see multiGet cannot be nil, because &multiGet.DataRequest would throw an invalid memory address / nil pointer error. Hence the check if multiGet == nil seems kind of pointless here.

Either that check should happen earlier or not at all, imho.

RFC: only support a single path layout?

As came up several times, the code keeps getting more and more complicated due to the fact that we support different path layouts (as defined below). The latest example for this is #99. While that PR does a lot of the work already, the complexity of adding features (such as multiple address books might) will always be much higher when accounting for support for all use cases.

I figured it might be wise to take a step back and reiterate how we got here, what problems we are actually trying to solve, and if there is maybe different approach altogether that could ease the pain here.

Let's start with some definitions: WebDAV/CardDAV/CalDAV define a set of paths that serve certain special purposes. We need to consider the following:

  • User principal URL - defined in RFC 5397, identifies a user, actually an extension, but required by CardDAV/CalDAV. Also indispensable for discovery purposes, so we need this. Its relation to other paths is not specified, so it could be anything really. However, many implementations make it the root of all collections that this user owns, as such a root has to exist anyway if you want to support accessing other users' resources.
  • Calendar/Address book home set - defined by CalDAV and CardDAV respectively, identifies a collection that contains all calendar/address book collections. Also used for discovery, see e.g. RFC 4791 section 8.4
  • Calendar/Address book URL - identifies a calendar or adress book, essentially a regular WebDAV collection which contains either events or contacts and provides some additional CalDAV/CardDAV-specific attributes. At the moment, go-webdav only supports a single calendar/address book. This URL has a relation to the calendar/address book home set: it should be underneath it (or at least equal to it), to be discoverable.

For brevity, I will from now on only focus on CalDAV, but the same applies of course to CardDAV.

Where we started

Initially, the server was designed in such a way that everything was served at the root path (/). The root path was user principal URL, calendar home set, and calendar URL at the same time. The only other URLs involved were the individual resources (events). This works, but has the following drawbacks:

  • Multiple calendars impossible - for multiple calendars, each one must be addressable, requiring URLs like /calendar1/ etc.
  • Serving calendars and address book in the same server impossible - for this to work, a user must be able to discover a calendar home set and an address book home set, which must be distinct from each other, e.g. /contacts/ and /calendars/.
  • Addressing other user's (and their calendars) impossible - though this might be far off in terms of being supported, it is worth considering at this point. For this to work, each user has to have a unique user principal URL, such as /bitfehler/ or /emersion/.

Current status

Right now, we try to still the support the old case of everything at /, and everything in between that and the most extreme opposite, where everything has it's own URL, such as /bitfehler/calendars/work/, where /bitfehler/ is a user pricipal URL, /bitfehler/calendars/ is the calendar home set, and /bitfehler/calendars/work/ is an actual calendar.

Keeping support for everything has caused the regression that #99 tries to fix and leads to all this complicated code where we have to check if something is e.g. also a calendar home set, even though we already know it's a user principal URL.

What now?

Writing all this down, I think my proposal would be to go with the layout that makes everything possible (/bitfehler/calendars/work/), but only support that. That should make the code much clearer again, while still allowing to add any feature we might want in the future. It would make writing a backend a tiny bit more work, but I think if all you need is one user and one calendar, hard-coding some names the backend is not too much to ask.

If you think this might be a route worth pursuing, I can offer to create a PR for that, to see what the code might look like?

internal: r/w roundtrip of Time broken

hi,

trying to retrieve the list of contacts (served by hydroxide) I noticed the internal.Time value XML encoding/decoding roundtrip was broken:

  • marshaling is done with time.RFC1123Z
  • unmarshaling with net/http.ParseTime that only has: [http.TimeFormat, time.RFC850, time.ANSIC] as a list of possible time formats.

How do I use this library to list the events in a calendar?

Hi, I'm trying to list the events in my calendar using your library. I've managed to get as far as the list of calendars, but I can't seem to get to the actual events. Can you please have a look and let me know what I'm doing wrong?

package main

import (
	"fmt"
	"log"
	"net/http"

	gowebdav "github.com/emersion/go-webdav"
	"github.com/emersion/go-webdav/caldav"
)

func main() {
	fmt.Println("hello world!")

	httpClient := &http.Client{}
	authorizedClient := gowebdav.HTTPClientWithBasicAuth(httpClient, "USERNAME", "PASSWORD")  // FIXME

	caldavClient, err := caldav.NewClient(authorizedClient, "https://caldav.fastmail.com/dav/calendars/user/USERNAME")
	if err != nil {
		log.Fatalf("NewClient: %s", err)
	}

	// curl -X PROPFIND -u USERNAME:PASSWORD https://caldav.fastmail.com/dav/principals/user/USERNAME
	homeSet, err := caldavClient.FindCalendarHomeSet("/dav/principals/user/USERNAME")
	if err != nil {
		log.Fatalf("FindCalendarHomeSet: %s", err)
	}

	fmt.Println(homeSet)

	calendars, err := caldavClient.FindCalendars(homeSet)
	if err != nil {
		log.Fatalf("FindCalendars: %s", err)
	}
	for i, calendar := range calendars {
		fmt.Printf("cal %d: %s %s\n", i, calendar.Name, calendar.Path)
	}

	compRequest := caldav.CalendarCompRequest{
		Name: "VEVENT",
		AllProps: true,
		AllComps: true,
	}
	compFilter := caldav.CompFilter{}
	query := caldav.CalendarQuery{CompRequest: compRequest, CompFilter: compFilter}
	objects, err := caldavClient.QueryCalendar(calendars[3].Path, &query)
	if err != nil {
		log.Fatalf("QueryCalendar: %s", err)
	}

	for i, obj := range objects {
		fmt.Printf("%d %s", i, obj.Path)
	}
}

I'm getting this error:

2022/04/18 13:21:01 QueryCalendar: 400 Bad Request: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title>400 Bad Request</title>
  </head>
  <body>
    <h1>Bad Request</h1>
    <p>The request was not understood by this server.</p>
    <hr>
    <address>Cyrus-HTTP/3.7.0-alpha0-387-g7ea99c4045-fm-20220413.002-g7ea99c40 Cyrus-SASL/2.1.27 Lib/XML2.9.10 Jansson/2.13.1 OpenSSL/1.1.1n Wslay/1.1.1 Zlib/1.2.11 Brotli/1.0.9 Xapian/1.5.0 LibiCal/3.0 ICU4C/69.1 SQLite/3.34.1 Server at caldav.fastmail.com Port 2275</address>
  </body>
</html>
exit status 1

Also, if there are any docs/tutorials on using your library?

FileSystem interface: provide context, make FileInfo an interface

Just a suggestion: I was looking to potentially use this project instead of golang.org/x/net/webdav and noticed the FileSystem interface is slightly different:

  1. go-webdav doesn't pass a Context to its calls.
  2. FileInfo is a struct and not an interface.
  3. Readdir returns a []FileInfo (slice of values) but Stat returns a *FileInfo (pointer).

I was wondering if you'd consider changing this in the future? Specifically, the first two are useful for when a file system is actually remote like e.g. Dropbox or Google Drive. And using pointers in the slice is just good for consistency (and also, deep copying a slice of values has a larger overhead).

carddav: consider falling back to input domain in Discover

If there's no SRV record, maybe we should default to the input domain, as suggested in the RFC?

   *  If an SRV record is not found, the client will need to prompt
      the user to enter the FQDN and port number information
      directly or use some other heuristic, for example, using the
      extracted "domain" as the FQDN and default HTTPS or HTTP port
      numbers.

Or at least return a fixed error value which can be handled by the user.

webdav: stop using http.File for backend

It's stateful, it's tied to os and it's too restrictive (we don't actually need io.Seeker).

Replace it with something like:

type File interface {
	io.ReadCloser
}

type FileSystem interface {
	Open(name string) (File, error)
	Stat(name string) (os.FileInfo, error)
	Readdir(name string) ([]os.FileInfo, error)
}

Where's the documentation?

Hello, we've recently come across your project and are very interested in using it.
However despite the large count of contributors and people using this library we've failed to find any source of documentation.
Does your project have documentation? If yes, could this possibly be pointed to somehow?

CalDav ETag UnmarshalText issue

I experimented a bit with this library and found an issue with caldavClient.QueryCalendar.
I am trying to query a calendar from OpenXchange but I get the error "webdav: failed to unquote ETag: invalid syntax". This error seems to come from the file "internal/elements.go:345" and occurs because in the returned event the ETag looks like this: "<D:getetag>http://www.open-xchange.com/etags/6063-1626334223127</D:getetag>".
Unfortunately, I don't know how to fix this. Should you need more information, I will of course make it available.

Program source

package main

import (
	"fmt"
	"github.com/emersion/go-webdav"
	"github.com/emersion/go-webdav/caldav"
	"log"
	"net/http"
	"net/http/httptrace"
	"net/http/httputil"
	"os"
	"time"
)

// transport is an http.RoundTripper that keeps track of the in-flight
// request and implements hooks to report HTTP tracing events.
type transport struct {
	current *http.Request
}

// RoundTrip wraps http.DefaultTransport.RoundTrip to keep track
// of the current request.
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
	t.current = req
	fmt.Println(req)
	resp, err := http.DefaultTransport.RoundTrip(req)
	dump, _ := httputil.DumpResponse(resp, true)

	fmt.Printf("\n\nResponse:\n%q\n", dump)
	return resp, err
}

func main() {
	t := &transport{}

	client := &http.Client{Transport: t}

	baHttpClient := webdav.HTTPClientWithBasicAuth(client, os.Getenv("USERNAME1"), os.Getenv("PASSWORD"))

	caldavClient, err := caldav.NewClient(baHttpClient, os.Getenv("ENDPOINT"))
	if err != nil {
		log.Fatal(err)
	}

	principal, err := caldavClient.FindCurrentUserPrincipal()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(principal)
	homeSet, err := caldavClient.FindCalendarHomeSet(principal)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(homeSet)
	calendars, err := caldavClient.FindCalendars(homeSet)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(calendars)

	query := &caldav.CalendarQuery{
		CompRequest: caldav.CalendarCompRequest{
			Name:  "VCALENDAR",
			Props: []string{"VERSION"},
			Comps: []caldav.CalendarCompRequest{{
				Name: "VEVENT",
				Props: []string{
					"SUMMARY",
					"UID",
					"DTSTART",
					"DTEND",
					"DURATION",
				},
			}},
		},
		CompFilter: caldav.CompFilter{
			Name: "VCALENDAR",
			Comps: []caldav.CompFilter{{
				Name:  "VEVENT",
				Start: time.Now().Add(-92 * time.Hour),
				End:   time.Now().Add(24 * time.Hour),
			}},
		},
	}

	cal, err := caldavClient.QueryCalendar(calendars[0].Path, query)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(cal)
}

Example Response:

[...]

"HTTP/2.0 207 Multi-Status\r\nConnection: close\r\nContent-Type: text/xml; charset=UTF-8\r\nDate: Sat, 17 Jul 2021 12:36:20 GMT\r\nServer: nginx\r\nStrict-Transport-Security: max-age=63072000;\r\nX-Robots-Tag: none\r\n\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<D:multistatus xmlns:D=\"DAV:\" xmlns:APPLE=\"http://apple.com/ns/ical/\" xmlns:CAL=\"urn:ietf:params:xml:ns:caldav\" xmlns:CS=\"http://calendarserver.org/ns/\"><D:response><D:href>/caldav/283/1bbda1e5-874d-460e-8114-720623dfdd7f.ics</D:href><D:propstat><D:prop><calendar-data xmlns=\"urn:ietf:params:xml:ns:caldav\"><![CDATA[BEGIN:VCALENDAR\nPRODID:Open-Xchange\nVERSION:2.0\nCALSCALE:GREGORIAN\nBEGIN:VTIMEZONE\nTZID:Europe/Berlin\nTZURL:http://tzurl.org/zoneinfo-outlook/Europe/Berlin\nX-LIC-LOCATION:Europe/Berlin\nBEGIN:DAYLIGHT\nTZOFFSETFROM:+0100\nTZOFFSETTO:+0200\nTZNAME:CEST\nDTSTART:19700329T020000\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\nEND:DAYLIGHT\nBEGIN:STANDARD\nTZOFFSETFROM:+0200\nTZOFFSETTO:+0100\nTZNAME:CET\nDTSTART:19701025T030000\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nDTSTAMP:20210717T100023Z\nSUMMARY:TestEvent\nDTSTART;VALUE=DATE:20150120\nDTEND;VALUE=DATE:20150121\nCLASS:PUBLIC\nX-MICROSOFT-CDO-BUSYSTATUS:FREE\nTRANSP:TRANSPARENT\nRRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1;BYMONTHDAY=20\nUID:1bbda1e5-874d-460e-8114-720623dfdd7f\nCREATED:20150114T083240Z\nLAST-MODIFIED:20150114T083240Z\nSEQUENCE:0\nEND:VEVENT\nEND:VCALENDAR\n]]></calendar-data><D:getetag>http://www.open-xchange.com/etags/490-1421224360501</D:getetag><D:getlastmodified xmlns:b=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\" b:dt=\"dateTime.rfc1123\">Wed, 14 Jan 2015 08:32:40 GMT</D:getlastmodified></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response><D:response><D:href>/caldav/283/af3262c2-e63f-418b-946d-6e1399167a19.ics</D:href><D:propstat><D:prop><calendar-data xmlns=\"urn:ietf:params:xml:ns:caldav\"><![CDATA[BEGIN:VCALENDAR\nPRODID:Open-Xchange\nVERSION:2.0\nCALSCALE:GREGORIAN\nBEGIN:VTIMEZONE\nTZID:Europe/Berlin\nTZURL:http://tzurl.org/zoneinfo-outlook/Europe/Berlin\nX-LIC-LOCATION:Europe/Berlin\nBEGIN:DAYLIGHT\nTZOFFSETFROM:+0100\nTZOFFSETTO:+0200\nTZNAME:CEST\nDTSTART:19700329T020000\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\nEND:DAYLIGHT\nBEGIN:STANDARD\nTZOFFSETFROM:+0200\nTZOFFSETTO:+0100\nTZNAME:CET\nDTSTART:19701025T030000\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\nEND:STANDARD\nEND:VTIMEZONE\nBEGIN:VEVENT\nDTSTAMP:20210717T100023Z\nSUMMARY:TestEvent2\nDTSTART;TZID=Europe/Berlin:20210715T160000\nDTEND;TZID=Europe/Berlin:20210715T170000\nCLASS:PUBLIC\nLOCATION:TestLocation2\nX-MICROSOFT-CDO-BUSYSTATUS:BUSY\nTRANSP:OPAQUE\nUID:af3262c2-e63f-418b-946d-6e1399167a19\nCREATED:20210715T072804Z\nLAST-MODIFIED:20210715T073023Z\nSEQUENCE:1\nBEGIN:VALARM\nTRIGGER:-PT60M\nACTION:DISPLAY\nDESCRIPTION:Alarm\nACKNOWLEDGED:20210715T125900Z\nX-MOZ-LASTACK:20210715T125900Z\nEND:VALARM\nEND:VEVENT\nEND:VCALENDAR\n]]></calendar-data><D:getetag>http://www.open-xchange.com/etags/6063-1626334223127</D:getetag><D:getlastmodified xmlns:b=\"urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/\" b:dt=\"dateTime.rfc1123\">Thu, 15 Jul 2021 07:30:23 GMT</D:getlastmodified></D:prop><D:status>HTTP/1.1 200 OK</D:status></D:propstat></D:response></D:multistatus>\r\n"

2021/07/17 12:36:20 webdav: failed to unquote ETag: invalid syntax

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.