🧑💻 Kernel serve CardDAV service on path /carddav/
(#12895)
* 🎨 add CardDAV server * 🎨 change CardDAV principals path * 🎨 implement load contacts feature * 🎨 implement save contacts feature * 🎨 implement address books CURD * 🐛 fix CardDAV method `OPTIONS` * 🎨 implement addresses CURD * 🎨 implement CardDAV `REPORT` method * 🎨 parse *.vcf file with multiple vCard
This commit is contained in:
parent
96194f7dae
commit
c110b9ff13
6 changed files with 1020 additions and 19 deletions
|
@ -23,6 +23,8 @@ require (
|
|||
github.com/denisbrodbeck/machineid v1.0.1
|
||||
github.com/dgraph-io/ristretto v1.0.0
|
||||
github.com/djherbis/times v1.6.0
|
||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff
|
||||
github.com/emersion/go-webdav v0.5.1-0.20240713135526-7f8c17ad7135
|
||||
github.com/emirpasic/gods v1.18.1
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb
|
||||
github.com/flopp/go-findfont v0.1.0
|
||||
|
|
|
@ -89,6 +89,12 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh
|
|||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/emersion/go-ical v0.0.0-20240127095438-fc1c9d8fb2b6/go.mod h1:BEksegNspIkjCQfmzWgsgbu6KdeJ/4LwUZs7DMBzjzw=
|
||||
github.com/emersion/go-vcard v0.0.0-20230815062825-8fda7d206ec9/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff h1:4N8wnS3f1hNHSmFD5zgFkWCyA4L1kCDkImPAtK7D6tg=
|
||||
github.com/emersion/go-vcard v0.0.0-20241024213814-c9703dde27ff/go.mod h1:HMJKR5wlh/ziNp+sHEDV2ltblO4JD2+IdDOWtGcQBTM=
|
||||
github.com/emersion/go-webdav v0.5.1-0.20240713135526-7f8c17ad7135 h1:Ssk00uh7jhctJ23eclGxhhGqplSQB+wCt6fmbjhnOS8=
|
||||
github.com/emersion/go-webdav v0.5.1-0.20240713135526-7f8c17ad7135/go.mod h1:mI8iBx3RAODwX7PJJ7qzsKAKs/vY429YfS2/9wKnDbQ=
|
||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||
github.com/facette/natsort v0.0.0-20181210072756-2cd4dd1e2dcb h1:IT4JYU7k4ikYg1SCxNI1/Tieq/NFvh6dzLdgi7eu0tM=
|
||||
|
@ -367,6 +373,7 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
|
|||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4dHjU=
|
||||
github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||
github.com/teambition/rrule-go v1.8.2/go.mod h1:Ieq5AbrKGciP1V//Wq8ktsTXwSwJHDD5mD/wLBGl3p4=
|
||||
github.com/tetratelabs/wazero v1.7.3 h1:PBH5KVahrt3S2AHgEjKu4u+LlDbbk+nsGE3KLucy6Rw=
|
||||
github.com/tetratelabs/wazero v1.7.3/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y=
|
||||
github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
|
||||
|
|
880
kernel/model/carddav.go
Normal file
880
kernel/model/carddav.go
Normal file
|
@ -0,0 +1,880 @@
|
|||
// SiYuan - Refactor your thinking
|
||||
// Copyright (c) 2020-present, b3log.org
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/emersion/go-vcard"
|
||||
"github.com/emersion/go-webdav/carddav"
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/util"
|
||||
)
|
||||
|
||||
const (
|
||||
// REF: https://developers.google.com/people/carddav#resources
|
||||
CardDavPrefixPath = "/carddav"
|
||||
CardDavPrincipalsPath = CardDavPrefixPath + "/principals" // 0 resourceTypeRoot
|
||||
CardDavUserPrincipalPath = CardDavPrincipalsPath + "/main" // 1 resourceTypeUserPrincipal
|
||||
CardDavHomeSetPath = CardDavUserPrincipalPath + "/contacts" // 2 resourceTypeAddressBookHomeSet
|
||||
|
||||
CardDavDefaultAddressBookPath = CardDavHomeSetPath + "/default" // 3 resourceTypeAddressBook
|
||||
CardDavDefaultAddressBookName = "default"
|
||||
|
||||
CardDavAddressBooksMetaDataFilePath = CardDavHomeSetPath + "/address-books.json"
|
||||
)
|
||||
|
||||
type PathDepth int
|
||||
|
||||
const (
|
||||
pathDepth_Root PathDepth = 1 + iota // /carddav
|
||||
pathDepth_Principals // /carddav/principals
|
||||
pathDepth_UserPrincipal // /carddav/principals/main
|
||||
pathDepth_HomeSet // /carddav/principals/main/contacts
|
||||
pathDepth_AddressBook // /carddav/principals/main/contacts/default
|
||||
pathDepth_Address // /carddav/principals/main/contacts/default/id.vcf
|
||||
)
|
||||
|
||||
var (
|
||||
defaultAddressBook = carddav.AddressBook{
|
||||
Path: CardDavDefaultAddressBookPath,
|
||||
Name: CardDavDefaultAddressBookName,
|
||||
Description: "Default address book",
|
||||
MaxResourceSize: 0,
|
||||
}
|
||||
contacts = Contacts{
|
||||
loaded: false,
|
||||
changed: false,
|
||||
lock: sync.Mutex{},
|
||||
books: sync.Map{},
|
||||
booksMetaData: []*carddav.AddressBook{},
|
||||
}
|
||||
|
||||
ErrorNotFound = errors.New("CardDAV: not found")
|
||||
ErrorPathInvalid = errors.New("CardDAV: path is invalid")
|
||||
|
||||
ErrorBookNotFound = errors.New("CardDAV: address book not found")
|
||||
ErrorBookPathInvalid = errors.New("CardDAV: address book path is invalid")
|
||||
|
||||
ErrorAddressNotFound = errors.New("CardDAV: address not found")
|
||||
ErrorAddressFileExtensionNameInvalid = errors.New("CardDAV: address file extension name is invalid")
|
||||
)
|
||||
|
||||
// CardDavPath2DirectoryPath converts CardDAV path to absolute path of the file system
|
||||
func CardDavPath2DirectoryPath(cardDavPath string) string {
|
||||
return filepath.Join(util.DataDir, "storage", strings.TrimPrefix(cardDavPath, "/"))
|
||||
}
|
||||
|
||||
// HomeSetPathPath returns the absolute path of the address book home set directory
|
||||
func HomeSetPathPath() string {
|
||||
return CardDavPath2DirectoryPath(CardDavHomeSetPath)
|
||||
}
|
||||
|
||||
// AddressBooksMetaDataFilePath returns the absolute path of the address books meta data file
|
||||
func AddressBooksMetaDataFilePath() string {
|
||||
return CardDavPath2DirectoryPath(CardDavAddressBooksMetaDataFilePath)
|
||||
}
|
||||
|
||||
func GetPathDepth(urlPath string) PathDepth {
|
||||
urlPath = path.Clean(urlPath)
|
||||
return PathDepth(len(strings.Split(urlPath, "/")) - 1)
|
||||
}
|
||||
|
||||
// ParseAddressPath parses address path to address book path and address ID
|
||||
func ParseAddressPath(addressPath string) (addressBookPath string, addressID string, err error) {
|
||||
addressBookPath, addressFileName := path.Split(addressPath)
|
||||
addressID = path.Base(addressFileName)
|
||||
addressFileExt := path.Ext(addressFileName)
|
||||
|
||||
if GetPathDepth(addressBookPath) != pathDepth_AddressBook {
|
||||
err = ErrorBookPathInvalid
|
||||
return
|
||||
}
|
||||
|
||||
if addressFileExt != ".vcf" {
|
||||
err = ErrorAddressFileExtensionNameInvalid
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// AddressPropsFilter filters address properties
|
||||
func AddressPropsFilter(address *carddav.AddressObject, req *carddav.AddressDataRequest) *carddav.AddressObject {
|
||||
var card *vcard.Card
|
||||
card = &address.Card
|
||||
|
||||
// if req.AllProp {
|
||||
// card = &address.Card
|
||||
// } else {
|
||||
// card = &vcard.Card{}
|
||||
// for _, prop := range req.Props {
|
||||
// fields := address.Card[prop]
|
||||
// if fields != nil {
|
||||
// for _, field := range fields {
|
||||
// card.Add(prop, field)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
return &carddav.AddressObject{
|
||||
Path: address.Path,
|
||||
ModTime: address.ModTime,
|
||||
ContentLength: address.ContentLength,
|
||||
ETag: address.ETag,
|
||||
Card: *card,
|
||||
}
|
||||
}
|
||||
|
||||
func LoadCards(filePath string) (cards []*vcard.Card, err error) {
|
||||
data, err := os.ReadFile(filePath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read vCard file [%s] failed: %s", filePath, err)
|
||||
return
|
||||
}
|
||||
|
||||
decoder := vcard.NewDecoder(bytes.NewReader(data))
|
||||
for {
|
||||
card, err := decoder.Decode()
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
logging.LogErrorf("decode vCard file [%s] failed: %s", filePath, err)
|
||||
return nil, err
|
||||
}
|
||||
cards = append(cards, &card)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type Contacts struct {
|
||||
loaded bool
|
||||
changed bool
|
||||
lock sync.Mutex // load & save
|
||||
books sync.Map // Path -> *AddressBook
|
||||
booksMetaData []*carddav.AddressBook
|
||||
}
|
||||
|
||||
// load all contacts
|
||||
func (c *Contacts) load() error {
|
||||
c.books.Clear()
|
||||
addressBooksMetaDataFilePath := AddressBooksMetaDataFilePath()
|
||||
metaData, err := os.ReadFile(addressBooksMetaDataFilePath)
|
||||
if os.IsNotExist(err) {
|
||||
c.booksMetaData = []*carddav.AddressBook{&defaultAddressBook}
|
||||
if err := c.saveAddressBooksMetaData(); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
// load meta data file
|
||||
c.booksMetaData = []*carddav.AddressBook{}
|
||||
if err = gulu.JSON.UnmarshalJSON(metaData, &c.booksMetaData); err != nil {
|
||||
logging.LogErrorf("unmarshal address books meta data failed: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(len(c.booksMetaData))
|
||||
for _, addressBookMetaData := range c.booksMetaData {
|
||||
addressBook := &AddressBook{
|
||||
Changed: false,
|
||||
DirectoryPath: CardDavPath2DirectoryPath(addressBookMetaData.Path),
|
||||
MetaData: addressBookMetaData,
|
||||
Addresses: sync.Map{},
|
||||
}
|
||||
c.books.Store(addressBookMetaData.Path, addressBook)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
addressBook.load()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
c.loaded = true
|
||||
c.changed = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// save all contacts
|
||||
func (c *Contacts) save(force bool) error {
|
||||
if force || c.changed {
|
||||
// save address books meta data
|
||||
if err := c.saveAddressBooksMetaData(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// save all address to *.vbf files
|
||||
wg := &sync.WaitGroup{}
|
||||
c.books.Range(func(path any, book any) bool {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
// path_ := path.(string)
|
||||
book_ := book.(*AddressBook)
|
||||
book_.save(force)
|
||||
}()
|
||||
return true
|
||||
})
|
||||
wg.Wait()
|
||||
c.changed = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// save all contacts
|
||||
func (c *Contacts) saveAddressBooksMetaData() error {
|
||||
data, err := gulu.JSON.MarshalIndentJSON(c.booksMetaData, "", " ")
|
||||
if err != nil {
|
||||
logging.LogErrorf("marshal address books meta data failed: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
dirPath := HomeSetPathPath()
|
||||
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||
logging.LogErrorf("create directory [%s] failed: %s", dirPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
filePath := AddressBooksMetaDataFilePath()
|
||||
if err := os.WriteFile(filePath, data, 0755); err != nil {
|
||||
logging.LogErrorf("write file [%s] failed: %s", filePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Contacts) Load() error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if !c.loaded {
|
||||
return c.load()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Contacts) GetAddress(addressPath string) (addressBook *AddressBook, addressObject *AddressObject, err error) {
|
||||
bookPath, addressID, err := ParseAddressPath(addressPath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("parse address path [%s] failed: %s", addressPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
if value, ok := c.books.Load(bookPath); ok {
|
||||
addressBook = value.(*AddressBook)
|
||||
} else {
|
||||
err = ErrorBookNotFound
|
||||
return
|
||||
}
|
||||
|
||||
if value, ok := addressBook.Addresses.Load(addressID); ok {
|
||||
addressObject = value.(*AddressObject)
|
||||
} else {
|
||||
err = ErrorAddressNotFound
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Contacts) ListAddressBooks() (addressBooks []carddav.AddressBook, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
for _, addressBook := range contacts.booksMetaData {
|
||||
addressBooks = append(addressBooks, *addressBook)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Contacts) GetAddressBook(path string) (addressBook *carddav.AddressBook, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if book, ok := contacts.books.Load(path); ok {
|
||||
addressBook = book.(*AddressBook).MetaData
|
||||
return
|
||||
}
|
||||
|
||||
err = ErrorBookNotFound
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Contacts) CreateAddressBook(addressBookMetaData *carddav.AddressBook) (err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
var addressBook *AddressBook
|
||||
|
||||
// update map
|
||||
if value, ok := c.books.Load(addressBookMetaData.Path); ok {
|
||||
// update map item
|
||||
addressBook = value.(*AddressBook)
|
||||
addressBook.MetaData = addressBookMetaData
|
||||
} else {
|
||||
// insert map item
|
||||
addressBook = &AddressBook{
|
||||
Changed: false,
|
||||
DirectoryPath: CardDavPath2DirectoryPath(addressBookMetaData.Path),
|
||||
MetaData: addressBookMetaData,
|
||||
Addresses: sync.Map{},
|
||||
}
|
||||
c.books.Store(addressBookMetaData.Path, addressBook)
|
||||
}
|
||||
|
||||
var index = -1
|
||||
for i, item := range c.booksMetaData {
|
||||
if item.Path == addressBookMetaData.Path {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if index >= 0 {
|
||||
// update list
|
||||
c.booksMetaData[index] = addressBookMetaData
|
||||
} else {
|
||||
// insert list
|
||||
c.booksMetaData = append(c.booksMetaData, addressBookMetaData)
|
||||
}
|
||||
|
||||
// create address book directory
|
||||
if err = os.MkdirAll(addressBook.DirectoryPath, 0755); err != nil {
|
||||
logging.LogErrorf("create directory [%s] failed: %s", addressBook, err)
|
||||
return
|
||||
}
|
||||
|
||||
// save meta data
|
||||
if err = c.saveAddressBooksMetaData(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Contacts) DeleteAddressBook(path string) (err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
var addressBook *AddressBook
|
||||
|
||||
// delete map item
|
||||
if value, loaded := c.books.LoadAndDelete(path); loaded {
|
||||
addressBook = value.(*AddressBook)
|
||||
}
|
||||
|
||||
// delete list item
|
||||
for i, item := range c.booksMetaData {
|
||||
if item.Path == path {
|
||||
c.booksMetaData = append(c.booksMetaData[:i], c.booksMetaData[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// remove address book directory
|
||||
if err = os.RemoveAll(addressBook.DirectoryPath); err != nil {
|
||||
logging.LogErrorf("remove directory [%s] failed: %s", addressBook, err)
|
||||
return
|
||||
}
|
||||
|
||||
// save meta data
|
||||
if err = c.saveAddressBooksMetaData(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Contacts) GetAddressObject(addressPath string, req *carddav.AddressDataRequest) (addressObject *carddav.AddressObject, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
_, address, err := c.GetAddress(addressPath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addressObject = AddressPropsFilter(address.Data, req)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Contacts) ListAddressObjects(bookPath string, req *carddav.AddressDataRequest) (addressObjects []carddav.AddressObject, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
var addressBook *AddressBook
|
||||
if value, ok := c.books.Load(bookPath); ok {
|
||||
addressBook = value.(*AddressBook)
|
||||
} else {
|
||||
err = ErrorBookNotFound
|
||||
return
|
||||
}
|
||||
|
||||
addressBook.Addresses.Range(func(id any, address any) bool {
|
||||
addressObjects = append(addressObjects, *AddressPropsFilter(address.(*AddressObject).Data, req))
|
||||
return true
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Contacts) QueryAddressObjects(urlPath string, query *carddav.AddressBookQuery) (addressObjects []carddav.AddressObject, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
switch GetPathDepth(urlPath) {
|
||||
case pathDepth_Root, pathDepth_Principals, pathDepth_UserPrincipal, pathDepth_HomeSet:
|
||||
c.books.Range(func(path any, book any) bool {
|
||||
addressBook := book.(*AddressBook)
|
||||
addressBook.Addresses.Range(func(id any, address any) bool {
|
||||
addressObjects = append(addressObjects, *address.(*AddressObject).Data)
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
case pathDepth_AddressBook:
|
||||
if value, ok := c.books.Load(urlPath); ok {
|
||||
addressBook := value.(*AddressBook)
|
||||
addressBook.Addresses.Range(func(id any, address any) bool {
|
||||
addressObjects = append(addressObjects, *address.(*AddressObject).Data)
|
||||
return true
|
||||
})
|
||||
}
|
||||
case pathDepth_Address:
|
||||
if _, address, _ := c.GetAddress(urlPath); address != nil {
|
||||
addressObjects = append(addressObjects, *address.Data)
|
||||
}
|
||||
default:
|
||||
err = ErrorPathInvalid
|
||||
return
|
||||
}
|
||||
|
||||
addressObjects, err = carddav.Filter(query, addressObjects)
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Contacts) PutAddressObject(addressPath string, card vcard.Card, opts *carddav.PutAddressObjectOptions) (addressObject *carddav.AddressObject, err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
bookPath, addressID, err := ParseAddressPath(addressPath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("parse address path [%s] failed: %s", addressPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
var addressBook *AddressBook
|
||||
if value, ok := c.books.Load(bookPath); ok {
|
||||
addressBook = value.(*AddressBook)
|
||||
} else {
|
||||
err = ErrorBookNotFound
|
||||
return
|
||||
}
|
||||
|
||||
var address *AddressObject
|
||||
if value, ok := addressBook.Addresses.Load(addressID); ok {
|
||||
address = value.(*AddressObject)
|
||||
|
||||
if opts.IfNoneMatch.IsSet() {
|
||||
addressObject = address.Data
|
||||
return
|
||||
}
|
||||
|
||||
address.Data.Card = card
|
||||
address.Changed = true
|
||||
} else {
|
||||
address = &AddressObject{
|
||||
Changed: true,
|
||||
FilePath: CardDavPath2DirectoryPath(addressPath),
|
||||
BookPath: bookPath,
|
||||
Data: &carddav.AddressObject{
|
||||
Card: card,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
err = address.save(true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = address.update()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addressBook.Addresses.Store(addressID, address)
|
||||
addressObject = address.Data
|
||||
return
|
||||
}
|
||||
|
||||
func (c *Contacts) DeleteAddressObject(addressPath string) (err error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
_, address, err := c.GetAddress(addressPath)
|
||||
if err != nil && err != ErrorAddressNotFound {
|
||||
return
|
||||
}
|
||||
|
||||
if err = os.Remove(address.FilePath); err != nil {
|
||||
logging.LogErrorf("remove file [%s] failed: %s", address.FilePath, err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
type AddressBook struct {
|
||||
Changed bool
|
||||
DirectoryPath string
|
||||
MetaData *carddav.AddressBook
|
||||
Addresses sync.Map // id -> *AddressObject
|
||||
}
|
||||
|
||||
// load an address book from multiple *.vcf files
|
||||
func (b *AddressBook) load() error {
|
||||
if err := os.MkdirAll(b.DirectoryPath, 0755); err != nil {
|
||||
logging.LogErrorf("create directory [%s] failed: %s", b.DirectoryPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(b.DirectoryPath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read dir [%s] failed: %s", b.DirectoryPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
filename := entry.Name()
|
||||
ext := path.Ext(filename)
|
||||
if ext == ".vcf" {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
// load cards
|
||||
addressFilePath := path.Join(b.DirectoryPath, filename)
|
||||
vCards, err := LoadCards(addressFilePath)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch len(vCards) {
|
||||
case 0: // invalid file
|
||||
case 1: // file contain 1 card
|
||||
address := &AddressObject{
|
||||
FilePath: addressFilePath,
|
||||
BookPath: b.MetaData.Path,
|
||||
Data: &carddav.AddressObject{
|
||||
Card: *vCards[0],
|
||||
},
|
||||
}
|
||||
if err := address.update(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id := path.Base(filename)
|
||||
b.Addresses.Store(id, address)
|
||||
default: // file contain multiple cards
|
||||
// Create a file for each card
|
||||
addressesWaitGroup := &sync.WaitGroup{}
|
||||
for _, vCard := range vCards {
|
||||
addressesWaitGroup.Add(1)
|
||||
go func() {
|
||||
defer addressesWaitGroup.Done()
|
||||
filename_ := util.AssetName(filename)
|
||||
address := &AddressObject{
|
||||
FilePath: path.Join(b.DirectoryPath, filename_),
|
||||
BookPath: b.MetaData.Path,
|
||||
Data: &carddav.AddressObject{
|
||||
Card: *vCard,
|
||||
},
|
||||
}
|
||||
if err := address.save(true); err != nil {
|
||||
return
|
||||
}
|
||||
if err := address.update(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
id := path.Base(filename)
|
||||
b.Addresses.Store(id, address)
|
||||
}()
|
||||
}
|
||||
|
||||
addressesWaitGroup.Wait()
|
||||
// Delete original file with multiple cards
|
||||
if err := os.Remove(addressFilePath); err != nil {
|
||||
logging.LogErrorf("remove file [%s] failed: %s", addressFilePath, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// save an address book to multiple *.vcf files
|
||||
func (b *AddressBook) save(force bool) error {
|
||||
if force || b.Changed {
|
||||
// create directory
|
||||
if err := os.MkdirAll(b.DirectoryPath, 0755); err != nil {
|
||||
logging.LogErrorf("create directory [%s] failed: %s", b.DirectoryPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
b.Addresses.Range(func(id any, address any) bool {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
// id_ := id.(string)
|
||||
address_ := address.(*AddressObject)
|
||||
address_.save(force)
|
||||
address_.update()
|
||||
}()
|
||||
return true
|
||||
})
|
||||
wg.Wait()
|
||||
b.Changed = false
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type AddressObject struct {
|
||||
Changed bool
|
||||
FilePath string
|
||||
BookPath string
|
||||
Data *carddav.AddressObject
|
||||
}
|
||||
|
||||
// load an address from *.vcf file
|
||||
func (o *AddressObject) load() error {
|
||||
// get file info
|
||||
addressFileInfo, err := os.Stat(o.FilePath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("get file [%s] info failed: %s", o.FilePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// read file
|
||||
addressData, err := os.ReadFile(o.FilePath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("read file [%s] failed: %s", o.FilePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// decode file
|
||||
reader := bytes.NewReader(addressData)
|
||||
decoder := vcard.NewDecoder(reader)
|
||||
card, err := decoder.Decode()
|
||||
if err != nil {
|
||||
logging.LogErrorf("decode file [%s] failed: %s", o.FilePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// load data
|
||||
o.Changed = false
|
||||
o.Data = &carddav.AddressObject{
|
||||
Path: path.Join(o.BookPath, addressFileInfo.Name()),
|
||||
ModTime: addressFileInfo.ModTime(),
|
||||
ContentLength: addressFileInfo.Size(),
|
||||
ETag: fmt.Sprintf("%x-%x", addressFileInfo.ModTime(), addressFileInfo.Size()),
|
||||
Card: card,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// save an address to *.vcf file
|
||||
func (o *AddressObject) save(force bool) error {
|
||||
if force || o.Changed {
|
||||
var addressData bytes.Buffer
|
||||
|
||||
// encode data
|
||||
encoder := vcard.NewEncoder(&addressData)
|
||||
if err := encoder.Encode(o.Data.Card); err != nil {
|
||||
logging.LogErrorf("encode card [%s] failed: %s", o.Data.Path, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// create directory
|
||||
dirPath := path.Dir(o.FilePath)
|
||||
if err := os.MkdirAll(dirPath, 0755); err != nil {
|
||||
logging.LogErrorf("create directory [%s] failed: %s", dirPath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
// write file
|
||||
if err := os.WriteFile(o.FilePath, addressData.Bytes(), 0755); err != nil {
|
||||
logging.LogErrorf("write file [%s] failed: %s", o.FilePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
o.Changed = false
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// update file info
|
||||
func (o *AddressObject) update() error {
|
||||
// update file info
|
||||
addressFileInfo, err := os.Stat(o.FilePath)
|
||||
if err != nil {
|
||||
logging.LogErrorf("get file [%s] info failed: %s", o.FilePath, err)
|
||||
return err
|
||||
}
|
||||
|
||||
o.Data.Path = path.Join(o.BookPath, addressFileInfo.Name())
|
||||
o.Data.ModTime = addressFileInfo.ModTime()
|
||||
o.Data.ContentLength = addressFileInfo.Size()
|
||||
o.Data.ETag = fmt.Sprintf("%x-%x", addressFileInfo.ModTime(), addressFileInfo.Size())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type CardDavBackend struct{}
|
||||
|
||||
func (b *CardDavBackend) CurrentUserPrincipal(ctx context.Context) (string, error) {
|
||||
// logging.LogDebugf("CardDAV CurrentUserPrincipal")
|
||||
return CardDavUserPrincipalPath, nil
|
||||
}
|
||||
|
||||
func (b *CardDavBackend) AddressBookHomeSetPath(ctx context.Context) (string, error) {
|
||||
// logging.LogDebugf("CardDAV AddressBookHomeSetPath")
|
||||
return CardDavHomeSetPath, nil
|
||||
}
|
||||
|
||||
func (b *CardDavBackend) ListAddressBooks(ctx context.Context) (addressBooks []carddav.AddressBook, err error) {
|
||||
// logging.LogDebugf("CardDAV ListAddressBooks")
|
||||
if err = contacts.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addressBooks, err = contacts.ListAddressBooks()
|
||||
// logging.LogDebugf("CardDAV ListAddressBooks <- addressBooks: %#v, err: %s", addressBooks, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CardDavBackend) GetAddressBook(ctx context.Context, bookPath string) (addressBook *carddav.AddressBook, err error) {
|
||||
// logging.LogDebugf("CardDAV GetAddressBook -> bookPath: %s", bookPath)
|
||||
if err = contacts.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addressBook, err = contacts.GetAddressBook(bookPath)
|
||||
// logging.LogDebugf("CardDAV GetAddressBook <- addressBook: %#v, err: %s", addressBook, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CardDavBackend) CreateAddressBook(ctx context.Context, addressBook *carddav.AddressBook) (err error) {
|
||||
// logging.LogDebugf("CardDAV CreateAddressBook -> addressBook: %#v", addressBook)
|
||||
if err = contacts.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = contacts.CreateAddressBook(addressBook)
|
||||
// logging.LogDebugf("CardDAV CreateAddressBook <- err: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CardDavBackend) DeleteAddressBook(ctx context.Context, bookPath string) (err error) {
|
||||
// logging.LogDebugf("CardDAV DeleteAddressBook -> bookPath: %s", bookPath)
|
||||
if err = contacts.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = contacts.DeleteAddressBook(bookPath)
|
||||
// logging.LogDebugf("CardDAV DeleteAddressBook <- err: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CardDavBackend) GetAddressObject(ctx context.Context, addressPath string, req *carddav.AddressDataRequest) (addressObject *carddav.AddressObject, err error) {
|
||||
// logging.LogDebugf("CardDAV GetAddressObject -> addressPath: %s, req: %#v", addressPath, req)
|
||||
if err = contacts.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addressObject, err = contacts.GetAddressObject(addressPath, req)
|
||||
// logging.LogDebugf("CardDAV GetAddressObject <- addressObject: %#v, err: %s", addressObject, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CardDavBackend) ListAddressObjects(ctx context.Context, bookPath string, req *carddav.AddressDataRequest) (addressObjects []carddav.AddressObject, err error) {
|
||||
// logging.LogDebugf("CardDAV ListAddressObjects -> bookPath: %s, req: %#v", bookPath, req)
|
||||
if err = contacts.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addressObjects, err = contacts.ListAddressObjects(bookPath, req)
|
||||
// logging.LogDebugf("CardDAV ListAddressObjects <- addressObjects: %#v, err: %s", addressObjects, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CardDavBackend) QueryAddressObjects(ctx context.Context, urlPath string, query *carddav.AddressBookQuery) (addressObjects []carddav.AddressObject, err error) {
|
||||
// logging.LogDebugf("CardDAV QueryAddressObjects -> urlPath: %s, query: %#v", urlPath, query)
|
||||
if err = contacts.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addressObjects, err = contacts.QueryAddressObjects(urlPath, query)
|
||||
// logging.LogDebugf("CardDAV QueryAddressObjects <- addressObjects: %#v, err: %s", addressObjects, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CardDavBackend) PutAddressObject(ctx context.Context, addressPath string, card vcard.Card, opts *carddav.PutAddressObjectOptions) (addressObject *carddav.AddressObject, err error) {
|
||||
// logging.LogDebugf("CardDAV PutAddressObject -> addressPath: %s, card: %#v, opts: %#v", addressPath, card, opts)
|
||||
if err = contacts.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
addressObject, err = contacts.PutAddressObject(addressPath, card, opts)
|
||||
// logging.LogDebugf("CardDAV PutAddressObject <- addressObject: %#v, err: %s", addressObject, err)
|
||||
return
|
||||
}
|
||||
|
||||
func (b *CardDavBackend) DeleteAddressObject(ctx context.Context, addressPath string) (err error) {
|
||||
// logging.LogDebugf("CardDAV DeleteAddressObject -> addressPath: %s", addressPath)
|
||||
if err = contacts.Load(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = contacts.DeleteAddressObject(addressPath)
|
||||
// logging.LogDebugf("CardDAV DeleteAddressObject <- err: %s", err)
|
||||
return
|
||||
}
|
|
@ -34,6 +34,11 @@ import (
|
|||
"github.com/steambap/captcha"
|
||||
)
|
||||
|
||||
var (
|
||||
BasicAuthHeaderKey = "WWW-Authenticate"
|
||||
BasicAuthHeaderValue = "Basic realm=\"SiYuan Authorization Require\", charset=\"UTF-8\""
|
||||
)
|
||||
|
||||
func LogoutAuth(c *gin.Context) {
|
||||
ret := gulu.Ret.NewResult()
|
||||
defer c.JSON(http.StatusOK, ret)
|
||||
|
@ -300,8 +305,8 @@ func CheckAuth(c *gin.Context) {
|
|||
}
|
||||
|
||||
// WebDAV BasicAuth Authenticate
|
||||
if strings.HasPrefix(c.Request.RequestURI, "/webdav") {
|
||||
c.Header("WWW-Authenticate", "Basic realm=Authorization Required")
|
||||
if strings.HasPrefix(c.Request.RequestURI, "/webdav") || strings.HasPrefix(c.Request.RequestURI, "/carddav") {
|
||||
c.Header(BasicAuthHeaderKey, BasicAuthHeaderValue)
|
||||
c.AbortWithStatus(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"strconv"
|
||||
|
||||
"github.com/siyuan-note/logging"
|
||||
"github.com/siyuan-note/siyuan/kernel/model"
|
||||
|
@ -143,7 +142,7 @@ func (PublishServiceTransport) RoundTrip(request *http.Request) (response *http.
|
|||
ProtoMinor: request.ProtoMinor,
|
||||
Request: request,
|
||||
Header: http.Header{
|
||||
"WWW-Authenticate": {"Basic realm=" + strconv.Quote("Authorization Required")},
|
||||
model.BasicAuthHeaderKey: {model.BasicAuthHeaderValue},
|
||||
},
|
||||
Close: false,
|
||||
ContentLength: -1,
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/88250/gulu"
|
||||
"github.com/emersion/go-webdav/carddav"
|
||||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-contrib/sessions"
|
||||
"github.com/gin-contrib/sessions/cookie"
|
||||
|
@ -47,17 +48,63 @@ import (
|
|||
"golang.org/x/net/webdav"
|
||||
)
|
||||
|
||||
const (
|
||||
MethodMkcol = "MKCOL"
|
||||
MethodCopy = "COPY"
|
||||
MethodMove = "MOVE"
|
||||
MethodLock = "LOCK"
|
||||
MethodUnlock = "UNLOCK"
|
||||
MethodPropFind = "PROPFIND"
|
||||
MethodPropPatch = "PROPPATCH"
|
||||
MethodReport = "REPORT"
|
||||
)
|
||||
|
||||
var (
|
||||
cookieStore = cookie.NewStore([]byte("ATN51UlxVq1Gcvdf"))
|
||||
WebDavMethod = []string{
|
||||
"OPTIONS",
|
||||
"GET", "HEAD",
|
||||
"POST", "PUT",
|
||||
"DELETE",
|
||||
"MKCOL",
|
||||
"COPY", "MOVE",
|
||||
"LOCK", "UNLOCK",
|
||||
"PROPFIND", "PROPPATCH",
|
||||
cookieStore = cookie.NewStore([]byte("ATN51UlxVq1Gcvdf"))
|
||||
HttpMethods = []string{
|
||||
http.MethodGet,
|
||||
http.MethodHead,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodPatch,
|
||||
http.MethodDelete,
|
||||
http.MethodConnect,
|
||||
http.MethodOptions,
|
||||
http.MethodTrace,
|
||||
}
|
||||
WebDavMethods = []string{
|
||||
http.MethodOptions,
|
||||
http.MethodHead,
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodDelete,
|
||||
|
||||
MethodMkcol,
|
||||
MethodCopy,
|
||||
MethodMove,
|
||||
MethodLock,
|
||||
MethodUnlock,
|
||||
MethodPropFind,
|
||||
MethodPropPatch,
|
||||
}
|
||||
CardDavMethods = []string{
|
||||
http.MethodOptions,
|
||||
http.MethodHead,
|
||||
http.MethodGet,
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodDelete,
|
||||
|
||||
MethodMkcol,
|
||||
// MethodCopy,
|
||||
// MethodMove,
|
||||
// MethodLock,
|
||||
// MethodUnlock,
|
||||
MethodPropFind,
|
||||
MethodPropPatch,
|
||||
|
||||
MethodReport,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -88,6 +135,7 @@ func Serve(fastMode bool) {
|
|||
serveAppearance(ginServer)
|
||||
serveWebSocket(ginServer)
|
||||
serveWebDAV(ginServer)
|
||||
serveCardDAV(ginServer)
|
||||
serveExport(ginServer)
|
||||
serveWidgets(ginServer)
|
||||
servePlugins(ginServer)
|
||||
|
@ -616,10 +664,19 @@ func serveWebDAV(ginServer *gin.Engine) {
|
|||
}
|
||||
|
||||
ginGroup := ginServer.Group("/webdav", model.CheckAuth, model.CheckAdminRole)
|
||||
ginGroup.Match(WebDavMethod, "/*path", func(c *gin.Context) {
|
||||
// ginGroup.Any NOT support extension methods (PROPFIND etc.)
|
||||
ginGroup.Match(WebDavMethods, "/*path", func(c *gin.Context) {
|
||||
if util.ReadOnly {
|
||||
switch c.Request.Method {
|
||||
case "POST", "PUT", "DELETE", "MKCOL", "COPY", "MOVE", "LOCK", "UNLOCK", "PROPPATCH":
|
||||
case http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodDelete,
|
||||
MethodMkcol,
|
||||
MethodCopy,
|
||||
MethodMove,
|
||||
MethodLock,
|
||||
MethodUnlock,
|
||||
MethodPropPatch:
|
||||
c.AbortWithError(http.StatusForbidden, fmt.Errorf(model.Conf.Language(34)))
|
||||
return
|
||||
}
|
||||
|
@ -628,6 +685,40 @@ func serveWebDAV(ginServer *gin.Engine) {
|
|||
})
|
||||
}
|
||||
|
||||
func serveCardDAV(ginServer *gin.Engine) {
|
||||
// REF: https://github.com/emersion/hydroxide/blob/master/carddav/carddav.go
|
||||
handler := carddav.Handler{
|
||||
Backend: &model.CardDavBackend{},
|
||||
Prefix: model.CardDavPrincipalsPath,
|
||||
}
|
||||
|
||||
ginServer.Match(CardDavMethods, "/.well-known/carddav", func(c *gin.Context) {
|
||||
handler.ServeHTTP(c.Writer, c.Request)
|
||||
})
|
||||
|
||||
ginGroup := ginServer.Group(model.CardDavPrefixPath, model.CheckAuth, model.CheckAdminRole)
|
||||
ginGroup.Match(CardDavMethods, "/*path", func(c *gin.Context) {
|
||||
// logging.LogDebugf("CardDAV -> [%s] %s", c.Request.Method, c.Request.URL.String())
|
||||
if util.ReadOnly {
|
||||
switch c.Request.Method {
|
||||
case http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodDelete,
|
||||
MethodMkcol,
|
||||
MethodCopy,
|
||||
MethodMove,
|
||||
MethodLock,
|
||||
MethodUnlock,
|
||||
MethodPropPatch:
|
||||
c.AbortWithError(http.StatusForbidden, fmt.Errorf(model.Conf.Language(34)))
|
||||
return
|
||||
}
|
||||
}
|
||||
handler.ServeHTTP(c.Writer, c.Request)
|
||||
// logging.LogDebugf("CardDAV <- [%s] %v", c.Request.Method, c.Writer.Status())
|
||||
})
|
||||
}
|
||||
|
||||
func shortReqMsg(msg []byte) []byte {
|
||||
s := gulu.Str.FromBytes(msg)
|
||||
max := 128
|
||||
|
@ -644,15 +735,32 @@ func shortReqMsg(msg []byte) []byte {
|
|||
}
|
||||
|
||||
func corsMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
allowMethods := strings.Join(HttpMethods, ", ")
|
||||
allowWebDavMethods := strings.Join(WebDavMethods, ", ")
|
||||
allowCardDavMethods := strings.Join(CardDavMethods, ", ")
|
||||
|
||||
return func(c *gin.Context) {
|
||||
c.Header("Access-Control-Allow-Origin", "*")
|
||||
c.Header("Access-Control-Allow-Credentials", "true")
|
||||
c.Header("Access-Control-Allow-Headers", "origin, Content-Length, Content-Type, Authorization")
|
||||
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS")
|
||||
c.Header("Access-Control-Allow-Private-Network", "true")
|
||||
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
if strings.HasPrefix(c.Request.RequestURI, "/webdav/") {
|
||||
c.Header("Access-Control-Allow-Methods", allowWebDavMethods)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(c.Request.RequestURI, "/carddav/") {
|
||||
c.Header("Access-Control-Allow-Methods", allowCardDavMethods)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("Access-Control-Allow-Methods", allowMethods)
|
||||
|
||||
switch c.Request.Method {
|
||||
case http.MethodOptions:
|
||||
c.Header("Access-Control-Max-Age", "600")
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
|
|
Loading…
Add table
Reference in a new issue