package handlers import ( "encoding/json" "log/slog" "net/http" "strings" "unicode/utf8" "github.com/google/uuid" ) func writeJSON(w http.ResponseWriter, status int, v any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(v) } func writeError(w http.ResponseWriter, status int, msg string) { writeJSON(w, status, map[string]string{"error": msg}) } // internalError logs the real error and returns a generic message to the client. func internalError(w http.ResponseWriter, msg string, err error) { slog.Error(msg, "error", err) writeError(w, http.StatusInternalServerError, "internal error") } // parsePathUUID extracts a UUID from the URL path using PathValue func parsePathUUID(r *http.Request, key string) (uuid.UUID, error) { return uuid.Parse(r.PathValue(key)) } // parseUUID parses a UUID string func parseUUID(s string) (uuid.UUID, error) { return uuid.Parse(s) } // --- Input validation helpers --- const ( maxTitleLen = 500 maxDescriptionLen = 10000 maxCaseNumberLen = 100 maxSearchLen = 200 maxPaginationLimit = 100 ) // validateStringLength checks if a string exceeds the given max length. func validateStringLength(field, value string, maxLen int) string { if utf8.RuneCountInString(value) > maxLen { return field + " exceeds maximum length" } return "" } // clampPagination enforces sane pagination defaults and limits. func clampPagination(limit, offset int) (int, int) { if limit <= 0 { limit = 20 } if limit > maxPaginationLimit { limit = maxPaginationLimit } if offset < 0 { offset = 0 } return limit, offset } // sanitizeFilename removes characters unsafe for Content-Disposition headers. func sanitizeFilename(name string) string { // Remove control characters, quotes, and backslashes var b strings.Builder for _, r := range name { if r < 32 || r == '"' || r == '\\' || r == '/' { b.WriteRune('_') } else { b.WriteRune(r) } } return b.String() } // maskSettingsPassword masks the CalDAV password in tenant settings JSON before returning to clients. func maskSettingsPassword(settings json.RawMessage) json.RawMessage { if len(settings) == 0 { return settings } var m map[string]json.RawMessage if err := json.Unmarshal(settings, &m); err != nil { return settings } caldavRaw, ok := m["caldav"] if !ok { return settings } var caldav map[string]json.RawMessage if err := json.Unmarshal(caldavRaw, &caldav); err != nil { return settings } if _, ok := caldav["password"]; ok { caldav["password"], _ = json.Marshal("********") } m["caldav"], _ = json.Marshal(caldav) result, _ := json.Marshal(m) return result }