// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0

// Copyright 2014 go-dockerclient authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// The content is borrowed from Docker's own source code to provide a simple
// tls based dialer

package docker

import (
	"crypto/tls"
	"errors"
	"net"
	"strings"
	"time"
)

type tlsClientCon struct {
	*tls.Conn
	rawConn net.Conn
}

func (c *tlsClientCon) CloseWrite() error {
	// Go standard tls.Conn doesn't provide the CloseWrite() method so we do it
	// on its underlying connection.
	if cwc, ok := c.rawConn.(interface {
		CloseWrite() error
	}); ok {
		return cwc.CloseWrite()
	}
	return nil
}

func tlsDialWithDialer(dialer *net.Dialer, network, addr string, config *tls.Config) (net.Conn, error) {
	// We want the Timeout and Deadline values from dialer to cover the
	// whole process: TCP connection and TLS handshake. This means that we
	// also need to start our own timers now.
	timeout := dialer.Timeout

	if !dialer.Deadline.IsZero() {
		deadlineTimeout := dialer.Deadline.Sub(time.Now())
		if timeout == 0 || deadlineTimeout < timeout {
			timeout = deadlineTimeout
		}
	}

	var errChannel chan error

	if timeout != 0 {
		errChannel = make(chan error, 2)
		time.AfterFunc(timeout, func() {
			errChannel <- errors.New("")
		})
	}

	rawConn, err := dialer.Dial(network, addr)
	if err != nil {
		return nil, err
	}

	colonPos := strings.LastIndex(addr, ":")
	if colonPos == -1 {
		colonPos = len(addr)
	}
	hostname := addr[:colonPos]

	// If no ServerName is set, infer the ServerName
	// from the hostname we're connecting to.
	if config.ServerName == "" {
		// Make a copy to avoid polluting argument or default.
		config = copyTLSConfig(config)
		config.ServerName = hostname
	}

	conn := tls.Client(rawConn, config)

	if timeout == 0 {
		err = conn.Handshake()
	} else {
		go func() {
			errChannel <- conn.Handshake()
		}()

		err = <-errChannel
	}

	if err != nil {
		rawConn.Close()
		return nil, err
	}

	// This is Docker difference with standard's crypto/tls package: returned a
	// wrapper which holds both the TLS and raw connections.
	return &tlsClientCon{conn, rawConn}, nil
}

// this exists to silent an error message in go vet
func copyTLSConfig(cfg *tls.Config) *tls.Config {
	return &tls.Config{
		Certificates:             cfg.Certificates,
		CipherSuites:             cfg.CipherSuites,
		ClientAuth:               cfg.ClientAuth,
		ClientCAs:                cfg.ClientCAs,
		ClientSessionCache:       cfg.ClientSessionCache,
		CurvePreferences:         cfg.CurvePreferences,
		InsecureSkipVerify:       cfg.InsecureSkipVerify,
		MaxVersion:               cfg.MaxVersion,
		MinVersion:               cfg.MinVersion,
		NameToCertificate:        cfg.NameToCertificate,
		NextProtos:               cfg.NextProtos,
		PreferServerCipherSuites: cfg.PreferServerCipherSuites,
		Rand:                     cfg.Rand,
		RootCAs:                  cfg.RootCAs,
		ServerName:               cfg.ServerName,
		SessionTicketKey:         cfg.SessionTicketKey,
		SessionTicketsDisabled:   cfg.SessionTicketsDisabled,
	}
}
