cleanups
This commit is contained in:
195
internal/extra/injector.go
Normal file
195
internal/extra/injector.go
Normal file
@@ -0,0 +1,195 @@
|
||||
package extra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
"fmt"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"nxcaldav/internal/backend"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
buffer *bytes.Buffer
|
||||
status int
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Write(b []byte) (int, error) {
|
||||
return rw.buffer.Write(b)
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(status int) {
|
||||
rw.status = status
|
||||
}
|
||||
|
||||
// Add Color To Calendar Propfind
|
||||
func InjectColor(r *http.Request, ctx context.Context, handler http.Handler, w http.ResponseWriter, be *backend.DBBackend) {
|
||||
reqBody, _ := io.ReadAll(r.Body)
|
||||
r.Body = io.NopCloser(bytes.NewBuffer(reqBody))
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
rw := &responseWriter{w, buf, http.StatusOK}
|
||||
handler.ServeHTTP(rw, r.WithContext(ctx))
|
||||
|
||||
body := buf.Bytes()
|
||||
|
||||
// this models after the Radicale Response, largely AI code
|
||||
// 1. Add namespaces to the root multistatus tag
|
||||
reMultistatus := regexp.MustCompile(`(<[a-zA-Z0-9]*:?multistatus)`)
|
||||
body = reMultistatus.ReplaceAll(body, []byte(`$1 xmlns:ICAL="http://apple.com/ns/ical/" xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:CS="http://calendarserver.org/ns/"`))
|
||||
|
||||
// 2. Response processing
|
||||
reResponse := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?response.*?>.*?</[a-zA-Z0-9]*:?response>`)
|
||||
reHref := regexp.MustCompile(`<[a-zA-Z0-9]*:?href.*?>(.*?)</[a-zA-Z0-9]*:?href>`)
|
||||
rePropstat := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?propstat.*?>.*?</[a-zA-Z0-9]*:?propstat>`)
|
||||
reStatusOk := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?status.*?>HTTP/1.1 200 OK</[a-zA-Z0-9]*:?status>`)
|
||||
reProp := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?prop.*?>.*?</[a-zA-Z0-9]*:?prop>`)
|
||||
rePropClose := regexp.MustCompile(`</[a-zA-Z0-9]*:?prop>`)
|
||||
|
||||
body = reResponse.ReplaceAllFunc(body, func(resp []byte) []byte {
|
||||
hrefMatch := reHref.FindSubmatch(resp)
|
||||
if len(hrefMatch) < 2 {
|
||||
return resp
|
||||
}
|
||||
href := string(hrefMatch[1])
|
||||
color := be.GetColor(r.Context(), href)
|
||||
if color == "" {
|
||||
return resp
|
||||
}
|
||||
|
||||
// 1. Strip any existing conflicting tags that might be in 404 blocks
|
||||
reStrip := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order).*?/>|<[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order).*?>.*?</[a-zA-Z0-9]*:?(calendar-color|getctag|calendar-order)>`)
|
||||
resp = reStrip.ReplaceAll(resp, []byte(""))
|
||||
|
||||
fullColor := strings.ToLower(color)
|
||||
if len(fullColor) == 7 && strings.HasPrefix(fullColor, "#") {
|
||||
fullColor += "ff"
|
||||
}
|
||||
// Prepare the properties to inject
|
||||
props := fmt.Sprintf("<ICAL:calendar-color>%s</ICAL:calendar-color>", fullColor)
|
||||
props += fmt.Sprintf("<C:calendar-color>%s</C:calendar-color>", fullColor)
|
||||
props += "<ICAL:calendar-order>0</ICAL:calendar-order>"
|
||||
props += fmt.Sprintf("<CS:getctag>\"%d\"</CS:getctag>", time.Now().Unix())
|
||||
|
||||
// 2. Try to inject into an existing 200 OK propstat
|
||||
has200 := false
|
||||
resp = rePropstat.ReplaceAllFunc(resp, func(ps []byte) []byte {
|
||||
if reStatusOk.Match(ps) {
|
||||
has200 = true
|
||||
return reProp.ReplaceAllFunc(ps, func(prop []byte) []byte {
|
||||
return rePropClose.ReplaceAllFunc(prop, func(closeTag []byte) []byte {
|
||||
return append([]byte(props), closeTag...)
|
||||
})
|
||||
})
|
||||
}
|
||||
return ps
|
||||
})
|
||||
|
||||
// 3. If no 200 OK propstat was found, create one
|
||||
if !has200 {
|
||||
newPropstat := fmt.Sprintf("<propstat xmlns=\"DAV:\"><prop>%s</prop><status>HTTP/1.1 200 OK</status></propstat>", props)
|
||||
reResponseClose := regexp.MustCompile(`</[a-zA-Z0-9]*:?response>`)
|
||||
resp = reResponseClose.ReplaceAllFunc(resp, func(closeTag []byte) []byte {
|
||||
return append([]byte(newPropstat), closeTag...)
|
||||
})
|
||||
}
|
||||
|
||||
return resp
|
||||
})
|
||||
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
|
||||
w.WriteHeader(rw.status)
|
||||
w.Write(body)
|
||||
}
|
||||
|
||||
// modify caledar probfind
|
||||
//
|
||||
// this again modeled after the Radicale response
|
||||
// Largly AI generated agian
|
||||
func HandleDiscoveryPropfind(r *http.Request, ctx context.Context, handler http.Handler, w http.ResponseWriter, be *backend.DBBackend) {
|
||||
reqBody, _ := io.ReadAll(r.Body)
|
||||
r.Body = io.NopCloser(bytes.NewBuffer(reqBody))
|
||||
|
||||
buf := &bytes.Buffer{}
|
||||
rw := &responseWriter{w, buf, http.StatusOK}
|
||||
handler.ServeHTTP(rw, r.WithContext(ctx))
|
||||
|
||||
body := buf.Bytes()
|
||||
|
||||
// 1. Unconditionally add namespaces to root tag
|
||||
reMultistatus := regexp.MustCompile(`(<[a-zA-Z0-9]*:?multistatus)`)
|
||||
body = reMultistatus.ReplaceAll(body, []byte(`$1 xmlns:C="urn:ietf:params:xml:ns:caldav" xmlns:card="urn:ietf:params:xml:ns:carddav"`))
|
||||
|
||||
// 2. Response processing
|
||||
reResponse := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?response.*?>.*?</[a-zA-Z0-9]*:?response>`)
|
||||
reHref := regexp.MustCompile(`<[a-zA-Z0-9]*:?href.*?>(.*?)</[a-zA-Z0-9]*:?href>`)
|
||||
rePropstat := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?propstat.*?>.*?</[a-zA-Z0-9]*:?propstat>`)
|
||||
reStatusOk := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?status.*?>HTTP/1.1 200 OK</[a-zA-Z0-9]*:?status>`)
|
||||
reProp := regexp.MustCompile(`(?s)<[a-zA-Z0-9]*:?prop.*?>.*?</[a-zA-Z0-9]*:?prop>`)
|
||||
rePropClose := regexp.MustCompile(`</[a-zA-Z0-9]*:?prop>`)
|
||||
|
||||
body = reResponse.ReplaceAllFunc(body, func(resp []byte) []byte {
|
||||
hrefMatch := reHref.FindSubmatch(resp)
|
||||
if len(hrefMatch) < 2 {
|
||||
return resp
|
||||
}
|
||||
|
||||
calHome, _ := be.CalendarHomeSetPath(ctx)
|
||||
cardHome, _ := be.AddressBookHomeSetPath(ctx)
|
||||
|
||||
// Strip these tags ONLY from non-200 propstats
|
||||
resp = rePropstat.ReplaceAllFunc(resp, func(ps []byte) []byte {
|
||||
if !reStatusOk.Match(ps) {
|
||||
reTags := regexp.MustCompile(`(?s)<[a-zA-Z0-9:]*(calendar-home-set|addressbook-home-set).*?/>|<[a-zA-Z0-9:]*(calendar-home-set|addressbook-home-set).*?>.*?</[a-zA-Z0-9:]*(calendar-home-set|addressbook-home-set)>`)
|
||||
return reTags.ReplaceAll(ps, []byte(""))
|
||||
}
|
||||
return ps
|
||||
})
|
||||
|
||||
props := ""
|
||||
if calHome != "" && !strings.Contains(string(resp), "calendar-home-set") {
|
||||
props += fmt.Sprintf("<C:calendar-home-set><href xmlns=\"DAV:\">%s</href></C:calendar-home-set>", calHome)
|
||||
}
|
||||
if cardHome != "" && !strings.Contains(string(resp), "addressbook-home-set") {
|
||||
props += fmt.Sprintf("<card:addressbook-home-set><href xmlns=\"DAV:\">%s</href></card:addressbook-home-set>", cardHome)
|
||||
}
|
||||
|
||||
if props == "" {
|
||||
return resp
|
||||
}
|
||||
|
||||
// 2. Try to inject into an existing 200 OK propstat
|
||||
has200 := false
|
||||
resp = rePropstat.ReplaceAllFunc(resp, func(ps []byte) []byte {
|
||||
if reStatusOk.Match(ps) {
|
||||
has200 = true
|
||||
return reProp.ReplaceAllFunc(ps, func(prop []byte) []byte {
|
||||
return rePropClose.ReplaceAllFunc(prop, func(closeTag []byte) []byte {
|
||||
return append([]byte(props), closeTag...)
|
||||
})
|
||||
})
|
||||
}
|
||||
return ps
|
||||
})
|
||||
|
||||
// 3. If no 200 OK propstat was found, create one
|
||||
if !has200 {
|
||||
newPropstat := fmt.Sprintf("<propstat xmlns=\"DAV:\"><prop>%s</prop><status>HTTP/1.1 200 OK</status></propstat>", props)
|
||||
reResponseClose := regexp.MustCompile(`</[a-zA-Z0-9]*:?response>`)
|
||||
resp = reResponseClose.ReplaceAllFunc(resp, func(closeTag []byte) []byte {
|
||||
return append([]byte(newPropstat), closeTag...)
|
||||
})
|
||||
}
|
||||
return resp
|
||||
})
|
||||
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(body)))
|
||||
w.WriteHeader(rw.status)
|
||||
w.Write(body)
|
||||
}
|
||||
Reference in New Issue
Block a user