khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 1 | // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style |
| 3 | // license that can be found in the LICENSE file. |
| 4 | |
| 5 | package websocket |
| 6 | |
| 7 | import ( |
| 8 | "bufio" |
| 9 | "errors" |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 10 | "io" |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 11 | "net/http" |
| 12 | "net/url" |
| 13 | "strings" |
| 14 | "time" |
| 15 | ) |
| 16 | |
| 17 | // HandshakeError describes an error with the handshake from the peer. |
| 18 | type HandshakeError struct { |
| 19 | message string |
| 20 | } |
| 21 | |
| 22 | func (e HandshakeError) Error() string { return e.message } |
| 23 | |
| 24 | // Upgrader specifies parameters for upgrading an HTTP connection to a |
| 25 | // WebSocket connection. |
| 26 | type Upgrader struct { |
| 27 | // HandshakeTimeout specifies the duration for the handshake to complete. |
| 28 | HandshakeTimeout time.Duration |
| 29 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 30 | // ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 31 | // size is zero, then buffers allocated by the HTTP server are used. The |
| 32 | // I/O buffer sizes do not limit the size of the messages that can be sent |
| 33 | // or received. |
| 34 | ReadBufferSize, WriteBufferSize int |
| 35 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 36 | // WriteBufferPool is a pool of buffers for write operations. If the value |
| 37 | // is not set, then write buffers are allocated to the connection for the |
| 38 | // lifetime of the connection. |
| 39 | // |
| 40 | // A pool is most useful when the application has a modest volume of writes |
| 41 | // across a large number of connections. |
| 42 | // |
| 43 | // Applications should use a single pool for each unique value of |
| 44 | // WriteBufferSize. |
| 45 | WriteBufferPool BufferPool |
| 46 | |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 47 | // Subprotocols specifies the server's supported protocols in order of |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 48 | // preference. If this field is not nil, then the Upgrade method negotiates a |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 49 | // subprotocol by selecting the first match in this list with a protocol |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 50 | // requested by the client. If there's no match, then no protocol is |
| 51 | // negotiated (the Sec-Websocket-Protocol header is not included in the |
| 52 | // handshake response). |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 53 | Subprotocols []string |
| 54 | |
| 55 | // Error specifies the function for generating HTTP error responses. If Error |
| 56 | // is nil, then http.Error is used to generate the HTTP response. |
| 57 | Error func(w http.ResponseWriter, r *http.Request, status int, reason error) |
| 58 | |
| 59 | // CheckOrigin returns true if the request Origin header is acceptable. If |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 60 | // CheckOrigin is nil, then a safe default is used: return false if the |
| 61 | // Origin request header is present and the origin host is not equal to |
| 62 | // request Host header. |
| 63 | // |
| 64 | // A CheckOrigin function should carefully validate the request origin to |
| 65 | // prevent cross-site request forgery. |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 66 | CheckOrigin func(r *http.Request) bool |
| 67 | |
| 68 | // EnableCompression specify if the server should attempt to negotiate per |
| 69 | // message compression (RFC 7692). Setting this value to true does not |
| 70 | // guarantee that compression will be supported. Currently only "no context |
| 71 | // takeover" modes are supported. |
| 72 | EnableCompression bool |
| 73 | } |
| 74 | |
| 75 | func (u *Upgrader) returnError(w http.ResponseWriter, r *http.Request, status int, reason string) (*Conn, error) { |
| 76 | err := HandshakeError{reason} |
| 77 | if u.Error != nil { |
| 78 | u.Error(w, r, status, err) |
| 79 | } else { |
| 80 | w.Header().Set("Sec-Websocket-Version", "13") |
| 81 | http.Error(w, http.StatusText(status), status) |
| 82 | } |
| 83 | return nil, err |
| 84 | } |
| 85 | |
| 86 | // checkSameOrigin returns true if the origin is not set or is equal to the request host. |
| 87 | func checkSameOrigin(r *http.Request) bool { |
| 88 | origin := r.Header["Origin"] |
| 89 | if len(origin) == 0 { |
| 90 | return true |
| 91 | } |
| 92 | u, err := url.Parse(origin[0]) |
| 93 | if err != nil { |
| 94 | return false |
| 95 | } |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 96 | return equalASCIIFold(u.Host, r.Host) |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 97 | } |
| 98 | |
| 99 | func (u *Upgrader) selectSubprotocol(r *http.Request, responseHeader http.Header) string { |
| 100 | if u.Subprotocols != nil { |
| 101 | clientProtocols := Subprotocols(r) |
| 102 | for _, serverProtocol := range u.Subprotocols { |
| 103 | for _, clientProtocol := range clientProtocols { |
| 104 | if clientProtocol == serverProtocol { |
| 105 | return clientProtocol |
| 106 | } |
| 107 | } |
| 108 | } |
| 109 | } else if responseHeader != nil { |
| 110 | return responseHeader.Get("Sec-Websocket-Protocol") |
| 111 | } |
| 112 | return "" |
| 113 | } |
| 114 | |
| 115 | // Upgrade upgrades the HTTP server connection to the WebSocket protocol. |
| 116 | // |
| 117 | // The responseHeader is included in the response to the client's upgrade |
| 118 | // request. Use the responseHeader to specify cookies (Set-Cookie) and the |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 119 | // application negotiated subprotocol (Sec-WebSocket-Protocol). |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 120 | // |
| 121 | // If the upgrade fails, then Upgrade replies to the client with an HTTP error |
| 122 | // response. |
| 123 | func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 124 | const badHandshake = "websocket: the client is not using the websocket protocol: " |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 125 | |
| 126 | if !tokenListContainsValue(r.Header, "Connection", "upgrade") { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 127 | return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'upgrade' token not found in 'Connection' header") |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 128 | } |
| 129 | |
| 130 | if !tokenListContainsValue(r.Header, "Upgrade", "websocket") { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 131 | return u.returnError(w, r, http.StatusBadRequest, badHandshake+"'websocket' token not found in 'Upgrade' header") |
| 132 | } |
| 133 | |
| 134 | if r.Method != "GET" { |
| 135 | return u.returnError(w, r, http.StatusMethodNotAllowed, badHandshake+"request method is not GET") |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 136 | } |
| 137 | |
| 138 | if !tokenListContainsValue(r.Header, "Sec-Websocket-Version", "13") { |
| 139 | return u.returnError(w, r, http.StatusBadRequest, "websocket: unsupported version: 13 not found in 'Sec-Websocket-Version' header") |
| 140 | } |
| 141 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 142 | if _, ok := responseHeader["Sec-Websocket-Extensions"]; ok { |
| 143 | return u.returnError(w, r, http.StatusInternalServerError, "websocket: application specific 'Sec-WebSocket-Extensions' headers are unsupported") |
| 144 | } |
| 145 | |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 146 | checkOrigin := u.CheckOrigin |
| 147 | if checkOrigin == nil { |
| 148 | checkOrigin = checkSameOrigin |
| 149 | } |
| 150 | if !checkOrigin(r) { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 151 | return u.returnError(w, r, http.StatusForbidden, "websocket: request origin not allowed by Upgrader.CheckOrigin") |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 152 | } |
| 153 | |
| 154 | challengeKey := r.Header.Get("Sec-Websocket-Key") |
| 155 | if challengeKey == "" { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 156 | return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank") |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 157 | } |
| 158 | |
| 159 | subprotocol := u.selectSubprotocol(r, responseHeader) |
| 160 | |
| 161 | // Negotiate PMCE |
| 162 | var compress bool |
| 163 | if u.EnableCompression { |
| 164 | for _, ext := range parseExtensions(r.Header) { |
| 165 | if ext[""] != "permessage-deflate" { |
| 166 | continue |
| 167 | } |
| 168 | compress = true |
| 169 | break |
| 170 | } |
| 171 | } |
| 172 | |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 173 | h, ok := w.(http.Hijacker) |
| 174 | if !ok { |
| 175 | return u.returnError(w, r, http.StatusInternalServerError, "websocket: response does not implement http.Hijacker") |
| 176 | } |
| 177 | var brw *bufio.ReadWriter |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 178 | netConn, brw, err := h.Hijack() |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 179 | if err != nil { |
| 180 | return u.returnError(w, r, http.StatusInternalServerError, err.Error()) |
| 181 | } |
| 182 | |
| 183 | if brw.Reader.Buffered() > 0 { |
| 184 | netConn.Close() |
| 185 | return nil, errors.New("websocket: client sent data before handshake is complete") |
| 186 | } |
| 187 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 188 | var br *bufio.Reader |
| 189 | if u.ReadBufferSize == 0 && bufioReaderSize(netConn, brw.Reader) > 256 { |
| 190 | // Reuse hijacked buffered reader as connection reader. |
| 191 | br = brw.Reader |
| 192 | } |
| 193 | |
| 194 | buf := bufioWriterBuffer(netConn, brw.Writer) |
| 195 | |
| 196 | var writeBuf []byte |
| 197 | if u.WriteBufferPool == nil && u.WriteBufferSize == 0 && len(buf) >= maxFrameHeaderSize+256 { |
| 198 | // Reuse hijacked write buffer as connection buffer. |
| 199 | writeBuf = buf |
| 200 | } |
| 201 | |
| 202 | c := newConn(netConn, true, u.ReadBufferSize, u.WriteBufferSize, u.WriteBufferPool, br, writeBuf) |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 203 | c.subprotocol = subprotocol |
| 204 | |
| 205 | if compress { |
| 206 | c.newCompressionWriter = compressNoContextTakeover |
| 207 | c.newDecompressionReader = decompressNoContextTakeover |
| 208 | } |
| 209 | |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 210 | // Use larger of hijacked buffer and connection write buffer for header. |
| 211 | p := buf |
| 212 | if len(c.writeBuf) > len(p) { |
| 213 | p = c.writeBuf |
| 214 | } |
| 215 | p = p[:0] |
| 216 | |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 217 | p = append(p, "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "...) |
| 218 | p = append(p, computeAcceptKey(challengeKey)...) |
| 219 | p = append(p, "\r\n"...) |
| 220 | if c.subprotocol != "" { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 221 | p = append(p, "Sec-WebSocket-Protocol: "...) |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 222 | p = append(p, c.subprotocol...) |
| 223 | p = append(p, "\r\n"...) |
| 224 | } |
| 225 | if compress { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 226 | p = append(p, "Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover; client_no_context_takeover\r\n"...) |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 227 | } |
| 228 | for k, vs := range responseHeader { |
| 229 | if k == "Sec-Websocket-Protocol" { |
| 230 | continue |
| 231 | } |
| 232 | for _, v := range vs { |
| 233 | p = append(p, k...) |
| 234 | p = append(p, ": "...) |
| 235 | for i := 0; i < len(v); i++ { |
| 236 | b := v[i] |
| 237 | if b <= 31 { |
| 238 | // prevent response splitting. |
| 239 | b = ' ' |
| 240 | } |
| 241 | p = append(p, b) |
| 242 | } |
| 243 | p = append(p, "\r\n"...) |
| 244 | } |
| 245 | } |
| 246 | p = append(p, "\r\n"...) |
| 247 | |
| 248 | // Clear deadlines set by HTTP server. |
| 249 | netConn.SetDeadline(time.Time{}) |
| 250 | |
| 251 | if u.HandshakeTimeout > 0 { |
| 252 | netConn.SetWriteDeadline(time.Now().Add(u.HandshakeTimeout)) |
| 253 | } |
| 254 | if _, err = netConn.Write(p); err != nil { |
| 255 | netConn.Close() |
| 256 | return nil, err |
| 257 | } |
| 258 | if u.HandshakeTimeout > 0 { |
| 259 | netConn.SetWriteDeadline(time.Time{}) |
| 260 | } |
| 261 | |
| 262 | return c, nil |
| 263 | } |
| 264 | |
| 265 | // Upgrade upgrades the HTTP server connection to the WebSocket protocol. |
| 266 | // |
| 267 | // Deprecated: Use websocket.Upgrader instead. |
| 268 | // |
| 269 | // Upgrade does not perform origin checking. The application is responsible for |
| 270 | // checking the Origin header before calling Upgrade. An example implementation |
| 271 | // of the same origin policy check is: |
| 272 | // |
| 273 | // if req.Header.Get("Origin") != "http://"+req.Host { |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 274 | // http.Error(w, "Origin not allowed", http.StatusForbidden) |
khenaidoo | 59ce9dd | 2019-11-11 13:05:32 -0500 | [diff] [blame] | 275 | // return |
| 276 | // } |
| 277 | // |
| 278 | // If the endpoint supports subprotocols, then the application is responsible |
| 279 | // for negotiating the protocol used on the connection. Use the Subprotocols() |
| 280 | // function to get the subprotocols requested by the client. Use the |
| 281 | // Sec-Websocket-Protocol response header to specify the subprotocol selected |
| 282 | // by the application. |
| 283 | // |
| 284 | // The responseHeader is included in the response to the client's upgrade |
| 285 | // request. Use the responseHeader to specify cookies (Set-Cookie) and the |
| 286 | // negotiated subprotocol (Sec-Websocket-Protocol). |
| 287 | // |
| 288 | // The connection buffers IO to the underlying network connection. The |
| 289 | // readBufSize and writeBufSize parameters specify the size of the buffers to |
| 290 | // use. Messages can be larger than the buffers. |
| 291 | // |
| 292 | // If the request is not a valid WebSocket handshake, then Upgrade returns an |
| 293 | // error of type HandshakeError. Applications should handle this error by |
| 294 | // replying to the client with an HTTP error response. |
| 295 | func Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header, readBufSize, writeBufSize int) (*Conn, error) { |
| 296 | u := Upgrader{ReadBufferSize: readBufSize, WriteBufferSize: writeBufSize} |
| 297 | u.Error = func(w http.ResponseWriter, r *http.Request, status int, reason error) { |
| 298 | // don't return errors to maintain backwards compatibility |
| 299 | } |
| 300 | u.CheckOrigin = func(r *http.Request) bool { |
| 301 | // allow all connections by default |
| 302 | return true |
| 303 | } |
| 304 | return u.Upgrade(w, r, responseHeader) |
| 305 | } |
| 306 | |
| 307 | // Subprotocols returns the subprotocols requested by the client in the |
| 308 | // Sec-Websocket-Protocol header. |
| 309 | func Subprotocols(r *http.Request) []string { |
| 310 | h := strings.TrimSpace(r.Header.Get("Sec-Websocket-Protocol")) |
| 311 | if h == "" { |
| 312 | return nil |
| 313 | } |
| 314 | protocols := strings.Split(h, ",") |
| 315 | for i := range protocols { |
| 316 | protocols[i] = strings.TrimSpace(protocols[i]) |
| 317 | } |
| 318 | return protocols |
| 319 | } |
| 320 | |
| 321 | // IsWebSocketUpgrade returns true if the client requested upgrade to the |
| 322 | // WebSocket protocol. |
| 323 | func IsWebSocketUpgrade(r *http.Request) bool { |
| 324 | return tokenListContainsValue(r.Header, "Connection", "upgrade") && |
| 325 | tokenListContainsValue(r.Header, "Upgrade", "websocket") |
| 326 | } |
khenaidoo | 2672188 | 2021-08-11 17:42:52 -0400 | [diff] [blame] | 327 | |
| 328 | // bufioReaderSize size returns the size of a bufio.Reader. |
| 329 | func bufioReaderSize(originalReader io.Reader, br *bufio.Reader) int { |
| 330 | // This code assumes that peek on a reset reader returns |
| 331 | // bufio.Reader.buf[:0]. |
| 332 | // TODO: Use bufio.Reader.Size() after Go 1.10 |
| 333 | br.Reset(originalReader) |
| 334 | if p, err := br.Peek(0); err == nil { |
| 335 | return cap(p) |
| 336 | } |
| 337 | return 0 |
| 338 | } |
| 339 | |
| 340 | // writeHook is an io.Writer that records the last slice passed to it vio |
| 341 | // io.Writer.Write. |
| 342 | type writeHook struct { |
| 343 | p []byte |
| 344 | } |
| 345 | |
| 346 | func (wh *writeHook) Write(p []byte) (int, error) { |
| 347 | wh.p = p |
| 348 | return len(p), nil |
| 349 | } |
| 350 | |
| 351 | // bufioWriterBuffer grabs the buffer from a bufio.Writer. |
| 352 | func bufioWriterBuffer(originalWriter io.Writer, bw *bufio.Writer) []byte { |
| 353 | // This code assumes that bufio.Writer.buf[:1] is passed to the |
| 354 | // bufio.Writer's underlying writer. |
| 355 | var wh writeHook |
| 356 | bw.Reset(&wh) |
| 357 | bw.WriteByte(0) |
| 358 | bw.Flush() |
| 359 | |
| 360 | bw.Reset(originalWriter) |
| 361 | |
| 362 | return wh.p[:cap(wh.p)] |
| 363 | } |