🧑‍💻 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:
Yingyi / 颖逸 2024-11-15 11:19:52 +08:00 committed by GitHub
parent 96194f7dae
commit c110b9ff13
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 1020 additions and 19 deletions

View file

@ -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