//go:build !js
// +build !js

package websocket

import (
	
	
	
	
	
	
	
	
	
	
	
	

	
)

// DialOptions represents Dial's options.
type DialOptions struct {
	// HTTPClient is used for the connection.
	// Its Transport must return writable bodies for WebSocket handshakes.
	// http.Transport does beginning with Go 1.12.
	HTTPClient *http.Client

	// HTTPHeader specifies the HTTP headers included in the handshake request.
	HTTPHeader http.Header

	// Host optionally overrides the Host HTTP header to send. If empty, the value
	// of URL.Host will be used.
	Host string

	// Subprotocols lists the WebSocket subprotocols to negotiate with the server.
	Subprotocols []string

	// CompressionMode controls the compression mode.
	// Defaults to CompressionDisabled.
	//
	// See docs on CompressionMode for details.
	CompressionMode CompressionMode

	// CompressionThreshold controls the minimum size of a message before compression is applied.
	//
	// Defaults to 512 bytes for CompressionNoContextTakeover and 128 bytes
	// for CompressionContextTakeover.
	CompressionThreshold int
}

func ( *DialOptions) ( context.Context) (context.Context, context.CancelFunc, *DialOptions) {
	var  context.CancelFunc

	var  DialOptions
	if  != nil {
		 = *
	}
	if .HTTPClient == nil {
		.HTTPClient = http.DefaultClient
	}
	if .HTTPClient.Timeout > 0 {
		,  = context.WithTimeout(, .HTTPClient.Timeout)

		 := *.HTTPClient
		.Timeout = 0
		.HTTPClient = &
	}
	if .HTTPHeader == nil {
		.HTTPHeader = http.Header{}
	}
	 := *.HTTPClient
	 := .HTTPClient.CheckRedirect
	.CheckRedirect = func( *http.Request,  []*http.Request) error {
		switch .URL.Scheme {
		case "ws":
			.URL.Scheme = "http"
		case "wss":
			.URL.Scheme = "https"
		}
		if  != nil {
			return (, )
		}
		return nil
	}
	.HTTPClient = &

	return , , &
}

// Dial performs a WebSocket handshake on url.
//
// The response is the WebSocket handshake response from the server.
// You never need to close resp.Body yourself.
//
// If an error occurs, the returned response may be non nil.
// However, you can only read the first 1024 bytes of the body.
//
// This function requires at least Go 1.12 as it uses a new feature
// in net/http to perform WebSocket handshakes.
// See docs on the HTTPClient option and https://github.com/golang/go/issues/26937#issuecomment-415855861
//
// URLs with http/https schemes will work and are interpreted as ws/wss.
func ( context.Context,  string,  *DialOptions) (*Conn, *http.Response, error) {
	return dial(, , , nil)
}

func ( context.Context,  string,  *DialOptions,  io.Reader) ( *Conn,  *http.Response,  error) {
	defer errd.Wrap(&, "failed to WebSocket dial")

	var  context.CancelFunc
	, ,  = .cloneWithDefaults()
	if  != nil {
		defer ()
	}

	,  := secWebSocketKey()
	if  != nil {
		return nil, nil, fmt.Errorf("failed to generate Sec-WebSocket-Key: %w", )
	}

	var  *compressionOptions
	if .CompressionMode != CompressionDisabled {
		 = .CompressionMode.opts()
	}

	,  := handshakeRequest(, , , , )
	if  != nil {
		return nil, , 
	}
	 := .Body
	.Body = nil
	defer func() {
		if  != nil {
			// We read a bit of the body for easier debugging.
			 := io.LimitReader(, 1024)

			 := time.AfterFunc(time.Second*3, func() {
				.Close()
			})
			defer .Stop()

			,  := io.ReadAll()
			.Close()
			.Body = io.NopCloser(bytes.NewReader())
		}
	}()

	,  = verifyServerResponse(, , , )
	if  != nil {
		return nil, , 
	}

	,  := .(io.ReadWriteCloser)
	if ! {
		return nil, , fmt.Errorf("response body is not a io.ReadWriteCloser: %T", )
	}

	return newConn(connConfig{
		subprotocol:    .Header.Get("Sec-WebSocket-Protocol"),
		rwc:            ,
		client:         true,
		copts:          ,
		flateThreshold: .CompressionThreshold,
		br:             getBufioReader(),
		bw:             getBufioWriter(),
	}), , nil
}

