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 84 Loop: 85 for _, accept := range strings.Split(acceptHeader, acceptSplit) { 86 parts := strings.Split(strings.TrimSpace(accept), partSplit) 87 88 name := strings.TrimSpace(parts[0]) 89 if name == "" { 90 continue 91 } 92 93 var ( 94 qVal float64 = 1 95 err error 96 ) 97 98 for _, part := range parts[1:] { 99 if strings.HasPrefix(strings.TrimSpace(part), weightPrefix) { 100 qVal, err = strconv.ParseFloat(part[len(weightPrefix):], 32) 101 if err != nil || qVal < 0 || qVal >= 2 { 102 continue Loop 103 } 104 105 break 106 } 107 } 108 109 weight := uint16(qVal * 1000) 110 111 name = strings.ToLower(name) 112 if name == identityEncoding { 113 allowIdentity = weight != 0 114 hasIdentity = true 115 } 116 117 accepts = append(accepts, encoding{ 118 encoding: Encoding(name), 119 weight: weight, 120 }) 121 } 122 123 sort.Stable(accepts) 124 125 for _, accept := range accepts { 126 switch accept.encoding { 127 case identityEncoding: 128 if accept.weight != 0 { 129 if h.Handle("") { 130 return true 131 } 132 } 133 134 allowIdentity = false 135 case anyEncoding: 136 if !hasIdentity { 137 if accept.weight != 0 { 138 if h.Handle("") { 139 return true 140 } 141 } 142 143 allowIdentity = false 144 } 145 default: 146 if h.Handle(accept.encoding) { 147 return true 148 } 149 } 150 } 151 152 if allowIdentity { 153 if h.Handle("") { 154 return true 155 } 156 } 157 158 return false 159 } 160 161 // ClearEncoding removes the Accept-Encoding header so that any further 162 // attempts to establish an encoding will simply used the default, plain text, 163 // encoding. 164 // 165 // Useful when you don't want a handler down the chain to also handle encoding. 166 func ClearEncoding(r *http.Request) { 167 r.Header.Del(acceptEncoding) 168 } 169