You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
4.2 KiB
172 lines
4.2 KiB
1 year ago
|
package rsp
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"sort"
|
||
|
"strconv"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
var errInvalidTypeSubtype = "accept: Invalid type '%s'."
|
||
|
|
||
|
// headerAccept represents a parsed headerAccept(-Charset|-Encoding|-Language) header.
|
||
|
type headerAccept struct {
|
||
|
Type, Subtype string
|
||
|
Q float64
|
||
|
Extensions map[string]string
|
||
|
}
|
||
|
|
||
|
// AcceptSlice is a slice of headerAccept.
|
||
|
type acceptSlice []headerAccept
|
||
|
|
||
|
func Accepts(header, expect string) bool {
|
||
|
_, typeSubtype, err := parseMediaRange(expect)
|
||
|
if err != nil {
|
||
|
return false
|
||
|
}
|
||
|
return accepts(parse(header), typeSubtype[0], typeSubtype[1])
|
||
|
}
|
||
|
|
||
|
func accepts(slice acceptSlice, typ, sub string) bool {
|
||
|
for _, a := range slice {
|
||
|
if a.Type != typ {
|
||
|
continue
|
||
|
}
|
||
|
if a.Subtype == "*" || a.Subtype == sub {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// parses a HTTP headerAccept(-Charset|-Encoding|-Language) header and returns
|
||
|
// AcceptSlice, sorted in decreasing order of preference. If the header lists
|
||
|
// multiple types that have the same level of preference (same specificity of
|
||
|
// type and subtype, same qvalue, and same number of extensions), the type
|
||
|
// that was listed in the header first comes first in the returned value.
|
||
|
//
|
||
|
// See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14 for more information.
|
||
|
func parse(header string) acceptSlice {
|
||
|
mediaRanges := strings.Split(header, ",")
|
||
|
accepted := make(acceptSlice, 0, len(mediaRanges))
|
||
|
for _, mediaRange := range mediaRanges {
|
||
|
rangeParams, typeSubtype, err := parseMediaRange(mediaRange)
|
||
|
if err != nil {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
accept := headerAccept{
|
||
|
Type: typeSubtype[0],
|
||
|
Subtype: typeSubtype[1],
|
||
|
Q: 1.0,
|
||
|
Extensions: make(map[string]string),
|
||
|
}
|
||
|
|
||
|
// If there is only one rangeParams, we can stop here.
|
||
|
if len(rangeParams) == 1 {
|
||
|
accepted = append(accepted, accept)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
// Validate the rangeParams.
|
||
|
validParams := true
|
||
|
for _, v := range rangeParams[1:] {
|
||
|
nameVal := strings.SplitN(v, "=", 2)
|
||
|
if len(nameVal) != 2 {
|
||
|
validParams = false
|
||
|
break
|
||
|
}
|
||
|
nameVal[1] = strings.TrimSpace(nameVal[1])
|
||
|
if name := strings.TrimSpace(nameVal[0]); name == "q" {
|
||
|
qval, err := strconv.ParseFloat(nameVal[1], 64)
|
||
|
if err != nil || qval < 0 {
|
||
|
validParams = false
|
||
|
break
|
||
|
}
|
||
|
if qval > 1.0 {
|
||
|
qval = 1.0
|
||
|
}
|
||
|
accept.Q = qval
|
||
|
} else {
|
||
|
accept.Extensions[name] = nameVal[1]
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if validParams {
|
||
|
accepted = append(accepted, accept)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sort.Sort(accepted)
|
||
|
return accepted
|
||
|
}
|
||
|
|
||
|
// Len implements the Len() method of the Sort interface.
|
||
|
func (a acceptSlice) Len() int {
|
||
|
return len(a)
|
||
|
}
|
||
|
|
||
|
// Less implements the Less() method of the Sort interface. Elements are
|
||
|
// sorted in order of decreasing preference.
|
||
|
func (a acceptSlice) Less(i, j int) bool {
|
||
|
// Higher qvalues come first.
|
||
|
if a[i].Q > a[j].Q {
|
||
|
return true
|
||
|
} else if a[i].Q < a[j].Q {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Specific types come before wildcard types.
|
||
|
if a[i].Type != "*" && a[j].Type == "*" {
|
||
|
return true
|
||
|
} else if a[i].Type == "*" && a[j].Type != "*" {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Specific subtypes come before wildcard subtypes.
|
||
|
if a[i].Subtype != "*" && a[j].Subtype == "*" {
|
||
|
return true
|
||
|
} else if a[i].Subtype == "*" && a[j].Subtype != "*" {
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// A lot of extensions comes before not a lot of extensions.
|
||
|
if len(a[i].Extensions) > len(a[j].Extensions) {
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
// Swap implements the Swap() method of the Sort interface.
|
||
|
func (a acceptSlice) Swap(i, j int) {
|
||
|
a[i], a[j] = a[j], a[i]
|
||
|
}
|
||
|
|
||
|
// parseMediaRange parses the provided media range, and on success returns the
|
||
|
// parsed range params and type/subtype pair.
|
||
|
func parseMediaRange(mediaRange string) (rangeParams, typeSubtype []string, err error) {
|
||
|
rangeParams = strings.Split(mediaRange, ";")
|
||
|
typeSubtype = strings.Split(rangeParams[0], "/")
|
||
|
|
||
|
// typeSubtype should have a length of exactly two.
|
||
|
if len(typeSubtype) > 2 {
|
||
|
err = fmt.Errorf(errInvalidTypeSubtype, rangeParams[0])
|
||
|
return
|
||
|
} else {
|
||
|
typeSubtype = append(typeSubtype, "*")
|
||
|
}
|
||
|
|
||
|
// Sanitize typeSubtype.
|
||
|
typeSubtype[0] = strings.TrimSpace(typeSubtype[0])
|
||
|
typeSubtype[1] = strings.TrimSpace(typeSubtype[1])
|
||
|
if typeSubtype[0] == "" {
|
||
|
typeSubtype[0] = "*"
|
||
|
}
|
||
|
if typeSubtype[1] == "" {
|
||
|
typeSubtype[1] = "*"
|
||
|
}
|
||
|
|
||
|
return
|
||
|
}
|