func ( context.Context,  string,  *DialOptions,  *compressionOptions,  string) (*http.Response, error) {
	,  := url.Parse()
	if  != nil {
		return nil, fmt.Errorf("failed to parse url: %w", )
	}

	switch .Scheme {
	case "ws":
		.Scheme = "http"
	case "wss":
		.Scheme = "https"
	case "http", "https":
	default:
		return nil, fmt.Errorf("unexpected url scheme: %q", .Scheme)
	}

	,  := http.NewRequestWithContext(, "GET", .String(), nil)
	if  != nil {
		return nil, fmt.Errorf("failed to create new http request: %w", )
	}
	if len(.Host) > 0 {
		.Host = .Host
	}
	.Header = .HTTPHeader.Clone()
	.Header.Set("Connection", "Upgrade")
	.Header.Set("Upgrade", "websocket")
	.Header.Set("Sec-WebSocket-Version", "13")
	.Header.Set("Sec-WebSocket-Key", )
	if len(.Subprotocols) > 0 {
		.Header.Set("Sec-WebSocket-Protocol", strings.Join(.Subprotocols, ","))
	}
	if  != nil {
		.Header.Set("Sec-WebSocket-Extensions", .String())
	}

	,  := .HTTPClient.Do()
	if  != nil {
		return nil, fmt.Errorf("failed to send handshake request: %w", )
	}
	return , nil
}

func ( io.Reader) (string, error) {
	if  == nil {
		 = rand.Reader
	}
	 := make([]byte, 16)
	,  := io.ReadFull(, )
	if  != nil {
		return "", fmt.Errorf("failed to read random data from rand.Reader: %w", )
	}
	return base64.StdEncoding.EncodeToString(), nil
}

func ( *DialOptions,  *compressionOptions,  string,  *http.Response) (*compressionOptions, error) {
	if .StatusCode != http.StatusSwitchingProtocols {
		return nil, fmt.Errorf("expected handshake response status code %v but got %v", http.StatusSwitchingProtocols, .StatusCode)
	}

	if !headerContainsTokenIgnoreCase(.Header, "Connection", "Upgrade") {
		return nil, fmt.Errorf("WebSocket protocol violation: Connection header %q does not contain Upgrade", .Header.Get("Connection"))
	}

	if !headerContainsTokenIgnoreCase(.Header, "Upgrade", "WebSocket") {
		return nil, fmt.Errorf("WebSocket protocol violation: Upgrade header %q does not contain websocket", .Header.Get("Upgrade"))
	}

	if .Header.Get("Sec-WebSocket-Accept") != secWebSocketAccept() {
		return nil, fmt.Errorf("WebSocket protocol violation: invalid Sec-WebSocket-Accept %q, key %q",
			.Header.Get("Sec-WebSocket-Accept"),
			,
		)
	}

	 := verifySubprotocol(.Subprotocols, )
	if  != nil {
		return nil, 
	}

	return verifyServerExtensions(, .Header)
}

func ( []string,  *http.Response) error {
	 := .Header.Get("Sec-WebSocket-Protocol")
	if  == "" {
		return nil
	}

	for ,  := range  {
		if strings.EqualFold(, ) {
			return nil
		}
	}

	return fmt.Errorf("WebSocket protocol violation: unexpected Sec-WebSocket-Protocol from server: %q", )
}

func ( *compressionOptions,  http.Header) (*compressionOptions, error) {
	 := websocketExtensions()
	if len() == 0 {
		return nil, nil
	}

	 := [0]
	if .name != "permessage-deflate" || len() > 1 ||  == nil {
		return nil, fmt.Errorf("WebSocket protcol violation: unsupported extensions from server: %+v", [1:])
	}

	 := *
	 = &

	for ,  := range .params {
		switch  {
		case "client_no_context_takeover":
			.clientNoContextTakeover = true
			continue
		case "server_no_context_takeover":
			.serverNoContextTakeover = true
			continue
		}
		if strings.HasPrefix(, "server_max_window_bits=") {
			// We can't adjust the deflate window, but decoding with a larger window is acceptable.
			continue
		}

		return nil, fmt.Errorf("unsupported permessage-deflate parameter: %q", )
	}

	return , nil
}

var bufioReaderPool sync.Pool

func ( io.Reader) *bufio.Reader {
	,  := bufioReaderPool.Get().(*bufio.Reader)
	if ! {
		return bufio.NewReader()
	}
	.Reset()
	return 
}

func ( *bufio.Reader) {
	bufioReaderPool.Put()
}

var bufioWriterPool sync.Pool

func ( io.Writer) *bufio.Writer {
	,  := bufioWriterPool.Get().(*bufio.Writer)
	if ! {
		return bufio.NewWriter()
	}
	.Reset()
	return 
}

func ( *bufio.Writer) {
	bufioWriterPool.Put()
}