141 lines
4.0 KiB
Go
141 lines
4.0 KiB
Go
package connection
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/cloudflare/cloudflared/h2mux"
|
|
)
|
|
|
|
var (
|
|
// h2mux-style special headers
|
|
RequestUserHeaders = "cf-cloudflared-request-headers"
|
|
ResponseUserHeaders = "cf-cloudflared-response-headers"
|
|
ResponseMetaHeader = "cf-cloudflared-response-meta"
|
|
|
|
// h2mux-style special headers
|
|
CanonicalResponseUserHeaders = http.CanonicalHeaderKey(ResponseUserHeaders)
|
|
CanonicalResponseMetaHeader = http.CanonicalHeaderKey(ResponseMetaHeader)
|
|
)
|
|
|
|
var (
|
|
// pre-generate possible values for res
|
|
responseMetaHeaderCfd = mustInitRespMetaHeader("cloudflared")
|
|
responseMetaHeaderOrigin = mustInitRespMetaHeader("origin")
|
|
)
|
|
|
|
type responseMetaHeader struct {
|
|
Source string `json:"src"`
|
|
}
|
|
|
|
func mustInitRespMetaHeader(src string) string {
|
|
header, err := json.Marshal(responseMetaHeader{Source: src})
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Failed to serialize response meta header = %s, err: %v", src, err))
|
|
}
|
|
return string(header)
|
|
}
|
|
|
|
var headerEncoding = base64.RawStdEncoding
|
|
|
|
// IsControlResponseHeader is called in the direction of eyeball <- origin.
|
|
func IsControlResponseHeader(headerName string) bool {
|
|
return strings.HasPrefix(headerName, ":") ||
|
|
strings.HasPrefix(headerName, "cf-int-") ||
|
|
strings.HasPrefix(headerName, "cf-cloudflared-")
|
|
}
|
|
|
|
// isWebsocketClientHeader returns true if the header name is required by the client to upgrade properly
|
|
func IsWebsocketClientHeader(headerName string) bool {
|
|
return headerName == "sec-websocket-accept" ||
|
|
headerName == "connection" ||
|
|
headerName == "upgrade"
|
|
}
|
|
|
|
// Serialize HTTP1.x headers by base64-encoding each header name and value,
|
|
// and then joining them in the format of [key:value;]
|
|
func SerializeHeaders(h1Headers http.Header) string {
|
|
// compute size of the fully serialized value and largest temp buffer we will need
|
|
serializedLen := 0
|
|
maxTempLen := 0
|
|
for headerName, headerValues := range h1Headers {
|
|
for _, headerValue := range headerValues {
|
|
nameLen := headerEncoding.EncodedLen(len(headerName))
|
|
valueLen := headerEncoding.EncodedLen(len(headerValue))
|
|
const delims = 2
|
|
serializedLen += delims + nameLen + valueLen
|
|
if nameLen > maxTempLen {
|
|
maxTempLen = nameLen
|
|
}
|
|
if valueLen > maxTempLen {
|
|
maxTempLen = valueLen
|
|
}
|
|
}
|
|
}
|
|
var buf strings.Builder
|
|
buf.Grow(serializedLen)
|
|
|
|
temp := make([]byte, maxTempLen)
|
|
writeB64 := func(s string) {
|
|
n := headerEncoding.EncodedLen(len(s))
|
|
if n > len(temp) {
|
|
temp = make([]byte, n)
|
|
}
|
|
headerEncoding.Encode(temp[:n], []byte(s))
|
|
buf.Write(temp[:n])
|
|
}
|
|
|
|
for headerName, headerValues := range h1Headers {
|
|
for _, headerValue := range headerValues {
|
|
if buf.Len() > 0 {
|
|
buf.WriteByte(';')
|
|
}
|
|
writeB64(headerName)
|
|
buf.WriteByte(':')
|
|
writeB64(headerValue)
|
|
}
|
|
}
|
|
|
|
return buf.String()
|
|
}
|
|
|
|
// Deserialize headers serialized by `SerializeHeader`
|
|
func DeserializeHeaders(serializedHeaders string) ([]h2mux.Header, error) {
|
|
const unableToDeserializeErr = "Unable to deserialize headers"
|
|
|
|
var deserialized []h2mux.Header
|
|
for _, serializedPair := range strings.Split(serializedHeaders, ";") {
|
|
if len(serializedPair) == 0 {
|
|
continue
|
|
}
|
|
|
|
serializedHeaderParts := strings.Split(serializedPair, ":")
|
|
if len(serializedHeaderParts) != 2 {
|
|
return nil, errors.New(unableToDeserializeErr)
|
|
}
|
|
|
|
serializedName := serializedHeaderParts[0]
|
|
serializedValue := serializedHeaderParts[1]
|
|
deserializedName := make([]byte, headerEncoding.DecodedLen(len(serializedName)))
|
|
deserializedValue := make([]byte, headerEncoding.DecodedLen(len(serializedValue)))
|
|
|
|
if _, err := headerEncoding.Decode(deserializedName, []byte(serializedName)); err != nil {
|
|
return nil, errors.Wrap(err, unableToDeserializeErr)
|
|
}
|
|
if _, err := headerEncoding.Decode(deserializedValue, []byte(serializedValue)); err != nil {
|
|
return nil, errors.Wrap(err, unableToDeserializeErr)
|
|
}
|
|
|
|
deserialized = append(deserialized, h2mux.Header{
|
|
Name: string(deserializedName),
|
|
Value: string(deserializedValue),
|
|
})
|
|
}
|
|
|
|
return deserialized, nil
|
|
}
|