hivedav

A curlable free/busy scheduler with CalDAV integration
git clone https://git.in0rdr.ch/hivedav.git
Log | Files | Refs | Pull requests |Archive | README | LICENSE

commit cb30c46aa50f8f9c3661480467df4067abdc0a56
parent a5bd60f719958c77bb7f9338c926574ce9f1a1fc
Author: Andreas Gruhler <andreas.gruhler@adfinis.com>
Date:   Wed,  4 Oct 2023 23:16:43 +0200

feat: success msg and ics folding

Diffstat:
Mserver.go | 97++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
1 file changed, 87 insertions(+), 10 deletions(-)

diff --git a/server.go b/server.go @@ -15,6 +15,7 @@ import ( "hivedav/caldav" "hivedav/config" "hivedav/tzdb" + "html" "html/template" "io" "log" @@ -43,9 +44,9 @@ type TableData struct { } type CubicleFormData struct { - Dtstart string + Dtstart string DtstartInput string - Version string + Version string } type IcsData struct { @@ -631,8 +632,8 @@ func (s *Server) CubicleForm(w http.ResponseWriter, req *http.Request, ps httpro icsData := CubicleFormData{ DtstartInput: dtStartTimeLocal.Format("2006-01-02T15:04"), - Dtstart: dtstart, - Version: hivedavVersion, + Dtstart: dtstart, + Version: hivedavVersion, } w.Header().Set("Content-Type", "text/html") @@ -674,8 +675,7 @@ func (s *Server) BookCubicle(w http.ResponseWriter, req *http.Request, ps httpro } mail := req.FormValue("mail") - msg := req.FormValue("msg") - log.Printf("Form: %+v\n", req.Form) + msg := html.UnescapeString(req.FormValue("msg")) if mail == "" || msg == "" { http.Error(w, fmt.Sprintf("Mail and msg required, try again: 'curl %s/book/%s -F 'mail=' -F 'msg='\n", s.config.HiveDavHost, dtstart), http.StatusBadRequest) @@ -688,7 +688,9 @@ func (s *Server) BookCubicle(w http.ResponseWriter, req *http.Request, ps httpro http.Error(w, err.Error(), http.StatusInternalServerError) return } - // TODO: print success message to user + + zone, _ := dtStartTimeLocal.Zone() + io.WriteString(w, fmt.Sprintf("Thank you for booking on %s %s\n", dtStartTimeLocal.Format("02 Jan 2006 15:04"), zone)) return } @@ -767,13 +769,13 @@ func (s *Server) sendMail(recepient string, msg string, dtstart time.Time) error DstTzOffsetTo: dstTzOffsetTo, Summary: s.config.BookingSummary, Reminder: s.config.BookingReminder, - Description: msg, + Description: fold(msg), Location: fmt.Sprintf("%s/%s", s.config.BookingLocation, strings.ToUpper(uuid)), Organizer: s.config.CaldavUser, Attendee: recepient, } - log.Printf("IcsData: +%v\n", icsData) + log.Printf("DESCRIPTION: %v\n", icsData.Description) err = icsTmpl.Execute(icsFile, icsData) if err != nil { @@ -783,7 +785,6 @@ func (s *Server) sendMail(recepient string, msg string, dtstart time.Time) error e.AttachFile(tmpFileName) // https://datatracker.ietf.org/doc/html/rfc2447#section-2.4 e.Attachments[0].ContentType = "text/calendar; charset=utf-8; method=REQUEST" - log.Printf("%s\n", e.Attachments[0].Content) smtpServer := fmt.Sprintf("%s:%d", s.config.SmtpHost, s.config.SmtpPort) auth := smtp.PlainAuth("", s.config.SmtpUser, s.config.SmtpPassword, s.config.SmtpHost) @@ -800,6 +801,82 @@ func (s *Server) sendMail(recepient string, msg string, dtstart time.Time) error return nil } +// simple string to rune and hex conversion in Python +// i=ord("\0"); hex(i) +func fold(s string) string { + // convert the string unicode code points + runeStr := []rune(s) + + // create buffer for escaped result TEXT + buf := make([]rune, 1) + + // bufl is the current buffer size incl. \0 + var bufl = 1 + // i is the iterator in s + var i = 0 + + // escch is the char to be escaped, + // only written when esc=true + escch := rune(0x0) // \0 + esc := false + + for i < len(runeStr)-1 || esc { + buf = append(buf, rune(0x0)) + bufl++ + + if (bufl > 1) && ((bufl % 77) == 0) { + // break lines after 75 chars + // split between any two characters by inserting a CRLF + // immediately followed by a white space character + buf[bufl-2] = rune(0xa) // newline '\n' + escch = rune(0x20) // whitespace ' ' + esc = true + continue + } + + if esc { + // only escape char, do not advance iterator i + buf[bufl-2] = escch + esc = false + } else { + // escape characters + // https://datatracker.ietf.org/doc/html/rfc5545#section-3.3.11 + switch runeStr[i] { + case rune(0x5c): // backslash `\` + buf[bufl-2] = rune(0x5c) + escch = rune(0x5c) + esc = true + break + case rune(0x3b): // semicolon ';' + buf[bufl-2] = rune(0x5c) + escch = rune(0x3b) + esc = true + break + case rune(0x2c): // comma ',' + buf[bufl-2] = rune(0x5c) + escch = rune(0x2c) + esc = true + break + case rune(0xa): // newline '\n' + buf[bufl-2] = rune(0x5c) // backslash '\' + escch = rune(0x6e) // literal 'n' + esc = true + break + default: + // write regular character from runeStr + buf[bufl-2] = runeStr[i] + break + } + i++ + } + + // terminate the char string in any case (esc or not) + buf[bufl-1] = rune(0x0) + } + + return string(buf) +} + // https://datatracker.ietf.org/doc/html/rfc7986#section-5.3 func genUid(n int) (string, error) { bytes := make([]byte, n)