[bugfix] send back Sec-Websocket-Protocol header for streaming WebSocket (#3169)

* [bugfix] send back Sec-Websocket-Protocol header for streaming WebSocket

Chrome expects the selected Sec-Websocket-Protocol to be sent back
on the WebSocket upgrade request (RFC6455 1.9).

* fiddle a bit to avoid getting headers multiple times

* add some explanatory notes

---------

Co-authored-by: tobi <tobi.smethurst@protonmail.com>
This commit is contained in:
Leah Neukirchen 2024-08-06 11:52:27 +02:00 committed by GitHub
parent b78be9fd4a
commit 4697271cef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 29 additions and 10 deletions

View File

@ -19,6 +19,7 @@ package streaming
import ( import (
"context" "context"
"net/http"
"slices" "slices"
"time" "time"
@ -151,15 +152,24 @@ import (
// description: bad request // description: bad request
func (m *Module) StreamGETHandler(c *gin.Context) { func (m *Module) StreamGETHandler(c *gin.Context) {
var ( var (
account *gtsmodel.Account token string
errWithCode gtserror.WithCode tokenInHeader bool
account *gtsmodel.Account
errWithCode gtserror.WithCode
) )
// Try query param access token. if t := c.Query(AccessTokenQueryKey); t != "" {
token := c.Query(AccessTokenQueryKey) // Token was provided as
if token == "" { // query param, no problem.
// Try fallback HTTP header provided token. token = t
token = c.GetHeader(AccessTokenHeader) } else if t := c.GetHeader(AccessTokenHeader); t != "" {
// Token was provided in "Sec-Websocket-Protocol" header.
//
// This is hacky and not technically correct but some
// clients do it since Mastodon allows it, so we must
// also allow it to avoid breaking expectations.
token = t
tokenInHeader = true
} }
if token != "" { if token != "" {
@ -230,7 +240,16 @@ func (m *Module) StreamGETHandler(c *gin.Context) {
// //
// If the upgrade fails, then Upgrade replies to the client // If the upgrade fails, then Upgrade replies to the client
// with an HTTP error response. // with an HTTP error response.
wsConn, err := m.wsUpgrade.Upgrade(c.Writer, c.Request, nil) var responseHeader http.Header
if tokenInHeader {
// Return the token in the response,
// else Chrome fails to connect.
//
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Protocol_upgrade_mechanism#sec-websocket-protocol
responseHeader = http.Header{AccessTokenHeader: {token}}
}
wsConn, err := m.wsUpgrade.Upgrade(c.Writer, c.Request, responseHeader)
if err != nil { if err != nil {
l.Errorf("error upgrading websocket connection: %v", err) l.Errorf("error upgrading websocket connection: %v", err)
stream.Close() stream.Close()

View File

@ -22,7 +22,7 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
"io/ioutil" "io"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -236,7 +236,7 @@ func (suite *StreamingTestSuite) TestSecurityHeader() {
result := recorder.Result() result := recorder.Result()
defer result.Body.Close() defer result.Body.Close()
b, err := ioutil.ReadAll(result.Body) b, err := io.ReadAll(result.Body)
suite.NoError(err) suite.NoError(err)
// check response // check response