1 // Package httpencoding provides a function to deal with the Accept-Encoding 2 // header. 3 package httpencoding // import "vimagination.zapto.org/httpencoding" 4 5 import ( 6 "net/http" 7 "sort" 8 "strconv" 9 "strings" 10 ) 11 12 const ( 13 acceptEncoding = "Accept-Encoding" 14 anyEncoding = "*" 15 identityEncoding = "identity" 16 acceptSplit = "," 17 partSplit = ";" 18 weightPrefix = "q=" 19 ) 20 21 type encodings []encoding 22 23 func (e encodings) Len() int { 24 return len(e) 25 } 26 27 func (e encodings) Less(i, j int) bool { 28 return e[j].weight < e[i].weight 29 } 30 31 func (e encodings) Swap(i, j int) { 32 e[i], e[j] = e[j], e[i] 33 } 34 35 type encoding struct { 36 encoding Encoding 37 weight uint16 38 } 39 40 // Encoding represents an encoding string as used by the client. Examples are 41 // gzip, br and deflate. 42 type Encoding string 43 44 // Handler provides an interface to handle an encoding. 45 // 46 // The encoding string (e.g. gzip, br, deflate) is passed to the handler, which 47 // is expected to return true if no more encodings are required and false 48 // otherwise. 49 // 50 // The empty string "" is used to signify the identity encoding, or plain text 51 type Handler interface { 52 Handle(encoding Encoding) bool 53 } 54 55 // HandlerFunc wraps a func to make it satisfy the Handler interface 56 type HandlerFunc func(Encoding) bool 57 58 // Handle calls the underlying func 59 func (h HandlerFunc) Handle(e Encoding) bool { 60 return h(e) 61 } 62 63 // InvalidEncoding writes the 406 header 64 func InvalidEncoding(w http.ResponseWriter) { 65 w.WriteHeader(http.StatusNotAcceptable) 66 } 67 68 // HandleEncoding will process the Accept-Encoding header and calls the given 69 // handler for each encoding until the handler returns true. 70 // 71 // This function returns true when the Handler returns true, false otherwise 72 // 73 // For the identity (plain text) encoding the encoding string will be the 74 // empty string. 75 // 76 // The wildcard encoding (*) is currently treated as identity when there is no 77 // independent identity encoding specified; otherwise, it is ignored. 78 func HandleEncoding(r *http.Request, h Handler) bool { 79 acceptHeader := r.Header.Get(acceptEncoding) 80 accepts := make(encodings, 0, strings.Count(acceptHeader, acceptSplit)+1) 81 allowIdentity := true 82 hasIdentity := false 83 Loop: 84 for _, accept := range strings.Split(acceptHeader, acceptSplit) { 85 parts := strings.Split(strings.TrimSpace(accept), partSplit) 86 name := strings.TrimSpace(parts[0]) 87 if name == "" { 88 continue 89 } 90 var ( 91 qVal float64 = 1 92 err error 93 ) 94 for _, part := range parts[1:] { 95 if strings.HasPrefix(strings.TrimSpace(part), weightPrefix) { 96 qVal, err = strconv.ParseFloat(part[len(weightPrefix):], 32) 97 if err != nil || qVal < 0 || qVal >= 2 { 98 continue Loop 99 } 100 break 101 } 102 } 103 name = strings.ToLower(name) 104 weight := uint16(qVal * 1000) 105 if name == identityEncoding { 106 allowIdentity = weight != 0 107 hasIdentity = true 108 } 109 accepts = append(accepts, encoding{ 110 encoding: Encoding(name), 111 weight: weight, 112 }) 113 } 114 sort.Stable(accepts) 115 for _, accept := range accepts { 116 switch accept.encoding { 117 case identityEncoding: 118 if accept.weight != 0 { 119 if h.Handle("") { 120 return true 121 } 122 } 123 allowIdentity = false 124 case anyEncoding: 125 if !hasIdentity { 126 if accept.weight != 0 { 127 if h.Handle("") { 128 return true 129 } 130 } 131 allowIdentity = false 132 } 133 default: 134 if h.Handle(accept.encoding) { 135 return true 136 } 137 } 138 } 139 if allowIdentity { 140 if h.Handle("") { 141 return true 142 } 143 } 144 return false 145 } 146 147 // ClearEncoding removes the Accept-Encoding header so that any further 148 // attempts to establish an encoding will simply used the default, plain text, 149 // encoding. 150 // 151 // Useful when you don't want a handler down the chain to also handle encoding 152 func ClearEncoding(r *http.Request) { 153 r.Header.Del(acceptEncoding) 154 }