1 // Package httpaccept provides a function to deal with the Accept header. 2 package httpaccept // import "vimagination.zapto.org/httpaccept" 3 4 import ( 5 "net/http" 6 "sort" 7 "strconv" 8 "strings" 9 ) 10 11 const ( 12 any = "*" 13 matchAny = "*/*" 14 accept = "Accept" 15 acceptSplit = "," 16 partSplit = ";" 17 weightPrefix = "q=" 18 ) 19 20 type mimes []mime 21 22 func (m mimes) Len() int { 23 return len(m) 24 } 25 26 func (m mimes) Less(i, j int) bool { 27 return m[j].weight < m[i].weight 28 } 29 30 func (m mimes) Swap(i, j int) { 31 m[i], m[j] = m[j], m[i] 32 } 33 34 type mime struct { 35 mime Mime 36 weight uint16 37 } 38 39 // Mime represents a accepted Mime Type 40 type Mime string 41 42 // Match checks to see whether a given Mime Type matches the value. 43 // 44 // The method allows for wildcards in the subtype sections. 45 func (m Mime) Match(n Mime) bool { 46 if strings.EqualFold(string(m), string(n)) || m == matchAny || n == matchAny { 47 return true 48 } 49 mParts := [2]string{any, any} 50 mPos := strings.IndexByte(string(m), '/') 51 if mPos < 0 { 52 mParts[0] = string(m) 53 } else { 54 mParts[0] = string(m[:mPos]) 55 mParts[1] = string(m[mPos+1:]) 56 } 57 nParts := [2]string{any, any} 58 nPos := strings.IndexByte(string(n), '/') 59 if nPos < 0 { 60 nParts[0] = string(n) 61 } else { 62 nParts[0] = string(n[:nPos]) 63 nParts[1] = string(n[nPos+1:]) 64 } 65 return strings.EqualFold(mParts[0], nParts[0]) && (strings.EqualFold(mParts[1], nParts[1]) || mParts[1] == any || nParts[1] == any) 66 } 67 68 // Handler provides an interface to handle a mime type. 69 // 70 // The mime string (e.g. text/html, application/json, text/plain) is passed to 71 // the handler, which is expected to return true if no more encodings are 72 // required and false otherwise. 73 // 74 // The empty string "" is used to signify when no preference is specified. 75 type Handler interface { 76 Handle(mime Mime) bool 77 } 78 79 // HandlerFunc wraps a func to make it satisfy the Handler interface 80 type HandlerFunc func(Mime) bool 81 82 // Handle calls the underlying func 83 func (h HandlerFunc) Handle(m Mime) bool { 84 return h(m) 85 } 86 87 // InvalidAccept writes the 406 header 88 func InvalidAccept(w http.ResponseWriter) { 89 w.WriteHeader(http.StatusNotAcceptable) 90 } 91 92 // HandleAccept will process the Accept header and calls the given handler for 93 // each mime type until the handler returns true. 94 // 95 // This function returns true when the Handler returns true, false otherwise 96 // 97 // When no Accept header is given the mime string will be the empty string. 98 func HandleAccept(r *http.Request, h Handler) bool { 99 acceptHeader := r.Header.Get(accept) 100 accepts := make(mimes, 0, strings.Count(acceptHeader, acceptSplit)+1) 101 Loop: 102 for _, accept := range strings.Split(acceptHeader, acceptSplit) { 103 parts := strings.Split(strings.TrimSpace(accept), partSplit) 104 name := strings.ToLower(strings.TrimSpace(parts[0])) 105 // check mime string format? 106 if name == "" { 107 continue 108 } 109 var ( 110 qVal float64 = 1 111 err error 112 ) 113 for _, part := range parts[1:] { 114 if strings.HasPrefix(strings.TrimSpace(part), weightPrefix) { 115 qVal, err = strconv.ParseFloat(part[len(weightPrefix):], 32) 116 if err != nil || qVal < 0 || qVal >= 2 { 117 continue Loop 118 } 119 break 120 } 121 } 122 accepts = append(accepts, mime{ 123 mime: Mime(name), 124 weight: uint16(qVal * 1000), 125 }) 126 } 127 if len(accepts) == 0 { 128 return h.Handle("") 129 } 130 sort.Stable(accepts) 131 for _, accept := range accepts { 132 if h.Handle(accept.mime) { 133 return true 134 } 135 } 136 return false 137 }