package ws

import (
	
	
	
	
	
	
	
	
	
	
	
	

	
	
)

// Constants used by Dialer.
const (
	DefaultClientReadBufferSize  = 4096
	DefaultClientWriteBufferSize = 4096
)

// Handshake represents handshake result.
type Handshake struct {
	// Protocol is the subprotocol selected during handshake.
	Protocol string

	// Extensions is the list of negotiated extensions.
	Extensions []httphead.Option
}

// Errors used by the websocket client.
var (
	ErrHandshakeBadStatus      = fmt.Errorf("unexpected http status")
	ErrHandshakeBadSubProtocol = fmt.Errorf("unexpected protocol in %q header", headerSecProtocol)
	ErrHandshakeBadExtensions  = fmt.Errorf("unexpected extensions in %q header", headerSecProtocol)
)

// DefaultDialer is dialer that holds no options and is used by Dial function.
var DefaultDialer Dialer

// Dial is like Dialer{}.Dial().
func ( context.Context,  string) (net.Conn, *bufio.Reader, Handshake, error) {
	return DefaultDialer.Dial(, )
}

// Dialer contains options for establishing websocket connection to an url.
type Dialer struct {
	// ReadBufferSize and WriteBufferSize is an I/O buffer sizes.
	// They used to read and write http data while upgrading to WebSocket.
	// Allocated buffers are pooled with sync.Pool to avoid extra allocations.
	//
	// If a size is zero then default value is used.
	ReadBufferSize, WriteBufferSize int

	// Timeout is the maximum amount of time a Dial() will wait for a connect
	// and an handshake to complete.
	//
	// The default is no timeout.
	Timeout time.Duration

	// Protocols is the list of subprotocols that the client wants to speak,
	// ordered by preference.
	//
	// See https://tools.ietf.org/html/rfc6455#section-4.1
	Protocols []string

	// Extensions is the list of extensions that client wants to speak.
	//
	// Note that if server decides to use some of this extensions, Dial() will
	// return Handshake struct containing a slice of items, which are the
	// shallow copies of the items from this list. That is, internals of
	// Extensions items are shared during Dial().
	//
	// See https://tools.ietf.org/html/rfc6455#section-4.1
	// See https://tools.ietf.org/html/rfc6455#section-9.1
	Extensions []httphead.Option

	// Header is an optional HandshakeHeader instance that could be used to
	// write additional headers to the handshake request.
	//
	// It used instead of any key-value mappings to avoid allocations in user
	// land.
	Header HandshakeHeader

	// OnStatusError is the callback that will be called after receiving non
	// "101 Continue" HTTP response status. It receives an io.Reader object
	// representing server response bytes. That is, it gives ability to parse
	// HTTP response somehow (probably with http.ReadResponse call) and make a
	// decision of further logic.
	//
	// The arguments are only valid until the callback returns.
	OnStatusError func(status int, reason []byte, resp io.Reader)

	// OnHeader is the callback that will be called after successful parsing of
	// header, that is not used during WebSocket handshake procedure. That is,
	// it will be called with non-websocket headers, which could be relevant
	// for application-level logic.
	//
	// The arguments are only valid until the callback returns.
	//
	// Returned value could be used to prevent processing response.
	OnHeader func(key, value []byte) (err error)

	// NetDial is the function that is used to get plain tcp connection.
	// If it is not nil, then it is used instead of net.Dialer.
	NetDial func(ctx context.Context, network, addr string) (net.Conn, error)

	// TLSClient is the callback that will be called after successful dial with
	// received connection and its remote host name. If it is nil, then the
	// default tls.Client() will be used.
	// If it is not nil, then TLSConfig field is ignored.
	TLSClient func(conn net.Conn, hostname string) net.Conn

	// TLSConfig is passed to tls.Client() to start TLS over established
	// connection. If TLSClient is not nil, then it is ignored. If TLSConfig is
	// non-nil and its ServerName is empty, then for every Dial() it will be
	// cloned and appropriate ServerName will be set.
	TLSConfig *tls.Config

	// WrapConn is the optional callback that will be called when connection is
	// ready for an i/o. That is, it will be called after successful dial and
	// TLS initialization (for "wss" schemes). It may be helpful for different
	// user land purposes such as end to end encryption.
	//
	// Note that for debugging purposes of an http handshake (e.g. sent request
	// and received response), there is an wsutil.DebugDialer struct.
	WrapConn func(conn net.Conn) net.Conn
}

