- Notifications
You must be signed in to change notification settings - Fork928
fix: ensure websocket close messages are truncated to 123 bytes#779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes from1 commit
9472717
c3bf516
c3bae1c
82ea13c
ceab625
c70e408
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
It's possible for websocket close messages to be too long, which causethem to silently fail without a proper close message. See error below:```2022-03-31 17:08:34.862 [INFO](stdlib)<close_notjs.go:72>"2022/03/31 17:08:34 websocket: failed to marshal close frame: reason string max is 123 but got \"insert provisioner daemon:Cannot encode []database.ProvisionerType into oid 19098 - []database.ProvisionerType must implement Encoder or be converted to a string\" with length 161"```
- Loading branch information
Uh oh!
There was an error while loading.Please reload this page.
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package coderd | ||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
const websocketCloseMaxLen = 123 | ||
// fmtWebsocketCloseMsg formats a websocket close message and ensures it is | ||
// truncated to the maximum allowed length. | ||
func fmtWebsocketCloseMsg(format string, vars ...any) string { | ||
coadler marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
msg := fmt.Sprintf(format, vars...) | ||
// Cap msg length at 123 bytes. nhooyr/websocket only allows close messages | ||
// of this length. | ||
if len(msg) > websocketCloseMaxLen { | ||
return truncateString(msg, websocketCloseMaxLen) | ||
coadler marked this conversation as resolved. Show resolvedHide resolvedUh oh!There was an error while loading.Please reload this page. | ||
} | ||
return msg | ||
} | ||
// truncateString safely truncates a string to a maximum size of byteLen. It | ||
// writes whole runes until a single rune would increase the string size above | ||
// byteLen. | ||
func truncateString(str string, byteLen int) string { | ||
builder := strings.Builder{} | ||
builder.Grow(byteLen) | ||
for _, char := range str { | ||
if builder.Len()+len(string(char)) > byteLen { | ||
break | ||
} | ||
_, _ = builder.WriteRune(char) | ||
} | ||
return builder.String() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Could we do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. You can't safely slice strings, because characters can be more than 1 byte. This could cause us to slice in the middle of a single character. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. Could we do There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. That would limit the rune count instead of the byte count There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. I left a brainded comment There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. You are correct sir There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others.Learn more. i made it simpler tho and got rid of the string builder | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// This file tests an internal function. | ||
//nolint:testpackage | ||
package coderd | ||
import ( | ||
"strings" | ||
"testing" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
func Test_websocketCloseMsg(t *testing.T) { | ||
t.Parallel() | ||
t.Run("TruncateSingleByteCharacters", func(t *testing.T) { | ||
t.Parallel() | ||
msg := strings.Repeat("d", 255) | ||
trunc := fmtWebsocketCloseMsg(msg) | ||
assert.LessOrEqual(t, len(trunc), 123) | ||
}) | ||
t.Run("TruncateMultiByteCharacters", func(t *testing.T) { | ||
t.Parallel() | ||
msg := strings.Repeat("こんにちは", 10) | ||
trunc := fmtWebsocketCloseMsg(msg) | ||
assert.LessOrEqual(t, len(trunc), 123) | ||
}) | ||
} |