package domainsniff

/*
	Domainsniff

	This package contain codes that perform project / domain specific behavior in Zoraxy
	If you want Zoraxy to handle a particular domain or open source project in a special way,
	you can add the checking logic here.

*/
import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"strings"
	"time"

	"imuslab.com/zoraxy/mod/utils"
)

// Check if the domain is reachable and return err if not reachable
func DomainReachableWithError(domain string) error {
	timeout := 1 * time.Second
	conn, err := net.DialTimeout("tcp", domain, timeout)
	if err != nil {
		return err
	}

	conn.Close()
	return nil
}

// Check if a domain have TLS but it is self-signed or expired
// Return false if sniff error
func DomainIsSelfSigned(domain string) bool {
	//Extract the domain from URl in case the user input the full URL
	host, port, err := net.SplitHostPort(domain)
	if err != nil {
		host = domain
	} else {
		domain = host + ":" + port
	}
	if !strings.Contains(domain, ":") {
		domain = domain + ":443"
	}

	//Get the certificate
	conn, err := net.Dial("tcp", domain)
	if err != nil {
		return false
	}
	defer conn.Close()

	//Connect with TLS using secure verify
	tlsConn := tls.Client(conn, nil)
	err = tlsConn.Handshake()
	if err == nil {
		//This is a valid certificate
		fmt.Println()
		return false
	}

	//Connect with TLS using insecure skip verify
	config := &tls.Config{
		InsecureSkipVerify: true,
	}
	tlsConn = tls.Client(conn, config)
	err = tlsConn.Handshake()
	//If the handshake is successful, this is a self-signed certificate
	return err == nil
}

// Check if domain reachable
func DomainReachable(domain string) bool {
	return DomainReachableWithError(domain) == nil
}

// Check if domain is served by a web server using HTTPS
func DomainUsesTLS(targetURL string) bool {
	//Check if the site support https
	httpsUrl := fmt.Sprintf("https://%s", targetURL)
	httpUrl := fmt.Sprintf("http://%s", targetURL)

	client := http.Client{Timeout: 5 * time.Second}

	resp, err := client.Head(httpsUrl)
	if err == nil && resp.StatusCode == http.StatusOK {
		return true
	}

	resp, err = client.Head(httpUrl)
	if err == nil && resp.StatusCode == http.StatusOK {
		return false
	}

	//If the site is not reachable, return false
	return false
}

/*
	WebSocket Header Sniff
*/

// Check if the requst is a special case where
// user defined header shall not be passed over

func RequireWebsocketHeaderCopy(r *http.Request) bool {
	//Return false for proxmox
	if IsProxmox(r) {
		return false
	}

	//Add more edge cases here
	return true
}

/*
	Request Handlers
*/
//Check if site support TLS
//Pass in ?selfsignchk=true to also check for self-signed certificate
func HandleCheckSiteSupportTLS(w http.ResponseWriter, r *http.Request) {
	targetURL, err := utils.PostPara(r, "url")
	if err != nil {
		utils.SendErrorResponse(w, "invalid url given")
		return
	}

	//If the selfsign flag is set, also chec for self-signed certificate
	_, err = utils.PostBool(r, "selfsignchk")
	if err == nil {
		//Return the https and selfsign status
		type result struct {
			Protocol string `json:"protocol"`
			SelfSign bool   `json:"selfsign"`
		}

		scanResult := result{Protocol: "http", SelfSign: false}

		if DomainUsesTLS(targetURL) {
			scanResult.Protocol = "https"
			if DomainIsSelfSigned(targetURL) {
				scanResult.SelfSign = true
			}
		}

		js, _ := json.Marshal(scanResult)
		utils.SendJSONResponse(w, string(js))
		return
	}

	if DomainUsesTLS(targetURL) {
		js, _ := json.Marshal("https")
		utils.SendJSONResponse(w, string(js))
		return
	} else {
		js, _ := json.Marshal("http")
		utils.SendJSONResponse(w, string(js))
		return
	}
}