// Dial connects to the url host and upgrades connection to WebSocket.
//
// If server has sent frames right after successful handshake then returned
// buffer will be non-nil. In other cases buffer is always nil. For better
// memory efficiency received non-nil bufio.Reader should be returned to the
// inner pool with PutReader() function after use.
//
// Note that Dialer does not implement IDNA (RFC5895) logic as net/http does.
// If you want to dial non-ascii host name, take care of its name serialization
// avoiding bad request issues. For more info see net/http Request.Write()
// implementation, especially cleanHost() function.
func ( Dialer) ( context.Context,  string) ( net.Conn,  *bufio.Reader,  Handshake,  error) {
	,  := url.ParseRequestURI()
	if  != nil {
		return nil, nil, , 
	}

	// Prepare context to dial with. Initially it is the same as original, but
	// if d.Timeout is non-zero and points to time that is before ctx.Deadline,
	// we use more shorter context for dial.
	 := 

	var  time.Time
	if  := .Timeout;  != 0 {
		 = time.Now().Add()
		if ,  := .Deadline(); ! || .Before() {
			var  context.CancelFunc
			,  = context.WithDeadline(, )
			defer ()
		}
	}
	if ,  = .dial(, );  != nil {
		return , nil, , 
	}
	defer func() {
		if  != nil {
			.Close()
		}
	}()
	if  == context.Background() {
		// No need to start I/O interrupter goroutine which is not zero-cost.
		.SetDeadline()
		defer .SetDeadline(noDeadline)
	} else {
		// Context could be canceled or its deadline could be exceeded.
		// Start the interrupter goroutine to handle context cancelation.
		 := setupContextDeadliner(, )
		defer func() {
			// Map Upgrade() error to a possible context expiration error. That
			// is, even if Upgrade() err is nil, context could be already
			// expired and connection be "poisoned" by SetDeadline() call.
			// In that case we must not return ctx.Err() error.
			(&)
		}()
	}

	, ,  = .Upgrade(, )

	return , , , 
}

var (
	// netEmptyDialer is a net.Dialer without options, used in Dialer.dial() if
	// Dialer.NetDial is not provided.
	netEmptyDialer net.Dialer
	// tlsEmptyConfig is an empty tls.Config used as default one.
	tlsEmptyConfig tls.Config
)

func tlsDefaultConfig() *tls.Config {
	return &tlsEmptyConfig
}

func hostport(,  string) (,  string) {
	var (
		   = strings.LastIndexByte(, ':')
		 = strings.IndexByte(, ']')
	)
	if  >  {
		return [:], 
	}
	return ,  + 
}

func ( Dialer) ( context.Context,  *url.URL) ( net.Conn,  error) {
	 := .NetDial
	if  == nil {
		 = netEmptyDialer.DialContext
	}
	switch .Scheme {
	case "ws":
		,  := hostport(.Host, ":80")
		,  = (, "tcp", )
	case "wss":
		,  := hostport(.Host, ":443")
		,  = (, "tcp", )
		if  != nil {
			return nil, 
		}
		 := .TLSClient
		if  == nil {
			 = .tlsClient
		}
		 = (, )
	default:
		return nil, fmt.Errorf("unexpected websocket scheme: %q", .Scheme)
	}
	if  := .WrapConn;  != nil {
		 = ()
	}
	return , 
}

func ( Dialer) ( net.Conn,  string) net.Conn {
	 := .TLSConfig
	if  == nil {
		 = tlsDefaultConfig()
	}
	if .ServerName == "" {
		 = tlsCloneConfig()
		.ServerName = 
	}
	// Do not make conn.Handshake() here because downstairs we will prepare
	// i/o on this conn with proper context's timeout handling.
	return tls.Client(, )
}

var (
	// This variables are set like in net/net.go.
	// noDeadline is just zero value for readability.
	noDeadline = time.Time{}
	// aLongTimeAgo is a non-zero time, far in the past, used for immediate
	// cancelation of dials.
	aLongTimeAgo = time.Unix(42, 0)
)

