1
0

Better caldav support (#73)

This commit is contained in:
konrad
2019-05-22 17:48:48 +00:00
committed by Gitea
parent de24fcc2f8
commit 7107d030fc
91 changed files with 7060 additions and 323 deletions

14
vendor/github.com/beevik/etree/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,14 @@
language: go
sudo: false
go:
- 1.11.x
- tip
matrix:
allow_failures:
- go: tip
script:
- go vet ./...
- go test -v ./...

10
vendor/github.com/beevik/etree/CONTRIBUTORS generated vendored Normal file
View File

@ -0,0 +1,10 @@
Brett Vickers (beevik)
Felix Geisendörfer (felixge)
Kamil Kisiel (kisielk)
Graham King (grahamking)
Matt Smith (ma314smith)
Michal Jemala (michaljemala)
Nicolas Piganeau (npiganeau)
Chris Brown (ccbrown)
Earncef Sequeira (earncef)
Gabriel de Labachelerie (wuzuf)

24
vendor/github.com/beevik/etree/LICENSE generated vendored Normal file
View File

@ -0,0 +1,24 @@
Copyright 2015-2019 Brett Vickers. 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.
THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``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 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.

205
vendor/github.com/beevik/etree/README.md generated vendored Normal file
View File

@ -0,0 +1,205 @@
[![Build Status](https://travis-ci.org/beevik/etree.svg?branch=master)](https://travis-ci.org/beevik/etree)
[![GoDoc](https://godoc.org/github.com/beevik/etree?status.svg)](https://godoc.org/github.com/beevik/etree)
etree
=====
The etree package is a lightweight, pure go package that expresses XML in
the form of an element tree. Its design was inspired by the Python
[ElementTree](http://docs.python.org/2/library/xml.etree.elementtree.html)
module.
Some of the package's capabilities and features:
* Represents XML documents as trees of elements for easy traversal.
* Imports, serializes, modifies or creates XML documents from scratch.
* Writes and reads XML to/from files, byte slices, strings and io interfaces.
* Performs simple or complex searches with lightweight XPath-like query APIs.
* Auto-indents XML using spaces or tabs for better readability.
* Implemented in pure go; depends only on standard go libraries.
* Built on top of the go [encoding/xml](http://golang.org/pkg/encoding/xml)
package.
### Creating an XML document
The following example creates an XML document from scratch using the etree
package and outputs its indented contents to stdout.
```go
doc := etree.NewDocument()
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
doc.CreateProcInst("xml-stylesheet", `type="text/xsl" href="style.xsl"`)
people := doc.CreateElement("People")
people.CreateComment("These are all known people")
jon := people.CreateElement("Person")
jon.CreateAttr("name", "Jon")
sally := people.CreateElement("Person")
sally.CreateAttr("name", "Sally")
doc.Indent(2)
doc.WriteTo(os.Stdout)
```
Output:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="style.xsl"?>
<People>
<!--These are all known people-->
<Person name="Jon"/>
<Person name="Sally"/>
</People>
```
### Reading an XML file
Suppose you have a file on disk called `bookstore.xml` containing the
following data:
```xml
<bookstore xmlns:p="urn:schemas-books-com:prices">
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<p:price>30.00</p:price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<p:price>29.99</p:price>
</book>
<book category="WEB">
<title lang="en">XQuery Kick Start</title>
<author>James McGovern</author>
<author>Per Bothner</author>
<author>Kurt Cagle</author>
<author>James Linn</author>
<author>Vaidyanathan Nagarajan</author>
<year>2003</year>
<p:price>49.99</p:price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<p:price>39.95</p:price>
</book>
</bookstore>
```
This code reads the file's contents into an etree document.
```go
doc := etree.NewDocument()
if err := doc.ReadFromFile("bookstore.xml"); err != nil {
panic(err)
}
```
You can also read XML from a string, a byte slice, or an `io.Reader`.
### Processing elements and attributes
This example illustrates several ways to access elements and attributes using
etree selection queries.
```go
root := doc.SelectElement("bookstore")
fmt.Println("ROOT element:", root.Tag)
for _, book := range root.SelectElements("book") {
fmt.Println("CHILD element:", book.Tag)
if title := book.SelectElement("title"); title != nil {
lang := title.SelectAttrValue("lang", "unknown")
fmt.Printf(" TITLE: %s (%s)\n", title.Text(), lang)
}
for _, attr := range book.Attr {
fmt.Printf(" ATTR: %s=%s\n", attr.Key, attr.Value)
}
}
```
Output:
```
ROOT element: bookstore
CHILD element: book
TITLE: Everyday Italian (en)
ATTR: category=COOKING
CHILD element: book
TITLE: Harry Potter (en)
ATTR: category=CHILDREN
CHILD element: book
TITLE: XQuery Kick Start (en)
ATTR: category=WEB
CHILD element: book
TITLE: Learning XML (en)
ATTR: category=WEB
```
### Path queries
This example uses etree's path functions to select all book titles that fall
into the category of 'WEB'. The double-slash prefix in the path causes the
search for book elements to occur recursively; book elements may appear at any
level of the XML hierarchy.
```go
for _, t := range doc.FindElements("//book[@category='WEB']/title") {
fmt.Println("Title:", t.Text())
}
```
Output:
```
Title: XQuery Kick Start
Title: Learning XML
```
This example finds the first book element under the root bookstore element and
outputs the tag and text of each of its child elements.
```go
for _, e := range doc.FindElements("./bookstore/book[1]/*") {
fmt.Printf("%s: %s\n", e.Tag, e.Text())
}
```
Output:
```
title: Everyday Italian
author: Giada De Laurentiis
year: 2005
price: 30.00
```
This example finds all books with a price of 49.99 and outputs their titles.
```go
path := etree.MustCompilePath("./bookstore/book[p:price='49.99']/title")
for _, e := range doc.FindElementsPath(path) {
fmt.Println(e.Text())
}
```
Output:
```
XQuery Kick Start
```
Note that this example uses the FindElementsPath function, which takes as an
argument a pre-compiled path object. Use precompiled paths when you plan to
search with the same path more than once.
### Other features
These are just a few examples of the things the etree package can do. See the
[documentation](http://godoc.org/github.com/beevik/etree) for a complete
description of its capabilities.
### Contributing
This project accepts contributions. Just fork the repo and submit a pull
request!

109
vendor/github.com/beevik/etree/RELEASE_NOTES.md generated vendored Normal file
View File

@ -0,0 +1,109 @@
Release v1.1.0
==============
**New Features**
* New attribute helpers.
* Added the `Element.SortAttrs` method, which lexicographically sorts an
element's attributes by key.
* New `ReadSettings` properties.
* Added `Entity` for the support of custom entity maps.
* New `WriteSettings` properties.
* Added `UseCRLF` to allow the output of CR-LF newlines instead of the
default LF newlines. This is useful on Windows systems.
* Additional support for text and CDATA sections.
* The `Element.Text` method now returns the concatenation of all consecutive
character data tokens immediately following an element's opening tag.
* Added `Element.SetCData` to replace the character data immediately
following an element's opening tag with a CDATA section.
* Added `Element.CreateCData` to create and add a CDATA section child
`CharData` token to an element.
* Added `Element.CreateText` to create and add a child text `CharData` token
to an element.
* Added `NewCData` to create a parentless CDATA section `CharData` token.
* Added `NewText` to create a parentless text `CharData`
token.
* Added `CharData.IsCData` to detect if the token contains a CDATA section.
* Added `CharData.IsWhitespace` to detect if the token contains whitespace
inserted by one of the document Indent functions.
* Modified `Element.SetText` so that it replaces a run of consecutive
character data tokens following the element's opening tag (instead of just
the first one).
* New "tail text" support.
* Added the `Element.Tail` method, which returns the text immediately
following an element's closing tag.
* Added the `Element.SetTail` method, which modifies the text immediately
following an element's closing tag.
* New element child insertion and removal methods.
* Added the `Element.InsertChildAt` method, which inserts a new child token
before the specified child token index.
* Added the `Element.RemoveChildAt` method, which removes the child token at
the specified child token index.
* New element and attribute queries.
* Added the `Element.Index` method, which returns the element's index within
its parent element's child token list.
* Added the `Element.NamespaceURI` method to return the namespace URI
associated with an element.
* Added the `Attr.NamespaceURI` method to return the namespace URI
associated with an element.
* Added the `Attr.Element` method to return the element that an attribute
belongs to.
* New Path filter functions.
* Added `[local-name()='val']` to keep elements whose unprefixed tag matches
the desired value.
* Added `[name()='val']` to keep elements whose full tag matches the desired
value.
* Added `[namespace-prefix()='val']` to keep elements whose namespace prefix
matches the desired value.
* Added `[namespace-uri()='val']` to keep elements whose namespace URI
matches the desired value.
**Bug Fixes**
* A default XML `CharSetReader` is now used to prevent failed parsing of XML
documents using certain encodings.
([Issue](https://github.com/beevik/etree/issues/53)).
* All characters are now properly escaped according to XML parsing rules.
([Issue](https://github.com/beevik/etree/issues/55)).
* The `Document.Indent` and `Document.IndentTabs` functions no longer insert
empty string `CharData` tokens.
**Deprecated**
* `Element`
* The `InsertChild` method is deprecated. Use `InsertChildAt` instead.
* The `CreateCharData` method is deprecated. Use `CreateText` instead.
* `CharData`
* The `NewCharData` method is deprecated. Use `NewText` instead.
Release v1.0.1
==============
**Changes**
* Added support for absolute etree Path queries. An absolute path begins with
`/` or `//` and begins its search from the element's document root.
* Added [`GetPath`](https://godoc.org/github.com/beevik/etree#Element.GetPath)
and [`GetRelativePath`](https://godoc.org/github.com/beevik/etree#Element.GetRelativePath)
functions to the [`Element`](https://godoc.org/github.com/beevik/etree#Element)
type.
**Breaking changes**
* A path starting with `//` is now interpreted as an absolute path.
Previously, it was interpreted as a relative path starting from the element
whose
[`FindElement`](https://godoc.org/github.com/beevik/etree#Element.FindElement)
method was called. To remain compatible with this release, all paths
prefixed with `//` should be prefixed with `.//` when called from any
element other than the document's root.
* [**edit 2/1/2019**]: Minor releases should not contain breaking changes.
Even though this breaking change was very minor, it was a mistake to include
it in this minor release. In the future, all breaking changes will be
limited to major releases (e.g., version 2.0.0).
Release v1.0.0
==============
Initial release.

1453
vendor/github.com/beevik/etree/etree.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

276
vendor/github.com/beevik/etree/helpers.go generated vendored Normal file
View File

@ -0,0 +1,276 @@
// Copyright 2015-2019 Brett Vickers.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package etree
import (
"bufio"
"io"
"strings"
"unicode/utf8"
)
// A simple stack
type stack struct {
data []interface{}
}
func (s *stack) empty() bool {
return len(s.data) == 0
}
func (s *stack) push(value interface{}) {
s.data = append(s.data, value)
}
func (s *stack) pop() interface{} {
value := s.data[len(s.data)-1]
s.data[len(s.data)-1] = nil
s.data = s.data[:len(s.data)-1]
return value
}
func (s *stack) peek() interface{} {
return s.data[len(s.data)-1]
}
// A fifo is a simple first-in-first-out queue.
type fifo struct {
data []interface{}
head, tail int
}
func (f *fifo) add(value interface{}) {
if f.len()+1 >= len(f.data) {
f.grow()
}
f.data[f.tail] = value
if f.tail++; f.tail == len(f.data) {
f.tail = 0
}
}
func (f *fifo) remove() interface{} {
value := f.data[f.head]
f.data[f.head] = nil
if f.head++; f.head == len(f.data) {
f.head = 0
}
return value
}
func (f *fifo) len() int {
if f.tail >= f.head {
return f.tail - f.head
}
return len(f.data) - f.head + f.tail
}
func (f *fifo) grow() {
c := len(f.data) * 2
if c == 0 {
c = 4
}
buf, count := make([]interface{}, c), f.len()
if f.tail >= f.head {
copy(buf[0:count], f.data[f.head:f.tail])
} else {
hindex := len(f.data) - f.head
copy(buf[0:hindex], f.data[f.head:])
copy(buf[hindex:count], f.data[:f.tail])
}
f.data, f.head, f.tail = buf, 0, count
}
// countReader implements a proxy reader that counts the number of
// bytes read from its encapsulated reader.
type countReader struct {
r io.Reader
bytes int64
}
func newCountReader(r io.Reader) *countReader {
return &countReader{r: r}
}
func (cr *countReader) Read(p []byte) (n int, err error) {
b, err := cr.r.Read(p)
cr.bytes += int64(b)
return b, err
}
// countWriter implements a proxy writer that counts the number of
// bytes written by its encapsulated writer.
type countWriter struct {
w io.Writer
bytes int64
}
func newCountWriter(w io.Writer) *countWriter {
return &countWriter{w: w}
}
func (cw *countWriter) Write(p []byte) (n int, err error) {
b, err := cw.w.Write(p)
cw.bytes += int64(b)
return b, err
}
// isWhitespace returns true if the byte slice contains only
// whitespace characters.
func isWhitespace(s string) bool {
for i := 0; i < len(s); i++ {
if c := s[i]; c != ' ' && c != '\t' && c != '\n' && c != '\r' {
return false
}
}
return true
}
// spaceMatch returns true if namespace a is the empty string
// or if namespace a equals namespace b.
func spaceMatch(a, b string) bool {
switch {
case a == "":
return true
default:
return a == b
}
}
// spaceDecompose breaks a namespace:tag identifier at the ':'
// and returns the two parts.
func spaceDecompose(str string) (space, key string) {
colon := strings.IndexByte(str, ':')
if colon == -1 {
return "", str
}
return str[:colon], str[colon+1:]
}
// Strings used by indentCRLF and indentLF
const (
indentSpaces = "\r\n "
indentTabs = "\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"
)
// indentCRLF returns a CRLF newline followed by n copies of the first
// non-CRLF character in the source string.
func indentCRLF(n int, source string) string {
switch {
case n < 0:
return source[:2]
case n < len(source)-1:
return source[:n+2]
default:
return source + strings.Repeat(source[2:3], n-len(source)+2)
}
}
// indentLF returns a LF newline followed by n copies of the first non-LF
// character in the source string.
func indentLF(n int, source string) string {
switch {
case n < 0:
return source[1:2]
case n < len(source)-1:
return source[1 : n+2]
default:
return source[1:] + strings.Repeat(source[2:3], n-len(source)+2)
}
}
// nextIndex returns the index of the next occurrence of sep in s,
// starting from offset. It returns -1 if the sep string is not found.
func nextIndex(s, sep string, offset int) int {
switch i := strings.Index(s[offset:], sep); i {
case -1:
return -1
default:
return offset + i
}
}
// isInteger returns true if the string s contains an integer.
func isInteger(s string) bool {
for i := 0; i < len(s); i++ {
if (s[i] < '0' || s[i] > '9') && !(i == 0 && s[i] == '-') {
return false
}
}
return true
}
type escapeMode byte
const (
escapeNormal escapeMode = iota
escapeCanonicalText
escapeCanonicalAttr
)
// escapeString writes an escaped version of a string to the writer.
func escapeString(w *bufio.Writer, s string, m escapeMode) {
var esc []byte
last := 0
for i := 0; i < len(s); {
r, width := utf8.DecodeRuneInString(s[i:])
i += width
switch r {
case '&':
esc = []byte("&amp;")
case '<':
esc = []byte("&lt;")
case '>':
if m == escapeCanonicalAttr {
continue
}
esc = []byte("&gt;")
case '\'':
if m != escapeNormal {
continue
}
esc = []byte("&apos;")
case '"':
if m == escapeCanonicalText {
continue
}
esc = []byte("&quot;")
case '\t':
if m != escapeCanonicalAttr {
continue
}
esc = []byte("&#x9;")
case '\n':
if m != escapeCanonicalAttr {
continue
}
esc = []byte("&#xA;")
case '\r':
if m == escapeNormal {
continue
}
esc = []byte("&#xD;")
default:
if !isInCharacterRange(r) || (r == 0xFFFD && width == 1) {
esc = []byte("\uFFFD")
break
}
continue
}
w.WriteString(s[last : i-width])
w.Write(esc)
last = i
}
w.WriteString(s[last:])
}
func isInCharacterRange(r rune) bool {
return r == 0x09 ||
r == 0x0A ||
r == 0x0D ||
r >= 0x20 && r <= 0xD7FF ||
r >= 0xE000 && r <= 0xFFFD ||
r >= 0x10000 && r <= 0x10FFFF
}

582
vendor/github.com/beevik/etree/path.go generated vendored Normal file
View File

@ -0,0 +1,582 @@
// Copyright 2015-2019 Brett Vickers.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package etree
import (
"strconv"
"strings"
)
/*
A Path is a string that represents a search path through an etree starting
from the document root or an arbitrary element. Paths are used with the
Element object's Find* methods to locate and return desired elements.
A Path consists of a series of slash-separated "selectors", each of which may
be modified by one or more bracket-enclosed "filters". Selectors are used to
traverse the etree from element to element, while filters are used to narrow
the list of candidate elements at each node.
Although etree Path strings are similar to XPath strings
(https://www.w3.org/TR/1999/REC-xpath-19991116/), they have a more limited set
of selectors and filtering options.
The following selectors are supported by etree Path strings:
. Select the current element.
.. Select the parent of the current element.
* Select all child elements of the current element.
/ Select the root element when used at the start of a path.
// Select all descendants of the current element.
tag Select all child elements with a name matching the tag.
The following basic filters are supported by etree Path strings:
[@attrib] Keep elements with an attribute named attrib.
[@attrib='val'] Keep elements with an attribute named attrib and value matching val.
[tag] Keep elements with a child element named tag.
[tag='val'] Keep elements with a child element named tag and text matching val.
[n] Keep the n-th element, where n is a numeric index starting from 1.
The following function filters are also supported:
[text()] Keep elements with non-empty text.
[text()='val'] Keep elements whose text matches val.
[local-name()='val'] Keep elements whose un-prefixed tag matches val.
[name()='val'] Keep elements whose full tag exactly matches val.
[namespace-prefix()='val'] Keep elements whose namespace prefix matches val.
[namespace-uri()='val'] Keep elements whose namespace URI matches val.
Here are some examples of Path strings:
- Select the bookstore child element of the root element:
/bookstore
- Beginning from the root element, select the title elements of all
descendant book elements having a 'category' attribute of 'WEB':
//book[@category='WEB']/title
- Beginning from the current element, select the first descendant
book element with a title child element containing the text 'Great
Expectations':
.//book[title='Great Expectations'][1]
- Beginning from the current element, select all child elements of
book elements with an attribute 'language' set to 'english':
./book/*[@language='english']
- Beginning from the current element, select all child elements of
book elements containing the text 'special':
./book/*[text()='special']
- Beginning from the current element, select all descendant book
elements whose title child element has a 'language' attribute of 'french':
.//book/title[@language='french']/..
- Beginning from the current element, select all book elements
belonging to the http://www.w3.org/TR/html4/ namespace:
.//book[namespace-uri()='http://www.w3.org/TR/html4/']
*/
type Path struct {
segments []segment
}
// ErrPath is returned by path functions when an invalid etree path is provided.
type ErrPath string
// Error returns the string describing a path error.
func (err ErrPath) Error() string {
return "etree: " + string(err)
}
// CompilePath creates an optimized version of an XPath-like string that
// can be used to query elements in an element tree.
func CompilePath(path string) (Path, error) {
var comp compiler
segments := comp.parsePath(path)
if comp.err != ErrPath("") {
return Path{nil}, comp.err
}
return Path{segments}, nil
}
// MustCompilePath creates an optimized version of an XPath-like string that
// can be used to query elements in an element tree. Panics if an error
// occurs. Use this function to create Paths when you know the path is
// valid (i.e., if it's hard-coded).
func MustCompilePath(path string) Path {
p, err := CompilePath(path)
if err != nil {
panic(err)
}
return p
}
// A segment is a portion of a path between "/" characters.
// It contains one selector and zero or more [filters].
type segment struct {
sel selector
filters []filter
}
func (seg *segment) apply(e *Element, p *pather) {
seg.sel.apply(e, p)
for _, f := range seg.filters {
f.apply(p)
}
}
// A selector selects XML elements for consideration by the
// path traversal.
type selector interface {
apply(e *Element, p *pather)
}
// A filter pares down a list of candidate XML elements based
// on a path filter in [brackets].
type filter interface {
apply(p *pather)
}
// A pather is helper object that traverses an element tree using
// a Path object. It collects and deduplicates all elements matching
// the path query.
type pather struct {
queue fifo
results []*Element
inResults map[*Element]bool
candidates []*Element
scratch []*Element // used by filters
}
// A node represents an element and the remaining path segments that
// should be applied against it by the pather.
type node struct {
e *Element
segments []segment
}
func newPather() *pather {
return &pather{
results: make([]*Element, 0),
inResults: make(map[*Element]bool),
candidates: make([]*Element, 0),
scratch: make([]*Element, 0),
}
}
// traverse follows the path from the element e, collecting
// and then returning all elements that match the path's selectors
// and filters.
func (p *pather) traverse(e *Element, path Path) []*Element {
for p.queue.add(node{e, path.segments}); p.queue.len() > 0; {
p.eval(p.queue.remove().(node))
}
return p.results
}
// eval evalutes the current path node by applying the remaining
// path's selector rules against the node's element.
func (p *pather) eval(n node) {
p.candidates = p.candidates[0:0]
seg, remain := n.segments[0], n.segments[1:]
seg.apply(n.e, p)
if len(remain) == 0 {
for _, c := range p.candidates {
if in := p.inResults[c]; !in {
p.inResults[c] = true
p.results = append(p.results, c)
}
}
} else {
for _, c := range p.candidates {
p.queue.add(node{c, remain})
}
}
}
// A compiler generates a compiled path from a path string.
type compiler struct {
err ErrPath
}
// parsePath parses an XPath-like string describing a path
// through an element tree and returns a slice of segment
// descriptors.
func (c *compiler) parsePath(path string) []segment {
// If path ends with //, fix it
if strings.HasSuffix(path, "//") {
path = path + "*"
}
var segments []segment
// Check for an absolute path
if strings.HasPrefix(path, "/") {
segments = append(segments, segment{new(selectRoot), []filter{}})
path = path[1:]
}
// Split path into segments
for _, s := range splitPath(path) {
segments = append(segments, c.parseSegment(s))
if c.err != ErrPath("") {
break
}
}
return segments
}
func splitPath(path string) []string {
pieces := make([]string, 0)
start := 0
inquote := false
for i := 0; i+1 <= len(path); i++ {
if path[i] == '\'' {
inquote = !inquote
} else if path[i] == '/' && !inquote {
pieces = append(pieces, path[start:i])
start = i + 1
}
}
return append(pieces, path[start:])
}
// parseSegment parses a path segment between / characters.
func (c *compiler) parseSegment(path string) segment {
pieces := strings.Split(path, "[")
seg := segment{
sel: c.parseSelector(pieces[0]),
filters: []filter{},
}
for i := 1; i < len(pieces); i++ {
fpath := pieces[i]
if fpath[len(fpath)-1] != ']' {
c.err = ErrPath("path has invalid filter [brackets].")
break
}
seg.filters = append(seg.filters, c.parseFilter(fpath[:len(fpath)-1]))
}
return seg
}
// parseSelector parses a selector at the start of a path segment.
func (c *compiler) parseSelector(path string) selector {
switch path {
case ".":
return new(selectSelf)
case "..":
return new(selectParent)
case "*":
return new(selectChildren)
case "":
return new(selectDescendants)
default:
return newSelectChildrenByTag(path)
}
}
var fnTable = map[string]struct {
hasFn func(e *Element) bool
getValFn func(e *Element) string
}{
"local-name": {nil, (*Element).name},
"name": {nil, (*Element).FullTag},
"namespace-prefix": {nil, (*Element).namespacePrefix},
"namespace-uri": {nil, (*Element).NamespaceURI},
"text": {(*Element).hasText, (*Element).Text},
}
// parseFilter parses a path filter contained within [brackets].
func (c *compiler) parseFilter(path string) filter {
if len(path) == 0 {
c.err = ErrPath("path contains an empty filter expression.")
return nil
}
// Filter contains [@attr='val'], [fn()='val'], or [tag='val']?
eqindex := strings.Index(path, "='")
if eqindex >= 0 {
rindex := nextIndex(path, "'", eqindex+2)
if rindex != len(path)-1 {
c.err = ErrPath("path has mismatched filter quotes.")
return nil
}
key := path[:eqindex]
value := path[eqindex+2 : rindex]
switch {
case key[0] == '@':
return newFilterAttrVal(key[1:], value)
case strings.HasSuffix(key, "()"):
fn := key[:len(key)-2]
if t, ok := fnTable[fn]; ok && t.getValFn != nil {
return newFilterFuncVal(t.getValFn, value)
}
c.err = ErrPath("path has unknown function " + fn)
return nil
default:
return newFilterChildText(key, value)
}
}
// Filter contains [@attr], [N], [tag] or [fn()]
switch {
case path[0] == '@':
return newFilterAttr(path[1:])
case strings.HasSuffix(path, "()"):
fn := path[:len(path)-2]
if t, ok := fnTable[fn]; ok && t.hasFn != nil {
return newFilterFunc(t.hasFn)
}
c.err = ErrPath("path has unknown function " + fn)
return nil
case isInteger(path):
pos, _ := strconv.Atoi(path)
switch {
case pos > 0:
return newFilterPos(pos - 1)
default:
return newFilterPos(pos)
}
default:
return newFilterChild(path)
}
}
// selectSelf selects the current element into the candidate list.
type selectSelf struct{}
func (s *selectSelf) apply(e *Element, p *pather) {
p.candidates = append(p.candidates, e)
}
// selectRoot selects the element's root node.
type selectRoot struct{}
func (s *selectRoot) apply(e *Element, p *pather) {
root := e
for root.parent != nil {
root = root.parent
}
p.candidates = append(p.candidates, root)
}
// selectParent selects the element's parent into the candidate list.
type selectParent struct{}
func (s *selectParent) apply(e *Element, p *pather) {
if e.parent != nil {
p.candidates = append(p.candidates, e.parent)
}
}
// selectChildren selects the element's child elements into the
// candidate list.
type selectChildren struct{}
func (s *selectChildren) apply(e *Element, p *pather) {
for _, c := range e.Child {
if c, ok := c.(*Element); ok {
p.candidates = append(p.candidates, c)
}
}
}
// selectDescendants selects all descendant child elements
// of the element into the candidate list.
type selectDescendants struct{}
func (s *selectDescendants) apply(e *Element, p *pather) {
var queue fifo
for queue.add(e); queue.len() > 0; {
e := queue.remove().(*Element)
p.candidates = append(p.candidates, e)
for _, c := range e.Child {
if c, ok := c.(*Element); ok {
queue.add(c)
}
}
}
}
// selectChildrenByTag selects into the candidate list all child
// elements of the element having the specified tag.
type selectChildrenByTag struct {
space, tag string
}
func newSelectChildrenByTag(path string) *selectChildrenByTag {
s, l := spaceDecompose(path)
return &selectChildrenByTag{s, l}
}
func (s *selectChildrenByTag) apply(e *Element, p *pather) {
for _, c := range e.Child {
if c, ok := c.(*Element); ok && spaceMatch(s.space, c.Space) && s.tag == c.Tag {
p.candidates = append(p.candidates, c)
}
}
}
// filterPos filters the candidate list, keeping only the
// candidate at the specified index.
type filterPos struct {
index int
}
func newFilterPos(pos int) *filterPos {
return &filterPos{pos}
}
func (f *filterPos) apply(p *pather) {
if f.index >= 0 {
if f.index < len(p.candidates) {
p.scratch = append(p.scratch, p.candidates[f.index])
}
} else {
if -f.index <= len(p.candidates) {
p.scratch = append(p.scratch, p.candidates[len(p.candidates)+f.index])
}
}
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
}
// filterAttr filters the candidate list for elements having
// the specified attribute.
type filterAttr struct {
space, key string
}
func newFilterAttr(str string) *filterAttr {
s, l := spaceDecompose(str)
return &filterAttr{s, l}
}
func (f *filterAttr) apply(p *pather) {
for _, c := range p.candidates {
for _, a := range c.Attr {
if spaceMatch(f.space, a.Space) && f.key == a.Key {
p.scratch = append(p.scratch, c)
break
}
}
}
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
}
// filterAttrVal filters the candidate list for elements having
// the specified attribute with the specified value.
type filterAttrVal struct {
space, key, val string
}
func newFilterAttrVal(str, value string) *filterAttrVal {
s, l := spaceDecompose(str)
return &filterAttrVal{s, l, value}
}
func (f *filterAttrVal) apply(p *pather) {
for _, c := range p.candidates {
for _, a := range c.Attr {
if spaceMatch(f.space, a.Space) && f.key == a.Key && f.val == a.Value {
p.scratch = append(p.scratch, c)
break
}
}
}
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
}
// filterFunc filters the candidate list for elements satisfying a custom
// boolean function.
type filterFunc struct {
fn func(e *Element) bool
}
func newFilterFunc(fn func(e *Element) bool) *filterFunc {
return &filterFunc{fn}
}
func (f *filterFunc) apply(p *pather) {
for _, c := range p.candidates {
if f.fn(c) {
p.scratch = append(p.scratch, c)
}
}
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
}
// filterFuncVal filters the candidate list for elements containing a value
// matching the result of a custom function.
type filterFuncVal struct {
fn func(e *Element) string
val string
}
func newFilterFuncVal(fn func(e *Element) string, value string) *filterFuncVal {
return &filterFuncVal{fn, value}
}
func (f *filterFuncVal) apply(p *pather) {
for _, c := range p.candidates {
if f.fn(c) == f.val {
p.scratch = append(p.scratch, c)
}
}
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
}
// filterChild filters the candidate list for elements having
// a child element with the specified tag.
type filterChild struct {
space, tag string
}
func newFilterChild(str string) *filterChild {
s, l := spaceDecompose(str)
return &filterChild{s, l}
}
func (f *filterChild) apply(p *pather) {
for _, c := range p.candidates {
for _, cc := range c.Child {
if cc, ok := cc.(*Element); ok &&
spaceMatch(f.space, cc.Space) &&
f.tag == cc.Tag {
p.scratch = append(p.scratch, c)
}
}
}
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
}
// filterChildText filters the candidate list for elements having
// a child element with the specified tag and text.
type filterChildText struct {
space, tag, text string
}
func newFilterChildText(str, text string) *filterChildText {
s, l := spaceDecompose(str)
return &filterChildText{s, l, text}
}
func (f *filterChildText) apply(p *pather) {
for _, c := range p.candidates {
for _, cc := range c.Child {
if cc, ok := cc.(*Element); ok &&
spaceMatch(f.space, cc.Space) &&
f.tag == cc.Tag &&
f.text == cc.Text() {
p.scratch = append(p.scratch, c)
}
}
}
p.candidates, p.scratch = p.scratch, p.candidates[0:0]
}

View File

@ -170,6 +170,8 @@ const (
charsetUTF8 = "charset=UTF-8"
// PROPFIND Method can be used on collection and property resources.
PROPFIND = "PROPFIND"
// REPORT Method can be used to get information about a resource, see rfc 3253
REPORT = "REPORT"
)
// Headers
@ -251,6 +253,7 @@ var (
PROPFIND,
http.MethodPut,
http.MethodTrace,
REPORT,
}
)

View File

@ -33,6 +33,7 @@ type (
propfind HandlerFunc
put HandlerFunc
trace HandlerFunc
report HandlerFunc
}
)
@ -247,6 +248,8 @@ func (n *node) addHandler(method string, h HandlerFunc) {
n.methodHandler.put = h
case http.MethodTrace:
n.methodHandler.trace = h
case REPORT:
n.methodHandler.report = h
}
}
@ -272,6 +275,8 @@ func (n *node) findHandler(method string) HandlerFunc {
return n.methodHandler.put
case http.MethodTrace:
return n.methodHandler.trace
case REPORT:
return n.methodHandler.report
default:
return nil
}

17
vendor/github.com/laurent22/ical-go/ical/calendar.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
package ical
type Calendar struct {
Items []CalendarEvent
}
func (this *Calendar) Serialize() string {
serializer := calSerializer{
calendar: this,
buffer: new(strBuffer),
}
return serializer.serialize()
}
func (this *Calendar) ToICS() string {
return this.Serialize()
}

View File

@ -0,0 +1,50 @@
package ical
import (
"time"
)
type CalendarEvent struct {
Id string
Summary string
Description string
Location string
CreatedAtUTC *time.Time
ModifiedAtUTC *time.Time
StartAt *time.Time
EndAt *time.Time
}
func (this *CalendarEvent) StartAtUTC() *time.Time {
return inUTC(this.StartAt)
}
func (this *CalendarEvent) EndAtUTC() *time.Time {
return inUTC(this.EndAt)
}
func (this *CalendarEvent) Serialize() string {
buffer := new(strBuffer)
return this.serializeWithBuffer(buffer)
}
func (this *CalendarEvent) ToICS() string {
return this.Serialize()
}
func (this *CalendarEvent) serializeWithBuffer(buffer *strBuffer) string {
serializer := calEventSerializer{
event: this,
buffer: buffer,
}
return serializer.serialize()
}
func inUTC(t *time.Time) *time.Time {
if t == nil {
return nil
}
tUTC := t.UTC()
return &tUTC
}

18
vendor/github.com/laurent22/ical-go/ical/lib.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
package ical
import (
"fmt"
"bytes"
)
type strBuffer struct {
buffer bytes.Buffer
}
func (b *strBuffer) Write(format string, elem ...interface{}) {
b.buffer.WriteString(fmt.Sprintf(format, elem...))
}
func (b *strBuffer) String() string {
return b.buffer.String()
}

168
vendor/github.com/laurent22/ical-go/ical/node.go generated vendored Normal file
View File

@ -0,0 +1,168 @@
package ical
import (
"time"
"regexp"
"strconv"
)
type Node struct {
Name string
Value string
Type int // 1 = Object, 0 = Name/Value
Parameters map[string]string
Children []*Node
}
func (this *Node) ChildrenByName(name string) []*Node {
var output []*Node
for _, child := range this.Children {
if child.Name == name {
output = append(output, child)
}
}
return output
}
func (this *Node) ChildByName(name string) *Node {
for _, child := range this.Children {
if child.Name == name {
return child
}
}
return nil
}
func (this *Node) PropString(name string, defaultValue string) string {
for _, child := range this.Children {
if child.Name == name {
return child.Value
}
}
return defaultValue
}
func (this *Node) PropDate(name string, defaultValue time.Time) time.Time {
node := this.ChildByName(name)
if node == nil { return defaultValue }
tzid := node.Parameter("TZID", "")
var output time.Time
var err error
if tzid != "" {
loc, err := time.LoadLocation(tzid)
if err != nil { panic(err) }
output, err = time.ParseInLocation("20060102T150405", node.Value, loc)
} else {
output, err = time.Parse("20060102T150405Z", node.Value)
}
if err != nil { panic(err) }
return output
}
func (this *Node) PropDuration(name string) time.Duration {
durStr := this.PropString(name, "")
if durStr == "" {
return time.Duration(0)
}
durRgx := regexp.MustCompile("PT(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)S)?")
matches := durRgx.FindStringSubmatch(durStr)
if len(matches) != 4 {
return time.Duration(0)
}
strToDuration := func(value string) time.Duration {
d := 0
if value != "" {
d, _ = strconv.Atoi(value)
}
return time.Duration(d)
}
hours := strToDuration(matches[1])
min := strToDuration(matches[2])
sec := strToDuration(matches[3])
return hours * time.Hour + min * time.Minute + sec * time.Second
}
func (this *Node) PropInt(name string, defaultValue int) int {
n := this.PropString(name, "")
if n == "" { return defaultValue }
output, err := strconv.Atoi(n)
if err != nil { panic(err) }
return output
}
func (this *Node) DigProperty(propPath... string) (string, bool) {
return this.dig("prop", propPath...)
}
func (this *Node) Parameter(name string, defaultValue string) string {
if len(this.Parameters) <= 0 { return defaultValue }
v, ok := this.Parameters[name]
if !ok { return defaultValue }
return v
}
func (this *Node) DigParameter(paramPath... string) (string, bool) {
return this.dig("param", paramPath...)
}
// Digs a value based on a given value path.
// valueType: can be "param" or "prop".
// valuePath: the path to access the value.
// Returns ("", false) when not found or (value, true) when found.
//
// Example:
// dig("param", "VCALENDAR", "VEVENT", "DTEND", "TYPE") -> It will search for "VCALENDAR" node,
// then a "VEVENT" node, then a "DTEND" note, then finally the "TYPE" param.
func (this *Node) dig(valueType string, valuePath... string) (string, bool) {
current := this
lastIndex := len(valuePath) - 1
for _, v := range valuePath[:lastIndex] {
current = current.ChildByName(v)
if current == nil {
return "", false
}
}
target := valuePath[lastIndex]
value := ""
if valueType == "param" {
value = current.Parameter(target, "")
} else if valueType == "prop" {
value = current.PropString(target, "")
}
if value == "" {
return "", false
}
return value, true
}
func (this *Node) String() string {
s := ""
if this.Type == 1 {
s += "===== " + this.Name
s += "\n"
} else {
s += this.Name
s += ":" + this.Value
s += "\n"
}
for _, child := range this.Children {
s += child.String()
}
if this.Type == 1 {
s += "===== /" + this.Name
s += "\n"
}
return s
}

106
vendor/github.com/laurent22/ical-go/ical/parsers.go generated vendored Normal file
View File

@ -0,0 +1,106 @@
package ical
import (
"log"
"errors"
"regexp"
"strings"
)
func ParseCalendar(data string) (*Node, error) {
r := regexp.MustCompile("([\r|\t| ]*\n[\r|\t| ]*)+")
lines := r.Split(strings.TrimSpace(data), -1)
node, _, err, _ := parseCalendarNode(lines, 0)
return node, err
}
func parseCalendarNode(lines []string, lineIndex int) (*Node, bool, error, int) {
line := strings.TrimSpace(lines[lineIndex])
_ = log.Println
colonIndex := strings.Index(line, ":")
if colonIndex <= 0 {
return nil, false, errors.New("Invalid value/pair: " + line), lineIndex + 1
}
name := line[0:colonIndex]
splitted := strings.Split(name, ";")
var parameters map[string]string
if len(splitted) >= 2 {
name = splitted[0]
parameters = make(map[string]string)
for i := 1; i < len(splitted); i++ {
p := strings.Split(splitted[i], "=")
if len(p) != 2 { panic("Invalid parameter format: " + name) }
parameters[p[0]] = p[1]
}
}
value := line[colonIndex+1:len(line)]
if name == "BEGIN" {
node := new(Node)
node.Name = value
node.Type = 1
lineIndex = lineIndex + 1
for {
child, finished, _, newLineIndex := parseCalendarNode(lines, lineIndex)
if finished {
return node, false, nil, newLineIndex
} else {
if child != nil {
node.Children = append(node.Children, child)
}
lineIndex = newLineIndex
}
}
} else if name == "END" {
return nil, true, nil, lineIndex + 1
} else {
node := new(Node)
node.Name = name
if name == "DESCRIPTION" || name == "SUMMARY" {
text, newLineIndex := parseTextType(lines, lineIndex)
node.Value = text
node.Parameters = parameters
return node, false, nil, newLineIndex
} else {
node.Value = value
node.Parameters = parameters
return node, false, nil, lineIndex + 1
}
}
panic("Unreachable")
return nil, false, nil, lineIndex + 1
}
func parseTextType(lines []string, lineIndex int) (string, int) {
line := lines[lineIndex]
colonIndex := strings.Index(line, ":")
output := strings.TrimSpace(line[colonIndex+1:len(line)])
lineIndex++
for {
line := lines[lineIndex]
if line == "" || line[0] != ' ' {
return unescapeTextType(output), lineIndex
}
output += line[1:len(line)]
lineIndex++
}
return unescapeTextType(output), lineIndex
}
func escapeTextType(input string) string {
output := strings.Replace(input, "\\", "\\\\", -1)
output = strings.Replace(output, ";", "\\;", -1)
output = strings.Replace(output, ",", "\\,", -1)
output = strings.Replace(output, "\n", "\\n", -1)
return output
}
func unescapeTextType(s string) string {
s = strings.Replace(s, "\\;", ";", -1)
s = strings.Replace(s, "\\,", ",", -1)
s = strings.Replace(s, "\\n", "\n", -1)
s = strings.Replace(s, "\\\\", "\\", -1)
return s
}

View File

@ -0,0 +1,9 @@
package ical
const (
VCALENDAR = "VCALENDAR"
VEVENT = "VEVENT"
DTSTART = "DTSTART"
DTEND = "DTEND"
DURATION = "DURATION"
)

121
vendor/github.com/laurent22/ical-go/ical/serializers.go generated vendored Normal file
View File

@ -0,0 +1,121 @@
package ical
import (
"time"
"strings"
)
type calSerializer struct {
calendar *Calendar
buffer *strBuffer
}
func (this *calSerializer) serialize() string {
this.serializeCalendar()
return strings.TrimSpace(this.buffer.String())
}
func (this *calSerializer) serializeCalendar() {
this.begin()
this.version()
this.items()
this.end()
}
func (this *calSerializer) begin() {
this.buffer.Write("BEGIN:VCALENDAR\n")
}
func (this *calSerializer) end() {
this.buffer.Write("END:VCALENDAR\n")
}
func (this *calSerializer) version() {
this.buffer.Write("VERSION:2.0\n")
}
func (this *calSerializer) items() {
for _, item := range this.calendar.Items {
item.serializeWithBuffer(this.buffer)
}
}
type calEventSerializer struct {
event *CalendarEvent
buffer *strBuffer
}
const (
eventSerializerTimeFormat = "20060102T150405Z"
)
func (this *calEventSerializer) serialize() string {
this.serializeEvent()
return strings.TrimSpace(this.buffer.String())
}
func (this *calEventSerializer) serializeEvent() {
this.begin()
this.uid()
this.created()
this.lastModified()
this.dtstart()
this.dtend()
this.summary()
this.description()
this.location()
this.end()
}
func (this *calEventSerializer) begin() {
this.buffer.Write("BEGIN:VEVENT\n")
}
func (this *calEventSerializer) end() {
this.buffer.Write("END:VEVENT\n")
}
func (this *calEventSerializer) uid() {
this.serializeStringProp("UID", this.event.Id)
}
func (this *calEventSerializer) summary() {
this.serializeStringProp("SUMMARY", this.event.Summary)
}
func (this *calEventSerializer) description() {
this.serializeStringProp("DESCRIPTION", this.event.Description)
}
func (this *calEventSerializer) location() {
this.serializeStringProp("LOCATION", this.event.Location)
}
func (this *calEventSerializer) dtstart() {
this.serializeTimeProp("DTSTART", this.event.StartAtUTC())
}
func (this *calEventSerializer) dtend() {
this.serializeTimeProp("DTEND", this.event.EndAtUTC())
}
func (this *calEventSerializer) created() {
this.serializeTimeProp("CREATED", this.event.CreatedAtUTC)
}
func (this *calEventSerializer) lastModified() {
this.serializeTimeProp("LAST-MODIFIED", this.event.ModifiedAtUTC)
}
func (this *calEventSerializer) serializeStringProp(name, value string) {
if value != "" {
escapedValue := escapeTextType(value)
this.buffer.Write("%s:%s\n", name, escapedValue)
}
}
func (this *calEventSerializer) serializeTimeProp(name string, value *time.Time) {
if value != nil {
this.buffer.Write("%s:%s\n", name, value.Format(eventSerializerTimeFormat))
}
}

94
vendor/github.com/laurent22/ical-go/ical/todo.go generated vendored Normal file
View File

@ -0,0 +1,94 @@
package ical
// import (
// "time"
// "strconv"
// "strings"
// )
//
// func TodoFromNode(node *Node) Todo {
// if node.Name != "VTODO" { panic("Node is not a VTODO") }
//
// var todo Todo
// todo.SetId(node.PropString("UID", ""))
// todo.SetSummary(node.PropString("SUMMARY", ""))
// todo.SetDescription(node.PropString("DESCRIPTION", ""))
// todo.SetDueDate(node.PropDate("DUE", time.Time{}))
// //todo.SetAlarmDate(this.TimestampBytesToTime(reminderDate))
// todo.SetCreatedDate(node.PropDate("CREATED", time.Time{}))
// todo.SetModifiedDate(node.PropDate("DTSTAMP", time.Time{}))
// todo.SetPriority(node.PropInt("PRIORITY", 0))
// todo.SetPercentComplete(node.PropInt("PERCENT-COMPLETE", 0))
// return todo
// }
//
// type Todo struct {
// CalendarItem
// dueDate time.Time
// }
//
// func (this *Todo) SetDueDate(v time.Time) { this.dueDate = v }
// func (this *Todo) DueDate() time.Time { return this.dueDate }
//
// func (this *Todo) ICalString(target string) string {
// s := "BEGIN:VTODO\n"
//
// if target == "macTodo" {
// status := "NEEDS-ACTION"
// if this.PercentComplete() == 100 {
// status = "COMPLETED"
// }
// s += "STATUS:" + status + "\n"
// }
//
// s += encodeDateProperty("CREATED", this.CreatedDate()) + "\n"
// s += "UID:" + this.Id() + "\n"
// s += "SUMMARY:" + escapeTextType(this.Summary()) + "\n"
// if this.PercentComplete() == 100 && !this.CompletedDate().IsZero() {
// s += encodeDateProperty("COMPLETED", this.CompletedDate()) + "\n"
// }
// s += encodeDateProperty("DTSTAMP", this.ModifiedDate()) + "\n"
// if this.Priority() != 0 {
// s += "PRIORITY:" + strconv.Itoa(this.Priority()) + "\n"
// }
// if this.PercentComplete() != 0 {
// s += "PERCENT-COMPLETE:" + strconv.Itoa(this.PercentComplete()) + "\n"
// }
// if target == "macTodo" {
// s += "SEQUENCE:" + strconv.Itoa(this.Sequence()) + "\n"
// }
// if this.Description() != "" {
// s += "DESCRIPTION:" + encodeTextType(this.Description()) + "\n"
// }
//
// s += "END:VTODO\n"
//
// return s
// }
//
// func encodeDateProperty(name string, t time.Time) string {
// var output string
// zone, _ := t.Zone()
// if zone != "UTC" && zone != "" {
// output = ";TZID=" + zone + ":" + t.Format("20060102T150405")
// } else {
// output = ":" + t.Format("20060102T150405") + "Z"
// }
// return name + output
// }
//
//
// func encodeTextType(s string) string {
// output := ""
// s = escapeTextType(s)
// lineLength := 0
// for _, c := range s {
// if lineLength + len(string(c)) > 75 {
// output += "\n "
// lineLength = 1
// }
// output += string(c)
// lineLength += len(string(c))
// }
// return output
// }

2
vendor/github.com/samedi/caldav-go/.gitignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
test-data/
vendor

69
vendor/github.com/samedi/caldav-go/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,69 @@
# CHANGELOG
v3.0.0
-----------
2017-08-01 Daniel Ferraz <d.ferrazm@gmail.com>
Main change:
Add two ways to get resources from the storage: shallow or not.
`data.GetShallowResource`: means that, if it's a collection resource, it will not include its child VEVENTs in the ICS data.
This is used throughout the palces where children don't matter.
`data.GetResource`: means that the child VEVENTs will be included in the returned ICS content data for collection resources.
This is used when sending a GET request to fetch a specific resource and expecting its full ICS data in response.
Other changes:
* Removed the need to pass the useless `writer http.ResponseWriter` parameter when calling the `caldav.HandleRequest` function.
* Added a `caldav.HandleRequestWithStorage` function that makes it easy to pass a custom storage to be used and handle the request with a single function call.
v2.0.0
-----------
2017-05-10 Daniel Ferraz <d.ferrazm@gmail.com>
All commits squashed and LICENSE updated to release as OSS in github.
Feature-wise it remains the same.
v1.0.1
-----------
2017-01-25 Daniel Ferraz <d.ferrazm@gmail.com>
Escape the contents in `<calendar-data>` and `<displayname>` in the `multistatus` XML responses. Fixing possible bugs
related to having special characters (e.g. &) in the XML multistatus responses that would possibly break the encoding.
v1.0.0
-----------
2017-01-18 Daniel Ferraz <d.ferrazm@gmail.com>
Main feature:
* Handles the `Prefer` header on PROPFIND and REPORT requests (defined in this [draft/proposal](https://tools.ietf.org/html/draft-murchison-webdav-prefer-05)). Useful to shrink down possible big and verbose responses when the client demands. Ex: current iOS calendar client uses this feature on its PROPFIND requests.
Other changes:
* Added the `handlers.Response` to allow clients of the lib to interact with the generated response before being written/sent back to the client.
* Added `GetResourcesByFilters` to the storage interface to allow filtering of resources in the storage level. Useful to provide an already filtered and smaller resource collection to a the REPORT handler when dealing with a filtered REPORT request.
* Added `GetResourcesByList` to the storage interface to fetch a set a of resources based on a set of paths. Useful to provide, in one call, the correct resource collection to the REPORT handler when dealing with a REPORT request for specific `hrefs`.
* Remove useless `IsResourcePresent` from the storage interface.
v0.1.0
-----------
2016-09-23 Daniel Ferraz <d.ferrazm@gmail.com>
This version implements:
* Allow: "GET, HEAD, PUT, DELETE, OPTIONS, PROPFIND, REPORT"
* DAV: "1, 3, calendar-access"
* Also only handles the following components: `VCALENDAR`, `VEVENT`
Currently unsupported:
* Components `VTODO`, `VJOURNAL`, `VFREEBUSY`
* `VEVENT` recurrences
* Resource locking
* User authentication

20
vendor/github.com/samedi/caldav-go/LICENSE generated vendored Normal file
View File

@ -0,0 +1,20 @@
Copyright 2017 samedi GmbH
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

197
vendor/github.com/samedi/caldav-go/README.md generated vendored Normal file
View File

@ -0,0 +1,197 @@
# go CalDAV
This is a Go lib that aims to implement the CalDAV specification ([RFC4791]). It allows the quick implementation of a CalDAV server in Go. Basically, it provides the request handlers that will handle the several CalDAV HTTP requests, fetch the appropriate resources, build and return the responses.
### How to install
```
go get github.com/samedi/caldav-go
```
### Dependencies
For dependency management, `glide` is used.
```bash
# install glide (once!)
curl https://glide.sh/get | sh
# install dependencies
glide install
```
### How to use it
The easiest way to quickly implement a CalDAV server is by just using the lib's request handler. Example:
```go
package mycaldav
import (
"net/http"
"github.com/samedi/caldav-go"
)
func runServer() {
http.HandleFunc(PATH, caldav.RequestHandler)
http.ListenAndServe(PORT, nil)
}
```
With that, all the HTTP requests (GET, PUT, REPORT, PROPFIND, etc) will be handled and responded by the `caldav` handler. In case of any HTTP methods not supported by the lib, a `501 Not Implemented` response will be returned.
In case you want more flexibility to handle the requests, e.g., if you wanted to access the generated response before being sent back to the caller, you could do like:
```go
package mycaldav
import (
"net/http"
"github.com/samedi/caldav-go"
)
func runServer() {
http.HandleFunc(PATH, myHandler)
http.ListenAndServe(PORT, nil)
}
func myHandler(writer http.ResponseWriter, request *http.Request) {
response := caldav.HandleRequest(request)
// ... do something with the response object before writing it back to the client ...
response.Write(writer)
}
```
### Storage & Resources
The storage is where the CalDAV resources are stored. To interact with that, the caldav lib needs only a type that conforms with the `data.Storage` interface to operate on top of the storage. Basically, this interface defines all the CRUD functions to work on top of the resources. With that, resources can be stored anywhere: in the filesystem, in the cloud, database, etc. As long as the used storage implements all the required storage interface functions, the caldav lib will work fine.
For example, we could use the following dummy read-only storage implementation, which returns dummy hard-coded resources:
```go
type DummyStorage struct{
resources map[string]string{
"/foo": `BEGING:VCALENDAR\nBEGIN:VEVENT\nDTSTART:20160914T170000\nEND:VEVENT\nEND:VCALENDAR`,
"/bar": `BEGING:VCALENDAR\nBEGIN:VEVENT\nDTSTART:20160915T180000\nEND:VEVENT\nEND:VCALENDAR`,
"/baz": `BEGING:VCALENDAR\nBEGIN:VEVENT\nDTSTART:20160916T190000\nEND:VEVENT\nEND:VCALENDAR`,
}
}
func (d *DummyStorage) GetResources(rpath string, withChildren bool) ([]Resource, error) {
return d.GetResourcesByList([]string{rpath})
}
func (d *DummyStorage) GetResourcesByFilters(rpath string, filters *ResourceFilter) ([]Resource, error) {
return nil, errors.New("filters are not supported")
}
func (d *DummyStorage) GetResourcesByList(rpaths []string) ([]Resource, error) {
result := []Resource{}
for _, rpath := range rpaths {
resource, found, _ := d.GetResource(rpath)
if found {
result = append(result, resource)
}
}
return result, nil
}
func (d *DummyStorage) GetResource(rpath string) (*Resource, bool, error) {
return d.GetShallowResource(rpath)
}
func (d *DummyStorage) GetShallowResource(rpath string) (*Resource, bool, error) {
result := []Resource{}
resContent := d.resources[rpath]
if resContent != "" {
resource = NewResource(rpath, DummyResourceAdapter{rpath, resContent})
return &resource, true, nil
}
return nil, false, nil
}
func (d *DummyStorage) CreateResource(rpath, content string) (*Resource, error) {
return nil, errors.New("creating resources are not supported")
}
func (d *DummyStorage) UpdateResource(rpath, content string) (*Resource, error) {
return nil, errors.New("updating resources are not supported")
}
func (d *DummyStorage) DeleteResource(rpath string) error {
return nil, errors.New("deleting resources are not supported")
}
```
Normally, when you provide your own storage implementation, you will need to provide also a custom `data.ResourceAdapter` interface implementation.
The resource adapter deals with the specificities of how resources are stored, which formats and how to deal with them. For example,
for file resources, the resources contents are the content read from the file itself, for resources in the cloud, it could be in JSON needing
some additional processing to parse the content, etc.
In our example here, we could say that the adapter for this case would be:
```go
type DummyResourceAdapter struct {
resourcePath string
resourceData string
}
func (a *DummyResourceAdapter) IsCollection() bool {
return false
}
func (a *DummyResourceAdapter) GetContent() string {
return a.resourceData
}
func (a *DummyResourceAdapter) GetContentSize() int64 {
return len(a.GetContent())
}
func (a *DummyResourceAdapter) CalculateEtag() string {
return hashify(a.GetContent())
}
func (a *DummyResourceAdapter) GetModTime() time.Time {
return time.Now()
}
```
Note that this adapter implementation is passed over whenever we initialize a new `Resource` instance in the storage implementation.
Then we just need to tell the caldav lib to use our dummy storage:
```go
dummyStg := new(DummyStorage)
caldav.SetupStorage(dummyStg)
```
All the CRUD operations on resources will then be forwarded to our dummy storage.
The default storage used (if none is explicitly set) is the `data.FileStorage` which deals with resources as files in the File System.
The resources can be of two types: collection and non-collection. A collection resource is basically a resource that has children resources, but does not have any data content. A non-collection resource is a resource that does not have children, but has data. In the case of a file storage, collections correspond to directories and non-collection to plain files. The data of a caldav resource is all the info that shows up in the calendar client, in the [iCalendar](https://en.wikipedia.org/wiki/ICalendar) format.
### Features
Please check the **CHANGELOG** to see specific features that are currently implemented.
### Contributing and testing
Everyone is welcome to contribute. Please raise an issue or pull request accordingly.
To run the tests:
```
./test.sh
```
### License
MIT License.
[RFC4791]: https://tools.ietf.org/html/rfc4791

24
vendor/github.com/samedi/caldav-go/config.go generated vendored Normal file
View File

@ -0,0 +1,24 @@
package caldav
import (
"github.com/samedi/caldav-go/data"
"github.com/samedi/caldav-go/global"
)
// SetupStorage sets the storage to be used by the server. The storage is where the resources data will be fetched from.
// You can provide a custom storage for your own purposes (which might be looking for data in the cloud, DB, etc).
// Just make sure it implements the `data.Storage` interface.
func SetupStorage(stg data.Storage) {
global.Storage = stg
}
// SetupUser sets the current user which is currently interacting with the calendar.
// It is used, for example, in some of the CALDAV responses, when rendering the path where to find the user's resources.
func SetupUser(username string) {
global.User = &data.CalUser{Name: username}
}
// SetupSupportedComponents sets all components which are supported by this storage implementation.
func SetupSupportedComponents(components []string) {
global.SupportedComponents = components
}

363
vendor/github.com/samedi/caldav-go/data/filters.go generated vendored Normal file
View File

@ -0,0 +1,363 @@
package data
import (
"errors"
"github.com/beevik/etree"
"log"
"strings"
"time"
"github.com/samedi/caldav-go/lib"
)
const (
TAG_FILTER = "filter"
TAG_COMP_FILTER = "comp-filter"
TAG_PROP_FILTER = "prop-filter"
TAG_PARAM_FILTER = "param-filter"
TAG_TIME_RANGE = "time-range"
TAG_TEXT_MATCH = "text-match"
TAG_IS_NOT_DEFINED = "is-not-defined"
// From the RFC, the time range `start` and `end` attributes MUST be in UTC and in this specific format
FILTER_TIME_FORMAT = "20060102T150405Z"
)
// ResourceFilter represents filters to filter out resources.
// Filters are basically a set of rules used to retrieve a range of resources.
// It is used primarily on REPORT requests and is described in details in RFC4791#7.8.
type ResourceFilter struct {
name string
text string
attrs map[string]string
children []ResourceFilter // collection of child filters.
etreeElem *etree.Element // holds the parsed XML node/tag as an `etree` element.
}
// ParseResourceFilters initializes a new `ResourceFilter` object from a snippet of XML string.
func ParseResourceFilters(xml string) (*ResourceFilter, error) {
doc := etree.NewDocument()
if err := doc.ReadFromString(xml); err != nil {
log.Printf("ERROR: Could not parse filter from XML string. XML:\n%s", xml)
return new(ResourceFilter), err
}
// Right now we're searching for a <filter> tag to initialize the filter struct from it.
// It SHOULD be a valid XML CALDAV:filter tag (RFC4791#9.7). We're not checking namespaces yet.
// TODO: check for XML namespaces and restrict it to accept only CALDAV:filter tag.
elem := doc.FindElement("//" + TAG_FILTER)
if elem == nil {
log.Printf("WARNING: The filter XML should contain a <%s> element. XML:\n%s", TAG_FILTER, xml)
return new(ResourceFilter), errors.New("invalid XML filter")
}
filter := newFilterFromEtreeElem(elem)
return &filter, nil
}
func newFilterFromEtreeElem(elem *etree.Element) ResourceFilter {
// init filter from etree element
filter := ResourceFilter{
name: elem.Tag,
text: strings.TrimSpace(elem.Text()),
etreeElem: elem,
attrs: make(map[string]string),
}
// set attributes
for _, attr := range elem.Attr {
filter.attrs[attr.Key] = attr.Value
}
return filter
}
// Attr searches an attribute by its name in the list of filter attributes and returns it.
func (f *ResourceFilter) Attr(attrName string) string {
return f.attrs[attrName]
}
// TimeAttr searches and returns a filter attribute as a `time.Time` object.
func (f *ResourceFilter) TimeAttr(attrName string) *time.Time {
t, err := time.Parse(FILTER_TIME_FORMAT, f.attrs[attrName])
if err != nil {
return nil
}
return &t
}
// GetTimeRangeFilter checks if the current filter has a child "time-range" filter and
// returns it (wrapped in a `ResourceFilter` type). It returns nil if the current filter does
// not contain any "time-range" filter.
func (f *ResourceFilter) GetTimeRangeFilter() *ResourceFilter {
return f.findChild(TAG_TIME_RANGE, true)
}
// Match returns whether a provided resource matches the filters.
func (f *ResourceFilter) Match(target ResourceInterface) bool {
if f.name == TAG_FILTER {
return f.rootFilterMatch(target)
}
return false
}
func (f *ResourceFilter) rootFilterMatch(target ResourceInterface) bool {
if f.isEmpty() {
return false
}
return f.rootChildrenMatch(target)
}
// checks if all the root's child filters match the target resource
func (f *ResourceFilter) rootChildrenMatch(target ResourceInterface) bool {
scope := []string{}
for _, child := range f.getChildren() {
// root filters only accept comp filters as children
if child.name != TAG_COMP_FILTER || !child.compMatch(target, scope) {
return false
}
}
return true
}
// See RFC4791-9.7.1.
func (f *ResourceFilter) compMatch(target ResourceInterface, scope []string) bool {
targetComp := target.ComponentName()
compName := f.attrs["name"]
if f.isEmpty() {
// Point #1 of RFC4791#9.7.1
return compName == targetComp
} else if f.contains(TAG_IS_NOT_DEFINED) {
// Point #2 of RFC4791#9.7.1
return compName != targetComp
} else {
// check each child of the current filter if they all match.
childrenScope := append(scope, compName)
return f.compChildrenMatch(target, childrenScope)
}
}
// checks if all the comp's child filters match the target resource
func (f *ResourceFilter) compChildrenMatch(target ResourceInterface, scope []string) bool {
for _, child := range f.getChildren() {
var match bool
switch child.name {
case TAG_TIME_RANGE:
// Point #3 of RFC4791#9.7.1
match = child.timeRangeMatch(target)
case TAG_PROP_FILTER:
// Point #4 of RFC4791#9.7.1
match = child.propMatch(target, scope)
case TAG_COMP_FILTER:
// Point #4 of RFC4791#9.7.1
match = child.compMatch(target, scope)
}
if !match {
return false
}
}
return true
}
// See RFC4791-9.9
func (f *ResourceFilter) timeRangeMatch(target ResourceInterface) bool {
startAttr := f.attrs["start"]
endAttr := f.attrs["end"]
// at least one of the two MUST be present
if startAttr == "" && endAttr == "" {
// if both of them are missing, return false
return false
} else if startAttr == "" {
// if missing only the `start`, set it open ended to the left
startAttr = "00010101T000000Z"
} else if endAttr == "" {
// if missing only the `end`, set it open ended to the right
endAttr = "99991231T235959Z"
}
// The logic below is only applicable for VEVENT components. So
// we return false if the resource is not a VEVENT component.
if target.ComponentName() != lib.VEVENT {
return false
}
rangeStart, err := time.Parse(FILTER_TIME_FORMAT, startAttr)
if err != nil {
log.Printf("ERROR: Could not parse start time in time-range filter.\nError: %s.\nStart attr: %s", err, startAttr)
return false
}
rangeEnd, err := time.Parse(FILTER_TIME_FORMAT, endAttr)
if err != nil {
log.Printf("ERROR: Could not parse end time in time-range filter.\nError: %s.\nEnd attr: %s", err, endAttr)
return false
}
// the following logic is inferred from the rules table for VEVENT components,
// described in RFC4791-9.9.
overlapRange := func(dtStart, dtEnd, rangeStart, rangeEnd time.Time) bool {
if dtStart.Equal(dtEnd) {
// Lines 3 and 4 of the table deal when the DTSTART and DTEND dates are equals.
// In this case we use the rule: (start <= DTSTART && end > DTSTART)
return (rangeStart.Before(dtStart) || rangeStart.Equal(dtStart)) && rangeEnd.After(dtStart)
} else {
// Lines 1, 2 and 6 of the table deal when the DTSTART and DTEND dates are different.
// In this case we use the rule: (start < DTEND && end > DTSTART)
return rangeStart.Before(dtEnd) && rangeEnd.After(dtStart)
}
}
// first we check each of the target recurrences (if any).
for _, recurrence := range target.Recurrences() {
// if any of them overlap the filter range, we return true right away
if overlapRange(recurrence.StartTime, recurrence.EndTime, rangeStart, rangeEnd) {
return true
}
}
// if none of the recurrences match, we just return if the actual
// resource's `start` and `end` times match the filter range
return overlapRange(target.StartTimeUTC(), target.EndTimeUTC(), rangeStart, rangeEnd)
}
// See RFC4791-9.7.2.
func (f *ResourceFilter) propMatch(target ResourceInterface, scope []string) bool {
propName := f.attrs["name"]
propPath := append(scope, propName)
if f.isEmpty() {
// Point #1 of RFC4791#9.7.2
return target.HasProperty(propPath...)
} else if f.contains(TAG_IS_NOT_DEFINED) {
// Point #2 of RFC4791#9.7.2
return !target.HasProperty(propPath...)
} else {
// check each child of the current filter if they all match.
return f.propChildrenMatch(target, propPath)
}
}
// checks if all the prop's child filters match the target resource
func (f *ResourceFilter) propChildrenMatch(target ResourceInterface, propPath []string) bool {
for _, child := range f.getChildren() {
var match bool
switch child.name {
case TAG_TIME_RANGE:
// Point #3 of RFC4791#9.7.2
// TODO: this point is not very clear on how to match time range against properties.
// So we're returning `false` in the meantime.
match = false
case TAG_TEXT_MATCH:
// Point #4 of RFC4791#9.7.2
propText := target.GetPropertyValue(propPath...)
match = child.textMatch(propText)
case TAG_PARAM_FILTER:
// Point #4 of RFC4791#9.7.2
match = child.paramMatch(target, propPath)
}
if !match {
return false
}
}
return true
}
// See RFC4791-9.7.3
func (f *ResourceFilter) paramMatch(target ResourceInterface, parentPropPath []string) bool {
paramName := f.attrs["name"]
paramPath := append(parentPropPath, paramName)
if f.isEmpty() {
// Point #1 of RFC4791#9.7.3
return target.HasPropertyParam(paramPath...)
} else if f.contains(TAG_IS_NOT_DEFINED) {
// Point #2 of RFC4791#9.7.3
return !target.HasPropertyParam(paramPath...)
} else {
child := f.getChildren()[0]
// param filters can also have (only-one) nested text-match filter
if child.name == TAG_TEXT_MATCH {
paramValue := target.GetPropertyParamValue(paramPath...)
return child.textMatch(paramValue)
}
}
return false
}
// See RFC4791-9.7.5
func (f *ResourceFilter) textMatch(targetText string) bool {
// TODO: collations are not being considered/supported yet.
// Texts are lowered to be case-insensitive, almost as the "i;ascii-casemap" value.
targetText = strings.ToLower(targetText)
expectedSubstr := strings.ToLower(f.text)
match := strings.Contains(targetText, expectedSubstr)
if f.attrs["negate-condition"] == "yes" {
return !match
}
return match
}
func (f *ResourceFilter) isEmpty() bool {
return len(f.getChildren()) == 0 && f.text == ""
}
func (f *ResourceFilter) contains(filterName string) bool {
if f.findChild(filterName, false) != nil {
return true
}
return false
}
func (f *ResourceFilter) findChild(filterName string, dig bool) *ResourceFilter {
for _, child := range f.getChildren() {
if child.name == filterName {
return &child
}
if !dig {
continue
}
dugChild := child.findChild(filterName, true)
if dugChild != nil {
return dugChild
}
}
return nil
}
// lazy evaluation of the child filters
func (f *ResourceFilter) getChildren() []ResourceFilter {
if f.children == nil {
f.children = []ResourceFilter{}
for _, childElem := range f.etreeElem.ChildElements() {
childFilter := newFilterFromEtreeElem(childElem)
f.children = append(f.children, childFilter)
}
}
return f.children
}

370
vendor/github.com/samedi/caldav-go/data/resource.go generated vendored Normal file
View File

@ -0,0 +1,370 @@
package data
import (
"fmt"
"github.com/laurent22/ical-go/ical"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
"time"
"github.com/samedi/caldav-go/files"
"github.com/samedi/caldav-go/lib"
)
// ResourceInterface defines the main interface of a CalDAV resource object. This
// interface exists only to define the common resource operation and should not be custom-implemented.
// The default and canonical implementation is provided by `data.Resource`, convering all the commonalities.
// Any specifics in implementations should be handled by the `data.ResourceAdapter`.
type ResourceInterface interface {
ComponentName() string
StartTimeUTC() time.Time
EndTimeUTC() time.Time
Recurrences() []ResourceRecurrence
HasProperty(propPath ...string) bool
GetPropertyValue(propPath ...string) string
HasPropertyParam(paramName ...string) bool
GetPropertyParamValue(paramName ...string) string
}
// ResourceAdapter serves as the object to abstract all the specicities in different resources implementations.
// For example, the way to tell whether a resource is a collection or how to read its content differentiates
// on resources stored in the file system, coming from a relational DB or from the cloud as JSON. These differentiations
// should be covered by providing a specific implementation of the `ResourceAdapter` interface. So, depending on the current
// resource storage strategy, a matching resource adapter implementation should be provided whenever a new resource is initialized.
type ResourceAdapter interface {
IsCollection() bool
CalculateEtag() string
GetContent() string
GetContentSize() int64
GetModTime() time.Time
}
// ResourceRecurrence represents a recurrence for a resource.
// NOTE: recurrences are not supported yet.
type ResourceRecurrence struct {
StartTime time.Time
EndTime time.Time
}
// Resource represents the CalDAV resource. Basically, it has a name it's accessible based on path.
// A resource can be a collection, meaning it doesn't have any data content, but it has child resources.
// A non-collection is the actual resource which has the data in iCal format and which will feed the calendar.
// When visualizing the whole resources set in a tree representation, the collection resource would be the inner nodes and
// the non-collection would be the leaves.
type Resource struct {
Name string
Path string
pathSplit []string
adapter ResourceAdapter
emptyTime time.Time
}
// NewResource initializes a new `Resource` instance based on its path and the `ResourceAdapter` implementation to be used.
func NewResource(rawPath string, adp ResourceAdapter) Resource {
pClean := lib.ToSlashPath(rawPath)
pSplit := strings.Split(strings.Trim(pClean, "/"), "/")
return Resource{
Name: pSplit[len(pSplit)-1],
Path: pClean,
pathSplit: pSplit,
adapter: adp,
}
}
// IsCollection tells whether a resource is a collection or not.
func (r *Resource) IsCollection() bool {
return r.adapter.IsCollection()
}
// IsPrincipal tells whether a resource is the principal resource or not.
// A principal resource means it's a root resource.
func (r *Resource) IsPrincipal() bool {
return len(r.pathSplit) <= 1
}
// ComponentName returns the type of the resource. VCALENDAR for collection resources, VEVENT otherwise.
func (r *Resource) ComponentName() string {
if r.IsCollection() {
return lib.VCALENDAR
}
return lib.VEVENT
}
// StartTimeUTC returns the start time in UTC of a VEVENT resource.
func (r *Resource) StartTimeUTC() time.Time {
vevent := r.icalVEVENT()
dtstart := vevent.PropDate(ical.DTSTART, r.emptyTime)
if dtstart == r.emptyTime {
log.Printf("WARNING: The property DTSTART was not found in the resource's ical data.\nResource path: %s", r.Path)
return r.emptyTime
}
return dtstart.UTC()
}
// EndTimeUTC returns the end time in UTC of a VEVENT resource.
func (r *Resource) EndTimeUTC() time.Time {
vevent := r.icalVEVENT()
dtend := vevent.PropDate(ical.DTEND, r.emptyTime)
// when the DTEND property is not present, we just add the DURATION (if any) to the DTSTART
if dtend == r.emptyTime {
duration := vevent.PropDuration(ical.DURATION)
dtend = r.StartTimeUTC().Add(duration)
}
return dtend.UTC()
}
// Recurrences returns an array of resource recurrences.
// NOTE: Recurrences are not supported yet. An empty array will always be returned.
func (r *Resource) Recurrences() []ResourceRecurrence {
// TODO: Implement. This server does not support iCal recurrences yet. We just return an empty array.
return []ResourceRecurrence{}
}
// HasProperty tells whether the resource has the provided property in its iCal content.
// The path to the property should be provided in case of nested properties.
// Example, suppose the resource has this content:
//
// BEGIN:VCALENDAR
// BEGIN:VEVENT
// DTSTART:20160914T170000
// END:VEVENT
// END:VCALENDAR
//
// HasProperty("VEVENT", "DTSTART") => returns true
// HasProperty("VEVENT", "DTEND") => returns false
func (r *Resource) HasProperty(propPath ...string) bool {
return r.GetPropertyValue(propPath...) != ""
}
// GetPropertyValue gets a property value from the resource's iCal content.
// The path to the property should be provided in case of nested properties.
// Example, suppose the resource has this content:
//
// BEGIN:VCALENDAR
// BEGIN:VEVENT
// DTSTART:20160914T170000
// END:VEVENT
// END:VCALENDAR
//
// GetPropertyValue("VEVENT", "DTSTART") => returns "20160914T170000"
// GetPropertyValue("VEVENT", "DTEND") => returns ""
func (r *Resource) GetPropertyValue(propPath ...string) string {
if propPath[0] == ical.VCALENDAR {
propPath = propPath[1:]
}
prop, _ := r.icalendar().DigProperty(propPath...)
return prop
}
// HasPropertyParam tells whether the resource has the provided property param in its iCal content.
// The path to the param should be provided in case of nested params.
// Example, suppose the resource has this content:
//
// BEGIN:VCALENDAR
// BEGIN:VEVENT
// ATTENDEE;PARTSTAT=NEEDS-ACTION:FOO
// END:VEVENT
// END:VCALENDAR
//
// HasPropertyParam("VEVENT", "ATTENDEE", "PARTSTAT") => returns true
// HasPropertyParam("VEVENT", "ATTENDEE", "OTHER") => returns false
func (r *Resource) HasPropertyParam(paramPath ...string) bool {
return r.GetPropertyParamValue(paramPath...) != ""
}
// GetPropertyParamValue gets a property param value from the resource's iCal content.
// The path to the param should be provided in case of nested params.
// Example, suppose the resource has this content:
//
// BEGIN:VCALENDAR
// BEGIN:VEVENT
// ATTENDEE;PARTSTAT=NEEDS-ACTION:FOO
// END:VEVENT
// END:VCALENDAR
//
// GetPropertyParamValue("VEVENT", "ATTENDEE", "PARTSTAT") => returns "NEEDS-ACTION"
// GetPropertyParamValue("VEVENT", "ATTENDEE", "OTHER") => returns ""
func (r *Resource) GetPropertyParamValue(paramPath ...string) string {
if paramPath[0] == ical.VCALENDAR {
paramPath = paramPath[1:]
}
param, _ := r.icalendar().DigParameter(paramPath...)
return param
}
// GetEtag returns the ETag of the resource and a flag saying if the ETag is present.
// For collection resource, it returns an empty string and false.
func (r *Resource) GetEtag() (string, bool) {
if r.IsCollection() {
return "", false
}
return r.adapter.CalculateEtag(), true
}
// GetContentType returns the type of the content of the resource.
// Collection resources are "text/calendar". Non-collection resources are "text/calendar; component=vcalendar".
func (r *Resource) GetContentType() (string, bool) {
if r.IsCollection() {
return "text/calendar", true
}
return "text/calendar; component=vcalendar", true
}
// GetDisplayName returns the name/identifier of the resource.
func (r *Resource) GetDisplayName() (string, bool) {
return r.Name, true
}
// GetContentData reads and returns the raw content of the resource as string and flag saying if the content was found.
// If the resource does not have content (like collection resource), it returns an empty string and false.
func (r *Resource) GetContentData() (string, bool) {
data := r.adapter.GetContent()
found := data != ""
return data, found
}
// GetContentLength returns the length of the resource's content and flag saying if the length is present.
// If the resource does not have content (like collection resource), it returns an empty string and false.
func (r *Resource) GetContentLength() (string, bool) {
// If its collection, it does not have any content, so mark it as not found
if r.IsCollection() {
return "", false
}
contentSize := r.adapter.GetContentSize()
return strconv.FormatInt(contentSize, 10), true
}
// GetLastModified returns the last time the resource was modified. The returned time
// is returned formatted in the provided `format`.
func (r *Resource) GetLastModified(format string) (string, bool) {
return r.adapter.GetModTime().Format(format), true
}
// GetOwner returns the owner of the resource. This is usually the principal resource associated (the root resource).
// If the resource does not have a owner (for example it's a principal resource alread), it returns an empty string.
func (r *Resource) GetOwner() (string, bool) {
var owner string
if len(r.pathSplit) > 1 {
owner = r.pathSplit[0]
} else {
owner = ""
}
return owner, true
}
// GetOwnerPath returns the path to this resource's owner, or an empty string when the resource does not have any owner.
func (r *Resource) GetOwnerPath() (string, bool) {
owner, _ := r.GetOwner()
if owner != "" {
return fmt.Sprintf("/%s/", owner), true
}
return "", false
}
// TODO: memoize
func (r *Resource) icalVEVENT() *ical.Node {
vevent := r.icalendar().ChildByName(ical.VEVENT)
// if nil, log it and return an empty vevent
if vevent == nil {
log.Printf("WARNING: The resource's ical data is missing the VEVENT property.\nResource path: %s", r.Path)
return &ical.Node{
Name: ical.VEVENT,
}
}
return vevent
}
// TODO: memoize
func (r *Resource) icalendar() *ical.Node {
data, found := r.GetContentData()
if !found {
log.Printf("WARNING: The resource's ical data does not have any data.\nResource path: %s", r.Path)
return &ical.Node{
Name: ical.VCALENDAR,
}
}
icalNode, err := ical.ParseCalendar(data)
if err != nil {
log.Printf("ERROR: Could not parse the resource's ical data.\nError: %s.\nResource path: %s", err, r.Path)
return &ical.Node{
Name: ical.VCALENDAR,
}
}
return icalNode
}
// FileResourceAdapter implements the `ResourceAdapter` for resources stored as files in the file system.
type FileResourceAdapter struct {
finfo os.FileInfo
resourcePath string
}
// IsCollection tells whether the file resource is a directory or not.
func (adp *FileResourceAdapter) IsCollection() bool {
return adp.finfo.IsDir()
}
// GetContent reads the file content and returns it as string. For collection resources (directories), it
// returns an empty string.
func (adp *FileResourceAdapter) GetContent() string {
if adp.IsCollection() {
return ""
}
data, err := ioutil.ReadFile(files.AbsPath(adp.resourcePath))
if err != nil {
log.Printf("ERROR: Could not read file content for the resource.\nError: %s.\nResource path: %s.", err, adp.resourcePath)
return ""
}
return string(data)
}
// GetContentSize returns the content length.
func (adp *FileResourceAdapter) GetContentSize() int64 {
return adp.finfo.Size()
}
// CalculateEtag calculates an ETag based on the file current modification status and returns it.
func (adp *FileResourceAdapter) CalculateEtag() string {
// returns ETag as the concatenated hex values of a file's
// modification time and size. This is not a reliable synchronization
// mechanism for directories, so for collections we return empty.
if adp.IsCollection() {
return ""
}
fi := adp.finfo
return fmt.Sprintf(`"%x%x"`, fi.ModTime().UnixNano(), fi.Size())
}
// GetModTime returns the time when the file was last modified.
func (adp *FileResourceAdapter) GetModTime() time.Time {
return adp.finfo.ModTime()
}

229
vendor/github.com/samedi/caldav-go/data/storage.go generated vendored Normal file
View File

@ -0,0 +1,229 @@
package data
import (
"github.com/samedi/caldav-go/errs"
"github.com/samedi/caldav-go/files"
"io/ioutil"
"log"
"os"
)
// Storage is the inteface responsible for the CRUD operations on the CalDAV resources. It represents
// where the resources should be fetched from and the various operations which can be performed on it.
// This is the interface one should implement in case it needs a custom storage strategy, like fetching
// data from the cloud, local DB, etc. After that, the custom storage implementation can be setup to be used
// in the server by passing the object instance to `caldav.SetupStorage`.
type Storage interface {
// GetResources gets a list of resources based on a given `rpath`. The
// `rpath` is the path to the original resource that's being requested. The resultant list
// will/must contain that original resource in it, apart from any additional resources. It also receives
// `withChildren` flag to say if the result must also include all the original resource`s
// children (if original is a collection resource). If `true`, the result will have the requested resource + children.
// If `false`, it will have only the requested original resource (from the `rpath` path).
// It returns errors if anything went wrong or if it could not find any resource on `rpath` path.
GetResources(rpath string, withChildren bool) ([]Resource, error)
// GetResourcesByList fetches a list of resources by path from the storage.
// This method fetches all the `rpaths` and return an array of the reosurces found.
// No error 404 will be returned if one of the resources cannot be found.
// Errors are returned if any errors other than "not found" happens.
GetResourcesByList(rpaths []string) ([]Resource, error)
// GetResourcesByFilters returns the filtered children of a target collection resource.
// The target collection resource is the one pointed by the `rpath` parameter. All of its children
// will be checked against a set of `filters` and the matching ones are returned. The results
// contains only the filtered children and does NOT include the target resource. If the target resource
// is not a collection, an empty array is returned as the result.
GetResourcesByFilters(rpath string, filters *ResourceFilter) ([]Resource, error)
// GetResource gets the requested resource based on a given `rpath` path. It returns the resource (if found) or
// nil (if not found). Also returns a flag specifying if the resource was found or not.
GetResource(rpath string) (*Resource, bool, error)
// GetShallowResource has the same behaviour of `storage.GetResource`. The only difference is that, for collection resources,
// it does not return its children in the collection `storage.Resource` struct (hence the name shallow). The motive is
// for optimizations reasons, as this function is used on places where the collection's children are not important.
GetShallowResource(rpath string) (*Resource, bool, error)
// CreateResource creates a new resource on the `rpath` path with a given `content`.
CreateResource(rpath, content string) (*Resource, error)
// UpdateResource udpates a resource on the `rpath` path with a given `content`.
UpdateResource(rpath, content string) (*Resource, error)
// DeleteResource deletes a resource on the `rpath` path.
DeleteResource(rpath string) error
}
// FileStorage is the storage that deals with resources as files in the file system. So, a collection resource
// is treated as a folder/directory and its children resources are the files it contains. Non-collection resources are just plain files.
// Each file represents then a CalAV resource and the data expects to contain the iCal data to feed the calendar events.
type FileStorage struct {
}
// GetResources get the file resources based on the `rpath`. See `Storage.GetResources` doc.
func (fs *FileStorage) GetResources(rpath string, withChildren bool) ([]Resource, error) {
result := []Resource{}
// tries to open the file by the given path
f, e := fs.openResourceFile(rpath, os.O_RDONLY)
if e != nil {
return nil, e
}
// add it as a resource to the result list
finfo, _ := f.Stat()
resource := NewResource(rpath, &FileResourceAdapter{finfo, rpath})
result = append(result, resource)
// if the file is a dir, add its children to the result list
if withChildren && finfo.IsDir() {
dirFiles, _ := f.Readdir(0)
for _, finfo := range dirFiles {
childPath := files.JoinPaths(rpath, finfo.Name())
resource = NewResource(childPath, &FileResourceAdapter{finfo, childPath})
result = append(result, resource)
}
}
return result, nil
}
// GetResourcesByFilters get the file resources based on the `rpath` and a set of filters. See `Storage.GetResourcesByFilters` doc.
func (fs *FileStorage) GetResourcesByFilters(rpath string, filters *ResourceFilter) ([]Resource, error) {
result := []Resource{}
childPaths := fs.getDirectoryChildPaths(rpath)
for _, path := range childPaths {
resource, _, err := fs.GetShallowResource(path)
if err != nil {
// if we can't find this resource, something weird went wrong, but not that serious, so we log it and continue
log.Printf("WARNING: returned error when trying to get resource with path %s from collection with path %s. Error: %s", path, rpath, err)
continue
}
// only add it if the resource matches the filters
if filters == nil || filters.Match(resource) {
result = append(result, *resource)
}
}
return result, nil
}
// GetResourcesByList get a list of file resources based on a list of `rpaths`. See `Storage.GetResourcesByList` doc.
func (fs *FileStorage) GetResourcesByList(rpaths []string) ([]Resource, error) {
results := []Resource{}
for _, rpath := range rpaths {
resource, found, err := fs.GetShallowResource(rpath)
if err != nil && err != errs.ResourceNotFoundError {
return nil, err
}
if found {
results = append(results, *resource)
}
}
return results, nil
}
// GetResource fetches and returns a single resource for a `rpath`. See `Storage.GetResoure` doc.
func (fs *FileStorage) GetResource(rpath string) (*Resource, bool, error) {
// For simplicity we just return the shallow resource.
return fs.GetShallowResource(rpath)
}
// GetShallowResource fetches and returns a single resource file/directory without any related children. See `Storage.GetShallowResource` doc.
func (fs *FileStorage) GetShallowResource(rpath string) (*Resource, bool, error) {
resources, err := fs.GetResources(rpath, false)
if err != nil {
return nil, false, err
}
if resources == nil || len(resources) == 0 {
return nil, false, errs.ResourceNotFoundError
}
res := resources[0]
return &res, true, nil
}
// CreateResource creates a file resource with the provided `content`. See `Storage.CreateResource` doc.
func (fs *FileStorage) CreateResource(rpath, content string) (*Resource, error) {
rAbsPath := files.AbsPath(rpath)
if fs.isResourcePresent(rAbsPath) {
return nil, errs.ResourceAlreadyExistsError
}
// create parent directories (if needed)
if err := os.MkdirAll(files.DirPath(rAbsPath), os.ModePerm); err != nil {
return nil, err
}
// create file/resource and write content
f, err := os.Create(rAbsPath)
if err != nil {
return nil, err
}
f.WriteString(content)
finfo, _ := f.Stat()
res := NewResource(rpath, &FileResourceAdapter{finfo, rpath})
return &res, nil
}
// UpdateResource updates a file resource with the provided `content`. See `Storage.UpdateResource` doc.
func (fs *FileStorage) UpdateResource(rpath, content string) (*Resource, error) {
f, e := fs.openResourceFile(rpath, os.O_RDWR)
if e != nil {
return nil, e
}
// update content
f.Truncate(0)
f.WriteString(content)
finfo, _ := f.Stat()
res := NewResource(rpath, &FileResourceAdapter{finfo, rpath})
return &res, nil
}
// DeleteResource deletes a file resource (and possibly all its children in case of a collection). See `Storage.DeleteResource` doc.
func (fs *FileStorage) DeleteResource(rpath string) error {
err := os.Remove(files.AbsPath(rpath))
return err
}
func (fs *FileStorage) isResourcePresent(rpath string) bool {
_, found, _ := fs.GetShallowResource(rpath)
return found
}
func (fs *FileStorage) openResourceFile(filepath string, mode int) (*os.File, error) {
f, e := os.OpenFile(files.AbsPath(filepath), mode, 0666)
if e != nil {
if os.IsNotExist(e) {
return nil, errs.ResourceNotFoundError
}
return nil, e
}
return f, nil
}
func (fs *FileStorage) getDirectoryChildPaths(dirpath string) []string {
content, err := ioutil.ReadDir(files.AbsPath(dirpath))
if err != nil {
log.Printf("ERROR: Could not read resource as file directory.\nError: %s.\nResource path: %s.", err, dirpath)
return nil
}
result := []string{}
for _, file := range content {
fpath := files.JoinPaths(dirpath, file.Name())
result = append(result, fpath)
}
return result
}

8
vendor/github.com/samedi/caldav-go/data/user.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
package data
// CalUser represents the calendar user. It is used, for example, to
// keep track globally what is the current user interacting with the calendar.
// This user data can be used in various places, including in some of the CALDAV responses.
type CalUser struct {
Name string
}

12
vendor/github.com/samedi/caldav-go/errs/errors.go generated vendored Normal file
View File

@ -0,0 +1,12 @@
package errs
import (
"errors"
)
var (
ResourceNotFoundError = errors.New("caldav: resource not found")
ResourceAlreadyExistsError = errors.New("caldav: resource already exists")
UnauthorizedError = errors.New("caldav: unauthorized. credentials needed.")
ForbiddenError = errors.New("caldav: forbidden operation.")
)

34
vendor/github.com/samedi/caldav-go/files/paths.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
package files
import (
"github.com/samedi/caldav-go/lib"
"path/filepath"
"strings"
)
const (
Separator = string(filepath.Separator)
)
// AbsPath converts the path into absolute path based on the current working directory.
func AbsPath(path string) string {
path = strings.Trim(path, "/")
absPath, _ := filepath.Abs(path)
return absPath
}
// DirPath returns all but the last element of path, typically the path's directory.
func DirPath(path string) string {
return filepath.Dir(path)
}
// JoinPaths joins two or more paths into a single path.
func JoinPaths(paths ...string) string {
return filepath.Join(paths...)
}
// ToSlashPath slashify the path, using '/' as separator.
func ToSlashPath(path string) string {
return lib.ToSlashPath(path)
}

8
vendor/github.com/samedi/caldav-go/glide.lock generated vendored Normal file
View File

@ -0,0 +1,8 @@
hash: ff037054c7b689c20a4fd4368897b8998d6ffa31b0f3f4a79e523a132e6a9e94
updated: 2018-04-04T08:38:45.523594-04:00
imports:
- name: github.com/beevik/etree
version: af219c0c7ea1b67ec263c0b1b1b96d284a9181ce
- name: github.com/laurent22/ical-go
version: e4fec34929693e2a4ba299d16380c55bac3fb42c
testImports: []

4
vendor/github.com/samedi/caldav-go/glide.yaml generated vendored Normal file
View File

@ -0,0 +1,4 @@
package: github.com/samedi/caldav-go
import:
- package: github.com/beevik/etree
- package: github.com/laurent22/ical-go

17
vendor/github.com/samedi/caldav-go/global/global.go generated vendored Normal file
View File

@ -0,0 +1,17 @@
// Package global defines the globally accessible variables in the caldav server
// and the interface to setup them.
package global
import (
"github.com/samedi/caldav-go/data"
"github.com/samedi/caldav-go/lib"
)
// Storage represents the global storage used in the CRUD operations of resources. Default storage is the `data.FileStorage`.
var Storage data.Storage = new(data.FileStorage)
// User defines the current caldav user, which is the user currently interacting with the calendar.
var User *data.CalUser
// SupportedComponents contains all components which are supported by the current storage implementation
var SupportedComponents = []string{lib.VCALENDAR, lib.VEVENT}

8
vendor/github.com/samedi/caldav-go/go.mod generated vendored Normal file
View File

@ -0,0 +1,8 @@
module github.com/samedi/caldav-go
go 1.12
require (
github.com/beevik/etree v0.0.0-20171015221209-af219c0c7ea1
github.com/laurent22/ical-go v0.0.0-20170824131750-e4fec3492969
)

29
vendor/github.com/samedi/caldav-go/handler.go generated vendored Normal file
View File

@ -0,0 +1,29 @@
package caldav
import (
"net/http"
"github.com/samedi/caldav-go/data"
"github.com/samedi/caldav-go/handlers"
)
// RequestHandler handles the given CALDAV request and writes the reponse righ away. This function is to be
// used by passing it directly as the handle func to the `http` lib. Example: http.HandleFunc("/", caldav.RequestHandler).
func RequestHandler(writer http.ResponseWriter, request *http.Request) {
response := HandleRequest(request)
response.Write(writer)
}
// HandleRequest handles the given CALDAV request and returns the response. Useful when the caller
// wants to do something else with the response before writing it to the response stream.
func HandleRequest(request *http.Request) *handlers.Response {
handler := handlers.NewHandler(request)
return handler.Handle()
}
// HandleRequestWithStorage handles the request the same way as `HandleRequest` does, but before,
// it sets the given storage that will be used throughout the request handling flow.
func HandleRequestWithStorage(request *http.Request, stg data.Storage) *handlers.Response {
SetupStorage(stg)
return HandleRequest(request)
}

36
vendor/github.com/samedi/caldav-go/handlers/builder.go generated vendored Normal file
View File

@ -0,0 +1,36 @@
package handlers
import (
"net/http"
)
// HandlerInterface represents a CalDAV request handler. It has only one function `Handle`,
// which is used to handle the CalDAV request and returns the response.
type HandlerInterface interface {
Handle() *Response
}
// NewHandler returns a new CalDAV request handler object based on the provided request.
// With the returned request handler, you can call `Handle()` to handle the request.
func NewHandler(request *http.Request) HandlerInterface {
response := NewResponse()
switch request.Method {
case "GET":
return getHandler{request, response, false}
case "HEAD":
return getHandler{request, response, true}
case "PUT":
return putHandler{request, response}
case "DELETE":
return deleteHandler{request, response}
case "PROPFIND":
return propfindHandler{request, response}
case "OPTIONS":
return optionsHandler{response}
case "REPORT":
return reportHandler{request, response}
default:
return notImplementedHandler{response}
}
}

40
vendor/github.com/samedi/caldav-go/handlers/delete.go generated vendored Normal file
View File

@ -0,0 +1,40 @@
package handlers
import (
"github.com/samedi/caldav-go/global"
"net/http"
)
type deleteHandler struct {
request *http.Request
response *Response
}
func (dh deleteHandler) Handle() *Response {
precond := requestPreconditions{dh.request}
// get the event from the storage
resource, _, err := global.Storage.GetShallowResource(dh.request.URL.Path)
if err != nil {
return dh.response.SetError(err)
}
// TODO: Handle delete on collections
if resource.IsCollection() {
return dh.response.Set(http.StatusMethodNotAllowed, "")
}
// check ETag pre-condition
resourceEtag, _ := resource.GetEtag()
if !precond.IfMatch(resourceEtag) {
return dh.response.Set(http.StatusPreconditionFailed, "")
}
// delete event after pre-condition passed
err = global.Storage.DeleteResource(resource.Path)
if err != nil {
return dh.response.SetError(err)
}
return dh.response.Set(http.StatusNoContent, "")
}

37
vendor/github.com/samedi/caldav-go/handlers/get.go generated vendored Normal file
View File

@ -0,0 +1,37 @@
package handlers
import (
"github.com/samedi/caldav-go/global"
"net/http"
)
type getHandler struct {
request *http.Request
response *Response
onlyHeaders bool
}
func (gh getHandler) Handle() *Response {
resource, _, err := global.Storage.GetResource(gh.request.URL.Path)
if err != nil {
return gh.response.SetError(err)
}
var response string
if gh.onlyHeaders {
response = ""
} else {
response, _ = resource.GetContentData()
}
etag, _ := resource.GetEtag()
lastm, _ := resource.GetLastModified(http.TimeFormat)
ctype, _ := resource.GetContentType()
gh.response.SetHeader("ETag", etag).
SetHeader("Last-Modified", lastm).
SetHeader("Content-Type", ctype).
Set(http.StatusOK, response)
return gh.response
}

27
vendor/github.com/samedi/caldav-go/handlers/headers.go generated vendored Normal file
View File

@ -0,0 +1,27 @@
package handlers
import (
"net/http"
)
const (
HD_DEPTH = "Depth"
HD_DEPTH_DEEP = "1"
HD_PREFER = "Prefer"
HD_PREFER_MINIMAL = "return=minimal"
HD_PREFERENCE_APPLIED = "Preference-Applied"
)
type headers struct {
http.Header
}
func (h headers) IsDeep() bool {
depth := h.Get(HD_DEPTH)
return (depth == HD_DEPTH_DEEP)
}
func (h headers) IsMinimal() bool {
prefer := h.Get(HD_PREFER)
return (prefer == HD_PREFER_MINIMAL)
}

View File

@ -0,0 +1,207 @@
package handlers
import (
"encoding/xml"
"fmt"
"github.com/samedi/caldav-go/data"
"github.com/samedi/caldav-go/global"
"github.com/samedi/caldav-go/ixml"
"github.com/samedi/caldav-go/lib"
"net/http"
)
// Wraps a multistatus response. It contains the set of `Responses`
// that will serve to build the final XML. Multistatus responses are
// used by the REPORT and PROPFIND methods.
type multistatusResp struct {
// The set of multistatus responses used to build each of the <DAV:response> nodes.
Responses []msResponse
// Flag that XML should be minimal or not
// [defined in the draft https://tools.ietf.org/html/draft-murchison-webdav-prefer-05]
Minimal bool
}
type msResponse struct {
Href string
Found bool
Propstats msPropstats
}
type msPropstats map[int]msProps
// Adds a msProp to the map with the key being the prop status.
func (stats msPropstats) Add(prop msProp) {
stats[prop.Status] = append(stats[prop.Status], prop)
}
func (stats msPropstats) Clone() msPropstats {
clone := make(msPropstats)
for k, v := range stats {
clone[k] = v
}
return clone
}
type msProps []msProp
type msProp struct {
Tag xml.Name
Content string
Contents []string
Status int
}
// Function that processes all the required props for a given resource.
// ## Params
// resource: the target calendar resource.
// reqprops: set of required props that must be processed for the resource.
// ## Returns
// The set of props (msProp) processed. Each prop is mapped to a HTTP status code.
// So if a prop is found and processed ok, it'll be mapped to 200. If it's not found,
// it'll be mapped to 404, and so on.
func (ms *multistatusResp) Propstats(resource *data.Resource, reqprops []xml.Name) msPropstats {
if resource == nil {
return nil
}
result := make(msPropstats)
for _, ptag := range reqprops {
pvalue := msProp{
Tag: ptag,
Status: http.StatusOK,
}
pfound := false
switch ptag {
case ixml.CALENDAR_DATA_TG:
pvalue.Content, pfound = resource.GetContentData()
if pfound {
pvalue.Content = ixml.EscapeText(pvalue.Content)
}
case ixml.GET_ETAG_TG:
pvalue.Content, pfound = resource.GetEtag()
case ixml.GET_CONTENT_TYPE_TG:
pvalue.Content, pfound = resource.GetContentType()
case ixml.GET_CONTENT_LENGTH_TG:
pvalue.Content, pfound = resource.GetContentLength()
case ixml.DISPLAY_NAME_TG:
pvalue.Content, pfound = resource.GetDisplayName()
if pfound {
pvalue.Content = ixml.EscapeText(pvalue.Content)
}
case ixml.GET_LAST_MODIFIED_TG:
pvalue.Content, pfound = resource.GetLastModified(http.TimeFormat)
case ixml.OWNER_TG:
pvalue.Content, pfound = resource.GetOwnerPath()
case ixml.GET_CTAG_TG:
pvalue.Content, pfound = resource.GetEtag()
case ixml.PRINCIPAL_URL_TG,
ixml.PRINCIPAL_COLLECTION_SET_TG,
ixml.CALENDAR_USER_ADDRESS_SET_TG,
ixml.CALENDAR_HOME_SET_TG:
pvalue.Content, pfound = ixml.HrefTag(resource.Path), true
case ixml.RESOURCE_TYPE_TG:
if resource.IsCollection() {
pvalue.Content, pfound = ixml.Tag(ixml.COLLECTION_TG, "")+ixml.Tag(ixml.CALENDAR_TG, ""), true
if resource.IsPrincipal() {
pvalue.Content += ixml.Tag(ixml.PRINCIPAL_TG, "")
}
} else {
// resourcetype must be returned empty for non-collection elements
pvalue.Content, pfound = "", true
}
case ixml.CURRENT_USER_PRINCIPAL_TG:
if global.User != nil {
path := fmt.Sprintf("/%s/", global.User.Name)
pvalue.Content, pfound = ixml.HrefTag(path), true
}
case ixml.SUPPORTED_CALENDAR_COMPONENT_SET_TG:
if resource.IsCollection() {
for _, component := range global.SupportedComponents {
// TODO: use ixml somehow to build the below tag
compTag := fmt.Sprintf(`<C:comp name="%s"/>`, component)
pvalue.Contents = append(pvalue.Contents, compTag)
}
pfound = true
}
}
if !pfound {
pvalue.Status = http.StatusNotFound
}
result.Add(pvalue)
}
return result
}
// Adds a new `msResponse` to the `Responses` array.
func (ms *multistatusResp) AddResponse(href string, found bool, propstats msPropstats) {
ms.Responses = append(ms.Responses, msResponse{
Href: href,
Found: found,
Propstats: propstats,
})
}
func (ms *multistatusResp) ToXML() string {
// init multistatus
var bf lib.StringBuffer
bf.Write(`<?xml version="1.0" encoding="UTF-8"?>`)
bf.Write(`<D:multistatus %s>`, ixml.Namespaces())
// iterate over event hrefs and build multistatus XML on the fly
for _, response := range ms.Responses {
bf.Write("<D:response>")
bf.Write(ixml.HrefTag(response.Href))
if response.Found {
propstats := response.Propstats.Clone()
if ms.Minimal {
delete(propstats, http.StatusNotFound)
if len(propstats) == 0 {
bf.Write("<D:propstat>")
bf.Write("<D:prop/>")
bf.Write(ixml.StatusTag(http.StatusOK))
bf.Write("</D:propstat>")
bf.Write("</D:response>")
continue
}
}
for status, props := range propstats {
bf.Write("<D:propstat>")
bf.Write("<D:prop>")
for _, prop := range props {
bf.Write(ms.propToXML(prop))
}
bf.Write("</D:prop>")
bf.Write(ixml.StatusTag(status))
bf.Write("</D:propstat>")
}
} else {
// if does not find the resource set 404
bf.Write(ixml.StatusTag(http.StatusNotFound))
}
bf.Write("</D:response>")
}
bf.Write("</D:multistatus>")
return bf.String()
}
func (ms *multistatusResp) propToXML(prop msProp) string {
for _, content := range prop.Contents {
prop.Content += content
}
xmlString := ixml.Tag(prop.Tag, prop.Content)
return xmlString
}

View File

@ -0,0 +1,13 @@
package handlers
import (
"net/http"
)
type notImplementedHandler struct {
response *Response
}
func (h notImplementedHandler) Handle() *Response {
return h.response.Set(http.StatusNotImplemented, "")
}

23
vendor/github.com/samedi/caldav-go/handlers/options.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
package handlers
import (
"net/http"
)
type optionsHandler struct {
response *Response
}
// Returns the allowed methods and the DAV features implemented by the current server.
// For more information about the values and format read RFC4918 Sections 10.1 and 18.
func (oh optionsHandler) Handle() *Response {
// Set the DAV compliance header:
// 1: Server supports all the requirements specified in RFC2518
// 3: Server supports all the revisions specified in RFC4918
// calendar-access: Server supports all the extensions specified in RFC4791
oh.response.SetHeader("DAV", "1, 3, calendar-access").
SetHeader("Allow", "GET, HEAD, PUT, DELETE, OPTIONS, PROPFIND, REPORT").
Set(http.StatusOK, "")
return oh.response
}

View File

@ -0,0 +1,23 @@
package handlers
import (
"net/http"
)
type requestPreconditions struct {
request *http.Request
}
func (p *requestPreconditions) IfMatch(etag string) bool {
etagMatch := p.request.Header["If-Match"]
return len(etagMatch) == 0 || etagMatch[0] == "*" || etagMatch[0] == etag
}
func (p *requestPreconditions) IfMatchPresent() bool {
return len(p.request.Header["If-Match"]) != 0
}
func (p *requestPreconditions) IfNoneMatch(value string) bool {
valueMatch := p.request.Header["If-None-Match"]
return len(valueMatch) == 1 && valueMatch[0] == value
}

View File

@ -0,0 +1,49 @@
package handlers
import (
"encoding/xml"
"github.com/samedi/caldav-go/global"
"net/http"
)
type propfindHandler struct {
request *http.Request
response *Response
}
func (ph propfindHandler) Handle() *Response {
requestBody := readRequestBody(ph.request)
header := headers{ph.request.Header}
// get the target resources based on the request URL
resources, err := global.Storage.GetResources(ph.request.URL.Path, header.IsDeep())
if err != nil {
return ph.response.SetError(err)
}
// read body string to xml struct
type XMLProp2 struct {
Tags []xml.Name `xml:",any"`
}
type XMLRoot2 struct {
XMLName xml.Name
Prop XMLProp2 `xml:"DAV: prop"`
}
var requestXML XMLRoot2
xml.Unmarshal([]byte(requestBody), &requestXML)
multistatus := &multistatusResp{
Minimal: header.IsMinimal(),
}
// for each href, build the multistatus responses
for _, resource := range resources {
propstats := multistatus.Propstats(&resource, requestXML.Prop.Tags)
multistatus.AddResponse(resource.Path, true, propstats)
}
if multistatus.Minimal {
ph.response.SetHeader(HD_PREFERENCE_APPLIED, HD_PREFER_MINIMAL)
}
return ph.response.Set(207, multistatus.ToXML())
}

65
vendor/github.com/samedi/caldav-go/handlers/put.go generated vendored Normal file
View File

@ -0,0 +1,65 @@
package handlers
import (
"github.com/samedi/caldav-go/errs"
"github.com/samedi/caldav-go/global"
"net/http"
)
type putHandler struct {
request *http.Request
response *Response
}
func (ph putHandler) Handle() *Response {
requestBody := readRequestBody(ph.request)
precond := requestPreconditions{ph.request}
success := false
// check if resource exists
resourcePath := ph.request.URL.Path
resource, found, err := global.Storage.GetShallowResource(resourcePath)
if err != nil && err != errs.ResourceNotFoundError {
return ph.response.SetError(err)
}
// PUT is allowed in 2 cases:
//
// 1. Item NOT FOUND and there is NO ETAG match header: CREATE a new item
if !found && !precond.IfMatchPresent() {
// create new event resource
resource, err = global.Storage.CreateResource(resourcePath, requestBody)
if err != nil {
return ph.response.SetError(err)
}
success = true
}
if found {
// TODO: Handle PUT on collections
if resource.IsCollection() {
return ph.response.Set(http.StatusPreconditionFailed, "")
}
// 2. Item exists, the resource etag is verified and there's no IF-NONE-MATCH=* header: UPDATE the item
resourceEtag, _ := resource.GetEtag()
if found && precond.IfMatch(resourceEtag) && !precond.IfNoneMatch("*") {
// update resource
resource, err = global.Storage.UpdateResource(resourcePath, requestBody)
if err != nil {
return ph.response.SetError(err)
}
success = true
}
}
if !success {
return ph.response.Set(http.StatusPreconditionFailed, "")
}
resourceEtag, _ := resource.GetEtag()
return ph.response.SetHeader("ETag", resourceEtag).
Set(http.StatusCreated, "")
}

168
vendor/github.com/samedi/caldav-go/handlers/report.go generated vendored Normal file
View File

@ -0,0 +1,168 @@
package handlers
import (
"encoding/xml"
"fmt"
"net/http"
"strings"
"github.com/samedi/caldav-go/data"
"github.com/samedi/caldav-go/global"
"github.com/samedi/caldav-go/ixml"
)
type reportHandler struct {
request *http.Request
response *Response
}
// See more at RFC4791#section-7.1
func (rh reportHandler) Handle() *Response {
requestBody := readRequestBody(rh.request)
header := headers{rh.request.Header}
urlResource, found, err := global.Storage.GetShallowResource(rh.request.URL.Path)
if !found {
return rh.response.Set(http.StatusNotFound, "")
} else if err != nil {
return rh.response.SetError(err)
}
// read body string to xml struct
var requestXML reportRootXML
xml.Unmarshal([]byte(requestBody), &requestXML)
// The resources to be reported are fetched by the type of the request. If it is
// a `calendar-multiget`, the resources come based on a set of `hrefs` in the request body.
// If it is a `calendar-query`, the resources are calculated based on set of filters in the request.
var resourcesToReport []reportRes
switch requestXML.XMLName {
case ixml.CALENDAR_MULTIGET_TG:
resourcesToReport, err = rh.fetchResourcesByList(urlResource, requestXML.Hrefs)
case ixml.CALENDAR_QUERY_TG:
resourcesToReport, err = rh.fetchResourcesByFilters(urlResource, requestXML.Filters)
default:
return rh.response.Set(http.StatusPreconditionFailed, "")
}
if err != nil {
return rh.response.SetError(err)
}
multistatus := &multistatusResp{
Minimal: header.IsMinimal(),
}
// for each href, build the multistatus responses
for _, r := range resourcesToReport {
propstats := multistatus.Propstats(r.resource, requestXML.Prop.Tags)
multistatus.AddResponse(r.href, r.found, propstats)
}
if multistatus.Minimal {
rh.response.SetHeader(HD_PREFERENCE_APPLIED, HD_PREFER_MINIMAL)
}
return rh.response.Set(207, multistatus.ToXML())
}
type reportPropXML struct {
Tags []xml.Name `xml:",any"`
}
type reportRootXML struct {
XMLName xml.Name
Prop reportPropXML `xml:"DAV: prop"`
Hrefs []string `xml:"DAV: href"`
Filters reportFilterXML `xml:"urn:ietf:params:xml:ns:caldav filter"`
}
type reportFilterXML struct {
XMLName xml.Name
InnerContent string `xml:",innerxml"`
}
func (rfXml reportFilterXML) toString() string {
return fmt.Sprintf("<%s>%s</%s>", rfXml.XMLName.Local, rfXml.InnerContent, rfXml.XMLName.Local)
}
// Wraps a resource that has to be reported, either fetched by filters or by a list.
// Basically it contains the original requested `href`, the actual `resource` (can be nil)
// and if the `resource` was `found` or not
type reportRes struct {
href string
resource *data.Resource
found bool
}
// The resources are fetched based on the origin resource and a set of filters.
// If the origin resource is a collection, the filters are checked against each of the collection's resources
// to see if they match. The collection's resources that match the filters are returned. The ones that will be returned
// are the resources that were not found (does not exist) and the ones that matched the filters. The ones that did not
// match the filter will not appear in the response result.
// If the origin resource is not a collection, the function just returns it and ignore any filter processing.
// [See RFC4791#section-7.8]
func (rh reportHandler) fetchResourcesByFilters(origin *data.Resource, filtersXML reportFilterXML) ([]reportRes, error) {
// The list of resources that has to be reported back in the response.
reps := []reportRes{}
if origin.IsCollection() {
filters, _ := data.ParseResourceFilters(filtersXML.toString())
resources, err := global.Storage.GetResourcesByFilters(origin.Path, filters)
if err != nil {
return reps, err
}
for in, resource := range resources {
reps = append(reps, reportRes{resource.Path, &resources[in], true})
}
} else {
// the origin resource is not a collection, so returns just that as the result
reps = append(reps, reportRes{origin.Path, origin, true})
}
return reps, nil
}
// The hrefs can come from (1) the request URL or (2) from the request body itself.
// If the origin resource from the URL points to a collection (2), we will check the request body
// to get the requested `hrefs` (resource paths). Each requested href has to be related to the collection.
// The ones that are not, we simply ignore them.
// If the resource from the URL is NOT a collection (1) we process the the report only for this resource
// and ignore any othre requested hrefs that might be present in the request body.
// [See RFC4791#section-7.9]
func (rh reportHandler) fetchResourcesByList(origin *data.Resource, requestedPaths []string) ([]reportRes, error) {
reps := []reportRes{}
if origin.IsCollection() {
resources, err := global.Storage.GetResourcesByList(requestedPaths)
if err != nil {
return reps, err
}
// we put all the resources found in a map path -> resource.
// this will be used later to query which requested resource was found
// or not and mount the response
resourcesMap := make(map[string]*data.Resource)
for _, resource := range resources {
r := resource
resourcesMap[resource.Path] = &r
}
for _, requestedPath := range requestedPaths {
// if the requested path does not belong to the origin collection, skip
// ('belonging' means that the path's prefix is the same as the collection path)
if !strings.HasPrefix(requestedPath, origin.Path) {
continue
}
resource, found := resourcesMap[requestedPath]
reps = append(reps, reportRes{requestedPath, resource, found})
}
} else {
reps = append(reps, reportRes{origin.Path, origin, true})
}
return reps, nil
}

View File

@ -0,0 +1,72 @@
package handlers
import (
"github.com/samedi/caldav-go/errs"
"io"
"net/http"
)
// Response represents the handled CalDAV response. Used this when one needs to proxy the generated
// response before being sent back to the client.
type Response struct {
Status int
Header http.Header
Body string
Error error
}
// NewResponse initializes a new response object.
func NewResponse() *Response {
return &Response{
Header: make(http.Header),
}
}
// Set sets the the status and body of the response.
func (r *Response) Set(status int, body string) *Response {
r.Status = status
r.Body = body
return r
}
// SetHeader adds a header to the response.
func (r *Response) SetHeader(key, value string) *Response {
r.Header.Set(key, value)
return r
}
// SetError sets the response as an error. It inflects the response status based on the provided error.
func (r *Response) SetError(err error) *Response {
r.Error = err
switch err {
case errs.ResourceNotFoundError:
r.Status = http.StatusNotFound
case errs.UnauthorizedError:
r.Status = http.StatusUnauthorized
case errs.ForbiddenError:
r.Status = http.StatusForbidden
default:
r.Status = http.StatusInternalServerError
}
return r
}
// Write writes the response back to the client using the provided `ResponseWriter`.
func (r *Response) Write(writer http.ResponseWriter) {
if r.Error == errs.UnauthorizedError {
r.SetHeader("WWW-Authenticate", `Basic realm="Restricted"`)
}
for key, values := range r.Header {
for _, value := range values {
writer.Header().Set(key, value)
}
}
writer.WriteHeader(r.Status)
io.WriteString(writer, r.Body)
}

18
vendor/github.com/samedi/caldav-go/handlers/shared.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
package handlers
import (
"bytes"
"io/ioutil"
"net/http"
)
// This function reads the request body and restore its content, so that
// the request body can be read a second time.
func readRequestBody(request *http.Request) string {
// Read the content
body, _ := ioutil.ReadAll(request.Body)
// Restore the io.ReadCloser to its original state
request.Body = ioutil.NopCloser(bytes.NewBuffer(body))
// Use the content
return string(body)
}

94
vendor/github.com/samedi/caldav-go/ixml/ixml.go generated vendored Normal file
View File

@ -0,0 +1,94 @@
package ixml
import (
"bytes"
"encoding/xml"
"fmt"
"net/http"
"github.com/samedi/caldav-go/lib"
)
const (
DAV_NS = "DAV:"
CALDAV_NS = "urn:ietf:params:xml:ns:caldav"
CALSERV_NS = "http://calendarserver.org/ns/"
)
var NS_PREFIXES = map[string]string{
DAV_NS: "D",
CALDAV_NS: "C",
CALSERV_NS: "CS",
}
var (
CALENDAR_TG = xml.Name{CALDAV_NS, "calendar"}
CALENDAR_DATA_TG = xml.Name{CALDAV_NS, "calendar-data"}
CALENDAR_HOME_SET_TG = xml.Name{CALDAV_NS, "calendar-home-set"}
CALENDAR_QUERY_TG = xml.Name{CALDAV_NS, "calendar-query"}
CALENDAR_MULTIGET_TG = xml.Name{CALDAV_NS, "calendar-multiget"}
CALENDAR_USER_ADDRESS_SET_TG = xml.Name{CALDAV_NS, "calendar-user-address-set"}
COLLECTION_TG = xml.Name{DAV_NS, "collection"}
CURRENT_USER_PRINCIPAL_TG = xml.Name{DAV_NS, "current-user-principal"}
DISPLAY_NAME_TG = xml.Name{DAV_NS, "displayname"}
GET_CONTENT_LENGTH_TG = xml.Name{DAV_NS, "getcontentlength"}
GET_CONTENT_TYPE_TG = xml.Name{DAV_NS, "getcontenttype"}
GET_CTAG_TG = xml.Name{CALSERV_NS, "getctag"}
GET_ETAG_TG = xml.Name{DAV_NS, "getetag"}
GET_LAST_MODIFIED_TG = xml.Name{DAV_NS, "getlastmodified"}
HREF_TG = xml.Name{DAV_NS, "href"}
OWNER_TG = xml.Name{DAV_NS, "owner"}
PRINCIPAL_TG = xml.Name{DAV_NS, "principal"}
PRINCIPAL_COLLECTION_SET_TG = xml.Name{DAV_NS, "principal-collection-set"}
PRINCIPAL_URL_TG = xml.Name{DAV_NS, "principal-URL"}
RESOURCE_TYPE_TG = xml.Name{DAV_NS, "resourcetype"}
STATUS_TG = xml.Name{DAV_NS, "status"}
SUPPORTED_CALENDAR_COMPONENT_SET_TG = xml.Name{CALDAV_NS, "supported-calendar-component-set"}
)
// Namespaces returns the default XML namespaces in for CalDAV contents.
func Namespaces() string {
bf := new(lib.StringBuffer)
bf.Write(`xmlns:%s="%s" `, NS_PREFIXES[DAV_NS], DAV_NS)
bf.Write(`xmlns:%s="%s" `, NS_PREFIXES[CALDAV_NS], CALDAV_NS)
bf.Write(`xmlns:%s="%s"`, NS_PREFIXES[CALSERV_NS], CALSERV_NS)
return bf.String()
}
// Tag returns a XML tag as string based on the given tag name and content. It
// takes in consideration the namespace and also if it is an empty content or not.
func Tag(xmlName xml.Name, content string) string {
name := xmlName.Local
ns := NS_PREFIXES[xmlName.Space]
if ns != "" {
ns = ns + ":"
}
if content != "" {
return fmt.Sprintf("<%s%s>%s</%s%s>", ns, name, content, ns, name)
} else {
return fmt.Sprintf("<%s%s/>", ns, name)
}
}
// HrefTag returns a DAV <D:href> tag with the given href path.
func HrefTag(href string) (tag string) {
return Tag(HREF_TG, href)
}
// StatusTag returns a DAV <D:status> tag with the given HTTP status. The
// status is translated into a label, e.g.: HTTP/1.1 404 NotFound.
func StatusTag(status int) string {
statusText := fmt.Sprintf("HTTP/1.1 %d %s", status, http.StatusText(status))
return Tag(STATUS_TG, statusText)
}
// EscapeText escapes any special character in the given text and returns the result.
func EscapeText(text string) string {
buffer := bytes.NewBufferString("")
xml.EscapeText(buffer, []byte(text))
return buffer.String()
}

8
vendor/github.com/samedi/caldav-go/lib/components.go generated vendored Normal file
View File

@ -0,0 +1,8 @@
package lib
const (
VCALENDAR = "VCALENDAR"
VEVENT = "VEVENT"
VJOURNAL = "VJOURNAL"
VTODO = "VTODO"
)

10
vendor/github.com/samedi/caldav-go/lib/paths.go generated vendored Normal file
View File

@ -0,0 +1,10 @@
package lib
import (
"path/filepath"
)
func ToSlashPath(path string) string {
cleanPath := filepath.Clean(path)
return filepath.ToSlash(cleanPath)
}

18
vendor/github.com/samedi/caldav-go/lib/strbuff.go generated vendored Normal file
View File

@ -0,0 +1,18 @@
package lib
import (
"bytes"
"fmt"
)
type StringBuffer struct {
buffer bytes.Buffer
}
func (b *StringBuffer) Write(format string, elem ...interface{}) {
b.buffer.WriteString(fmt.Sprintf(format, elem...))
}
func (b *StringBuffer) String() string {
return b.buffer.String()
}

4
vendor/github.com/samedi/caldav-go/test.sh generated vendored Normal file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
go test -race ./...
rm -rf test-data

5
vendor/github.com/samedi/caldav-go/version.go generated vendored Normal file
View File

@ -0,0 +1,5 @@
package caldav
const (
VERSION = "3.0.0"
)

15
vendor/modules.txt vendored
View File

@ -12,6 +12,8 @@ github.com/alecthomas/template
github.com/alecthomas/template/parse
# github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a
github.com/asaskevich/govalidator
# github.com/beevik/etree v1.1.0
github.com/beevik/etree
# github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973
github.com/beorn7/perks/quantile
# github.com/client9/misspell v0.3.4
@ -78,7 +80,7 @@ github.com/inconshreveable/mousetrap
# github.com/jgautheron/goconst v0.0.0-20170703170152-9740945f5dcb
github.com/jgautheron/goconst/cmd/goconst
github.com/jgautheron/goconst
# github.com/labstack/echo/v4 v4.1.5
# github.com/labstack/echo/v4 v4.1.5 => ../../github.com/labstack/echo
github.com/labstack/echo/v4
github.com/labstack/echo/v4/middleware
# github.com/labstack/gommon v0.2.8
@ -86,6 +88,8 @@ github.com/labstack/gommon/log
github.com/labstack/gommon/color
github.com/labstack/gommon/bytes
github.com/labstack/gommon/random
# github.com/laurent22/ical-go v0.1.0
github.com/laurent22/ical-go/ical
# github.com/magiconair/properties v1.8.0
github.com/magiconair/properties
# github.com/mailru/easyjson v0.0.0-20190403194419-1ea4449da983
@ -130,6 +134,15 @@ github.com/prometheus/procfs
github.com/prometheus/procfs/nfs
github.com/prometheus/procfs/xfs
github.com/prometheus/procfs/internal/util
# github.com/samedi/caldav-go v3.0.0+incompatible => ../../github.com/samedi/caldav-go
github.com/samedi/caldav-go
github.com/samedi/caldav-go/data
github.com/samedi/caldav-go/errs
github.com/samedi/caldav-go/lib
github.com/samedi/caldav-go/global
github.com/samedi/caldav-go/handlers
github.com/samedi/caldav-go/files
github.com/samedi/caldav-go/ixml
# github.com/spf13/afero v1.2.2
github.com/spf13/afero
github.com/spf13/afero/mem