-
Notifications
You must be signed in to change notification settings - Fork 0
/
handlers.go
142 lines (119 loc) · 4.21 KB
/
handlers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package main
import (
"encoding/json"
"log"
"net/http"
"reflect"
"strings"
"time"
)
type NavTimingReport struct {
Details NavTimingDetails `json:"nav-timing" statName:"navTiming"`
Page string `json:"page-uri" statName:"pageUri"`
Referer string `statName:"referer"`
UserAgent string `statName:"userAgent`
}
type NavTimingDetails struct {
DNS int64 `json:"dns" statName:"dns"`
Connect int64 `json:"connect" statName:"connect"`
TTFB int64 `json:"ttfb" statName:"ttfb"`
BasePage int64 `json:"basePage" statName:"basePage"`
FrontEnd int64 `json:"frontEnd" statName:"frontEnd"`
}
// for Navigation Timing API
func NavTimingHandler(recorders []Recorder) http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
var timing NavTimingReport
if req.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
w.Header().Set("Allow", "POST")
return
}
if err := json.NewDecoder(req.Body).Decode(&timing); err != nil {
http.Error(w, "Error parsing JSON", http.StatusBadRequest)
return
}
// You could consider this a flaw, but we don't send the stat anywhere
// if it can't go to one of the recorders.
for _, recorder := range recorders {
if !recorder.validStat(timing.Page) {
http.Error(w, "Invalid page-uri passed", http.StatusNotAcceptable)
return
}
}
// for each recorder we're sending all the NavTimingDetails stats
t := reflect.TypeOf(timing.Details)
v := reflect.ValueOf(timing.Details)
for i := 0; i < v.NumField(); i++ {
for _, recorder := range recorders {
stat := recorder.cleanURI(timing.Page) + t.Field(i).Tag.Get("statName")
val := v.Field(i).Int()
recorder.pushStat(stat, val)
}
}
}
}
type JsErrorReport struct {
PageURI string `json:"page-uri"`
QueryString string `json:"query-string"`
Details JsError `json:"js-error"`
ReportTime time.Time
}
type JsError struct {
UserAgent string `json:"user-agent"`
ErrorType string `json:"error-type"`
Description string `json:"description"`
}
func JsErrorReportHandler() http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
var jsError JsErrorReport
if req.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
w.Header().Set("Allow", "POST")
return
}
if err := json.NewDecoder(req.Body).Decode(&jsError); err != nil {
http.Error(w, "Error parsing JSON", http.StatusBadRequest)
return
}
jsError.ReportTime = time.Now().UTC()
jsError.Details.UserAgent = req.UserAgent()
// do something smart with the error
dets, _ := json.Marshal(jsError)
log.Println(strings.Join(req.Header["X-Real-Ip"], ""), "encountered a javascript error:", string(dets))
}
}
type CSPReport struct {
Details CSPDetails `json:"csp-report" statName:"cspReport"`
ReportTime time.Time `statName:"dateTime"`
}
type CSPDetails struct {
DocumentUri string `json:"document-uri" statName:"documentUri" validate:"min=1,max=200"`
Referrer string `json:"referrer" statName:"referrer" validate:"max=200"`
BlockedUri string `json:"blocked-uri" statName:"blockedUri" validate:"max=200"`
ViolatedDirective string `json:"violated-directive" statName:"violatedDirective" validate:"min=1,max=200,regexp=^[a-z0-9 '/\\*\\.:;-]+$"`
}
// for Content Security Policy
func CSPReportHandler() http.HandlerFunc {
return func(w http.ResponseWriter, req *http.Request) {
var report CSPReport
if req.Method != "POST" {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
w.Header().Set("Allow", "POST")
return
}
if err := json.NewDecoder(req.Body).Decode(&report); err != nil {
http.Error(w, "Error parsing JSON", http.StatusBadRequest)
return
}
report.ReportTime = time.Now().UTC()
// if validationError := validator.Validate(report); validationError != nil {
// log.Println("Request failed validation:", validationError)
// log.Println("Failed with report:", report)
// http.Error(w, "Unable to validate JSON", http.StatusBadRequest)
// return
// }
// do something smart with the report
log.Println("policy violation from", strings.Join(req.Header["X-Real-Ip"], ""), "was:", report.Details.ViolatedDirective)
}
}