// Upgrade writes an upgrade request to the given io.ReadWriter conn at given
// url u and reads a response from it.
//
// It is a caller responsibility to manage I/O deadlines on conn.
//
// It returns handshake info and some bytes which could be written by the peer
// right after response and be caught by us during buffered read.
func ( Dialer) ( io.ReadWriter,  *url.URL) ( *bufio.Reader,  Handshake,  error) {
	// headerSeen constants helps to report whether or not some header was seen
	// during reading request bytes.
	const (
		 = 1 << iota
		
		

		// headerSeenAll is the value that we expect to receive at the end of
		// headers read/parse loop.
		 = 0 |
			 |
			 |
			
	)

	 = pbufio.GetReader(,
		nonZero(.ReadBufferSize, DefaultClientReadBufferSize),
	)
	 := pbufio.GetWriter(,
		nonZero(.WriteBufferSize, DefaultClientWriteBufferSize),
	)
	defer func() {
		pbufio.PutWriter()
		if .Buffered() == 0 ||  != nil {
			// Server does not wrote additional bytes to the connection or
			// error occurred. That is, no reason to return buffer.
			pbufio.PutReader()
			 = nil
		}
	}()

	 := make([]byte, nonceSize)
	initNonce()

	httpWriteUpgradeRequest(, , , .Protocols, .Extensions, .Header)
	if  := .Flush();  != nil {
		return , , 
	}

	// Read HTTP status line like "HTTP/1.1 101 Switching Protocols".
	,  := readLine()
	if  != nil {
		return , , 
	}
	// Begin validation of the response.
	// See https://tools.ietf.org/html/rfc6455#section-4.2.2
	// Parse request line data like HTTP version, uri and method.
	,  := httpParseResponseLine()
	if  != nil {
		return , , 
	}
	// Even if RFC says "1.1 or higher" without mentioning the part of the
	// version, we apply it only to minor part.
	if .major != 1 || .minor < 1 {
		 = ErrHandshakeBadProtocol
		return , , 
	}
	if .status != http.StatusSwitchingProtocols {
		 = StatusError(.status)
		if  := .OnStatusError;  != nil {
			// Invoke callback with multireader of status-line bytes br.
			(.status, .reason,
				io.MultiReader(
					bytes.NewReader(),
					strings.NewReader(crlf),
					,
				),
			)
		}
		return , , 
	}
	// If response status is 101 then we expect all technical headers to be
	// valid. If not, then we stop processing response without giving user
	// ability to read non-technical headers. That is, we do not distinguish
	// technical errors (such as parsing error) and protocol errors.
	var  byte
	for {
		,  := readLine()
		if  != nil {
			 = 
			return , , 
		}
		if len() == 0 {
			// Blank line, no more lines to read.
			break
		}

		, ,  := httpParseHeaderLine()
		if ! {
			 = ErrMalformedResponse
			return , , 
		}

		switch btsToString() {
		case headerUpgradeCanonical:
			 |= 
			if !bytes.Equal(, specHeaderValueUpgrade) && !bytes.EqualFold(, specHeaderValueUpgrade) {
				 = ErrHandshakeBadUpgrade
				return , , 
			}

		case headerConnectionCanonical:
			 |= 
			// Note that as RFC6455 says:
			//   > A |Connection| header field with value "Upgrade".
			// That is, in server side, "Connection" header could contain
			// multiple token. But in response it must contains exactly one.
			if !bytes.Equal(, specHeaderValueConnection) && !bytes.EqualFold(, specHeaderValueConnection) {
				 = ErrHandshakeBadConnection
				return , , 
			}

		case headerSecAcceptCanonical:
			 |= 
			if !checkAcceptFromNonce(, ) {
				 = ErrHandshakeBadSecAccept
				return , , 
			}

		case headerSecProtocolCanonical:
			// RFC6455 1.3:
			//   "The server selects one or none of the acceptable protocols
			//   and echoes that value in its handshake to indicate that it has
			//   selected that protocol."
			for ,  := range .Protocols {
				if string() ==  {
					.Protocol = 
					break
				}
			}
			if .Protocol == "" {
				// Server echoed subprotocol that is not present in client
				// requested protocols.
				 = ErrHandshakeBadSubProtocol
				return , , 
			}

		case headerSecExtensionsCanonical:
			.Extensions,  = matchSelectedExtensions(, .Extensions, .Extensions)
			if  != nil {
				return , , 
			}

		default:
			if  := .OnHeader;  != nil {
				if  := (, );  != nil {
					 = 
					return , , 
				}
			}
		}
	}
	if  == nil &&  !=  {
		switch {
		case & == 0:
			 = ErrHandshakeBadUpgrade
		case & == 0:
			 = ErrHandshakeBadConnection
		case & == 0:
			 = ErrHandshakeBadSecAccept
		default:
			panic("unknown headers state")
		}
	}
	return , , 
}

