feat(03-01): HTTP handler POST /hook with validation and body size limit
- handleHook validates POST method (405 on others) - MaxBytesReader limits body to 64KB (400 on overflow) - JSON decode errors return 400 - Valid payloads update registry via processHookEvent - 4 tests cover OK, method not allowed, bad JSON, body too large Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
26
hook.go
26
hook.go
@@ -1,6 +1,10 @@
|
||||
package main
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HookEvent represents the JSON payload sent by Claude Code hooks.
|
||||
type HookEvent struct {
|
||||
@@ -16,6 +20,26 @@ type HookEvent struct {
|
||||
ToolName string `json:"tool_name,omitempty"`
|
||||
}
|
||||
|
||||
// handleHook is the HTTP handler for POST /hook.
|
||||
// Validates method, limits body size, decodes JSON, and dispatches to processHookEvent.
|
||||
func (d *Daemon) handleHook(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 64*1024)
|
||||
|
||||
var event HookEvent
|
||||
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
d.processHookEvent(event)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// processHookEvent maps a Claude Code hook event to a registry update.
|
||||
// Ignores empty session IDs and unknown event types.
|
||||
func (d *Daemon) processHookEvent(event HookEvent) {
|
||||
|
||||
75
hook_test.go
75
hook_test.go
@@ -1,6 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -225,3 +228,75 @@ func TestUpdateFromHookClearsWaitingSince(t *testing.T) {
|
||||
t.Errorf("WaitingSince should be nil after returning to Working, got %v", list[0].WaitingSince)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleHookPostOK(t *testing.T) {
|
||||
d := newTestDaemon(t)
|
||||
|
||||
body := `{
|
||||
"session_id": "sess-1",
|
||||
"hook_event_name": "Notification",
|
||||
"notification_type": "permission_prompt",
|
||||
"cwd": "/home/user/project"
|
||||
}`
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/hook", strings.NewReader(body))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
d.handleHook(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", w.Code)
|
||||
}
|
||||
|
||||
list := d.registry.List()
|
||||
if len(list) != 1 {
|
||||
t.Fatalf("registry len = %d, want 1", len(list))
|
||||
}
|
||||
if list[0].WaitType != "permission" {
|
||||
t.Errorf("WaitType = %q, want %q", list[0].WaitType, "permission")
|
||||
}
|
||||
if list[0].State != "Needs Input" {
|
||||
t.Errorf("State = %q, want %q", list[0].State, "Needs Input")
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleHookMethodNotAllowed(t *testing.T) {
|
||||
d := newTestDaemon(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/hook", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
d.handleHook(w, req)
|
||||
|
||||
if w.Code != http.StatusMethodNotAllowed {
|
||||
t.Errorf("status = %d, want 405", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleHookBadJSON(t *testing.T) {
|
||||
d := newTestDaemon(t)
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/hook", strings.NewReader("not json"))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
d.handleHook(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("status = %d, want 400", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleHookBodyTooLarge(t *testing.T) {
|
||||
d := newTestDaemon(t)
|
||||
|
||||
// 65KB body exceeds the 64KB limit
|
||||
bigBody := strings.Repeat("x", 65*1024)
|
||||
req := httptest.NewRequest(http.MethodPost, "/hook", strings.NewReader(bigBody))
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
d.handleHook(w, req)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("status = %d, want 400", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user