This adds a pattern for creating pages that require authentication. The settings page is currently empty, but it's helpful to figure out how to do this pattern.
156 lines
3.9 KiB
Go
156 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// acceptValue represents a parsed accept header value
|
|
type acceptValue struct {
|
|
value string // the media type or value
|
|
quality float64 // quality factor (0.0 to 1.0)
|
|
specificity int // specificity level for sorting
|
|
order int // original order in the accept header
|
|
}
|
|
|
|
// NegotiateContent returns the best content to offer from a set of possible
|
|
// values, based on the preferences represented by the accept values.
|
|
func NegotiateContent(accepts []string, offers []string) string {
|
|
if len(offers) == 0 {
|
|
return ""
|
|
}
|
|
|
|
// If no accept values, return first offer
|
|
if len(accepts) == 0 {
|
|
return offers[0]
|
|
}
|
|
|
|
// Parse accept values (limit to first 32 to avoid DOS)
|
|
acceptValues := parseAcceptValues(accepts)
|
|
if len(acceptValues) > 32 {
|
|
acceptValues = acceptValues[:32]
|
|
}
|
|
|
|
// Find best match
|
|
bestMatch := ""
|
|
bestScore := -1.0
|
|
bestSpecificity := -1
|
|
bestOrder := len(offers) // use offer order as tiebreaker
|
|
|
|
for offerIdx, offer := range offers {
|
|
score, specificity := calculateScore(offer, acceptValues)
|
|
|
|
// Check if this is a better match
|
|
if score > bestScore ||
|
|
(score == bestScore && specificity > bestSpecificity) ||
|
|
(score == bestScore && specificity == bestSpecificity && offerIdx < bestOrder) {
|
|
bestMatch = offer
|
|
bestScore = score
|
|
bestSpecificity = specificity
|
|
bestOrder = offerIdx
|
|
}
|
|
}
|
|
|
|
if bestScore <= 0 {
|
|
return ""
|
|
}
|
|
|
|
return bestMatch
|
|
}
|
|
|
|
// parseAcceptValues parses accept header values into structured format
|
|
func parseAcceptValues(accepts []string) []acceptValue {
|
|
var values []acceptValue
|
|
order := 0
|
|
|
|
for _, accept := range accepts {
|
|
// Split by comma to handle multiple values in one string
|
|
parts := strings.Split(accept, ",")
|
|
for _, part := range parts {
|
|
part = strings.TrimSpace(part)
|
|
if part == "" {
|
|
continue
|
|
}
|
|
|
|
value := acceptValue{
|
|
quality: 1.0, // default quality
|
|
order: order,
|
|
}
|
|
|
|
// Split by semicolon to separate value from parameters
|
|
sections := strings.Split(part, ";")
|
|
value.value = strings.TrimSpace(sections[0])
|
|
|
|
// Parse quality parameter
|
|
for i := 1; i < len(sections); i++ {
|
|
param := strings.TrimSpace(sections[i])
|
|
if strings.HasPrefix(param, "q=") {
|
|
if q, err := strconv.ParseFloat(param[2:], 64); err == nil {
|
|
if q >= 0.0 && q <= 1.0 {
|
|
value.quality = q
|
|
}
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
// Calculate specificity for media types
|
|
value.specificity = calculateSpecificity(value.value)
|
|
|
|
values = append(values, value)
|
|
order++
|
|
}
|
|
}
|
|
|
|
// Sort by quality (desc), then specificity (desc), then order (asc)
|
|
sort.Slice(values, func(i, j int) bool {
|
|
if values[i].quality != values[j].quality {
|
|
return values[i].quality > values[j].quality
|
|
}
|
|
if values[i].specificity != values[j].specificity {
|
|
return values[i].specificity > values[j].specificity
|
|
}
|
|
return values[i].order < values[j].order
|
|
})
|
|
|
|
return values
|
|
}
|
|
|
|
// calculateSpecificity returns specificity level for media type matching
|
|
func calculateSpecificity(mediaType string) int {
|
|
if mediaType == "*/*" {
|
|
return 0 // least specific
|
|
}
|
|
if strings.HasSuffix(mediaType, "/*") {
|
|
return 1 // type wildcard
|
|
}
|
|
return 2 // exact match (most specific)
|
|
}
|
|
|
|
// calculateScore returns the quality score and specificity for an offer against accept values
|
|
func calculateScore(offer string, acceptValues []acceptValue) (float64, int) {
|
|
for _, accept := range acceptValues {
|
|
if matches(offer, accept.value) {
|
|
return accept.quality, accept.specificity
|
|
}
|
|
}
|
|
return 0.0, 0
|
|
}
|
|
|
|
// matches checks if an offer matches an accept value (including wildcards)
|
|
func matches(offer, accept string) bool {
|
|
if accept == "*/*" {
|
|
return true
|
|
}
|
|
|
|
if strings.HasSuffix(accept, "/*") {
|
|
// Type wildcard (e.g., "text/*")
|
|
offerType := strings.Split(offer, "/")[0]
|
|
acceptType := strings.Split(accept, "/")[0]
|
|
return offerType == acceptType
|
|
}
|
|
|
|
// Exact match
|
|
return offer == accept
|
|
}
|