// PutReader returns bufio.Reader instance to the inner reuse pool.
// It is useful in rare cases, when Dialer.Dial() returns non-nil buffer which
// contains unprocessed buffered data, that was sent by the server quickly
// right after handshake.
func ( *bufio.Reader) {
	pbufio.PutReader()
}

// StatusError contains an unexpected status-line code from the server.
type StatusError int

func ( StatusError) () string {
	return "unexpected HTTP response status: " + strconv.Itoa(int())
}

func isTimeoutError( error) bool {
	,  := .(net.Error)
	return  && .Timeout()
}

func matchSelectedExtensions( []byte, ,  []httphead.Option) ([]httphead.Option, error) {
	if len() == 0 {
		return , nil
	}
	var (
		  int
		 httphead.Option
		    error
	)
	 = -1
	 := func() ( bool) {
		for ,  := range  {
			// A server accepts one or more extensions by including a
			// |Sec-WebSocket-Extensions| header field containing one or more
			// extensions that were requested by the client.
			//
			// The interpretation of any extension parameters, and what
			// constitutes a valid response by a server to a requested set of
			// parameters by a client, will be defined by each such extension.
			if bytes.Equal(.Name, .Name) {
				// Check parsed extension to be present in client
				// requested extensions. We move matched extension
				// from client list to avoid allocation of httphead.Option.Name,
				// httphead.Option.Parameters have to be copied from the header
				.Parameters, _ = .Parameters.Copy(make([]byte, .Parameters.Size()))
				 = append(, )
				return true
			}
		}
		return false
	}
	 := httphead.ScanOptions(, func( int, , ,  []byte) httphead.Control {
		if  !=  {
			// Met next option.
			 = 
			if  != 0 && !() {
				// Server returned non-requested extension.
				 = ErrHandshakeBadExtensions
				return httphead.ControlBreak
			}
			 = httphead.Option{Name: }
		}
		if  != nil {
			.Parameters.Set(, )
		}
		return httphead.ControlContinue
	})
	if ! {
		 = ErrMalformedResponse
		return , 
	}
	if !() {
		return , ErrHandshakeBadExtensions
	}
	return , 
}

// setupContextDeadliner is a helper function that starts connection I/O
// interrupter goroutine.
//
// Started goroutine calls SetDeadline() with long time ago value when context
// become expired to make any I/O operations failed. It returns done function
// that stops started goroutine and maps error received from conn I/O methods
// to possible context expiration error.
//
// In concern with possible SetDeadline() call inside interrupter goroutine,
// caller passes pointer to its I/O error (even if it is nil) to done(&err).
// That is, even if I/O error is nil, context could be already expired and
// connection "poisoned" by SetDeadline() call. In that case done(&err) will
// store at *err ctx.Err() result. If err is caused not by timeout, it will
// leaved untouched.
func setupContextDeadliner( context.Context,  net.Conn) ( func(*error)) {
	var (
		      = make(chan struct{})
		 = make(chan error, 1)
	)
	go func() {
		select {
		case <-:
			 <- nil
		case <-.Done():
			// Cancel i/o immediately.
			.SetDeadline(aLongTimeAgo)
			 <- .Err()
		}
	}()
	return func( *error) {
		close()
		// If ctx.Err() is non-nil and the original err is net.Error with
		// Timeout() == true, then it means that I/O was canceled by us by
		// SetDeadline(aLongTimeAgo) call, or by somebody else previously
		// by conn.SetDeadline(x).
		//
		// Even on race condition when both deadlines are expired
		// (SetDeadline() made not by us and context's), we prefer ctx.Err() to
		// be returned.
		if  := <-;  != nil && (* == nil || isTimeoutError(*)) {
			* = 
		}
	}
}