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,
|
|
)
|
|
}
|