mirror of
https://github.com/nais/wonderwall.git
synced 2026-05-08 17:37:01 +00:00
105 lines
2.6 KiB
Go
105 lines
2.6 KiB
Go
package http
|
|
|
|
import (
|
|
"net/http"
|
|
"net/url"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/nais/wonderwall/pkg/cookie"
|
|
)
|
|
|
|
// IsNavigationRequest checks if the request is a navigation request by using Sec-Fetch headers.
|
|
// This is used to separate between redirects for browser navigation and redirects for resource requests (e.g., Fetch or XHR).
|
|
// We fall back to checking the Accept header if the browser doesn't support fetch metadata.
|
|
func IsNavigationRequest(r *http.Request) bool {
|
|
// we assume that navigation requests are always GET requests
|
|
if r.Method != http.MethodGet {
|
|
return false
|
|
}
|
|
|
|
mode := r.Header.Get("Sec-Fetch-Mode")
|
|
dest := r.Header.Get("Sec-Fetch-Dest")
|
|
if mode == "" && dest == "" {
|
|
return Accepts(r, "text/html")
|
|
}
|
|
|
|
return mode == "navigate" && dest == "document"
|
|
}
|
|
|
|
func HasSecFetchMetadata(r *http.Request) bool {
|
|
return r.Header.Get("Sec-Fetch-Mode") != "" && r.Header.Get("Sec-Fetch-Dest") != ""
|
|
}
|
|
|
|
func Accepts(r *http.Request, accepted ...string) bool {
|
|
// iterate over all Accept headers
|
|
for _, header := range r.Header.Values("Accept") {
|
|
// iterate over all comma-separated values in a single Accept header
|
|
for v := range strings.SplitSeq(header, ",") {
|
|
v = strings.ToLower(v)
|
|
v = strings.TrimSpace(v)
|
|
v = strings.Split(v, ";")[0]
|
|
|
|
if slices.Contains(accepted, v) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// Attributes returns a map of interesting properties for the request.
|
|
func Attributes(r *http.Request) map[string]any {
|
|
return map[string]any{
|
|
"request.cookies": nonEmptyRequestCookies(r),
|
|
"request.host": r.Host,
|
|
"request.is_navigational": IsNavigationRequest(r),
|
|
"request.method": r.Method,
|
|
"request.path": r.URL.Path,
|
|
"request.protocol": r.Proto,
|
|
"request.referer": refererStripped(r),
|
|
"request.sec_fetch_dest": r.Header.Get("Sec-Fetch-Dest"),
|
|
"request.sec_fetch_mode": r.Header.Get("Sec-Fetch-Mode"),
|
|
"request.sec_fetch_site": r.Header.Get("Sec-Fetch-Site"),
|
|
"request.user_agent": r.UserAgent(),
|
|
}
|
|
}
|
|
|
|
func nonEmptyRequestCookies(r *http.Request) string {
|
|
result := make([]string, 0)
|
|
|
|
for _, c := range r.Cookies() {
|
|
if !isRelevantCookie(c.Name) || len(c.Value) <= 0 {
|
|
continue
|
|
}
|
|
|
|
result = append(result, c.Name)
|
|
}
|
|
|
|
return strings.Join(result, ", ")
|
|
}
|
|
|
|
func isRelevantCookie(name string) bool {
|
|
switch name {
|
|
case cookie.Session,
|
|
cookie.Login,
|
|
cookie.Logout:
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func refererStripped(r *http.Request) string {
|
|
referer := r.Referer()
|
|
refererUrl, err := url.Parse(referer)
|
|
if err == nil {
|
|
refererUrl.RawQuery = ""
|
|
refererUrl.RawFragment = ""
|
|
referer = refererUrl.String()
|
|
}
|
|
|
|
return referer
|
|
}
|