198 lines
4.8 KiB
Go
198 lines
4.8 KiB
Go
|
package wsflate
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"strconv"
|
||
|
|
||
|
"github.com/gobwas/httphead"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
ExtensionName = "permessage-deflate"
|
||
|
|
||
|
serverNoContextTakeover = "server_no_context_takeover"
|
||
|
clientNoContextTakeover = "client_no_context_takeover"
|
||
|
serverMaxWindowBits = "server_max_window_bits"
|
||
|
clientMaxWindowBits = "client_max_window_bits"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
ExtensionNameBytes = []byte(ExtensionName)
|
||
|
|
||
|
serverNoContextTakeoverBytes = []byte(serverNoContextTakeover)
|
||
|
clientNoContextTakeoverBytes = []byte(clientNoContextTakeover)
|
||
|
serverMaxWindowBitsBytes = []byte(serverMaxWindowBits)
|
||
|
clientMaxWindowBitsBytes = []byte(clientMaxWindowBits)
|
||
|
)
|
||
|
|
||
|
var windowBits [8][]byte
|
||
|
|
||
|
func init() {
|
||
|
for i := range windowBits {
|
||
|
windowBits[i] = []byte(strconv.Itoa(i + 8))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Parameters contains compression extension options.
|
||
|
type Parameters struct {
|
||
|
ServerNoContextTakeover bool
|
||
|
ClientNoContextTakeover bool
|
||
|
ServerMaxWindowBits WindowBits
|
||
|
ClientMaxWindowBits WindowBits
|
||
|
}
|
||
|
|
||
|
// WindowBits specifies window size accordingly to RFC.
|
||
|
// Use its Bytes() method to obtain actual size of window in bytes.
|
||
|
type WindowBits byte
|
||
|
|
||
|
// Defined reports whether window bits were specified.
|
||
|
func (b WindowBits) Defined() bool {
|
||
|
return b > 0
|
||
|
}
|
||
|
|
||
|
// Bytes returns window size in number of bytes.
|
||
|
func (b WindowBits) Bytes() int {
|
||
|
return 1 << uint(b)
|
||
|
}
|
||
|
|
||
|
const (
|
||
|
MaxLZ77WindowSize = 32768 // 2^15
|
||
|
)
|
||
|
|
||
|
// Parse reads parameters from given HTTP header option accordingly to RFC.
|
||
|
//
|
||
|
// It returns non-nil error at least in these cases:
|
||
|
// - The negotiation offer contains an extension parameter not defined for
|
||
|
// use in an offer/response.
|
||
|
// - The negotiation offer/response contains an extension parameter with an
|
||
|
// invalid value.
|
||
|
// - The negotiation offer/response contains multiple extension parameters
|
||
|
// with the same name.
|
||
|
func (p *Parameters) Parse(opt httphead.Option) (err error) {
|
||
|
const (
|
||
|
clientMaxWindowBitsSeen = 1 << iota
|
||
|
serverMaxWindowBitsSeen
|
||
|
clientNoContextTakeoverSeen
|
||
|
serverNoContextTakeoverSeen
|
||
|
)
|
||
|
|
||
|
// Reset to not mix parsed data from previous Parse() calls.
|
||
|
*p = Parameters{}
|
||
|
|
||
|
var seen byte
|
||
|
opt.Parameters.ForEach(func(key, val []byte) (ok bool) {
|
||
|
switch string(key) {
|
||
|
case clientMaxWindowBits:
|
||
|
if len(val) == 0 {
|
||
|
p.ClientMaxWindowBits = 1
|
||
|
return true
|
||
|
}
|
||
|
if seen&clientMaxWindowBitsSeen != 0 {
|
||
|
err = paramError("duplicate", key, val)
|
||
|
return false
|
||
|
}
|
||
|
seen |= clientMaxWindowBitsSeen
|
||
|
if p.ClientMaxWindowBits, ok = bitsFromASCII(val); !ok {
|
||
|
err = paramError("invalid", key, val)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
case serverMaxWindowBits:
|
||
|
if len(val) == 0 {
|
||
|
err = paramError("invalid", key, val)
|
||
|
return false
|
||
|
}
|
||
|
if seen&serverMaxWindowBitsSeen != 0 {
|
||
|
err = paramError("duplicate", key, val)
|
||
|
return false
|
||
|
}
|
||
|
seen |= serverMaxWindowBitsSeen
|
||
|
if p.ServerMaxWindowBits, ok = bitsFromASCII(val); !ok {
|
||
|
err = paramError("invalid", key, val)
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
case clientNoContextTakeover:
|
||
|
if len(val) > 0 {
|
||
|
err = paramError("invalid", key, val)
|
||
|
return false
|
||
|
}
|
||
|
if seen&clientNoContextTakeoverSeen != 0 {
|
||
|
err = paramError("duplicate", key, val)
|
||
|
return false
|
||
|
}
|
||
|
seen |= clientNoContextTakeoverSeen
|
||
|
p.ClientNoContextTakeover = true
|
||
|
|
||
|
case serverNoContextTakeover:
|
||
|
if len(val) > 0 {
|
||
|
err = paramError("invalid", key, val)
|
||
|
return false
|
||
|
}
|
||
|
if seen&serverNoContextTakeoverSeen != 0 {
|
||
|
err = paramError("duplicate", key, val)
|
||
|
return false
|
||
|
}
|
||
|
seen |= serverNoContextTakeoverSeen
|
||
|
p.ServerNoContextTakeover = true
|
||
|
|
||
|
default:
|
||
|
err = paramError("unexpected", key, val)
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
})
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Option encodes parameters into HTTP header option.
|
||
|
func (p Parameters) Option() httphead.Option {
|
||
|
opt := httphead.Option{
|
||
|
Name: ExtensionNameBytes,
|
||
|
}
|
||
|
setBool(&opt, serverNoContextTakeoverBytes, p.ServerNoContextTakeover)
|
||
|
setBool(&opt, clientNoContextTakeoverBytes, p.ClientNoContextTakeover)
|
||
|
setBits(&opt, serverMaxWindowBitsBytes, p.ServerMaxWindowBits)
|
||
|
setBits(&opt, clientMaxWindowBitsBytes, p.ClientMaxWindowBits)
|
||
|
return opt
|
||
|
}
|
||
|
|
||
|
func isValidBits(x int) bool {
|
||
|
return 8 <= x && x <= 15
|
||
|
}
|
||
|
|
||
|
func bitsFromASCII(p []byte) (WindowBits, bool) {
|
||
|
n, ok := httphead.IntFromASCII(p)
|
||
|
if !ok || !isValidBits(n) {
|
||
|
return 0, false
|
||
|
}
|
||
|
return WindowBits(n), true
|
||
|
}
|
||
|
|
||
|
func setBits(opt *httphead.Option, name []byte, bits WindowBits) {
|
||
|
if bits == 0 {
|
||
|
return
|
||
|
}
|
||
|
if bits == 1 {
|
||
|
opt.Parameters.Set(name, nil)
|
||
|
return
|
||
|
}
|
||
|
if !isValidBits(int(bits)) {
|
||
|
panic(fmt.Sprintf("wsflate: invalid bits value: %d", bits))
|
||
|
}
|
||
|
opt.Parameters.Set(name, windowBits[bits-8])
|
||
|
}
|
||
|
|
||
|
func setBool(opt *httphead.Option, name []byte, flag bool) {
|
||
|
if flag {
|
||
|
opt.Parameters.Set(name, nil)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func paramError(reason string, key, val []byte) error {
|
||
|
return fmt.Errorf(
|
||
|
"wsflate: %s extension parameter %q: %q",
|
||
|
reason, key, val,
|
||
|
)
|
||
|
}
|