(w http.ResponseWriter, r *http.Request)
| 1291 | } |
| 1292 | |
| 1293 | func (a *API) handleFeedback(w http.ResponseWriter, r *http.Request) { |
| 1294 | ip := clientIP(r) |
| 1295 | if ok, retryAfter := a.feedbackLimit.AllowWithRetryAfter(ip); !ok { |
| 1296 | writeTooManyRequests(w, retryAfter, "rate limit exceeded — max 10 feedback submissions per hour per IP") |
| 1297 | return |
| 1298 | } |
| 1299 | |
| 1300 | var req struct { |
| 1301 | Email string `json:"email"` |
| 1302 | Category string `json:"category"` |
| 1303 | Message string `json:"message"` |
| 1304 | } |
| 1305 | if err := readJSON(w, r, &req, maxRequestBytesSmall); err != nil { |
| 1306 | http.Error(w, "invalid request body", http.StatusBadRequest) |
| 1307 | return |
| 1308 | } |
| 1309 | if strings.TrimSpace(req.Message) == "" { |
| 1310 | http.Error(w, "message is required", http.StatusBadRequest) |
| 1311 | return |
| 1312 | } |
| 1313 | if len([]rune(req.Message)) > 5000 { |
| 1314 | http.Error(w, "message too long (max 5000 characters)", http.StatusBadRequest) |
| 1315 | return |
| 1316 | } |
| 1317 | if len(req.Email) > 254 { |
| 1318 | http.Error(w, "email too long", http.StatusBadRequest) |
| 1319 | return |
| 1320 | } |
| 1321 | if req.Category == "" { |
| 1322 | req.Category = "general" |
| 1323 | } |
| 1324 | if req.Category != "bug" && req.Category != "feature" && req.Category != "general" { |
| 1325 | http.Error(w, "category must be bug, feature, or general", http.StatusBadRequest) |
| 1326 | return |
| 1327 | } |
| 1328 | |
| 1329 | // If user is authenticated, use their email |
| 1330 | if a.userAuth != nil { |
| 1331 | if user := a.userAuth.AuthenticateRequest(r); user != nil { |
| 1332 | if req.Email == "" { |
| 1333 | req.Email = user.Email |
| 1334 | } |
| 1335 | } |
| 1336 | } |
| 1337 | |
| 1338 | // Create GitHub issue |
| 1339 | labelMap := map[string]string{ |
| 1340 | "bug": "bug", |
| 1341 | "feature": "enhancement", |
| 1342 | "general": "feedback", |
| 1343 | } |
| 1344 | label := labelMap[req.Category] |
| 1345 | |
| 1346 | // Sanitize user input to prevent GitHub @mention spam and Markdown injection |
| 1347 | sanitize := func(s string) string { |
| 1348 | return strings.ReplaceAll(s, "@", "@\u200B") // zero-width space breaks @mentions |
| 1349 | } |
| 1350 |
nothing calls this directly
no test coverage detected